2
votes

I have an entity with @OneToOne association. Like this:

@Entity
public class Person {
    @Id Long id;
    @OneToOne(fetch = FetchType.LAZY) Address address;
}

When I load such entity Hibernate ignores LAZY. Vlad explains:

Lazy loading works except for the parent side of a @OneToOne association. This is because Hibernate has no other way of knowing whether to assign a null or a Proxy to this variable.

Similar statement here. I don't get it. There is FK column PERSON.ADDRESS_ID and in case there is any value Hibernate should know Proxy should be used. Am I missing something?


UPADTE: My original code was in Kotlin. I have tried to create the same example in Java and surprisingly lazy loading works fine there.

1
When I load such entity - please show how did you load the entity. What hibernate version do you use?SternK
I am using simple Spring Data JpaRepository (findAll). Spring Boot (+ Spring Data JPA) 2.4.4, Hibernate 5.4.29Mamut
And how did you identify that hibernate ignore LAZY?SternK
I will post complete example on GitHub. I have activated spring.jpa.properties.hibernate.show_sql=true.Mamut

1 Answers

2
votes

Try to understand it using @OneToMany relationship.

When you have that, you specify some collection i.e List, for example we have an entity

class A {
   @OneToMany
   List<B> bs;

   public List<B> getBs() {
      return bs;
   }
}

So when hibernate loads the A, it is able to identify that you have List<B> and you may call getBs() just after the class is loaded so hibernate creates a wrapper list which doesn't have any B yet and it will wait until you perform any operation on the list ie. iterate, add etc.

As soon as you perform the operation, hibernate will issue the query and load the objects into the set, hence lazy loading works fine here.

That's why one-to-many by default is lazy

Now let's take example of @OneToOne

class A {
   @OneToOne
   B b;
   
   public B getB() {}
}

When hibernate loads A, it will see that user may call the getB just after A is loaded, so it needs to initialise B as well.

Now, even if B supports proxy, hibernate have to initialise it with proxy or null and how that decision will be made, it will have to query the B to check if it exists or not but if it queries just to check, why just check only, why not initialise it fully, hence it does it eagerly, ignoring the Lazy attribute1

But this is not true for child side, if you specify the @One-To-One on child side

class B {
   @OneToOne(lazy)
   A a;
   
   public A getA() {}
}

Because this is the entity for table which holds the foreign key to A entity table, hibernate will initialise it with the proxy of A because hibernate knows that this entity is child entity and has foreign key associated, so it can lazy load when required, if it's null, you would get null A.

Correction:

The above behaviour is obvious for the optionable relation (optional = true) as I have already explained and you may find other answers stating that, but it is not obvious when you use optional=false.

With non-optional relation, we would think that hibernate identify that there would be a child present for the parent so hibernate will initialise the proxy and it should depict the lazy loading behaviour.

But to even initialise the proxy, hibernate will need minimum information like identifier and it would need to query from the child table, hence it becomes the same case as optional relation and loads eagerly.

There is one solution to still make it work though (at least I thought so), that if you share the primary key of your parent entity with the child entity using @MapsId

class A {
    @Id
    private Integer id;
    
    @OneToOne(fetch = Lazy, mappedBy = "a", optional = false)
    private B b;
}

class B {
   @Id
   private Integer id;
   
   @OneToOne
   @MapsId
   private A a;
}

This should have worked, because now you are sharing the parent primary key with the child and hibernate now knows the identifier and it doesn't need to query it from the table and should be able to initialise the proxy easily.

However, it doesn't work and still loads eagerly which is strange and after a little digging, I found this issue reported by Vlad himself.2

Although I found a workaround in related issues and have also asked on the above issue about it if that is a valid one, that's why not posting here.


1

2I checked this behaviour using hibernate version 5.4.8.Final and 5.4.30.Final