My code is currently working using the query below and I am converting the query to JPA Specification.
@Query("SELECT DISTINCT h, SUM(m.annualIncome) " + "FROM Household h LEFT JOIN h.familyMemberList m " + "GROUP BY h.id " + "HAVING SUM(m.annualIncome) < 100000 " + "AND (:householdSize IS NULL OR COUNT(m) = :householdSize) " + "AND (:householdIncome IS NULL OR SUM(m.annualIncome) = :householdIncome)") List<Household> findGrantEligibleHouseholds(@Param("householdSize") long householdSize, @Param("householdIncome") long householdIncome);
This is what I have done so far which is working but in an unclean manner.
public static Specification<Household> grantEligibleHouseholdsSpecification(HouseholdCriteria criteria) { return Specification.where( (root, query, builder) -> { List<Predicate> searchCriteria = new ArrayList<>(); final Join<Household, FamilyMember> householdFamilyMemberJoin = root.join(Household_.familyMemberList, JoinType.LEFT); if(criteria.getHousingType() != null) { searchCriteria.add(builder.equal(root.get(Household_.housingType), criteria.getHousingType())); } query.groupBy(root.get(Household_.id)); if(criteria.getHouseholdIncome() != null && criteria.getHouseholdSize() != null) { query.having(builder.lt(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),100000) ,builder.equal(builder.count(householdFamilyMemberJoin),criteria.getHouseholdSize()) ,builder.equal(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),criteria.getHouseholdIncome())); } else if(criteria.getHouseholdIncome() != null) { query.having(builder.lt(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),100000) ,builder.equal(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),criteria.getHouseholdIncome() )); } else if(criteria.getHouseholdSize() != null) { query.having(builder.lt(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),100000) ,builder.equal(builder.count(householdFamilyMemberJoin),criteria.getHouseholdSize())); } else { query.having(builder.lt(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),100000)); } query.multiselect(); return builder.and(searchCriteria.toArray(new Predicate[searchCriteria.size()])); } ); }
How do I improve this code so in the future it can accept more criteria without going through so many null checks like this? Thanks!
Advertisement
Answer
You can use same approach as for searchCriteria
– collect multiple predicates into list:
final List<Predicate> havingPredicates = new ArrayList<>(); // default predicates havingPredicates.add(builder.lt( builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)), 100000)); // custom predicates if (criteria.getHouseholdIncome() != null) { havingPredicates.add(builder.equal( builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)), criteria.getHouseholdIncome())); } if (criteria.getHouseholdSize() != null) { havingPredicates.add(builder.equal( builder.count(householdFamilyMemberJoin), criteria.getHouseholdSize())); } query.having(havingPredicates.toArray(new Predicate[0]));