HHH-18883 fix for TransientObjectException

This commit is contained in:
Gavin King 2024-11-27 13:34:04 +00:00
parent 1fe23ae2ed
commit f9d2e8e974
3 changed files with 200 additions and 7 deletions

View File

@ -334,10 +334,11 @@ public final class ForeignKeys {
* Return the identifier of the persistent or transient object, or throw
* an exception if the instance is "unsaved"
* <p>
* 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 final class ForeignKeys {
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 final class ForeignKeys {
}
}
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 '"

View File

@ -1583,14 +1583,14 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
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

View File

@ -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;
}
}
}