| Andrea Boriero and I have been working on this and I think it is pretty close. I wanted to outline the approach being taken to get feedback. PR forthcoming soon too... First a few terms so we can have a common "language" in discussing this. For lack of a better name we called this a "bytecode proxy" or "enhancement proxy". We already have the notion of "fetch groups", including the "default fetch group"; this work adds a new conceptual "base fetch group" (think all of the state that was previously "loaded" when the enhanced entity is created. The first step was to "clean up" org.hibernate.mapping.Property#isLazy, org.hibernate.persister.entity.EntityPersister#hasProxy and a few other questionable named/used methods. The javadocs I added to Property#isLazy explains a lot of this:
/**
* Is this property lazy in the "bytecode" sense?
*
* Lazy here means whether we should push *something* to the entity
* instance for this field in its "base fetch group". Mainly it affects
* whether we should list this property's columns in the SQL select
* for the owning entity when we load its "base fetch group".
*
* The "something" we push varies based on the nature (basic, etc) of
* the property.
*/
public boolean isLazy() {
if ( value instanceof ToOne ) {
return false;
}
return lazy;
}
The important take away is the impact on the SQL select for the base fetch group. As noted in other comments - previously if enhancement is used and NO_PROXY is specified for a to-one, the columns making up the FK are not rendered in the SQL select for the base fetch group. This change effectively changes that behavior to always select the FKs for all to-ones (just the FK value). So when we go to hydrate/resolve the to-one property from a ResultSet, its FK value is always available in the result set. In the NO_PROXY case, this then leads us to... org.hibernate.event.internal.DefaultLoadEventListener#proxyOrLoad was also changed to understand and account for this. The main change here is the addition of a new org.hibernate.engine.spi.PersistentAttributeInterceptor implementation I named EnhancementAsProxyLazinessInterceptor (better name forthcoming). Back in #proxyOrLoad, in this "bytecode proxy" case, we:
- instantiate the enhanced entity,
- set its identifier,
- create a EnhancementAsProxyLazinessInterceptor,
- push the interceptor to the entity
The interceptor triggers loading of the entity's base fetch group at which time it also injects the "normal" interceptor to the entity. All in all this works very well. But there are downsides and it changes certain little behaviors, hence I am adding a setting to enable/disable this (disabled by default I am thinking for compatibility). The downsides mainly center on org.hibernate.Hibernate and its methods returning different answers. Specifically:
- #isInitialized - I added a block to deal with these bytecode proxies. That itself is not so bad I guess since this is something new.
- #isPropertyInitialized - this is the bigger one. The contract for this method, when passed an enhanced entity, is to return whether that property is set on the instances field (back to the "bytecode lazy" comments above). Well with this new bytecode proxy feature all the to-one associations are initialized (once the base fetch group is loaded). For example, this leads to lots of test failures which assume a call to Hibernate.isPropertyInitialized( entity, "theToOne" ) returning true means the MyToOne entity itself is initialized. We could approximately the older behavior by having #isPropertyInitialized additionally check the initialized state of the associated entity if it is enhanced as well. Kind of torn about this. I think both pieces of information have value separately. I could be persuaded that this approximation is ok though. The other "solution" would be change the test code (and apps relying on this behavior as well) to check both or allow a setting to opt out of bytecode-proxy support.
I think we may also want to alter Hibernate#initialize to account for bytecode enhancement. ATM it does not, like at all. It only has any effect when the thing based in is an uninitialized traditional proxy or an uninitialized collection. For an uninitialized enhanced entity it does nothing. The question there becomes what exactly should happen. Does this trigger just the base fetch group? All fetch groups? Not sure there is a "correct" answer here. |