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

基于MyBatis攔截器實(shí)現(xiàn)數(shù)據(jù)變更記錄的技術(shù)方案

 更新時(shí)間:2026年04月20日 09:28:03   作者:蒙眼過河  
本文介紹了在日常應(yīng)用開發(fā)中記錄業(yè)務(wù)數(shù)據(jù)變更的需求與實(shí)現(xiàn)方法,設(shè)計(jì)了整體架構(gòu),實(shí)現(xiàn)自定義注解、數(shù)據(jù)變更記錄實(shí)體、事務(wù)同步機(jī)制和攔截器注冊(cè)方案,并通過示例展示了使用方法及常見問題解決方案,最后總結(jié)了方案的優(yōu)勢(shì)和注意事項(xiàng),需要的朋友可以參考下

一、背景與需求

在日常應(yīng)用開發(fā)中,數(shù)據(jù)變更記錄是一個(gè)常見需求。我們需要記錄業(yè)務(wù)數(shù)據(jù)的每一次變更,包括:

  • 字段級(jí)別的變更記錄
  • 變更前后的值
  • 變更人、變更時(shí)間
  • 支持INSERT/UPDATE/DELETE操作
  • 支持單條和批量操作
  • 確保數(shù)據(jù)一致性和準(zhǔn)確性

二、整體架構(gòu)設(shè)計(jì)

2.1 核心組件

┌─────────────────────────────────────┐
│         Controller/Service層         │
└───────────────┬─────────────────────┘
                │
                ▼
┌─────────────────────────────────────┐
│         Mapper接口(帶注解)          │
│    @DataChangeLog(businessType)     │
└───────────────┬─────────────────────┘
                │
                ▼
┌─────────────────────────────────────┐
│      MyBatis Executor.update()       │
│          攔截器攔截點(diǎn)                 │
└───────────────┬─────────────────────┘
                │
                ▼
┌─────────────────────────────────────┐
│     ImprovedDataChangeInterceptor    │
│  ┌─────────────────────────────┐   │
│  │ 1. 獲取注解/判斷是否需要記錄 │   │
│  │ 2. 查詢?cè)紨?shù)據(jù)              │   │
│  │ 3. 執(zhí)行SQL操作               │   │
│  │ 4. 事務(wù)提交后查詢新數(shù)據(jù)       │   │
│  │ 5. 對(duì)比差異并保存記錄         │   │
│  └─────────────────────────────┘   │
└───────────────┬─────────────────────┘
                │
                ▼
┌─────────────────────────────────────┐
│         sys_data_change_log          │
│          數(shù)據(jù)變更記錄表               │
└─────────────────────────────────────┘

2.2 數(shù)據(jù)表設(shè)計(jì)

CREATE TABLE sys_data_change_log (
    id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
    business_type varchar(50) NOT NULL COMMENT '業(yè)務(wù)類型',
    business_id varchar(50) NOT NULL COMMENT '業(yè)務(wù)數(shù)據(jù)ID',
    field_name varchar(50) NOT NULL COMMENT '字段名稱',
    field_comment varchar(100) DEFAULT NULL COMMENT '字段注釋',
    old_value text COMMENT '變更前值',
    new_value text COMMENT '變更后值',
    change_type varchar(20) DEFAULT 'UPDATE' COMMENT '變更類型',
    change_by varchar(50) NOT NULL COMMENT '變更人',
    change_time datetime NOT NULL COMMENT '變更時(shí)間',
    PRIMARY KEY (id),
    KEY idx_business (business_type, business_id),
    KEY idx_change_time (change_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='數(shù)據(jù)變更記錄表';

三、核心代碼實(shí)現(xiàn)

3.1 自定義注解

package com.demo.framework.aspectj.lang.annotation;
import java.lang.annotation.*;
/**
 * 數(shù)據(jù)變更記錄注解
 * 標(biāo)記在Mapper方法上,表示該方法需要記錄數(shù)據(jù)變更
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataChangeLog {
    /**
     * 業(yè)務(wù)類型
     */
    String businessType() default "";
    /**
     * 是否記錄詳情(字段級(jí)別)
     */
    boolean detail() default true;
    /**
     * 忽略的字段
     */
    String[] ignoreFields() default {"createTime", "updateTime", "createBy", "updateBy"};
}

3.2 數(shù)據(jù)變更記錄實(shí)體

package com.demo.system.domain;

import com.demo.common.annotation.Excel;
import java.util.Date;

/**
 * 數(shù)據(jù)變更記錄對(duì)象 sys_data_change_log
 */
public class SysDataChangeLog {
    private static final long serialVersionUID = 1L;
    
    /** 主鍵ID */
    private Long id;
    
    /** 業(yè)務(wù)類型 */
    @Excel(name = "業(yè)務(wù)類型")
    private String businessType;
    
    /** 業(yè)務(wù)數(shù)據(jù)ID */
    @Excel(name = "業(yè)務(wù)數(shù)據(jù)ID")
    private String businessId;
    
    /** 字段名稱 */
    @Excel(name = "字段名稱")
    private String fieldName;
    
    /** 字段注釋 */
    @Excel(name = "字段注釋")
    private String fieldComment;
    
    /** 變更前值 */
    @Excel(name = "變更前值")
    private String oldValue;
    
    /** 變更后值 */
    @Excel(name = "變更后值")
    private String newValue;
    
    /** 變更類型 */
    @Excel(name = "變更類型")
    private String changeType;
    
    /** 變更人 */
    @Excel(name = "變更人")
    private String changeBy;
    
    /** 變更時(shí)間 */
    @Excel(name = "變更時(shí)間", dateFormat = "yyyy-MM-dd HH:mm:ss")
    private Date changeTime;
    
    // getters and setters...
}

四、數(shù)據(jù)提交后再記錄日志的方案

4.1 事務(wù)同步機(jī)制

使用Spring的TransactionSynchronization實(shí)現(xiàn)事務(wù)提交后執(zhí)行記錄操作:

/**
 * 注冊(cè)事務(wù)提交后的任務(wù)
 * 核心:確保數(shù)據(jù)真正寫入數(shù)據(jù)庫后才記錄變更日志
 */
private void registerAfterCommitTask(Runnable task) {
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
        // 事務(wù)中:注冊(cè)事務(wù)同步器,事務(wù)提交后執(zhí)行
        TransactionSynchronizationManager.registerSynchronization(
            new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    task.run();
                }
                
                @Override
                public void afterCompletion(int status) {
                    if (status == STATUS_ROLLED_BACK) {
                        log.debug("事務(wù)回滾,不記錄變更");
                    }
                }
            }
        );
    } else {
        // 無事務(wù):直接執(zhí)行
        log.debug("無事務(wù)環(huán)境,直接執(zhí)行");
        task.run();
    }
}

4.2 更新操作處理流程

/**
 * 處理更新操作
 * 三步走策略:
 * 1. 更新前查詢?cè)紨?shù)據(jù)
 * 2. 執(zhí)行更新操作
 * 3. 事務(wù)提交后查詢新數(shù)據(jù)并對(duì)比記錄
 */
private Object handleUpdate(Invocation invocation, MappedStatement ms, 
                            Object parameter, DataChangeLog dataChangeLog) throws Throwable {
    log.debug("處理更新操作,業(yè)務(wù)類型:{}", dataChangeLog.businessType());
    
    // 1. 更新前查詢?cè)紨?shù)據(jù)
    Object beforeData = queryDataById(parameter, ms);
    log.debug("更新前數(shù)據(jù):{}", JSON.toJSONString(beforeData));
    
    // 2. 緩存操作信息
    OperationInfo opInfo = new OperationInfo();
    opInfo.setBusinessType(dataChangeLog.businessType());
    opInfo.setIgnoreFields(dataChangeLog.ignoreFields());
    opInfo.setParameter(parameter);
    opInfo.setSqlCommandType(SqlCommandType.UPDATE);
    opInfo.setMapperId(ms.getId());
    opInfo.setBeforeData(beforeData);
    OPERATION_INFO_THREAD_LOCAL.set(opInfo);
    
    // 3. 執(zhí)行更新操作
    Object result = invocation.proceed();
    
    // 4. 如果更新成功,在事務(wù)提交后處理變更記錄
    if (Integer.parseInt(result.toString()) > 0) {
        registerAfterCommitTask(() -> processUpdateAfterCommit(opInfo));
    }
    
    return result;
}

/**
 * 事務(wù)提交后處理更新操作
 */
private void processUpdateAfterCommit(OperationInfo opInfo) {
    try {
        // 1. 更新后查詢最新數(shù)據(jù)
        Object afterData = queryDataById(opInfo.getParameter(), opInfo.getMapperId());
        log.debug("更新后數(shù)據(jù):{}", JSON.toJSONString(afterData));
        
        if (opInfo.getBeforeData() != null && afterData != null) {
            // 2. 對(duì)比數(shù)據(jù)差異
            List<SysDataChangeLog> changeLogs = compareData(
                opInfo.getBeforeData(), 
                afterData, 
                opInfo.getBusinessType(),
                opInfo.getIgnoreFields()
            );
            
            // 3. 批量保存變更記錄
            if (!changeLogs.isEmpty()) {
                dataChangeLogService.insertBatch(changeLogs);
                log.info("保存{}條更新變更記錄", changeLogs.size());
            }
        }
    } catch (Exception e) {
        log.error("處理更新后變更記錄失敗", e);
    }
}

4.3 數(shù)據(jù)對(duì)比工具方法

/**
 * 對(duì)比數(shù)據(jù)差異
 * 支持日期、數(shù)字類型的精確比較
 */
private List<SysDataChangeLog> compareData(Object beforeData, Object afterData, 
                                           String businessType, String[] ignoreFields) {
    List<SysDataChangeLog> changeLogs = new ArrayList<>();
    
    try {
        Map<String, Object> beforeMap = convertToMap(beforeData);
        Map<String, Object> afterMap = convertToMap(afterData);
        
        String businessId = extractBusinessId(afterMap);
        
        // 遍歷更新后的數(shù)據(jù)
        for (Map.Entry<String, Object> entry : afterMap.entrySet()) {
            String fieldName = entry.getKey();
            
            // 忽略指定字段
            if (shouldIgnore(fieldName, ignoreFields)) {
                continue;
            }
            
            Object beforeValue = beforeMap.get(fieldName);
            Object afterValue = entry.getValue();
            
            // 比較值是否變化
            if (!isValueEquals(beforeValue, afterValue)) {
                SysDataChangeLog changeLog = createChangeLog(
                    businessType, businessId, fieldName,
                    getFieldComment(afterData, fieldName),
                    beforeValue, afterValue, "UPDATE"
                );
                changeLogs.add(changeLog);
            }
        }
        
    } catch (Exception e) {
        log.error("對(duì)比數(shù)據(jù)差異失敗", e);
    }
    
    return changeLogs;
}

/**
 * 判斷兩個(gè)值是否相等(處理各種數(shù)據(jù)類型)
 */
private boolean isValueEquals(Object v1, Object v2) {
    if (v1 == null && v2 == null) return true;
    if (v1 == null || v2 == null) return false;
    
    // 處理日期類型
    if (v1 instanceof Date && v2 instanceof Date) {
        return ((Date) v1).getTime() == ((Date) v2).getTime();
    }
    
    // 處理數(shù)字類型(避免精度問題)
    if (v1 instanceof Number && v2 instanceof Number) {
        return new BigDecimal(v1.toString())
            .compareTo(new BigDecimal(v2.toString())) == 0;
    }
    
    return v1.equals(v2);
}

五、攔截器未注冊(cè)到Spring容器的解決方案

5.1 問題分析

在實(shí)際項(xiàng)目中,經(jīng)常遇到攔截器配置了@Component注解,但MyBatis并沒有自動(dòng)注冊(cè)攔截器的情況。主要原因:

  1. MyBatis自動(dòng)配置順序問題:攔截器注冊(cè)時(shí)機(jī)過早
  2. 多數(shù)據(jù)源配置:需要為每個(gè)SqlSessionFactory手動(dòng)注冊(cè)
  3. 自定義MyBatis配置:覆蓋了默認(rèn)配置

5.2 解決方案一:手動(dòng)注冊(cè)攔截器(推薦)

package com.demo.framework.config;

import com.demo.framework.interceptor.ImprovedDataChangeInterceptor;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import javax.annotation.PostConstruct;
import java.util.List;

/**
 * 攔截器手動(dòng)注冊(cè)配置
 * 解決攔截器未自動(dòng)注冊(cè)到MyBatis的問題
 */
@Configuration
public class InterceptorManualConfig {
    
    private static final org.slf4j.Logger log = 
        org.slf4j.LoggerFactory.getLogger(InterceptorManualConfig.class);
    
    @Autowired
    private List<SqlSessionFactory> sqlSessionFactories;
    
    @Autowired
    @Lazy // 避免循環(huán)依賴
    private ImprovedDataChangeInterceptor dataChangeInterceptor;
    
    @PostConstruct
    public void manualRegisterInterceptor() {
        log.info("========== 開始手動(dòng)注冊(cè)數(shù)據(jù)變更攔截器 ==========");
        log.info("發(fā)現(xiàn) {} 個(gè)SqlSessionFactory", sqlSessionFactories.size());
        
        for (int i = 0; i < sqlSessionFactories.size(); i++) {
            SqlSessionFactory sqlSessionFactory = sqlSessionFactories.get(i);
            org.apache.ibatis.session.Configuration configuration = 
                sqlSessionFactory.getConfiguration();
            
            // 檢查是否已經(jīng)注冊(cè)
            boolean alreadyRegistered = false;
            for (Interceptor interceptor : configuration.getInterceptors()) {
                if (interceptor instanceof ImprovedDataChangeInterceptor) {
                    alreadyRegistered = true;
                    log.info("SqlSessionFactory[{}] 攔截器已存在,跳過注冊(cè)", i);
                    break;
                }
            }
            
            if (!alreadyRegistered) {
                log.info("注冊(cè)攔截器到 SqlSessionFactory[{}]: {}", 
                    i, sqlSessionFactory);
                configuration.addInterceptor(dataChangeInterceptor);
            }
        }
        
        // 驗(yàn)證注冊(cè)結(jié)果
        verifyInterceptorRegistration();
        log.info("========== 攔截器手動(dòng)注冊(cè)完成 ==========");
    }
    
    /**
     * 驗(yàn)證攔截器注冊(cè)結(jié)果
     */
    private void verifyInterceptorRegistration() {
        for (int i = 0; i < sqlSessionFactories.size(); i++) {
            SqlSessionFactory sqlSessionFactory = sqlSessionFactories.get(i);
            List<Interceptor> interceptors = 
                sqlSessionFactory.getConfiguration().getInterceptors();
            
            log.info("SqlSessionFactory[{}] 已注冊(cè)攔截器數(shù)量: {}", i, interceptors.size());
            for (Interceptor interceptor : interceptors) {
                log.info("  - {}", interceptor.getClass().getSimpleName());
            }
        }
    }
}

5.3 解決方案二:使用MyBatis配置類

package com.demo.framework.config;

import com.demo.framework.interceptor.ImprovedDataChangeInterceptor;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

/**
 * 通過MyBatis配置類注冊(cè)攔截器
 */
@Configuration
@AutoConfigureAfter(MybatisAutoConfiguration.class)
public class MyBatisInterceptorConfig {
    
    @Autowired
    private ApplicationContext applicationContext;
    
    @Autowired
    private ImprovedDataChangeInterceptor dataChangeInterceptor;
    
    @PostConstruct
    public void init() {
        // 獲取所有SqlSessionFactory
        String[] sqlSessionFactoryNames = 
            applicationContext.getBeanNamesForType(SqlSessionFactory.class);
        
        for (String name : sqlSessionFactoryNames) {
            SqlSessionFactory sqlSessionFactory = 
                applicationContext.getBean(name, SqlSessionFactory.class);
            
            // 獲取當(dāng)前的攔截器列表
            org.apache.ibatis.session.Configuration configuration = 
                sqlSessionFactory.getConfiguration();
            
            // 添加攔截器(如果不存在)
            boolean exists = false;
            for (Interceptor interceptor : configuration.getInterceptors()) {
                if (interceptor instanceof ImprovedDataChangeInterceptor) {
                    exists = true;
                    break;
                }
            }
            
            if (!exists) {
                configuration.addInterceptor(dataChangeInterceptor);
            }
        }
    }
}

5.4 解決方案三:驗(yàn)證攔截器是否注冊(cè)

package com.demo.framework.config;

import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.List;

/**
 * 啟動(dòng)后驗(yàn)證攔截器注冊(cè)情況
 */
@Component
public class InterceptorVerifyRunner implements CommandLineRunner {
    
    private static final org.slf4j.Logger log = 
        org.slf4j.LoggerFactory.getLogger(InterceptorVerifyRunner.class);
    
    @Autowired
    private List<SqlSessionFactory> sqlSessionFactories;
    
    @Override
    public void run(String... args) throws Exception {
        log.info("========== 數(shù)據(jù)變更攔截器注冊(cè)驗(yàn)證 ==========");
        
        for (int i = 0; i < sqlSessionFactories.size(); i++) {
            SqlSessionFactory factory = sqlSessionFactories.get(i);
            List<Interceptor> interceptors = factory.getConfiguration().getInterceptors();
            
            boolean hasInterceptor = false;
            for (Interceptor interceptor : interceptors) {
                String className = interceptor.getClass().getName();
                if (className.contains("DataChangeInterceptor")) {
                    hasInterceptor = true;
                    log.info("? SqlSessionFactory[{}] 已注冊(cè)攔截器: {}", 
                        i, className);
                }
            }
            
            if (!hasInterceptor) {
                log.error("? SqlSessionFactory[{}] 未找到數(shù)據(jù)變更攔截器!", i);
            }
        }
        
        log.info("==========================================");
    }
}

5.5 調(diào)試用REST接口

package com.demo.web.controller.monitor;

import com.demo.common.core.controller.BaseController;
import com.demo.common.core.domain.AjaxResult;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 攔截器調(diào)試控制器
 */
@RestController
@RequestMapping("/monitor/interceptor")
public class InterceptorDebugController extends BaseController {
    
    @Autowired
    private List<SqlSessionFactory> sqlSessionFactories;
    
    /**
     * 查看攔截器注冊(cè)狀態(tài)
     */
    @GetMapping("/status")
    public AjaxResult status() {
        List<Map<String, Object>> result = new ArrayList<>();
        
        for (int i = 0; i < sqlSessionFactories.size(); i++) {
            SqlSessionFactory factory = sqlSessionFactories.get(i);
            Map<String, Object> factoryInfo = new HashMap<>();
            factoryInfo.put("factoryIndex", i);
            factoryInfo.put("factoryName", factory.getClass().getSimpleName());
            
            List<Interceptor> interceptors = factory.getConfiguration().getInterceptors();
            List<String> interceptorNames = new ArrayList<>();
            for (Interceptor interceptor : interceptors) {
                interceptorNames.add(interceptor.getClass().getSimpleName());
            }
            
            factoryInfo.put("interceptorCount", interceptors.size());
            factoryInfo.put("interceptors", interceptorNames);
            factoryInfo.put("hasDataChangeInterceptor", 
                interceptorNames.stream().anyMatch(name -> name.contains("DataChange")));
            
            result.add(factoryInfo);
        }
        
        return success(result);
    }
    
    /**
     * 測(cè)試攔截器是否工作
     */
    @GetMapping("/test")
    public AjaxResult test() {
        // 返回提示信息,實(shí)際測(cè)試需要調(diào)用Mapper方法
        return success("請(qǐng)調(diào)用實(shí)際的Mapper方法測(cè)試攔截器,如:/system/user/update");
    }
}

六、使用示例

6.1 Mapper接口配置

package com.demo.system.mapper;

import com.demo.framework.aspectj.lang.annotation.DataChangeLog;
import com.demo.system.domain.SysUser;
import org.apache.ibatis.annotations.Param;
import java.util.List;

public interface SysUserMapper {
    
    /**
     * 修改用戶信息
     */
    @DataChangeLog(
        businessType = "user.update",
        ignoreFields = {"createTime", "updateTime", "password"}
    )
    public int updateUser(SysUser user);
    
    /**
     * 新增用戶
     */
    @DataChangeLog(businessType = "user.insert")
    public int insertUser(SysUser user);
    
    /**
     * 刪除用戶
     */
    @DataChangeLog(businessType = "user.delete")
    public int deleteUserById(Long userId);
    
    /**
     * 批量刪除用戶
     */
    @DataChangeLog(businessType = "user.batchDelete")
    public int deleteUserByIds(@Param("userIds") List<Long> userIds);
    
    /**
     * 查詢用戶(用于獲取變更前后的數(shù)據(jù))
     */
    public SysUser selectUserById(Long userId);
}

6.2 Service層調(diào)用

@Service
public class SysUserServiceImpl implements ISysUserService {
    
    @Autowired
    private SysUserMapper userMapper;
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateUser(SysUser user) {
        // 直接調(diào)用Mapper,攔截器會(huì)自動(dòng)處理數(shù)據(jù)變更記錄
        // 記錄會(huì)在事務(wù)提交后自動(dòng)寫入
        return userMapper.updateUser(user);
    }
}

七、常見問題與解決方案

7.1 攔截器未生效排查清單

問題檢查點(diǎn)解決方案
攔截器類未掃描@Component注解是否存在添加注解或手動(dòng)注冊(cè)Bean
MyBatis未注冊(cè)查看configuration.getInterceptors()手動(dòng)注冊(cè)攔截器
多數(shù)據(jù)源問題所有SqlSessionFactory都檢查為每個(gè)數(shù)據(jù)源注冊(cè)攔截器
事務(wù)問題事務(wù)提交前查詢不到新數(shù)據(jù)使用afterCommit回調(diào)
注解未識(shí)別Mapper方法上是否有@DataChangeLog檢查注解是否存在

7.2 性能優(yōu)化建議

/**
 * 批量操作優(yōu)化
 */
private static final int BATCH_THRESHOLD = 100; // 批量閾值

// 分批保存變更記錄
if (allChangeLogs.size() > BATCH_THRESHOLD) {
    List<List<SysDataChangeLog>> partitions = partitionList(allChangeLogs, BATCH_THRESHOLD);
    for (List<SysDataChangeLog> partition : partitions) {
        dataChangeLogService.insertBatch(partition);
    }
} else {
    dataChangeLogService.insertBatch(allChangeLogs);
}

7.3 日志配置

# application.yml
logging:
  level:
    com.demo.framework.interceptor: DEBUG
    org.apache.ibatis: INFO

八、總結(jié)

8.1 方案優(yōu)勢(shì)

  1. 數(shù)據(jù)一致性:事務(wù)提交后才記錄日志,確保數(shù)據(jù)準(zhǔn)確性
  2. 性能優(yōu)化:異步處理變更記錄,不影響主業(yè)務(wù)流程
  3. 字段級(jí)對(duì)比:精確記錄每個(gè)字段的變化
  4. 易于集成:通過注解配置,對(duì)業(yè)務(wù)代碼無侵入
  5. 支持批量:完善的批量操作處理機(jī)制

8.2 注意事項(xiàng)

  1. 查詢方法必須存在:需要提供根據(jù)ID查詢數(shù)據(jù)的方法
  2. 事務(wù)管理:確保在事務(wù)環(huán)境中使用
  3. 大字段處理:考慮對(duì)BLOB等大字段的特殊處理
  4. 數(shù)據(jù)清理:定期清理歷史變更記錄

8.3 擴(kuò)展建議

  • 支持異步記錄(使用消息隊(duì)列)
  • 支持變更記錄查詢和導(dǎo)出
  • 支持自定義字段轉(zhuǎn)換器
  • 支持敏感數(shù)據(jù)脫敏

以上就是基于MyBatis攔截器實(shí)現(xiàn)數(shù)據(jù)變更記錄的技術(shù)方案的詳細(xì)內(nèi)容,更多關(guān)于MyBatis攔截器數(shù)據(jù)變更記錄的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java線程變量ThreadLocal源碼分析

    Java線程變量ThreadLocal源碼分析

    ThreadLocal用來提供線程內(nèi)部的局部變量,不同的線程之間不會(huì)相互干擾,這種變量在多線程環(huán)境下訪問時(shí)能保證各個(gè)線程的變量相對(duì)獨(dú)立于其他線程內(nèi)的變量,在線程的生命周期內(nèi)起作用,可以減少同一個(gè)線程內(nèi)多個(gè)函數(shù)或組件之間一些公共變量傳遞的復(fù)雜度
    2022-08-08
  • 深度分析MybatisPlus查詢結(jié)果映射失敗@TableField失效解決辦法

    深度分析MybatisPlus查詢結(jié)果映射失敗@TableField失效解決辦法

    本文針對(duì)SpringBoot使用MybatisPlus框架時(shí)出現(xiàn)的查詢結(jié)果映射失敗問題進(jìn)行深入分析,通過源碼解析,闡述了@TableField注解失效的原因,并提出了四種有效的解決方案,感興趣的朋友一起看看吧
    2025-07-07
  • Java并發(fā)編程之synchronized底層實(shí)現(xiàn)原理分析

    Java并發(fā)編程之synchronized底層實(shí)現(xiàn)原理分析

    這篇文章主要介紹了Java并發(fā)編程之synchronized底層實(shí)現(xiàn)原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-02-02
  • Java使用Collections.sort()排序的方法

    Java使用Collections.sort()排序的方法

    這篇文章介紹了Java使用Collections.sort()排序的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-12-12
  • 使用Mybatis的Batch?Insert?Support?實(shí)現(xiàn)批量插入

    使用Mybatis的Batch?Insert?Support?實(shí)現(xiàn)批量插入

    這篇文章主要介紹了使用Mybatis的Batch?Insert?Support?實(shí)現(xiàn)批量插入。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • Mybatis Plugin攔截器開發(fā)過程詳解

    Mybatis Plugin攔截器開發(fā)過程詳解

    這篇文章主要介紹了Mybatis Plugin攔截器開發(fā)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-02-02
  • Spring cloud gateway工作流程原理解析

    Spring cloud gateway工作流程原理解析

    這篇文章主要介紹了Spring cloud gateway工作流程原理解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • java實(shí)現(xiàn)百度云文字識(shí)別接口代碼

    java實(shí)現(xiàn)百度云文字識(shí)別接口代碼

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)百度云文字識(shí)別的接口代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-11-11
  • Intellij IDEA 2019 最新亂碼問題及解決必殺技(必看篇)

    Intellij IDEA 2019 最新亂碼問題及解決必殺技(必看篇)

    大家在使用Intellij IDEA 的時(shí)候會(huì)經(jīng)常遇到各種亂碼問題,今天小編給大家分享一些關(guān)于Intellij IDEA 2019 最新亂碼問題及解決必殺技,感興趣的朋友跟隨小編一起看看吧
    2020-04-04
  • SpringBoot+Redis海量重復(fù)提交問題解決

    SpringBoot+Redis海量重復(fù)提交問題解決

    在實(shí)際的開發(fā)項(xiàng)目中,一個(gè)對(duì)外暴露的接口往往會(huì)面臨很多次請(qǐng)求,所以本文介紹一下SpringBoot+Redis海量重復(fù)提交問題解決,感興趣的可以了解一下
    2023-12-12

最新評(píng)論

南昌市| 峨山| 全南县| 商都县| 柘城县| 江口县| 肥乡县| 原平市| 水城县| 怀安县| 潞城市| 家居| 恭城| 交口县| 崇义县| 肇源县| 西乌珠穆沁旗| 丰县| 赞皇县| 宣威市| 彭泽县| 潍坊市| 兴城市| 吐鲁番市| 壶关县| 通许县| 德庆县| 湘西| 新和县| 余江县| 郴州市| 仲巴县| 德兴市| 辽阳县| 东至县| 宁南县| 大厂| 武宁县| 罗山县| 兴国县| 思茅市|