one to one

This commit is contained in:
Gavin 2023-05-09 19:33:04 +02:00 committed by Gavin King
parent f012afe95f
commit 33a3af7abc
1 changed files with 108 additions and 3 deletions

View File

@ -34,6 +34,12 @@ An entity must:
On the other hand, the entity class may be either concrete or `abstract`, and it may have any number of additional constructors. On the other hand, the entity class may be either concrete or `abstract`, and it may have any number of additional constructors.
[TIP]
.Inner entity classes
====
An entity class may be a `static` inner class.
====
Every entity class must be annotated `@Entity`. Every entity class must be annotated `@Entity`.
[source,java] [source,java]
@ -694,19 +700,32 @@ The `@ManyToOne` annotation marks the "one" side of the association, and so a un
[source,java] [source,java]
---- ----
class Book { class Book {
... @Id @GeneratedValue
@ManyToOne Long id;
@ManyToOne(fetch=LAZY)
Publisher publisher; Publisher publisher;
... ...
} }
---- ----
[TIP]
.Almost all associations should be lazy
====
A very unfortunate misfeature of JPA is that `@ManyToOne` associations are fetched eagerly by default.
This is almost never what we want.
The only scenario in which `fetch=EAGER` makes sense is if we think there's always a _very_ high probability that the associated object will be found in the second-level cache.
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`. 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`.
[source,java] [source,java]
---- ----
class Publisher { class Publisher {
... @Id @GeneratedValue
Long id;
@OneToMany(mappedBy="publisher") @OneToMany(mappedBy="publisher")
Set<Book> books; Set<Book> books;
... ...
@ -761,6 +780,92 @@ Now? I guess we're happy to let you guys decide.
In hindsight, we could have done more to make clear that this was always a viable option. In hindsight, we could have done more to make clear that this was always a viable option.
==== ====
[[one-to-one-fk]]
=== One to one (first way)
The simplest sort of one to one association is almost exactly line a `@ManyToOne` association, except that it maps to a foreign key column with a `UNIQUE` constraint.
[source,java]
----
@Entity
static class Author {
@Id @GeneratedValue
Long id;
@OneToOne(optional=false, fetch=LAZY)
Person author;
...
}
----
We can make this association bidirectional by adding a reference back to the `Author` in the `Person` entity:
[source,java]
----
@Entity
static class Person {
@Id @GeneratedValue
Long id;
@OneToOne(mappedBy = "person")
Author author;
...
}
----
[NOTE]
.Lazy fetching for one to one associations
====
Notice that we did not declare the unowned end of the association `fetch=LAZY`.
That's because:
1. not every `Person` has an associated `Author`, and
2. the foreign key is held in the table mapped by `Author`, not in the table mapped by `Person`.
Therefore, Hibernate can't tell if the reference from `Person` to `Author` is `null` without fetching the associated `Author`.
On the other hand, if _every_ `Person` was an `Author`, that is, if the association were non-`optional`, we would not have to consider the possibility of `null` references, and we would map it like this:
[source,java]
----
@OneToOne(optional=false, mappedBy = "person", fetch=LAZY)
Author author;
----
====
This is not the only sort of one to one association.
[[one-to-one-pk]]
=== One to one (second way)
An arguably more elegant way to represent such a relationship is to share a primary key between the two tables.
That is, the foreign key would be the primary key.
To use this approach, the `Author` class must be annotated like this:
[source,java]
----
@Entity
static class Author {
@Id
Long id;
@OneToOne(optional=false, fetch=LAZY)
@MapsId
Person author;
...
}
----
Notice that the `@Id` attribute is no longer a `@GeneratedValue` and, instead, the `author` association is annotated `@MapsId`.
This lets Hibernate know that the association to `Person` is the source of primary key values for `Author`.
That is, that the foreign key column referring to the `Author` table is also the primary key of the `Person` table.
The `Person` class does not change.
If the association is bidirectional, we annotate the unowned side `@OneToOne(mappedBy = "person")` just as before.
[[equals-and-hash]] [[equals-and-hash]]