利用java反射动态拼接JPA查询条件
2020-05-09 本文已影响0人
ivms8200
起因
假设有个需求是需要按照条件查询数据库的数据。一般来说我们可以根据指定的参数去创建JPA查询条件。就像这样:
if (StringUtils.isNotBlank(pageCgRecordInfoValidator.getCgmc())) {
list.add(cb.like(root.get("cgmc"), "%" + pageCgRecordInfoValidator.getCgmc() + "%"));
}
如果有多个参数,那么就要写很多行这样的代码。这样实在是太麻烦了。能不能写一个方法,让他去动态拼接查询参数?这时候就可以用到反射了。
实现
首先我们约定,把参数放到一个对象里面,这个对象可以是DTO BO DO等。然后我们通过反射去解析这个对象,如果该对象的某个属性有值,我们就在List<Predicate> list
中添加一条查询条件。
代码如下
package com.*****.utils;
import org.apache.commons.lang3.StringUtils;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public class JpaUtils {
/**
* 获取List<Predicate>,暂时只支持String,Integer,Long,Double,Short,Date
* 只适用于全包装类属性的对象
* 该方法暂时不适用于数字或者日期之间查询,如需查询要单独将条件加入predicateList
*
* @param o
* @param root
* @param cb
* @return
*/
public static List<Predicate> getPredicateList(Object o, Root root, CriteriaBuilder cb) {
//集合 用于封装查询条件
List<Predicate> list = new ArrayList<>();
// 得到类对象
Class objClass = (Class) o.getClass();
/* 得到类中的所有属性集合 */
Field[] fs = objClass.getDeclaredFields();
for (int i = 0; i < fs.length; i++) {
Field f = fs[i];
f.setAccessible(true); // 设置些属性是可以访问的
Object value;
try {
value = f.get(o);
// 得到此属性的键
String key = f.getName();
//得到此属性的类型
String type = f.getType().toString();
String substring = type.substring(type.lastIndexOf(".") + 1);
switch (substring) {
case "String":
String trValue = (String) value;
if (StringUtils.isNotBlank(trValue)) {
list.add(cb.like(root.get(key), "%" + trValue + "%"));
}
break;
case "Date":
case "Integer":
case "Long":
case "Double":
case "Short":
if (value != null) {
list.add(cb.equal(root.get(key), value));
}
break;
default:
System.out.println("不适用");
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return list;
}
}
因为项目中还没遇到其他的类型,所以暂时只写了对以上几种类型的处理。
使用方法如下,是不是方便很多了?
/**
* 根据参数查询一条数据
*/
@Override
public List<CgRecordInfo> findByParam(CgRecordInfoDTO cgRecordInfoDTO) {
//构造自定义查询条件
// Root 用于获取属性字段,CriteriaQuery可以用于简单条件查询,CriteriaBuilder 用于构造复杂条件查询
Specification<CgRecordInfo> specification = (root, query, cb) -> {
List<Predicate> predicateList = JpaUtils.getPredicateList(cgRecordInfoDTO, root, cb);
return cb.and(predicateList.toArray(new Predicate[0]));
};
return cgRecordInfoRepository.findAll(specification);
}
测试
经测试,该方式几乎不会有性能损失。
注意
实体最好使用包装类,这样便于判断是否有传入值作为查询条件。
该方法暂时不适用于数字或者日期之间查询。
如果有特殊需求,可以在获取List<Predicate> predicateList
后手动添加查询条件。比如我要查询某个时间范围内的数据
if(pageCgRecordInfoValidator.getUpdateTimeStart() != null && pageCgRecordInfoValidator.getUpdateTimeEnd() != null){
predicateList.add(cb.between(root.get("updateTime"), pageCgRecordInfoValidator.getUpdateTimeStart(), pageCgRecordInfoValidator.getUpdateTimeEnd()));
}