Rakesh Kumar Cherukuri
Rakesh Kumar Cherukuri

Reputation: 1271

JPA and Hibernate : one to one mapping causes three select queries

JPA 2.0 Hibernate 4.3.5

Hi,

Following is my OneToOne mapping (with example code assuming 1 customer can only have 1 order)

class Customer {
    private Order order;
    @OneToOne(mappedBy="customer", fetch=FetchType.LAZY)
    public Order getOrder() { return order; }
    public void setOrder(Order order) { this.order = order ; }
}

class Order {
    private Customer customer;
    @OneToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="cust_id")
    public Customer getCustomer() { return customer; }
    public void setCustomer(Customer customer) { this.customer = customer; }
}

//Calling code
Order order = em.find(Order.class, 4);    // Line 1
System.out.println(order.getCustomer());  // Line 2
</code>

Above calling code is actually resulting in 3 select statements i.e

Line 1 causing

   select * from order where id = ?      #1

Line 2 causing following two statements

   select * from customer where id = ?   #2
   select * from order where cust_id = ? #3

My Question: Shouldn't it be only two queries (i.e #1 and #2) given that the LAZY fetching is enabled on both ends ?

Thanks, Rakesh

Upvotes: 7

Views: 4549

Answers (4)

M2je
M2je

Reputation: 127

I think the problem is on one-to-one relations you can not have lazy loading, take a look at https://community.jboss.org/wiki/SomeExplanationsOnLazyLoadingone-to-one.

Since you are loading the order object before the customer, it will issues the next two queries for loading the customer and then its one-to-one relations.

One possibility to simply avoid this issue is to define the customer object to be optional:

class Customer {
    private Order order;
    @OneToOne(mappedBy="customer", fetch=FetchType.LAZY,**optional=true**)
    public Order getOrder() { return order; }
    public void setOrder(Order order) { this.order = order ; }
}

Upvotes: 2

Maarten Winkels
Maarten Winkels

Reputation: 2417

It will never be two queries, because of the way your tables are laid out.

Setting fetchMode to LAZY instructs hibernate to create a proxy. Such a proxy contains the type of the object to load plus its key (id) so that hibernate can load it later on when any of it's methods are used (some exceptions).

So when hibernate loads an order from the db, it can easily construct the order object and the customer proxy: it has all the information from the order table, since the cust_id is in that table. [This is what happens on the first line of your calling code]

This is not the case for loading the customer object: in order to create the order for the proxy object, hibernate would have to query the order table anyway, since the key for that proxy is defined in that table. That is why you always see that third query. [This is what happens on the second line of your calling code: the toString() method is invoked on the proxy and loads the actual data.]

Setting fetchMode to LAZY on the reverse side of a one-to-one association has thus virtually no effect on terms of queries. The situation would be different for one-to-many.

Depending on what you really need, you could come up with exotic mappings - like reversing the one-to-one association with a join or joining in just a couple of fields - to help improve the performance.

Upvotes: 2

Xstian
Xstian

Reputation: 8272

If you want to solve the problem in only one query, you should make a variant of "n+1 query problem", solved by JOIN FETCH.

FETCH JOINS as the easiest way to implement and solve the (N Time M + 1) query problem.

SELECT item FROM ItemRecord
 JOIN FETCH item.costs,
 JOIN FETCH item.notes,
 JOIN FETCH item.stats;

I suggest you this article.

Upvotes: 1

uaiHebert
uaiHebert

Reputation: 1912

You have defined both relationships as LAZY.

When you load the Order the Customer attribute will not be loaded because it is LAZY. A LAZY attribute will only be loaded when it is accessed or if you define it to be eagerly loaded.

Every mapping finished with one (OneToOne or ManyToOne) will be eagerly by default, but you set it as LAZY.

Try to define your relationship as EAGER:

@OneToOne(fetch=FetchType.EAGER) // or just @OneToOne
@JoinColumn(name="cust_id")
public Customer getCustomer() { return customer; }

By the way, your customer can have only one order? O.o

Upvotes: 4

Related Questions