Hibernate Proxy Inheritance Issue
I am using the inheritance strategy table per concrete class and today I faced something very strange. I still don't know the reason for that problem but let me explain what it is...
I have the following classes:
@Entity
@Table(name="...")
@Inheritance(strategy=InheritanceType.JOINED)
public class A implements Serializable{...}
@Entity
@Table(name="...")
@PrimaryKeyJoinColumn(name="...")
public class B extends A{...}
Looks pretty simple, and the query i use to retrieve entries is basically this:
FROM A
This also returns instances of B which is what i expect. The entries are loaded into a backing bean for a view(Bean is viewscoped, persistence context has ended). The view accesses the bean via EL to retrieve the entries for a dataTable:
<h开发者_StackOverflow:dataTable value="#{bean.entries}" var="entry">...</h:dataTable>
Within the dataTable i have a commandLink like that:
<h:commandLink value="click" actionListener="#{bean.doSomething}">
<f:setPropertyActionListener value="#{entry}" target="#{bean.selected}" />
</h:commandLink>
The bean works with objects of type A, but the CDI-Decorator which is called when invoking the actionListener-Expression would do additional stuff if the type of the selected entry is the subclass:
public void doSomething(ActionEvent event){
if(delegate.getSelected() instanceof B){
// special
}else{
delegate.doSomething(event);
}
}
And now it gets complicated. The if is entered "sometimes" but not when expected. Debugging revealed that the Object returned by delegate.getSelected()
is of the type A_javassisst
, EVEN when it should be an instance of B. The best thing about that is, that the toString() method returns B@123 which let me believe at first place, that the object is of type B but it's not...
Now we come to my question... What the hell is happening there??? I have already thought of some serialization problems that could occur when saving the state of the dataTable, but I am not sure(Shouldn't the datatable retrieve the values returned by the value-expression and use these for walking through or would that maybe break the state?).
The dataTable is a PrimeFaces datatable, have not tried the JSF datatable but it can't be primefaces' fault...
All this on the following environment:
- WAS 8.0.0.1 (=> OpenWebBeans)
- CODI 1.1.1
- MyFaces 2.1.1
- Hibernate 3.6.5
Thanks in advance for your help!
EDIT:
All my objects are of type B, means that the db contains an entry in table "b" for every entry in table "a", but sometimes I get Objects from JPA/Hibernate returned that are not instance of B!! Please I need help in this, I have no idea why this could happen!?!
EDIT:
My diagnosis was wrong, the returned types are the right ones! I really have a Proxy of type B and not a Proxy of type A. My problem was that the method that was decorated was invoked before the setter. This is more JSF related than Hibernate!
I didn't face problems and pitfalls you reported yet, using instanceof works fine for me!
The symptoms match a hibernate limitation. Briefly, lazy-loading proxies for polymorphic entities respond differently to instanceof
than the entities they proxy. That's because the proxy is instantiated at a time the actual type of the entity is not yet known, and being a Java object, cannot change its runtime class after creation.
Hibernate will return lazy-loading-proxies instead of a materialized entity if
- You explictly request a proxy with
session.load()
- as stand-in for an entity referenced by a single-valued, lazily fetched assocation from another loaded entity
- a proxy has been previously created in the same hibernate session using the above means
Case 2 is most common. I have gone as far as to write a unit test that checks my mapping for polymorphic lazy non-null single-valued associations to alert me to this possibility.
There are ways to use instanceof
and casts with proxies, but they are not trivial. To cast, proxy interfaces must be declared for the polymorphic entities, and all code must program against these interfaces, rather than the entity classes. Hibernate will then have a proxy implement all interfaces the entity might possibly have, allowing all casts. For instanceof
, you could declare:
class A {
boolean isInstanceOf(Class<X extends A> clazz) {
return clazz.isInstance(this);
}
}
and then write
if (entity.isInstanceOf(B.class)) {
B b = (B) entity;
// work with b
}
instead of
if (entity instanceof B) {
...
Such is the issues with hibernate when using polymorphism.
In a nutshell, when trawling a hibernate model, where lazy is true, hibernate does not load the real type - loading the real type would requite a db hit and that wouldn't be lazy. If you want the real type to be returned you have to instrument the classes and set the lazy to "proxy". What this would do is not hydrate the proxy until you ask for it, only then would the db get queried to ascertain its real type - this behaviour however is only available with instrumentation on as instrumentation intercepts the call.
Turn off lazy wherever you can see it and see if the behaviour changes - it should work with lazy off.
This is one of the pitfalls of using hibernate.
I think clazz.isInstance(this) works, because this is the instance of the target object. That is the difference to instanceof. Take into consideration the the object is unproxied on that call.
Try to unproxy your object :
/**
*
* @param <T>
* @param entity
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T initializeAndUnproxy(T entity) {
if (entity == null) {
// throw new NullPointerException("Entity passed for initialization is null");
return null;
}
Hibernate.initialize(entity);
if (entity instanceof HibernateProxy) {
entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation();
}
return entity;
}
精彩评论