Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HHH-17761 Merging a bytecode enhanced entity with all properties set to null does not apply the update #8091

Merged
merged 3 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,14 @@ private DynamicType.Builder<?> doEnhance(Supplier<DynamicType.Builder<?>> 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 ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand Down Expand Up @@ -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 );

Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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()
Expand All @@ -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() );
Expand Down Expand Up @@ -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 ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,28 @@ 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<SelfDirtinessTracker> because of JDK-8180450:
// use a custom functional interface with explicit type.
@FunctionalInterface
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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()
Expand All @@ -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();
Expand Down Expand Up @@ -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 );
}
}

Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 );
}
}
}
}
Expand Down
Loading
Loading