Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Data JPA Select Distinct

I have a situation where I need to build a select distinct a.address from Person a (where address is a Address entity inside the Person) kind of query.

I am using Specification to dynamically build my where clause and using the findAll(Specification<T>) function to get the result. The problem is I can not use specification to build my select clause and hence can not use findAll(Spcification) function.

What would be the best way to do something like this?

like image 313
Nirav Shah Avatar asked Oct 17 '12 21:10

Nirav Shah


2 Answers

I came across the same issue, so in case it would help someone, this is what I did:

The Specification is being translated to the where clause, and the findAll(Specification<T>) function is creating its own select clause. So there is no way we can fix this by somehow using the findAll(Specification<T>) function. I already had custom repository which extends SimpleJpaRepository, so I've added a new method:

@Override
    @Transactional(readOnly = true)
    public List<Object> findDistinctValues(Specifications<T> spec, String columnName) {
        return getQuery(spec, columnName).getResultList();
    }

    protected TypedQuery<Object> getQuery(Specification<T> spec, final String distinctColumnName) {

        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Object> query = builder.createQuery(Object.class);
        Root<T> root = applySpecificationToCriteria(spec, query);

        if (null != distinctColumnName) {
            query.distinct(true);
            query.multiselect(root.get(distinctColumnName));
        }

        // We order by the distinct column, Asc
        query.orderBy(builder.asc(root.get(distinctColumnName)));

        return em.createQuery(query);
    }

applySpecificationToCriteria is in the SimpleJpaRepository class.

Now you can use the findDistinctValues method.

like image 179
yishaiz Avatar answered Nov 20 '22 13:11

yishaiz


As it's the top question in google, I'll post the answer here.

In the specification you have access to query, so you can do

query.distinct(true);

Full example, which results in such SQL emitted:

2015-04-27 12:03:39 EEST [7766-759] postgres@sales LOG: execute : SELECT DISTINCT t1.ID, t1.NAME, t1.WEBNAME, t1.WEBORDER, t1.PVGROUPPARENT_ID, t1.SITE_ID FROM PRODUCTVARIANT t0, PVGROUP t1 WHERE ((t0.PRODUCTTYPE_ID = $1) AND (t0.PVGROUP_ID = t1.ID)) 2015-04-27 12:03:39 EEST [7766-760] postgres@sales DETAIL: parameters: $1 = '4608bdc9-d0f2-4230-82fd-b0f776dc2cfd'

public static Specification<PVGroup> byProductType(final ProductType pt) {
        return (final Root<PVGroup> root, final CriteriaQuery<?> query, final CriteriaBuilder builder) -> {

            query.distinct(true);
            final CollectionJoin<PVGroup, ProductVariant> jPV = root.join(PVGroup_.productVariant);

            final Path<ProductType> ptPath = jPV.get(ProductVariant_.productType);

            return builder.equal(ptPath, pt);
        };
    }
}
like image 10
Maxym Avatar answered Nov 20 '22 14:11

Maxym