美文网首页
关于JPA 表达式封装用法(一)

关于JPA 表达式封装用法(一)

作者: PrimaryKeyEnoch | 来源:发表于2019-02-27 11:30 被阅读0次

1. 关于JPA争议

  这里不讨论JPA和mybatis的好处,智者见智仁者见仁吧. 个人比较喜欢JPA的操作. 
可能开发效率上更为方便吧.节省很多的时间

2. JPA简单的用法

   1. 用过JPA的同学,都知道JPA有简单方法,就是驼峰式方法命名规范,这是一种入门级的API.
   2. 第二种查询可以用@Query注解,进行查询,同样注解方式也有两种方式                       
        1> 原生HQL方式
        2> 采用原生SQL方式(这种对于动态参数的支持不是很好,有时间写一章关于原生sql的方法)
   3. 稍微高级的用法就是JPA投影.(稍后介绍)
   4. JPA高级用法就是Specification表达式.这篇文章专门花时间介绍下这个表达式 

3. 普通表达式用法

3.1 这里用一个简单例子介绍下表达式的普通用法

1. 定义一个实体类
     @Entity
     @Table(name = "t_article")
     @Data
     public class Article implements Serializable{
          private static final long serialVersionUID = 6112067846581696118L;
         @Id
         @GeneratedValue
         private Integer aid;
         private String title;
         private Date postTime;
         private Date lastEditTime;
         private String ip;
         private String tag;
         private boolean forbidComment;//禁止评论
         @ManyToOne
         @JoinColumn(name = "uid")
         private User user; 
    }
2. 定义用户表
      @Entity
      @Table(name = "t_user")
      @Data
      public class User implements Serializable {
 
           private static final long serialVersionUID = 3703405133265901053L;
           @Id
           @GeneratedValue
           private Integer uid;
           private String nickname;
           private String password;
    }
3. 如果定义一个controller接口, 进行查询用户和文章的关系,那么咱们定义一个实体类
     public class SearchArticle implements Serializable {
          private static final long serialVersionUID = -1082122462716689486L;
          private int page = 1;
          private int limit;
         @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME,pattern = "yyyy-MM-dd HH:mm:ss")
          private Date postTimeStart;
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME,pattern = "yyyy-MM-dd HH:mm:ss")
          private Date postTimeEnd;
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME,pattern = "yyyy-MM-dd HH:mm:ss")
          private Date recTimeStart;
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME,pattern = "yyyy-MM-dd HH:mm:ss")
          private Date recTimeEnd;
 
          private String nickname;
    }
4. 定义一个文章接口
    public interface ArticleRepository extends JpaRepository<Article,Integer>,JpaSpecificationExecutor<Article> {}

    这里不需要实现任何接口
5. 定义查询接口,实现类
          @Autowired
    private ArticleRepository articleRepository;
    
@Override
    public QueryResult<ArticleModel> findArticle(SearchArticle searchArticle) {
        Sort sort = new Sort(Sort.Direction.DESC,"postTime");
        Specification<Article> specification = getWhereClause(searchArticle);
        Page<Article> all = articleRepository.findAll(specification, new PageRequest(searchArticle.getPage() - 1, searchArticle.getLimit(),sort));
        QueryResult<ArticleModel> result = new QueryResult<>();
        List<ArticleModel> list = new ArrayList<>(searchArticle.getLimit());
        for (Article article:all.getContent()){
            ArticleModel model = new ArticleModel(article.getAid(),article.getTitle(),article.getPostTime(),article.isRecommend(),
                    article.getRecommendTime(),article.getIp(),article.getUser().getUid(),article.getUser().getNickname());
            list.add(model);
        }
        result.setRows(list);
        result.setTotal(all.getTotalElements());
        return result;
    }
 
    /**
     * 动态生成where语句
     * @param searchArticle
     * @return
     */
    private Specification<Article> getWhereClause(final SearchArticle searchArticle){
        return new Specification<Article>() {
            @Override
            public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> predicate = new ArrayList<>();
                if(searchArticle.getPostTimeStart()!=null){
                    predicate.add(cb.greaterThanOrEqualTo(root.get("postTime").as(Date.class), searchArticle.getPostTimeStart()));
                }
                if(searchArticle.getPostTimeEnd()!=null){
                    predicate.add(cb.lessThanOrEqualTo(root.get("postTime").as(Date.class), searchArticle.getPostTimeEnd()));
                }
                if(searchArticle.getRecTimeStart()!=null){
                    predicate.add(cb.greaterThanOrEqualTo(root.get("recommendTime").as(Date.class), searchArticle.getRecTimeStart()));
                }
                if (searchArticle.getRecTimeEnd()!=null){
                    predicate.add(cb.lessThanOrEqualTo(root.get("recommendTime").as(Date.class), searchArticle.getRecTimeEnd()));
                }
                if (StringUtils.isNotBlank(searchArticle.getNickname())){
                    //两张表关联查询
                    Join<Article,User> userJoin = root.join(root.getModel().getSingularAttribute("user",User.class),JoinType.LEFT);
                    predicate.add(cb.like(userJoin.get("nickname").as(String.class), "%" + searchArticle.getNickname() + "%"));
                }
                Predicate[] pre = new Predicate[predicate.size()];
                return query.where(predicate.toArray(pre)).getRestriction();
            }
        };
    }
这样的表达式就已经完成了.但是代码量实在是太大了.如果每次都来一次这样的查询,那么service将是很庞大的.
就算公共的方法抽出来,也不是一个很好地方式.

4. 由于每次都写这样的代码.太辛苦了.下面是花时间进行简单的封装

    1. 当前的封装,支持一个多表关联查询,并且只支持AND查询,同时支持分页,order by,group by等
    2. 而且当前封装和前端结合, 前端只需动态的传入json参数,后台进行动态解析成SQL,进行查询, 
       如果前端加了一个参数,后台是无须进行修改代码逻辑的.这一点还是很方便的. 

4.1 下面进行代码展示

1. 公共参数接受类
 public class QueryConditionVo {

    private List<ConditionField> conditions = Lists.newArrayList();
    private Pagination pagination = new Pagination();
    private Map<String, String> sort = Maps.newHashMap();

    private String params;

    public QueryConditionVo() {
        ConditionField stateCondition = new ConditionField();
        stateCondition.setName("isDeleted");
        stateCondition.setOperator("EQ");
        stateCondition.setValue("N");
        conditions.add(stateCondition);
    }

    public ConditionField getConditionField(String key) {
        return conditions.stream().filter(condition -> condition.getName().equals(key)).findFirst().orElse(null);
    }


    public String getParams() {
        return params;
    }

    public void setParams(String params) {
        this.params = params;
    }

    public List<ConditionField> getConditions() {
        return conditions;
    }

    public void setConditions(List<ConditionField> conditions) {
        this.conditions = conditions;
    }

    public Pagination getPagination() {
        return pagination;
    }

    public void setPagination(Pagination pagination) {
        this.pagination = pagination;
    }

    public Map<String, String> getSort() {
        return sort;
    }

    public void setSort(Map<String, String> sort) {
        this.sort = sort;
    }

    @Override
    public String toString() {
        return "QueryConditionVo{" +
                "conditions=" + conditions +
                ", pagination=" + pagination +
                ", sort=" + sort +
                '}';
    }
}
2. 分页类
@Data
public class Pagination {
    private int page = 0;
    private int size = 10;

}
3. 属性查询类
@Data
public class ConditionField {
    //属性
    private String name;
    //类似sql中的判断
    private String operator;
    //值
    private String value;

    public ConditionField() {
    }

    public ConditionField(String name, String operator, String value) {
        this.name = name;
        this.operator = operator;
        this.value = value;
    }

    public static ConditionField deleteConditionField() {
        ConditionField deleteCondition = new ConditionField();
        deleteCondition.setName("isDeleted");
        deleteCondition.setOperator("eq");
        deleteCondition.setValue("N");
        return deleteCondition;
    }
}
4. 查询SQL时定义的方法
public class CriteriaCondition {
    public enum Operator {
        EQ, NEQ, LIKE, GT, LT, GE, LE, NULL, IN
    }

    private String fieldName;
    private Object value;
    private Operator operator;


    public CriteriaCondition(String fieldName, Operator operator, Object value) {
        this.fieldName = fieldName;
        this.value = value;
        this.operator = operator;
    }

    public String getFieldName() {
        return fieldName;
    }

    public void setFieldName(String fieldName) {
        this.fieldName = fieldName;
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

    public Operator getOperator() {
        return operator;
    }

    public void setOperator(Operator operator) {
        this.operator = operator;
    }
}
5. 解析JSON为JPA表达式类
public class QueryHandler<T> {
    private static final Logger logger = LoggerFactory.getLogger(QueryHandler.class);

    private QueryConditionVo queryConditionVo;

    public QueryHandler(QueryConditionVo queryConditionVo) {

        this.queryConditionVo = queryConditionVo;
    }

    public Specification<T> buildSpecificationOr(final Class clazz) {
        return new Specification<T>() {

            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<Predicate>();
                Predicate predicate = null;
                In in = null;
                check(root, criteriaBuilder, predicates, predicate, in, clazz);
                if (CollectionUtils.isEmpty(predicates)) {
                    return predicate;
                }
                predicate = criteriaBuilder.or(predicates.toArray(new Predicate[predicates.size()]));
                return predicate;
            }
        };
    }

    public Specification<T> buildSpecification(final Class clazz, String... params) {
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<Predicate>();
                Predicate predicate = null;
                In in = null;
                check(root, criteriaBuilder, predicates, predicate, in, clazz);
                predicate = criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
                if (params != null && params.length > 0) {
                    for (String param : params) {
                        criteriaQuery.groupBy(root.get(param));
                    }

                }
                return predicate;
            }
        };
    }

    public Specification<T> buildSpecification(final Class clazz) {
        return buildSpecification(clazz, null);
    }

    private void check(Root<T> root, CriteriaBuilder criteriaBuilder, List<Predicate> predicates, Predicate predicate, In in, Class clazz) {
        List<CriteriaCondition> criteriaConditions = getCriteriaConditionList();
        for (CriteriaCondition filter : criteriaConditions) {
            Object value = filter.getValue();
            String fieldName = filter.getFieldName();
            Path path = null;
            if (fieldName.contains(".")) {
                String[] fields = fieldName.split("\\.");
                if (fields.length == 2) {
                    path = root.join(fields[0]).get(fields[1]);
                }
                if (fields.length == 3) {
                    path = root.join(fields[0]).join(fields[1]).get(fields[2]);
                }
                if (fields.length == 4) {
                    path = root.join(fields[0]).join(fields[1]).join(fields[2]).get(fields[3]);
                }

            } else {
                path = root.get(fieldName);
                Field field = null;
                try {
                    field = clazz.getDeclaredField(fieldName);
                } catch (NoSuchFieldException e) {
                    //不存在的field,尝试从父类获取
                    try {
                        field = clazz.getSuperclass().getDeclaredField(fieldName);
                    } catch (Exception e1) {
                        continue;
                    }
                }
                if (field.getType().isEnum()) {
                    value = Enum.valueOf((Class<Enum>) field.getType(), value.toString());
                }

                if (field.getType().isInstance(new Timestamp(System.currentTimeMillis()))) {
                    if (!"null".equals(value)) {
                        value = Timestamp.valueOf(value.toString());
                    }

                }
            }

            switch (filter.getOperator()) {
                case GE:
                    predicate = criteriaBuilder.greaterThanOrEqualTo(path, (Comparable) value);
                    break;
                case GT:
                    predicate = criteriaBuilder.greaterThan(path, (Comparable) value);
                    break;
                case LT:
                    predicate = criteriaBuilder.lessThan(path, (Comparable) value);
                    break;
                case LE:
                    predicate = criteriaBuilder.lessThanOrEqualTo(path, (Comparable) value);
                    break;
                case NEQ:
                    predicate = criteriaBuilder.notEqual(path, value);
                    break;
                case EQ: {
                    predicate = criteriaBuilder.equal(path, value);
                    break;
                }
                case NULL:
                    predicate = criteriaBuilder.isNull(path);
                    break;
                case LIKE:
                    predicate = criteriaBuilder.like(path, "%" + value + "%");
                    break;
                case IN:
                    in = criteriaBuilder.in(path);
                    if (value != null) {
                        String inStr = value.toString();
                        String[] vals = inStr.split(",");
                        for (String val : vals) {
                            try {
                                Long _v = Long.parseLong(val);
                                in.value(_v);
                            } catch (Exception e) {
                                in.value(val);
                            }
                        }
                    }
                    break;
                default:
                    break;
            }
            if (predicate != null) {
                predicates.add(predicate);
            }
            if (in != null) {
                predicates.add(in);
            }
        }
    }


    /**
     * 生成分页、排序条件
     *
     * @return
     */
    public Pageable buildPage() {
        Pagination pagination = queryConditionVo.getPagination();
        Map<String, String> sort = queryConditionVo.getSort();
        if (pagination == null) {
            return null;
        }
        if (queryConditionVo.getSort() == null) {
            return PageRequest.of(pagination.getPage(), pagination.getSize());
        } else {
            Sort sortObject = null;
            for (Map.Entry<String, String> entry : sort.entrySet()) {
                Sort tempSort = parseSortCondition(entry.getKey(), entry.getValue());
                if (tempSort == null) {
                    continue;
                }
                sortObject = (sortObject == null) ? tempSort : (sortObject.and(tempSort));
            }
            if (sortObject != null) {
                return PageRequest.of(pagination.getPage(), Math.min(50, pagination.getSize()), sortObject);

            } else {
                return PageRequest.of(pagination.getPage(), Math.min(50, pagination.getSize()));

            }

        }
    }

    /**
     * 生成查询条件
     *
     * @return
     */
    public List<CriteriaCondition> getCriteriaConditionList() {
        List<ConditionField> conditions = queryConditionVo.getConditions();
        if (conditions == null) {
            return null;
        }
        List<CriteriaCondition> criteriaConditions = new ArrayList<CriteriaCondition>();
        for (ConditionField field : conditions) {
            CriteriaCondition filter = parseSearchFilter(field);
            if (filter == null) {
                continue;
            }
            criteriaConditions.add(filter);
        }
        return criteriaConditions;
    }

    /**
     * 解析查询条件
     *
     * @param field
     * @return
     */
    private CriteriaCondition parseSearchFilter(ConditionField field) {
        CriteriaCondition.Operator operator = null;
        if (StringUtils.equalsIgnoreCase(field.getOperator(), "like")) {
            operator = CriteriaCondition.Operator.LIKE;
        } else if (StringUtils.equalsIgnoreCase(field.getOperator(), "le")) {
            operator = CriteriaCondition.Operator.LE;
        } else if (StringUtils.equalsIgnoreCase(field.getOperator(), "lt")) {
            operator = CriteriaCondition.Operator.LT;
        } else if (StringUtils.equalsIgnoreCase(field.getOperator(), "eq")) {
            operator = CriteriaCondition.Operator.EQ;
        } else if (StringUtils.equalsIgnoreCase(field.getOperator(), "neq")) {
            operator = CriteriaCondition.Operator.NEQ;
        } else if (StringUtils.equalsIgnoreCase(field.getOperator(), "gt")) {
            operator = CriteriaCondition.Operator.GT;
        } else if (StringUtils.equalsIgnoreCase(field.getOperator(), "ge")) {
            operator = CriteriaCondition.Operator.GE;
        } else if (StringUtils.equalsIgnoreCase(field.getOperator(), "null")) {
            operator = CriteriaCondition.Operator.NULL;
        } else if (StringUtils.equalsIgnoreCase(field.getOperator(), "in")) {
            operator = CriteriaCondition.Operator.IN;
        }
        if (operator != null) {
            return new CriteriaCondition(field.getName(), operator, field.getValue());
        }
        return null;
    }

    /**
     * 生成排序条件
     *
     * @param key
     * @param value
     * @return
     */
    private Sort parseSortCondition(String key, String value) {
        if (StringUtils.equalsIgnoreCase(value, "asc")) {
            return new Sort(Sort.Direction.ASC, key);
        } else if (StringUtils.equalsIgnoreCase(value, "desc")) {
            return new Sort(Sort.Direction.DESC, key);
        }
        return null;
    }
}

以上5个大类就是我封装的类, 前端传入JSON参数,后台动态解析SQL,进行实体查询,下面给出例子

1.  controller层
    @PostMapping("/list")
    public ResponseEntity<String> list(@RequestBody String body) {
        logger.info("模板分页查询:{}", body);
        QueryConditionVo queryConditionVo = JSONObject.parseObject(body, QueryConditionVo.class);
        return response(templateService.list(queryConditionVo));
    }
    这里的response是我封装在父类的一个函数
2. service层
   public Page<Template> list(QueryConditionVo queryConditionVo) {
        QueryHandler<Template> templateQueryHandler = new QueryHandler<>(queryConditionVo);
      return templateRepository.findAll(templateQueryHandler.buildSpecification(Template.class), templateQueryHandler.buildPage());
    }
3. 前端参数传输
    {
            "conditions": [{
                "name": "isDeleted",
                "operator": "EQ",
                "value": "N"
            }, {
                "name": "labels.labelName",
                "operator": "EQ",
                "value": "标签1"
            },{
                "name": "templateState",
                "operator": "EQ",
                "value": "SPECIAL/BASE"
            },{
                "name": "templateName",
                "operator": "LIKE",
                "value": "模板名称"
            }],
            "pagination": {
                "page": 0,
                "size": 10
            },
            "sort": {
                "gmtCreate": "desc",
                "pageView": "desc"
            }
        }
 conditions:节点就是查询参数,当前JSON节点意思就是select * from template left join label on tempate.id= label.template_id where isDelete='N' and label_name = '标签一' and tempalteState ='BASE' and templateName like '%名称%'.
 pagination: 分页参数,JPA默认从0开始
 sort: 排序

5. JPA表达式总结

 1.   原生表达式写法,直观,但是代码量比较多,而且如果前端查询参数扩展的话,后台也是需要跟着改变.有点麻烦
 
 2.  当前的封装,简化了form表单的查询,同时支持分页,动态参数,排序,分组.调用起来也是几行代码的事情.

说完优点,就是缺点了. 缺点就是当前的封装只是支持全部AND查询,全部OR查询,并不是很友好的支持and,or同时共用, 不过大家不用担心. 我已经做出了改造. 下一篇文章会

  JPA原生SQL也会专门写一篇文章进行简述
    大家在用JPA的时候会怎么玩呢..欢迎交流哦

相关文章

  • 关于JPA 表达式封装用法(二)

    上篇文章中已经介绍了JPA表达式的用法,以及form表单的查询. 下面这篇文章将继续讲述JPA表达式的封装用法. ...

  • 关于JPA 表达式封装用法(一)

    1. 关于JPA争议 2. JPA简单的用法 3. 普通表达式用法 3.1 这里用一个简单例子介绍下表达式的普通用...

  • 第6章 数据持久化 Spring Data JPA

    6.1、本节目标: JPA 简介 Spring Data JPA 用法介绍 Spring Data JPA、Hib...

  • JPA findBy的语法

    JPA findBy的语法JPA findBy的语法 spring boot jpa 的高级用法,以及swagge...

  • SpringBoot中使用JPA

    Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套 JPA 应用...

  • Springboot整合jpa

    什么是Spring Data Jpa? 它是Spring基于ORM框架、JPA规范封装的一套JPA应用框架,可以使...

  • Spring Data JPA入门

    [TOC] SpringData JPA是spring基于ORM框架、JPA规范的基础上封装的一套JPA应用框架,...

  • Swift基础知识

    autoclosure @autoclosure 做的事情就是把一句表达式自动地封装成一个闭包。用法就是在类型签名...

  • JPA(二)Spring Data JPA

    一 概述 Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JP...

  • 【SpringBoot】分页查询

    SpringBoot Jpa封装了分页查询 Pageable是 Spring 封装的分页实现类,使用的时候需要传入...

网友评论

      本文标题:关于JPA 表达式封装用法(一)

      本文链接:https://www.haomeiwen.com/subject/dasxvxtx.html