Hi,
We've encountered a pretty strange error, it seems that the entity manager loads the entity from the second level cache ignoring the entity class
We're using Spring 3.1.3 with Hibernate 4.1.7 (though I tested it with 4.3.0 and the problem still exists). Let's suppose that we have a root entity:
Code:
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(name = "test_entities")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class TestEntityRoot {
@Id
@GeneratedValue(generator = "entity_id_generator")
@GenericGenerator(name = "entity_id_generator", strategy
= "org.hibernate.id.enhanced.TableGenerator", parameters = {
@Parameter(name = TableGenerator.CONFIG_PREFER_SEGMENT_PER_ENTITY, value = "true"),
@Parameter(name = TableGenerator.INCREMENT_PARAM, value = "100"),
@Parameter(name = TableGenerator.OPT_PARAM, value = "pooled")})
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
and two child entities, A
Code:
@Entity
public class TestEntityA extends TestEntityRoot{
}
and B
Code:
@Entity
public class TestEntityB extends TestEntityRoot{
}
And we have the following (minimal) test class:
Code:
@Component
@Transactional
public class TestHibernate implements ITestHibernate {
@PersistenceContext
private EntityManager entityManager;
public static void main(String[] args) {
ITestHibernate test = new ClassPathXmlApplicationContext("classpath:/META-INF/context.xml").getBean(ITestHibernate.class);
long id = test.createA();
test.createB();
test.loadAll();
test.testLoad(id);
}
@Override
public long createA() {
TestEntityA entity = new TestEntityA();
entityManager.persist(entity);
return entity.getId();
}
@Override
public void createB() {
TestEntityB entity = new TestEntityB();
entityManager.persist(entity);
}
@Override
public void loadAll() {
entityManager.unwrap(Session.class).createQuery("select E from " + TestEntityRoot.class.
getName() + " E").setCacheable(true).list();
}
@Override
public void testLoad(long id) {
TestEntityB b = entityManager.find(TestEntityB.class, id);
System.out.println(b);
}
}
If we run this class it would fail in the testLoad() method with
Code:
Exception in thread "main" java.lang.ClassCastException: test.TestEntityA cannot be cast to test.TestEntityB
at test.TestHibernate.testLoad(TestHibernate.java:57)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:318)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at com.sun.proxy.$Proxy108.testLoad(Unknown Source)
at test.TestHibernate.main(TestHibernate.java:33)
So, basically, the test creates two entities A and B, then loads them both using and HQL query and then tries to read B with the id of A. All four methods use the separate transactions and thus each has its own session.
If the @Cache annotation is commented then the test works as expected, i.e., the find() method returns null. But if the cache is enabled then it loads A despite the fact that it was told to load an instance of B. I debugged it a little and it seems that this problem is caused by the following lines in the org.hibernate.event.internal.DefaultLoadEventListener's loadFromSecondLevelCache method:
Code:
final CacheKey ck = source.generateCacheKey(
event.getEntityId(),
persister.getIdentifierType(),
persister.getRootEntityName()
);
It seems that it ignores the entity name and uses the root entity instead. Is it a bug in Hibernate or am I missing something? And is there a fix or workaround to this issue besides disabling the second level cache?