Skip to content

Commit 2669848

Browse files
beikovSanne
authored andcommitted
HHH-14329 consider mutable types always as potentially dirty when using DirtinessTracker
1 parent 5ea0d92 commit 2669848

File tree

8 files changed

+122
-27
lines changed

8 files changed

+122
-27
lines changed

hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ private List<AnnotatedFieldDescription> collectCollectionFields(TypeDescription
411411
continue;
412412
}
413413
AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( enhancementContext, ctField );
414-
if ( enhancementContext.isPersistentField( annotatedField ) && !enhancementContext.isMappedCollection( annotatedField ) ) {
414+
if ( enhancementContext.isPersistentField( annotatedField ) && enhancementContext.isMappedCollection( annotatedField ) ) {
415415
if ( ctField.getType().asErasure().isAssignableTo( Collection.class ) || ctField.getType().asErasure().isAssignableTo( Map.class ) ) {
416416
collectionList.add( annotatedField );
417417
}
@@ -441,7 +441,7 @@ private Collection<AnnotatedFieldDescription> collectInheritCollectionFields(Typ
441441
for ( FieldDescription ctField : managedCtSuperclass.getDeclaredFields() ) {
442442
if ( !Modifier.isStatic( ctField.getModifiers() ) ) {
443443
AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( enhancementContext, ctField );
444-
if ( enhancementContext.isPersistentField( annotatedField ) && !enhancementContext.isMappedCollection( annotatedField ) ) {
444+
if ( enhancementContext.isPersistentField( annotatedField ) && enhancementContext.isMappedCollection( annotatedField ) ) {
445445
if ( ctField.getType().asErasure().isAssignableTo( Collection.class ) || ctField.getType().asErasure().isAssignableTo( Map.class ) ) {
446446
collectionList.add( annotatedField );
447447
}

hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/EntityEnhancer.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ private List<CtField> collectCollectionFields(CtClass managedCtClass) {
283283
if ( Modifier.isStatic( ctField.getModifiers() ) || ctField.getName().startsWith( "$$_hibernate_" ) ) {
284284
continue;
285285
}
286-
if ( enhancementContext.isPersistentField( ctField ) && !enhancementContext.isMappedCollection( ctField ) ) {
286+
if ( enhancementContext.isPersistentField( ctField ) && enhancementContext.isMappedCollection( ctField ) ) {
287287
if ( PersistentAttributesHelper.isAssignable( ctField, Collection.class.getName() ) ||
288288
PersistentAttributesHelper.isAssignable( ctField, Map.class.getName() ) ) {
289289
collectionList.add( ctField );
@@ -314,7 +314,7 @@ private Collection<CtField> collectInheritCollectionFields(CtClass managedCtClas
314314

315315
for ( CtField ctField : managedCtSuperclass.getDeclaredFields() ) {
316316
if ( !Modifier.isStatic( ctField.getModifiers() ) ) {
317-
if ( enhancementContext.isPersistentField( ctField ) && !enhancementContext.isMappedCollection( ctField ) ) {
317+
if ( enhancementContext.isPersistentField( ctField ) && enhancementContext.isMappedCollection( ctField ) ) {
318318
if ( PersistentAttributesHelper.isAssignable( ctField, Collection.class.getName() ) ||
319319
PersistentAttributesHelper.isAssignable( ctField, Map.class.getName() ) ) {
320320
collectionList.add( ctField );

hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/DefaultEnhancementContext.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
*/
77
package org.hibernate.bytecode.enhance.spi;
88

9+
import javax.persistence.Basic;
10+
import javax.persistence.Convert;
911
import javax.persistence.ElementCollection;
1012
import javax.persistence.Embeddable;
1113
import javax.persistence.Entity;
@@ -106,7 +108,13 @@ public boolean isPersistentField(UnloadedField ctField) {
106108
*/
107109
@Override
108110
public boolean isMappedCollection(UnloadedField field) {
109-
return field.hasAnnotation( OneToMany.class ) || field.hasAnnotation( ManyToMany.class ) || field.hasAnnotation( ElementCollection.class );
111+
// If the collection is definitely a plural attribute, we respect that
112+
if (field.hasAnnotation( OneToMany.class ) || field.hasAnnotation( ManyToMany.class ) || field.hasAnnotation( ElementCollection.class )) {
113+
return true;
114+
}
115+
// But a collection might be treated like a singular attribute if it is annotated with `@Basic`
116+
// If no annotations are given though, a collection is treated like a OneToMany
117+
return !field.hasAnnotation( Basic.class );
110118
}
111119

112120
/**

hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.hibernate.persister.entity.EntityPersister;
3434
import org.hibernate.persister.entity.UniqueKeyLoadable;
3535
import org.hibernate.pretty.MessageHelper;
36+
import org.hibernate.proxy.HibernateProxy;
3637

3738
/**
3839
* A base implementation of EntityEntry
@@ -346,7 +347,24 @@ public boolean requiresDirtyCheck(Object entity) {
346347
@SuppressWarnings( {"SimplifiableIfStatement"})
347348
private boolean isUnequivocallyNonDirty(Object entity) {
348349
if ( entity instanceof SelfDirtinessTracker ) {
349-
return ! persister.hasCollections() && ! ( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes();
350+
boolean uninitializedProxy = false;
351+
if ( entity instanceof PersistentAttributeInterceptable ) {
352+
final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) entity;
353+
final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor();
354+
if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) {
355+
EnhancementAsProxyLazinessInterceptor enhancementAsProxyLazinessInterceptor = (EnhancementAsProxyLazinessInterceptor) interceptor;
356+
// When a proxy has dirty attributes, we have to treat it like a normal entity to flush changes
357+
uninitializedProxy = !enhancementAsProxyLazinessInterceptor.isInitialized() && !( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes();
358+
}
359+
}
360+
else if ( entity instanceof HibernateProxy ) {
361+
uninitializedProxy = ( (HibernateProxy) entity ).getHibernateLazyInitializer()
362+
.isUninitialized();
363+
}
364+
// we never have to check an uninitialized proxy
365+
return uninitializedProxy || !persister.hasCollections()
366+
&& !persister.hasMutableProperties()
367+
&& !( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes();
350368
}
351369

352370
if ( entity instanceof PersistentAttributeInterceptable ) {

hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java

+9-12
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.hibernate.engine.internal.Versioning;
2323
import org.hibernate.engine.spi.EntityEntry;
2424
import org.hibernate.engine.spi.EntityKey;
25+
import org.hibernate.engine.spi.ManagedEntity;
2526
import org.hibernate.engine.spi.PersistenceContext;
2627
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
2728
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
@@ -39,6 +40,7 @@
3940
import org.hibernate.metadata.ClassMetadata;
4041
import org.hibernate.persister.entity.EntityPersister;
4142
import org.hibernate.pretty.MessageHelper;
43+
import org.hibernate.proxy.HibernateProxy;
4244
import org.hibernate.stat.spi.StatisticsImplementor;
4345
import org.hibernate.type.Type;
4446

@@ -525,18 +527,13 @@ protected void dirtyCheck(final FlushEntityEvent event) throws HibernateExceptio
525527

526528
if ( dirtyProperties == null ) {
527529
if ( entity instanceof SelfDirtinessTracker ) {
528-
if ( ( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes() ) {
529-
int[] dirty = persister.resolveAttributeIndexes( ( (SelfDirtinessTracker) entity ).$$_hibernate_getDirtyAttributes() );
530-
531-
// HHH-12051 - filter non-updatable attributes
532-
// TODO: add Updatability to EnhancementContext and skip dirty tracking of those attributes
533-
int count = 0;
534-
for ( int i : dirty ) {
535-
if ( persister.getPropertyUpdateability()[i] ) {
536-
dirty[count++] = i;
537-
}
538-
}
539-
dirtyProperties = count == 0 ? ArrayHelper.EMPTY_INT_ARRAY : count == dirty.length ? dirty : Arrays.copyOf( dirty, count );
530+
if ( ( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes() || persister.hasMutableProperties() ) {
531+
dirtyProperties = persister.resolveDirtyAttributeIndexes(
532+
values,
533+
loadedState,
534+
( (SelfDirtinessTracker) entity ).$$_hibernate_getDirtyAttributes(),
535+
session
536+
);
540537
}
541538
else {
542539
dirtyProperties = ArrayHelper.EMPTY_INT_ARRAY;

hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java

+48-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.sql.SQLException;
1313
import java.util.ArrayList;
1414
import java.util.Arrays;
15+
import java.util.BitSet;
1516
import java.util.Collections;
1617
import java.util.Comparator;
1718
import java.util.HashMap;
@@ -67,7 +68,6 @@
6768
import org.hibernate.engine.jdbc.spi.JdbcServices;
6869
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
6970
import org.hibernate.engine.spi.CascadeStyle;
70-
import org.hibernate.engine.spi.CascadingAction;
7171
import org.hibernate.engine.spi.CascadingActions;
7272
import org.hibernate.engine.spi.CollectionKey;
7373
import org.hibernate.engine.spi.EntityEntry;
@@ -81,6 +81,7 @@
8181
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
8282
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
8383
import org.hibernate.engine.spi.SessionFactoryImplementor;
84+
import org.hibernate.engine.spi.SessionImplementor;
8485
import org.hibernate.engine.spi.SharedSessionContractImplementor;
8586
import org.hibernate.engine.spi.ValueInclusion;
8687
import org.hibernate.event.spi.EventSource;
@@ -104,7 +105,6 @@
104105
import org.hibernate.loader.entity.BatchingEntityLoaderBuilder;
105106
import org.hibernate.loader.entity.CacheEntityLoaderHelper;
106107
import org.hibernate.loader.entity.CascadeEntityLoader;
107-
import org.hibernate.loader.entity.plan.DynamicBatchingEntityLoaderBuilder;
108108
import org.hibernate.loader.entity.EntityLoader;
109109
import org.hibernate.loader.entity.UniqueEntityLoader;
110110
import org.hibernate.loader.entity.plan.MultiEntityLoadingSupport;
@@ -2261,6 +2261,52 @@ public int[] resolveAttributeIndexes(String[] attributeNames) {
22612261
return Arrays.copyOf( fields, counter );
22622262
}
22632263

2264+
@Override
2265+
public int[] resolveDirtyAttributeIndexes(
2266+
final Object[] currentState,
2267+
final Object[] previousState,
2268+
final String[] attributeNames,
2269+
final SessionImplementor session) {
2270+
final BitSet mutablePropertiesIndexes = entityMetamodel.getMutablePropertiesIndexes();
2271+
final int estimatedSize = attributeNames == null ? 0 : attributeNames.length + mutablePropertiesIndexes.cardinality();
2272+
final List<Integer> fields = new ArrayList<>( estimatedSize );
2273+
if ( estimatedSize == 0 ) {
2274+
return ArrayHelper.EMPTY_INT_ARRAY;
2275+
}
2276+
if ( !mutablePropertiesIndexes.isEmpty() ) {
2277+
// We have to check the state for "mutable" properties as dirty tracking isn't aware of mutable types
2278+
final Type[] propertyTypes = entityMetamodel.getPropertyTypes();
2279+
final boolean[] propertyCheckability = entityMetamodel.getPropertyCheckability();
2280+
mutablePropertiesIndexes.stream().forEach( i -> {
2281+
// This is kindly borrowed from org.hibernate.type.TypeHelper.findDirty
2282+
final boolean dirty = currentState[i] != LazyPropertyInitializer.UNFETCHED_PROPERTY &&
2283+
( previousState[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY ||
2284+
( propertyCheckability[i]
2285+
&& propertyTypes[i].isDirty(
2286+
previousState[i],
2287+
currentState[i],
2288+
propertyColumnUpdateable[i],
2289+
session
2290+
) ) );
2291+
if ( dirty ) {
2292+
fields.add( i );
2293+
}
2294+
} );
2295+
}
2296+
2297+
if ( attributeNames != null ) {
2298+
final boolean[] propertyUpdateability = entityMetamodel.getPropertyUpdateability();
2299+
for ( String attributeName : attributeNames ) {
2300+
final Integer index = entityMetamodel.getPropertyIndexOrNull( attributeName );
2301+
if ( index != null && propertyUpdateability[index] && !fields.contains( index ) ) {
2302+
fields.add( index );
2303+
}
2304+
}
2305+
}
2306+
2307+
return ArrayHelper.toIntArray( fields );
2308+
}
2309+
22642310
protected String[] getSubclassPropertySubclassNameClosure() {
22652311
return subclassPropertySubclassNameClosure;
22662312
}

hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
import org.hibernate.MappingException;
1818
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
1919
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
20-
import org.hibernate.bytecode.spi.NotInstrumentedException;
2120
import org.hibernate.cache.spi.access.EntityDataAccess;
2221
import org.hibernate.cache.spi.access.NaturalIdDataAccess;
2322
import org.hibernate.cache.spi.entry.CacheEntry;
2423
import org.hibernate.cache.spi.entry.CacheEntryStructure;
2524
import org.hibernate.engine.spi.CascadeStyle;
2625
import org.hibernate.engine.spi.EntityEntryFactory;
2726
import org.hibernate.engine.spi.SessionFactoryImplementor;
27+
import org.hibernate.engine.spi.SessionImplementor;
2828
import org.hibernate.engine.spi.SharedSessionContractImplementor;
2929
import org.hibernate.engine.spi.ValueInclusion;
3030
import org.hibernate.id.IdentifierGenerator;
@@ -840,6 +840,25 @@ default BytecodeEnhancementMetadata getBytecodeEnhancementMetadata() {
840840
*/
841841
int[] resolveAttributeIndexes(String[] attributeNames);
842842

843+
/**
844+
* Like {@link #resolveAttributeIndexes(String[])} but also always returns mutable attributes
845+
*
846+
*
847+
* @param values
848+
* @param loadedState
849+
* @param attributeNames Array of names to be resolved
850+
*
851+
* @param session
852+
* @return A set of unique indexes of the attribute names found in the metamodel
853+
*/
854+
default int[] resolveDirtyAttributeIndexes(
855+
Object[] values,
856+
Object[] loadedState,
857+
String[] attributeNames,
858+
SessionImplementor session) {
859+
return resolveAttributeIndexes( attributeNames );
860+
}
861+
843862
boolean canUseReferenceCacheEntries();
844863

845864
/**

hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java

+13-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import java.io.Serializable;
1010
import java.util.ArrayList;
11+
import java.util.BitSet;
1112
import java.util.Collections;
1213
import java.util.HashMap;
1314
import java.util.HashSet;
@@ -44,6 +45,7 @@
4445
import org.hibernate.tuple.ValueGeneration;
4546
import org.hibernate.tuple.ValueGenerator;
4647
import org.hibernate.type.AssociationType;
48+
import org.hibernate.type.ComponentType;
4749
import org.hibernate.type.CompositeType;
4850
import org.hibernate.type.EntityType;
4951
import org.hibernate.type.Type;
@@ -97,7 +99,7 @@ public class EntityMetamodel implements Serializable {
9799

98100
private final Map<String, Integer> propertyIndexes = new HashMap<>();
99101
private final boolean hasCollections;
100-
private final boolean hasMutableProperties;
102+
private final BitSet mutablePropertiesIndexes;
101103
private final boolean hasLazyProperties;
102104
private final boolean hasNonIdentifierPropertyNamedId;
103105

@@ -208,7 +210,7 @@ public EntityMetamodel(
208210
int tempVersionProperty = NO_VERSION_INDX;
209211
boolean foundCascade = false;
210212
boolean foundCollection = false;
211-
boolean foundMutable = false;
213+
BitSet mutableIndexes = new BitSet();
212214
boolean foundNonIdentifierPropertyNamedId = false;
213215
boolean foundUpdateableNaturalIdProperty = false;
214216

@@ -318,8 +320,9 @@ else if ( timing == GenerationTiming.ALWAYS ) {
318320
foundCollection = true;
319321
}
320322

321-
if ( propertyTypes[i].isMutable() && propertyCheckability[i] ) {
322-
foundMutable = true;
323+
// Component types are dirty tracked as well so they are not exactly mutable for the "maybeDirty" check
324+
if ( propertyTypes[i].isMutable() && propertyCheckability[i] && !( propertyTypes[i] instanceof ComponentType ) ) {
325+
mutableIndexes.set( i );
323326
}
324327

325328
mapPropertyToIndex(prop, i);
@@ -395,7 +398,7 @@ else if ( timing == GenerationTiming.ALWAYS ) {
395398
}
396399

397400
hasCollections = foundCollection;
398-
hasMutableProperties = foundMutable;
401+
mutablePropertiesIndexes = mutableIndexes;
399402

400403
iter = persistentClass.getSubclassIterator();
401404
final Set<String> subclassEntityNamesLocal = new HashSet<>();
@@ -890,7 +893,11 @@ public boolean hasCollections() {
890893
}
891894

892895
public boolean hasMutableProperties() {
893-
return hasMutableProperties;
896+
return !mutablePropertiesIndexes.isEmpty();
897+
}
898+
899+
public BitSet getMutablePropertiesIndexes() {
900+
return mutablePropertiesIndexes;
894901
}
895902

896903
public boolean hasNonIdentifierPropertyNamedId() {

0 commit comments

Comments
 (0)