IllegalArgumentException возникает, когда путь создается в поле с @Convert

Я пытаюсь создать запрос критериев и для каждого входящего фильтра предикат и связанный, например.

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);
Root<T> entityRoot = criteriaQuery.from(SampleEntity.class);
// for each filterable field create a Predicate and included in as `criteriaQuery.where(...)`
criteriaQuery.select(criteriaBuilder.count(entityRoot));
entityManager.createQuery(criteriaQuery).setMaxResults(maxResult).getSingleResult();

Для каждого такого фильтра (см. строку с комментариями в примере кода выше) сначала создается путь.

Path<?> path = PathBuilder.buildFromFieldName(fieldName, rootEntity);

Это будет метод:

public static <T> Path<?> buildFromFieldName(String fieldName, Root<T> entity) throws IllegalArgumentException {
    Path<?> path = entity;
    List<String> fieldNames = Arrays.asList(fieldName.split("\\."));
    for (String fieldNamePath : fieldNames) {
        path = path.get(fieldNamePath);
    }
    return path;
}

Вложенные поля появятся, например. с именем myChild.mySet в следующем примере.

@Entity
@Table(name = "T_SAMPLE")
public class SampleEntity {

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

    @Embedded
    private SampleChildEntity myChild;
}

@Embeddable
public class SampleChildEntity {

    @Convert(converter = CommaSeparatedSetConverter.class, attributeName = "mySet")
    private Set<String> mySet = new HashSet<>();

}

Учитывая этот пример, при вызове criteriaBuilder.isMember(filterValue, path) возникает следующее исключение (filterValue будет значением для сравнения, например, строкой поиска).

Причина: В методе buildFromFieldName сначала создается путь из корневого объекта (SampleEntity). Тогда он имеет тип org.hibernate.query.criteria.internal.path.RootImpl. Когда в цикле for обрабатывается fieldNamePath myChild, Path воссоздается. Затем он имеет тип org.hibernate.query.criteria.internal.path.SingularAttributePath и остается таким, когда обрабатывается последний fieldNamePath mySet. Только когда @Convert удаляется, тип изменяется на org.hibernate.query.criteria.internal.path.PluralAttributePath, поскольку это тип коллекции.

java.lang.IllegalArgumentException: unknown collection expression type 
        [org.hibernate.query.criteria.internal.path.SingularAttributePath]

    at org.hibernate.query.criteria.internal.CriteriaBuilderImpl.isMember(CriteriaBuilderImpl.java:1324)
    [... stack trace contains further local classes ...]

Итак, причина в @Convert в поле mySet в файле SampleChildEntity. Hibernate видит тип Set<String>, но использует не PluralAttributePath, а SingularAttributePath, что вызывает проблему. Без @Convert работает, но снимать преобразователь не вариант.

Есть ли способ, которым это может работать? Может ли путь быть создан по-другому?


person Kavau    schedule 13.11.2020    source источник


Ответы (2)


Постоянный атрибут, который использует преобразователь, например. @Convert — это базовый атрибут и, следовательно, SingularAttribute, поэтому абсолютно правильно, что вы видите SingularAttributePath. Просто неправильно предполагать, что вы можете использовать предикат во множественном числе для такого атрибута в единственном числе. Hibernate не может перевести это в SQL, так как не знает, что делает ваш преобразователь. Я предполагаю, что столбец представляет собой список, разделенный запятыми? В этом случае вы будете использовать, например. аналогичный предикат для написания фильтра. Что-то вроде этого:

WHERE alias.myChild.mySet = :filterValue 
   OR alias.myChild.mySet LIKE CONCAT(:filterValue, ';%') 
   OR alias.myChild.mySet LIKE CONCAT(CONCAT('%;', :filterValue), ';%') 
   OR alias.myChild.mySet LIKE CONCAT('%;', :filterValue)

Перевод этого на критерии JPA должен быть легким.

person Christian Beikov    schedule 16.11.2020
comment
Да, Hibernate работает корректно. И да, поле будет содержать список, разделенный запятыми. Ваше решение должно работать. Я опубликовал еще одну возможность, используя @Formula - person Kavau; 19.11.2020

Решением/обходным путем было бы использование @Formula во втором поле, которое ссылается - как String - на поле, разделенное запятыми. Затем запрос может быть сделан для этого поля String.

@Embeddable
public class SampleChildEntity {

    // no getter/setter
    @Formula(value = "MY_SET")
    private String mySetCommaSeparated;

    @Column(name = "MY_SET")
    @Convert(converter = CommaSeparatedSetConverter.class, attributeName = "mySet")
    private Set<String> mySet = new HashSet<>();

}
person Kavau    schedule 19.11.2020