Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate lazy loading for reverse one to one workaround - how does this work?

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.

like image 782
RNJ Avatar asked Jun 17 '13 19:06

RNJ


People also ask

How does hibernate lazy loading work?

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.

How does JPA lazy loading work?

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.

What is the result of lazy loading strategy in?

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.

What is difference between lazy true and lazy false in hibernate?

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.


2 Answers

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:

  1. AnnotationBinder handles NO_PROXY option

    if ( lazy != null ) {
        toOne.setLazy( !( lazy.value() == LazyToOneOption.FALSE ) );
        toOne.setUnwrapProxy( ( lazy.value() == LazyToOneOption.NO_PROXY ) );
    }
    
  2. 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(

  3. 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();
    
  4. 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.

like image 62
lifus Avatar answered Sep 23 '22 11:09

lifus


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;
    }
}
like image 22
Michael de Hoog Avatar answered Sep 19 '22 11:09

Michael de Hoog