diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java index dd764c121d0a..4b7bc926d097 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java @@ -152,7 +152,8 @@ private static Object generateId( EventSource source, BeforeExecutionGenerator generator, EntityPersister persister) { - final Object id = generator.generate( source, entity, null, INSERT ); + final Object currentValue = generator.allowAssignedIdentifiers() ? persister.getIdentifier( entity ) : null; + final Object id = generator.generate( source, entity, currentValue, INSERT ); if ( id == null ) { throw new IdentifierGenerationException( "Null id generated for entity '" + persister.getEntityName() + "'" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java index 68120b24746b..368918fdde52 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java @@ -14,9 +14,12 @@ import org.hibernate.boot.model.relational.ExportableProducer; import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.BeforeExecutionGenerator; import org.hibernate.property.access.spi.Setter; import org.hibernate.type.CompositeType; +import static org.hibernate.generator.EventType.INSERT; + /** * For composite identifiers, defines a number of "nested" generations that * need to happen to "fill" the identifier property(s). @@ -71,7 +74,6 @@ public interface GenerationContextLocator { * determined {@linkplain GenerationContextLocator#locateGenerationContext context} */ public interface GenerationPlan extends ExportableProducer { - /** * Initializes this instance, in particular pre-generates SQL as necessary. *
@@ -82,12 +84,9 @@ public interface GenerationPlan extends ExportableProducer {
void initialize(SqlStringGenerationContext context);
/**
- * Execute the value generation.
- *
- * @param session The current session
- * @param incomingObject The entity for which we are generating id
+ * Retrieve the generator for this generation plan
*/
- Object execute(SharedSessionContractImplementor session, Object incomingObject);
+ BeforeExecutionGenerator getGenerator();
/**
* Returns the {@link Setter injector} for the generated property.
@@ -129,7 +128,17 @@ public Object generate(SharedSessionContractImplementor session, Object object)
null :
new ArrayList<>( generationPlans.size() );
for ( GenerationPlan generationPlan : generationPlans ) {
- final Object generated = generationPlan.execute( session, object );
+ final BeforeExecutionGenerator generator = generationPlan.getGenerator();
+ final Object generated;
+ if ( generator.generatedBeforeExecution( object, session ) ) {
+ final Object currentValue = generator.allowAssignedIdentifiers()
+ ? compositeType.getPropertyValue( context, generationPlan.getPropertyIndex(), session )
+ : null;
+ generated = generator.generate( session, object, currentValue, INSERT );
+ }
+ else {
+ throw new IdentifierGenerationException( "Identity generation isn't supported for composite ids" );
+ }
if ( generatedValues != null ) {
generatedValues.add( generated );
}
diff --git a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java
index d1ddf0045344..f28cd9176b31 100644
--- a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java
@@ -191,7 +191,8 @@ public Object insert(String entityName, Object entity) {
if ( !generator.generatesOnInsert() ) {
throw new IdentifierGenerationException( "Identifier generator must generate on insert" );
}
- id = ( (BeforeExecutionGenerator) generator ).generate( this, entity, null, INSERT );
+ final Object currentValue = generator.allowAssignedIdentifiers() ? persister.getIdentifier( entity ) : null;
+ id = ( (BeforeExecutionGenerator) generator ).generate( this, entity, currentValue, INSERT );
persister.setIdentifier( entity, id, this );
if ( firePreInsert(entity, id, state, persister) ) {
return id;
diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java
index a47c2440a4ed..fef0347aebde 100644
--- a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java
+++ b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java
@@ -55,7 +55,6 @@
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;
-import static org.hibernate.generator.EventType.INSERT;
import static org.hibernate.internal.util.StringHelper.qualify;
import static org.hibernate.mapping.MappingHelper.checkPropertyColumnDuplication;
import static org.hibernate.mapping.MappingHelper.classForName;
@@ -790,13 +789,8 @@ public int getPropertyIndex() {
}
@Override
- public Object execute(SharedSessionContractImplementor session, Object incomingObject) {
- if ( generator.generatedBeforeExecution( incomingObject, session ) ) {
- return generator.generate( session, incomingObject, null, INSERT );
- }
- else {
- throw new IdentifierGenerationException( "Identity generation isn't supported for composite ids" );
- }
+ public BeforeExecutionGenerator getGenerator() {
+ return generator;
}
@Override
diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/userdefined/BeforeExecutionAssignedValuesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/userdefined/BeforeExecutionAssignedValuesTest.java
new file mode 100644
index 000000000000..04f95fd62585
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/userdefined/BeforeExecutionAssignedValuesTest.java
@@ -0,0 +1,258 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.orm.test.idgen.userdefined;
+
+import jakarta.persistence.Embeddable;
+import jakarta.persistence.EmbeddedId;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.Id;
+import org.hibernate.annotations.IdGeneratorType;
+import org.hibernate.annotations.ValueGenerationType;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.hibernate.generator.BeforeExecutionGenerator;
+import org.hibernate.generator.EventType;
+import org.hibernate.generator.EventTypeSets;
+import org.hibernate.testing.orm.junit.DomainModel;
+import org.hibernate.testing.orm.junit.Jira;
+import org.hibernate.testing.orm.junit.SessionFactory;
+import org.hibernate.testing.orm.junit.SessionFactoryScope;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Test;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.EnumSet;
+import java.util.UUID;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SessionFactory
+@DomainModel(annotatedClasses = {
+ BeforeExecutionAssignedValuesTest.EntityWithGeneratedId.class,
+ BeforeExecutionAssignedValuesTest.GeneratedCompositeId.class,
+ BeforeExecutionAssignedValuesTest.EntityWithGeneratedEmbeddedId.class,
+ BeforeExecutionAssignedValuesTest.EntityWithGeneratedProperty.class,
+})
+@Jira("https://hibernate.atlassian.net/browse/HHH-19320")
+class BeforeExecutionAssignedValuesTest {
+ @Test
+ void testAssignedId(SessionFactoryScope scope) {
+ final EntityWithGeneratedId entity1 = new EntityWithGeneratedId( "assigned-id", "assigned-entity" );
+ scope.inTransaction( session -> session.persist( entity1 ) );
+ assertThat( entity1.getGeneratedId() ).isEqualTo( "assigned-id" );
+
+ final EntityWithGeneratedId entity2 = new EntityWithGeneratedId( "stateless-id", "stateless-entity" );
+ scope.inStatelessTransaction( session -> session.insert( entity2 ) );
+ assertThat( entity2.getGeneratedId() ).isEqualTo( "stateless-id" );
+ }
+
+ @Test
+ void testGeneratedId(SessionFactoryScope scope) {
+ final EntityWithGeneratedId entity = new EntityWithGeneratedId( null, "assigned-entity" );
+ scope.inTransaction( session -> session.persist( entity ) );
+ assertThat( entity.getGeneratedId() ).isNotNull();
+ }
+
+ @Test
+ void testAssignedEmbeddedId(SessionFactoryScope scope) {
+ final EntityWithGeneratedEmbeddedId entity1 = new EntityWithGeneratedEmbeddedId(
+ new GeneratedCompositeId( "assigned-1", null),
+ "generated-entity"
+ );
+ scope.inTransaction( session -> session.persist( entity1 ) );
+ assertThat( entity1.getId().getId1() ).isEqualTo( "assigned-1" );
+ assertThat( entity1.getId().getId2() ).isNotNull();
+
+ final EntityWithGeneratedEmbeddedId entity2 = new EntityWithGeneratedEmbeddedId(
+ new GeneratedCompositeId( "new-assigned-1", "assigned-2"),
+ "generated-entity"
+ );
+ scope.inTransaction( session -> session.persist( entity2 ) );
+ assertThat( entity2.getId().getId1() ).isEqualTo( "new-assigned-1" );
+ assertThat( entity2.getId().getId2() ).isEqualTo( "assigned-2" );
+ }
+
+ @Test
+ void testGeneratedEmbeddedId(SessionFactoryScope scope) {
+ final EntityWithGeneratedEmbeddedId entity = new EntityWithGeneratedEmbeddedId(
+ new GeneratedCompositeId(),
+ "generated-entity"
+ );
+ scope.inTransaction( session -> session.persist( entity ) );
+ assertThat( entity.getId().getId1() ).isNotNull();
+ assertThat( entity.getId().getId2() ).isNotNull();
+ }
+
+ @Test
+ void testInsertAssignedProperty(SessionFactoryScope scope) {
+ final String assigned = "assigned-property";
+ final EntityWithGeneratedProperty entity = new EntityWithGeneratedProperty( 1L, assigned );
+ scope.inTransaction( session -> session.persist( entity ) );
+ assertThat( entity.getGeneratedProperty() ).isEqualTo( assigned );
+ }
+
+ @Test
+ void testGeneratedPropertyAndUpdate(SessionFactoryScope scope) {
+ final EntityWithGeneratedProperty entity = new EntityWithGeneratedProperty( 2L, null );
+ scope.inTransaction( session -> {
+ session.persist( entity );
+ session.flush();
+
+ assertThat( entity.getGeneratedProperty() ).isNotNull();
+
+ // test update
+ entity.setGeneratedProperty( "new-assigned-property" );
+ } );
+
+ assertThat( entity.getGeneratedProperty() ).isEqualTo( "new-assigned-property" );
+ }
+
+ @AfterAll
+ public void tearDown(SessionFactoryScope scope) {
+ scope.getSessionFactory().getSchemaManager().truncateMappedObjects();
+ }
+
+ @Entity(name = "EntityWithGeneratedId")
+ static class EntityWithGeneratedId {
+ @Id
+ @GeneratedValue
+ @AssignableGenerator
+ private String generatedId;
+
+ private String name;
+
+ public EntityWithGeneratedId() {
+ }
+
+ public EntityWithGeneratedId(String generatedId, String name) {
+ this.generatedId = generatedId;
+ this.name = name;
+ }
+
+ public String getGeneratedId() {
+ return generatedId;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ @Embeddable
+ static class GeneratedCompositeId {
+ @AssignableGenerator
+ private String id1;
+
+ @AssignableGenerator
+ private String id2;
+
+ public GeneratedCompositeId() {
+ }
+
+ public GeneratedCompositeId(String id1, String id2) {
+ this.id1 = id1;
+ this.id2 = id2;
+ }
+
+ public String getId1() {
+ return id1;
+ }
+
+ public String getId2() {
+ return id2;
+ }
+ }
+
+ @Entity(name = "EntityWithGeneratedEmbeddedId")
+ static class EntityWithGeneratedEmbeddedId {
+ @EmbeddedId
+ private GeneratedCompositeId id;
+
+ private String name;
+
+ public EntityWithGeneratedEmbeddedId() {
+ }
+
+ public EntityWithGeneratedEmbeddedId(GeneratedCompositeId id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public GeneratedCompositeId getId() {
+ return id;
+ }
+ }
+
+ @Entity(name = "EntityWithGeneratedProperty")
+ static class EntityWithGeneratedProperty {
+ @Id
+ private Long id;
+
+ @AssignableGenerator
+ private String generatedProperty;
+
+ public EntityWithGeneratedProperty() {
+ }
+
+ public EntityWithGeneratedProperty(Long id, String generatedProperty) {
+ this.id = id;
+ this.generatedProperty = generatedProperty;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getGeneratedProperty() {
+ return generatedProperty;
+ }
+
+ public void setGeneratedProperty(String generatedProperty) {
+ this.generatedProperty = generatedProperty;
+ }
+ }
+
+ @IdGeneratorType(AssignedIdGenerator.class)
+ @ValueGenerationType(generatedBy = AssignedGenerator.class)
+ @Retention(RUNTIME)
+ @Target({FIELD, METHOD})
+ @interface AssignableGenerator {
+ }
+
+ public static class AssignedGenerator implements BeforeExecutionGenerator {
+ @Override
+ public Object generate(
+ SharedSessionContractImplementor session,
+ Object owner,
+ Object currentValue,
+ EventType eventType) {
+ if ( currentValue != null ) {
+ return currentValue;
+ }
+ return UUID.randomUUID().toString();
+ }
+
+ @Override
+ public EnumSet