我们经常会碰上某个字段是集合元素(List,Set)的情况,并且我们要过滤出集合中包含某个或某些元素的数据。 譬如一个类User
/**
* 检索人
*/
private Long userId;
/**
* 省、直辖市集合
*/
@ElementCollection
@CollectionTable
private List<String> provinces;
/**
* 市、区集合
*/
@ElementCollection
@CollectionTable
private List<String> cities;
/**
* 行业集合
*/
@ElementCollection
@CollectionTable
private List<Integer> vocations;
包含一个String型的集合,我们希望能查询出province=”1”或者”2”的User集合。 倘若使用Hql或者原生sql是比较简单的,但是使用Criteria查询就不那么简单了,尤其是当User中包含多个集合元素,并且查询条件不确定时。 Jpa中Criteria用来构建复杂查询,之前我的文章中(http://blog.csdn.net/tianyaleixiaowu/article/details/72876732)已经讲过了如何构建动态条件查询,里面就有如何实现查询集合元素中是否包含某元素的功能。 重点看一下那篇文章中的SimpleExpression.java,里面的case IS_MEMBER,调用了CriteriaBuilder的isMember方法,该方法就能查询出你的集合中是否包含某个元素。 请注意,我定义User类时,注解写的是:@ElementCollection,映射的是基本类型不是一个javaBean类,所以无法使用表关联的写法如user.address.id=XXX,这样的hibernate表关联写法。 那么就需要使用isMember这样的写法(注意:需要导入上面提到的那篇文章的几个类,才能用下面的写法):
Criteria<PtSearchCondition> criteria = new Criteria<>();
criteria.add(Restrictions.hasMembers("provinces", "110000", "120000"));
Page page = userRepository.findAll(criteria, new PageRequest(0, 10));
return page.getContent();
有个地方需要说明一下,@ElementCollection这个注解代表该属性是一个集合属性,它和one-to-many类似,但不是同一个东西,one-to-many注解的另一方也要是一个表,不能只是一个普通的基本类型的集合。 如果你的@ElementCollection注解的集合对象也是一个JavaBean,不是String或者Integer时,譬如User有多个Address,Set《Address》 addressSet,那么Address类需要加@Embeddable注解,否则报错。@Embeddable代表是一个嵌入式的对象,不是一个表映射对象。如果你用的是one-to-many,那么Address就需要加上@Entity,代表需要映射到数据库表。 下面还看查询的问题: 如果你的属性是一个对象的集合,并且是@ElementCollection注解的,那么如何查询呢? 很简单,同样还是使用
Criteria<PtSearchCondition> criteria = new Criteria<>();
criteria.add(Restrictions.hasMembers("address.name", "北京"));
Page page = userRepository.findAll(criteria, new PageRequest(0, 10));
在我的SimpleExpression.java中,有这样一段代码来处理一对多的查询
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Predicate toPredicate(Root<?> root, CriteriaQuery<?> query,
CriteriaBuilder builder) {
Path expression;
//此处是表关联数据,注意仅限一层关联,如user.address,
//查询user的address集合中,address的name为某个值
if (fieldName.contains(".")) {
String[] names = StringUtils.split(fieldName, ".");
//获取该属性的类型,Set?List?Map?
expression = root.get(names[0]);
Class clazz = expression.getJavaType();
if (clazz.equals(Set.class)) {
SetJoin setJoin = root.joinSet(names[0]);
expression = setJoin.get(names[1]);
} else if (clazz.equals(List.class)) {
ListJoin listJoin = root.joinList(names[0]);
expression = listJoin.get(names[1]);
} else if (clazz.equals(Map.class)) {
MapJoin mapJoin = root.joinMap(names[0]);
expression = mapJoin.get(names[1]);
} else {
//是many to one时
expression = expression.get(names[1]);
}
} else {
//单表查询
expression = root.get(fieldName);
}
里面使用了SetJoin来完成对多的一方的某字段的匹配查询。 在Restrictions.java中,做了判断多的一方是基本类型还是JavaBean的判断:
/**
* 集合包含某几个元素,譬如可以查询User类中Set<String> set包含"ABC","bcd"的User集合,
* 或者查询User中Set<Address>的Address的name为"北京"的所有User集合
* 集合可以为基本类型或者JavaBean,可以是one to many或者是@ElementCollection
* @param fieldName
* 列名
* @param value
* 集合
* @return
* expresssion
*/
public static LogicalExpression hasMembers(String fieldName, Object... value) {
SimpleExpression[] ses = new SimpleExpression[value.length];
int i = 0;
//集合中对象是基本类型,如Set<Long>,List<String>
Criterion.Operator operator = Criterion.Operator.IS_MEMBER;
//集合中对象是JavaBean
if (fieldName.contains(".")) {
operator = Criterion.Operator.EQ;
}
for (Object obj : value) {
ses[i] = new SimpleExpression(fieldName, obj, operator);
i++;
}
return new LogicalExpression(ses, Criterion.Operator.OR);
}
同理,如果是使用one to many来映射的1对多表关系,同样可以使用上面的方法,写法也完全相同。
Criteria<PtSearchCondition> criteria = new Criteria<>();
criteria.add(Restrictions.hasMembers("address.name", "北京"));
criteria.add(Restrictions.hasMembers("provinces", "110000", "120000"));
Page page = userRepository.findAll(criteria, new PageRequest(0, 10));
以上就能完成Jpa中1对多,根据多的一方的某属性进行过滤匹配。