How to filter a PostgreSQL array column with the JPA Criteria API?

hibernate, java, jpa, postgresql, spring

Solution

According to JPA 2.0 specs:

Expressions that evaluate to embeddable types are not supported in collection member expressions. Support for use of embeddables in collection member expressions may be added in a future release of this specification.

However, I built a working example on GitHub using Hibernate.

Assuming we have this `CalendarEvent` entity and the `MailingCode` DTO object:

@Entity(name = "CalendarEvent")
@Table
public static class CalendarEvent implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @ElementCollection
    private final List<Integer> mailingCodes = new ArrayList<>();

}

public static class MailingCode {
    private Integer id;

    public MailingCode(Integer id) {
        this.id = id;
    }

    public Integer getId() {
        return id;
    }
}

You can write the Criteria API code as follows:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<CalendarEvent> criteria = builder.createQuery(CalendarEvent.class);
Root<CalendarEvent> root = criteria.from(CalendarEvent.class);

List<MailingCode> mailingCodes = Arrays.asList(
    new MailingCode(1),
    new MailingCode(2),
    new MailingCode(3)
);

Expression<List<Integer>> mailingCodesPath = root.get("mailingCodes");

Predicate predicate = builder.conjunction();

for(MailingCode mailingCode: mailingCodes){
    predicate = builder.and(predicate, builder.isMember(mailingCode.getId(), mailingCodesPath));
}

criteria.where(predicate);
List<CalendarEvent> events = entityManager.createQuery(criteria).getResultList();

However, an IN query is a much better choice since the SQL query above is suboptimal.

Problem

I am using: - Hibernate 4.3.5 - Spring JPA 1.6.0 - Javax Persistence API 2.1 The "refcodemailing" column is defined as an array of int: int[] My entity object: ``` @Entity @Table public class CalendarEvent implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id = 0; @Convert(converter = IntegerArrayConverter.class) @Column(name = "refcodemailing") private final List<Integer> mailingCodes = new ArrayList<>(); // .... } ``` I am trying to filter the column array with the following JPA Specification method: ``` private final List<MailingCode> mailingCodes = new ArrayList<>(); @Override public Predicate toPredicate(Root<CalendarEvent> root, CriteriaQuery<?> query, CriteriaBuilder cb) { // Mailing codes if(!mailingCodes.isEmpty()){ List<Predicate> mailingCodePred = new ArrayList<>(); for(MailingCode mailingCode: mailingCodes){ restrictions.add(cb.isMember(mailingCode.getId(), root.<List<Integer>>get("mailingCodes"))); } restrictions.add(cb.and(cb.isNotNull(root.<List<Integer>>get("mailingCodes")), cb.or(mailingCodePred.toArray(new Predicate[]{})))); } } ``` But the following exception is thrown: ``` java.lang.IllegalArgumentException: unknown collection expression type [org.hibernate.jpa.criteria.path.SingularAttributePath] at org.hibernate.jpa.criteria.CriteriaBuilderImpl.isMember(CriteriaBuilderImpl.java:1332) at com.agenda.CalendarEventQuery.toPredicate(CalendarEventQuery.java:100) at org.springframework.data.jpa.repository.support.SimpleJpaRepository.applySpecificationToCriteria(SimpleJpaRepository.java:521) at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:472) ``` Is there a way to do it?

Original source