基于 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 欢迎留言,可以一起讨论~

posted @ 2021-05-21 17:26  天青色等烟雨~  阅读(2549)  评论(1编辑  收藏  举报