HHH-10017 - bytecode enhancement - consistent handling of persistent attributes, regardeless of access type

This commit is contained in:
barreiro 2015-08-05 08:58:14 +01:00
parent 603a410fdc
commit ed185b9b48
14 changed files with 686 additions and 195 deletions

View File

@ -34,15 +34,13 @@ public abstract class AttributeTypeDescriptor {
final StringBuilder builder = new StringBuilder();
try {
// should ignore primary keys
for ( Object o : currentValue.getType().getAnnotations() ) {
if ( o instanceof Id) {
return "";
}
if (PersistentAttributesHelper.hasAnnotation( currentValue, Id.class ) ) {
return "";
}
// primitives || enums
if ( currentValue.getType().isPrimitive() || currentValue.getType().isEnum() ) {
builder.append( String.format( "if (%s != $1)", currentValue.getName() ) );
builder.append( String.format( " if (%s != $1)", currentValue.getName() ) );
}
// simple data types
else if ( currentValue.getType().getName().startsWith( "java.lang" )
@ -51,7 +49,7 @@ public abstract class AttributeTypeDescriptor {
|| currentValue.getType().getName().startsWith( "java.sql.Date" )
|| currentValue.getType().getName().startsWith( "java.util.Date" )
|| currentValue.getType().getName().startsWith( "java.util.Calendar" ) ) {
builder.append( String.format( "if (%s == null || !%<s.equals($1))", currentValue.getName() ) );
builder.append( String.format( " if (%s == null || !%<s.equals($1))", currentValue.getName() ) );
}
// all other objects
else {
@ -64,15 +62,12 @@ public abstract class AttributeTypeDescriptor {
}
}
}
builder.append( String.format( "if (%1$s == null || !%2$s.equals(%1$s, $1))",
builder.append( String.format( " if (%1$s == null || !%2$s.equals(%1$s, $1))",
currentValue.getName(),
EqualsHelper.class.getName() ) );
}
builder.append( String.format( " { %s(\"%s\"); }", EnhancerConstants.TRACKER_CHANGER_NAME, currentValue.getName() ) );
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
catch (NotFoundException e) {
e.printStackTrace();
}
@ -128,22 +123,18 @@ public abstract class AttributeTypeDescriptor {
}
public String buildReadInterceptionBodyFragment(String fieldName) {
return String.format( "" +
"if ( %3$s() != null ) {%n" +
" this.%1$s = (%2$s) %3$s().readObject(this, \"%1$s\", this.%1$s);%n" +
"}",
return String.format(
" if ( %3$s() != null ) { this.%1$s = (%2$s) %3$s().readObject(this, \"%1$s\", this.%1$s); }%n",
fieldName,
type,
EnhancerConstants.INTERCEPTOR_GETTER_NAME );
}
public String buildWriteInterceptionBodyFragment(String fieldName) {
return String.format( "" +
"%2$s localVar = $1;%n" +
"if ( %3$s() != null ) {%n" +
" localVar = (%2$s) %3$s().writeObject(this, \"%1$s\", this.%1$s, $1);%n" +
"}%n" +
"this.%1$s = localVar;",
return String.format(
" %2$s localVar = $1;%n" +
" if ( %3$s() != null ) { localVar = (%2$s) %3$s().writeObject(this, \"%1$s\", this.%1$s, $1); }%n" +
" this.%1$s = localVar;",
fieldName,
type,
EnhancerConstants.INTERCEPTOR_GETTER_NAME );
@ -166,22 +157,18 @@ public abstract class AttributeTypeDescriptor {
}
public String buildReadInterceptionBodyFragment(String fieldName) {
return String.format( "" +
"if (%3$s() != null ) {%n" +
" this.%1$s = %3$s().read%2$s(this, \"%1$s\", this.%1$s);%n" +
"}",
return String.format(
" if (%3$s() != null ) { this.%1$s = %3$s().read%2$s(this, \"%1$s\", this.%1$s); }",
fieldName,
type,
EnhancerConstants.INTERCEPTOR_GETTER_NAME );
}
public String buildWriteInterceptionBodyFragment(String fieldName) {
return String.format( "" +
"%2$s localVar = $1;%n" +
"if ( %4$s() != null ) {%n" +
" localVar = %4$s().write%3$s(this, \"%1$s\", this.%1$s, $1);%n" +
"}%n" +
"this.%1$s = localVar;",
return String.format(
" %2$s localVar = $1;%n" +
" if ( %4$s() != null ) { localVar = %4$s().write%3$s(this, \"%1$s\", this.%1$s, $1); }%n" +
" this.%1$s = localVar;",
fieldName,
type.toLowerCase( Locale.ROOT ),
type,

View File

@ -17,13 +17,16 @@ import javassist.CtField;
import javassist.Modifier;
import javassist.NotFoundException;
import org.hibernate.bytecode.enhance.internal.tracker.CollectionTracker;
import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker;
import org.hibernate.bytecode.enhance.internal.tracker.SimpleCollectionTracker;
import org.hibernate.bytecode.enhance.internal.tracker.SimpleFieldTracker;
import org.hibernate.bytecode.enhance.spi.CollectionTracker;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.EnhancementException;
import org.hibernate.bytecode.enhance.spi.Enhancer;
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoader;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.SelfDirtinessTracker;
/**
@ -39,6 +42,7 @@ public class EntityEnhancer extends Enhancer {
// 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();
private static final String COLLECTION_TRACKER_IMPL = SimpleCollectionTracker.class.getName();
public void enhance(CtClass managedCtClass) {
// add the ManagedEntity interface
@ -110,7 +114,7 @@ public class EntityEnhancer extends Enhancer {
FieldWriter.addField(
managedCtClass,
classPool.get( DIRTY_TRACKER_IMPL ),
classPool.get( DirtyTracker.class.getName() ),
EnhancerConstants.TRACKER_FIELD_NAME
);
FieldWriter.addField(
@ -181,6 +185,24 @@ public class EntityEnhancer extends Enhancer {
EnhancerConstants.TRACKER_FIELD_NAME,
EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME
);
MethodWriter.write(
managedCtClass,
"public void %1$s(boolean f) {%n" +
" if (%2$s == null) %2$s = new %3$s();%n %2$s.suspend(f);%n" +
"}",
EnhancerConstants.TRACKER_SUSPEND_NAME,
EnhancerConstants.TRACKER_FIELD_NAME ,
DIRTY_TRACKER_IMPL
);
MethodWriter.write(
managedCtClass,
"public %s %s() { return %s; }",
CollectionTracker.class.getName(),
EnhancerConstants.TRACKER_COLLECTION_GET_NAME,
EnhancerConstants.TRACKER_COLLECTION_NAME
);
}
catch (CannotCompileException cce) {
cce.printStackTrace();
@ -197,7 +219,7 @@ public class EntityEnhancer extends Enhancer {
}
if ( enhancementContext.isPersistentField( ctField ) ) {
for ( CtClass ctClass : ctField.getType().getInterfaces() ) {
if ( ctClass.getName().equals( Collection.class.getName() ) ) {
if ( PersistentAttributesHelper.isAssignable( ctClass, Collection.class.getName() ) ) {
collectionList.add( ctField );
break;
}
@ -217,9 +239,7 @@ public class EntityEnhancer extends Enhancer {
body.append(
String.format(
"private boolean %1$s() {%n" +
" if (%2$s == null) {%n" +
" return false;%n" +
" }%n",
" if (%2$s == null) { return false; }%n%n",
EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME,
EnhancerConstants.TRACKER_COLLECTION_NAME
)
@ -231,7 +251,7 @@ public class EntityEnhancer extends Enhancer {
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",
" if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { return true; }%n%n",
ctField.getName(),
EnhancerConstants.TRACKER_COLLECTION_NAME
)
@ -254,7 +274,7 @@ public class EntityEnhancer extends Enhancer {
body.append(
String.format(
"private void %1$s(%3$s tracker) {%n" +
" if (%2$s == null) { return; }%n",
" if (%2$s == null) { return; }%n%n",
EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME,
EnhancerConstants.TRACKER_COLLECTION_NAME,
DirtyTracker.class.getName()
@ -267,7 +287,7 @@ public class EntityEnhancer extends Enhancer {
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",
" if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { tracker.add(\"%1$s\"); }%n%n",
ctField.getName(),
EnhancerConstants.TRACKER_COLLECTION_NAME
)
@ -289,21 +309,35 @@ public class EntityEnhancer extends Enhancer {
body.append(
String.format(
"private void %1$s() {%n" +
" if (%2$s == null) { %2$s = new %3$s(); }%n",
"private void %1$s() {%n" +
" if (%2$s == null) { %2$s = new %3$s(); }%n" +
" %4$s lazyInterceptor = null;%n",
EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME,
EnhancerConstants.TRACKER_COLLECTION_NAME,
CollectionTracker.class.getName()
COLLECTION_TRACKER_IMPL,
LazyAttributeLoader.class.getName()
)
);
if ( PersistentAttributesHelper.isAssignable( managedCtClass, PersistentAttributeInterceptable.class.getName() ) ) {
body.append(
String.format(
" if(%1$s != null && %1$s instanceof %2$s) lazyInterceptor = (%2$s) %1$s;%n%n",
EnhancerConstants.INTERCEPTOR_FIELD_NAME,
LazyAttributeLoader.class.getName()
)
);
}
for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
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",
" // collection field [%1$s]%n" +
" if (lazyInterceptor == null || lazyInterceptor.isAttributeLoaded(\"%1$s\")) {%n" +
" if (%1$s == null) { %2$s.add(\"%1$s\", -1); }%n" +
" else { %2$s.add(\"%1$s\", %1$s.size()); }%n" +
" }%n%n",
ctField.getName(),
EnhancerConstants.TRACKER_COLLECTION_NAME
)

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.bytecode.enhance.internal;
import javax.persistence.Transient;
import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtField;
@ -13,12 +15,11 @@ import javassist.Modifier;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.annotation.Annotation;
import org.hibernate.bytecode.enhance.spi.EnhancementException;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import javax.persistence.Transient;
/**
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
*/
@ -50,7 +51,7 @@ public class FieldWriter {
private static void addPrivateTransient(CtClass target, CtClass type, String name) {
addWithModifiers( target, type, name, Modifier.PRIVATE | Modifier.TRANSIENT, Transient.class );
log.debugf( "Wrote field into [%s]: @Transient private transient %s %s;%n", target.getName(), type.getName(), name );
log.debugf( "Wrote field into [%s]: @Transient private transient %s %s;", target.getName(), type.getName(), name );
}
private static void addWithModifiers(CtClass target, CtClass type, String name, int modifiers, Class<?> ... annotations ) {

View File

@ -37,7 +37,7 @@ public class MethodWriter {
public static CtMethod write(CtClass target, String format, Object ... args) throws CannotCompileException {
final String body = String.format( format, args );
// System.out.printf( "writing method into [%s]:%n%s%n", target.getName(), body );
log.debugf( "writing method into [%s]:%n%s%n", target.getName(), body );
log.debugf( "writing method into [%s]:%n%s", target.getName(), body );
final CtMethod method = CtNewMethod.make( body, target );
target.addMethod( method );
return method;
@ -47,7 +47,7 @@ public class MethodWriter {
public static CtMethod addGetter(CtClass target, String field, String name) {
try {
log.debugf( "Writing getter method [%s] into [%s] for field [%s]%n", name, target.getName(), field );
log.debugf( "Writing getter method [%s] into [%s] for field [%s]", name, target.getName(), field );
final CtMethod method = CtNewMethod.getter( name, target.getField( field ) );
target.addMethod( method );
return method;
@ -64,7 +64,7 @@ public class MethodWriter {
public static CtMethod addSetter(CtClass target, String field, String name) {
try {
log.debugf( "Writing setter method [%s] into [%s] for field [%s]%n", name, target.getName(), field );
log.debugf( "Writing setter method [%s] into [%s] for field [%s]", name, target.getName(), field );
final CtMethod method = CtNewMethod.setter( name, target.getField( field ) );
target.addMethod( method );
return method;

View File

@ -9,7 +9,9 @@ package org.hibernate.bytecode.enhance.internal;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.persistence.Embedded;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
@ -26,9 +28,9 @@ import javassist.bytecode.CodeIterator;
import javassist.bytecode.ConstPool;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import javassist.bytecode.SignatureAttribute;
import javassist.bytecode.stackmap.MapMaker;
import org.hibernate.Hibernate;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.EnhancementException;
import org.hibernate.bytecode.enhance.spi.Enhancer;
@ -119,13 +121,14 @@ public class PersistentAttributesEnhancer extends Enhancer {
// read attempts only have to deal lazy-loading support, not dirty checking;
// so if the field is not enabled as lazy-loadable return a plain simple getter as the reader
if ( !enhancementContext.isLazyLoadable( persistentField ) ) {
if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass )
|| !enhancementContext.isLazyLoadable( persistentField ) ) {
return MethodWriter.addGetter( managedCtClass, fieldName, readerName );
}
try {
return MethodWriter.write(
managedCtClass, "public %s %s() {%n %s%n return this.%s;%n}",
managedCtClass, "public %s %s() {%n%s%n return this.%s;%n}",
persistentField.getType().getName(),
readerName,
typeDescriptor.buildReadInterceptionBodyFragment( fieldName ),
@ -160,13 +163,14 @@ public class PersistentAttributesEnhancer extends Enhancer {
try {
final CtMethod writer;
if ( !enhancementContext.isLazyLoadable( persistentField ) ) {
if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass )
|| !enhancementContext.isLazyLoadable( persistentField ) ) {
writer = MethodWriter.addSetter( managedCtClass, fieldName, writerName );
}
else {
writer = MethodWriter.write(
managedCtClass,
"public void %s(%s %s) {%n %s%n}",
"public void %s(%s %s) {%n%s%n}",
writerName,
persistentField.getType().getName(),
fieldName,
@ -174,25 +178,26 @@ public class PersistentAttributesEnhancer extends Enhancer {
);
}
if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
writer.insertBefore(
String.format(
"if (%s != null) { %<s.callOwner(\".%s\"); }%n",
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME,
fieldName
)
);
}
else if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
writer.insertBefore(
typeDescriptor.buildInLineDirtyCheckingBodyFragment(
enhancementContext,
persistentField
)
);
}
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
writer.insertBefore(
String.format(
" if (%1$s != null) { %1$s.callOwner(\"\"); }%n",
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME
)
);
}
else {
writer.insertBefore(
typeDescriptor.buildInLineDirtyCheckingBodyFragment(
enhancementContext,
persistentField
)
);
}
handleCompositeField( managedCtClass, persistentField, writer );
handleCompositeField( managedCtClass, persistentField, writer );
}
if ( enhancementContext.doBiDirectionalAssociationManagement( persistentField ) ) {
handleBiDirectionalAssociation( managedCtClass, persistentField, writer );
@ -219,21 +224,21 @@ public class PersistentAttributesEnhancer extends Enhancer {
private void handleBiDirectionalAssociation(CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter)
throws NotFoundException, CannotCompileException {
if ( !isPossibleBiDirectionalAssociation( persistentField ) ) {
if ( !PersistentAttributesHelper.isPossibleBiDirectionalAssociation( persistentField ) ) {
return;
}
final CtClass targetEntity = getTargetEntityClass( persistentField );
final CtClass targetEntity = PersistentAttributesHelper.getTargetEntityClass( managedCtClass, persistentField );
if ( targetEntity == null ) {
log.debugf(
log.infof(
"Could not find type of bi-directional association for field [%s#%s]",
managedCtClass.getName(),
persistentField.getName()
);
return;
}
final String mappedBy = getMappedBy( persistentField, targetEntity );
if ( mappedBy.isEmpty() ) {
log.warnf(
final String mappedBy = PersistentAttributesHelper.getMappedBy( persistentField, targetEntity, enhancementContext );
if ( mappedBy == null || mappedBy.isEmpty() ) {
log.infof(
"Could not find bi-directional association for field [%s#%s]",
managedCtClass.getName(),
persistentField.getName()
@ -244,32 +249,73 @@ public class PersistentAttributesEnhancer extends Enhancer {
// create a temporary getter and setter on the target entity to be able to compile our code
final String mappedByGetterName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + mappedBy;
final String mappedBySetterName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + mappedBy;
MethodWriter.addGetter( targetEntity, mappedBy, mappedByGetterName );
MethodWriter.addSetter( targetEntity, mappedBy, mappedBySetterName );
CtMethod getter;
CtMethod setter;
boolean tmpTargetMethods = false;
try {
getter = targetEntity.getDeclaredMethod( mappedByGetterName );
setter = targetEntity.getDeclaredMethod( mappedByGetterName );
}
catch (NotFoundException nfe){
getter = MethodWriter.addGetter( targetEntity, mappedBy, mappedByGetterName );
setter = MethodWriter.addSetter( targetEntity, mappedBy, mappedBySetterName );
tmpTargetMethods = true;
}
if ( persistentField.hasAnnotation( OneToOne.class ) ) {
// code fragments to check loaded state. We don't want to trigger lazy loading in association management code
String currentAssociationLoaded = String.format(
"%s.isPropertyInitialized(this.%s, \"%s\")",
Hibernate.class.getName(),
persistentField.getName(),
mappedBy
);
String targetElementLoaded = String.format(
"%s.isPropertyInitialized(target, \"%s\")",
Hibernate.class.getName(),
mappedBy
);
String newAssociationLoaded = String.format(
"%s.isPropertyInitialized($1, \"%s\")",
Hibernate.class.getName(),
mappedBy
);
if ( PersistentAttributesHelper.hasAnnotation( persistentField, OneToOne.class ) ) {
// only unset when $1 != null to avoid recursion
fieldWriter.insertBefore(
String.format(
"if ($0.%s != null && $1 != null) $0.%<s.%s(null);%n",
" if (this.%1$s != null && %2$s && $1 != null) { this.%1$s.%3$s(null); }%n",
persistentField.getName(),
currentAssociationLoaded,
mappedBySetterName
)
);
fieldWriter.insertAfter(
String.format(
"if ($1 != null && $1.%s() != $0) $1.%s($0);%n",
" if ($1 != null && %s && $1.%s() != this) { $1.%s(this); }%n",
newAssociationLoaded,
mappedByGetterName,
mappedBySetterName
)
);
}
if ( persistentField.hasAnnotation( OneToMany.class ) ) {
if ( PersistentAttributesHelper.hasAnnotation( persistentField, OneToMany.class ) ) {
boolean isMap = PersistentAttributesHelper.isAssignable( persistentField.getType(), Map.class.getName() );
String toArrayMethod = isMap ? "values().toArray()" : "toArray()" ;
// only remove elements not in the new collection or else we would loose those elements
// don't use iterator to avoid ConcurrentModException
fieldWriter.insertBefore(
String.format(
"if ($0.%s != null) { Object[] array = $0.%<s.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if ($1 == null || !$1.contains(target)) target.%s(null); } }%n",
" if (this.%3$s != null && %1$s) {%n" +
" Object[] array = this.%3$s.%2$s;%n" +
" for (int i = 0; i < array.length; i++) {%n" +
" %4$s target = (%4$s) array[i];%n" +
" if ($1 == null || !$1.contains(target)) { target.%5$s(null); }%n" +
" }%n" +
" }%n",
currentAssociationLoaded,
toArrayMethod,
persistentField.getName(),
targetEntity.getName(),
mappedBySetterName
@ -277,34 +323,63 @@ public class PersistentAttributesEnhancer extends Enhancer {
);
fieldWriter.insertAfter(
String.format(
"if ($1 != null) { Object[] array = $1.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if (target.%s() != $0) target.%s((%s)$0); } }%n",
" if ($1 != null && %1$s) {%n" +
" Object[] array = $1.%2$s;%n" +
" for (int i = 0; i < array.length; i++) {%n" +
" %4$s target = (%4$s) array[i];%n" +
" if (%3$s && target.%5$s() != this) { target.%6$s(this); }%n" +
" }%n" +
" }%n",
newAssociationLoaded,
toArrayMethod,
targetElementLoaded,
targetEntity.getName(),
mappedByGetterName,
mappedBySetterName,
managedCtClass.getName()
mappedBySetterName
)
);
}
if ( persistentField.hasAnnotation( ManyToOne.class ) ) {
if ( PersistentAttributesHelper.hasAnnotation( persistentField, ManyToOne.class ) ) {
fieldWriter.insertBefore(
String.format(
"if ($0.%1$s != null && $0.%1$s.%2$s() != null) $0.%1$s.%2$s().remove($0);%n",
" if (this.%2$s != null && %1$s && this.%2$s.%3$s() != null) { this.%2$s.%3$s().remove(this); }%n",
currentAssociationLoaded,
persistentField.getName(),
mappedByGetterName
)
);
// check .contains($0) to avoid double inserts (but preventing duplicates)
// check .contains(this) to avoid double inserts (but preventing duplicates)
fieldWriter.insertAfter(
String.format(
"if ($1 != null) { java.util.Collection c = $1.%s(); if (c != null && !c.contains($0)) c.add($0); }%n",
" if ($1 != null && %s) {%n" +
" java.util.Collection c = $1.%s();%n" +
" if (c != null && !c.contains(this)) { c.add(this); }%n" +
" }%n",
newAssociationLoaded,
mappedByGetterName
)
);
}
if ( persistentField.hasAnnotation( ManyToMany.class ) ) {
if ( PersistentAttributesHelper.hasAnnotation( persistentField, ManyToMany.class ) ) {
if ( PersistentAttributesHelper.isAssignable( persistentField.getType(), Map.class.getName() ) ||
PersistentAttributesHelper.isAssignable( targetEntity.getField( mappedBy ).getType() , Map.class.getName() ) ) {
log.infof(
"Bi-directional association for field [%s#%s] not managed: @ManyToMany in java.util.Map attribute not supported ",
managedCtClass.getName(),
persistentField.getName()
);
return;
}
fieldWriter.insertBefore(
String.format(
"if ($0.%s != null) { Object[] array = $0.%<s.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if ($1 == null || !$1.contains(target)) target.%s().remove($0); } }%n",
" if (this.%2$s != null && %1$s) {%n" +
" Object[] array = this.%2$s.toArray();%n" +
" for (int i = 0; i < array.length; i++) {%n" +
" %3$s target = (%3$s) array[i];%n" +
" if ($1 == null || !$1.contains(target)) { target.%4$s().remove(this); }%n" +
" }%n" +
" }%n",
currentAssociationLoaded,
persistentField.getName(),
targetEntity.getName(),
mappedByGetterName
@ -312,106 +387,36 @@ public class PersistentAttributesEnhancer extends Enhancer {
);
fieldWriter.insertAfter(
String.format(
"if ($1 != null) { Object[] array = $1.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; java.util.Collection c = target.%s(); if ( c != $0 && c != null) c.add($0); } }%n",
" if ($1 != null && %s) {%n" +
" Object[] array = $1.toArray();%n" +
" for (int i = 0; i < array.length; i++) {%n" +
" %s target = (%<s) array[i];%n" +
" if (%s) {%n" +
" java.util.Collection c = target.%s();%n" +
" if (c != this && c != null) { c.add(this); }%n" +
" }%n" +
" }%n" +
" }%n",
newAssociationLoaded,
targetEntity.getName(),
targetElementLoaded,
mappedByGetterName
)
);
}
// implementation note: association management @OneToMany and @ManyToMay works for add() operations but for remove() a snapshot of the collection is needed so we know what associations to break.
// another approach that could force that behavior would be to return Collections.unmodifiableCollection() ...
}
private boolean isPossibleBiDirectionalAssociation(CtField persistentField) {
return persistentField.hasAnnotation( OneToOne.class ) ||
persistentField.hasAnnotation( OneToMany.class ) ||
persistentField.hasAnnotation( ManyToOne.class ) ||
persistentField.hasAnnotation( ManyToMany.class );
}
private String getMappedBy(CtField persistentField, CtClass targetEntity) {
final String local = getMappedByFromAnnotation( persistentField );
return local.isEmpty() ? getMappedByFromTargetEntity( persistentField, targetEntity ) : local;
}
private String getMappedByFromAnnotation(CtField persistentField) {
try {
if ( persistentField.hasAnnotation( OneToOne.class ) ) {
return ( (OneToOne) persistentField.getAnnotation( OneToOne.class ) ).mappedBy();
}
if ( persistentField.hasAnnotation( OneToMany.class ) ) {
return ( (OneToMany) persistentField.getAnnotation( OneToMany.class ) ).mappedBy();
}
// For @ManyToOne associations, mappedBy must come from the @OneToMany side of the association
if ( persistentField.hasAnnotation( ManyToMany.class ) ) {
return ( (ManyToMany) persistentField.getAnnotation( ManyToMany.class ) ).mappedBy();
}
if ( tmpTargetMethods ) {
targetEntity.removeMethod( getter );
targetEntity.removeMethod( setter );
}
catch (ClassNotFoundException ignore) {
}
return "";
}
private String getMappedByFromTargetEntity(CtField persistentField, CtClass targetEntity) {
// get mappedBy value by searching in the fields of the target entity class
for ( CtField f : targetEntity.getDeclaredFields() ) {
if ( enhancementContext.isPersistentField( f ) && getMappedByFromAnnotation( f ).equals( persistentField.getName() ) ) {
log.debugf(
"mappedBy association for field [%s:%s] is [%s:%s]",
persistentField.getDeclaringClass().getName(),
persistentField.getName(),
targetEntity.getName(),
f.getName()
);
return f.getName();
}
}
return "";
}
private CtClass getTargetEntityClass(CtField persistentField) throws NotFoundException {
// get targetEntity defined in the annotation
try {
Class<?> targetClass = null;
if ( persistentField.hasAnnotation( OneToOne.class ) ) {
targetClass = ( (OneToOne) persistentField.getAnnotation( OneToOne.class ) ).targetEntity();
}
if ( persistentField.hasAnnotation( OneToMany.class ) ) {
targetClass = ( (OneToMany) persistentField.getAnnotation( OneToMany.class ) ).targetEntity();
}
if ( persistentField.hasAnnotation( ManyToOne.class ) ) {
targetClass = ( (ManyToOne) persistentField.getAnnotation( ManyToOne.class ) ).targetEntity();
}
if ( persistentField.hasAnnotation( ManyToMany.class ) ) {
targetClass = ( (ManyToMany) persistentField.getAnnotation( ManyToMany.class ) ).targetEntity();
}
if ( targetClass != null && targetClass != void.class ) {
return classPool.get( targetClass.getName() );
}
}
catch (ClassNotFoundException ignore) {
}
// infer targetEntity from generic type signature
if ( persistentField.hasAnnotation( OneToOne.class ) || persistentField.hasAnnotation( ManyToOne.class ) ) {
return persistentField.getType();
}
if ( persistentField.hasAnnotation( OneToMany.class ) || persistentField.hasAnnotation( ManyToMany.class ) ) {
try {
final SignatureAttribute.TypeArgument target = ( (SignatureAttribute.ClassType) SignatureAttribute.toFieldSignature(
persistentField.getGenericSignature()
) ).getTypeArguments()[0];
return persistentField.getDeclaringClass().getClassPool().get( target.toString() );
}
catch (BadBytecode ignore) {
}
}
return null;
}
private void handleCompositeField(CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter)
throws NotFoundException, CannotCompileException {
if ( !persistentField.hasAnnotation( Embedded.class ) ) {
if ( !enhancementContext.isCompositeClass( persistentField.getType() ) ||
!PersistentAttributesHelper.hasAnnotation( persistentField, Embedded.class ) ) {
return;
}
@ -421,9 +426,9 @@ public class PersistentAttributesEnhancer extends Enhancer {
if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
// if a composite have a embedded field we need to implement the TRACKER_CHANGER_NAME method as well
MethodWriter.write(
managedCtClass, "" +
managedCtClass,
"public void %1$s(String name) {%n" +
" if (%2$s != null) { %2$s.callOwner(\".\" + name) ; }%n}",
" if (%2$s != null) { %2$s.callOwner(\".\" + name); }%n}",
EnhancerConstants.TRACKER_CHANGER_NAME,
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME
);
@ -432,7 +437,6 @@ public class PersistentAttributesEnhancer extends Enhancer {
// cleanup previous owner
fieldWriter.insertBefore(
String.format(
"" +
"if (%1$s != null) { ((%2$s) %1$s).%3$s(\"%1$s\"); }%n",
persistentField.getName(),
CompositeTracker.class.getName(),
@ -443,7 +447,6 @@ public class PersistentAttributesEnhancer extends Enhancer {
// trigger track changes
fieldWriter.insertAfter(
String.format(
"" +
"((%2$s) %1$s).%4$s(\"%1$s\", (%3$s) this);%n" +
"%5$s(\"%1$s\");",
persistentField.getName(),
@ -563,6 +566,7 @@ public class PersistentAttributesEnhancer extends Enhancer {
}
if ( targetCtClass == managedCtClass
|| !enhancementContext.isPersistentField( targetCtClass.getField( fieldName ) )
|| PersistentAttributesHelper.hasAnnotation( targetCtClass, fieldName, Id.class )
|| "this$0".equals( fieldName ) ) {
continue;
}

View File

@ -0,0 +1,384 @@
/*
* 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;
import java.beans.Introspector;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Map;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMember;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.SignatureAttribute;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
/**
* util methods to fetch attribute metadata. consistent for both field and property access types.
*
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
* @see org.hibernate.internal.util.ReflectHelper
*/
public class PersistentAttributesHelper {
private PersistentAttributesHelper() {
}
private static final CoreMessageLogger log = CoreLogging.messageLogger( PersistentAttributesHelper.class );
public static boolean hasAnnotation(CtField attribute, Class<? extends Annotation> annotation) {
return getAnnotation( attribute, annotation ) != null;
}
public static boolean hasAnnotation(CtClass ctClass, String attributeName, Class<? extends Annotation> annotation) {
return getAnnotation( ctClass, attributeName, annotation ) != null;
}
public static <T extends Annotation> T getAnnotation(CtField attribute, Class<T> annotation) {
return getAnnotation( attribute.getDeclaringClass(), attribute.getName(), annotation );
}
public static <T extends Annotation> T getAnnotation(CtClass ctClass, String attributeName, Class<T> annotation) {
AccessType classAccessType = getAccessTypeOrNull( ctClass );
CtField field = findFieldOrNull( ctClass, attributeName );
CtMethod getter = findGetterOrNull( ctClass, attributeName );
if ( classAccessType == AccessType.FIELD || ( field != null && getAccessTypeOrNull( field ) == AccessType.FIELD ) ) {
return field == null ? null : getAnnotationOrNull( field, annotation );
}
if ( classAccessType == AccessType.PROPERTY || ( getter != null && getAccessTypeOrNull( getter ) == AccessType.PROPERTY ) ) {
return getter == null ? null : getAnnotationOrNull( getter, annotation );
}
T found = ( getter == null ? null : getAnnotationOrNull( getter, annotation ) );
if ( found == null && field != null ) {
return getAnnotationOrNull( field, annotation );
}
return found;
}
private static <T extends Annotation> T getAnnotationOrNull(CtMember ctMember, Class<T> annotation) {
try {
if ( ctMember.hasAnnotation( annotation ) ) {
return annotation.cast( ctMember.getAnnotation( annotation ) );
}
}
catch (ClassNotFoundException cnfe) {
// should never happen
}
return null;
}
private static AccessType getAccessTypeOrNull(CtMember ctMember) {
Access access = getAnnotationOrNull( ctMember, Access.class );
return access == null ? null : access.value();
}
private static AccessType getAccessTypeOrNull(CtClass ctClass) {
try {
if ( ctClass.hasAnnotation( Access.class ) ) {
return ( (Access) ctClass.getAnnotation( Access.class ) ).value();
}
else {
CtClass extendsClass = ctClass.getSuperclass();
return extendsClass == null ? null : getAccessTypeOrNull( extendsClass );
}
}
catch (ClassNotFoundException e) {
return null;
}
catch (NotFoundException e) {
return null;
}
}
//
/**
* duplicated here to take CtClass instead of Class
* @see org.hibernate.internal.util.ReflectHelper#locateField
*/
private static CtField findFieldOrNull(CtClass ctClass, String propertyName) {
if ( ctClass == null ) {
return null;
}
try {
return ctClass.getField( propertyName );
}
catch ( NotFoundException nsfe ) {
try {
return findFieldOrNull( ctClass.getSuperclass(), propertyName );
}
catch (NotFoundException e) {
return null;
}
}
}
/**
* duplicated here to take CtClass instead of Class
* @see org.hibernate.internal.util.ReflectHelper#findGetterMethod
*/
private static CtMethod findGetterOrNull(CtClass ctClass, String propertyName) {
if ( ctClass == null ) {
return null;
}
CtMethod method = getterOrNull( ctClass, propertyName );
if ( method != null ) {
return method;
}
try {
// check if extends
method = findGetterOrNull( ctClass.getSuperclass(), propertyName );
if ( method != null ) {
return method;
}
// check if implements
for ( CtClass interfaceCtClass : ctClass.getInterfaces() ) {
method = getterOrNull( interfaceCtClass, propertyName );
if ( method != null ) {
return method;
}
}
}
catch (NotFoundException nfe) {
// give up
}
return null;
}
private static CtMethod getterOrNull(CtClass containerClass, String propertyName) {
for ( CtMethod method : containerClass.getDeclaredMethods() ) {
try {
// if the method has parameters, skip it
if ( method.isEmpty() || method.getParameterTypes().length != 0 ) {
continue;
}
}
catch (NotFoundException e) {
continue;
}
final String methodName = method.getName();
// try "get"
if ( methodName.startsWith( "get" ) ) {
String testStdMethod = Introspector.decapitalize( methodName.substring( 3 ) );
String testOldMethod = methodName.substring( 3 );
if ( testStdMethod.equals( propertyName ) || testOldMethod.equals( propertyName ) ) {
return method;
}
}
// if not "get", then try "is"
if ( methodName.startsWith( "is" ) ) {
String testStdMethod = Introspector.decapitalize( methodName.substring( 2 ) );
String testOldMethod = methodName.substring( 2 );
if ( testStdMethod.equals( propertyName ) || testOldMethod.equals( propertyName ) ) {
return method;
}
}
}
return null;
}
//
public static boolean isPossibleBiDirectionalAssociation(CtField persistentField) {
return PersistentAttributesHelper.hasAnnotation( persistentField, OneToOne.class ) ||
PersistentAttributesHelper.hasAnnotation( persistentField, OneToMany.class ) ||
PersistentAttributesHelper.hasAnnotation( persistentField, ManyToOne.class ) ||
PersistentAttributesHelper.hasAnnotation( persistentField, ManyToMany.class );
}
public static String getMappedBy(CtField persistentField, CtClass targetEntity, EnhancementContext context) {
final String local = getMappedByFromAnnotation( persistentField );
return local.isEmpty() ? getMappedByFromTargetEntity( persistentField, targetEntity, context ) : local;
}
private static String getMappedByFromAnnotation(CtField persistentField) {
OneToOne oto = PersistentAttributesHelper.getAnnotation( persistentField, OneToOne.class );
if ( oto != null ) {
return oto.mappedBy();
}
OneToMany otm = PersistentAttributesHelper.getAnnotation( persistentField, OneToMany.class );
if ( otm != null ) {
return otm.mappedBy();
}
// For @ManyToOne associations, mappedBy must come from the @OneToMany side of the association
ManyToMany mtm = PersistentAttributesHelper.getAnnotation( persistentField, ManyToMany.class );
return mtm == null ? "" : mtm.mappedBy();
}
private static String getMappedByFromTargetEntity(
CtField persistentField,
CtClass targetEntity,
EnhancementContext context) {
// get mappedBy value by searching in the fields of the target entity class
for ( CtField f : targetEntity.getDeclaredFields() ) {
if ( context.isPersistentField( f ) && getMappedByFromAnnotation( f ).equals( persistentField.getName() ) ) {
log.debugf(
"mappedBy association for field [%s#%s] is [%s#%s]",
persistentField.getDeclaringClass().getName(),
persistentField.getName(),
targetEntity.getName(),
f.getName()
);
return f.getName();
}
}
return "";
}
public static CtClass getTargetEntityClass(CtClass managedCtClass, CtField persistentField) throws NotFoundException {
// get targetEntity defined in the annotation
try {
OneToOne oto = PersistentAttributesHelper.getAnnotation( persistentField, OneToOne.class );
OneToMany otm = PersistentAttributesHelper.getAnnotation( persistentField, OneToMany.class );
ManyToOne mto = PersistentAttributesHelper.getAnnotation( persistentField, ManyToOne.class );
ManyToMany mtm = PersistentAttributesHelper.getAnnotation( persistentField, ManyToMany.class );
Class<?> targetClass = null;
if ( oto != null ) {
targetClass = oto.targetEntity();
}
if ( otm != null ) {
targetClass = otm.targetEntity();
}
if ( mto != null ) {
targetClass = mto.targetEntity();
}
if ( mtm != null ) {
targetClass = mtm.targetEntity();
}
if ( targetClass != null && targetClass != void.class ) {
return managedCtClass.getClassPool().get( targetClass.getName() );
}
}
catch (NotFoundException ignore) {
}
// infer targetEntity from generic type signature
String inferredTypeName = inferTypeName( managedCtClass, persistentField.getName() );
return inferredTypeName == null ? null : managedCtClass.getClassPool().get( inferredTypeName );
}
/**
* Consistent with hasAnnotation()
*/
private static String inferTypeName(CtClass ctClass, String attributeName ) {
AccessType classAccessType = getAccessTypeOrNull( ctClass );
CtField field = findFieldOrNull( ctClass, attributeName );
CtMethod getter = findGetterOrNull( ctClass, attributeName );
if ( classAccessType == AccessType.FIELD || ( field != null && getAccessTypeOrNull( field ) == AccessType.FIELD ) ) {
return field == null ? null : inferFieldTypeName( field );
}
if ( classAccessType == AccessType.PROPERTY || ( getter != null && getAccessTypeOrNull( getter ) == AccessType.PROPERTY ) ) {
return getter == null ? null : inferMethodTypeName( getter );
}
String found = ( getter == null ? null : inferMethodTypeName( getter ) );
if ( found == null && field != null ) {
return inferFieldTypeName( field );
}
return found;
}
private static String inferFieldTypeName(CtField field) {
try {
if ( field.getFieldInfo().getAttribute( SignatureAttribute.tag ) == null ){
return field.getType().getName();
}
return inferGenericTypeName(
field.getType(),
SignatureAttribute.toTypeSignature( field.getGenericSignature() )
);
}
catch (BadBytecode ignore) {
return null;
}
catch (NotFoundException e) {
return null;
}
}
private static String inferMethodTypeName(CtMethod method) {
try {
if ( method.getMethodInfo().getAttribute( SignatureAttribute.tag ) == null ){
return method.getReturnType().getName();
}
return inferGenericTypeName(
method.getReturnType(),
SignatureAttribute.toMethodSignature( method.getGenericSignature() ).getReturnType()
);
}
catch (BadBytecode ignore) {
return null;
}
catch (NotFoundException e) {
return null;
}
}
private static String inferGenericTypeName(CtClass ctClass, SignatureAttribute.Type genericSignature) {
// infer targetEntity from generic type signature
if ( isAssignable( ctClass, Collection.class.getName() ) ) {
return ( (SignatureAttribute.ClassType) genericSignature ).getTypeArguments()[0].toString();
}
if ( isAssignable( ctClass, Map.class.getName() ) ) {
return ( (SignatureAttribute.ClassType) genericSignature ).getTypeArguments()[1].toString();
}
return ctClass.getName();
}
//
public static boolean isAssignable(CtClass thisCtClass, String targetClassName) {
if ( thisCtClass == null ) {
return false;
}
if ( thisCtClass.getName().equals( targetClassName ) ) {
return true;
}
try {
// check if extends
if ( isAssignable( thisCtClass.getSuperclass(), targetClassName ) ) {
return true;
}
// check if implements
for ( CtClass interfaceCtClass : thisCtClass.getInterfaces() ) {
if ( isAssignable( interfaceCtClass, targetClassName ) ) {
return true;
}
}
}
catch (NotFoundException e) {
// keep going
}
return false;
}
}

View File

@ -22,4 +22,6 @@ public interface DirtyTracker {
boolean isEmpty();
String[] get();
void suspend(boolean suspend);
}

View File

@ -8,21 +8,24 @@ package org.hibernate.bytecode.enhance.internal.tracker;
import java.util.Arrays;
import org.hibernate.bytecode.enhance.spi.CollectionTracker;
/**
* small low memory class to keep track of the number of elements in a collection
*
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
*/
public final class CollectionTracker {
public final class SimpleCollectionTracker implements CollectionTracker {
private String[] names;
private int[] sizes;
public CollectionTracker() {
public SimpleCollectionTracker() {
names = new String[0];
sizes = new int[0];
}
@Override
public void add(String name, int size) {
for ( int i = 0; i < names.length; i++ ) {
if ( names[i].equals( name ) ) {
@ -36,6 +39,7 @@ public final class CollectionTracker {
sizes[sizes.length - 1] = size;
}
@Override
public int getSize(String name) {
for ( int i = 0; i < names.length; i++ ) {
if ( name.equals( names[i] ) ) {

View File

@ -19,6 +19,7 @@ import java.util.Arrays;
public final class SimpleFieldTracker implements DirtyTracker {
private String[] names;
private boolean suspended;
public SimpleFieldTracker() {
names = new String[0];
@ -26,6 +27,9 @@ public final class SimpleFieldTracker implements DirtyTracker {
@Override
public void add(String name) {
if ( suspended ) {
return;
}
if ( !contains( name ) ) {
names = Arrays.copyOf( names, names.length + 1 );
names[names.length - 1] = name;
@ -44,7 +48,9 @@ public final class SimpleFieldTracker implements DirtyTracker {
@Override
public void clear() {
names = new String[0];
if ( !isEmpty() ) {
names = new String[0];
}
}
@Override
@ -57,4 +63,9 @@ public final class SimpleFieldTracker implements DirtyTracker {
return names;
}
@Override
public void suspend(boolean suspend) {
this.suspended = suspend;
}
}

View File

@ -16,6 +16,7 @@ package org.hibernate.bytecode.enhance.internal.tracker;
public final class SortedFieldTracker implements DirtyTracker {
private String[] names;
private boolean suspended;
public SortedFieldTracker() {
names = new String[0];
@ -23,6 +24,9 @@ public final class SortedFieldTracker implements DirtyTracker {
@Override
public void add(String name) {
if ( suspended ) {
return;
}
// 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;
for ( int low = 0, high = names.length - 1; low <= high; ) {
@ -70,7 +74,9 @@ public final class SortedFieldTracker implements DirtyTracker {
@Override
public void clear() {
names = new String[0];
if ( !isEmpty() ) {
names = new String[0];
}
}
@Override
@ -83,4 +89,9 @@ public final class SortedFieldTracker implements DirtyTracker {
return names;
}
@Override
public void suspend(boolean suspend) {
this.suspended = suspend;
}
}

View File

@ -0,0 +1,19 @@
/*
* 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.spi;
/**
* Interface to be implemented by collection trackers that hold the expected size od collections, a simplified Map<String, int>.
*
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
*/
public interface CollectionTracker {
void add(String name, int size);
int getSize(String name);
}

View File

@ -137,6 +137,16 @@ public final class EnhancerConstants {
*/
public static final String TRACKER_CLEAR_NAME = "$$_hibernate_clearDirtyAttributes";
/**
* Name of method to suspend dirty tracking
*/
public static final String TRACKER_SUSPEND_NAME = "$$_hibernate_suspendDirtyTracking";
/**
* Name of method to check if collection fields are dirty
*/
public static final String TRACKER_COLLECTION_GET_NAME = "$$_hibernate_getCollectionTracker";
/**
* Name of method to check if collection fields are dirty
*/

View File

@ -7,12 +7,15 @@
package org.hibernate.bytecode.enhance.spi.interceptor;
import java.util.Collection;
import java.util.Set;
import org.hibernate.LazyInitializationException;
import org.hibernate.bytecode.enhance.internal.tracker.SimpleFieldTracker;
import org.hibernate.bytecode.enhance.spi.CollectionTracker;
import org.hibernate.bytecode.instrumentation.spi.LazyPropertyInitializer;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.SelfDirtinessTracker;
import org.hibernate.engine.spi.SessionImplementor;
/**
@ -51,11 +54,10 @@ public class LazyAttributeLoader implements PersistentAttributeInterceptor {
);
initializedFields.add( fieldName );
takeCollectionSizeSnapshot( target, fieldName, loadedValue );
return loadedValue;
}
else {
return value;
}
return value;
}
public final void setSession(SessionImplementor session) {
@ -90,7 +92,17 @@ public class LazyAttributeLoader implements PersistentAttributeInterceptor {
return "LazyAttributeLoader(entityName=" + entityName + " ,lazyFields=" + lazyFields + ')';
}
/* --- */
//
private void takeCollectionSizeSnapshot(Object target, String fieldName, Object value) {
if ( value != null && value instanceof Collection && target instanceof SelfDirtinessTracker ) {
CollectionTracker tracker = ( (SelfDirtinessTracker) target ).$$_hibernate_getCollectionTracker();
if ( tracker == null ) {
( (SelfDirtinessTracker) target ).$$_hibernate_clearDirtyAttributes();
}
tracker.add( fieldName, ( (Collection) value ).size() );
}
}
@Override
public boolean readBoolean(Object obj, String name, boolean oldValue) {

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.engine.spi;
import org.hibernate.bytecode.enhance.spi.CollectionTracker;
/**
* Contract for an entity to report that it tracks the dirtiness of its own state,
* as opposed to needing Hibernate to perform state-diff dirty calculations.
@ -41,4 +43,14 @@ public interface SelfDirtinessTracker {
* Clear the stored dirty attributes
*/
void $$_hibernate_clearDirtyAttributes();
/**
* Temporarily enable / disable dirty tracking
*/
void $$_hibernate_suspendDirtyTracking(boolean suspend);
/**
* Get access to the CollectionTracker
*/
CollectionTracker $$_hibernate_getCollectionTracker();
}