I was having problems today with lazy loading not working when using a mapped by collection. I found this excellent article that seems to fix the problem
http://justonjava.blogspot.co.uk/2010/09/lazy-one-to-one-and-one-to-many.html
One thing I do not understand is how the workaround using FieldHandled works. Can anyone help me understand this? The code in question is below (copied from the example on the link):
@Entity
public class Animal implements FieldHandled {
private Person owner;
private FieldHandler fieldHandler;
@OneToOne(fetch = FetchType.LAZY, optional = true, mappedBy = "animal")
@LazyToOne(LazyToOneOption.NO_PROXY)
public Person getOwner() {
if (fieldHandler != null) {
return (Person) fieldHandler.readObject(this, "owner", owner);
}
return owner;
}
public void setOwner(Person owner) {
if (fieldHandler != null) {
this.owner = fieldHandler.writeObject(this, "owner", this.owner, owner);
return;
}
this.owner = owner;
}
public FieldHandler getFieldHandler() {
return fieldHandler;
}
public void setFieldHandler(FieldHandler fieldHandler) {
this.fieldHandler = fieldHandler;
}
}
What am I missing? Perhaps I dont know enough about hibernate's lifecycle here? Im happy to investigate but can anyone give me some pointers.
Thanks in advance.
EDIT
I pushed through a lot of changes so a lot of my entities implemented FieldHandled but then discovered some of my tests were failing. I pumped out the SQL and got some weird things where the SQLs were happening in different orders if this interface was implemented with just these methods set.
public FieldHandler getFieldHandler() {
return fieldHandler;
}
public void setFieldHandler(FieldHandler fieldHandler) {
this.fieldHandler = fieldHandler;
}
This was causing tests to fail as things were not quite in the correct state when I was asserting. This adds to my mis-understanding of this FieldHandler variable.
Lazy loading in Hibernate means fetching and loading the data, only when it is needed, from a persistent storage like a database. Lazy loading improves the performance of data fetching and significantly reduces the memory footprint.
By default, JPA uses the lazy fetch strategy in associations of type @ElementCollection. Thus, any access to the collection in a closed Persistence Context will result in an exception. This test throws an exception when we try to access the phone list because the Persistence Context is closed.
Lazy loading helps you render an application faster. It also improves user experience by enhancing the bootstrap and startup process. This guide will give you hands-on experience with lazy loading implementation by covering the following topics: Eager loading and its limitations.
To use lazy collection, you may optionally use lazy="true" attribute in your collection. It is by default true, so you don't need to do this. If you set it to false, all the child objects will be loaded initially which will decrease performance in case of big data.
The following code tells Hibernate to use interception handler instead of proxy.
@LazyToOne(LazyToOneOption.NO_PROXY)
From javadoc:
give back the real object loaded when a reference is requested (Bytecode enhancement is mandatory for this option, fall back to PROXY if the class is not enhanced)
As can be seen, it's required to instrument the bytecode before using it. 'Persisted class is enhanced' after its 'bytecode is instrumented'.
The idea is to fool Hibernate that the entity class which we want to use has been already instrumented
Instrumentation task is called after code is compiled. Instrumented entity extends FieldHandled
. FieldHandled
is an 'Interface introduced to the enhanced class'
Hibernate verifies entity at a run time and comes to a conclusion that class was enhanced that's why it uses the real object instead of proxy and isn't loading related entity object as it normally did.
Edit:
Lets take a look under the hood:
AnnotationBinder handles NO_PROXY
option
if ( lazy != null ) {
toOne.setLazy( !( lazy.value() == LazyToOneOption.FALSE ) );
toOne.setUnwrapProxy( ( lazy.value() == LazyToOneOption.NO_PROXY ) );
}
Both org.hibernate.mapping.ManyToOne
and org.hibernate.mapping.OneToOne
are subclasses of org.hibernate.mapping.ToOne
. ToOne#isUnwrapProxy()
only usage is in #getType
:
getMappings().getTypeResolver().getTypeFactory().oneToOne(
Both ManyToOneType
and OneToOneType
are subclasses of EntityType
and only usage of 'EntityType#unwrapProxy' is in EntityType#resolveIdentifier(Serializable, SessionImplementor)
boolean isProxyUnwrapEnabled = unwrapProxy &&
session.getFactory()
.getEntityPersister( getAssociatedEntityName() )
.isInstrumented();
Here's Tentative call hierarchy: AbstractEntityPersister#isInstrumented()
->EntityMetamodel#isInstrumented()
->EntityInstrumentationMetadata#isInstrumented()
-> etc. and finally BytecodeProviderImpl.EntityInstrumentationMetadataImpl.EntityInstrumentationMetadataImpl()
this.isInstrumented = FieldHandled.class.isAssignableFrom( entityClass );
That's why it's required either instrument the code (e.g with InstrumentTask
) or implement FieldHandled
.
In order to make long story short you may take a look at EntityType#resolveIdentifier(Serializable, SessionImplementor)
. That's the reason why second object is not loaded even if it's nullable.
The FieldHandled
interface has been replaced with the PersistentAttributeInterceptable
interface in Hibernate 5. You can achieve the same result by implementing this new interface:
@Entity
public class Animal implements PersistentAttributeInterceptable {
private Person owner;
private PersistentAttributeInterceptor interceptor;
@OneToOne(fetch = FetchType.LAZY, optional = true, mappedBy = "animal")
@LazyToOne(LazyToOneOption.NO_PROXY)
public Person getOwner() {
if (interceptor != null) {
return (Person) interceptor.readObject(this, "owner", owner);
}
return owner;
}
public void setOwner(Person owner) {
if (interceptor != null) {
this.owner = interceptor.writeObject(this, "owner", this.owner, owner);
return;
}
this.owner = owner;
}
@Override
public PersistentAttributeInterceptor $$_hibernate_getInterceptor() {
return interceptor;
}
@Override
public void $$_hibernate_setInterceptor(PersistentAttributeInterceptor interceptor) {
this.interceptor = interceptor;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With