我正在使用:
" refcodemailing" column被定义为int:int []
的数组我的实体对象:
@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<>();
// ....
}
我正在尝试使用以下JPA规范方法过滤列数组:
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[]{}))));
}
}
但抛出以下异常:
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)
有办法吗?
答案 0 :(得分:1)
我尝试了各种选择,但对我不起作用。终于明白了,如果第二个参数是内置函数的数组,就是扩展变量,转换为myVarArgMethod。所以我所做的是,我编写了自己的自定义数据库函数,如下所示。
如果 searchKey 是单个值,我们可以使用 arrayContains,如果 searchKey 包含多个值,我们可以使用 java util 函数将列表转换为 postrgress 数组格式字符串,在 postgres 函数中,我们可以通过类型转换将其转换为数组。
用于将列表转换为 postgress 数组字符串的 Java util 方法,反之亦然
public static String convertToPGArray(List<String> content){
StringBuilder str = new StringBuilder();
if(content != null){
str.append("{");
int counter = 0;
for(String text : content){
if(counter != 0){
str.append(",");
counter++;
}else{
counter++;
}
str.append("\"").append(text).append("\"");
}
str.append("}");
}else{
str.append("{}");
}
return str.toString();
}
public static List<String> convertToList(String content){
List<String> returnList = new ArrayList<>();
if(!(content == null || content.equals("{}") || content.trim().equals(""))){
String tempContent = content;
String[] tokens = tempContent.replace("{", "").replace("}", "").split(",");
returnList = Arrays.stream(tokens).collect(Collectors.toList());
}
return returnList;
}
arrayContains 和 arrayContainsAny 的自定义 postgres 函数
CREATE OR REPLACE FUNCTION arrayContains(arrayContent text[], searchKey text) RETURNS BOOLEAN as
'
DECLARE arrContent text[];
countVal integer :=0;
BEGIN
arrContent = $1::text[];
countVal = (SELECT count(array_position(arrContent, searchKey)));
IF countVal = 0 THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
EXCEPTION WHEN others THEN
RETURN FALSE;
END;'
LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION arrayContainsAny(arrayContent text[], searchKeys text) RETURNS BOOLEAN as
'
DECLARE arrContent text[];
DECLARE serKeys text[];
countVal integer :=0;
result boolean;
searchkey text;
BEGIN
arrContent = $1::text[];
serKeys = $2::text[];
IF (count(cardinality(arrContent)) = 0 OR count(cardinality(serKeys)) = 0 OR cardinality(arrContent) = 0 OR cardinality(serKeys) = 0) THEN
RAISE NOTICE $quote$array is null$quote$;
RETURN TRUE;
END IF;
RAISE NOTICE $quote$after if condition$quote$;
FOREACH searchkey IN ARRAY serKeys
LOOP
result = arrayContains(arrContent, searchkey);
IF result = true THEN
RETURN TRUE;
END IF;
END LOOP;
RETURN FALSE;
EXCEPTION WHEN others THEN
RAISE NOTICE $quote$exception$quote$;
RETURN FALSE;
END;'
LANGUAGE plpgsql;
我们可以在 QueryBuilder 或 @Query 注释中调用上面的函数,如下所示
如果 QueryBuilder 示例输出如下
Specification<T> siteReqSpec1 = new Specification<T>() {
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// TODO Auto-generated method stub
logger.info("getRBACResourceTagSpec entityName {} value {}", root.getModel().getName(), root.get(colName).getJavaType());
//return cb.isNotNull(root.get(colName));
//return cb.isMember(roleTagName, root.get(colName));
return cb.or(cb.isNull(root.get(colName)),
cb.isTrue(cb.function("arrayContains", Boolean.class, root.get(colName), cb.literal(roleTagName))));
}
};
如果@Query注解如下
@Query("from DeviceworkFlowLite wf where wf.orgName = :organization and arrayContainsAny(rbac_resource_tags, :rbacResourceTags) = true")
Page<DeviceworkFlowLite> findAllByOrgNameAndRBACResourceTagsIn(@Param("organization")String organization, @Param("rbacResourceTags")String rbacResourceTags, Pageable pageable);
jdbc SQL 语句的情况
private static final String GET_ALL_TEMPLATE_FILTER_BY_ORG =
"select name,rbac_resource_tags from template_metadata "
+ " and arrayContainsAny(rbac_resource_tags, ?) = true ";
ps.setString(4, jsonArrayRBACRoleResourceTag);
答案 1 :(得分:1)
i v s narayana 带我到了那里。我为我的用例简化了他们的答案,只是使用了内置的 sql 函数。
要检查 valueName 是否是存储在 columnName 中的 sql 数组的成员:
cb.isNotNull(cb.function("array_position", Integer.class, root.get(columnName), cb.literal(valueName))),
答案 2 :(得分:0)
根据JPA 2.0规范:
不支持评估为可嵌入类型的表达式 集合成员表达式。支持使用嵌入式软件 集合成员表达式可能会在以后的版本中添加 说明书
但是,我使用Hibernate构建了a working example on GitHub。
假设我们有CalendarEvent
个实体和MailingCode
DTO对象:
@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;
}
}
您可以按如下方式编写Criteria API代码:
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();
它有效:
SELECT criteriaap0_.id AS id1_0_
FROM CalendarEvent criteriaap0_
WHERE 1 = 1
AND ( 1 IN ( SELECT mailingcod1_.mailingCodes
FROM CalendarEvent_mailingCodes mailingcod1_
WHERE criteriaap0_.id = mailingcod1_.CalendarEvent_id ) )
AND ( 2 IN ( SELECT mailingcod2_.mailingCodes
FROM CalendarEvent_mailingCodes mailingcod2_
WHERE criteriaap0_.id = mailingcod2_.CalendarEvent_id ) )
AND ( 3 IN ( SELECT mailingcod3_.mailingCodes
FROM CalendarEvent_mailingCodes mailingcod3_
WHERE criteriaap0_.id = mailingcod3_.CalendarEvent_id ) )
但是,IN查询是一个更好的选择,因为上面的SQL查询不是最理想的。