show off typesafety with the metamodel and new API of NaturalIdLoadAccess

This commit is contained in:
Gavin 2023-05-24 23:32:05 +02:00 committed by Gavin King
parent 76fa597d1b
commit f881c5243f
4 changed files with 42 additions and 19 deletions

View File

@ -951,17 +951,17 @@ There are three annotations for mapping associations: `@ManyToOne`, `@OneToMany`
They share some common annotation members:
.Association-defining annotation members
[cols="14,~,35"]
[cols="13,~,35"]
|===
| Member | Interpretation | Default value
| `cascade` | Persistence operations which should <<cascade,cascade>> to the associated entity; a list of ``CascadeType``s | `{}`
| `fetch` | Whether the association is eagerly <<association-fetching,fetched>> or may be <<proxies-and-lazy-fetching,proxied>>
| `fetch` | Whether the association is <<entity-graph,eagerly>> <<association-fetching,fetched>> or may be <<proxies-and-lazy-fetching,proxied>>
a|
- `LAZY` for `@OneToMany` and `@ManyToMany`
- `EAGER` for `@ManyToOne` 💀💀💀
| `targetEntity` | The associated entity class | Determined from the attribute type declaration
| `optional` | For `@ManyToOne` or `@OneToOne` associations, whether the association can be `null` | `true`
| `optional` | For a `@ManyToOne` or `@OneToOne` association, whether the association can be `null` | `true`
| `mappedBy` | For a bidirectional association, an attribute of the associated entity which maps the association | By default, the association is assumed unidirectional
|===
@ -974,6 +974,7 @@ Let's begin with the most common association multiplicity.
A many-to-one association is the most basic sort of association we can imagine.
It maps completely naturally to a foreign key in the database.
Almost all the associations in your domain model are going to be of this form.
[TIP]
// .One-to-many join table mappings
@ -981,7 +982,7 @@ It maps completely naturally to a foreign key in the database.
Later, we'll see how to map a many-to-one association to an <<join-table-mappings,association table>>.
====
The `@ManyToOne` annotation marks the "one" side of the association, and so a unidirectional many-to-one association looks like this:
The `@ManyToOne` annotation marks the "to one" side of the association, and so a unidirectional many-to-one association looks like this:
[source,java]
----
@ -1007,7 +1008,11 @@ The only scenario in which `fetch=EAGER` makes sense is if we think there's alwa
Whenever this isn't the case, remember to explicitly specify `fetch=LAZY`.
====
To make this association bidirectional, we need to add a collection-valued attribute to the `Publisher` class, and annotate it `@OneToMany`, using the `mappedBy` member to refer back to `Book.publisher`.
Most of the time, we would like to be able to easily navigate our associations in both directions.
We do need a way to get the `Publisher` of a given `Book`, but we would also like to be able to obtain all the ``Book``s belonging to a given publisher.
To make this association bidirectional, we need to add a collection-valued attribute to the `Publisher` class, and annotate it `@OneToMany`.
To indicate clearly that this is a bidirectional association, and to reuse any mapping information already specified in the `Book` entity, we must use the `mappedBy` annotation member to refer back to `Book.publisher`.
[source,java]
----
@ -1024,6 +1029,16 @@ class Publisher {
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:
[source,java]
----
@OneToMany(mappedBy=Book_.PUBLISHER) // get used to doing it this way!
Set<Book> books;
----
We're going to use this approach for the rest of the Introduction.
[WARNING]
// .To modify a bidirectional association, you must change the _owning side_!
====
@ -1052,7 +1067,7 @@ In particular, the `List` may not contain duplicate elements, and its order will
[source,java]
----
@OneToMany(mappedBy="publisher")
@OneToMany(mappedBy=Book_.PUBLISHER)
Collection<Book> books;
----
@ -1119,7 +1134,7 @@ class Person {
@Id @GeneratedValue
Long id;
@OneToOne(mappedBy = "person")
@OneToOne(mappedBy = Author_.PERSON)
Author author;
...
@ -1142,7 +1157,7 @@ On the other hand, if _every_ `Person` was an `Author`, that is, if the associat
[source,java]
----
@OneToOne(optional=false, mappedBy = "person", fetch=LAZY)
@OneToOne(optional=false, mappedBy = Author_.PERSON, fetch=LAZY)
Author author;
----
****
@ -1182,7 +1197,7 @@ Here, there's no extra foreign key column in the `Author` table, since the `id`
That is, the primary key of the `Author` table does double duty as the foreign key referring to the `Person` table.
The `Person` class doesn't change.
If the association is bidirectional, we annotate the unowned side `@OneToOne(mappedBy = "person")` just as before.
If the association is bidirectional, we annotate the unowned side `@OneToOne(mappedBy = Author_.PERSON)` just as before.
[[many-to-many]]
=== Many-to-many
@ -1215,7 +1230,7 @@ class Book {
@Id @GeneratedValue
Long id;
@ManyToMany(mappedBy="authors")
@ManyToMany(mappedBy=Author_.BOOKS)
Set<Author> authors;
...

View File

@ -293,7 +293,7 @@ To set up cascading, we specify the `cascade` member of one of the association m
@Entity
class Order {
...
@OneToMany(mappedby="order",
@OneToMany(mappedby=Item_.ORDER,
// cascade persist(), remove(), and refresh() from Order to Item
cascade={PERSIST,REMOVE,REFRESH},
// also remove() orphaned Items

View File

@ -211,7 +211,7 @@ class Book { ... }
The `@Table` annotation can do more than just specify a name:
.`@Table` annotation members
[%autowidth.stretch]
[cols="20,~"]
|===
| Annotation member | Purpose
@ -237,7 +237,7 @@ Instead:
The `@SecondaryTable` annotation is even more interesting:
.`@SecondaryTable` annotation members
[%autowidth.stretch]
[cols="20,~"]
|===
| Annotation member | Purpose
@ -299,7 +299,7 @@ class Author {
Here, there should be a `UNIQUE` constraint on _both_ columns of the association table.
.`@JoinTable` annotation members
[%autowidth.stretch]
[cols="20,~"]
|===
| Annotation member | Purpose
@ -341,7 +341,7 @@ We use the `@Column` annotation to map basic attributes.
The `@Column` annotation is not only useful for specifying the column name.
.`@Column` annotation members
[%autowidth.stretch]
[cols="20,~"]
|===
| Annotation member | Purpose
@ -395,7 +395,7 @@ We don't use `@Column` to map associations.
The `@JoinColumn` annotation is used to customize a foreign key column.
.`@JoinColumn` annotation members
[%autowidth.stretch]
[cols="20,~"]
|===
| Annotation member | Purpose
@ -508,7 +508,7 @@ The `@PrimaryKeyJoinColumn` is a special-purpose annotation for mapping:
- the primary key column of the primary table mapped by a subclass in a `JOINED` inheritance hierarchy—which is also a foreign key referencing the primary table mapped by the root entity.
.`@PrimaryKeyJoinColumn` annotation members
[%autowidth.stretch]
[cols="20,~"]
|===
| Annotation member | Purpose

View File

@ -272,7 +272,7 @@ class Publisher {
...
@Cache(usage=READ_WRITE, region="PublishedBooks")
@OneToMany(mappedBy="publisher")
@OneToMany(mappedBy=Book_.PUBLISHER)
Set<Book> books;
...
@ -363,11 +363,19 @@ This cache is utilized when the entity is retrieved using one of the operations
- `bySimpleNaturalId()` if just one attribute is annotation `@NaturalId`, or
- `byNaturalId()` if multiple attributes are annotated `@NaturalId`.
Here's how we can retrieve an entity by its composite natural id:
[source,java]
----
Book book = session.byNaturalId().using("isbn", isbn, "printing", printing).load();
Book book =
session.byNaturalId(Book.class)
.using(Book_.isbn, isbn)
.using(Book_.printing, printing)
.load();
----
Notice that this code fragment is completely typesafe.
[NOTE]
====
Since the natural id cache doesn't contain the actual state of the entity, it doesn't make sense to annotate an entity `@NaturalIdCache` unless it's already eligible for storage in the second-level cache, that is, unless it's also annotated `@Cache`.