From 411355852aa6d7842694954f217d7df86992f899 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 3 Nov 2021 22:53:25 +0100 Subject: [PATCH] Fix PersistenceUnitUtilImpl#getIdentifier() throws NPE for NonAggregateIdentifier --- .../LazyAttributeLoadingInterceptor.java | 2 +- .../internal/util/EntityPrinter.java | 2 +- .../jpa/internal/PersistenceUnitUtilImpl.java | 2 +- .../mapping/EntityIdentifierMapping.java | 5 + .../BasicEntityIdentifierMappingImpl.java | 8 + .../EmbeddedIdentifierMappingImpl.java | 9 + .../NonAggregatedIdentifierMappingImpl.java | 81 ++++- .../entity/AbstractEntityPersister.java | 7 +- .../persister/entity/EntityPersister.java | 2 + .../sqm/sql/BaseSqmToSqlAstConverter.java | 3 +- .../main/java/org/hibernate/type/AnyType.java | 2 +- .../java/org/hibernate/type/EntityType.java | 8 +- .../CompositeIdDerivedIdWithIdClassTest.java | 322 +++++++++++++++++ .../GoofyPersisterClassProvider.java | 5 + .../PersisterClassProviderTest.java | 5 + .../orm/test/legacy/CustomPersister.java | 5 + .../CompositeIdDerivedIdWithIdClassTest.java | 329 ------------------ 17 files changed, 449 insertions(+), 348 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/derivedidentities/bidirectional/CompositeIdDerivedIdWithIdClassTest.java delete mode 100644 hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/CompositeIdDerivedIdWithIdClassTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java index 9a3613cf9f..6b1ea88cdc 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java @@ -79,7 +79,7 @@ public class LazyAttributeLoadingInterceptor extends AbstractLazyLoadInterceptor final EntityPersister persister = session.getFactory().getMetamodel().entityPersister( getEntityName() ); if ( isTemporarySession ) { - final Object id = persister.getIdentifier( target, null ); + final Object id = persister.getIdentifier( target, session ); // Add an entry for this entity in the PC of the temp Session // NOTE : a few arguments that would be nice to pass along here... diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/EntityPrinter.java b/hibernate-core/src/main/java/org/hibernate/internal/util/EntityPrinter.java index 4501a95bdc..1d2db52517 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/EntityPrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/EntityPrinter.java @@ -51,7 +51,7 @@ public final class EntityPrinter { result.put( entityPersister.getIdentifierPropertyName(), entityPersister.getIdentifierType().toLoggableString( - entityPersister.getIdentifier( entity ), + entityPersister.getIdentifier( entity, factory ), factory ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/internal/PersistenceUnitUtilImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/internal/PersistenceUnitUtilImpl.java index da943aab95..132eaecbde 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/internal/PersistenceUnitUtilImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/internal/PersistenceUnitUtilImpl.java @@ -103,7 +103,7 @@ public class PersistenceUnitUtilImpl implements PersistenceUnitUtil, Serializabl catch (MappingException ex) { throw new IllegalArgumentException( entityClass.getName() + " is not an entity", ex ); } - return persister.getIdentifier( entity, null ); + return persister.getIdentifier( entity, persister.getFactory() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java index 5eeb1f3fb7..8d94d71e00 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java @@ -7,6 +7,7 @@ package org.hibernate.metamodel.mapping; import org.hibernate.engine.spi.IdentifierValue; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -30,6 +31,10 @@ public interface EntityIdentifierMapping extends ValueMapping, ModelPart { IdentifierValue getUnsavedStrategy(); Object getIdentifier(Object entity, SharedSessionContractImplementor session); + + Object getIdentifier(Object entity, SessionFactoryImplementor sessionFactory); + void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session); + Object instantiate(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java index 8e85fd3f2f..ad08430a77 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java @@ -125,6 +125,14 @@ public class BasicEntityIdentifierMappingImpl implements BasicEntityIdentifierMa return propertyAccess.getGetter().get( entity ); } + @Override + public Object getIdentifier(Object entity, SessionFactoryImplementor sessionFactory) { + if ( entity instanceof HibernateProxy ) { + return ( (HibernateProxy) entity ).getHibernateLazyInitializer().getIdentifier(); + } + return propertyAccess.getGetter().get( entity ); + } + @Override public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { propertyAccess.getSetter().set( entity, id, session.getFactory() ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedIdentifierMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedIdentifierMappingImpl.java index 13516ed6c3..645a7c1635 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedIdentifierMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedIdentifierMappingImpl.java @@ -79,6 +79,15 @@ public class EmbeddedIdentifierMappingImpl @Override public Object getIdentifier(Object entity, SharedSessionContractImplementor session) { + return getIdentifier( entity ); + } + + @Override + public Object getIdentifier(Object entity, SessionFactoryImplementor sessionFactory) { + return EmbeddedIdentifierMappingImpl.this.getIdentifier( entity ); + } + + private Object getIdentifier(Object entity) { if ( entity instanceof HibernateProxy ) { return ( (HibernateProxy) entity ).getHibernateLazyInitializer().getIdentifier(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java index 87e113ed83..8bfb0f3fa9 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java @@ -10,6 +10,7 @@ import java.io.Serializable; import java.util.List; import java.util.function.BiConsumer; +import org.hibernate.EntityNameResolver; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -24,12 +25,15 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess; +import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.proxy.HibernateProxy; import org.hibernate.query.NavigablePath; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.type.ComponentType; +import org.hibernate.type.Type; /** * A "non-aggregated" composite identifier. @@ -42,8 +46,8 @@ import org.hibernate.type.ComponentType; public class NonAggregatedIdentifierMappingImpl extends AbstractCompositeIdentifierMapping { private final List idAttributeMappings; - private final ComponentType bootIdComponentType; - private final ComponentType bootCidComponentType; + private final ComponentType mappedIdComponentType; + private final ComponentType virtualComponentType; private final boolean[] isBootIdPropertyManyToOne; @@ -71,8 +75,8 @@ public class NonAggregatedIdentifierMappingImpl extends AbstractCompositeIdentif for ( int i = 0; i < propertySpan; i++ ) { isBootIdPropertyManyToOne[i] = bootIdClassDescriptor.getProperty( i ).getValue() instanceof ManyToOne; } - bootIdComponentType = (ComponentType) bootIdClassDescriptor.getType(); - bootCidComponentType = (ComponentType) bootCidDescriptor.getType(); + mappedIdComponentType = (ComponentType) bootIdClassDescriptor.getType(); + virtualComponentType = (ComponentType) bootCidDescriptor.getType(); } @Override @@ -88,14 +92,75 @@ public class NonAggregatedIdentifierMappingImpl extends AbstractCompositeIdentif @Override public Object getIdentifier(Object entity, SharedSessionContractImplementor session) { if ( hasContainingClass() ) { - final Serializable disassemble = bootCidComponentType.disassemble( entity, session, null ); - return bootIdComponentType.assemble( disassemble, session, null ); + final Serializable disassemble = virtualComponentType.disassemble( entity, session, null ); + return mappedIdComponentType.assemble( disassemble, session, null ); } else { return entity; } } + @Override + public Object getIdentifier(Object entity, SessionFactoryImplementor sessionFactory){ + if ( hasContainingClass() ) { + final Object id = mappedIdComponentType.instantiate(); + final Object[] propertyValues = virtualComponentType.getPropertyValues( entity ); + final Type[] subTypes = virtualComponentType.getSubtypes(); + final Type[] copierSubTypes = mappedIdComponentType.getSubtypes(); + final int length = subTypes.length; + for ( int i = 0; i < length; i++ ) { + //JPA 2 @MapsId + @IdClass points to the pk of the entity + if ( subTypes[i].isAssociationType() && !copierSubTypes[i].isAssociationType() ) { + propertyValues[i] = determineEntityId( propertyValues[i], sessionFactory ); + } + } + mappedIdComponentType.setPropertyValues( id, propertyValues ); + return id; + } + else { + return entity; + } + } + + private static Object determineEntityId(Object entity, SessionFactoryImplementor sessionFactory) { + if ( entity == null ) { + return null; + } + + if ( HibernateProxy.class.isInstance( entity ) ) { + // entity is a proxy, so we know it is not transient; just return ID from proxy + return ( (HibernateProxy) entity ).getHibernateLazyInitializer().getInternalIdentifier(); + } + + final EntityPersister persister = resolveEntityPersister( + entity, + sessionFactory + ); + + return persister.getIdentifier( entity, sessionFactory ); + } + + private static EntityPersister resolveEntityPersister(Object entity, SessionFactoryImplementor sessionFactory) { + assert sessionFactory != null; + + String entityName = null; + final MetamodelImplementor metamodel = sessionFactory.getMetamodel(); + for ( EntityNameResolver entityNameResolver : metamodel.getEntityNameResolvers() ) { + entityName = entityNameResolver.resolveEntityName( entity ); + if ( entityName != null ) { + break; + } + } + if ( entityName == null ) { + // old fall-back + entityName = entity.getClass().getName(); + } + + return metamodel.findEntityDescriptor( entityName ); + } + + + @Override public Object disassemble(Object value, SharedSessionContractImplementor session) { if ( !getEmbeddableTypeDescriptor().getMappedJavaTypeDescriptor() @@ -121,7 +186,7 @@ public class NonAggregatedIdentifierMappingImpl extends AbstractCompositeIdentif } else { final SessionFactoryImplementor factory = session.getFactory(); - final Object[] propertyValues = bootIdComponentType.getPropertyValues( id, session ); + final Object[] propertyValues = mappedIdComponentType.getPropertyValues( id, session ); forEachAttribute( (position, attribute) -> { Object propertyValue = propertyValues[position]; @@ -214,6 +279,6 @@ public class NonAggregatedIdentifierMappingImpl extends AbstractCompositeIdentif @Override public boolean hasContainingClass() { - return bootIdComponentType != bootCidComponentType; + return mappedIdComponentType != virtualComponentType; } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 27038ed7ee..86dfd0d8a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -5029,7 +5029,7 @@ public abstract class AbstractEntityPersister @Override public Object getIdentifier(Object object) { - return getIdentifier( object, null ); + return getIdentifier( object, getFactory() ); } @Override @@ -5037,6 +5037,11 @@ public abstract class AbstractEntityPersister return identifierMapping.getIdentifier( entity, session ); } + @Override + public Object getIdentifier(Object entity, SessionFactoryImplementor sessionFactory){ + return identifierMapping.getIdentifier( entity, sessionFactory ); + } + @Override public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { identifierMapping.setIdentifier( entity, id, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index 0e2a298db4..caddbbdf86 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -821,6 +821,8 @@ public interface EntityPersister */ Object getIdentifier(Object entity, SharedSessionContractImplementor session); + Object getIdentifier(Object entity, SessionFactoryImplementor sessionFactoryImplementor); + /** * Inject the identifier value into the given entity. */ diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index 96aa1f1e2a..e51e643beb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -3395,8 +3395,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base associationKeyPart = identifierMapping; associationKey = identifierMapping.getIdentifier( literal.getLiteralValue(), - null - ); + getCreationContext().getSessionFactory()); } if ( associationKeyPart instanceof BasicValuedMapping ) { return new QueryLiteral<>( diff --git a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java index f5ea249483..6f0e5781a1 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java @@ -156,7 +156,7 @@ public class AnyType extends AbstractType implements CompositeType, AssociationT final EntityPersister concretePersister = guessEntityPersister( entity ); return concretePersister == null ? null - : concretePersister.getIdentifier( entity, null ); + : concretePersister.getIdentifier( entity,concretePersister.getFactory() ); } private EntityPersister guessEntityPersister(Object object) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java index 0d8d6505f0..9ba7ccf6cf 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java @@ -324,7 +324,7 @@ public abstract class EntityType extends AbstractType implements AssociationType else { final Class mappedClass = persister.getMappedClass(); if ( mappedClass.isAssignableFrom( x.getClass() ) ) { - id = persister.getIdentifier( x ); + id = persister.getIdentifier( x, factory ); } else { id = x; @@ -353,7 +353,7 @@ public abstract class EntityType extends AbstractType implements AssociationType } else { if ( mappedClass.isAssignableFrom( x.getClass() ) ) { - xid = persister.getIdentifier( x ); + xid = persister.getIdentifier( x, factory ); } else { //JPA 2 case where @IdClass contains the id and not the associated entity @@ -368,7 +368,7 @@ public abstract class EntityType extends AbstractType implements AssociationType } else { if ( mappedClass.isAssignableFrom( y.getClass() ) ) { - yid = persister.getIdentifier( y ); + yid = persister.getIdentifier( y, factory ); } else { //JPA 2 case where @IdClass contains the id and not the associated entity @@ -504,7 +504,7 @@ public abstract class EntityType extends AbstractType implements AssociationType id = proxy.getHibernateLazyInitializer().getInternalIdentifier(); } else { - id = persister.getIdentifier( value ); + id = persister.getIdentifier( value, factory ); } result.append( '#' ) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/derivedidentities/bidirectional/CompositeIdDerivedIdWithIdClassTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/derivedidentities/bidirectional/CompositeIdDerivedIdWithIdClassTest.java new file mode 100644 index 0000000000..cb41dddde9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/derivedidentities/bidirectional/CompositeIdDerivedIdWithIdClassTest.java @@ -0,0 +1,322 @@ +/* + * 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.orm.test.annotations.derivedidentities.bidirectional; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.hibernate.jpa.internal.PersistenceUnitUtilImpl; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@DomainModel( + annotatedClasses = { + CompositeIdDerivedIdWithIdClassTest.ShoppingCart.class, + CompositeIdDerivedIdWithIdClassTest.LineItem.class + } +) +@SessionFactory +public class CompositeIdDerivedIdWithIdClassTest { + + @AfterEach + public void cleanup(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "delete from LineItem" ).executeUpdate(); + session.createQuery( "delete from Cart" ).executeUpdate(); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-11328") + public void testMergeTransientIdManyToOne(SessionFactoryScope scope) { + ShoppingCart transientCart = new ShoppingCart( "cart1" ); + transientCart.addLineItem( new LineItem( 0, "description2", transientCart ) ); + + // assertion for HHH-11274 - checking for exception + final Object identifier = new PersistenceUnitUtilImpl( scope.getSessionFactory() ).getIdentifier( transientCart.getLineItems() + .get( 0 ) ); + + // merge ID with transient many-to-one + scope.inTransaction( + session -> + session.merge( transientCart ) + ); + + scope.inTransaction( + session -> { + ShoppingCart updatedCart = session.get( ShoppingCart.class, "cart1" ); + // assertion for HHH-11274 - checking for exception + new PersistenceUnitUtilImpl( scope.getSessionFactory() ) + .getIdentifier( transientCart.getLineItems().get( 0 ) ); + + assertEquals( 1, updatedCart.getLineItems().size() ); + assertEquals( "description2", updatedCart.getLineItems().get( 0 ).getDescription() ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-10623") + public void testMergeDetachedIdManyToOne(SessionFactoryScope scope) { + ShoppingCart cart = new ShoppingCart( "cart1" ); + + scope.inTransaction( + session -> + session.persist( cart ) + ); + + // cart is detached now + LineItem lineItem = new LineItem( 0, "description2", cart ); + cart.addLineItem( lineItem ); + + // merge lineItem with an ID with detached many-to-one + scope.inTransaction( + session -> + session.merge( lineItem ) + ); + + scope.inTransaction( + session -> { + ShoppingCart updatedCart = session.get( ShoppingCart.class, "cart1" ); + assertEquals( 1, updatedCart.getLineItems().size() ); + assertEquals( "description2", updatedCart.getLineItems().get( 0 ).getDescription() ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12007") + public void testBindTransientEntityWithTransientKeyManyToOne(SessionFactoryScope scope) { + + ShoppingCart cart = new ShoppingCart( "cart" ); + LineItem item = new LineItem( 0, "desc", cart ); + + scope.inTransaction( + session -> { + String cartId = session.createQuery( + "select c.id from Cart c left join c.lineItems i where i = :item", + String.class + ).setParameter( "item", item ).uniqueResult(); + + assertNull( cartId ); + + assertFalse( session.contains( item ) ); + assertFalse( session.contains( cart ) ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12007") + public void testBindTransientEntityWithPersistentKeyManyToOne(SessionFactoryScope scope) { + ShoppingCart cart = new ShoppingCart( "cart" ); + LineItem item = new LineItem( 0, "desc", cart ); + + scope.inTransaction( + session -> { + session.persist( cart ); + String cartId = session.createQuery( + "select c.id from Cart c left join c.lineItems i where i = :item", + String.class + ).setParameter( "item", item ).uniqueResult(); + + assertNull( cartId ); + + assertFalse( session.contains( item ) ); + assertTrue( session.contains( cart ) ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12007") + public void testBindTransientEntityWithDetachedKeyManyToOne(SessionFactoryScope scope) { + + ShoppingCart cart = new ShoppingCart( "cart" ); + LineItem item = new LineItem( 0, "desc", cart ); + + scope.inTransaction( + session -> { + String cartId = session.createQuery( + "select c.id from Cart c left join c.lineItems i where i = :item", + String.class + ).setParameter( "item", item ).uniqueResult(); + + assertNull( cartId ); + + assertFalse( session.contains( item ) ); + assertFalse( session.contains( cart ) ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12007") + public void testBindTransientEntityWithCopiedKeyManyToOne(SessionFactoryScope scope) { + + ShoppingCart cart = new ShoppingCart( "cart" ); + LineItem item = new LineItem( 0, "desc", new ShoppingCart( "cart" ) ); + + scope.inTransaction( + session -> { + String cartId = session.createQuery( + "select c.id from Cart c left join c.lineItems i where i = :item", + String.class + ).setParameter( "item", item ).uniqueResult(); + + assertNull( cartId ); + + assertFalse( session.contains( item ) ); + assertFalse( session.contains( cart ) ); + } + ); + } + + @Entity(name = "Cart") + public static class ShoppingCart implements Serializable { + @Id + @Column(name = "id", nullable = false) + private String id; + + @OneToMany(mappedBy = "cart", cascade = CascadeType.ALL, orphanRemoval = true) + private List lineItems = new ArrayList<>(); + + protected ShoppingCart() { + } + + public ShoppingCart(String id) { + this.id = id; + } + + public List getLineItems() { + return lineItems; + } + + public void addLineItem(LineItem lineItem) { + lineItems.add( lineItem ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + ShoppingCart that = (ShoppingCart) o; + return Objects.equals( id, that.id ); + } + + @Override + public int hashCode() { + return Objects.hash( id ); + } + } + + @Entity(name = "LineItem") + @IdClass(LineItem.Pk.class) + public static class LineItem implements Serializable { + + @Id + @Column(name = "item_seq_number", nullable = false) + private Integer sequenceNumber; + + @Column(name = "description") + private String description; + + @Id + @ManyToOne + @JoinColumn(name = "cart_id") + private ShoppingCart cart; + + protected LineItem() { + } + + public LineItem(Integer sequenceNumber, String description, ShoppingCart cart) { + this.sequenceNumber = sequenceNumber; + this.description = description; + this.cart = cart; + } + + public Integer getSequenceNumber() { + return sequenceNumber; + } + + public ShoppingCart getCart() { + return cart; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !( o instanceof LineItem ) ) { + return false; + } + LineItem lineItem = (LineItem) o; + return Objects.equals( getSequenceNumber(), lineItem.getSequenceNumber() ) && + Objects.equals( getCart(), lineItem.getCart() ); + } + + @Override + public int hashCode() { + return Objects.hash( getSequenceNumber(), getCart() ); + } + + public String getDescription() { + return description; + } + + public static class Pk implements Serializable { + public Integer sequenceNumber; + public String cart; + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !( o instanceof Pk ) ) { + return false; + } + Pk pk = (Pk) o; + return Objects.equals( sequenceNumber, pk.sequenceNumber ) && + Objects.equals( cart, pk.cart ); + } + + @Override + public int hashCode() { + return Objects.hash( sequenceNumber, cart ); + } + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java index 57be1653cf..d9f3845a84 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java @@ -570,6 +570,11 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver { return null; } + @Override + public Object getIdentifier(Object entity, SessionFactoryImplementor sessionFactoryImplementor) { + return null; + } + @Override public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java index bb447af765..e9246c1b0a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java @@ -580,6 +580,11 @@ public class PersisterClassProviderTest { return null; } + @Override + public Object getIdentifier(Object entity, SessionFactoryImplementor sessionFactoryImplementor) { + return null; + } + @Override public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java b/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java index 45cee8678c..72dc7d8325 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java @@ -212,6 +212,11 @@ public class CustomPersister implements EntityPersister { return ( (Custom) entity ).id; } + @Override + public Object getIdentifier(Object entity, SessionFactoryImplementor sessionFactoryImplementor) { + return ( (Custom) entity ).id; + } + @Override public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { ( (Custom) entity ).id = (String) id; diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/CompositeIdDerivedIdWithIdClassTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/CompositeIdDerivedIdWithIdClassTest.java deleted file mode 100644 index 0a578ca99d..0000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/CompositeIdDerivedIdWithIdClassTest.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * 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.test.annotations.derivedidentities.bidirectional; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.IdClass; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; - -import org.junit.After; -import org.junit.Test; - -import org.hibernate.Session; -import org.hibernate.jpa.internal.PersistenceUnitUtilImpl; - -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -public class CompositeIdDerivedIdWithIdClassTest extends BaseCoreFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - ShoppingCart.class, - LineItem.class - }; - } - - @After - public void cleanup() { - Session s = openSession(); - s.getTransaction().begin(); - s.createQuery( "delete from LineItem" ).executeUpdate(); - s.createQuery( "delete from Cart" ).executeUpdate(); - s.getTransaction().commit(); - s.close(); - } - - @Test - @TestForIssue(jiraKey = "HHH-11328") - public void testMergeTransientIdManyToOne() throws Exception { - ShoppingCart transientCart = new ShoppingCart( "cart1" ); - transientCart.addLineItem( new LineItem( 0, "description2", transientCart ) ); - - // assertion for HHH-11274 - checking for exception - final Object identifier = new PersistenceUnitUtilImpl( sessionFactory() ).getIdentifier( transientCart.getLineItems().get( 0 ) ); - - // merge ID with transient many-to-one - Session s = openSession(); - s.getTransaction().begin(); - s.merge( transientCart ); - s.getTransaction().commit(); - s.close(); - - s = openSession(); - s.getTransaction().begin(); - ShoppingCart updatedCart = s.get( ShoppingCart.class, "cart1" ); - // assertion for HHH-11274 - checking for exception - new PersistenceUnitUtilImpl( sessionFactory() ).getIdentifier( transientCart.getLineItems().get( 0 ) ); - assertEquals( 1, updatedCart.getLineItems().size() ); - assertEquals( "description2", updatedCart.getLineItems().get( 0 ).getDescription() ); - s.getTransaction().commit(); - s.close(); - } - - @Test - @TestForIssue(jiraKey = "HHH-10623") - public void testMergeDetachedIdManyToOne() throws Exception { - ShoppingCart cart = new ShoppingCart("cart1"); - - Session s = openSession(); - s.getTransaction().begin(); - s.persist( cart ); - s.getTransaction().commit(); - s.close(); - - // cart is detached now - LineItem lineItem = new LineItem( 0, "description2", cart ); - cart.addLineItem( lineItem ); - - // merge lineItem with an ID with detached many-to-one - s = openSession(); - s.getTransaction().begin(); - s.merge( lineItem ); - s.getTransaction().commit(); - s.close(); - - s = openSession(); - s.getTransaction().begin(); - ShoppingCart updatedCart = s.get( ShoppingCart.class, "cart1" ); - assertEquals( 1, updatedCart.getLineItems().size() ); - assertEquals("description2", updatedCart.getLineItems().get( 0 ).getDescription()); - s.getTransaction().commit(); - s.close(); - } - - @Test - @TestForIssue( jiraKey = "HHH-12007") - public void testBindTransientEntityWithTransientKeyManyToOne() { - - ShoppingCart cart = new ShoppingCart( "cart" ); - LineItem item = new LineItem( 0, "desc", cart ); - - Session s = openSession(); - s.getTransaction().begin(); - - String cartId = s.createQuery( - "select c.id from Cart c left join c.lineItems i where i = :item", - String.class - ).setParameter( "item", item ).uniqueResult(); - - assertNull( cartId ); - - assertFalse( s.contains( item ) ); - assertFalse( s.contains( cart ) ); - - s.getTransaction().commit(); - s.close(); - } - - @Test - @TestForIssue( jiraKey = "HHH-12007") - public void testBindTransientEntityWithPersistentKeyManyToOne() { - ShoppingCart cart = new ShoppingCart( "cart" ); - LineItem item = new LineItem( 0, "desc", cart ); - - Session s = openSession(); - s.getTransaction().begin(); - - session.persist( cart ); - String cartId = s.createQuery( - "select c.id from Cart c left join c.lineItems i where i = :item", - String.class - ).setParameter( "item", item ).uniqueResult(); - - assertNull( cartId ); - - assertFalse( s.contains( item ) ); - assertTrue( s.contains( cart ) ); - - s.getTransaction().commit(); - s.close(); - - } - - @Test - @TestForIssue( jiraKey = "HHH-12007") - public void testBindTransientEntityWithDetachedKeyManyToOne() { - Session s = openSession(); - s.getTransaction().begin(); - - ShoppingCart cart = new ShoppingCart( "cart" ); - - s.getTransaction().commit(); - s.close(); - - LineItem item = new LineItem( 0, "desc", cart ); - - s = openSession(); - s.getTransaction().begin(); - - String cartId = s.createQuery( - "select c.id from Cart c left join c.lineItems i where i = :item", - String.class - ).setParameter( "item", item ).uniqueResult(); - - assertNull( cartId ); - - assertFalse( s.contains( item ) ); - assertFalse( s.contains( cart ) ); - - s.getTransaction().commit(); - s.close(); - } - - @Test - @TestForIssue( jiraKey = "HHH-12007") - public void testBindTransientEntityWithCopiedKeyManyToOne() { - Session s = openSession(); - s.getTransaction().begin(); - - ShoppingCart cart = new ShoppingCart( "cart" ); - s.getTransaction().commit(); - s.close(); - - LineItem item = new LineItem( 0, "desc", new ShoppingCart( "cart" ) ); - - s = openSession(); - s.getTransaction().begin(); - - String cartId = s.createQuery( - "select c.id from Cart c left join c.lineItems i where i = :item", - String.class - ).setParameter( "item", item ).uniqueResult(); - - assertNull( cartId ); - - assertFalse( s.contains( item ) ); - assertFalse( s.contains( cart ) ); - - s.getTransaction().commit(); - s.close(); - } - - @Entity(name = "Cart") - public static class ShoppingCart implements Serializable{ - @Id - @Column(name = "id", nullable = false) - private String id; - - @OneToMany(mappedBy = "cart", cascade = CascadeType.ALL, orphanRemoval = true) - private List lineItems = new ArrayList<>(); - - protected ShoppingCart() { - } - - public ShoppingCart(String id) { - this.id = id; - } - - public List getLineItems() { - return lineItems; - } - - public void addLineItem(LineItem lineItem) { - lineItems.add(lineItem); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ShoppingCart that = (ShoppingCart) o; - return Objects.equals( id, that.id ); - } - - @Override - public int hashCode() { - return Objects.hash(id); - } - } - - @Entity(name = "LineItem") - @IdClass(LineItem.Pk.class) - public static class LineItem implements Serializable { - - @Id - @Column(name = "item_seq_number", nullable = false) - private Integer sequenceNumber; - - @Column(name = "description") - private String description; - - @Id - @ManyToOne - @JoinColumn(name = "cart_id") - private ShoppingCart cart; - - protected LineItem() { - } - - public LineItem(Integer sequenceNumber, String description, ShoppingCart cart) { - this.sequenceNumber = sequenceNumber; - this.description = description; - this.cart = cart; - } - - public Integer getSequenceNumber() { - return sequenceNumber; - } - - public ShoppingCart getCart() { - return cart; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof LineItem)) return false; - LineItem lineItem = (LineItem) o; - return Objects.equals(getSequenceNumber(), lineItem.getSequenceNumber()) && - Objects.equals(getCart(), lineItem.getCart()); - } - - @Override - public int hashCode() { - return Objects.hash( getSequenceNumber(), getCart() ); - } - - public String getDescription() { - return description; - } - - public static class Pk implements Serializable { - public Integer sequenceNumber; - public String cart; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Pk)) return false; - Pk pk = (Pk) o; - return Objects.equals(sequenceNumber, pk.sequenceNumber) && - Objects.equals(cart, pk.cart); - } - - @Override - public int hashCode() { - return Objects.hash(sequenceNumber, cart); - } - } - } -}