Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate @OneToMany causing multiple select statements

Tags:

java

hibernate

I am working on a Hibernate issue, which involves 2 separate Entity beans defined separately in their own classes:

  • Store
  • StoreServer

Note that a Store will have more than one StoreServer - hence the use of the @OneToMany annotation. Please see the code snippets as follows:

Store:

@Entity
@Table(name="Store")
public class Store implements Serializable {
/**
* Serializable class - generated UID
*/
private static final long serialVersionUID = 5644190852867691168L;

@Id
@Column(name="STORE_NO", nullable=false)
private int storeNumber;

@Column(name="STORE_NAME", nullable=false)
private String storeName;

@Column(name="STORE_PHONE", nullable=false)
private String storePhone;

//other Store fields...

@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name="STORE_NO", insertable=false, updatable=false)
private List<StoreServer> storeServers = new ArrayList<StoreServer>();

//getters and setters

StoreServer:

@Entity
@Table(name="Store_Server")
public class StoreServer implements Serializable {
/**
* Serializable class - generated UID
*/
private static final long serialVersionUID = -5410564578856243437L;

@Id
private StoreServerPK storeServerPK;

@Column(name="IP_ADDRESS", nullable=true)
private String ipAddress;

//other StoreServer fields...getters and setters

Since StoreServer has a composite Primary Key, here is StoreServerPK:

@Embeddable
public class StoreServerPK implements Serializable {
/**
* Serializable class - generated UID
*/
private static final long serialVersionUID = -1401889029390423604L;

@Column(name="STORE_NO", nullable=false)
protected int storeNumber;

@Column(name="SERVER_NO", nullable=false)
protected String serverNumber;

//getters and setters

At present, I am getting the correct results, but the performance is unacceptably SLOW. I have switched on logging in Hibernate and I can see that a separate SELECT query is being run for each Store Entity in order to obtain the associated StoreServer records.

Currently, in the logs, I see a single SELECT statement to obtain the Store records (more than 200 results returned). Then for each store, a new SELECT statement to get the StoreServer records. My question is...Why is Hibernate not doing a join (running one query)?

Please could I get some help on how to tell Hibernate to run a single query, using a JOIN?

Thank you

like image 671
user2318704 Avatar asked Oct 31 '22 11:10

user2318704


1 Answers

It is called N+1 problem

The solution actually depends on how do you make your query - in case if you are using Criteria API you should use Root.fetch method:

CriteriaBuilder qb = em.getCriteriaBuilder();
CriteriaQuery<Store> cq = qb.createQuery(Store.class);

Root<Store> root = cq.from(Store.class);
root.fetch(App_.storeServers, JoinType.LEFT);

cq.select(root);

return em.createQuery(cq).getResultList();

If you are using HQL you should use fetch keyword:

select distinct st from Store st left join fetch st.storeServers

It might be a good idea to validate the number of queries generated by Hibernate in your unit tests using in-memory database like H2 and JDBC Sniffer

like image 76
bedrin Avatar answered Nov 15 '22 04:11

bedrin