diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc index 2c5b1f1e70..5702eac733 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc @@ -442,3 +442,173 @@ include::{extrasdir}/embeddable/embeddable-multiple-namingstrategy-entity-mappin ---- You could even develop your own naming strategy to do other types of implicit naming strategies. + +[[embeddable-mapping-aggregate]] +==== Aggregate embeddable mapping + +An embeddable mapping is usually just a way to encapsulate columns of a table into a Java type, +but as of Hibernate 6.2, it is also possible to map embeddable types as SQL aggregate types. + +Currently, there are three possible SQL aggregate types which can be specified by annotating one of the following +annotations on a persistent attribute: + +* `@Struct` - maps to a named SQL object type +* `@JdbcTypeCode(SqlTypes.JSON)` - maps to the SQL type JSON +* `@JdbcTypeCode(SqlTypes.SQLXML)` - maps to the SQL type XML + +Any read or assignment (in an update statement) expression for an attribute of such an embeddable +will resolve to the proper SQL expression to access/update the attribute of the SQL type. + +Since object, JSON and XML types are not supported equally on all databases, beware that not every mapping will work on all databases. +The following table outlines the current support for the different aggregate types: + +|=== +|Database |Struct |JSON |XML + +|PostgreSQL +|Yes +|Yes +|No (not yet) + +|Oracle +|Yes +|Yes +|No (not yet) + +|DB2 +|Yes +|No (not yet) +|No (not yet) + +|SQL Server +|No (not yet) +|No (not yet) +|No (not yet) +|=== + +Also note that embeddable types that are used in aggregate mappings do not yet support all kinds of attribute mappings, most notably: + +* Association mappings (`@ManyToOne`, `@OneToOne`, `@OneToMany`, `@ManyToMany`, `@ElementCollection`) +* Basic array mappings + +===== `@Struct` aggregate embeddable mapping + +The `@Struct` annotation can be placed on either the persistent attribute, or the embeddable type, +and requires the specification of a name i.e. the name of the SQL object type that it maps to. + +The following example mapping, maps the `EmbeddableAggregate` type to the SQL object type `structType`: + +.Mapping embeddable as SQL object type on persistent attribute level +==== +[source,java] +---- +include::{example-dir-emeddable}/StructEmbeddableTest.java[tag=embeddable-struct-type-mapping-example, indent=0] +---- +==== + +The schema generation will by default emit DDL for that object type, which looks something along the lines of + +==== +[source,sql] +---- +create type structType as ( + ... +) +create table StructHolder as ( + id bigint not null primary key, + aggregate structType +) +---- +==== + +The name and the nullability of the column can be refined through applying a `@Column` on the persistent attribute. + +One very important thing to note is that the order of columns in the DDL definition of a type must match the order that Hibernate expects. +By default, the order of columns is based on the alphabetical ordering of the embeddable type attribute names. + +Consider the following class: + +==== +[source,java] +---- +@Embeddable +@Struct(name = "myStruct") +public class MyStruct { + @Column(name = "b") + String attr1; + @Column(name = "a") + String attr2; +} +---- +==== + +The expected ordering of columns will be `(b,a)`, because the name `attr1` comes before `attr2` in alphabetical ordering. +This example aims at showing the importance of the persistent attribute name. + +Defining the embeddable type as Java record instead of a class can force a particular ordering through the definition of canonical constructor. + +==== +[source,java] +---- +@Embeddable +@Struct(name = "myStruct") +public record MyStruct ( + @Column(name = "a") + String attr2, + @Column(name = "b") + String attr1 +) {} +---- +==== + +In this particular example, the expected ordering of columns will be `(a,b)`, because the canonical constructor of the record +defines a specific ordering of persistent attributes, which Hibernate makes use of for `@Struct` mappings. + +It is not necessary to switch to Java records to configure the order though. +The `@Struct` annotation allows specifying the order through the `attributes` member, +an array of attribute names that the embeddable type declares, which defines the order in columns appear in the SQL object type. + +The same ordering as with the Java record can be achieved this way: + +==== +[source,java] +---- +@Embeddable +@Struct(name = "myStruct", attributes = {"attr2", "attr1"}) +public class MyStruct { + @Column(name = "b") + String attr1; + @Column(name = "a") + String attr2; +} +---- +==== + +===== JSON/XML aggregate embeddable mapping + +The `@JdbcTypeCode` annotation for JSON and XML mappings can only be placed on the persistent attribute. + +The following example mapping, maps the `EmbeddableAggregate` type to the JSON SQL type: + +.Mapping embeddable as JSON +==== +[source,java] +---- +include::{example-dir-emeddable}/JsonEmbeddableTest.java[tag=embeddable-json-type-mapping-example, indent=0] +---- +==== + +The schema generation will by default emit DDL that ensures the constraints of the embeddable type are respected, which looks something along the lines of + +==== +[source,sql] +---- +create table JsonHolder as ( + id bigint not null primary key, + aggregate json, + check (json_value(aggregate, '$.attribute1') is not null) +) +---- +==== + +Again, the name and the nullability of the `aggregate` column can be refined through applying a `@Column` on the persistent attribute. diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/JsonEmbeddableTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/JsonEmbeddableTest.java index 04ba68dd6e..ed76d715d9 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/JsonEmbeddableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/JsonEmbeddableTest.java @@ -302,8 +302,8 @@ public class JsonEmbeddableTest extends BaseSessionFactoryFunctionalTest { @JdbcTypeCode(SqlTypes.JSON) private EmbeddableAggregate aggregate; + //end::embeddable-json-type-mapping-example[] //Getters and setters are omitted for brevity - //end::embeddable-json-type-mapping-example[] public JsonHolder() { } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/StructEmbeddableTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/StructEmbeddableTest.java index cfa2711515..dab158c1ad 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/StructEmbeddableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/StructEmbeddableTest.java @@ -555,13 +555,11 @@ public class StructEmbeddableTest extends BaseSessionFactoryFunctionalTest { @Id private Long id; - //end::embeddable-struct-type-mapping-example[] @Struct(name = "structType") - //tag::embeddable-struct-type-mapping-example[] private EmbeddableAggregate aggregate; + //end::embeddable-struct-type-mapping-example[] //Getters and setters are omitted for brevity - //end::embeddable-struct-type-mapping-example[] public StructHolder() { } @@ -587,5 +585,7 @@ public class StructEmbeddableTest extends BaseSessionFactoryFunctionalTest { this.aggregate = aggregate; } + //tag::embeddable-struct-type-mapping-example[] } + //end::embeddable-struct-type-mapping-example[] }