HHH-10981 - Support private persistent fields in @MappedSuperclass

This commit is contained in:
barreiro 2016-07-22 03:10:04 +01:00 committed by Gail Badner
parent 6030f1ff94
commit d23deb37cd
5 changed files with 333 additions and 118 deletions

View File

@ -27,12 +27,18 @@ import org.hibernate.internal.util.compare.EqualsHelper;
*/
public abstract class AttributeTypeDescriptor {
protected InheritanceMetadata inheritanceMetadata;
protected AttributeTypeDescriptor(InheritanceMetadata inheritanceMetadata) {
this.inheritanceMetadata = inheritanceMetadata;
}
public abstract String buildReadInterceptionBodyFragment(String fieldName);
public abstract String buildWriteInterceptionBodyFragment(String fieldName);
public String buildInLineDirtyCheckingBodyFragment(EnhancementContext context, CtField currentValue) {
final StringBuilder builder = new StringBuilder();
StringBuilder builder = new StringBuilder();
try {
// should ignore primary keys
if ( PersistentAttributesHelper.hasAnnotation( currentValue, Id.class )
@ -40,9 +46,13 @@ public abstract class AttributeTypeDescriptor {
return "";
}
String readFragment = inheritanceMetadata.isInherited() && !inheritanceMetadata.isVisible()
? "super." + inheritanceMetadata.getReaderName() + "()"
: "this." + currentValue.getName();
if ( currentValue.getType().isPrimitive() || currentValue.getType().isEnum() ) {
// primitives || enums
builder.append( String.format( " if (%s != $1)", currentValue.getName() ) );
builder.append( String.format( " if ( %s != $1 )", readFragment ) );
}
else {
// if the field is a collection we return since we handle that in a separate method
@ -58,14 +68,13 @@ public abstract class AttributeTypeDescriptor {
String.format(
" if ( !%s.areEqual( %s, $1 ) )",
EqualsHelper.class.getName(),
currentValue.getName()
readFragment
)
);
}
builder.append( String.format( " { %s(\"%s\"); }", EnhancerConstants.TRACKER_CHANGER_NAME, currentValue.getName() ) );
}
catch (NotFoundException e) {
e.printStackTrace();
catch (NotFoundException ignore) {
}
return builder.toString();
}
@ -75,33 +84,39 @@ public abstract class AttributeTypeDescriptor {
/**
* factory method to get the AttributeTypeDescriptor for a particular field type
*/
public static AttributeTypeDescriptor resolve(CtField persistentField) throws NotFoundException {
if ( persistentField.getType() == CtClass.booleanType ) {
return new PrimitiveAttributeTypeDescriptor( Boolean.TYPE );
public static AttributeTypeDescriptor resolve(CtClass managedCtClass, CtField persistentField) throws NotFoundException {
boolean inherited = !managedCtClass.equals( persistentField.getDeclaringClass() );
boolean visible = persistentField.visibleFrom( managedCtClass );
String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + persistentField.getName();
String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + persistentField.getName();
InheritanceMetadata inheritanceMetadata = new InheritanceMetadata( inherited, visible, readerName, writerName );
if ( CtClass.booleanType.equals( persistentField.getType() ) ) {
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Boolean.TYPE );
}
else if ( persistentField.getType() == CtClass.byteType ) {
return new PrimitiveAttributeTypeDescriptor( Byte.TYPE );
else if ( CtClass.byteType.equals( persistentField.getType() )) {
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Byte.TYPE );
}
else if ( persistentField.getType() == CtClass.charType ) {
return new PrimitiveAttributeTypeDescriptor( Character.TYPE );
else if ( CtClass.charType.equals( persistentField.getType() ) ) {
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Character.TYPE );
}
else if ( persistentField.getType() == CtClass.shortType ) {
return new PrimitiveAttributeTypeDescriptor( Short.TYPE );
else if ( CtClass.shortType.equals( persistentField.getType() ) ) {
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Short.TYPE );
}
else if ( persistentField.getType() == CtClass.intType ) {
return new PrimitiveAttributeTypeDescriptor( Integer.TYPE );
else if ( CtClass.intType.equals( persistentField.getType() ) ) {
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Integer.TYPE );
}
else if ( persistentField.getType() == CtClass.longType ) {
return new PrimitiveAttributeTypeDescriptor( Long.TYPE );
else if ( CtClass.longType.equals( persistentField.getType() ) ) {
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Long.TYPE );
}
else if ( persistentField.getType() == CtClass.doubleType ) {
return new PrimitiveAttributeTypeDescriptor( Double.TYPE );
else if ( CtClass.doubleType.equals( persistentField.getType() ) ) {
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Double.TYPE );
}
else if ( persistentField.getType() == CtClass.floatType ) {
return new PrimitiveAttributeTypeDescriptor( Float.TYPE );
else if ( CtClass.floatType.equals( persistentField.getType() ) ) {
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Float.TYPE );
}
else {
return new ObjectAttributeTypeDescriptor( persistentField.getType() );
return new ObjectAttributeTypeDescriptor( inheritanceMetadata, persistentField.getType() );
}
}
@ -114,19 +129,45 @@ public abstract class AttributeTypeDescriptor {
private final String type;
private ObjectAttributeTypeDescriptor(CtClass concreteType) {
private ObjectAttributeTypeDescriptor(InheritanceMetadata inheritanceMetadata, CtClass concreteType) {
super( inheritanceMetadata );
this.type = concreteType.getName();
}
@Override
public String buildReadInterceptionBodyFragment(String fieldName) {
if ( inheritanceMetadata.isInherited() && !inheritanceMetadata.isVisible() ) {
return String.format(
" if( %3$s() != null ) { super.%5$s( (%2$s) %3$s().readObject(this, \"%1$s\", super.%4$s())); }%n",
fieldName,
type,
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
inheritanceMetadata.getReaderName(),
inheritanceMetadata.getWriterName() );
}
else {
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 );
}
}
@Override
public String buildWriteInterceptionBodyFragment(String fieldName) {
if ( inheritanceMetadata.isInherited() && !inheritanceMetadata.isVisible() ) {
return String.format(
" %2$s localVar = $1;%n" +
" if ( %3$s() != null ) { localVar = (%2$s) %3$s().writeObject(this, \"%1$s\", super.%4$s(), $1); }%n" +
" super.%5$s(localVar);",
fieldName,
type,
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
inheritanceMetadata.getReaderName(),
inheritanceMetadata.getWriterName() );
}
else {
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" +
@ -136,6 +177,7 @@ public abstract class AttributeTypeDescriptor {
EnhancerConstants.INTERCEPTOR_GETTER_NAME );
}
}
}
/**
* AttributeTypeDescriptor for primitive types
@ -144,7 +186,8 @@ public abstract class AttributeTypeDescriptor {
private final String type;
private PrimitiveAttributeTypeDescriptor(Class<?> primitiveType) {
private PrimitiveAttributeTypeDescriptor(InheritanceMetadata inheritanceMetadata, Class<?> primitiveType) {
super( inheritanceMetadata );
if ( !primitiveType.isPrimitive() ) {
throw new IllegalArgumentException( "Primitive attribute type descriptor can only be used on primitive types" );
}
@ -152,15 +195,41 @@ public abstract class AttributeTypeDescriptor {
this.type = primitiveType.getSimpleName().substring( 0, 1 ).toUpperCase( Locale.ROOT ) + primitiveType.getSimpleName().substring( 1 );
}
@Override
public String buildReadInterceptionBodyFragment(String fieldName) {
if ( inheritanceMetadata.isInherited() && !inheritanceMetadata.isVisible() ) {
return String.format(
" if (%3$s() != null ) { super.%5$s( %3$s().read%2$s(this, \"%1$s\", super.%4$s())); }",
fieldName,
type,
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
inheritanceMetadata.getReaderName(),
inheritanceMetadata.getWriterName() );
}
else {
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 );
}
}
@Override
public String buildWriteInterceptionBodyFragment(String fieldName) {
if ( inheritanceMetadata.isInherited() && !inheritanceMetadata.isVisible() ) {
return String.format(
" %2$s localVar = $1;%n" +
" if ( %4$s() != null ) { localVar = %4$s().write%3$s(this, \"%1$s\", super.%5$s(), $1); }%n" +
" super.%6$s(localVar);",
fieldName,
type.toLowerCase( Locale.ROOT ),
type,
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
inheritanceMetadata.getReaderName(),
inheritanceMetadata.getWriterName() );
}
else {
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" +
@ -172,5 +241,39 @@ public abstract class AttributeTypeDescriptor {
);
}
}
}
//
private static class InheritanceMetadata {
private boolean inherited;
private boolean visible;
private String readerName;
private String writerName;
public InheritanceMetadata(boolean inherited, boolean visible, String readerName, String writerName) {
this.inherited = inherited;
this.visible = visible;
this.readerName = readerName;
this.writerName = writerName;
}
public boolean isInherited() {
return inherited;
}
public boolean isVisible() {
return visible;
}
public String getReaderName() {
return readerName;
}
public String getWriterName() {
return writerName;
}
}
}

View File

@ -6,7 +6,9 @@
*/
package org.hibernate.bytecode.enhance.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@ -17,6 +19,7 @@ import javassist.CtClass;
import javassist.CtField;
import javassist.Modifier;
import javassist.NotFoundException;
import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker;
import org.hibernate.bytecode.enhance.internal.tracker.SimpleCollectionTracker;
import org.hibernate.bytecode.enhance.internal.tracker.SimpleFieldTracker;
@ -206,7 +209,7 @@ public class EntityEnhancer extends PersistentAttributesEnhancer {
}
private List<CtField> collectCollectionFields(CtClass managedCtClass) {
final List<CtField> collectionList = new LinkedList<CtField>();
List<CtField> collectionList = new ArrayList<>();
for ( CtField ctField : managedCtClass.getDeclaredFields() ) {
// skip static fields and skip fields added by enhancement
@ -222,20 +225,41 @@ public class EntityEnhancer extends PersistentAttributesEnhancer {
}
// HHH-10646 Add fields inherited from @MappedSuperclass
for ( CtField ctField : managedCtClass.getDeclaredFields() ) {
if ( !enhancementContext.isMappedSuperclassClass( ctField.getDeclaringClass() ) || Modifier.isStatic( ctField.getModifiers() ) ) {
continue;
// HHH-10981 There is no need to do it for @MappedSuperclass
if ( !enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
collectionList.addAll( collectInheritCollectionFields( managedCtClass ) );
}
if ( enhancementContext.isPersistentField( ctField ) ) {
return collectionList;
}
private Collection<CtField> collectInheritCollectionFields(CtClass managedCtClass) {
if ( managedCtClass == null || Object.class.getName().equals( managedCtClass.getName() ) ) {
return Collections.emptyList();
}
try {
CtClass managedCtSuperclass = managedCtClass.getSuperclass();
if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass ) ) {
return collectInheritCollectionFields( managedCtSuperclass );
}
List<CtField> collectionList = new ArrayList<CtField>();
for ( CtField ctField : managedCtSuperclass.getDeclaredFields() ) {
if ( !Modifier.isStatic( ctField.getModifiers() ) && enhancementContext.isPersistentField( ctField ) ) {
if ( PersistentAttributesHelper.isAssignable( ctField, Collection.class.getName() ) ||
PersistentAttributesHelper.isAssignable( ctField, Map.class.getName() ) ) {
collectionList.add( ctField );
}
}
}
collectionList.addAll( collectInheritCollectionFields( managedCtSuperclass ) );
return collectionList;
}
catch ( NotFoundException nfe ) {
return Collections.emptyList();
}
}
private void createCollectionDirtyCheckMethod(CtClass managedCtClass) {
try {

View File

@ -6,6 +6,10 @@
*/
package org.hibernate.bytecode.enhance.internal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
@ -76,42 +80,60 @@ public class PersistentAttributesEnhancer extends Enhancer {
}
private CtField[] collectPersistentFields(CtClass managedCtClass) {
final List<CtField> persistentFieldList = new LinkedList<CtField>();
List<CtField> persistentFieldList = new ArrayList<CtField>();
for ( CtField ctField : managedCtClass.getDeclaredFields() ) {
// skip static fields and skip fields added by enhancement
if ( Modifier.isStatic( ctField.getModifiers() ) || ctField.getName().startsWith( "$$_hibernate_" ) ) {
// skip static fields and skip fields added by enhancement and outer reference in inner classes
if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) {
continue;
}
// skip outer reference in inner classes
if ( "this$0".equals( ctField.getName() ) ) {
continue;
}
if ( enhancementContext.isPersistentField( ctField ) ) {
if ( !Modifier.isStatic( ctField.getModifiers() ) && enhancementContext.isPersistentField( ctField ) ) {
persistentFieldList.add( ctField );
}
}
// HHH-10646 Add fields inherited from @MappedSuperclass
// CtClass.getFields() does not return private fields, while CtClass.getDeclaredFields() does not return inherit
for ( CtField ctField : managedCtClass.getFields() ) {
if ( ctField.getDeclaringClass().equals( managedCtClass ) ) {
// Already processed above
// HHH-10981 There is no need to do it for @MappedSuperclass
if ( !enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
persistentFieldList.addAll( collectInheritPersistentFields( managedCtClass ) );
}
CtField[] orderedFields = enhancementContext.order( persistentFieldList.toArray( new CtField[0] ) );
log.debugf( "Persistent fields for entity %s: %s", managedCtClass.getName(), Arrays.toString( orderedFields ));
return orderedFields;
}
private Collection<CtField> collectInheritPersistentFields(CtClass managedCtClass) {
if ( managedCtClass == null || Object.class.getName().equals( managedCtClass.getName() ) ) {
return Collections.emptyList();
}
try {
CtClass managedCtSuperclass = managedCtClass.getSuperclass();
if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass ) ) {
return collectInheritPersistentFields( managedCtSuperclass );
}
log.debugf( "Found @MappedSuperclass %s to collectPersistenceFields", managedCtSuperclass.getName() );
List<CtField> persistentFieldList = new ArrayList<CtField>();
for ( CtField ctField : managedCtSuperclass.getDeclaredFields() ) {
if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) {
continue;
}
if ( !enhancementContext.isMappedSuperclassClass( ctField.getDeclaringClass() ) || Modifier.isStatic( ctField.getModifiers() ) ) {
continue;
}
if ( enhancementContext.isPersistentField( ctField ) ) {
if ( !Modifier.isStatic( ctField.getModifiers() ) && enhancementContext.isPersistentField( ctField ) ) {
persistentFieldList.add( ctField );
}
}
return enhancementContext.order( persistentFieldList.toArray( new CtField[persistentFieldList.size()] ) );
persistentFieldList.addAll( collectInheritPersistentFields( managedCtSuperclass ) );
return persistentFieldList;
}
catch ( NotFoundException nfe ) {
log.warnf( "Could not find the superclass of %s", managedCtClass );
return Collections.emptyList();
}
}
private PersistentAttributeAccessMethods enhancePersistentAttribute(
CtClass managedCtClass,
CtField persistentField) {
private PersistentAttributeAccessMethods enhancePersistentAttribute( CtClass managedCtClass, CtField persistentField) {
try {
final AttributeTypeDescriptor typeDescriptor = AttributeTypeDescriptor.resolve( persistentField );
AttributeTypeDescriptor typeDescriptor = AttributeTypeDescriptor.resolve( managedCtClass, persistentField );
return new PersistentAttributeAccessMethods(
generateFieldReader( managedCtClass, persistentField, typeDescriptor ),
generateFieldWriter( managedCtClass, persistentField, typeDescriptor )
@ -131,25 +153,58 @@ public class PersistentAttributesEnhancer extends Enhancer {
CtClass managedCtClass,
CtField persistentField,
AttributeTypeDescriptor typeDescriptor) {
final String fieldName = persistentField.getName();
final String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName;
String fieldName = persistentField.getName();
String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName;
String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName;
CtMethod tmpSuperReader = null;
CtMethod tmpSuperWriter = null;
CtMethod reader = null;
try {
boolean declared = persistentField.getDeclaringClass().equals( managedCtClass );
String declaredReadFragment = "this." + fieldName + "";
String superReadFragment = "super." + readerName + "()";
if (!declared) {
// create a temporary getter on the supper entity to be able to compile our code
try {
persistentField.getDeclaringClass().getDeclaredMethod( readerName );
persistentField.getDeclaringClass().getDeclaredMethod( writerName );
}
catch (NotFoundException nfe){
tmpSuperReader = MethodWriter.addGetter( persistentField.getDeclaringClass(), persistentField.getName(), readerName );
tmpSuperWriter = MethodWriter.addSetter( persistentField.getDeclaringClass(), persistentField.getName(), writerName );
}
}
// 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.hasLazyLoadableAttributes( managedCtClass )
|| !enhancementContext.isLazyLoadable( persistentField ) ) {
return MethodWriter.addGetter( managedCtClass, fieldName, readerName );
reader = MethodWriter.write(
managedCtClass, "public %s %s() { return %s;%n}",
persistentField.getType().getName(),
readerName,
declared ? declaredReadFragment : superReadFragment
);
}
try {
return MethodWriter.write(
managedCtClass, "public %s %s() {%n%s%n return this.%s;%n}",
else {
reader = MethodWriter.write(
managedCtClass, "public %s %s() {%n%s%n return %s;%n}",
persistentField.getType().getName(),
readerName,
typeDescriptor.buildReadInterceptionBodyFragment( fieldName ),
fieldName
declared ? declaredReadFragment : superReadFragment
);
}
if ( tmpSuperReader != null ) {
persistentField.getDeclaringClass().removeMethod( tmpSuperReader );
}
if ( tmpSuperWriter != null ) {
persistentField.getDeclaringClass().removeMethod( tmpSuperWriter );
}
return reader;
}
catch (CannotCompileException cce) {
final String msg = String.format(
"Could not enhance entity class [%s] to add field reader method [%s]",
@ -172,15 +227,39 @@ public class PersistentAttributesEnhancer extends Enhancer {
CtClass managedCtClass,
CtField persistentField,
AttributeTypeDescriptor typeDescriptor) {
final String fieldName = persistentField.getName();
final String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName;
String fieldName = persistentField.getName();
String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName;
String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName;
CtMethod tmpSuperReader = null;
CtMethod tmpSuperWriter = null;
CtMethod writer;
try {
final CtMethod writer;
boolean declared = persistentField.getDeclaringClass().equals( managedCtClass );
String declaredWriteFragment = "this." + fieldName + "=" + fieldName + ";";
String superWriteFragment = "super." + writerName + "(" + fieldName + ");";
if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass )
|| !enhancementContext.isLazyLoadable( persistentField ) ) {
writer = MethodWriter.addSetter( managedCtClass, fieldName, writerName );
if (!declared) {
// create a temporary setter on the supper entity to be able to compile our code
try {
persistentField.getDeclaringClass().getDeclaredMethod( readerName );
persistentField.getDeclaringClass().getDeclaredMethod( writerName );
}
catch (NotFoundException nfe){
tmpSuperReader = MethodWriter.addGetter( persistentField.getDeclaringClass(), persistentField.getName(), readerName );
tmpSuperWriter = MethodWriter.addSetter( persistentField.getDeclaringClass(), persistentField.getName(), writerName );
}
}
if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) || !enhancementContext.isLazyLoadable( persistentField ) ) {
writer = MethodWriter.write(
managedCtClass,
"public void %s(%s %s) {%n %s%n}",
writerName,
persistentField.getType().getName(),
fieldName,
declared ? declaredWriteFragment : superWriteFragment
);
}
else {
writer = MethodWriter.write(
@ -203,12 +282,7 @@ public class PersistentAttributesEnhancer extends Enhancer {
);
}
else {
writer.insertBefore(
typeDescriptor.buildInLineDirtyCheckingBodyFragment(
enhancementContext,
persistentField
)
);
writer.insertBefore( typeDescriptor.buildInLineDirtyCheckingBodyFragment( enhancementContext, persistentField ) );
}
handleCompositeField( managedCtClass, persistentField, writer );
@ -217,6 +291,13 @@ public class PersistentAttributesEnhancer extends Enhancer {
if ( enhancementContext.doBiDirectionalAssociationManagement( persistentField ) ) {
handleBiDirectionalAssociation( managedCtClass, persistentField, writer );
}
if ( tmpSuperReader != null ) {
persistentField.getDeclaringClass().removeMethod( tmpSuperReader );
}
if ( tmpSuperWriter != null ) {
persistentField.getDeclaringClass().removeMethod( tmpSuperWriter );
}
return writer;
}
catch (CannotCompileException cce) {

View File

@ -172,6 +172,13 @@ public class EnhancerTest extends BaseUnitTestCase {
@TestForIssue( jiraKey = "HHH-10646" )
public void testMappedSuperclass() {
EnhancerTestUtils.runEnhancerTestTask( MappedSuperclassTestTask.class );
EnhancerTestUtils.runEnhancerTestTask( MappedSuperclassTestTask.class, new EnhancerTestContext() {
@Override
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
// HHH-10981 - Without lazy loading, the generation of getters and setters has a different code path
return false;
}
} );
}
@Test

View File

@ -29,7 +29,7 @@ public class MappedSuperclassTestTask extends AbstractEnhancerTestTask {
public void execute() {
Employee charles = new Employee( "Charles", "Engineer" );
charles.oca = 1002;
charles.setOca( 1002 );
// Check that both types of class attributes are being dirty tracked
EnhancerTestUtils.checkDirtyTracking( charles, "title", "oca" );
@ -47,9 +47,9 @@ public class MappedSuperclassTestTask extends AbstractEnhancerTestTask {
@MappedSuperclass private static class Person {
@Id String name;
@Id private String name;
@Version long oca;
@Version private long oca;
public Person(String name) {
this();