【原创】为MybatisPlus增加一个逻辑删除插件,让XML中的SQL也能自动增加逻辑删除功能

前言

看到这个标题有人就要说了,D哥啊,MybatisPlus不是本来就有逻辑删除的配置吗,比如@TableLogic注解,配置文件里也能添加如下配置设置逻辑删除。

mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  configuration:
    mapUnderscoreToCamelCase: true
  global-config:
    db-config:
      logic-delete-field: del
      logic-delete-value: 1
      logic-notDelete-value: 0

但是我想说,xml中添加了逻辑删除了吗?很明显这个没有,MybatisPlus只在QueryWrapper中做了手脚,而xml是Mybatis的功能,非MybatisPlus的功能。而xml中写SQL又是我工作中最常用到的,优势在于SQL可读性强,结合MybatisX插件并在IDEA中连接database后能够直接跳转到方法和表,且对多表join和子查询支持都比QueryWrapper来得好。而逻辑删除又是会经常漏掉的字段,虽然说手动添加也不费多少时间,但是麻烦的是容易漏掉,特别是子查询和join的情况,而且时间积少成多,我觉得有必要解决这个问题。

本插件适用于绝大部分表都拥有逻辑删除字段的情况!!

本文使用的MybatisPlus的版本为3.5.3.1

不多说了,直接上代码!


/**
 * @author DCT
 * @version 1.0
 * @date 2023/11/9 23:10:18
 * @description
 */
@Slf4j
public class DeleteMpInterceptor implements InnerInterceptor {
    public static final String LOGIC_DELETE = "LOGIC_DELETE";
    public static final List<String> excludeFunctions = new ArrayList<>();
    protected String deleteFieldName;

    public DeleteMpInterceptor(String deleteFieldName) {
        this.deleteFieldName = deleteFieldName;
    }

    static {
        excludeFunctions.add("selectList");
        excludeFunctions.add("selectById");
        excludeFunctions.add("selectBatchIds");
        excludeFunctions.add("selectByMap");
        excludeFunctions.add("selectOne");
        excludeFunctions.add("selectCount");
        excludeFunctions.add("selectObjs");
        excludeFunctions.add("selectPage");
        excludeFunctions.add("selectMapsPage");
    }

    @SneakyThrows
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        if (InterceptorIgnoreHelper.willIgnoreOthersByKey(ms.getId(), LOGIC_DELETE)) {
            return;
        }

        if (StringUtils.isBlank(deleteFieldName)) {
            // INFO: Zhouwx: 2023/11/20 没有设置逻辑删除的字段名,也忽略添加逻辑删除
            return;
        }

        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
        String sql = mpBs.sql();
        Statement statement = CCJSqlParserUtil.parse(sql);

        if (!(statement instanceof Select)) {
            return;
        }

        String id = ms.getId();
        int lastIndexOf = id.lastIndexOf(".");
        String functionName = id.substring(lastIndexOf + 1);
        if (excludeFunctions.contains(functionName)) {
            // INFO: DCT: 2023/11/12 QueryWrapper的查询,本来就会加逻辑删除,不需要再加了
            return;
        }

        Select select = (Select) statement;
        SelectBody selectBody = select.getSelectBody();

        // INFO: DCT: 2023/11/12 处理核心业务
        handleSelectBody(selectBody);

        // INFO: DCT: 2023/11/12 将处理完的数据重新转为MPBoundSql
        String sqlChange = statement.toString();
        mpBs.sql(sqlChange);
    }

    protected void handleSelectBody(SelectBody selectBody) {
        if (selectBody instanceof PlainSelect) {
            PlainSelect plainSelect = (PlainSelect) selectBody;

            // INFO: DCT: 2023/11/12 处理join中的内容
            handleJoins(plainSelect);

            Expression where = plainSelect.getWhere();

            FromItem fromItem = plainSelect.getFromItem();
            EqualsTo equalsTo = getEqualTo(fromItem);

            if (where == null) {
                // INFO: DCT: 2023/11/12 where条件为空,增加一个where,且赋值为 表名.del = 0
                plainSelect.setWhere(equalsTo);
            } else {
                // INFO: DCT: 2023/11/12 普通where,后面直接加上本表的 表名.del = 0 
                AndExpression andExpression = new AndExpression(where, equalsTo);
                plainSelect.setWhere(andExpression);

                // INFO: DCT: 2023/11/12 有子查询的处理子查询 
                handleWhereSubSelect(where);
            }
        }
    }

    /**
     * 这一段来自MybatisPlus的租户插件源码,通过递归的方式加上需要的SQL
     *
     * @param where
     */
    protected void handleWhereSubSelect(Expression where) {
        if (where == null) {
            return;
        }
        if (where instanceof FromItem) {
            processOtherFromItem((FromItem) where);
            return;
        }
        if (where.toString().indexOf("SELECT") > 0) {
            // 有子查询
            if (where instanceof BinaryExpression) {
                // 比较符号 , and , or , 等等
                BinaryExpression expression = (BinaryExpression) where;
                handleWhereSubSelect(expression.getLeftExpression());
                handleWhereSubSelect(expression.getRightExpression());
            } else if (where instanceof InExpression) {
                // in
                InExpression expression = (InExpression) where;
                Expression inExpression = expression.getRightExpression();
                if (inExpression instanceof SubSelect) {
                    handleSelectBody(((SubSelect) inExpression).getSelectBody());
                }
            } else if (where instanceof ExistsExpression) {
                // exists
                ExistsExpression expression = (ExistsExpression) where;
                handleWhereSubSelect(expression.getRightExpression());
            } else if (where instanceof NotExpression) {
                // not exists
                NotExpression expression = (NotExpression) where;
                handleWhereSubSelect(expression.getExpression());
            } else if (where instanceof Parenthesis) {
                Parenthesis expression = (Parenthesis) where;
                handleWhereSubSelect(expression.getExpression());
            }
        }
    }

    /**
     * 处理子查询等
     */
    protected void processOtherFromItem(FromItem fromItem) {
        // 去除括号
        while (fromItem instanceof ParenthesisFromItem) {
            fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
        }

        if (fromItem instanceof SubSelect) {
            SubSelect subSelect = (SubSelect) fromItem;
            if (subSelect.getSelectBody() != null) {
                // INFO: Zhouwx: 2023/11/20 递归从select开始查找
                handleSelectBody(subSelect.getSelectBody());
            }
        } else if (fromItem instanceof ValuesList) {
            log.debug("Perform a subQuery, if you do not give us feedback");
        } else if (fromItem instanceof LateralSubSelect) {
            LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;
            if (lateralSubSelect.getSubSelect() != null) {
                SubSelect subSelect = lateralSubSelect.getSubSelect();
                if (subSelect.getSelectBody() != null) {
                    // INFO: Zhouwx: 2023/11/20 递归从select开始查找
                    handleSelectBody(subSelect.getSelectBody());
                }
            }
        }
    }

    protected void handleJoins(PlainSelect plainSelect) {
        List<Join> joins = plainSelect.getJoins();
        if (joins == null) {
            return;
        }

        for (Join join : joins) {
            // INFO: DCT: 2023/11/12 获取表别名,并获取 表.del = 0
            FromItem rightItem = join.getRightItem();
            EqualsTo equalsTo = getEqualTo(rightItem);

            // INFO: DCT: 2023/11/12 获取on右边的表达式
            Expression onExpression = join.getOnExpression();
            // INFO: DCT: 2023/11/12 给表达式增加 and 表.del = 0
            AndExpression andExpression = new AndExpression(onExpression, equalsTo);

            ArrayList<Expression> expressions = new ArrayList<>();
            expressions.add(andExpression);
            // INFO: DCT: 2023/11/12 目前的这个表达式就是and后的表达式了,不用再增加原来的表达式,因为这个and表达式已经包含了原表达式所有的内容了
            join.setOnExpressions(expressions);
        }
    }

    protected EqualsTo getEqualTo(FromItem fromItem) {
        Alias alias = fromItem.getAlias();

        String aliasName = "";
        if (alias == null) {
            if (fromItem instanceof Table) {
                Table table = (Table) fromItem;
                aliasName = table.getName();
            }
        } else {
            aliasName = alias.getName();
        }

        EqualsTo equalsTo = new EqualsTo();
        Column leftColumn = new Column();
        leftColumn.setColumnName(aliasName + "." + deleteFieldName);
        equalsTo.setLeftExpression(leftColumn);
        equalsTo.setRightExpression(new LongValue(0));

        return equalsTo;
    }
}

代码说明

这代码中已经有很多注释了,其实整段代码并非100%我的原创,而是借鉴了MybatisPlus自己的TenantLineInnerIntercept,因为两者的功能实际上非常详尽,我依葫芦画瓢造了一个,特别是中间的handleWhereSubSelect方法,令人拍案叫绝!使用大量递归,通过非常精简的代码处理完了SQL查询中所有的子查询。

getEqualTo方法可以算是逻辑删除插件的核心代码了,先判断表是否存在别名,如果有,就拿别名,如果没有就拿表名,防止出现多个表都有逻辑删除字段的情况下指代不清的情况,出现查询ambiguous错误。通过net.sf.jsqlparser中的EqualsTo表达式,拼上字段名和未被逻辑删除的值0(这里可以按照自己的情况进行修改!)

顶上的excludeFunctions是为了排除QueryWrapper的影响,因为QueryWrapper自己会加上逻辑删除,而这个插件还会再添加一个逻辑删除,导致出现重复,打印出来的SQL不美观。

使用方法

和MybatisPlus其他的插件一样,都通过@Bean的方式进行配置

    @Value("${mybatis-plus.global-config.db-config.logic-delete-field}")
    private String deleteFieldName;


    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new DeleteMpInterceptor(deleteFieldName));
        return interceptor;
    }

我这里的deleteFieldName直接借用了MybatisPlus自己的逻辑删除配置,可以自己设置

排除使用这个插件

如果有几张表是没有逻辑删除字段的,那么这个插件自动补的逻辑删除字段则会导致SQL出现报错,可以通过添加以下注解@InterceptorIgnore排除插件对于该方法的生效

@Repository
interface ExampleMapper : BaseMapper<ExampleEntity> {
    @InterceptorIgnore(others = [DeleteMpInterceptor.LOGIC_DELETE + "@true"])
    fun findByList(query: ExampleQo): List<ExampleListVo>
}

上面的代码是Kotlin写的,但是不妨碍查看

运行效果

mapper中代码为:

/**
 * @author DCTANT
 * @version 1.0
 * @date 2023/11/20 17:40:41
 * @description
 */
@Repository
interface ExampleMapper : BaseMapper<ExampleEntity> {
    fun findByList(query: ExampleQo): List<ExampleListVo>
}

xml中的代码为:

    <select id="findByList" resultType="com.itdct.server.admin.example.vo.ExampleListVo">
        select t.* from test_example as t
        <where>
            <if test="name != null and name != ''">and t.name = #{name}</if>
            <if test="number != null">and t.number = #{number}</if>
            <if test="keyword != null and keyword != ''">and t.name like concat('%',#{keyword},'%')</if>
            <if test="startTime != null">and t.create_time &gt; #{startTime}</if>
            <if test="endTime != null">and t.create_time &lt; #{endTime}</if>
        </where>
        order by t.create_time desc
    </select>

执行效果为:

t.del = 0就是逻辑删除插件添加的代码,当然join和子查询我也使用了,目前来看没有什么问题

欢迎大家提出修改意见

目前这个代码还处于Demo阶段,并没有上线使用,还是处于没充分测试的状态,欢迎大家提出整改意见!如果有bug我也会及时修复。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/172342.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java-认识String类

本章重点&#xff1a; 1. 认识 String 类 2. 了解 String 类的基本用法 3. 熟练掌握 String 类的常见操作 4. 认识字符串常量池 5. 认识 StringBuffer 和 StringBuilder 1.String类的重要性 在C语言中已经涉及到字符串了&#xff0c;但是在C语言中要表示字符串只能使用字符数组…

C++中的内存管理

✨前言✨ &#x1f4d8; 博客主页&#xff1a;to Keep博客主页 &#x1f646;欢迎关注&#xff0c;&#x1f44d;点赞&#xff0c;&#x1f4dd;留言评论 ⏳首发时间&#xff1a;2023年11月21日 &#x1f4e8; 博主码云地址&#xff1a;博主码云地址 &#x1f4d5;参考书籍&…

如何选择适合的开源框架来构建微服务架构?

随着科技的飞速发展&#xff0c;云计算和大规模应用的需求日益显著&#xff0c;这促使微服务架构在软件开发领域中占据了主流地位。微服务架构的广泛应用为开发人员提供了灵活性、可伸缩性和高可用性&#xff0c;从而推动了快速的应用程序开发。然而&#xff0c;在构建微服务架…

ky10 server x86 安装、更新openssl3.1.4(在线编译安装、离线安装)

查看openssl版本 openssl version 离线编译安装升级 #!/bin/shOPENSSLVER3.1.4OPENSSL_Vopenssl versionecho "当前OpenSSL 版本 ${OPENSSL_V}" #------------------------------------------------ #wget https://www.openssl.org/source/openssl-3.1.4.tar.gzech…

git撤销某一次commit提交

一&#xff1a;撤销上一次commit提交&#xff0c;但不删除修改的代码 可以使用使用VSCode 二&#xff1a;使用 git reset --hard命令删除提交时&#xff0c;将会删除该提交及其之后的所有更改&#xff08;相当于你想要回滚到的提交的提交ID&#xff09; git reset --hard 版本…

ubuntu18.04安装并运行ORB-SLAM2

查看版本号 lsb_release -a 换源 Ubuntu系统自带的源都是国外的网址&#xff0c;国内用户在使用的时候下载比较慢甚至无法获取&#xff0c;需要替换成国内的镜像源 备份源文件 sudo cp /etc/apt/sources.list /etc/apt/sources.list.old 打开文件 sudo gedit /etc/apt/so…

【Python】153是一个非常特殊的数,它等于它的每位数字的立方和,即153=1*1*1+5*5*5+3*3*3。编程求所有满足这种条件的三位十进制数。

问题描述 153是一个非常特殊的数&#xff0c;它等于它的每位数字的立方和&#xff0c;即1531*1*15*5*53*3*3。编程求所有满足这种条件的三位十进制数。 输出格式 按从小到大的顺序输出满足条件的三位十进制数&#xff0c;每个数占一行。 方法一&#xff1a; for i in range(10…

入行IC | 从小白助理级,到总监专家级,到底要经历怎样的成长阶段呢?

《中国集成电路产业人才发展报告》是业内和IC设计、IC人才都息息相关的一份报告。 &#xff08;文末可领全部报告资料&#xff09; * 从报告数据来看&#xff0c;无论在半导体产业的哪个环节&#xff0c;个人发展路径和年薪待遇都是逐级攀升的趋势。 那么从小白助理级&a…

k8s-pod管理 3

pod是可以创建和管理k8s 计算的最小可部署单元&#xff0c;一个pod 代表着集群中运行的一个进程&#xff0c;每个pod 都有一个唯一的ip pod包裹了容器 下载测试镜像 创建自主式的pod 查看创建的pod的详情信息 删除pod 创建控制器 副本过多&#xff0c;需要进行负载均衡减轻节点…

外部 prometheus监控k8s集群资源

prometheus监控k8s集群资源 一&#xff0c;通过CADvisior 监控pod的资源状态1.1 授权外边用户可以访问prometheus接口。1.2 获取token保存1.3 配置prometheus.yml 启动并查看状态1.4 Grafana 导入仪表盘 二&#xff0c;通过kube-state-metrics 监控k8s资源状态2.1 部署 kube-st…

【操作系统】文件系统的实现

文章目录 文件系统的层次结构文件系统的实现目录实现线性列表哈希表 文件的实现连续分配链接分配索引分配 文件存储空间管理空闲表法与空闲链表法成组链接法位示图法 文件系统的层次结构 文件系统从上往下分为了五层&#xff0c;分别是用户调用接口、文件目录系统、存取控制模…

解放双手!一键助你快速发圈、批量加好友,好用哭了!

朋友们&#xff0c;你们有没有经历过管理多个微信账号的繁琐和压力&#xff1f; 会不会因为忙不过来&#xff0c;忘记及时回复客户&#xff0c;错过了推广的时机&#xff1f; 别担心&#xff0c;现在有了微信管理系统&#xff0c;一切都变得简单轻松起来&#xff01; 微信管…

打造高效医患沟通:陪诊小程序开发技术指南

随着科技的不断发展&#xff0c;陪诊小程序作为医患沟通的新工具逐渐成为关注焦点。本文将带领你通过使用React和Node.js技术栈&#xff0c;构建一个功能强大且用户友好的陪诊小程序&#xff0c;实现医患互动的便捷和高效。 1. 准备工作 确保你的开发环境中已安装了Node.js和…

Unity下载资源且保存

UnityWebRequest(WWW——已过时) 替代&#xff1a;Unity不再支持WWW后&#xff0c;使用UnityWebRequest完成web请求。 Unity - Scripting API: UnityWebRequest (unity3d.com)https://docs.unity3d.com/ScriptReference/Networking.UnityWebRequest.html if (www.isNetworkEr…

Java 多线程之 volatile(可见性/重排序)

文章目录 一、概述二、使用方法三、测试程序3.1 验证可见性的示例3.2 验证指令重排序的示例 一、概述 在Java中&#xff0c;volatile 关键字用于修饰变量&#xff0c;其作用是确保多个线程之间对该变量的可见性和禁止指令重排序优化。 当一个变量被声明为volatile时&#xff0…

基于Vue+SpringBoot的校园电商物流云平台开源项目

项目编号&#xff1a; S 034 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S034&#xff0c;文末获取源码。} 项目编号&#xff1a;S034&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 商品数据模块2.3 快…

企业怎么进行人事管理?一篇文章带你了解!

阅读本文你将了解企业如何运用数字化工具进行人事管理&#xff1a;一、数字化、线上化&#xff0c;解放人力&#xff1b;二、规范管理流程&#xff0c;提升处理效率&#xff1b;三、数据分析可视化&#xff0c;支持并优化决策&#xff1b;四、个性化定制&#xff0c;灵活适应需…

mac 和 windows 相互传输文件【共享文件夹】

文章目录 前言创建共享文件夹mac 连接共享文件夹 前言 温馨提示&#xff1a;mac 电脑和 windows 电脑必须处于同一局域网下 本文根据创建共享文件夹的方式实现文件互相传输&#xff0c;所以两台电脑必须处于同一网络 windows 创建共享文件夹&#xff0c;mac 电脑通过 windows…

Enterprise Architect安装与使用

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl Enterprise Architect概述 官方网站&#xff1a;https://www.sparxsystems.cn/products/ea/&#xff1b;图示如下&#xff1a; Enterprise Architect是一个全功能的、基于…

Spring-IOC-FactoryBean机制(难点且重点)

1、第一个案例 1.1、Book.java package com.atguigu.ioc; import lombok.Data; Data public class Book {private String bid;private String bname; }1.2、Book2.java package com.atguigu.ioc; import lombok.Data; Data public class Book2 extends Book {private String co…