diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java index ef8de17005..21b4cdd8c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java @@ -11,13 +11,17 @@ import java.util.Collection; import java.util.HashSet; import java.util.Iterator; +import org.hibernate.Hibernate; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadingAction; import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.Status; import org.hibernate.event.spi.EventSource; import org.hibernate.internal.CoreLogging; @@ -92,10 +96,30 @@ public final class Cascade { if ( style.doCascade( action ) ) { Object child; - // For bytecode enhanced entities, need to fetch the attribute - if ( hasUninitializedLazyProperties && persister.getPropertyLaziness()[i] && action.performOnLazyProperty() ) { - LazyAttributeLoadingInterceptor interceptor = persister.getInstrumentationMetadata().extractInterceptor( parent ); - child = interceptor.fetchAttribute( parent, propertyName ); + if ( hasUninitializedLazyProperties && + !Hibernate.isPropertyInitialized( parent, propertyName ) ) { + // parent is a bytecode enhanced entity. + // cascading to an uninitialized, lazy value. + if ( types[i].isCollectionType() ) { + // The collection does not need to be loaded from the DB. + // CollectionType#resolve will return an uninitialized PersistentCollection. + // The action will initialize the collection later, if necessary. + child = types[i].resolve( LazyPropertyInitializer.UNFETCHED_PROPERTY, eventSource, parent ); + // TODO: it would be nice to be able to set the attribute in parent using + // persister.setPropertyValue( parent, i, child ). + // Unfortunately, that would cause the uninitialized collection to be + // loaded from the DB. + } + else if ( action.performOnLazyProperty() ) { + // The (non-collection) attribute needs to be initialized so that + // the action can be performed on the initialized attribute. + LazyAttributeLoadingInterceptor interceptor = persister.getInstrumentationMetadata().extractInterceptor( parent ); + child = interceptor.fetchAttribute( parent, propertyName ); + } + else { + // Nothing to do, so just skip cascading to this lazy (non-collection) attribute. + continue; + } } else { child = persister.getPropertyValue( parent, i ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java index 3ec8318acd..7add5c82e0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java @@ -14,6 +14,8 @@ import org.hibernate.test.bytecode.enhancement.lazy.group.SimpleLazyGroupUpdateT import org.hibernate.test.bytecode.enhancement.association.InheritedAttributeAssociationTestTask; import org.hibernate.test.bytecode.enhancement.otherentityentrycontext.OtherEntityEntryContextTestTask; import org.hibernate.test.bytecode.enhancement.cascade.CascadeWithFkConstraintTestTask; +import org.hibernate.test.bytecode.enhancement.merge.MergeEnhancedEntityTestTask; +import org.hibernate.test.bytecode.enhancement.merge.RefreshEnhancedEntityTestTask; import org.hibernate.testing.DialectChecks; import org.hibernate.testing.FailureExpected; import org.hibernate.testing.RequiresDialectFeature; @@ -85,6 +87,13 @@ public class EnhancerTest extends BaseUnitTestCase { EnhancerTestUtils.runEnhancerTestTask( DirtyTrackingTestTask.class ); } + @Test + @TestForIssue( jiraKey = "HHH-11459" ) + public void testMergeRefresh() { + EnhancerTestUtils.runEnhancerTestTask( MergeEnhancedEntityTestTask.class ); + EnhancerTestUtils.runEnhancerTestTask( RefreshEnhancedEntityTestTask.class ); + } + @Test public void testEviction() { EnhancerTestUtils.runEnhancerTestTask( EvictionTestTask.class ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/merge/MergeEnhancedEntityTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/merge/MergeEnhancedEntityTestTask.java new file mode 100644 index 0000000000..2434881a5a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/merge/MergeEnhancedEntityTestTask.java @@ -0,0 +1,97 @@ +/* + * 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.bytecode.enhancement.merge; + +import org.hibernate.Session; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; +import org.junit.Assert; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Luis Barreiro + */ +public class MergeEnhancedEntityTestTask extends AbstractEnhancerTestTask { + + private long entityId; + + public Class[] getAnnotatedClasses() { + return new Class[]{Person.class, PersonAddress.class}; + } + + public void prepare() { + Configuration cfg = new Configuration(); + cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); + super.prepare( cfg ); + + try ( Session s = getFactory().openSession() ) { + s.beginTransaction(); + + s.persist( new Person( 1L, "Sam" ) ); + s.getTransaction().commit(); + } + } + + public void execute() { + try ( Session s = getFactory().openSession() ) { + s.beginTransaction(); + Person entity = s.get( Person.class, 1L ); + entity.name = "Jhon"; + try { + s.merge( entity ); + s.getTransaction().commit(); + } catch ( RuntimeException e ) { + Assert.fail( "Enhanced entity can't be refreshed: " + e.getMessage() ); + } + } + } + + protected void cleanup() { + } + + @Entity( name = "Person" ) + public static class Person { + + @Id + private Long id; + + @Column( name = "name", length = 10, nullable = false ) + private String name; + + @OneToMany( fetch = FetchType.LAZY, mappedBy = "parent", orphanRemoval = true, cascade = CascadeType.ALL ) + private List details = new ArrayList<>(); + + protected Person() { + } + + public Person(Long id, String name) { + this.id = id; + this.name = name; + } + } + + @Entity( name = "PersonAddress" ) + public static class PersonAddress { + + @Id + private Long id; + + @ManyToOne( optional = false, fetch = FetchType.LAZY ) + private Person parent; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/merge/RefreshEnhancedEntityTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/merge/RefreshEnhancedEntityTestTask.java new file mode 100644 index 0000000000..cd58277438 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/merge/RefreshEnhancedEntityTestTask.java @@ -0,0 +1,97 @@ +/* + * 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.bytecode.enhancement.merge; + +import org.hibernate.Session; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; +import org.junit.Assert; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Luis Barreiro + */ +public class RefreshEnhancedEntityTestTask extends AbstractEnhancerTestTask { + + private long entityId; + + public Class[] getAnnotatedClasses() { + return new Class[]{Person.class, PersonAddress.class}; + } + + public void prepare() { + Configuration cfg = new Configuration(); + cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); + super.prepare( cfg ); + + try ( Session s = getFactory().openSession() ) { + s.beginTransaction(); + + s.persist( new Person( 1L, "Sam" ) ); + s.getTransaction().commit(); + } + } + + public void execute() { + try ( Session s = getFactory().openSession() ) { + s.beginTransaction(); + Person entity = s.get( Person.class, 1L ); + entity.name = "Jhon"; + try { + s.refresh( entity ); + s.getTransaction().commit(); + } catch ( RuntimeException e ) { + Assert.fail( "Enhanced entity can't be refreshed: " + e.getMessage() ); + } + } + } + + protected void cleanup() { + } + + @Entity(name = "Person") + public static class Person { + + @Id + private Long id; + + @Column( name = "name", length = 10, nullable = false ) + private String name; + + @OneToMany( fetch = FetchType.LAZY, mappedBy = "parent", orphanRemoval = true, cascade = CascadeType.ALL ) + private List details = new ArrayList<>(); + + protected Person() { + } + + public Person(Long id, String name) { + this.id = id; + this.name = name; + } + } + + @Entity(name = "PersonAddress") + public static class PersonAddress { + + @Id + private Long id; + + @ManyToOne( optional = false, fetch = FetchType.LAZY ) + private Person parent; + } +}