From 7cabbc07240222267105b766d83232fecffa5834 Mon Sep 17 00:00:00 2001 From: Gavin Date: Mon, 15 May 2023 00:27:06 +0200 Subject: [PATCH] discuss mapping embeddables to UDTs or JSON --- .../main/asciidoc/introduction/Entities.adoc | 17 ++-- .../main/asciidoc/introduction/Mapping.adoc | 78 +++++++++++++++++++ 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/documentation/src/main/asciidoc/introduction/Entities.adoc b/documentation/src/main/asciidoc/introduction/Entities.adoc index b2be697b6f..b6ab9411ac 100644 --- a/documentation/src/main/asciidoc/introduction/Entities.adoc +++ b/documentation/src/main/asciidoc/introduction/Entities.adoc @@ -792,7 +792,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 doesn't 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 have its own 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. @@ -828,7 +828,7 @@ class Name { An embeddable class must satisfy the same requirements that entity classes satisfy, with the exception that an embeddable class has no `@Id` attribute. In particular, it must have a constructor with no parameters. -Alternatively, and embeddable type may be defined as a Java record type: +Alternatively, an embeddable type may be defined as a Java record type: [source,java] ---- @@ -855,6 +855,9 @@ class Author { } ---- +Embeddable types can be nested. +That is, an `@Embeddable` class may have an attribute whose type is itself a different `@Embeddable` class. + [TIP] // .The `@Embedded` annotation is not required ==== @@ -862,12 +865,16 @@ JPA provides an `@Embedded` annotation to identify an attribute of an entity tha This annotation is completely optional, and so we don't usually use it. ==== +Usually, embeddable types are stored in a "flattened" format. +Their attributes map columns of the table of their parent entity. +Later, in <>, we'll see a couple of different options. + An attribute of embeddable type represents a relationship between a Java object with a persistent identity, and a Java object with no persistent identity. -You can think of it as a whole-part relationship. +We can think of it as a whole/part relationship. The embeddable object belongs to the entity, and can't be shared with other entity instances. +And it exits for only as long as its parent entity exists. -Now we'll discuss a different kind of relationship: a relationship between Java objects that each have their persistent identity and persistence lifecycle. - +Next we'll discuss a different kind of relationship: a relationship between Java objects that each have their persistent identity and persistence lifecycle. [[associations]] === Associations diff --git a/documentation/src/main/asciidoc/introduction/Mapping.adoc b/documentation/src/main/asciidoc/introduction/Mapping.adoc index dda7e63899..1489a30d3b 100644 --- a/documentation/src/main/asciidoc/introduction/Mapping.adoc +++ b/documentation/src/main/asciidoc/introduction/Mapping.adoc @@ -653,6 +653,84 @@ InputStream bytes = book.images.getBinaryStream(); Of course, the behavior here depends very much on the JDBC driver, and so we really can't promise that this is a sensible thing to do on your database. +[[mapping-embeddables]] +=== Mapping embeddable types to UDTs or to JSON + +There's a couple of alternative ways to represent an embeddable type on the database side. + +[discrete] +==== Embeddables as UDTs + +First, a really nice option, at least in the case of Java record types, and for databases which support _user-defined types_ (UDTs), is to define a UDT which represents the record type. +Hibernate 6 makes this really easy. +Just annotate the record type, or the attribute which holds a reference to it, with the new `@Struct` annotation: + +[source,java] +---- +@Embeddable +@Struct(name="PersonName") +record Name(String firstName, String middleName, String lastName) {} +---- +[source,java] +---- +@Entity +class Person { + ... + Name name; + ... +} +---- + +This results in the following UDT: + +[source,sql] +---- +create type PersonName as (firstName varchar(255), middleName varchar(255), lastName varchar(255)) +---- + +And the `name` column of the `Author` table will have the type `PersonName`. + +[discrete] +==== Embeddables to JSON + +A second option that's available is to map the embeddable type to a `JSON` (or `JSONB`) column. +Now, this isn't something we would exactly _recommend_ if you're defining a data model from scratch, but it's at least useful for mapping pre-existing tables with JSON-typed columns. +Since embeddable types are nestable, we can map some JSON formats this way, and even query JSON properties using HQL. + +[NOTE] +==== +At this time, JSON arrays are not supported! +==== + +To map an attribute of embeddable type to JSON, we must annotate the attribute `@JdbcTypeCode(SqlTypes.JSON)`, instead of annotating the embeddable type. +But the embeddable type `Name` should still be annotated `@Embeddable` if we want to query its attributes using HQL. + +[source,java] +---- +@Embeddable +record Name(String firstName, String middleName, String lastName) {} +---- +[source,java] +---- +@Entity +class Person { + ... + @JdbcTypeCode(SqlTypes.JSON) + Name name; + ... +} +---- + +We also need to add Jackson or an implementation of JSONB—for example, Yasson—to our runtime classpath. +To use Jackson we could add this line to our Gradle build: + +[source,groovy] +---- +runtimeOnly 'com.fasterxml.jackson.core:jackson-databind:{jacksonVersion}' +---- + +Now the `name` column of the `Author` table will have the type `jsonb`, and Hibernate will automatically use Jackson to serialize a `Name` to and from JSON format. + [[mapping-formulas]] === Mapping to formulas