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.
12I checked this behaviour using hibernate version 5.4.8.Final and 5.4.30.Final
When I load such entity
- please show how did you load the entity. What hibernate version do you use? – SternK