How to Make a JPA Onetoone Relation Lazy

How can I make a JPA OneToOne relation lazy

First off, some clarifications to KLE's answer:

  1. Unconstrained (nullable) one-to-one association is the only one that can not be proxied without bytecode instrumentation. The reason for this is that owner entity MUST know whether association property should contain a proxy object or NULL and it can't determine that by looking at its base table's columns due to one-to-one normally being mapped via shared PK, so it has to be eagerly fetched anyway making proxy pointless. Here's a more detailed explanation.

  2. many-to-one associations (and one-to-many, obviously) do not suffer from this issue. Owner entity can easily check its own FK (and in case of one-to-many, empty collection proxy is created initially and populated on demand), so the association can be lazy.

  3. Replacing one-to-one with one-to-many is pretty much never a good idea. You can replace it with unique many-to-one but there are other (possibly better) options.

Rob H. has a valid point, however you may not be able to implement it depending on your model (e.g. if your one-to-one association is nullable).

Now, as far as original question goes:

A) @ManyToOne(fetch=FetchType.LAZY) should work just fine. Are you sure it's not being overwritten in the query itself? It's possible to specify join fetch in HQL and / or explicitly set fetch mode via Criteria API which would take precedence over class annotation. If that's not the case and you're still having problems, please post your classes, query and resulting SQL for more to-the-point conversation.

B) @OneToOne is trickier. If it's definitely not nullable, go with Rob H.'s suggestion and specify it as such:

@OneToOne(optional = false, fetch = FetchType.LAZY)

Otherwise, if you can change your database (add a foreign key column to owner table), do so and map it as "joined":

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="other_entity_fk")
public OtherEntity getOther()

and in OtherEntity:

@OneToOne(mappedBy = "other")
public OwnerEntity getOwner()

If you can't do that (and can't live with eager fetching) bytecode instrumentation is your only option. I have to agree with CPerkins, however - if you have 80!!! joins due to eager OneToOne associations, you've got bigger problems then this :-)

Lazy loading of @OneToOne relation with Hibernate

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.



1Some of the older version of hibernate does support the lazy loading from parent side as well but that behaviour is removed in recent versions because they needed to check the existence of the child.

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

Issue about JPA One to One Lazy Loading

Address and Member are the same. They own the column representing the association. Let's say, for example, the column team_id in the table Member and the column order_id in the table Address.

When Hibernate ORM reads a row from the table Member, by checking the value of the team_id column, it knows if that value is null or if there is an association. Therefore, when the entity is created it can decide if the value mapping the association (Mapping#team)is null or a proxy. In this case lazy fetching is possible.

The table Order doesn't have a column representing the association, that's because the association is mapped by the column order_id in Address. When Hibernate ORM needs to convert a row in the table Order to an entity, it also needs to check the table Address to see if an association exists. And depending on the result, it will set the value Order#address to null or the associated address. In this case, it cannot know if the value of the association is null or not without checking the table Address: lazy fetching is not possible.

Note that it might still be possible to lazy fetch the association when the mapping states that the association is never null: @OneToOne(mappedBy="order", optional=false).

Basically, the problem is that if a value can be mapped to null, Hibernate ORM needs to check first if this value is actually null because, otherwise, code like if (order.address == null) { is not going to work if a proxy is created.

Empty *-to-many associations are different in this regard because Hibernate ORM maps them with an empty collection (and never with null) and this means that it can always return a proxy.

Spring JPA Lazy loading @OneToOne entities doesn't work

Have a look at Vlad Mihalceas article The best way to map a @OneToOne relationship with JPA and Hibernate

As described there, one of the solutions would be to drop the relation on the parentside...

@Transient
private BillingAddressEntity billingAddress;

and load the BillingAddressEntity manually using the shared id.

if (order.billingAddress == null) 
{
order.billingAddress = entityManager.find(BillingAddressEntity.class, order.id);
}


Another approach would be to drop the shared key, use a foreign key field instead and mark the relation as @ManyToOne. This will sacrifice the OneToOne constraint check though.

@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "billing_address_id")
private BillingAddressEntity billingAddress;


Then there's also the byte code enhancement which would allow you to make this a @LazyToOne(LazyToOneOption.NO_PROXY)relation. I can't help you with that though, as I've never done that myself.



Related Topics



Leave a reply



Submit