Reduce number of queries for JPQL POJO containing an entity Reduce number of queries for JPQL POJO containing an entity postgresql postgresql

Reduce number of queries for JPQL POJO containing an entity


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]));


Well PostgreSQL (And any other SQL database too) will block you from using mentioned query: you have to group by all columns of account table, not by id. That is why Hibernate generates the query, grouping by ID of the account - That is what is intended to be, and then fetching the other parts. Because it cannot predict in general way, what else will be needed to be joined and grouped(!!!), and in general this could produce situation, when multiple entities with the same ID are fetched (just create a proper query and take a look at execution plan, this will be especially significant when you have OneToMany fields in your Account entity, or any other ManyToOne part of the Account entity) that is why Hibernate behaves this way.

Also, having accounts with mentioned IDs in First level cache, will force Hibernate to pick them up from that. Or IF they are rarely modified entities, you can put them in Second level cache, and hibernate will not make query to database, but rather pick them from Second level cache.

If you need to get those from database in single hint, but not use all the goodness of Hibernate, just go to pure JPA Approach based on Native queries, like this:

@NamedNativeQuery(        name = "Pojo.groupedInfo",        query = "SELECT account.*, SUM(t.value) as sum FROM transactions JOIN accounts on transactions.account_id = accounts.id GROUP BY account.id, account.etc ...",        resultClass = Pojo.class,        resultSetMapping = "Pojo.groupedInfo")@SqlResultSetMapping(        name = "Pojo.groupedInfo",        classes = {            @ConstructorResult(                targetClass = Pojo.class,                columns =  {                    @ColumnResult(name = "sum", type = BigDecimal.class),                    /*                     * Mappings for Account part of entity.                     */                }            )        }   ) public class Pojo implements Serializable {     private BigDecimal sum;     /* .... */     public Pojo(BigDecimal sum, ...) {}     /* .... */}

For sure this will work for you well, unless you will use the Account, fetched by this query in other entities. This will make Hibernate "mad" - the "entity", but not fetched by Hibernate...


Interesting, the described behaviour is as if t instances are returned from the actual query and t.account association in the first argument of Pojo constructor is actually navigated on t instances when marshalling results of the query (when creating Pojo instances from the result rows of the query). I am not sure if this is a bug or intended feature for constructor expressions.

But the following form of the query should work (no t.account navigation in the constructor expression, and no join fetch without the owner of the fetched association because it does not make sense to eagerly initialize something that is not actually returned from the query):

SELECT new com.test.Pojo(acc, SUM(t.value))FROM Transaction t JOIN t.account accGROUP BY acc

EDIT

Very good observation by Ilya Dyoshin about the group by clause; I completely oversaw it here. To stay in the HQL world, you could simply preload all accounts with transactions before executing the query with grouping:

SELECT acc FROM Account accWHERE acc.id in (SELECT t.account.id FROM Transaction t)