association table mappings
This commit is contained in:
parent
0ed12f6869
commit
218a58ebbc
|
@ -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,28 +1027,31 @@ 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;
|
||||||
|
|
||||||
@ManyToMany
|
@ManyToMany
|
||||||
Set<Author> authors;
|
Set<Author> authors;
|
||||||
|
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
@ -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]
|
||||||
----
|
----
|
||||||
|
|
|
@ -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.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue