目录
- 一、参考
- 二、概述
- 流程
- 效果感受
- 三、实现原理
- AST树结构
- AstNode主要结构
- 把表达式解析成Ast
- Ast 解析成 JPA Specification
- 三、项目地址
- 四、目前阶段的总结
一、参考
如何搞一个支持自定义函数和变量的四则运算的抽象语法树出来
二、概述
这是一个可以优化生成Specification对象的轮子,目前处于一个不太靠近中间的产品。
流程
效果感受
这是随便测试的一个例子,与下面讲解的例子不一样。
Specification正常的写法:
public Page<Image> pagingImage(String title, Integer start, Integer size) {
Pageable pageable = PageRequest.of(start, size, Sort.by(Sort.Direction.DESC , "id"));
Specification<Image> specification = (root, query, criteriaBuilder) -> {
List<Predicate> and = new ArrayList<>();
and.add(criteriaBuilder.like(root.get("title").as(String.class), String.format("%%%s%%", title)));
and.add(criteriaBuilder.notLike(root.get("file").as(String.class), "%http://%"));
and.add(criteriaBuilder.notLike(root.get("file").as(String.class), "%https://%"));
List<Predicate> and$2 = new ArrayList<>();
and$2.add(criteriaBuilder.equal(root.get("status").as(Integer.class), 3));
and$2.and(criteriaBuilder.equal(root.get("deleted").as(Boolean.class), false));
return criteriaBuilder.or(and.toArray(new Predicate[and.size()]), and$2.toArray(new Predicate[and$2.size()]));
};
Page<Image> page = imageRepository.findAll(specification, pageable);
return page;
}
目前的写法:
private final SpecificationApplication<GroundEntity> specificationApplication = new SpecificationApplication<>(Image.class);
public Page<Image> pagingImage(String title, Integer start, Integer size) {
Pageable pageable = PageRequest.of(start, size, Sort.by(Sort.Direction.DESC , "id"));
String sql$1 = "file not like %http://% and file not like %https://%";
String sql = String.format("(title like %%%s%% and %s) or (status=3 and deleted=false)", title, sql$1);
Specification<Image> specification = specificationApplication.specification(sql);
Page<Image> page = imageRepository.findAll(specification, pageable);
return page;
}
上面的代码相对来说确实少了一些,但是字段在IDEA中没有提示、关键字没有提示,所以这是后面的改进方向,后面会用链式调用去改进,还有sql字段也没有相对应的验证,根据验证来决定拼接sql。
未来的写法可能是这样:
public Page<Image> pagingImage(String title, Integer start, Integer size) {
Pageable pageable = PageRequest.of(start, size, Sort.by(Sort.Direction.DESC , "id"));
Image image = new Image();
Specification<Image> specification = image.openBracket()
.title().notLike("%http://%")
.and()
.file().notLike("%https://%")
.closeBracket()
.openBracket()
.or()
.status().eq(3)
.deleted().eq(false)
.build();
Page<Image> page = imageRepository.findAll(specification, pageable);
return page;
}
因为在IDEA这样的编辑器中,方法都是有提示的,所有写上面的代码会显得非常轻松。但还是不够的,因为SQL要动态拼接,如何更加方便的进行拼接还是需要思考的问题。
三、实现原理
AST树结构
AstNode主要结构
- AstDichotomyOperation
表达式由左右两边构成,左右两边都可以解析。
如:expression and expression
- AstNoDichotomyOperation
表达式只有变量和关键字构成,不能继续解析。
如:xxx IS NULL | xxx IS NOT NULL | xxx ISNULL…
- AstSymmetryOperation
单边的表达式,该表达式的右边还会继续解析。
如:!(expression) / not(expression)
- AstStatic
最终的节点,无法被继续分析。
把表达式解析成Ast
- AbstractOperationHandle:解决字符串的解析。
- NoDichotomyHandle:解析 xxx IS NULL | xxx IS NOT NULL | xxx ISNULL …
- DichotomyHandle:解析expression and expression。
- SymmetryHandle:解析!(expression) / not(expression)
- BracktHandle:解析(expression)
Ast 解析成 JPA Specification
感觉过程有点复杂,简单点描述来说,就是深度搜索AST树,在先序、中序、后序中分别让表达式入栈出栈的过程,最后predicate栈剩下的一个元素就是我们需要的Predicate。类协同比较核心的图我给出来,但是过程太过麻烦,就不给图了。
- AstRecursion 深度搜索
- OperationOrStaticHandle 协同各种逻辑、变量、值入栈出栈、创建Predicate并入栈出栈
- StackParse 四个栈,存储值、变量、Predicate
- PredicateGenerate 一个抽象的接口,用于创建对于类型的Predicate
三、项目地址
https://gitee.com/jiangjinghong520/jpa-specification.git
四、目前阶段的总结
目前是不能支持链式调用的中间产品,先一步一步做中间产品,做链式挺简单,但是要做好还是挺难,得研究一下SpringSecurity中build/config和Java Stream的设计,模仿它们。要更加方便的编程,还要借鉴lombok,掌握JSR 256技术。一下子是完全做不出来的,对于我来说一下子要学的东西还挺多,我觉得也挺难。