diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java index 8e1cef2adc..fabf0aebf8 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java @@ -334,10 +334,11 @@ public static boolean isTransient( * Return the identifier of the persistent or transient object, or throw * an exception if the instance is "unsaved" *

- * Used by OneToOneType and ManyToOneType to determine what id value should + * Used by {@link org.hibernate.type.OneToOneType} and + * {@link org.hibernate.type.ManyToOneType} to determine what id value should * be used for an object that may or may not be associated with the session. * This does a "best guess" using any/all info available to use (not just the - * EntityEntry). + * {@link EntityEntry}). * * @param entityName The name of the entity * @param object The entity instance @@ -357,9 +358,9 @@ public static Object getEntityIdentifierIfNotUnsaved( else { final Object id = session.getContextEntityIdentifier( object ); if ( id == null ) { - // context-entity-identifier returns null explicitly if the entity - // is not associated with the persistence context; so make some - // deeper checks... + // context-entity-identifier always returns null if the + // entity is not associated with the persistence context; + // so make some deeper checks... throwIfTransient( entityName, object, session ); return session.getEntityPersister( entityName, object ).getIdentifier( object, session ); } @@ -369,6 +370,21 @@ public static Object getEntityIdentifierIfNotUnsaved( } } + public static Object getEntityIdentifier( + final String entityName, + final Object object, + final SharedSessionContractImplementor session) { + if ( object == null ) { + return null; + } + else { + final Object id = session.getContextEntityIdentifier( object ); + return id == null + ? session.getEntityPersister( entityName, object ).getIdentifier( object, session ) + : id; + } + } + private static void throwIfTransient(String entityName, Object object, SharedSessionContractImplementor session) { if ( isTransient( entityName, object, Boolean.FALSE, session ) ) { throw new TransientObjectException( "Entity references an unsaved transient instance of '" diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java index f3192d5d44..1bfc1a5d61 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java @@ -1583,14 +1583,14 @@ protected void registerPossibleUniqueKeyEntries( final Type type = entry.getPropertyType(); // polymorphism not really handled completely correctly, - // perhaps...well, actually its ok, assuming that the + // perhaps...well, actually it's ok, assuming that the // entity name used in the lookup is the same as the // one used here, which it will be if ( resolvedEntityState[index] != null ) { final Object key; if ( type instanceof ManyToOneType manyToOneType ) { - key = ForeignKeys.getEntityIdentifierIfNotUnsaved( + key = ForeignKeys.getEntityIdentifier( manyToOneType.getAssociatedEntityName(), resolvedEntityState[index], session diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/batch/HandleVersionNumbersInitializedToNegativeValueTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/batch/HandleVersionNumbersInitializedToNegativeValueTests.java new file mode 100644 index 0000000000..1575315018 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/batch/HandleVersionNumbersInitializedToNegativeValueTests.java @@ -0,0 +1,177 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.batch; + +import jakarta.persistence.*; +import org.hibernate.Hibernate; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.testing.orm.junit.*; +import org.junit.jupiter.api.Test; + +import java.util.Objects; +import java.util.UUID; + +import static jakarta.persistence.FetchType.LAZY; +import static org.assertj.core.api.Assertions.assertThat; + +@DomainModel( + annotatedClasses = { + HandleVersionNumbersInitializedToNegativeValueTests.RootEntity.class, + HandleVersionNumbersInitializedToNegativeValueTests.ChildEntity.class + } +) +@ServiceRegistry( + settings = { + // For your own convenience to see generated queries: + @Setting(name = AvailableSettings.SHOW_SQL, value = "true"), + @Setting(name = AvailableSettings.FORMAT_SQL, value = "true"), + } +) +@SessionFactory +class HandleVersionNumbersInitializedToNegativeValueTests { + + @Test @JiraKey("HHH-18883") + void hhh18883Test(SessionFactoryScope scope) { + var id = UUID.randomUUID(); + scope.inTransaction(session -> { + RootEntity rootEntity = new RootEntity(id, new ChildEntity()); + session.persist(rootEntity); + }); + + scope.inTransaction(session -> { + RootEntity rootEntity = session.find(RootEntity.class, id); + assertThat(rootEntity).isNotNull(); + assertThat(rootEntity.getChildEntity()).isNotNull(); + }); + } + + + @Entity + @Table + public static class RootEntity { + + @Id + private UUID id; + + @OneToOne(mappedBy = "rootEntity", cascade = CascadeType.ALL) + @PrimaryKeyJoinColumn + private ChildEntity childEntity; + + @Version + private int version = -1; + + public RootEntity() { + } + + public RootEntity(UUID id, ChildEntity childEntity) { + setId(id); + setChildEntity(childEntity); + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public ChildEntity getChildEntity() { + return childEntity; + } + + public void setChildEntity(ChildEntity childEntity) { + this.childEntity = childEntity; + if (childEntity != null) { + childEntity.setRootEntity(this); + } + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) { + return false; + } + RootEntity event = (RootEntity) o; + return Objects.equals(id, event.id); + } + + @Override + public int hashCode() { + return 0; + } + } + + @Entity + @Table + public static class ChildEntity { + + @Id + private UUID id; + + @OneToOne(fetch = LAZY) + @MapsId + private RootEntity rootEntity; + + @Version + private int version = -1; + + public ChildEntity() { + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public RootEntity getRootEntity() { + return rootEntity; + } + + public void setRootEntity(RootEntity rootEntity) { + this.rootEntity = rootEntity; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) { + return false; + } + ChildEntity event = (ChildEntity) o; + return Objects.equals(id, event.id); + } + + @Override + public int hashCode() { + return 0; + } + } + +}