一.問題描述
在業務中經常會有這樣一種需求即某字段不能重復,例如用戶表的手機又或者是身份證.而遇到這種問題一般兩種處理方法,一:插入或修改之前先進行一次查詢判斷是否存在該記錄;二:利用數據庫唯一索引約束保證數據的唯一性.
但如果用方法一會有兩個缺點,一是低效率,二是在高並發的系統中,很難保證其可靠性,故我們在這使用第二中方法,也就是設置唯一索引.設置唯一索引本身是沒問題的,但目前需要基於邏輯刪除之上整合.
故要思考如何才能讓兩者之間完美的整合在一起

二.解決方法
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 歡迎留言,可以一起討論~
