association table mappings

This commit is contained in:
Gavin 2023-05-11 11:03:20 +02:00 committed by Christian Beikov
parent 0ed12f6869
commit 218a58ebbc
2 changed files with 125 additions and 18 deletions

View File

@ -716,7 +716,7 @@ For example, `Types.VARCHAR` represents the SQL type `VARCHAR` (or `VARCHAR2` on
Since Hibernate understand more SQL types than JDBC, there's an extended list of integer type codes in the class `org.hibernate.type.SqlTypes`. Since Hibernate understand more SQL types than JDBC, there's an extended list of integer type codes in the class `org.hibernate.type.SqlTypes`.
==== ====
If a given `JavaType` does not know how to convert its instances to the type required by its partner `JdbcType`, we must help it out by providing a JPA `AttributeConverter` to perform the conversion. If a given `JavaType` doesn't know how to convert its instances to the type required by its partner `JdbcType`, we must help it out by providing a JPA `AttributeConverter` to perform the conversion.
For example, to form a basic type using `LongJavaType` and `TimestampJdbcType`, we would provide an `AttributeConverter<Long,Timestamp>`. For example, to form a basic type using `LongJavaType` and `TimestampJdbcType`, we would provide an `AttributeConverter<Long,Timestamp>`.
@ -732,7 +732,7 @@ Let's abandon our analogy right here, before we start calling this basic type a
[[embeddable-objects]] [[embeddable-objects]]
=== Embeddable objects === Embeddable objects
An embeddable object is a Java class whose state maps to multiple columns of a table, but which does not itself have a persistent identity. An embeddable object is a Java class whose state maps to multiple columns of a table, but which doesn't itself have a persistent identity.
That is, it's a class with mapped attributes, but no `@Id` attribute. That is, it's a class with mapped attributes, but no `@Id` attribute.
An embeddable object can only be made persistent by assigning it to the attribute of an entity. An embeddable object can only be made persistent by assigning it to the attribute of an entity.
@ -833,6 +833,12 @@ 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.
[TIP]
.One-to-many join table mappings
====
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 "one" side of the association, and so a unidirectional many-to-one association looks like this:
[source,java] [source,java]
@ -847,6 +853,8 @@ class Book {
} }
---- ----
Here, the `Book` table has a foreign key column holding the identifier of the associated `Publisher`.
[TIP] [TIP]
.Almost all associations should be lazy .Almost all associations should be lazy
==== ====
@ -889,7 +897,7 @@ That said, it's not a hard requirement to update the unowned side, at least if y
.Unidirectional `@OneToMany`? .Unidirectional `@OneToMany`?
==== ====
In principle Hibernate _does_ allow you to have a unidirectional one to many, that is, a `@OneToMany` with no matching `@ManyToOne` on the other side. In principle Hibernate _does_ allow you to have a unidirectional one to many, that is, a `@OneToMany` with no matching `@ManyToOne` on the other side.
In practice, this mapping is unnatural, and does not work very well. In practice, this mapping is unnatural, and just doesn't work very well.
Avoid it. Avoid it.
==== ====
@ -925,6 +933,12 @@ In hindsight, we could have done more to make clear that this was always a viabl
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. 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.
[TIP]
.One-to-many join table mappings
====
Later, we'll see how to map a many-to-one association to an <<join-table-mappings,association table>>.
====
A one-to-one association must be annotated `@OneToOne`: A one-to-one association must be annotated `@OneToOne`:
[source,java] [source,java]
@ -941,6 +955,8 @@ class Author {
} }
---- ----
Here, the `Author` table has a foreign key column holding the identifier of the associated `Publisher`.
[TIP] [TIP]
.One-to-one associations are a way to represent subtyping .One-to-one associations are a way to represent subtyping
==== ====
@ -1011,21 +1027,24 @@ class Author {
Notice that the `@Id` attribute is no longer a `@GeneratedValue` and, instead, the `author` association is annotated `@MapsId`. 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`. 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. Here, there's no extra foreign key column in the `Author` table, since the `id` column holds the identifier of `Person`.
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 = "person")` just as before.
[[many-to-many]] [[many-to-many]]
=== Many-to-many === Many-to-many
A unidirectional many-to-many association is represented as a collection-valued attribute. A unidirectional many-to-many association is represented as a collection-valued attribute.
It maps to a separate _association table_ in the database. It always maps to a separate _association table_ in the database.
A many-to-many association must be annotated `@ManyToMany`: A many-to-many association must be annotated `@ManyToMany`:
[source,java] [source,java]
---- ----
@Entity
class Book { class Book {
@Id @GeneratedValue @Id @GeneratedValue
Long id; Long id;
@ -1041,6 +1060,7 @@ If the association is bidirectional, we add a very similar-looking attribute to
[source,java] [source,java]
---- ----
@Entity
class Book { class Book {
@Id @GeneratedValue @Id @GeneratedValue
Long id; Long id;
@ -1130,7 +1150,7 @@ And from this point of view, SQL arrays look quite attractive, at least for cert
If we're comfortable mapping `byte[]` to `VARBINARY(255)`, why would we shy away from mapping `DayOfWeek[]` to `TINYINT ARRAY[7]`? If we're comfortable mapping `byte[]` to `VARBINARY(255)`, why would we shy away from mapping `DayOfWeek[]` to `TINYINT ARRAY[7]`?
==== ====
Unfortunately, JPA does not define a standard way to map SQL arrays, but here's how we can do it in Hibernate: Unfortunately, JPA doesn't define a standard way to map SQL arrays, but here's how we can do it in Hibernate:
[source, java] [source, java]
---- ----

View File

@ -151,7 +151,7 @@ is a bad idea, since it's impossible to create a foreign key constraint that tar
// However, it's possible to emulate a mix of `SINGLE_TABLE` and `JOINED` inheritance using the `@SecondaryTable` annotation. // However, it's possible to emulate a mix of `SINGLE_TABLE` and `JOINED` inheritance using the `@SecondaryTable` annotation.
[[table-mappings]] [[table-mappings]]
=== Mapping entities to tables === Mapping to tables
The following annotations specify exactly how elements of the domain model map to tables of the relational model: The following annotations specify exactly how elements of the domain model map to tables of the relational model:
@ -162,10 +162,15 @@ The following annotations specify exactly how elements of the domain model map t
| `@Table` | Map an entity class to its primary table | `@Table` | Map an entity class to its primary table
| `@SecondaryTable` | Define a secondary table for an entity class | `@SecondaryTable` | Define a secondary table for an entity class
| `@JoinTable` | Map a many-to-many association to its association table | `@JoinTable` | Map a many-to-many or many-to-one association to its association table
| `@CollectionTable` | Map an `@ElementCollection` to its table | `@CollectionTable` | Map an `@ElementCollection` to its table
|=== |===
The first two annotations are used to map an entity to its _primary table_ and, optionally, one or more _secondary tables_.
[[entity-table-mappings]]
=== Mapping entities to tables
By default, an entity maps to a single table, which may be specified using `@Table`: By default, an entity maps to a single table, which may be specified using `@Table`:
[source,java] [source,java]
@ -200,12 +205,16 @@ The `@Table` annotation can do more than just specify a name:
|=== |===
[TIP] [TIP]
.Don't hardcode the schema and catalog .If you don't need to, don't hardcode the schema and catalog
====
It's very often a bad idea to hardcode the schema and catalog in a `@Table` annotation.
It's usually better to set the configuration properties `hibernate.default_schema` and `hibernate.default_catalog`, or simply ensure that your JDBC connection URL specifies the schema and catalog.
==== ====
It only makes sense to explicitly specify the `schema` in annotations if the domain model is spread across multiple schemas.
Otherwise, it's a bad idea to hardcode the schema (or catalog) in a `@Table` annotation.
Instead:
- set the configuration property `hibernate.default_schema` (or `hibernate.default_catalog`), or
- simply specify the schema in the JDBC connection URL.
====
The `@SecondaryTable` annotation is even more interesting: The `@SecondaryTable` annotation is even more interesting:
@ -223,7 +232,67 @@ The `@SecondaryTable` annotation is even more interesting:
| `foreignKey` | An `@ForeignKey` annotation specifying the name of the `FOREIGN KEY` constraint on the ``@PrimaryKeyJoinColumn``s | `foreignKey` | An `@ForeignKey` annotation specifying the name of the `FOREIGN KEY` constraint on the ``@PrimaryKeyJoinColumn``s
|=== |===
To understand this annotation better, we must first discuss column mappings in general. [[join-table-mappings]]
=== Mapping associations to tables
The `@JoinTable` annotation specifies an _association table_, that is, a table holding foreign keys of both associated entities.
This annotation is usually used with `@ManyToMany` associations:
[source,java]
----
@Entity
class Book {
...
@ManyToMany
@JoinTable(name="BooksAuthors")
Set<Author> authors;
...
}
----
But it's even possible to use it to map a `@ManyToOne` or `@OneToOne` association to an association table.
[source,java]
----
@Entity
class Book {
...
@ManyToOne(fetch=LAZY)
@JoinTable(name="BookPublisher")
Publisher publisher;
...
}
----
[source,java]
----
@Entity
class Author {
...
@OneToOne(optional=false, fetch=LAZY)
@JoinTable(name="AuthorPerson")
Person author;
...
}
----
.`@JoinTable` annotation members
[cols=",8"]
|===
| Annotation member | Purpose
| `name` | The name of the mapped association table
| `schema` 💀 | The schema to which the table belongs
| `catalog` 💀 | The catalog to which the table belongs
| `uniqueConstraints` | One or more `@UniqueConstraint` annotations declaring multi-column unique constraints
| `indexes` | One or more `@Index` annotations each declaring an index
| `joinColumns` | One or more `@JoinColumn` annotations, specifying <<join-column-mappings,foreign key column mappings>> to the table of the owning side
| `inverseJoinColumns` | One or more `@JoinColumn` annotations, specifying <<join-column-mappings,foreign key column mappings>> to the table of the unowned side
| `foreignKey` | An `@ForeignKey` annotation specifying the name of the `FOREIGN KEY` constraint on the ``joinColumns``s
| `inverseForeignKey` | An `@ForeignKey` annotation specifying the name of the `FOREIGN KEY` constraint on the ``inverseJoinColumns``s
|===
To better understand these annotations, we must first discuss column mappings in general.
[[column-mappings]] [[column-mappings]]
=== Mapping to columns === Mapping to columns
@ -343,6 +412,24 @@ If you don't supply a name explicitly, Hibernate will generate a quite ugly name
To be fair, this is perfectly fine if you're only using the generated DDL for testing. To be fair, this is perfectly fine if you're only using the generated DDL for testing.
==== ====
For associations mapped to a `@JoinTable`, fetching the association requires two joins, and so we must declare the ``@JoinColumn``s inside the `@JoinTable` annotation:
[source,java]
----
@Entity
class Book {
@Id @GeneratedValue
Long id;
@ManyToMany
@JoinTable(joinColumns=@JoinColumn(name="bookId"),
inverseJoinColumns=@joinColumn(name="authorId"))
Set<Author> authors;
...
}
----
[[primary-key-column-mappings]] [[primary-key-column-mappings]]
=== Mapping primary key joins between tables === Mapping primary key joins between tables
@ -457,10 +544,10 @@ Instead, as we just saw in <<column-lengths>>, all you need is to specify a larg
[WARNING] [WARNING]
.PostgreSQL `BYTEA` and `TEXT` .PostgreSQL `BYTEA` and `TEXT`
==== ====
Unfortunately, the driver for PostgreSQL does not allow `BYTEA` or `TEXT` columns to be read via the JDBC LOB APIs. Unfortunately, the driver for PostgreSQL doesn't allow `BYTEA` or `TEXT` columns to be read via the JDBC LOB APIs.
This limitation of the Postgres driver has resulted in a whole cottage industry of bloggers and stackoverflow question-answerers recommending convoluted ways to hack the Hibernate `Dialect` for Postgres to allow an attribute annotated `@Lob` to be written using `setString()` and read using `getString()`. This limitation of the Postgres driver has resulted in a whole cottage industry of bloggers and stackoverflow question-answerers recommending convoluted ways to hack the Hibernate `Dialect` for Postgres to allow an attribute annotated `@Lob` to be written using `setString()` and read using `getString()`.
But _simply removing the `@Lob` annotation has exactly the same effect!_ But simply removing the `@Lob` annotation has exactly the same effect.
==== ====