diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc index 99344cef28..58009a1db2 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc @@ -99,7 +99,7 @@ The restriction that a composite identifier has to be represented by a "primary Hibernate does allow composite identifiers to be defined without a "primary key class", although that modeling technique is deprecated and therefore omitted from this discussion. ==== -The attributes making up the composition can be either basic, composite, ManyToOne. +The attributes making up the composition can be either basic, composite, `@ManyToOne`. Note especially that collections and one-to-ones are never appropriate. [[identifiers-composite-aggregated]] @@ -208,6 +208,69 @@ include::{sourcedir}/IdManyToOneTest.java[tag=identifiers-composite-id-fetching- ---- ==== +[[identifiers-composite-generated]] +==== Composite identifiers with generated properties + +When using composite identifiers, the underlying identifier properties must be manually assigned by the user. + +Automatically generated properties are not supported can be used to generate the value of an underlying property that makes the composite identifier. + +Therefore, you cannot use any of the automatic property generator described by the <> like `@Generated`, `@CreationTimestamp` or `@ValueGenerationType` or database-generated values. + +Nevertheless, you can still generate the identifier properties prior to constructing the composite identifier, as illustrated by the following examples. + +Assuming we have the following `EventId` composite identifier and an `Event` entity which uses the aforementioned composite identifier. + +[[identifiers-composite-generated-mapping-example]] +.The Event entity and EventId composite identifier +==== +[source,java] +---- +include::{sourcedir}/composite/Event.java[tag=identifiers-composite-generated-mapping-example, indent=0] +---- + +[source,java] +---- +include::{sourcedir}/composite/EventId.java[tag=identifiers-composite-generated-mapping-example, indent=0] +---- +==== + +[[identifiers-composite-generated-in-memory]] +===== In-memory generated composite identifier properties + +If you want to generate the composite identifier properties in-memory, +you need to do that as follows: + +[[identifiers-composite-generated-in-memory-example]] +.In-memory generated composite identifier properties example +==== +[source,java] +---- +include::{sourcedir}/composite/EmbeddedIdInMemoryGeneratedValueTest.java[tag=identifiers-composite-generated-in-memory-example, indent=0] +---- +==== + +Notice that the `createdOn` property of the `EventId` composite identifier was generated by the data access code and assigned to the +identifier prior to persisting the `Event` entity. + +[[identifiers-composite-generated-database]] +===== Database generated composite identifier properties + +If you want to generate the composite identifier properties using a database function or stored procedure, +you could to do it as illustrated by the following example. + +[[identifiers-composite-generated-database-example]] +.Database generated composite identifier properties example +==== +[source,java] +---- +include::{sourcedir}/composite/EmbeddedIdDatabaseGeneratedValueTest.java[tag=identifiers-composite-generated-database-example, indent=0] +---- +==== + +Notice that the `createdOn` property of the `EventId` composite identifier was generated by calling the `CURRENT_TIMESTAMP` database function, +and we assigned it to the composite identifier prior to persisting the `Event` entity. + [[identifiers-generators]] ==== Generated identifier values diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/composite/EmbeddedIdDatabaseGeneratedValueTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/composite/EmbeddedIdDatabaseGeneratedValueTest.java new file mode 100644 index 0000000000..1bd0ab711c --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/composite/EmbeddedIdDatabaseGeneratedValueTest.java @@ -0,0 +1,67 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier.composite; + +import java.sql.Timestamp; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +public class EmbeddedIdDatabaseGeneratedValueTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Event.class }; + } + + @Test + @TestForIssue(jiraKey = "HHH-13096") + public void test() { + final EventId eventId = doInJPA( this::entityManagerFactory, entityManager -> { + //tag::identifiers-composite-generated-database-example[] + Timestamp currentTimestamp = (Timestamp) entityManager + .createNativeQuery( + "SELECT CURRENT_TIMESTAMP" ) + .getSingleResult(); + + EventId id = new EventId(); + id.setCategory( 1 ); + id.setCreatedOn( currentTimestamp ); + + Event event = new Event(); + event.setId( id ); + event.setKey( "Temperature" ); + event.setValue( "9" ); + + entityManager.persist( event ); + //end::identifiers-composite-generated-database-example[] + return event.getId(); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + + Event event = entityManager.find( Event.class, eventId ); + + assertEquals( "Temperature", event.getKey() ); + assertEquals( "9", event.getValue() ); + + return event.getId(); + } ); + } + +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/composite/EmbeddedIdInMemoryGeneratedValueTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/composite/EmbeddedIdInMemoryGeneratedValueTest.java new file mode 100644 index 0000000000..72de5a8146 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/composite/EmbeddedIdInMemoryGeneratedValueTest.java @@ -0,0 +1,58 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier.composite; + +import java.sql.Timestamp; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class EmbeddedIdInMemoryGeneratedValueTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Event.class }; + } + + @Test + @TestForIssue(jiraKey = "HHH-13096") + public void test() { + final EventId eventId = doInJPA( this::entityManagerFactory, entityManager -> { + //tag::identifiers-composite-generated-in-memory-example[] + EventId id = new EventId(); + id.setCategory( 1 ); + id.setCreatedOn( new Timestamp( System.currentTimeMillis() ) ); + + Event event = new Event(); + event.setId( id ); + event.setKey( "Temperature" ); + event.setValue( "9" ); + + entityManager.persist( event ); + //end::identifiers-composite-generated-in-memory-example[] + return event.getId(); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + + Event event = entityManager.find( Event.class, eventId ); + + assertEquals( "Temperature", event.getKey() ); + assertEquals( "9", event.getValue() ); + + return event.getId(); + } ); + } +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/composite/Event.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/composite/Event.java new file mode 100644 index 0000000000..7b8ac9bd87 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/composite/Event.java @@ -0,0 +1,55 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier.composite; + +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Vlad Mihalcea + */ +//tag::identifiers-composite-generated-mapping-example[] +@Entity +class Event { + + @Id + private EventId id; + + private String key; + + private String value; + + //Getters and setters are omitted for brevity +//end::identifiers-composite-generated-mapping-example[] + + public EventId getId() { + return id; + } + + public void setId(EventId id) { + this.id = id; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +//tag::identifiers-composite-generated-mapping-example[] +} +//end::identifiers-composite-generated-mapping-example[] + diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/composite/EventId.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/composite/EventId.java new file mode 100644 index 0000000000..18d8bcce27 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/composite/EventId.java @@ -0,0 +1,64 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier.composite; + +import java.io.Serializable; +import java.sql.Timestamp; +import java.util.Objects; +import javax.persistence.Embeddable; + +/** + * @author Vlad Mihalcea + */ +//tag::identifiers-composite-generated-mapping-example[] +@Embeddable +class EventId implements Serializable { + + private Integer category; + + private Timestamp createdOn; + + //Getters and setters are omitted for brevity +//end::identifiers-composite-generated-mapping-example[] + + public Integer getCategory() { + return category; + } + + public void setCategory(Integer category) { + this.category = category; + } + + public Timestamp getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Timestamp createdOn) { + this.createdOn = createdOn; + } + +//tag::identifiers-composite-generated-mapping-example[] + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + EventId that = (EventId) o; + return Objects.equals( category, that.category ) && + Objects.equals( createdOn, that.createdOn ); + } + + @Override + public int hashCode() { + return Objects.hash( category, createdOn ); + } +} +//end::identifiers-composite-generated-mapping-example[] +