简体   繁体   中英

How to solve the N+1 problem in Spring Data JPA?

I am using Spring Data JPA as the persistence layer and I'm facing the N+1 problem. I'm also using the Specifications API because of which I'm finding difficult to solve N+1 problem. Please help.

@Entity
public class PopulationHealth {

    @Id
    private int caseId;

    @OneToMany(mappedBy = "caseId", fetch = FetchType.LAZY)
    private List<CostSaving> costSavings;
}
public class CostSaving {

    @Id
    private int caseId;
}
@Transactional(readOnly = true)
public interface PopulationHealthRepository extends JpaRepository<PopulationHealth, Integer>, JpaSpecificationExecutor<PopulationHealth> {

    Page<PopulationHealth> findAll(Specification<PopulationHealth> spec, Pageable pageable);
}

In the below class, the root.join() method, I added this later, this creates the left join in the query but, N+1 problem still happens. Please refer to below log output:

public class PopulationHealthSearchSpec implements Specification<PopulationHealth> {

    private List<PopulationHealthCriteriaDto> criteria;

    public PopulationHealthSearchSpec() {
        criteria = new ArrayList<>();
    }

    public void addCriteria(List<PopulationHealthCriteriaDto> criteria) {
        this.criteria.addAll(criteria);
    }

    @Override
    public Predicate toPredicate(Root<PopulationHealth> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        List<Predicate> predicates = new ArrayList<>();
        criteria.forEach(p -> {
            SearchOperation operation = p.getOperation();
            root.join("costSavings", JoinType.LEFT);
            switch (operation) {
                case GREATER_THAN:
                    predicates.add(cb.greaterThan(root.get(p.getKey().toString()), convertToDate(p.getValue())));
                    break;
                case GREATER_THAN_EQUAL:
                    predicates.add(cb.greaterThanOrEqualTo(root.get(p.getKey().toString()), convertToDate(p.getValue())));
                    break;
                case LESS_THAN:
                    predicates.add(cb.lessThan(root.get(p.getKey().toString()), convertToDate(p.getValue())));
                    break;
                case LESS_THAN_EQUAL:
                    predicates.add(cb.lessThanOrEqualTo(root.get(p.getKey().toString()), convertToDate(p.getValue())));
                    break;
                case EQUAL:
                    predicates.add(cb.equal(root.get(p.getKey().toString()), p.getValue()));
                    break;
                case NOT_EQUAL:
                    predicates.add(cb.notEqual(root.get(p.getKey().toString()), p.getValue()));
                    break;
                case IN:
                    predicates.add(getInPredicates(cb, root, p));
                    break;
                case NOT_IN:
                    predicates.add(getInPredicates(cb, root, p).not());
                    break;
            }
        });
        return cb.and(predicates.toArray(new Predicate[0]));
    }

Log Output with N+1 queries. The first query has a Left join But still there is N+1 problem.

2020-05-06 19:46:41,527 DEBUG org.hibernate.SQL : select population0_.CaseId as CaseId1_3_, population0_.CaseCreateDate as CaseCrea2_3_ from POPULATION_HEALTH_UNMASKED population0_ left outer join COST_SAVINGS costsaving1_ on population0_.CaseId=costsaving1_.CaseId where population0_.CaseId in (3098584 , 3098587 , 3098591) order by population0_.CaseCreateDate asc limit ?
2020-05-06 19:46:41,709 DEBUG org.hibernate.SQL : select costsaving0_.CaseId as CaseId1_1_0_, costsaving0_.CaseId as CaseId1_1_1_ from COST_SAVINGS costsaving0_ where costsaving0_.CaseId=?
2020-05-06 19:46:41,744 DEBUG org.hibernate.SQL : select costsaving0_.CaseId as CaseId1_1_0_, costsaving0_.CaseId as CaseId1_1_1_ from COST_SAVINGS costsaving0_ where costsaving0_.CaseId=?
2020-05-06 19:46:41,781 DEBUG org.hibernate.SQL : select costsaving0_.CaseId as CaseId1_1_0_, costsaving0_.CaseId as CaseId1_1_1_ from COST_SAVINGS costsaving0_ where costsaving0_.CaseId=?

After using the fetch() instead of a join() in the Specification class, I get below issue:

2020-05-06 20:29:25,315 ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias1,role=com.cambia.mmt.cdqs.api.entity.PopulationHealth.costSavings,tableName=COST_SAVINGS,tableAlias=costsaving1_,origin=POPULATION_HEALTH_UNMASKED population0_,columns={population0_.CaseId ,className=com.cambia.mmt.cdqs.api.entity.CostSaving}}] [select count(generatedAlias0) from com.cambia.mmt.cdqs.api.entity.PopulationHealth as generatedAlias0 left join fetch generatedAlias0.costSavings as generatedAlias1 where generatedAlias0.caseCreateDate>:param0]; nested exception is java.lang.IllegalArgumentException: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias1,role=com.cambia.mmt.cdqs.api.entity.PopulationHealth.costSavings,tableName=COST_SAVINGS,tableAlias=costsaving1_,origin=POPULATION_HEALTH_UNMASKED population0_,columns={population0_.CaseId ,className=com.cambia.mmt.cdqs.api.entity.CostSaving}}] [select count(generatedAlias0) from com.cambia.mmt.cdqs.api.entity.PopulationHealth as generatedAlias0 left join fetch generatedAlias0.costSavings as generatedAlias1 where generatedAlias0.caseCreateDate>:param0]] with root cause
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias1,role=com.cambia.mmt.cdqs.api.entity.PopulationHealth.costSavings,tableName=COST_SAVINGS,tableAlias=costsaving1_,origin=POPULATION_HEALTH_UNMASKED population0_,columns={population0_.CaseId ,className=com.cambia.mmt.cdqs.api.entity.CostSaving}}]

I don't think Spring-Data can do any better here since it tries to do a count query first in order to provide the total count information in the Page object. You could use Slice to avoid the count query.

If you want something more advanced you can take a look at the Blaze-Persistence integration with Spring-Data . It will use a different pagination mechanism that allows this to work and is also more efficient. Using Entity-Views will even give you an additional performance boost.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM