Skip to content

Commit f4e36a1

Browse files
committed
HHH-13096 - Document that composite identifier cannot use auto-generated properties
1 parent 84bc30d commit f4e36a1

File tree

5 files changed

+308
-1
lines changed

5 files changed

+308
-1
lines changed

documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ The restriction that a composite identifier has to be represented by a "primary
9999
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.
100100
====
101101

102-
The attributes making up the composition can be either basic, composite, ManyToOne.
102+
The attributes making up the composition can be either basic, composite, `@ManyToOne`.
103103
Note especially that collections and one-to-ones are never appropriate.
104104

105105
[[identifiers-composite-aggregated]]
@@ -208,6 +208,69 @@ include::{sourcedir}/IdManyToOneTest.java[tag=identifiers-composite-id-fetching-
208208
----
209209
====
210210

211+
[[identifiers-composite-generated]]
212+
==== Composite identifiers with generated properties
213+
214+
When using composite identifiers, the underlying identifier properties must be manually assigned by the user.
215+
216+
Automatically generated properties are not supported can be used to generate the value of an underlying property that makes the composite identifier.
217+
218+
Therefore, you cannot use any of the automatic property generator described by the <<chapters/domain/basic_types.adoc#mapping-generated, generated properties section>> like `@Generated`, `@CreationTimestamp` or `@ValueGenerationType` or database-generated values.
219+
220+
Nevertheless, you can still generate the identifier properties prior to constructing the composite identifier, as illustrated by the following examples.
221+
222+
Assuming we have the following `EventId` composite identifier and an `Event` entity which uses the aforementioned composite identifier.
223+
224+
[[identifiers-composite-generated-mapping-example]]
225+
.The Event entity and EventId composite identifier
226+
====
227+
[source,java]
228+
----
229+
include::{sourcedir}/composite/Event.java[tag=identifiers-composite-generated-mapping-example, indent=0]
230+
----
231+
232+
[source,java]
233+
----
234+
include::{sourcedir}/composite/EventId.java[tag=identifiers-composite-generated-mapping-example, indent=0]
235+
----
236+
====
237+
238+
[[identifiers-composite-generated-in-memory]]
239+
===== In-memory generated composite identifier properties
240+
241+
If you want to generate the composite identifier properties in-memory,
242+
you need to do that as follows:
243+
244+
[[identifiers-composite-generated-in-memory-example]]
245+
.In-memory generated composite identifier properties example
246+
====
247+
[source,java]
248+
----
249+
include::{sourcedir}/composite/EmbeddedIdInMemoryGeneratedValueTest.java[tag=identifiers-composite-generated-in-memory-example, indent=0]
250+
----
251+
====
252+
253+
Notice that the `createdOn` property of the `EventId` composite identifier was generated by the data access code and assigned to the
254+
identifier prior to persisting the `Event` entity.
255+
256+
[[identifiers-composite-generated-database]]
257+
===== Database generated composite identifier properties
258+
259+
If you want to generate the composite identifier properties using a database function or stored procedure,
260+
you could to do it as illustrated by the following example.
261+
262+
[[identifiers-composite-generated-database-example]]
263+
.Database generated composite identifier properties example
264+
====
265+
[source,java]
266+
----
267+
include::{sourcedir}/composite/EmbeddedIdDatabaseGeneratedValueTest.java[tag=identifiers-composite-generated-database-example, indent=0]
268+
----
269+
====
270+
271+
Notice that the `createdOn` property of the `EventId` composite identifier was generated by calling the `CURRENT_TIMESTAMP` database function,
272+
and we assigned it to the composite identifier prior to persisting the `Event` entity.
273+
211274
[[identifiers-generators]]
212275
==== Generated identifier values
213276

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.userguide.mapping.identifier.composite;
8+
9+
import java.sql.Timestamp;
10+
11+
import org.hibernate.dialect.H2Dialect;
12+
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
13+
14+
import org.hibernate.testing.RequiresDialect;
15+
import org.hibernate.testing.TestForIssue;
16+
import org.junit.Test;
17+
18+
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
19+
import static org.junit.Assert.assertEquals;
20+
21+
/**
22+
* @author Vlad Mihalcea
23+
*/
24+
@RequiresDialect(H2Dialect.class)
25+
public class EmbeddedIdDatabaseGeneratedValueTest extends BaseEntityManagerFunctionalTestCase {
26+
27+
@Override
28+
protected Class<?>[] getAnnotatedClasses() {
29+
return new Class[] { Event.class };
30+
}
31+
32+
@Test
33+
@TestForIssue(jiraKey = "HHH-13096")
34+
public void test() {
35+
final EventId eventId = doInJPA( this::entityManagerFactory, entityManager -> {
36+
//tag::identifiers-composite-generated-database-example[]
37+
Timestamp currentTimestamp = (Timestamp) entityManager
38+
.createNativeQuery(
39+
"SELECT CURRENT_TIMESTAMP" )
40+
.getSingleResult();
41+
42+
EventId id = new EventId();
43+
id.setCategory( 1 );
44+
id.setCreatedOn( currentTimestamp );
45+
46+
Event event = new Event();
47+
event.setId( id );
48+
event.setKey( "Temperature" );
49+
event.setValue( "9" );
50+
51+
entityManager.persist( event );
52+
//end::identifiers-composite-generated-database-example[]
53+
return event.getId();
54+
} );
55+
56+
doInJPA( this::entityManagerFactory, entityManager -> {
57+
58+
Event event = entityManager.find( Event.class, eventId );
59+
60+
assertEquals( "Temperature", event.getKey() );
61+
assertEquals( "9", event.getValue() );
62+
63+
return event.getId();
64+
} );
65+
}
66+
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.userguide.mapping.identifier.composite;
8+
9+
import java.sql.Timestamp;
10+
11+
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
12+
13+
import org.hibernate.testing.TestForIssue;
14+
import org.junit.Test;
15+
16+
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
17+
import static org.junit.Assert.assertEquals;
18+
19+
/**
20+
* @author Vlad Mihalcea
21+
*/
22+
public class EmbeddedIdInMemoryGeneratedValueTest extends BaseEntityManagerFunctionalTestCase {
23+
24+
@Override
25+
protected Class<?>[] getAnnotatedClasses() {
26+
return new Class[] { Event.class };
27+
}
28+
29+
@Test
30+
@TestForIssue(jiraKey = "HHH-13096")
31+
public void test() {
32+
final EventId eventId = doInJPA( this::entityManagerFactory, entityManager -> {
33+
//tag::identifiers-composite-generated-in-memory-example[]
34+
EventId id = new EventId();
35+
id.setCategory( 1 );
36+
id.setCreatedOn( new Timestamp( System.currentTimeMillis() ) );
37+
38+
Event event = new Event();
39+
event.setId( id );
40+
event.setKey( "Temperature" );
41+
event.setValue( "9" );
42+
43+
entityManager.persist( event );
44+
//end::identifiers-composite-generated-in-memory-example[]
45+
return event.getId();
46+
} );
47+
48+
doInJPA( this::entityManagerFactory, entityManager -> {
49+
50+
Event event = entityManager.find( Event.class, eventId );
51+
52+
assertEquals( "Temperature", event.getKey() );
53+
assertEquals( "9", event.getValue() );
54+
55+
return event.getId();
56+
} );
57+
}
58+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.userguide.mapping.identifier.composite;
8+
9+
import javax.persistence.Entity;
10+
import javax.persistence.Id;
11+
12+
/**
13+
* @author Vlad Mihalcea
14+
*/
15+
//tag::identifiers-composite-generated-mapping-example[]
16+
@Entity
17+
class Event {
18+
19+
@Id
20+
private EventId id;
21+
22+
private String key;
23+
24+
private String value;
25+
26+
//Getters and setters are omitted for brevity
27+
//end::identifiers-composite-generated-mapping-example[]
28+
29+
public EventId getId() {
30+
return id;
31+
}
32+
33+
public void setId(EventId id) {
34+
this.id = id;
35+
}
36+
37+
public String getKey() {
38+
return key;
39+
}
40+
41+
public void setKey(String key) {
42+
this.key = key;
43+
}
44+
45+
public String getValue() {
46+
return value;
47+
}
48+
49+
public void setValue(String value) {
50+
this.value = value;
51+
}
52+
//tag::identifiers-composite-generated-mapping-example[]
53+
}
54+
//end::identifiers-composite-generated-mapping-example[]
55+
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.userguide.mapping.identifier.composite;
8+
9+
import java.io.Serializable;
10+
import java.sql.Timestamp;
11+
import java.util.Objects;
12+
import javax.persistence.Embeddable;
13+
14+
/**
15+
* @author Vlad Mihalcea
16+
*/
17+
//tag::identifiers-composite-generated-mapping-example[]
18+
@Embeddable
19+
class EventId implements Serializable {
20+
21+
private Integer category;
22+
23+
private Timestamp createdOn;
24+
25+
//Getters and setters are omitted for brevity
26+
//end::identifiers-composite-generated-mapping-example[]
27+
28+
public Integer getCategory() {
29+
return category;
30+
}
31+
32+
public void setCategory(Integer category) {
33+
this.category = category;
34+
}
35+
36+
public Timestamp getCreatedOn() {
37+
return createdOn;
38+
}
39+
40+
public void setCreatedOn(Timestamp createdOn) {
41+
this.createdOn = createdOn;
42+
}
43+
44+
//tag::identifiers-composite-generated-mapping-example[]
45+
@Override
46+
public boolean equals(Object o) {
47+
if ( this == o ) {
48+
return true;
49+
}
50+
if ( o == null || getClass() != o.getClass() ) {
51+
return false;
52+
}
53+
EventId that = (EventId) o;
54+
return Objects.equals( category, that.category ) &&
55+
Objects.equals( createdOn, that.createdOn );
56+
}
57+
58+
@Override
59+
public int hashCode() {
60+
return Objects.hash( category, createdOn );
61+
}
62+
}
63+
//end::identifiers-composite-generated-mapping-example[]
64+

0 commit comments

Comments
 (0)