利用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()));
}
上一篇下一篇

猜你喜欢

热点阅读