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 Christian Beikov
parent 27c3b34dcf
commit 7b009640c1
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: They share some common annotation members:
.Association-defining annotation members .Association-defining annotation members
[cols="14,~,35"] [cols="13,~,35"]
|=== |===
| Member | Interpretation | Default value | Member | Interpretation | Default value
| `cascade` | Persistence operations which should <<cascade,cascade>> to the associated entity; a list of ``CascadeType``s | `{}` | `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| a|
- `LAZY` for `@OneToMany` and `@ManyToMany` - `LAZY` for `@OneToMany` and `@ManyToMany`
- `EAGER` for `@ManyToOne` 💀💀💀 - `EAGER` for `@ManyToOne` 💀💀💀
| `targetEntity` | The associated entity class | Determined from the attribute type declaration | `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 | `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. 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. 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] [TIP]
// .One-to-many join table mappings // .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>>. 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] [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`. 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] [source,java]
---- ----
@ -1024,6 +1029,16 @@ class Publisher {
The `Publisher.books` field is called the _unowned_ side of the association. 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] [WARNING]
// .To modify a bidirectional association, you must change the _owning side_! // .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] [source,java]
---- ----
@OneToMany(mappedBy="publisher") @OneToMany(mappedBy=Book_.PUBLISHER)
Collection<Book> books; Collection<Book> books;
---- ----
@ -1119,7 +1134,7 @@ class Person {
@Id @GeneratedValue @Id @GeneratedValue
Long id; Long id;
@OneToOne(mappedBy = "person") @OneToOne(mappedBy = Author_.PERSON)
Author author; Author author;
... ...
@ -1142,7 +1157,7 @@ On the other hand, if _every_ `Person` was an `Author`, that is, if the associat
[source,java] [source,java]
---- ----
@OneToOne(optional=false, mappedBy = "person", fetch=LAZY) @OneToOne(optional=false, mappedBy = Author_.PERSON, fetch=LAZY)
Author author; 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. 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. 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]]
=== Many-to-many === Many-to-many
@ -1215,7 +1230,7 @@ class Book {
@Id @GeneratedValue @Id @GeneratedValue
Long id; Long id;
@ManyToMany(mappedBy="authors") @ManyToMany(mappedBy=Author_.BOOKS)
Set<Author> authors; 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 @Entity
class Order { class Order {
... ...
@OneToMany(mappedby="order", @OneToMany(mappedby=Item_.ORDER,
// cascade persist(), remove(), and refresh() from Order to Item // cascade persist(), remove(), and refresh() from Order to Item
cascade={PERSIST,REMOVE,REFRESH}, cascade={PERSIST,REMOVE,REFRESH},
// also remove() orphaned Items // also remove() orphaned Items

View File

@ -211,7 +211,7 @@ class Book { ... }
The `@Table` annotation can do more than just specify a name: The `@Table` annotation can do more than just specify a name:
.`@Table` annotation members .`@Table` annotation members
[%autowidth.stretch] [cols="20,~"]
|=== |===
| Annotation member | Purpose | Annotation member | Purpose
@ -237,7 +237,7 @@ Instead:
The `@SecondaryTable` annotation is even more interesting: The `@SecondaryTable` annotation is even more interesting:
.`@SecondaryTable` annotation members .`@SecondaryTable` annotation members
[%autowidth.stretch] [cols="20,~"]
|=== |===
| Annotation member | Purpose | Annotation member | Purpose
@ -299,7 +299,7 @@ class Author {
Here, there should be a `UNIQUE` constraint on _both_ columns of the association table. Here, there should be a `UNIQUE` constraint on _both_ columns of the association table.
.`@JoinTable` annotation members .`@JoinTable` annotation members
[%autowidth.stretch] [cols="20,~"]
|=== |===
| Annotation member | Purpose | 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. The `@Column` annotation is not only useful for specifying the column name.
.`@Column` annotation members .`@Column` annotation members
[%autowidth.stretch] [cols="20,~"]
|=== |===
| Annotation member | Purpose | 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. The `@JoinColumn` annotation is used to customize a foreign key column.
.`@JoinColumn` annotation members .`@JoinColumn` annotation members
[%autowidth.stretch] [cols="20,~"]
|=== |===
| Annotation member | Purpose | 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. - 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 .`@PrimaryKeyJoinColumn` annotation members
[%autowidth.stretch] [cols="20,~"]
|=== |===
| Annotation member | Purpose | Annotation member | Purpose

View File

@ -272,7 +272,7 @@ class Publisher {
... ...
@Cache(usage=READ_WRITE, region="PublishedBooks") @Cache(usage=READ_WRITE, region="PublishedBooks")
@OneToMany(mappedBy="publisher") @OneToMany(mappedBy=Book_.PUBLISHER)
Set<Book> books; 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 - `bySimpleNaturalId()` if just one attribute is annotation `@NaturalId`, or
- `byNaturalId()` if multiple attributes are annotated `@NaturalId`. - `byNaturalId()` if multiple attributes are annotated `@NaturalId`.
Here's how we can retrieve an entity by its composite natural id:
[source,java] [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] [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`. 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`.