• 基于 MyBatis-Plus 解决数据库逻辑删除与唯一索引问题


    一.问题描述

    在业务中经常会有这样一种需求即某字段不能重复,例如用户表的手机又或者是身份证.而遇到这种问题一般两种处理方法,一:插入或修改之前先进行一次查询判断是否存在该记录;二:利用数据库唯一索引约束保证数据的唯一性.

    但如果用方法一会有两个缺点,一是低效率,二是在高并发的系统中,很难保证其可靠性,故我们在这使用第二中方法,也就是设置唯一索引.设置唯一索引本身是没问题的,但目前需要基于逻辑删除之上整合.

    故要思考如何才能让两者之间完美的整合在一起

    二.解决方法

    1.历史表

    每个表新建一个历史表,存储已经删除的历史数据,缺点是大量的历史表。当然还可以参考mysql schema的table表来设计,存储schema和tableName,然后行数据json类型存储,需要根据场景选择。

    2.删除时间(推荐)

    删除标志位不使用0、1,改为使用删除时间戳来替代,使用初始值0或者Null来作为未删除标志符,会占用一定的存储空间,但可以显示删除时间,并且 MyBatisPlus 自带就支持了这种做法,使用这个方法只需,将删除标识字段用 datetime 存储,逻辑未删除值和已删除值支持配置为字符串null,另一个值支持配置为函数来获取值如now()
    这里贴出 yml 配置文件:

    mybatis-plus:
     global-config:
      db-config:
       # 设置逻辑删除值为当前时间
       logic-delete-value: "now()" 
       # 设置未删除值为 "null"
       logic-not-delete-value: "null" 
    

    3.已删除设为null(推荐)

    将未删除标识设置默认值(例如0),再将唯一字段与删除标记添加唯一键约束。当某一记录需要删除时,将删除标记置为NULL。
    由于NULL不会和其他字段有组合唯一键的效果,所以当记录被删除时(删除标记被置为NULL时),解除了唯一键的约束。

    三.具体实现

    这里讲述的是第三种方法,也就是将已删除设为null,这种方法呢 MyBatisPlus 本身是不支持的,故可以利用自定义BaseMapper进行拓展补充,以下是具体实现思路及代码:

    1. 继承AbstractMethod组装数据

    LogicDeleteById.class

    /**
     * 根据id逻辑删除
     *  情况:唯一主键  未删除:0  删除:null
     *
     * @author Brave
     * @version V1.0
     * @date 2021/5/21
     */
    public class LogicDeleteById extends AbstractMethod {
    
        /**
         * 注入自定义 MappedStatement
         *
         * @param mapperClass mapper 接口
         * @param modelClass  mapper 泛型
         * @param tableInfo   数据库表反射信息
         * @return MappedStatement
         */
        @Override
        public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
            // 准备语句
            String sql =  SqlMethod.UPDATE_BY_ID.getSql();
            // set语句
            String set = "set deleted_flag = null";
            // 组装sql
            String buildSql = String.format(
                    sql,
                    // 表名
                    tableInfo.getTableName(),
                    // set值
                    set,
                    // 主键(数据库字段名)
                    tableInfo.getKeyColumn(),
                    // 实体类属性名
                    tableInfo.getKeyProperty(),
                    // and 主键 = yes | 如果是false的话就是 and 主键 = no
                    tableInfo.getLogicDeleteSql(true, true));
    
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, buildSql, mapperClass);
            return addUpdateMappedStatement(mapperClass, modelClass,"logicDeleteById", sqlSource);
        }
    
    }
    

    LogicDeleteByIds.class

    /**
     * 根据ids逻辑删除
     *  情况:唯一主键  未删除:0  删除:null
     *
     * @author Brave
     * @version V1.0
     * @date 2021/5/21
     */
    public class LogicDeleteByIds extends AbstractMethod {
    
        /**
         * 注入自定义 MappedStatement
         *
         * @param mapperClass mapper 接口
         * @param modelClass  mapper 泛型
         * @param tableInfo   数据库表反射信息
         * @return MappedStatement
         */
        @Override
        public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
    
            // set语句
            String set = "set deleted_flag = null";
    
            SqlMethod sqlMethod = SqlMethod.LOGIC_DELETE_BATCH_BY_IDS;
            String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), set,
                    tableInfo.getKeyColumn(),
                    SqlScriptUtils.convertForeach("#{item}", COLLECTION, null, "item", COMMA),
                    tableInfo.getLogicDeleteSql(true, true));
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Object.class);
            return addUpdateMappedStatement(mapperClass, modelClass, "logicDeleteByIds", sqlSource);
        }
    
    }
    

    这个类主要是将表名&字段&筛选条件&值等内容构建成SqlSource,继而让MyBatisPlus进行处理

    2.继承DefaultSqlInjector添加自定义方法

    /**
     * 自定义 SqlInjector
     *
     * @author Brave
     * @version V1.0
     * @date 2021/5/21
     */
    @Component
    public class MyLogicSqlInjector extends DefaultSqlInjector {
    
        /**
         * 如果只需增加方法,保留MP自带方法
         * 可以super.getMethodList() 再add
         * @return
         */
        @Override
        public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
            List<AbstractMethod> methodList = super.getMethodList(mapperClass);
            methodList.add(new LogicDeleteById());
            methodList.add(new LogicDeleteByIds());
            return methodList;
        }
    
    }
    

    这里主要是将自定义的方法添加到通用方法的集合中

    3.创建通用Mapper继承BaseMapper

    /**
     * 通用Mapper
     *
     * @author Brave
     * @version V1.0
     * @date 2021/5/21
     */
    public interface SuperMapper<T> extends BaseMapper<T> {
    
        /**
         * 唯一主键情况下:根据id进行逻辑删除
         *
         * @param id
         * @return
         */
        int logicDeleteById(Serializable id);
    
        /**
         * 唯一主键情况下:根据ids进行逻辑删除
         *
         * @param idList
         * @return
         */
        int logicDeleteByIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
    }
    

    最后业务mapper继承SuperMapper,即可调用自定义方法

    四.结尾

    自此即可实现基于 MyBatis-Plus 解决数据库逻辑删除与唯一索引问题,我相信这个方法不是最优的,若是老哥们有更好的 idea 欢迎留言,可以一起讨论~

  • 相关阅读:
    2020-08-11 题目题解
    N皇后问题
    逆序对(模板)
    归并选择(模板)
    快速选择(模板)
    快速排序(模板)
    vuecli4+elementui实现面包屑
    vue-路由导航(守卫)那些事
    vue-vant中ImagePreview 图片预览正确的打开方式
    Selenium 对表格table处理
  • 原文地址:https://www.cnblogs.com/z-coding/p/14794737.html
Copyright © 2020-2023  润新知