From b1c630dda1ab1505c1e5c6d40aa8e9149d537acd Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Tue, 20 Feb 2024 13:59:59 +0100 Subject: [PATCH 1/3] HHH-17761 Add a reproducer --- .../merge/MergeEnhancedEntityTest.java | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/merge/MergeEnhancedEntityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/merge/MergeEnhancedEntityTest.java index 2c8d37e9f817..0f25bdbc9582 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/merge/MergeEnhancedEntityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/merge/MergeEnhancedEntityTest.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.fail; @@ -37,7 +38,7 @@ public class MergeEnhancedEntityTest extends BaseCoreFunctionalTestCase { private Person person; @Override public Class[] getAnnotatedClasses() { - return new Class[]{Person.class, PersonAddress.class}; + return new Class[]{Person.class, PersonAddress.class, NullablePerson.class}; } @Before @@ -77,11 +78,49 @@ public void testRefresh() { } ); } + @Test + public void testMergeWithNullValues() { + doInHibernate( this::sessionFactory, em -> { + NullablePerson nullablePerson = new NullablePerson( 1L, "Sam", 100 ); + em.persist( nullablePerson ); + } ); + doInHibernate( this::sessionFactory, em -> { + NullablePerson updated = em.find( NullablePerson.class, 1L ); + assertThat( updated.name ).isEqualTo( "Sam" ); + assertThat( updated.number ).isEqualTo( 100 ); + } ); + + // only some properties are null + doInHibernate( this::sessionFactory, em -> { + NullablePerson nullablePerson = new NullablePerson( 1L, "Joe", null ); + em.merge( nullablePerson ); + } ); + doInHibernate( this::sessionFactory, em -> { + NullablePerson updated = em.find( NullablePerson.class, 1L ); + assertThat( updated.name ).isEqualTo( "Joe" ); + assertThat( updated.number ).isNull(); + } ); + + // all properties are null: + doInHibernate( this::sessionFactory, em -> { + NullablePerson nullablePerson = new NullablePerson( 1L, null, null ); + em.merge( nullablePerson ); + } ); + doInHibernate( this::sessionFactory, em -> { + NullablePerson updated = em.find( NullablePerson.class, 1L ); + assertThat( updated.name ).isNull(); + assertThat( updated.number ).isNull(); + } ); + } + @After public void cleanup() { doInHibernate( this::sessionFactory, s -> { s.delete( person ); } ); + doInHibernate( this::sessionFactory, s -> { + s.createQuery( "delete from NullablePerson" ); + } ); } // --- // @@ -118,4 +157,27 @@ private static class PersonAddress { @ManyToOne( optional = false, fetch = FetchType.LAZY ) Person parent; } + + @Entity(name = "NullablePerson") + @Table(name = "NULLABLE_PERSON") + private static class NullablePerson { + + @Id + Long id; + + @Column + String name; + + @Column + Integer number; + + NullablePerson() { + } + + NullablePerson(Long id, String name, Integer number) { + this.id = id; + this.name = name; + this.number = number; + } + } } From eb56f79640a69e352fde8b100b81e47cde206310 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 26 Feb 2024 19:58:40 +0100 Subject: [PATCH 2/3] HHH-17761 Add test for issue --- .../MergeEnhancedEntityDynamicUpdateTest.java | 193 ++++++++++++++++++ .../merge/MergeEnhancedEntityTest.java | 2 +- 2 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/merge/MergeEnhancedEntityDynamicUpdateTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/merge/MergeEnhancedEntityDynamicUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/merge/MergeEnhancedEntityDynamicUpdateTest.java new file mode 100644 index 000000000000..53d3616160e8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/merge/MergeEnhancedEntityDynamicUpdateTest.java @@ -0,0 +1,193 @@ +/* + * 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.bytecode.enhancement.merge; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.fail; + +/** + * @author Luis Barreiro + */ +@TestForIssue( jiraKey = "HHH-11459" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class MergeEnhancedEntityDynamicUpdateTest extends BaseCoreFunctionalTestCase { + private Person person; + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Person.class, PersonAddress.class, NullablePerson.class}; + } + + @Before + public void prepare() { + person = new Person( 1L, "Sam" ); + doInHibernate( this::sessionFactory, s -> { + s.persist( person ); + } ); + } + + @Test + public void testMerge() { + doInHibernate( this::sessionFactory, s -> { + Person entity = s.find( Person.class, 1L ); + entity.name = "John"; + try { + s.merge( entity ); + } catch ( RuntimeException e ) { + fail( "Enhanced entity can't be merged: " + e.getMessage() ); + } + } ); + } + + @Test + public void testRefresh() { + doInHibernate( this::sessionFactory, s -> { + Person entity = s.find( Person.class, 1L ); + entity.name = "John"; + + s.refresh( entity ); + +// try { +// s.refresh( entity ); +// } catch ( RuntimeException e ) { +// fail( "Enhanced entity can't be refreshed: " + e.getMessage() ); +// } + } ); + } + + @Test + public void testMergeWithNullValues() { + doInHibernate( this::sessionFactory, em -> { + NullablePerson nullablePerson = new NullablePerson( 1L, "Sam", 100 ); + em.persist( nullablePerson ); + } ); + doInHibernate( this::sessionFactory, em -> { + NullablePerson updated = em.find( NullablePerson.class, 1L ); + assertThat( updated.name ).isEqualTo( "Sam" ); + assertThat( updated.number ).isEqualTo( 100 ); + } ); + + // only some properties are null + doInHibernate( this::sessionFactory, em -> { + NullablePerson nullablePerson = new NullablePerson( 1L, "Joe", null ); + em.merge( nullablePerson ); + } ); + doInHibernate( this::sessionFactory, em -> { + NullablePerson updated = em.find( NullablePerson.class, 1L ); + assertThat( updated.name ).isEqualTo( "Joe" ); + assertThat( updated.number ).isNull(); + } ); + + // all properties are null: + doInHibernate( this::sessionFactory, em -> { + NullablePerson nullablePerson = new NullablePerson( 1L, null, null ); + em.merge( nullablePerson ); + } ); + doInHibernate( this::sessionFactory, em -> { + NullablePerson updated = em.find( NullablePerson.class, 1L ); + assertThat( updated.name ).isNull(); + assertThat( updated.number ).isNull(); + } ); + } + + @After + public void cleanup() { + doInHibernate( this::sessionFactory, s -> { + s.delete( person ); + } ); + doInHibernate( this::sessionFactory, s -> { + s.createQuery( "delete from NullablePerson" ); + } ); + } + + // --- // + + @Entity + @Table( name = "PERSON" ) + @DynamicUpdate + @DynamicInsert + private static class Person { + + @Id + Long id; + + @Column( name = "name", length = 10, nullable = false ) + String name; + + @OneToMany( fetch = FetchType.LAZY, mappedBy = "parent", orphanRemoval = true, cascade = CascadeType.ALL ) + List details = new ArrayList<>(); + + Person() { + } + + Person(Long id, String name) { + this.id = id; + this.name = name; + } + } + + @Entity + @Table( name = "PERSON_ADDRESS" ) + @DynamicUpdate + @DynamicInsert + private static class PersonAddress { + + @Id + Long id; + + @ManyToOne( optional = false, fetch = FetchType.LAZY ) + Person parent; + } + + @Entity(name = "NullablePerson") + @Table(name = "NULLABLE_PERSON") + @DynamicUpdate + @DynamicInsert + private static class NullablePerson { + + @Id + Long id; + + @Column + String name; + + @Column(name = "NUMBER_COLUMN") + Integer number; + + NullablePerson() { + } + + NullablePerson(Long id, String name, Integer number) { + this.id = id; + this.name = name; + this.number = number; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/merge/MergeEnhancedEntityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/merge/MergeEnhancedEntityTest.java index 0f25bdbc9582..e0177f054963 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/merge/MergeEnhancedEntityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/merge/MergeEnhancedEntityTest.java @@ -168,7 +168,7 @@ private static class NullablePerson { @Column String name; - @Column + @Column(name = "NUMBER_COLUMN") Integer number; NullablePerson() { From 9bf8f523c74c0ae91a2b4efb35f231797f4d696e Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 29 Mar 2024 11:33:35 +0100 Subject: [PATCH 3/3] HHH-17761 Merging a bytecode enhanced entity with all properties set to null does not apply the update --- .../internal/bytebuddy/EnhancerImpl.java | 8 ++++ .../enhance/spi/EnhancerConstants.java | 5 +++ .../BytecodeEnhancementMetadataPojoImpl.java | 8 ++++ .../engine/internal/AbstractEntityEntry.java | 11 +++++- .../engine/internal/EntityEntryContext.java | 22 +++++++++++ .../engine/internal/ManagedTypeHelper.java | 15 +++++++ .../hibernate/engine/spi/ManagedEntity.java | 39 +++++++++++++++++++ .../internal/AbstractSaveEventListener.java | 3 ++ .../DefaultFlushEntityEventListener.java | 35 ++++++++++++----- .../internal/DefaultMergeEventListener.java | 16 ++++++-- .../entity/AbstractEntityPersister.java | 7 ++++ .../enhance/version/SimpleEntity.java | 10 +++++ .../enhancement/basic/BasicSessionTest.java | 10 +++++ ...deEnhancedImmutableReferenceCacheTest.java | 9 +++++ 14 files changed, 185 insertions(+), 13 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index ef4b1241b923..715100ff8a91 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -241,6 +241,14 @@ private DynamicType.Builder doEnhance(Supplier> builde EnhancerConstants.NEXT_SETTER_NAME ); + builder = addFieldWithGetterAndSetter( + builder, + boolean.class, + EnhancerConstants.USE_TRACKER_FIELD_NAME, + EnhancerConstants.USE_TRACKER_GETTER_NAME, + EnhancerConstants.USE_TRACKER_SETTER_NAME + ); + builder = addInterceptorHandling( builder, managedCtClass ); if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancerConstants.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancerConstants.java index dd3d377e37b7..0cc697efcc68 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancerConstants.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancerConstants.java @@ -182,6 +182,11 @@ public final class EnhancerConstants { */ public static final String TRACKER_COMPOSITE_CLEAR_OWNER = "$$_hibernate_clearOwner"; + public static final String USE_TRACKER_FIELD_NAME = "$$_hibernate_useTracker"; + public static final String USE_TRACKER_GETTER_NAME = "$$_hibernate_useTracker"; + public static final String USE_TRACKER_SETTER_NAME = "$$_hibernate_setUseTracker"; + + private EnhancerConstants() { } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/BytecodeEnhancementMetadataPojoImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/BytecodeEnhancementMetadataPojoImpl.java index 148ad7093bf9..1bbcec19fb2c 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/BytecodeEnhancementMetadataPojoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/BytecodeEnhancementMetadataPojoImpl.java @@ -17,6 +17,7 @@ import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.bytecode.spi.NotInstrumentedException; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; @@ -31,6 +32,7 @@ import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptableType; +import static org.hibernate.engine.internal.ManagedTypeHelper.processIfManagedEntity; import static org.hibernate.engine.internal.ManagedTypeHelper.processIfSelfDirtinessTracker; /** @@ -153,6 +155,8 @@ public PersistentAttributeInterceptable createEnhancedProxy(EntityKey entityKey, // clear the fields that are marked as dirty in the dirtiness tracker processIfSelfDirtinessTracker( entity, BytecodeEnhancementMetadataPojoImpl::clearDirtyAttributes ); + processIfManagedEntity( entity, BytecodeEnhancementMetadataPojoImpl::useTracker ); + // add the entity (proxy) instance to the PC persistenceContext.addEnhancedProxy( entityKey, entity ); @@ -188,6 +192,10 @@ private static void clearDirtyAttributes(final SelfDirtinessTracker entity) { entity.$$_hibernate_clearDirtyAttributes(); } + private static void useTracker(final ManagedEntity entity) { + entity.$$_hibernate_setUseTracker( true ); + } + @Override public LazyAttributeLoadingInterceptor injectInterceptor( Object entity, diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java index 0ca255eb3921..6fc393284e97 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java @@ -19,6 +19,7 @@ import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntryExtraState; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; @@ -35,11 +36,13 @@ import static org.hibernate.engine.internal.AbstractEntityEntry.EnumState.LOCK_MODE; import static org.hibernate.engine.internal.AbstractEntityEntry.EnumState.PREVIOUS_STATUS; import static org.hibernate.engine.internal.AbstractEntityEntry.EnumState.STATUS; +import static org.hibernate.engine.internal.ManagedTypeHelper.asManagedEntity; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.asSelfDirtinessTracker; import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isSelfDirtinessTracker; +import static org.hibernate.engine.internal.ManagedTypeHelper.processIfManagedEntity; import static org.hibernate.engine.internal.ManagedTypeHelper.processIfSelfDirtinessTracker; import static org.hibernate.engine.spi.CachedNaturalIdValueSource.LOAD; import static org.hibernate.engine.spi.Status.DELETED; @@ -279,6 +282,7 @@ public void postUpdate(Object entity, Object[] updatedState, Object nextVersion) } processIfSelfDirtinessTracker( entity, AbstractEntityEntry::clearDirtyAttributes ); + processIfManagedEntity( entity, AbstractEntityEntry::useTracker ); final SharedSessionContractImplementor session = getPersistenceContext().getSession(); session.getFactory().getCustomEntityDirtinessStrategy() @@ -289,6 +293,10 @@ private static void clearDirtyAttributes(final SelfDirtinessTracker entity) { entity.$$_hibernate_clearDirtyAttributes(); } + private static void useTracker(final ManagedEntity entity) { + entity.$$_hibernate_setUseTracker( true ); + } + @Override public void postDelete() { setCompressedValue( PREVIOUS_STATUS, getStatus() ); @@ -367,7 +375,8 @@ else if ( isHibernateProxy( entity ) ) { return uninitializedProxy || !persister.hasCollections() && !persister.hasMutableProperties() - && !asSelfDirtinessTracker( entity ).$$_hibernate_hasDirtyAttributes(); + && !asSelfDirtinessTracker( entity ).$$_hibernate_hasDirtyAttributes() + && asManagedEntity( entity ).$$_hibernate_useTracker(); } else { if ( isPersistentAttributeInterceptable( entity ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java index 6ace1b46c50a..5a29e433a486 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java @@ -553,9 +553,11 @@ private static class ManagedEntityImpl implements ManagedEntity { private EntityEntry entityEntry; private ManagedEntity previous; private ManagedEntity next; + private boolean useTracker; public ManagedEntityImpl(Object entityInstance) { this.entityInstance = entityInstance; + useTracker = false; } @Override @@ -583,6 +585,16 @@ public ManagedEntityImpl(Object entityInstance) { this.next = next; } + @Override + public void $$_hibernate_setUseTracker(boolean useTracker) { + this.useTracker = useTracker; + } + + @Override + public boolean $$_hibernate_useTracker() { + return useTracker; + } + @Override public ManagedEntity $$_hibernate_getPreviousManagedEntity() { return previous; @@ -663,6 +675,16 @@ public ImmutableManagedEntityHolder(ManagedEntity immutableManagedEntity) { this.next = next; } + @Override + public void $$_hibernate_setUseTracker(boolean useTracker) { + managedEntity.$$_hibernate_setUseTracker( useTracker ); + } + + @Override + public boolean $$_hibernate_useTracker() { + return managedEntity.$$_hibernate_useTracker(); + } + /* Check instance type of EntityEntry and if type is ImmutableEntityEntry, check to see if entity is referenced cached in the second level cache */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ManagedTypeHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ManagedTypeHelper.java index b1a204c61395..555bdc30d76f 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/ManagedTypeHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ManagedTypeHelper.java @@ -218,6 +218,16 @@ public static void processIfSelfDirtinessTracker(final Object entity, final Self } } + public static void processIfManagedEntity(final Object entity, final ManagedEntityConsumer action) { + if ( entity instanceof PrimeAmongSecondarySupertypes ) { + final PrimeAmongSecondarySupertypes t = (PrimeAmongSecondarySupertypes) entity; + final ManagedEntity e = t.asManagedEntity(); + if ( e != null ) { + action.accept( e ); + } + } + } + // Not using Consumer because of JDK-8180450: // use a custom functional interface with explicit type. @FunctionalInterface @@ -225,6 +235,11 @@ public interface SelfDirtinessTrackerConsumer { void accept(SelfDirtinessTracker tracker); } + @FunctionalInterface + public interface ManagedEntityConsumer { + void accept(ManagedEntity entity); + } + /** * If the entity is implementing SelfDirtinessTracker, apply some action to it; this action should take * a parameter of type T. diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedEntity.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedEntity.java index 67dc6ccfc1b1..f239f1b48763 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedEntity.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedEntity.java @@ -80,6 +80,45 @@ public interface ManagedEntity extends Managed { */ void $$_hibernate_setNextManagedEntity(ManagedEntity next); + /** + * Used to understand if the tracker can be used to detect dirty properties. + * + * E.g: + * @Entity + * class MyEntity{ + * @Id Integer id + * String name + * } + * + * inSession ( + * session -> { + * MyEntity entity = new MyEntity(1, "Poul"); + * session.persist(entity); + * }); + * + * + * inSession ( + * session -> { + * MyEntity entity = new MyEntity(1, null); + * session.merge(entity); + * }); + * + * Because the attribute `name` has been set to null the SelfDirtyTracker does not detect any change and + * so doesn't mark the attribute as dirty so the merge does not perform any update. + * + * + * @param useTracker true if the tracker can be used to detect dirty properties, false otherwise + * + */ + void $$_hibernate_setUseTracker(boolean useTracker); + + /** + * Can the tracker be used to detect dirty attributes + * + * @return true if the tracker can be used to detect dirty properties, false otherwise + */ + boolean $$_hibernate_useTracker(); + /** * Special internal contract to optimize type checking * @see PrimeAmongSecondarySupertypes diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java index e1106e9af453..2b256b47e3fa 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java @@ -20,6 +20,7 @@ import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntryExtraState; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SessionImplementor; @@ -38,6 +39,7 @@ import org.hibernate.type.TypeHelper; import static org.hibernate.engine.internal.ManagedTypeHelper.processIfSelfDirtinessTracker; +import static org.hibernate.engine.internal.ManagedTypeHelper.processIfManagedEntity; import static org.hibernate.engine.internal.Versioning.getVersion; import static org.hibernate.engine.internal.Versioning.seedVersion; import static org.hibernate.generator.EventType.INSERT; @@ -196,6 +198,7 @@ protected Object performSave( callbackRegistry.preCreate( entity ); processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes ); + processIfManagedEntity( entity, (managedEntity) -> managedEntity.$$_hibernate_setUseTracker( true ) ); if ( persister.getGenerator() instanceof Assigned ) { id = persister.getIdentifier( entity, source ); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java index d50b04c30818..edc86466543c 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java @@ -18,6 +18,7 @@ import org.hibernate.engine.internal.Nullability; import org.hibernate.engine.internal.Versioning; import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; @@ -41,11 +42,13 @@ import org.hibernate.type.Type; import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY; +import static org.hibernate.engine.internal.ManagedTypeHelper.asManagedEntity; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.asSelfDirtinessTracker; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isSelfDirtinessTracker; import static org.hibernate.engine.internal.ManagedTypeHelper.processIfSelfDirtinessTracker; +import static org.hibernate.engine.internal.ManagedTypeHelper.processIfManagedEntity; import static org.hibernate.engine.internal.Versioning.getVersion; import static org.hibernate.engine.internal.Versioning.incrementVersion; import static org.hibernate.engine.internal.Versioning.setVersion; @@ -219,6 +222,7 @@ private boolean isUpdateNecessary(final FlushEntityEvent event, final boolean mi else { final Object entity = event.getEntity(); processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes ); + processIfManagedEntity( entity, DefaultFlushEntityEventListener::useTracker ); final EventSource source = event.getSession(); source.getFactory() .getCustomEntityDirtinessStrategy() @@ -231,6 +235,10 @@ private boolean isUpdateNecessary(final FlushEntityEvent event, final boolean mi } } + private static void useTracker(final ManagedEntity entity) { + entity.$$_hibernate_setUseTracker( true ); + } + private boolean scheduleUpdate(final FlushEntityEvent event) { final EntityEntry entry = event.getEntityEntry(); final EventSource session = event.getSession(); @@ -553,9 +561,10 @@ private static int[] getDirtyProperties(FlushEntityEvent event) { } else { final Object entity = event.getEntity(); - return isSelfDirtinessTracker( entity ) - ? getDirtyPropertiesFromSelfDirtinessTracker( asSelfDirtinessTracker( entity ), event ) - : getDirtyPropertiesFromCustomEntityDirtinessStrategy( event ); + if ( isSelfDirtinessTracker( entity ) && asManagedEntity( entity ).$$_hibernate_useTracker() ) { + return getDirtyPropertiesFromSelfDirtinessTracker( asSelfDirtinessTracker( entity ), event ); + } + return getDirtyPropertiesFromCustomEntityDirtinessStrategy( event ); } } @@ -595,18 +604,26 @@ private static int[] getDirtyPropertiesFromSelfDirtinessTracker(SelfDirtinessTra final EntityEntry entry = event.getEntityEntry(); final EntityPersister persister = entry.getPersister(); if ( tracker.$$_hibernate_hasDirtyAttributes() || persister.hasMutableProperties() ) { - return persister.resolveDirtyAttributeIndexes( - event.getPropertyValues(), - entry.getLoadedState(), - tracker.$$_hibernate_getDirtyAttributes(), - event.getSession() - ); + return resolveDirtyAttributeIndex( tracker, event, persister, entry ); } else { return ArrayHelper.EMPTY_INT_ARRAY; } } + private static int[] resolveDirtyAttributeIndex( + SelfDirtinessTracker tracker, + FlushEntityEvent event, + EntityPersister persister, + EntityEntry entry) { + return persister.resolveDirtyAttributeIndexes( + event.getPropertyValues(), + entry.getLoadedState(), + tracker.$$_hibernate_getDirtyAttributes(), + event.getSession() + ); + } + private static class DirtyCheckAttributeInfoImpl implements CustomEntityDirtinessStrategy.AttributeInformation { private final FlushEntityEvent event; private final EntityPersister persister; diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java index 675c3de9ce45..2abf27aef5be 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java @@ -22,6 +22,7 @@ import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; @@ -47,6 +48,7 @@ import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; +import static org.hibernate.engine.internal.ManagedTypeHelper.asManagedEntity; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.asSelfDirtinessTracker; import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy; @@ -514,10 +516,18 @@ private static void markInterceptorDirty(final Object entity, final Object targe // for enhanced entities, copy over the dirty attributes if ( isSelfDirtinessTracker( entity ) && isSelfDirtinessTracker( target ) ) { // clear, because setting the embedded attributes dirties them + final ManagedEntity managedEntity = asManagedEntity( target ); + final boolean useTracker = asManagedEntity( entity ).$$_hibernate_useTracker(); final SelfDirtinessTracker selfDirtinessTrackerTarget = asSelfDirtinessTracker( target ); - selfDirtinessTrackerTarget.$$_hibernate_clearDirtyAttributes(); - for ( String fieldName : asSelfDirtinessTracker( entity ).$$_hibernate_getDirtyAttributes() ) { - selfDirtinessTrackerTarget.$$_hibernate_trackChange( fieldName ); + if ( !selfDirtinessTrackerTarget.$$_hibernate_hasDirtyAttributes() && !useTracker ) { + managedEntity.$$_hibernate_setUseTracker( false ); + } + else { + managedEntity.$$_hibernate_setUseTracker( true ); + selfDirtinessTrackerTarget.$$_hibernate_clearDirtyAttributes(); + for ( String fieldName : asSelfDirtinessTracker( entity ).$$_hibernate_getDirtyAttributes() ) { + selfDirtinessTrackerTarget.$$_hibernate_trackChange( fieldName ); + } } } } 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 3adde0a9f75d..de92b43ab2ed 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 @@ -90,6 +90,7 @@ import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.NaturalIdResolutions; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptable; @@ -304,6 +305,7 @@ import static java.util.Collections.emptySet; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; +import static org.hibernate.engine.internal.ManagedTypeHelper.processIfManagedEntity; import static org.hibernate.engine.internal.ManagedTypeHelper.processIfPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.processIfSelfDirtinessTracker; import static org.hibernate.engine.internal.Versioning.isVersionIncrementRequired; @@ -4403,12 +4405,17 @@ public void afterInitialize(Object entity, SharedSessionContractImplementor sess // clear the fields that are marked as dirty in the dirtiness tracker processIfSelfDirtinessTracker( entity, AbstractEntityPersister::clearDirtyAttributes ); + processIfManagedEntity( entity, AbstractEntityPersister::useTracker ); } private static void clearDirtyAttributes(final SelfDirtinessTracker entity) { entity.$$_hibernate_clearDirtyAttributes(); } + private static void useTracker(final ManagedEntity entity) { + entity.$$_hibernate_setUseTracker( true ); + } + @Override public String[] getPropertyNames() { return entityMetamodel.getPropertyNames(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/SimpleEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/SimpleEntity.java index e32cf99e9ce1..66c93e8a6681 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/SimpleEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/SimpleEntity.java @@ -59,4 +59,14 @@ public class SimpleEntity implements ManagedEntity { public void $$_hibernate_setNextManagedEntity(ManagedEntity next) { } + + @Override + public void $$_hibernate_setUseTracker(boolean useTracker) { + + } + + @Override + public boolean $$_hibernate_useTracker() { + return false; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/basic/BasicSessionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/basic/BasicSessionTest.java index 2d2000dbdbb7..c9aae9149dd0 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/basic/BasicSessionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/basic/BasicSessionTest.java @@ -123,5 +123,15 @@ private static class MyEntity implements ManagedEntity { public void $$_hibernate_setPreviousManagedEntity(ManagedEntity previous) { this.previous = previous; } + + @Override + public void $$_hibernate_setUseTracker(boolean useTracker) { + + } + + @Override + public boolean $$_hibernate_useTracker() { + return false; + } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cache/ByteCodeEnhancedImmutableReferenceCacheTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cache/ByteCodeEnhancedImmutableReferenceCacheTest.java index fd5ecf9a16e7..0fd8db17a2b1 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/cache/ByteCodeEnhancedImmutableReferenceCacheTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cache/ByteCodeEnhancedImmutableReferenceCacheTest.java @@ -310,5 +310,14 @@ public void setTheValue(String theValue) { this.previous = previous; } + @Override + public void $$_hibernate_setUseTracker(boolean useTracker) { + + } + + @Override + public boolean $$_hibernate_useTracker() { + return false; + } } }