Springboot結(jié)合Mybatis-Plus實現(xiàn)業(yè)務(wù)撤銷回滾功能
操作記錄表
CREATE TABLE `operation_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增id', `check_id` bigint(20) NOT NULL COMMENT '檢測id', `status` varchar(64) DEFAULT NULL COMMENT '檢測old狀態(tài)-檢測new狀態(tài)', `operation` varchar(255) NOT NULL COMMENT '操作事件', `deleted` tinyint(1) DEFAULT '0' COMMENT '是否刪除', `created_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間', `created_by_id` bigint(20) DEFAULT NULL COMMENT '創(chuàng)建者id', `created_by_name` varchar(255) DEFAULT NULL COMMENT '創(chuàng)建者名稱', `old_data` json DEFAULT NULL COMMENT '存儲操作前的舊數(shù)據(jù),JSON格式', `remark` varchar(255) DEFAULT NULL COMMENT '備注信息,用于記錄額外的操作說明', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='操作記錄';
操作記錄實體類
package com.applets.manager.core.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 操作記錄實體類
* </p>
*
* @author MybatisGenerator
* @since 2024-05-15
*/
@Data
@Builder
@TableName("check_status_record")
public class CheckStatusRecord implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 自增id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 檢測id(業(yè)務(wù)主鍵,可以自行修改)
*/
@TableField("check_id")
private Long checkId;
/**
* 檢測狀態(tài)
*/
@TableField("status")
private String status;
/**
* 操作事件
*/
@TableField("operation")
private String operation;
/**
* 是否刪除
*/
@TableField("deleted")
private Boolean deleted;
/**
* 創(chuàng)建時間
*/
@TableField("created_time")
private LocalDateTime createdTime;
/**
* 創(chuàng)建者id
*/
@TableField("created_by_id")
private Long createdById;
/**
* 創(chuàng)建者名稱
*/
@TableField("created_by_name")
private String createdByName;
/**
* 老數(shù)據(jù)
*/
@TableField("old_data")
private String oldData;
/**
* 備注
*/
@TableField("remark")
private String remark;
}
操作記錄表數(shù)據(jù)存儲實例

執(zhí)行業(yè)務(wù),存入操作記錄并且存入快照數(shù)據(jù)
此處的我存在oldData的字段類型為map,這樣oldData可以一次性存入多個修改記錄,就比如說如下方法enterEvaluationPrice修改了ylcheck和saleConditions

- key為YlCheck.class.getName() + “-” + “ylCheckMapper”+"-update"即(存入的對象全類名-容器中操作對應(yīng)類的mapper名稱-數(shù)據(jù)庫操作類型)
- value 為歷史快照數(shù)據(jù)
- 主要使用方法就是在做數(shù)據(jù)庫操作之前,把要操作的歷史數(shù)據(jù)查詢出來,json的方法序列化,存入操作記錄表中的oldData的字段,因為我這里是操作了多個表,所有我用map的方式存儲多個
@Override
@Transactional(rollbackFor = Exception.class)
public Result enterEvaluationPrice(EnterPriceReqVo req) {
Integer deliveryDays = req.getDeliveryDays();
Long id = req.getId();
BigDecimal acquisitionPrice = req.getAcquisitionPrice();
BigDecimal multiply = acquisitionPrice.multiply(new BigDecimal(10000));
try {
HashMap<String, Object> oldDataMap = new HashMap<>();
YlCheck ylCheck = ylCheckMapper.selectById(id);
if (ylCheck == null) {
return Result.failure(CommonResultStatus.VEHICLE_REPORT_NOT_FIND);
}
if (ObjectUtils.isNotEmpty(deliveryDays)) {
LambdaUpdateWrapper<SaleConditions> saleConditionsLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
LambdaUpdateWrapper<SaleConditions> qw = saleConditionsLambdaUpdateWrapper
.eq(SaleConditions::getCarId, ylCheck.getCheckCarId());
saleConditionsLambdaUpdateWrapper.set(SaleConditions::getDeliveryDays, deliveryDays);
List<SaleConditions> saleConditions = saleConditionsMapper.selectList(qw);
oldDataMap.put(SaleConditions.class.getName() + "-" + "saleConditionsMapper"+"-update", saleConditions); //存入歷史數(shù)據(jù)
saleConditionsMapper.update(null, saleConditionsLambdaUpdateWrapper);
}
oldDataMap.put(YlCheck.class.getName() + "-" + "ylCheckMapper"+"-update", ylCheck);//存入歷史數(shù)據(jù)
//其他業(yè)務(wù)代碼
ylCheckMapper.update(null, new LambdaUpdateWrapper<YlCheck>()
.eq(YlCheck::getId, ylCheck.getId())
.set(YlCheck::getStatus, to.name())//變更后的狀態(tài)
);
// 存入操作記錄
CheckStatusRecord checkStatusRecord = CheckStatusRecord.builder()
.checkId(ylCheck.getId())
.status(from.name() + "-" + to.name())
.operation(event.name())
.createdByName(localUserInfo.getUserName())
.createdTime(LocalDateTime.now())
.deleted(false)
.oldData(JSON.toJSONString(oldDataMap))
.build();
checkStatusRecordMapper.insert(checkStatusRecord);
//其他業(yè)務(wù)代碼
}
評估回滾請求對象
- 此處就傳了一個checkId即我這里的業(yè)務(wù)主鍵,可以自定義修改
package com.applets.manager.core.model.vo.req;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
@Data
@Accessors(chain = true)
@ApiModel("評估回滾請求對象")
public class YlCheckRollbackReqVo extends BaseReqVo implements Serializable {
private static final long serialVersionUID = 718144265818263634L;
/**
* 檢測id
*/
@ApiModelProperty("檢測id")
private Long checkId;
}
根據(jù)操作記錄回滾業(yè)務(wù)數(shù)據(jù)
rollback業(yè)務(wù)方法
- checkId 為某業(yè)務(wù)主鍵,可以自行改動
- 通過checkid查詢出對應(yīng)的操作記錄(我這里是查詢最新的,讀者可以自定義查詢條件)
- 查到操作記錄后將historyRecord.getOldData() JSON 字符串反序列化為 Map
- 遍歷 Map,反射獲取類和 Mapper
- 通過反射獲取類,提供給反序列化時使用
- 進(jìn)行反序列化
- 檢查數(shù)據(jù)是否是數(shù)組類型還是單個對象(兩種反序列化的方式不同)
- 根據(jù) type 判斷操作類型并進(jìn)行相應(yīng)的回滾(insert,update,delete)
- 如果是邏輯刪除的化就單獨(dú)使用update回滾即可,把對應(yīng)的刪除標(biāo)志修改一下即可
- 如果不是邏輯刪除的化insert回滾就需要反向執(zhí)行刪除操作;delete回滾就需要反向執(zhí)行插入操作.
- 內(nèi)部也需要檢查數(shù)據(jù)是否是數(shù)組類型還是單個對象(baseMapper處理數(shù)組類型和單個對象的方法不同)
@Override
@Transactional(rollbackFor = Exception.class)
public void rollback(YlCheckRollbackReqVo req) {
Long checkId = req.getCheckId();
if (ObjectUtils.isEmpty(checkId)){
throw new BusinessException("參數(shù)錯誤");
}
// 查詢保存的歷史數(shù)據(jù)最新的一條 (JSON 字符串)
LambdaQueryWrapper<CheckStatusRecord> qw = new LambdaQueryWrapper<>();
qw.eq(CheckStatusRecord::getCheckId, checkId)
.orderByDesc(CheckStatusRecord::getCreatedTime)
.last("LIMIT 1"); // 只查詢一條
CheckStatusRecord historyRecord = checkStatusRecordMapper.selectOne(qw);
if (historyRecord == null || historyRecord.getOldData() == null) {
throw new BusinessException("沒有找到可以回滾的歷史數(shù)據(jù)");
}
// 1. 將 JSON 字符串反序列化為 Map
Map<String, Object> oldDataMap = JSON.parseObject(historyRecord.getOldData(), Map.class);
// 2. 遍歷 Map,反射獲取類和 Mapper
for (Map.Entry<String, Object> entry : oldDataMap.entrySet()) {
// 解析 key,獲取類名和 Mapper 名,和操作類型
String[] keyParts = entry.getKey().split("-");
String className = keyParts[0];
String mapperName = keyParts[1];
String type = keyParts[2]; // 獲取操作類型
try {
// 通過反射獲取類
Class<?> clazz = Class.forName(className);
Object oldData;
// 3. 檢查數(shù)據(jù)是否是數(shù)組類型
if (entry.getValue() instanceof JSONArray) {
// 如果是數(shù)組類型,使用 parseArray 進(jìn)行反序列化
oldData = JSON.parseArray(JSON.toJSONString(entry.getValue()), clazz);
} else {
// 如果是單個對象,使用 parseObject 進(jìn)行反序列化
oldData = JSON.parseObject(JSON.toJSONString(entry.getValue()), clazz);
}
// 通過 Spring 應(yīng)用上下文獲取 Mapper 實例
Object mapper = applicationContext.getBean(mapperName);
// 4. 根據(jù) type 判斷操作類型并進(jìn)行相應(yīng)的回滾
if ("insert".equals(type)) {
// 如果是新增操作,回滾時執(zhí)行刪除操作
if (oldData instanceof List) {
for (Object item : (List<?>) oldData) {
if (mapper instanceof BaseMapper) {
String jsonItem = JSON.toJSONString(item);
JSONObject jsonObjectItem = JSON.parseObject(jsonItem);
BaseMapper baseMapper = (BaseMapper) mapper;
baseMapper.deleteById(jsonObjectItem.getLong("id")); // 刪除每個元素
}
}
} else {
if (mapper instanceof BaseMapper) {
String jsonOldData = JSON.toJSONString(oldData);
JSONObject jsonObjectOldData = JSON.parseObject(jsonOldData);
BaseMapper baseMapper = (BaseMapper) mapper;
baseMapper.deleteById(jsonObjectOldData.getLong("id")); // 刪除單個對象
}
}
} else if ("update".equals(type)) {
// 如果是更新操作,回滾時恢復(fù)原始數(shù)據(jù)
if (oldData instanceof List) {
for (Object item : (List<?>) oldData) {
if (mapper instanceof BaseMapper) {
BaseMapper baseMapper = (BaseMapper) mapper;
baseMapper.updateById(item); // 恢復(fù)每個元素
}
}
} else {
if (mapper instanceof BaseMapper) {
BaseMapper baseMapper = (BaseMapper) mapper;
baseMapper.updateById(oldData); // 恢復(fù)單個對象
}
}
} else if ("delete".equals(type)) {
// 如果是刪除操作,回滾時重新插入數(shù)據(jù)
if (oldData instanceof List) {
for (Object item : (List<?>) oldData) {
if (mapper instanceof BaseMapper) {
BaseMapper baseMapper = (BaseMapper) mapper;
baseMapper.insert(item); // 插入每個元素
}
}
} else {
if (mapper instanceof BaseMapper) {
BaseMapper baseMapper = (BaseMapper) mapper;
baseMapper.insert(oldData); // 插入單個對象
}
}
}
} catch (ClassNotFoundException e) {
log.error("回滾操作失敗: {}", e);
throw new BusinessException("回滾操作失敗: " + className);
}
}
}
業(yè)務(wù)回滾測試測試

到此這篇關(guān)于Springboot結(jié)合Mybatis-Plus實現(xiàn)業(yè)務(wù)撤銷回滾功能的文章就介紹到這了,更多相關(guān)Springboot Mybatis-Plus業(yè)務(wù)撤銷回滾內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud Gateway使用redis實現(xiàn)動態(tài)路由的方法
這篇文章主要介紹了SpringCloud Gateway使用redis實現(xiàn)動態(tài)路由的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01
java8 如何實現(xiàn)分組計算數(shù)量和計算總數(shù)
這篇文章主要介紹了java8 如何實現(xiàn)分組計算數(shù)量和計算總數(shù)的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
springboot 整合fluent mybatis的過程,看這篇夠了
這篇文章主要介紹了springboot 整合fluent mybatis的過程,配置數(shù)據(jù)庫連接創(chuàng)建數(shù)據(jù)庫的詳細(xì)代碼,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-08-08

