association table mappings

This commit is contained in:
Gavin 2023-05-11 11:03:20 +02:00 committed by Gavin King
parent 6edefe6f4d
commit 3e187ad82a
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`.
====
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>`.
@ -732,7 +732,7 @@ Let's abandon our analogy right here, before we start calling this basic type a
[[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.
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.
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:
[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]
.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`?
====
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.
====
@ -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.
[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`:
[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]
.One-to-one associations are a way to represent subtyping
====
@ -1011,28 +1027,31 @@ class 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.
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.
[[many-to-many]]
=== Many-to-many
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`:
[source,java]
----
@Entity
class Book {
@Id @GeneratedValue
Long id;
@ManyToMany
Set<Author> authors;
...
}
----
@ -1041,6 +1060,7 @@ If the association is bidirectional, we add a very similar-looking attribute to
[source,java]
----
@Entity
class Book {
@Id @GeneratedValue
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]`?
====
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]
----

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.
[[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:
@ -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
| `@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
|===
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`:
[source,java]
@ -200,12 +205,16 @@ The `@Table` annotation can do more than just specify a name:
|===
[TIP]
.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.
.If you don't need to, don't hardcode 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:
@ -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
|===
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]]
=== 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.
====
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]]
=== 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]
.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()`.
But _simply removing the `@Lob` annotation has exactly the same effect!_
But simply removing the `@Lob` annotation has exactly the same effect.
====