Hey, i’m playing with hibernate 6.6.15.Final and facing a weird issue that I believe was introduced recently as it works 6.6.10.Final(I think).
The issue is a bit hard to reproduce as the class load(or better to say hibernate parsing I believe) order is involved.
There are 2 entities A and B. they are in the different packages. and let’s B extends A ( or A extends B depending what will be parsed first)
both entities has protected fields and public getters/setters:
for example:
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class A {
@Id
protected long id;
protected String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import org.hibernate.bugs.A;
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class B extends A{
@Id
protected long id;
protected String bname;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getBname() {
return bname;
}
public void setBname(String bname) {
this.bname = bname;
}
}
Now start the test:
// Entities are auto-discovered, so just add them anywhere on class-path
// Add your tests, using standard JUnit.
@Test
void hhh123Test() throws Exception {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
// Do stuff...
org.hibernate.bugs.A a = entityManager.find(A.class, 1L);
entityManager.getTransaction().commit();
entityManager.close();
}
and see exception:
Suppressed: java.lang.NullPointerException: Cannot invoke "jakarta.persistence.EntityManagerFactory.close()" because "this.entityManagerFactory" is null
at org.hibernate.bugs.JPAUnitTestCase.destroy(JPAUnitTestCase.java:25)
... 3 more
Caused by: org.hibernate.HibernateException: java.lang.ClassCastException: class org.hibernate.bugs.A$HibernateAccessOptimizer5name cannot be cast to class org.hibernate.bytecode.spi.ReflectionOptimizer$AccessOptimizer (org.hibernate.bugs.A$HibernateAccessOptimizer5name and org.hibernate.bytecode.spi.ReflectionOptimizer$AccessOptimizer are in unnamed module of loader 'app')
at org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl.getReflectionOptimizer(BytecodeProviderImpl.java:280)
at org.hibernate.metamodel.internal.EntityRepresentationStrategyPojoStandard.resolveReflectionOptimizer(EntityRepresentationStrategyPojoStandard.java:325)
at org.hibernate.metamodel.internal.EntityRepresentationStrategyPojoStandard.<init>(EntityRepresentationStrategyPojoStandard.java:154)
at org.hibernate.metamodel.internal.ManagedTypeRepresentationResolverStandard.resolveStrategy(ManagedTypeRepresentationResolverStandard.java:62)
at org.hibernate.persister.entity.AbstractEntityPersister.<init>(AbstractEntityPersister.java:562)
at org.hibernate.persister.entity.JoinedSubclassEntityPersister.<init>(JoinedSubclassEntityPersister.java:187)
at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486)
at org.hibernate.persister.internal.PersisterFactoryImpl.createEntityPersister(PersisterFactoryImpl.java:94)
at org.hibernate.persister.internal.PersisterFactoryImpl.createEntityPersister(PersisterFactoryImpl.java:77)
at org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl.processBootEntities(MappingMetamodelImpl.java:250)
at org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl.finishInitialization(MappingMetamodelImpl.java:184)
at org.hibernate.internal.SessionFactoryImpl.initializeMappingModel(SessionFactoryImpl.java:373)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:302)
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:463)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1517)
... 7 more
Caused by: java.lang.ClassCastException: class org.hibernate.bugs.A$HibernateAccessOptimizer5name cannot be cast to class org.hibernate.bytecode.spi.ReflectionOptimizer$AccessOptimizer (org.hibernate.bugs.A$HibernateAccessOptimizer5name and org.hibernate.bytecode.spi.ReflectionOptimizer$AccessOptimizer are in unnamed module of loader 'app')
at org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl.getReflectionOptimizer(BytecodeProviderImpl.java:276)
... 23 more
I did a bit of debugging:
So it first loads/parse the “child” entity, in my specific case this is B. The code in org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl#getReflectionOptimizer(java.lang.Class<?>, java.util.Map<java.lang.String,org.hibernate.property.access.spi.PropertyAccess>) finds a superclass - which is A (A and B are in the different packages)
org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl#determineAccessOptimizerSuperClass
and loads/optimizes A
byteBuddyState.load(
foreignPackageClassInfo.clazz,
className,
(byteBuddy, namingStrategy) -> {
DynamicType.Builder<?> builder = byteBuddy.with( namingStrategy ).subclass( newSuperClass );
so after processing for B class is finished it will process A and in org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl#getReflectionOptimizer(java.lang.Class<?>, java.util.Map<java.lang.String,org.hibernate.property.access.spi.PropertyAccess>)
it will not optimize it with(probably because it was already loaded/optimized as a superclass?):
byteBuddyState.load( clazz, className, (byteBuddy, namingStrategy) -> byteBuddy
.with( namingStrategy )
.subclass( superClass )
.implement( ReflectionOptimizer.AccessOptimizer.class )
and so attempt to cast to ReflectionOptimizer.AccessOptimizer.class fails.