• 让mybatis-plus支持NOT逻辑运算


    在做一个项目的时候,使用mybatis-plus进行数据库的操作,有个场景需要根据一个条件树生成对应的where 条件,需要根据条件树构造 条件 之间的andor以及 not 逻辑关系,mybatis-plus 提供的方法支持andor。但是not 函数不是 条件前面添加 NOT 关键字。因此需要进行扩展实现此需求。

    使用 Wrapper 进行过滤

    Wrapper定义:

    @FunctionalInterface
    public interface ISqlSegment extends Serializable {
    
        /**
         * SQL 片段
         */
        String getSqlSegment();
    }
    
    public abstract class Wrapper<T> implements ISqlSegment {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    mybatis-plus 通过 ISqlSegment 生成各sql 片段,最后组装成一个sql 语句。

    Wrapper主要有2个实现,有各自的使用方式。

    QueryWrapper

    直接使用SQL 语句片段方式。

    下列示例直接编写SQL语句。

    	//以下代码直接设置select 语句
       QueryWrapper queryWrapper = new QueryWrapper<>();
    		queryWrapper.select("count(update_date) as total"
    				, "min(update_date) as minDate"
    				, "max(update_date) as maxDate");
    
    		if (ObjectUtil.isNotNull(beginDate)) {
    			queryWrapper.ge("update_date", beginDate);
    		}
    		List<Map<String, Object>> info = this.baseMapper.selectMaps(queryWrapper);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    			//以下代码 设置 过滤条件,直接进行字段编码。
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    
            queryWrapper.eq("name", "张三");
            queryWrapper.eq("age", 28);
            queryWrapper.eq("last_name", null);
    
            // 这样也可以
            // queryWrapper.eq("name", "张三").eq("age", 28).eq("last_name", null);
    
            List<User> users = userMapper.selectList(queryWrapper);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    LambdaQueryWrapper

    以属性方式操作数据表字段,函数方式构造过滤条件,规避了使用SQL语句方式会出现的很多错误。

    下面代码示例直接以属性方式访问字段:

    		LambdaQueryWrapper<BaseInfo> lambdaQuery = Wrappers.<BaseInfo>query().lambda()
    				.ge(BaseInfo::getUpdateDate, beginDate)    
    				.lt(BaseInfo::getUpdateDate, endDate);
    		return this.baseMapper.selectList(lambdaQuery);
    
    • 1
    • 2
    • 3
    • 4

    Wrapper 的逻辑运算

    andor

        @Override
        public Children and(boolean condition, Consumer<Children> consumer) {
            return and(condition).addNestedCondition(condition, consumer);
        }
    
        @Override
        public Children or(boolean condition, Consumer<Children> consumer) {
            return or(condition).addNestedCondition(condition, consumer);
        }
        @Override
        public Children nested(boolean condition, Consumer<Children> consumer) {
            return addNestedCondition(condition, consumer);
        }
    
        protected Children addNestedCondition(boolean condition, Consumer<Children> consumer) {
            final Children instance = instance();
            consumer.accept(instance);
            return doIt(condition, LEFT_BRACKET, instance, RIGHT_BRACKET);
        }
    
    
        @Override
        public Children or(boolean condition) {
            return doIt(condition, OR);
        }
        protected Children and(boolean condition) {
            return doIt(condition, AND);
        }
    
    
        protected Children not(boolean condition) {
            return doIt(condition, NOT);
        }
    
        public Children notBetween(boolean condition, R column, Object val1, Object val2) {
            return not(condition).between(condition, column, val1, val2);
        }
    
        protected Children doIt(boolean condition, ISqlSegment... sqlSegments) {
            if (condition) {
                expression.add(sqlSegments);
            }
            return typedThis;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    根据源码可以发现 ,andor 方法都会把条件放在子集(即括号内),然后添加 ANDOR 字符串。

    not()方法是protected 访问,不能访问。只是在 条件表达式前加上 NOT 字符串,并不会添加上括号(),导致 不能达到对整个表达式取反操作。对于所有的取反,则会有 notLikenotBeTweennotIn 等方法,内部调用not()方法。

    综上所述,mybatis-plus未提供NOT 逻辑运算符。

    实现

    思路:

    既然最终都是构造SQL语句,则实现一个子类,能够在条件前面加上NOT,则满足要求。

    QueryWrapper实现

    public class CloudQueryWrapper<T> extends QueryWrapper<T> {
        public CloudQueryWrapper() {
            super();
        }
        //增加not()方法。参数为 一个表达式
    
        public QueryWrapper<T> not(Consumer<QueryWrapper<T>> consumer) {
            return not(true, consumer);
        }
    
        public QueryWrapper<T> not(boolean condition, Consumer<QueryWrapper<T>> consumer) {
            final QueryWrapper<T> instance = instance();
            consumer.accept(instance);
            //必须要这么写,不然会出错
          // 第一个参数是:NOT,第二个是左括号,第二个是 条件 ,第三个是右括号
          //  protected Children doIt(boolean condition, ISqlSegment... sqlSegments)
          //参考:protected Children addNestedCondition(boolean condition, Consumer consumer) 的实现
            return doIt(condition, MybatisExtension.Not.INSTANCE, LEFT_BRACKET, instance, RIGHT_BRACKET);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    LambdaQueryWrapper实现

    /**
     * 继承 LambdaQueryWrapper
     * 为了暴露出 not 方法
     *
     * @author lihz
     * @date 2021/10/13
     */
    public class CloudLambdaQueryWrapper<T> extends LambdaQueryWrapper<T> {
    
        public CloudLambdaQueryWrapper() {
            super();
        }
    
        public LambdaQueryWrapper<T> not(Consumer<LambdaQueryWrapper<T>> consumer) {
            return not(true, consumer);
        }
    
        public LambdaQueryWrapper<T> not(boolean condition, Consumer<LambdaQueryWrapper<T>> consumer) {
            final LambdaQueryWrapper<T> instance = instance();
            consumer.accept(instance);
            //必须要这么写,不然会出错
            return doIt(condition, MybatisExtension.Not.INSTANCE, LEFT_BRACKET, instance, RIGHT_BRACKET);
        }
    
        /**
         * 覆盖父类的方法,支持直接设置字段的名称
         * @param column
         * @param onlyColumn
         * @return
         */
        @Override
        protected String columnToString(SFunction<T, ?> column, boolean onlyColumn) {
            if (column instanceof CloudSFunction) {
                CloudSFunction<T> c = (CloudSFunction) column;
                return c.apply(null);
            }
    
            return super.columnToString(column, onlyColumn);
        }
    
        /**
         * 返回固定列名的接口
         *
         * @author lihz
         * @date 2021/10/13
         */
        public interface CloudSFunction<T> extends SFunction<T, String> {
    
        }
    
        /**
         * 不建议直接 new 该实例
         */
        CloudLambdaQueryWrapper(T entity, Class<T> entityClass, SharedString sqlSelect, AtomicInteger paramNameSeq,
                                Map<String, Object> paramNameValuePairs, MergeSegments mergeSegments,
                                SharedString lastSql, SharedString sqlComment) {
            super.setEntity(entity);
            this.paramNameSeq = paramNameSeq;
            this.paramNameValuePairs = paramNameValuePairs;
            this.expression = mergeSegments;
            //this.sqlSelect = sqlSelect;
            this.entityClass = entityClass;
            this.lastSql = lastSql;
            this.sqlComment = sqlComment;
        }
    
        /**
         * 用于生成嵌套 sql
         * 

    故 sqlSelect 不向下传递,直接从LambdaQueryWrapper拷贝的,用于在内部使用CloudLambdaQueryWrapper 的覆盖的方法

    */
    @Override protected LambdaQueryWrapper<T> instance() { return new CloudLambdaQueryWrapper<>(entity, entityClass, null, paramNameSeq, paramNameValuePairs, new MergeSegments(), SharedString.emptyString(), SharedString.emptyString()); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    NOT 的定义

    在前面调用 doIt(condition, MybatisExtension.Not.INSTANCE, LEFT_BRACKET, instance, RIGHT_BRACKET),没有使用mybatis-plusNOT

    会出现问题,因此自定义了一个

    /**
     * Mybatis的扩展类
     *
     * @author lihz
     * @date 2021/10/14
     */
    @NoArgsConstructor(access = AccessLevel.PRIVATE)
    public class MybatisExtension {
        /**
         * 输出NOT,不能使用mybatis 自己的NOT,不然有问题
         */
        static class Not implements ISqlSegment {
            public static final Not INSTANCE = new Not();
    
            private Not() {
    
            }
    
            /**
             * SQL 片段
             */
            @Override
            public String getSqlSegment() {
                return " NOT ";
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    使用

        private void f2(AbstractWrapper wrapper, Consumer<? extends AbstractWrapper> c) {
            if (wrapper instanceof CloudLambdaQueryWrapper) {
                //使用扩展的 CloudLambdaQueryWrapper
                CloudLambdaQueryWrapper wrapper0 = (CloudLambdaQueryWrapper) wrapper;
                wrapper0.not(c);
            }
        }
    
    
    private <W extends AbstractWrapper> Consumer<W> handleLogicCriteria(LogicCriteria logicCriteria
                , Map<RelationalOperatorType, TriConsumer> funcMaps
                , BiConsumer<W, Consumer<W>> notFunc) {
    
            return (W wrapper) -> {
    
                switch (logicCriteria.getOperator()) {
                    case NOT:
    
                        if (CollUtil.isEmpty(logicCriteria.getChildren())) {
                            break;
                        }
                        if (logicCriteria.getChildren().size() > 1) {
                            throw new IllegalArgumentException("NOT 节点只能有一个子节点");
                        }
    
                        Consumer<W> c = createCriteria(logicCriteria.getChildren().get(0), funcMaps, notFunc);
                        //使用扩展的 CloudLambdaQueryWrapper
                        notFunc.accept(wrapper, c);
    
                        break;
                    case OR:
                        // OR,AND 逻辑相似,逻辑写在下面
                    case AND:
    
                        BiConsumer<AbstractWrapper, Consumer<? extends AbstractWrapper>> f = FUNCTION_LOGIC.get(logicCriteria.getOperator());
                        if (ObjectUtil.isNotNull(f)) {
                            for (ICriteriaNode node : logicCriteria.getChildren()) {
                                Consumer<? extends AbstractWrapper> wrapperChild = createCriteria(node, funcMaps, notFunc);
                                f.accept(wrapper, wrapperChild);
                            }
                        }
                        break;
                    default:
                }
            };
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    如果有更好的方式,欢迎指正!

  • 相关阅读:
    2022亚太数学杯数学建模竞赛B题(思路、程序......)
    Fiddler(四) - http请求结果分析&认识菜单
    Spring Boot工程开发流程
    并发编程永远绕不开的难题,跟着大牛带你Java并发编程从入门到精通
    基于实现地图弹窗轮播功能及遇到的问题解决
    【Java】Java 11 新特性概览
    [附源码]计算机毕业设计网上书城网站Springboot程序
    com.upd.sso.client.SsoFilter.init(SsoFilter.java..)无法访问swagger-ui.html
    (零)如何做机器视觉项目
    ES6 入门教程 19 Generator 函数的语法 19.7 yield星号表达式
  • 原文地址:https://blog.csdn.net/demon7552003/article/details/126649944