最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于SpringBoot + MyBatis-Plus高效實現數據變更記錄

 更新時間:2026年02月05日 09:20:05   作者:(farerboy)  
在應用開發(fā)過程中,在某些情況下,需要實現數據的變更記錄,以便于在未來進行數據操作變更的回溯,那有什么辦法高效實現數據操作變更記錄,而不需要一個一個地方去加呢?答案當然是有,今天小編就給大家介紹下如何基于SpringBoot + MyBatis-Plus高效實現數據的變更記錄,

前言

在應用開發(fā)過程中,在某些情況下,需要實現數據的變更記錄。以便于在未來進行數據操作變更的回溯。

常規(guī)的做法是在對應修改數據的 service 方法中手動記錄數據的變更。這種方式實現起來簡單,不用費什么腦子。但卻并不是最高效的。

public void updateOrder(Order order) {
    Order oldOrder = orderMapper.selectById(order.getId()); // 第一次查詢
    // ... 一堆業(yè)務邏輯 ...
    orderMapper.updateById(order); // 業(yè)務更新
    logService.saveChangeLog(oldOrder, order); 
}

試想一下,如果修改數據的 service 方法很多,或者項目在開發(fā)快結束的時候臨時決定需要額外添加數據變更記錄。此時一個個的手動添加無疑是一件費時費力切低效的操作。這樣做的后果是 嚴重侵入業(yè)務、重復代碼泛濫、事務邊界混亂、性能極差。

那有什么辦法高效實現數據操作變更記錄,而不需要一個一個地方去加呢?答案當然是有,今天小編就給大家介紹下如何高效實現數據的變更記錄。

實現原理

我們主要通過攔截器 + 事件驅動架構實現。通過 MyBatis-Plus 的攔截器插件攔截新增、修改、刪除操作,發(fā)布事件通知,由事件消費者進行消費,并記錄到變更記錄表中。

該方案的優(yōu)勢:

  • 高效,只需要編寫攔截器與事件消費者即可,不需要一個個方法修改,避免漏寫、寫錯的情況。
  • 無業(yè)務代碼侵入,完全解耦。
  • 高性能,因為是通過異步消息實現記錄,不會阻塞原有業(yè)務方法。

代碼實戰(zhàn)

第一步:自定義 MyBatis 攔截器(捕獲變更時機)

這是核心鉤子,用于在數據發(fā)生變更時發(fā)布一個事件,而非直接記錄。

@Intercepts({
    @Signature(type = Executor.class, method = "update", 
               args = {MappedStatement.class, Object.class})
})
@Component
@Slf4j
public class DataChangeInterceptor implements Interceptor {
    
    @Autowired
    private ApplicationEventPublisher eventPublisher; // 事件發(fā)布器
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        Object parameter = invocation.getArgs()[1];
        
        // 1. 僅攔截增刪改操作
        SqlCommandType commandType = ms.getSqlCommandType();
        if (commandType == SqlCommandType.INSERT || 
            commandType == SqlCommandType.UPDATE || 
            commandType == SqlCommandType.DELETE) {
            
            // 2. 獲取實體信息(MyBatis-Plus增強)
            if (parameter instanceof Map) {
                // 處理Wrapper等復雜參數,提取實體
            } else if (parameter != null) {
                // 3. 關鍵:在操作執(zhí)行前,根據ID查詢舊數據(僅UPDATE需要)
                Object oldData = null;
                if (commandType == SqlCommandType.UPDATE) {
                    oldData = fetchOldData(parameter, ms); // 根據主鍵查舊數據
                }
                
                // 4. 執(zhí)行原始SQL操作
                Object result = invocation.proceed();
                
                // 5. 異步發(fā)布變更事件(不阻塞主流程)
                if ((int)result > 0) {
                    DataChangeEvent event = new DataChangeEvent(
                            this, 
                            commandType, 
                            oldData, 
                            parameter, // 新數據
                            ThreadLocalUtil.getCurrentOperator() // 操作人從線程上下文獲取
                    );
                    eventPublisher.publishEvent(event); // 異步處理
                }
                return result;
            }
        }
        return invocation.proceed();
    }
    
    private Object fetchOldData(Object entity, MappedStatement ms) {
        // 利用MyBatis-Plus的TableInfo工具類,反射獲取主鍵值和實體類型
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entity.getClass());
        if (tableInfo != null) {
            Object idValue = tableInfo.getPropertyValue(entity, tableInfo.getKeyProperty());
            return sqlSessionTemplate.selectOne(ms.getId() + "_selectById", idValue); // 復用Mapper查詢
        }
        return null;
    }
}

第二步:設計領域事件(封裝變更內容)

事件對象應攜帶變更的所有元數據。

@Data
public class DataChangeEvent {
    private final SqlCommandType changeType; // 操作類型
    private final Object oldData;            // 變更前數據(JSON字符串或實體)
    private final Object newData;            // 變更后數據
    private final String operator;           // 操作人(從ThreadLocal或安全上下文獲?。?
    private final LocalDateTime changeTime = LocalDateTime.now();
    private final String entityClassName;    // 實體類名
    
    // 關鍵:將數據轉換為JSON,避免后續(xù)序列化問題
    public String getOldDataJson() {
        return JSON.toJSONString(oldData);
    }
    
    public String getNewDataJson() {
        return JSON.toJSONString(newData);
    }
}

第三步:異步事件監(jiān)聽器(真正執(zhí)行記錄)

這是性能關鍵,必須異步化,且要有降級策略。

@Component
@Slf4j
public class DataChangeEventListener {
    
    @Async("dataChangeExecutor") // 指定獨立線程池,不占用業(yè)務資源
    @EventListener
    @Transactional(propagation = Propagation.REQUIRES_NEW) // 新事務,與業(yè)務事務分離
    public void handleDataChangeEvent(DataChangeEvent event) {
        try {
            // 1. 構建變更記錄實體
            ChangeLog changeLog = new ChangeLog();
            changeLog.setEntityClass(event.getEntityClassName());
            changeLog.setChangeType(event.getChangeType().name());
            changeLog.setOldData(event.getOldDataJson());
            changeLog.setNewData(event.getNewDataJson());
            changeLog.setOperator(event.getOperator());
            
            // 2. 計算具體變更的字段(精細化記錄)
            if (event.getChangeType() == SqlCommandType.UPDATE) {
                Map<String, Object> fieldChanges = DiffUtil.diff(
                    event.getOldDataJson(), 
                    event.getNewDataJson()
                );
                changeLog.setChangedFields(JSON.toJSONString(fieldChanges));
            }
            
            // 3. 持久化到數據庫(或發(fā)送到消息隊列)
            changeLogMapper.insert(changeLog);
            
        } catch (Exception e) {
            // 4. 降級策略:記錄失敗時,至少打印日志或存入死信隊列
            log.error("數據變更記錄失敗,事件內容:{}", JSON.toJSONString(event), e);
            // 可在此處將事件發(fā)送至Redis或Kafka進行重試
        }
    }
}

第四步:關鍵配置與優(yōu)化

1. 獨立線程池

防止監(jiān)聽器阻塞影響主業(yè)務。

spring:
  task:
    execution:
      pool:
        data-change-executor:
          core-size: 2
          max-size: 5
          queue-capacity: 1000 # 緩沖區(qū),抗瞬時峰值

2. 變更日志表設計

CREATE TABLE `change_log` (
  `id` bigint NOT NULL COMMENT '主鍵',
  `entity_class` varchar(255) NOT NULL COMMENT '實體類名',
  `entity_id` varchar(64) NOT NULL COMMENT '實體ID', -- 從數據中提取
  `change_type` varchar(10) NOT NULL COMMENT '操作類型',
  `changed_fields` json DEFAULT NULL COMMENT '變更的字段(JSON)', -- 快速定位
  `old_data` json DEFAULT NULL COMMENT '完整舊數據',
  `new_data` json DEFAULT NULL COMMENT '完整新數據',
  `operator` varchar(64) DEFAULT NULL COMMENT '操作人',
  `create_time` datetime NOT NULL COMMENT '創(chuàng)建時間',
  PRIMARY KEY (`id`),
  KEY `idx_entity` (`entity_class`,`entity_id`), -- 按實體查詢
  KEY `idx_time` (`create_time`) -- 按時間范圍查詢
) COMMENT='數據變更記錄表';

3. 操作人信息自動注入

public class OperatorContext {
    private static final ThreadLocal<String> CURRENT_OPERATOR = new ThreadLocal<>();
    
    public static void setOperator(String operatorId) {
        CURRENT_OPERATOR.set(operatorId);
    }
    // 在攔截器或Spring Security過濾器中設置
}

總結

  • 業(yè)務零侵入:業(yè)務開發(fā)無需關心記錄邏輯,專注核心功能。
  • 性能無損:主流程僅增加一次事件發(fā)布(內存操作),記錄過程完全異步化。
  • 數據完整:通過攔截器在執(zhí)行前后捕獲數據,保證變更前后的完整快照。
  • 架構擴展:事件驅動架構允許你輕松擴展,如:
    1. 將變更記錄同時寫入Elasticsearch供快速檢索。
    2. 將重大變更發(fā)送消息通知相關系統(tǒng)。
    3. 實現操作回放功能(通過舊數據+新數據反向操作)。

以上就是基于SpringBoot + MyBatis-Plus高效實現數據變更記錄的詳細內容,更多關于SpringBoot MyBatis-Plus數據變更記錄的資料請關注腳本之家其它相關文章!

相關文章

  • 詳解java中jvm虛擬機棧的作用

    詳解java中jvm虛擬機棧的作用

    這篇文章主要介紹了jvm虛擬機棧的作用,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-08-08
  • Java設計模式之Adapter適配器模式

    Java設計模式之Adapter適配器模式

    這篇文章主要為大家詳細介紹了Java設計模式之Adapter適配器模式,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • IDEA2023隱藏.idea和.iml文件的實現步驟

    IDEA2023隱藏.idea和.iml文件的實現步驟

    IDEA新建項目會自動生成一個.idea文件夾和.iml文件,本文主要介紹了IDEA2023隱藏.idea和.iml文件的實現步驟,具有一定的參考價值,感興趣的可以了解一下
    2023-09-09
  • Java Web開發(fā)防止多用戶重復登錄的完美解決方案

    Java Web開發(fā)防止多用戶重復登錄的完美解決方案

    在web項目開發(fā)中,很多情況下都可以讓同一個賬號信息在不同的登錄入口登錄很多次,這樣子做的不是很完善。一般解決這種情況有兩種解決方案,小編呢主要以第二種方式給大家介紹具體的實現方法,對java web 防止多用戶重復登錄的解決方案感興趣的朋友一起看看吧
    2016-11-11
  • Java 判斷數組是否相等的方法示例

    Java 判斷數組是否相等的方法示例

    這篇文章主要介紹了Java 判斷數組是否相等的方法示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-09-09
  • Java中volatile?的作用

    Java中volatile?的作用

    這篇文章主要介紹了Java中volatile?的作用,volatile是Java并發(fā)編程的重要組成部分,主要作用是保證內存的可見性和禁止指令重排序,下文更多對volatile作用的介紹,需要的小伙伴可以參考一下
    2022-05-05
  • 解決IntelliJ IDEA創(chuàng)建spring boot無法連接http://start.spring.io/問題

    解決IntelliJ IDEA創(chuàng)建spring boot無法連接http://start.spring.io/問題

    這篇文章主要介紹了解決IntelliJ IDEA創(chuàng)建spring boot無法連接http://start.spring.io/問題,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-08-08
  • java_object的簡單使用詳解

    java_object的簡單使用詳解

    下面小編就為大家?guī)硪黄猨ava_object的簡單使用詳解。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-06-06
  • 在項目中直接使用hystrix的流程分析

    在項目中直接使用hystrix的流程分析

    最近由于一些背景原因,需要在項目中需要對接口進行限流。所以就考慮到了直接使用Hystrix,但是呢,又不想直接使用SpringCloud,而是直接引入原生,現在發(fā)現挺好用的,所以記錄下來,需要的朋友參考下吧
    2022-06-06
  • NoHttpResponseException問題排查解決記錄分析

    NoHttpResponseException問題排查解決記錄分析

    這篇文章主要為大家介紹了NoHttpResponseException問題排查解決記錄分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08

最新評論

景东| 恩平市| 叶城县| 平山县| 泰顺县| 巴林右旗| 荔浦县| 宿州市| 甘德县| 务川| 富平县| 鄂尔多斯市| 石楼县| 陆良县| 龙岩市| 马公市| 洛南县| 定西市| 靖江市| 大丰市| 米林县| 楚雄市| 格尔木市| 嘉兴市| 孝义市| 松溪县| 新巴尔虎右旗| 徐州市| 靖远县| 泰安市| 阿合奇县| 麦盖提县| 沙田区| 莎车县| 甘谷县| 灌南县| 四平市| 昌黎县| 应用必备| 壶关县| 崇信县|