|
Given two entities, A and B, A has a many-to-one lazy reference to B with no-proxy laziness type. First, we get A instance from session, then get its reference to B, then load all Bs from session. After these operations Hibernate can violate reference equality constraint, i.e. there will be two instances of B with the same primary key, one is original B instance, another is a proxy.
To run the attached
The reason is in the following code (org.hibernate.loader.Loader, lines 739-750):
if ( returnProxies ) {
for ( int i = 0; i < entitySpan; i++ ) {
Object entity = row[i];
Object proxy = session.getPersistenceContext().proxyFor( persisters[i], keys[i], entity );
if ( entity != proxy ) {
( (HibernateProxy) proxy ).getHibernateLazyInitializer().setImplementation(entity);
row[i] = proxy;
}
}
}
So, Hibernate returns proxy from Query.getResultList() when there is proxy in session. But this proxy is in session, since no-proxy and proxy lazy options are implemented the same way, except no-proxy also unwraps proxy immediately. So, when we call a.getB(), we create proxy for B, but then simply don't use it.
To reproduce the issue, just build the attached project (mvn clean package) and run the built jar file {[java -jar target/hibernate-bug-0.0.1-SNAPSHOT.jar}}.
Despite the Hibernate documentation claims that
This approach requires buildtime bytecode instrumentation and is rarely necessary.
this issue is very important. I can't agree with the statement, as when you deal with reach domain model, you always have complex mappings together with lazy loading and inheritance. Regarding persistence ignorance principle, we can't refer directly to Hibernate (or some other ORM) inside our domain, so we have to rely on correct behavior of Hibernate out of the box, without injecting workarounds into domain code. Proxies can't be good solution in this case. Personally, I use no-proxy loading on every lazy relation. Also, take a look at EclipseLink, that does not support lazy loading without bytecode instrumentation.
|