HHH-9985 - bytecode enhancer - fix merge use case

This commit is contained in:
barreiro 2015-07-25 06:26:40 +01:00 committed by Steve Ebersole
parent 463decd245
commit f759c152e2
11 changed files with 121 additions and 36 deletions

View File

@ -18,7 +18,8 @@ import javassist.Modifier;
import javassist.NotFoundException;
import org.hibernate.bytecode.enhance.internal.tracker.CollectionTracker;
import org.hibernate.bytecode.enhance.internal.tracker.SimpleDirtyTracker;
import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker;
import org.hibernate.bytecode.enhance.internal.tracker.SimpleFieldTracker;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.EnhancementException;
import org.hibernate.bytecode.enhance.spi.Enhancer;
@ -36,8 +37,8 @@ public class EntityEnhancer extends Enhancer {
super( context );
}
// for very small sizes SimpleDirtyTracker implementation ends up being faster
private static final String TRACKER_IMPL = SimpleDirtyTracker.class.getName();
// assuming the number of fields is not very high, SimpleFieldTracker implementation it's the fastest
private static final String DIRTY_TRACKER_IMPL = SimpleFieldTracker.class.getName();
public void enhance(CtClass managedCtClass) {
// add the ManagedEntity interface
@ -107,7 +108,11 @@ public class EntityEnhancer extends Enhancer {
try {
managedCtClass.addInterface( classPool.get( SelfDirtinessTracker.class.getName() ) );
FieldWriter.addField( managedCtClass, classPool.get( TRACKER_IMPL ), EnhancerConstants.TRACKER_FIELD_NAME );
FieldWriter.addField(
managedCtClass,
classPool.get( DIRTY_TRACKER_IMPL ),
EnhancerConstants.TRACKER_FIELD_NAME
);
FieldWriter.addField(
managedCtClass,
classPool.get( CollectionTracker.class.getName() ),
@ -124,14 +129,14 @@ public class EntityEnhancer extends Enhancer {
private void createDirtyTrackerMethods(CtClass managedCtClass) {
try {
MethodWriter.write(
managedCtClass, "" +
managedCtClass,
"public void %1$s(String name) {%n" +
" if (%2$s == null) { %2$s = new %3$s(); }%n" +
" %2$s.add(name);%n" +
"}",
EnhancerConstants.TRACKER_CHANGER_NAME,
EnhancerConstants.TRACKER_FIELD_NAME,
TRACKER_IMPL
DIRTY_TRACKER_IMPL
);
createCollectionDirtyCheckMethod( managedCtClass );
@ -139,7 +144,7 @@ public class EntityEnhancer extends Enhancer {
createClearDirtyCollectionMethod( managedCtClass );
MethodWriter.write(
managedCtClass, "" +
managedCtClass,
"public String[] %1$s() {%n" +
" if(%3$s == null) {%n" +
" return (%2$s == null) ? new String[0] : %2$s.get();%n" +
@ -153,12 +158,11 @@ public class EntityEnhancer extends Enhancer {
EnhancerConstants.TRACKER_FIELD_NAME,
EnhancerConstants.TRACKER_COLLECTION_NAME,
EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME,
TRACKER_IMPL
DIRTY_TRACKER_IMPL
);
MethodWriter.write(
managedCtClass,
"" +
"public boolean %1$s() {%n" +
" return (%2$s != null && !%2$s.isEmpty()) || %3$s();%n" +
"}",
@ -169,7 +173,6 @@ public class EntityEnhancer extends Enhancer {
MethodWriter.write(
managedCtClass,
"" +
"public void %1$s() {%n" +
" if (%2$s != null) { %2$s.clear(); }%n" +
" %3$s();%n" +
@ -213,7 +216,6 @@ public class EntityEnhancer extends Enhancer {
body.append(
String.format(
"" +
"private boolean %1$s() {%n" +
" if (%2$s == null) {%n" +
" return false;%n" +
@ -227,7 +229,6 @@ public class EntityEnhancer extends Enhancer {
if ( !enhancementContext.isMappedCollection( ctField ) ) {
body.append(
String.format(
"" +
" // collection field [%1$s]%n" +
" if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { return true; }%n" +
" if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { return true; }%n",
@ -252,12 +253,11 @@ public class EntityEnhancer extends Enhancer {
body.append(
String.format(
"" +
"private void %1$s(%3$s tracker) {%n" +
" if (%2$s == null) { return; }%n",
EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME,
EnhancerConstants.TRACKER_COLLECTION_NAME,
TRACKER_IMPL
DirtyTracker.class.getName()
)
);
@ -265,7 +265,6 @@ public class EntityEnhancer extends Enhancer {
if ( !enhancementContext.isMappedCollection( ctField ) ) {
body.append(
String.format(
"" +
" // Collection field [%1$s]%n" +
" if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { tracker.add(\"%1$s\"); }%n" +
" if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { tracker.add(\"%1$s\"); }%n",
@ -290,7 +289,6 @@ public class EntityEnhancer extends Enhancer {
body.append(
String.format(
"" +
"private void %1$s() {%n" +
" if (%2$s == null) { %2$s = new %3$s(); }%n",
EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME,
@ -303,7 +301,6 @@ public class EntityEnhancer extends Enhancer {
if ( !enhancementContext.isMappedCollection( ctField ) ) {
body.append(
String.format(
"" +
" // Collection field [%1$s]%n" +
" if (%1$s == null) { %2$s.add(\"%1$s\", -1); }%n" +
" else { %2$s.add(\"%1$s\", %1$s.size()); }%n",

View File

@ -0,0 +1,25 @@
/*
* 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.bytecode.enhance.internal.tracker;
/**
* Interface to be implemented by dirty trackers, a simplified Set of String.
*
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
*/
public interface DirtyTracker {
void add(String name);
boolean contains(String name);
void clear();
boolean isEmpty();
String[] get();
}

View File

@ -16,14 +16,15 @@ import java.util.Arrays;
*
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
*/
public final class SimpleDirtyTracker {
public final class SimpleFieldTracker implements DirtyTracker {
private String[] names;
public SimpleDirtyTracker() {
public SimpleFieldTracker() {
names = new String[0];
}
@Override
public void add(String name) {
if ( !contains( name ) ) {
names = Arrays.copyOf( names, names.length + 1 );
@ -31,6 +32,7 @@ public final class SimpleDirtyTracker {
}
}
@Override
public boolean contains(String name) {
for ( String existing : names ) {
if ( existing.equals( name ) ) {
@ -40,14 +42,17 @@ public final class SimpleDirtyTracker {
return false;
}
@Override
public void clear() {
names = new String[0];
}
@Override
public boolean isEmpty() {
return names.length == 0;
}
@Override
public String[] get() {
return names;
}

View File

@ -13,14 +13,15 @@ package org.hibernate.bytecode.enhance.internal.tracker;
*
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
*/
public final class SortedDirtyTracker {
public final class SortedFieldTracker implements DirtyTracker {
private String[] names;
public SortedDirtyTracker() {
public SortedFieldTracker() {
names = new String[0];
}
@Override
public void add(String name) {
// we do a binary search: even if we don't find the name at least we get the position to insert into the array
int insert = 0;
@ -41,12 +42,13 @@ public final class SortedDirtyTracker {
}
}
final String[] newNames = new String[names.length + 1];
System.arraycopy( names, 0, newNames, 0, insert);
System.arraycopy( names, insert, newNames, insert + 1, names.length - insert);
System.arraycopy( names, 0, newNames, 0, insert );
System.arraycopy( names, insert, newNames, insert + 1, names.length - insert );
newNames[insert] = name;
names = newNames;
}
@Override
public boolean contains(String name) {
for ( int low = 0, high = names.length - 1; low <= high; ) {
final int middle = low + ( ( high - low ) / 2 );
@ -66,14 +68,17 @@ public final class SortedDirtyTracker {
return false;
}
@Override
public void clear() {
names = new String[0];
}
@Override
public boolean isEmpty() {
return names.length == 0;
}
@Override
public String[] get() {
return names;
}

View File

@ -11,7 +11,8 @@ package org.hibernate.bytecode.enhance.spi;
*
* @author Steve Ebersole
*/
public class EnhancerConstants {
public final class EnhancerConstants {
/**
* Prefix for persistent-field reader methods.
*/
@ -141,22 +142,34 @@ public class EnhancerConstants {
*/
public static final String TRACKER_COLLECTION_CHANGED_NAME = "$$_hibernate_areCollectionFieldsDirty";
/**
* Name of the field that holds the collection tracker
*/
public static final String TRACKER_COLLECTION_NAME = "$$_hibernate_collectionTracker";
/**
* Name of method to get dirty collection field names
*/
public static final String TRACKER_COLLECTION_CHANGED_FIELD_NAME = "$$_hibernate_getCollectionFieldDirtyNames";
/**
* Name of method to clear dirty attribute on collection fields
*/
public static final String TRACKER_COLLECTION_CLEAR_NAME = "$$_hibernate_clearDirtyCollectionNames";
public static final String TRACKER_COMPOSITE_DIRTY_CHECK = "$$_hibernate_areCompositeFieldsDirty";
public static final String TRACKER_COMPOSITE_DIRTY_FIELDS_GETTER = "$$_hibernate_getCompositeDirtyFields";
/**
* Field to hold the track the owner of the embeddable entity
*/
public static final String TRACKER_COMPOSITE_FIELD_NAME = "$$_hibernate_compositeOwners";
/**
* Method to set the owner of the embedded entity
*/
public static final String TRACKER_COMPOSITE_SET_OWNER = "$$_hibernate_setOwner";
/**
* Method to clear the owner of the embedded entity
*/
public static final String TRACKER_COMPOSITE_CLEAR_OWNER = "$$_hibernate_clearOwner";
private EnhancerConstants() {

View File

@ -10,7 +10,7 @@ package org.hibernate.bytecode.enhance.spi.interceptor;
import java.util.Set;
import org.hibernate.LazyInitializationException;
import org.hibernate.bytecode.enhance.internal.tracker.SimpleDirtyTracker;
import org.hibernate.bytecode.enhance.internal.tracker.SimpleFieldTracker;
import org.hibernate.bytecode.instrumentation.spi.LazyPropertyInitializer;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.SessionImplementor;
@ -26,7 +26,7 @@ public class LazyAttributeLoader implements PersistentAttributeInterceptor {
private final Set<String> lazyFields;
private final String entityName;
private final SimpleDirtyTracker initializedFields = new SimpleDirtyTracker();
private final SimpleFieldTracker initializedFields = new SimpleFieldTracker();
public LazyAttributeLoader(SessionImplementor session, Set<String> lazyFields, String entityName) {
this.session = session;
@ -58,6 +58,14 @@ public class LazyAttributeLoader implements PersistentAttributeInterceptor {
}
}
public void setLoaded(String attributeName) {
initializedFields.add( attributeName );
}
public String[] getiInitializedFields() {
return initializedFields.get();
}
@Override
public String toString() {
return "LazyAttributeLoader(entityName=" + entityName + " ,lazyFields=" + lazyFields + ')';

View File

@ -287,7 +287,7 @@ public abstract class AbstractEntityEntry implements Serializable, EntityEntry {
}
if( entity instanceof SelfDirtinessTracker ) {
((SelfDirtinessTracker) entity).$$_hibernate_clearDirtyAttributes();
( (SelfDirtinessTracker) entity ).$$_hibernate_clearDirtyAttributes();
}
persistenceContext.getSession()
@ -342,7 +342,7 @@ public abstract class AbstractEntityEntry implements Serializable, EntityEntry {
private boolean isUnequivocallyNonDirty(Object entity) {
if(entity instanceof SelfDirtinessTracker) {
return ((SelfDirtinessTracker) entity).$$_hibernate_hasDirtyAttributes();
return ! ( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes();
}
final CustomEntityDirtinessStrategy customEntityDirtinessStrategy =

View File

@ -32,6 +32,11 @@ public interface SelfDirtinessTracker {
*/
String[] $$_hibernate_getDirtyAttributes();
/**
* Adds persistent attribute to the set of values that have changed
*/
void $$_hibernate_trackChange(String attributes);
/**
* Clear the stored dirty attributes
*/

View File

@ -15,6 +15,7 @@ import org.hibernate.ObjectDeletedException;
import org.hibernate.StaleObjectStateException;
import org.hibernate.WrongClassException;
import org.hibernate.boot.registry.selector.spi.StrategySelector;
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoader;
import org.hibernate.bytecode.instrumentation.spi.FieldInterceptor;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.internal.Cascade;
@ -23,6 +24,9 @@ import org.hibernate.engine.spi.CascadingAction;
import org.hibernate.engine.spi.CascadingActions;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.SelfDirtinessTracker;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.event.spi.EntityCopyObserver;
@ -341,6 +345,27 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
interceptor.dirty();
}
}
// for enhanced entities, copy over the dirty attributes and the lazy/loaded fields in the interceptor
if ( entity instanceof SelfDirtinessTracker && target instanceof SelfDirtinessTracker ) {
for ( String fieldName : ( (SelfDirtinessTracker) entity ).$$_hibernate_getDirtyAttributes() ) {
( (SelfDirtinessTracker) target ).$$_hibernate_trackChange( fieldName );
}
}
if ( entity instanceof PersistentAttributeInterceptable
&& target instanceof PersistentAttributeInterceptable
&& ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor() != null
&& ( (PersistentAttributeInterceptable) target ).$$_hibernate_getInterceptor() != null ) {
PersistentAttributeInterceptor entityInterceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor();
PersistentAttributeInterceptor targetInterceptor = ( (PersistentAttributeInterceptable) target ).$$_hibernate_getInterceptor();
if ( entityInterceptor instanceof LazyAttributeLoader && targetInterceptor instanceof LazyAttributeLoader ) {
for ( String fieldName : ( (LazyAttributeLoader) entityInterceptor ).getiInitializedFields() ) {
( (LazyAttributeLoader) targetInterceptor ).setLoaded( fieldName );
}
}
}
}
private boolean isVersionChanged(Object entity, EventSource source, EntityPersister persister, Object target) {

View File

@ -98,6 +98,7 @@ public abstract class DecompileUtils {
}
if ( interfaceNames.contains( SelfDirtinessTracker.class.getName() ) ) {
assertTrue( fieldNames.contains( EnhancerConstants.TRACKER_FIELD_NAME ) );
assertTrue( methodNames.contains( EnhancerConstants.TRACKER_CHANGER_NAME ) );
assertTrue( methodNames.contains( EnhancerConstants.TRACKER_GET_NAME ) );
assertTrue( methodNames.contains( EnhancerConstants.TRACKER_CLEAR_NAME ) );
assertTrue( methodNames.contains( EnhancerConstants.TRACKER_HAS_CHANGED_NAME ) );

View File

@ -6,8 +6,9 @@
*/
package org.hibernate.test.bytecode.enhancement.tracker;
import org.hibernate.bytecode.enhance.internal.tracker.SimpleDirtyTracker;
import org.hibernate.bytecode.enhance.internal.tracker.SortedDirtyTracker;
import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker;
import org.hibernate.bytecode.enhance.internal.tracker.SimpleFieldTracker;
import org.hibernate.bytecode.enhance.internal.tracker.SortedFieldTracker;
import org.junit.Test;
@ -22,7 +23,7 @@ public class DirtyTrackerTest {
@Test
public void testSimpleTracker() {
SimpleDirtyTracker tracker = new SimpleDirtyTracker();
DirtyTracker tracker = new SimpleFieldTracker();
assertTrue(tracker.isEmpty());
assertTrue(tracker.get().length == 0);
@ -46,7 +47,7 @@ public class DirtyTrackerTest {
@Test
public void testSortedTracker() {
SortedDirtyTracker tracker = new SortedDirtyTracker();
DirtyTracker tracker = new SortedFieldTracker();
assertTrue(tracker.isEmpty());
assertTrue(tracker.get().length == 0);