Skip to content
Advertisement

Unable to insert null checks when using HAVING clause while building Spring Data JPA Specifications

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]));
User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement