Fix PersistenceUnitUtilImpl#getIdentifier() throws NPE for NonAggregateIdentifier

This commit is contained in:
Andrea Boriero 2021-11-03 22:53:25 +01:00 committed by Christian Beikov
parent eae9a39b14
commit 411355852a
17 changed files with 449 additions and 348 deletions

View File

@ -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...

View File

@ -51,7 +51,7 @@ public final class EntityPrinter {
result.put(
entityPersister.getIdentifierPropertyName(),
entityPersister.getIdentifierType().toLoggableString(
entityPersister.getIdentifier( entity ),
entityPersister.getIdentifier( entity, factory ),
factory
)
);

View File

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

View File

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

View File

@ -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() );

View File

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

View File

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

View File

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

View File

@ -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.
*/

View File

@ -3395,8 +3395,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
associationKeyPart = identifierMapping;
associationKey = identifierMapping.getIdentifier(
literal.getLiteralValue(),
null
);
getCreationContext().getSessionFactory());
}
if ( associationKeyPart instanceof BasicValuedMapping ) {
return new QueryLiteral<>(

View File

@ -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) {

View File

@ -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( '#' )

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<LineItem> lineItems = new ArrayList<>();
protected ShoppingCart() {
}
public ShoppingCart(String id) {
this.id = id;
}
public List<LineItem> 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 );
}
}
}
}

View File

@ -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) {
}

View File

@ -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) {
}

View File

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

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<LineItem> lineItems = new ArrayList<>();
protected ShoppingCart() {
}
public ShoppingCart(String id) {
this.id = id;
}
public List<LineItem> 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);
}
}
}
}