Mybatis-Plus同時(shí)使用邏輯刪除和唯一索引的問(wèn)題及解決辦法(報(bào)數(shù)據(jù)重復(fù)Duplicate entry的問(wèn)題)
1 問(wèn)題背景
在開(kāi)發(fā)中,我們經(jīng)常會(huì)有邏輯刪除和唯一索引同時(shí)使用的情況。但當(dāng)使用mybatis plus時(shí),如果同時(shí)使用邏輯刪除和唯一索引,會(huì)報(bào)數(shù)據(jù)重復(fù)Duplicate entry的問(wèn)題。
舉例來(lái)說(shuō),有表user,建立唯一索引(user_name,is_del)
CREATE TABLE `user` ( `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Id', `user_name` varchar(64) DEFAULT NULL COMMENT '用戶(hù)名', `is_del` bigint(13) DEFAULT '0' COMMENT '邏輯刪除標(biāo)識(shí)', PRIMARY KEY (`id`), UNIQUE KEY `unique_user_name` (`user_name`) ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4;
如果我們插入一條數(shù)據(jù)user_name='張三’的數(shù)據(jù),然后再刪除它,這時(shí)數(shù)據(jù)表中存在一條記錄,如下圖:

這時(shí)如果再插入一條’張三’,雖然之前的一條記錄已經(jīng)被邏輯刪除,但是唯一索引只建在了user_name上,所以這時(shí)會(huì)報(bào)數(shù)據(jù)重復(fù)的錯(cuò)誤
2 第一次改進(jìn)
我們把唯一索引的組合增加is_del字段
UNIQUE KEY `unique_user_name_is_del` (`user_name`,`is_del`)
這下可以再次插入’張三’這條數(shù)據(jù),插入后如下圖

但是如果第二次刪除’張三’,則還是會(huì)報(bào)錯(cuò),因?yàn)橐呀?jīng)有一條[‘張三’,1]的數(shù)據(jù),當(dāng)程序想把另一條’zhangsan’的is_del字段值為1時(shí),會(huì)因?yàn)閿?shù)據(jù)重復(fù)失敗!
3 第二次改進(jìn)
此時(shí)應(yīng)該如何改進(jìn)呢,可以在user表中增加一個(gè)del_version字段,用來(lái)把已經(jīng)刪除的數(shù)據(jù)加上版本號(hào),然后將這個(gè)字段也加入唯一索引中
CREATE TABLE `user` ( `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Id', `user_name` varchar(64) DEFAULT NULL COMMENT '用戶(hù)名', `del_version` bigint(11) DEFAULT '0' COMMENT '版本標(biāo)識(shí),用于邏輯刪除', `is_del` bigint(13) DEFAULT '0' COMMENT '邏輯刪除標(biāo)識(shí)', PRIMARY KEY (`id`), UNIQUE KEY `unique_user_name_is_del_del_version` (`user_name`,`is_del`,`del_version`) ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4;
未刪除的數(shù)據(jù)del_version=0,已刪除的數(shù)據(jù)del_version修改成這條記錄的id(自增id全局唯一),這樣既可以保證無(wú)法多次插入同名的數(shù)據(jù),又可以滿(mǎn)足數(shù)據(jù)可以多次刪除的情況
例如,我們兩次刪除同樣數(shù)據(jù)后,再重新插入,這時(shí)數(shù)據(jù)表中的數(shù)據(jù)如下:

4 代碼解決方案
我們使用mybatis plus提供的工具生成代碼,這時(shí)所有的service層接口都會(huì)繼承 IService 這個(gè)接口,這個(gè)接口有很多默認(rèn)方法,實(shí)現(xiàn)了對(duì)數(shù)據(jù)庫(kù)的操作。
我們的思路是,新建一個(gè)IBaseService接口,繼承IService接口。在這個(gè)IBaseService接口中,重寫(xiě)所有和刪除相關(guān)的方法,在其中設(shè)置【del_version】=【自增id】。而原來(lái)的所有service層接口,不再繼承IService,而是繼承我們新的IBaseService。
這樣就解決了邏輯刪除和唯一索引共用的問(wèn)題,IBaseService具體代碼如下:
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.llbqhh.dao.entity.BaseDO;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* @Author wuKeFan
* @Date 2023/11/08
* @Description: 邏輯刪除前先更新版本號(hào)
*/
public interface IBaseService<T extends BaseDO> extends IService<T> {
/**
* 根據(jù) ID 刪除
*
* @param id 主鍵ID
*/
@Override
default boolean removeById(Serializable id) {
T objDO = getBaseMapper().selectById(id);
return beforeDelete(objDO) && SqlHelper.retBool(getBaseMapper().deleteById(id));
}
/**
* 刪除對(duì)象前,先修改其版本號(hào)
* @param objDO
* @return
*/
default boolean beforeDelete(T objDO) {
if (Objects.isNull(objDO)) {
return false;
}
// 邏輯刪除前先更新版本號(hào)
objDO.setDelVersion(objDO.getId());
return SqlHelper.retBool(getBaseMapper().updateById(objDO));
}
/**
* 根據(jù) columnMap 條件,刪除記錄
*
* @param columnMap 表字段 map 對(duì)象
*/
@Override
default boolean removeByMap(Map<String, Object> columnMap) {
throw new RuntimeException("不支持的數(shù)據(jù)庫(kù)刪除操作");
}
/**
* 根據(jù) entity 條件,刪除記錄
*
* @param queryWrapper 實(shí)體包裝類(lèi) {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
@Override
default boolean remove(Wrapper<T> queryWrapper) {
List<T> objDOS = getBaseMapper().selectList(queryWrapper);
if (CollectionUtils.isNotEmpty(objDOS)) {
objDOS.forEach(objDO -> beforeDelete(objDO));
}
return SqlHelper.retBool(getBaseMapper().delete(queryWrapper));
}
/**
* 刪除(根據(jù)ID 批量刪除)
*
* @param idList 主鍵ID列表
*/
@Override
default boolean removeByIds(Collection<? extends Serializable> idList) {
if (CollectionUtils.isEmpty(idList)) {
return false;
}
List<T> objDOS = getBaseMapper().selectBatchIds(idList);
if (CollectionUtils.isNotEmpty(objDOS)) {
objDOS.forEach(objDO -> beforeDelete(objDO));
}
return SqlHelper.retBool(getBaseMapper().deleteBatchIds(idList));
}
}到此這篇關(guān)于Mybatis-Plus同時(shí)使用邏輯刪除和唯一索引的問(wèn)題及解決辦法的文章就介紹到這了,更多相關(guān)Mybatis-Plus邏輯刪除和唯一索引內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- MyBatis-Plus實(shí)現(xiàn)邏輯刪除功能解析
- Mybatis-Plus邏輯刪除的用法詳解
- mybatis-plus實(shí)現(xiàn)邏輯刪除的示例代碼
- MyBatis-Plus解決邏輯刪除與唯一索引的問(wèn)題
- MyBatis-Plus實(shí)現(xiàn)邏輯刪除的示例代碼
- MyBatis-Plus的物理刪除和邏輯刪除(使用場(chǎng)景)
- 關(guān)于mybatis-plus邏輯刪除無(wú)效的問(wèn)題
- MyBatis-Plus之邏輯刪除的實(shí)現(xiàn)
- mybatis-plus3.4.0邏輯刪除報(bào)錯(cuò)的解決
- mybatis-plus邏輯刪除與唯一約束沖突問(wèn)題
相關(guān)文章
Spring覆蓋容器中Bean的注解如何實(shí)現(xiàn)@OverrideBean
文章介紹了在項(xiàng)目開(kāi)發(fā)中如何通過(guò)偷梁換柱的方式重寫(xiě)Spring容器中的內(nèi)置Bean,并指出了需要注意的兩點(diǎn):1. 對(duì)應(yīng)的Bean應(yīng)基于接口注入;2. 如果不是基于接口注入,可以使用同包名同類(lèi)名的方式重寫(xiě)(可能存在潛在問(wèn)題,不推薦),文章還強(qiáng)調(diào)了“基于接口編程”的好處2025-01-01
Java壓縮和解壓縮ZIP文件實(shí)戰(zhàn)案例
這篇文章主要給大家介紹了關(guān)于Java壓縮和解壓縮ZIP文件的相關(guān)資料,ZIP是一種較為常見(jiàn)的壓縮形式,最近項(xiàng)目中遇到了再Java中壓縮和解壓縮zip文件的需求,特此分享給大家,需要的朋友可以參考下2023-07-07
Java泛型T,E,K,V,N,?與Object區(qū)別和含義
Java?泛型(generics)是?JDK?5?中引入的一個(gè)新特性,?泛型提供了編譯時(shí)類(lèi)型安全檢測(cè)機(jī)制,該機(jī)制允許程序員在編譯時(shí)檢測(cè)到非法的類(lèi)型。本文將詳細(xì)講講Java泛型T、E、K、V、N、?和Object區(qū)別和含義,需要發(fā)可以參考一下2022-03-03
Java實(shí)現(xiàn)定時(shí)任務(wù)的方法詳解
大家都用過(guò)鬧鐘,鬧鐘可以說(shuō)是一種定時(shí)任務(wù)。那么,在?Java?中,如何實(shí)現(xiàn)這樣的功能呢?即如何實(shí)現(xiàn)定時(shí)任務(wù)呢?本文就來(lái)詳細(xì)和大家聊聊2022-10-10
java如何實(shí)現(xiàn)postman中用x-www-form-urlencoded參數(shù)的請(qǐng)求
在Java開(kāi)發(fā)中,模擬Postman發(fā)送x-www-form-urlencoded類(lèi)型的請(qǐng)求是一個(gè)常見(jiàn)需求,本文主要介紹了如何在Java中實(shí)現(xiàn)這一功能,首先,需要通過(guò)導(dǎo)入http-client包來(lái)創(chuàng)建HTTP客戶(hù)端,接著,利用該客戶(hù)端發(fā)送Post請(qǐng)求2024-09-09
springboot遠(yuǎn)程執(zhí)行服務(wù)器指令
這篇文章主要介紹了springboot遠(yuǎn)程執(zhí)行服務(wù)器指令,本例是java遠(yuǎn)程連接到服務(wù)器,去抓取查詢(xún)kubesphere中的etcd日志,并返回,需要的朋友可以參考下2023-09-09

