首页 > 其他分享 >在Mybatis中不对select做任何改变,将物理删除的逻辑迁移到逻辑删除

在Mybatis中不对select做任何改变,将物理删除的逻辑迁移到逻辑删除

时间:2024-07-31 14:41:11浏览次数:8  
标签:instanceof 逻辑 删除 processWhereClauseSubSelects table Mybatis import where select

假设现在有一个已经完成或者已经到中期的项目,但是忘记做逻辑删除了,更改的办法如下

第一步,在项目中添加如下的拦截器

import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import lombok.SneakyThrows;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.*;
import org.apache.commons.collections.CollectionUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;

import java.io.StringReader;
import java.sql.Connection;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

@Component
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class LogicDeleteInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

        // 判断是否是select
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
            return invocation.proceed();
        }

        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        String sql = boundSql.getSql();
        String finalSql = this.handleSql(sql);
        metaObject.setValue("delegate.boundSql.sql", finalSql);
        return invocation.proceed();
    }

    /**
     * 改写SQL
     * {@link DataPermissionInterceptor}
     *
     * @param originalSql 执行的SQL语句
     * @return 处理后的SQL
     */
    private String handleSql(String originalSql) throws JSQLParserException {
        CCJSqlParserManager parserManager = new CCJSqlParserManager();
        Select select = (Select) parserManager.parse(new StringReader(originalSql));
        processSelectBody(select.getSelectBody());
        return select.toString();
    }

    /**
     * 设置 where 条件 -- 使用CCJSqlParser将原SQL进行解析并改写
     *
     * @param plainSelect 查询对象
     */
    @SneakyThrows(Exception.class)
    protected void setWhere(PlainSelect plainSelect) {
        this.processPlainSelect(plainSelect);
    }

    /**
     * 生成拦截对象的代理
     *
     * @param target 目标对象
     * @return 代理对象
     */
    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    /**
     * mybatis配置的属性
     *
     * @param properties mybatis配置的属性
     */
    @Override
    public void setProperties(Properties properties) {
    }

    /**
     * 处理查询对象(初步分解)
     *
     * @param select 查询对象
     */
    private void processSelectBody(SelectBody select) throws JSQLParserException {
        // select from ...
        if (select instanceof PlainSelect) {
            processPlainSelect((PlainSelect) select);
        }
        // select from ... (union | union all | ...) select from ...
        else if (select instanceof SetOperationList setOperationList) {
            List<SelectBody> selectBodyList = setOperationList.getSelects();
            for (SelectBody s : selectBodyList) {
                processPlainSelect((PlainSelect) s);
            }
        }
    }

    /**
     * 处理查询对象(具体细化)
     *
     * @param plainSelect 查询对象
     */
    private void processPlainSelect(PlainSelect plainSelect) throws JSQLParserException {
        FromItem fromItem = plainSelect.getFromItem();

        // select 1
        if (fromItem == null) {
            return;
        }

        String mainTableLogicDelete = null;
        if (fromItem instanceof Table table) { // select from table
            Alias fromItemAlias = table.getAlias();
            String originalTableName = table.getName();
            String mainTableName = fromItemAlias == null ? originalTableName : fromItemAlias.getName();
            mainTableLogicDelete = mainTableName + ".del_flag = 0";
        } else if (fromItem instanceof SubSelect subSelect) { // select from (select ...)
            processSelectBody(subSelect.getSelectBody());
        }

        // join ...
        List<Join> joins = plainSelect.getJoins();
        if (CollectionUtils.isNotEmpty(joins)) {
            for (Join join : joins) {
                Table rightItem = (Table) join.getRightItem();
                Alias rightItemAlias = rightItem.getAlias();
                String rightItemOriginalTableName = rightItem.getName();
                String rightItemMainTableName = rightItemAlias == null ? rightItemOriginalTableName : rightItemAlias.getName();
                String subTableLogicDelete = rightItemMainTableName + ".del_flag = 0";
                Expression onExpression = join.getOnExpression();
                if (onExpression == null) {
                    join.setOnExpressions(Collections.singletonList(CCJSqlParserUtil.parseCondExpression(subTableLogicDelete)));
                } else {
                    join.setOnExpressions(Collections.singletonList(new AndExpression(onExpression, CCJSqlParserUtil.parseCondExpression(subTableLogicDelete))));
                }
            }
        }

        // where ...
        if (mainTableLogicDelete != null) {
            if (plainSelect.getWhere() == null) {
                plainSelect.setWhere(CCJSqlParserUtil.parseCondExpression(mainTableLogicDelete));
            } else {
                processWhereClauseSubSelects(plainSelect.getWhere());
                plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), CCJSqlParserUtil.parseCondExpression(mainTableLogicDelete)));
            }
        }
    }

    /**
     * 处理 where 子查询
     *
     * @param where where 条件
     */
    private void processWhereClauseSubSelects(Expression where) throws JSQLParserException {
        if (where instanceof SubSelect subSelect) {
            PlainSelect subSelectBody = (PlainSelect) subSelect.getSelectBody();
            processPlainSelect(subSelectBody);
        } else if (where instanceof BinaryExpression binaryExpression) {
            processWhereClauseSubSelects(binaryExpression.getLeftExpression());
            processWhereClauseSubSelects(binaryExpression.getRightExpression());
        } else if (where instanceof InExpression inExpression) {
            ItemsList rightItemsList = inExpression.getRightItemsList();
            if (rightItemsList instanceof SubSelect subSelect) {
                PlainSelect subSelectBody = (PlainSelect) subSelect.getSelectBody();
                processPlainSelect(subSelectBody);
            } else if (rightItemsList instanceof ExpressionList expressionList) {
                List<Expression> expressions = expressionList.getExpressions();
                for (Expression expression : expressions) {
                    processWhereClauseSubSelects(expression);
                }
            }
        } else if (where instanceof ExistsExpression existsExpression) {
            processWhereClauseSubSelects(existsExpression.getRightExpression());
        } else if (where instanceof NotExpression notExpression) {
            processWhereClauseSubSelects(notExpression.getExpression());
        } else if (where instanceof Parenthesis parenthesis) {
            processWhereClauseSubSelects(parenthesis.getExpression());
        } else if (where instanceof IsNullExpression isNullExpression) {
            processWhereClauseSubSelects(isNullExpression.getLeftExpression());
        } else if (where instanceof CaseExpression caseExpression) {
            for (Expression whenClause : caseExpression.getWhenClauses()) {
                processWhereClauseSubSelects(whenClause);
            }
            if (caseExpression.getElseExpression() != null) {
                processWhereClauseSubSelects(caseExpression.getElseExpression());
            }
        } else if (where instanceof Function function) {
            for (Expression parameter : function.getParameters().getExpressions()) {
                processWhereClauseSubSelects(parameter);
            }
        } else if (where instanceof Between between) {
            processWhereClauseSubSelects(between.getLeftExpression());
            processWhereClauseSubSelects(between.getBetweenExpressionStart());
            processWhereClauseSubSelects(between.getBetweenExpressionEnd());
        } else if (where instanceof AnyComparisonExpression anyComparisonExpression) {
            processWhereClauseSubSelects(anyComparisonExpression.getSubSelect());
        }
    }
}

然后将这个插件注册到mybatis里面

sessionFactory.setPlugins(new LogicDeleteInterceptor());

此时仅完成第一步,也就是对所有select在不手动改变的情况下,迁移到逻辑删除的逻辑上来

第二步,修改所有delete

这一步很简单,vscode的查找替换功能很强大,正则匹配也不复杂,可自行选择写python脚本或是直接编辑器替换

第三步,改表,执行如下的存储过程,为数据库内的所有表加一列

del_flag tinyint(1) not null default 0 comment '逻辑删除位'

create
    definer = root@localhost procedure alterMultiTableAddDelFlag()
BEGIN
    -- 定义表名变量
    DECLARE s_tablename VARCHAR(100);

    DECLARE cur_table_structure CURSOR
        FOR
        SELECT table_name
        FROM INFORMATION_SCHEMA.TABLES
        -- test = 数据库名称
        WHERE table_schema = '数据库名称'
          AND table_name NOT IN (SELECT t.table_name
                                 FROM (SELECT table_name, column_name
                                       FROM information_schema.columns
                                       WHERE table_name IN (SELECT table_name
                                                            FROM INFORMATION_SCHEMA.TABLES
                                                            WHERE table_schema = 'drgx-mes')) t
                                 WHERE t.column_name = 'object_name');

    DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET s_tablename = NULL;

    OPEN cur_table_structure;

    FETCH cur_table_structure INTO s_tablename;

    WHILE (s_tablename IS NOT NULL)
        DO
            SET @MyQuery = CONCAT("alter table `", s_tablename,
                                  "` add COLUMN `del_flag` TINYINT(1) ", "COMMENT '逻辑删除位' NOT NULL DEFAULT 0");
            PREPARE msql FROM @MyQuery;

            EXECUTE msql;

            FETCH cur_table_structure INTO s_tablename;
        END WHILE;
    CLOSE cur_table_structure;


END;

完成以上三步,即可简单实现从物理删除到逻辑删除的迁移,具体情况还可具体分析

注:这样做的好处是,在已经很多张表并且业务逻辑已经在物理删除的框架内难以轻易改动的情况下,不需要往pojo里加delFlag,不需要在mapper的resultMap里加del_flag,不需要修改mapper里面任何的select语句,只需要修改mapper里的delete语句,就能完成迁移(前提是默认用户不需要接触到delFlag这个属性,大部分情况下是这样的,因为删除的操作会提供接口,delFlag这个属性一定要暴露给用户进行get和set的操作较少)

标签:instanceof,逻辑,删除,processWhereClauseSubSelects,table,Mybatis,import,where,select
From: https://www.cnblogs.com/merielvaren/p/18334558

相关文章

  • 手写MyBatis 重要基本原理框架
    1.手写MyBatis重要基本原理框架@目录1.手写MyBatis重要基本原理框架1.1第一步:IDEA中创建模块1.2第二步:资源工具类,方便获取指向配置文件的输入流1.3第三步:定义SqlSessionFactoryBuilder类1.4第四步:分析SqlSessionFactory类中有哪些属性1.5第五步:定义JDBCTransaction1.6......
  • oracle-DBCA删除数据库
    DBCADBCA是Oracle软件包中用来安装/卸载/配置数据库的一个工具,其提供了两种方式对数据库进行安装或卸载,分别是GUI界面和静默模式。图形化安装的方式相对来说比较简单,容易上手,但是不具备重复利用性。每次重新安装数据库的时候都可能需要进行相同的配置,而这无疑会消耗工作者的......
  • Java逻辑控制语句
    一、Java的逻辑控制语句的大体分类:二、顺序结构1.顺序结构:即按顺序执行代码(从头到尾按顺序执行代码),如publicclassMain{publicstaticvoidmain(Stringargs[]){intnum1=10;intnum2=20;System.out.println(num1+num2);//先输出......
  • 如何恢复WPS文档中未保存或删除的文件
    由于各种原因,您可能会丢失WPS文档,例如意外删除、硬盘格式化、病毒攻击等。您是否遇到过丢失未保存的WPS文件的情况?您知道如何恢复WPS文档中未保存的文件吗?WPSOffice是一款办公套件,可以作为MicrosoftOfficeSuite的绝佳替代品。它主要由三个主要组件组成:WPSWriter......
  • 5 款最佳电脑照片恢复软件,助您恢复误删除的照片
    电脑可以作为存储盒来保存您美好的照片记忆。然而,病毒、格式化、删除等突发事件可能会夺走你由图片组成的记忆。我怎样才能从我的计算机恢复已删除的照片?照片恢复软件就是答案。本页列出了适用于Windows和Mac的5个最佳图片恢复程序,以帮助您安全快速地从计算机恢复丢......
  • 无法从Android恢复删除的文件该怎么办?5 个方法可以学习参考下
    Android设备已成为我们生活中不可或缺的一部分,充当重要文件、照片和文档的存储中心。但是,意外时有发生,有时我们会不小心从Android设备中删除重要文件。好消息是,有一些方法可以从Android恢复永久删除的文件。在这篇博文中,我们将探讨各种方法和解决方案,以帮助您检索有价值的数据......
  • 通过删除元素垂直打印二维列表的问题
    我有这个函数,它接受一个二维数组,并返回一个字符串。该字符串应该有列,其中每列都是二维数组中的列表。由于某种原因,打印中间3个列表时出现问题,我不明白为什么。有谁知道出了什么问题吗?defprintFancyNetwork(self)->None:temp_network:list=self.n......
  • 如何从类型提示中删除Optional?
    如何在python中解包(这个问题让人想起rust)类型提示?或者换句话说,如何将list[Optional[str]]类型的变量变成list[str]?问题:我做了一个小函数来解释我的问题。所以我从一个空列表开始,但在某些点(这里称为点B)我想将我的列表传递给我的代码中的......
  • SQL执行顺序和逻辑
    SQL执行顺序和逻辑MySQL的执行顺序:  (9)SELECT  (6)SUM(聚合函数)  (10)DISTINCT<select_list>  (1)FROM<left_table>  (3)<join_type>JOIN<right_table>  (2)ON<join_condition>  (4)WHERE<where_condition>  (5)GROUP......
  • Datawhale AI夏令营 逻辑推理
    #AI夏令营#Datawhale#夏令营#大模型#微调实践步骤:一、跑通baseline二、尝试个人idea三、尝试进阶baseline比赛链接:上海科学智能研究院一、baseline介绍baseline代码由官方提供,无需修改食用指南:Datawhalebaseline收获官方提供的baseline可以做到一键运行,只......