Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reduce number of queries for JPQL POJO containing an entity

Entity relation: Transaction(@ManyToOne - eager by default) -> Account

String sql = "SELECT new com.test.Pojo(t.account, SUM(t.value)) FROM Transaction t GROUP BY t.account";
List list = entityManager.createQuery(sql).getResultList();

By default JPA using Hibernate implementation will generate 1 + n queries. The n queries are for lazy loading of the account entities.

How can I make this query eager and load everything with a single query? The sql equivalent would be something like

SELECT account.*, SUM(t.value) FROM transactions JOIN accounts on transactions.account_id = accounts.id GROUP BY account.id

, a syntax that works well on PostgreSQL. From my findings Hibernate is generating a query that justifies the lazy loading.

SELECT account.id, SUM(t.value) FROM transactions JOIN accounts on transactions.account_id = accounts.id GROUP BY account.id
like image 567
johnlemon Avatar asked Oct 06 '16 07:10

johnlemon


People also ask

How do you map native query results to pojo?

Solution: JPA supports @SqlResultSetMappings which you can use to map the query result to a POJO. The following code snippet shows an example of such a mapping. The @ConstructorResult annotation defines a constructor call of the BookValue class.

What is the difference between native query and JPQL?

JPQL is the most commonly used query language with JPA and Hibernate. It provides an easy way to query data from the database. But it supports only a small subset of the SQL standard, and it also does not support database-specific features. If you want to use any of these features, you need to use a native SQL query.

What is the difference between JPA and JPQL?

The main difference between JPQL and SQL lies in that the former deals with JPA entities, while the latter deals directly with relational data.

How do you map native query results to entities?

The easiest way to map a query result to an entity is to provide the entity class as a parameter to the createNativeQuery(String sqlString, Class resultClass) method of the EntityManager and use the default mapping.


1 Answers

Try marking the @ManyToOne field as lazy:

@ManyToOne(fetch = FetchType.LAZY)
private Account account;

And change your query using a JOIN FETCH of the account field to generate only one query with all you need, like this:

String sql = "SELECT new com.test.Pojo(acc, SUM(t.value)) "
    + "FROM Transaction t JOIN FETCH t.account acc GROUP BY acc";

UPDATE:

Sorry, you're right, the fetch attribute of @ManyToOne is not required because in Hibernate that is the default value. The JOIN FETCH isn't working, it's causing a QueryException: "Query specified join fetching, but the owner of the fetched association was not present".

I have tried with some other approaches, the most simple one that avoids doing n + 1 queries is to remove the creation of the Pojo object from your query and process the result list, manually creating the objects:

String hql = "SELECT acc, SUM(t.value)"
  + " FROM " + Transaction.class.getName() +  " t"
  + " JOIN t.account acc"
  + " GROUP BY acc";

Query query = getEntityManager().createQuery(hql);
List<Pojo> pojoList = new ArrayList<>();
List<Object[]> list = query.getResultList();

for (Object[] result : list)
    pojoList.add(new Pojo((Account)result[0], (BigDecimal)result[1]));
like image 159
JMSilla Avatar answered Oct 16 '22 09:10

JMSilla