BytecodeProviderImpl.findAccessor() throws an exception when called to find the accessor method for a property that is inherited:
This is likely because it relies only on clazz.getDeclaredMethod( name, params ) and nothing outside it:
private static Method findAccessor(Class clazz, String name, Class[] params, int index)
throws BulkAccessorException {
try {
final Method method = clazz.getDeclaredMethod( name, params ); if ( Modifier.isPrivate( method.getModifiers() ) ) {
throw new BulkAccessorException( "private property", index );
}
return method;
}
catch (NoSuchMethodException e) {
throw new BulkAccessorException( "cannot find an accessor", index );
}
}
The method names that end up passed to the above findAccessor() method are discovered based on the methods that have already been found, which brings the question as to why do they need to be found again as well. The code that discovers them before does iterate over subclasses to find them all - the following code is in ReflectHelper:
public static Method findGetterMethod(Class containerClass, String propertyName) {
Class checkClass = containerClass;
Method getter = null;
while ( getter == null && checkClass != null ) {
if ( checkClass.equals( Object.class ) ) {
break;
}
getter = getGetterOrNull( checkClass, propertyName );
if ( getter == null ) {
getter = getGetterOrNull( checkClass.getInterfaces(), propertyName );
}
checkClass = checkClass.getSuperclass();
}
...
}
But the code in PojoEntityTuplizer then gets their names only and passes those names on, instead of the methods themselves. The following is a relevant snippet from the PojoEntityTuplizer constructor and it has a possibly relevant "todo" comment in there:
String[] getterNames = new String[propertySpan];
String[] setterNames = new String[propertySpan];
Class[] propTypes = new Class[propertySpan];
for ( int i = 0; i < propertySpan; i++ ) {
getterNames[i] = getters[i].getMethodName();
setterNames[i] = setters[i].getMethodName();
propTypes[i] = getters[i].getReturnType();
}
if ( hasCustomAccessors || !Environment.useReflectionOptimizer() ) {
optimizer = null;
}
else {
optimizer = Environment.getBytecodeProvider().getReflectionOptimizer(
mappedClass,
getterNames,
setterNames,
propTypes
);
I cannot create a small yet comprehensive test case yet, unfortunately. Additional notes
- Mapping is loaded from (legacy) *.hbm.xml files. Code has no relevant annotations.
- Entity bytecode is enhanced by the hibernate-enhance-maven-plugin with enableLazyInitialization, enableDirtyTracking and enableAssociationManagement set to true and enableExtendedEnhancement set to false.
- I am not aware of any workarounds - I would appreciate if someone could identify them.
- Despite the bytecode enhancement with lazy initialization enabled, BytecodeEnhancementMetadataPojoImpl instance for the problem class, at the time when the exception is thrown, has the enhancedForLazyLoading field set to false. This may be an unrelated issue or may indicate a possibility of a workaround.
|