new section on Derived Identity

This commit is contained in:
Gavin 2023-09-07 14:00:13 +02:00 committed by Gavin King
parent c014abb0d5
commit e099b06c6d
3 changed files with 151 additions and 4 deletions

View File

@ -974,7 +974,7 @@ class Book {
@FetchProfileOverride(profile = Book_.PROFILE_EAGER_BOOK, mode = JOIN)
Set<Author> authors;
...
...
}
----
[source,java]
@ -1013,7 +1013,7 @@ class Book {
mode = SUBSELECT)
Set<Author> authors;
...
...
}
----
@ -1045,6 +1045,7 @@ But Hibernate offers alternatives that we think are more compelling most of the
The one and only advantage unique to fetch profiles is that they let us very selectively request subselect fetching.
We can't do that with entity graphs, and we can't do it with HQL.
[%unbreakable]
[TIP]
====
There's a special built-in fetch profile named `org.hibernate.defaultProfile` which is defined as the profile with `@FetchProfileOverride(mode=JOIN)` applied to every eager `@ManyToOne` or `@OneToOne` association.

View File

@ -1047,6 +1047,8 @@ The `Publisher.books` field is called the _unowned_ side of the association.
Now, we passionately _hate_ the stringly-typed `mappedBy` reference to the owning side of the association.
Thankfully, the <<metamodel-generator, Metamodel Generator>> gives us a way to make it a
bit more typesafe:
[[mapped-by-metamodel]]
[source,java]
----
@OneToMany(mappedBy=Book_.PUBLISHER) // get used to doing it this way!

View File

@ -174,7 +174,7 @@ is a bad idea, since it's impossible to create a foreign key constraint that tar
The following annotations specify exactly how elements of the domain model map to tables of the relational model:
.Annotations for mapping tables
[%autowidth.stretch]
[%breakable,cols="25,~"]
|===
| Annotation | Purpose
@ -334,7 +334,7 @@ To better understand these annotations, we must first discuss column mappings in
These annotations specify how elements of the domain model map to columns of tables in the relational model:
.Annotations for mapping columns
[%autowidth.stretch]
[%breakable,cols="25,~"]
|===
| Annotation | Purpose
@ -836,6 +836,150 @@ class Order {
}
----
[[derived-identity]]
=== Derived Identity
An entity has a _derived identity_ if it inherits part of its primary key from an associated "parent" entity.
We've already met a kind of degenerate case of _derived identity_ when we talked about <<one-to-one-pk,one-to-one associations with a shared primary key>>.
But a `@ManyToOne` association may also form part of a derived identity.
That is to say, there could be a foreign key column or columns included as part of the composite primary key.
There's three different ways to represent this situation on the Java side of things:
- using `@IdClass` without `@MapsId`,
- using `@IdClass` with `@MapsId`, or
- using `@EmbeddedId` with `@MapsId`.
Let's suppose we have a `Parent` entity class defined as follows:
[source,java]
----
@Entity
class Parent {
@Id
Long parentId;
...
}
----
[discrete]
==== First way
In the first, slightly simpler approach, we define an `@IdClass`:
[source,java]
----
class DerivedId {
Long parent;
String childId;
// constructors, equals, hashcode, etc
...
}
----
And a `Child` entity class with a `@ManyToOne` association annotated `@Id`:
[source,java]
----
@Entity
@IdClass(DerivedId.class)
class Child {
@Id
String childId;
@Id @ManyToOne
@JoinColumn(name="parentId")
Parent parent;
...
}
----
Then the primary key of the `Child` table comprises the columns `(childId,parentId)`.
[discrete]
==== Second way
This is fine, but sometimes it's nice to have a field for each element of the primary key.
We may use the `@MapsId` annotation we met <<one-to-one-pk,earlier>>:
[source,java]
----
@Entity
@IdClass(DerivedId.class)
class Child {
@Id
Long parentId;
@Id
String childId;
@ManyToOne
@MapsId(Child_.PARENT_ID) // typesafe reference to Child.parentId
@JoinColumn(name="parentId")
Parent parent;
...
}
----
We're using the approach we saw <<mapped-by-metamodel,previously>> to refer to the `parentId` property of `Child` in a typesafe way.
Note that we must place column mapping information on the association annotated `@MapsId`, not on the `@Id` field.
We must slightly modify our `@IdClass` so that field names align:
[source,java]
----
class DerivedId {
Long parentId;
String childId;
// constructors, equals, hashcode, etc
...
}
----
[discrete]
==== Third way
The third alternative is to redefine our `@IdClass` as an `@Embeddable`.
We don't actually need to change the `DerivedId` class, but we do need to add the annotation.
[source,java]
----
@Embeddable
class DerivedId {
Long parentId;
String childId;
// constructors, equals, hashcode, etc
...
}
----
Then we may use `@EmbeddedId` in `Child`:
[source,java]
----
@Entity
class Child {
@EmbeddedId
DerivedId id;
@ManyToOne
@MapsId(DerivedId_.PARENT_ID) // typesafe reference to DerivedId.parentId
@JoinColumn(name="parentId")
Parent parent;
...
}
----
The <<composite-identifiers,choice>> between `@IdClass` and `@EmbeddedId` boils down to taste.
The `@EmbeddedId` is perhaps a little DRYer.
[[constraints]]
=== Adding constraints