当前位置: 代码网 > it编程>编程语言>Java > 基于SpringBoot + MyBatis-Plus高效实现数据变更记录

基于SpringBoot + MyBatis-Plus高效实现数据变更记录

2026年02月05日 Java 我要评论
前言在应用开发过程中,在某些情况下,需要实现数据的变更记录。以便于在未来进行数据操作变更的回溯。常规的做法是在对应修改数据的 service 方法中手动记录数据的变更。这种方式实现起来简单,不用费什么

前言

在应用开发过程中,在某些情况下,需要实现数据的变更记录。以便于在未来进行数据操作变更的回溯。

常规的做法是在对应修改数据的 service 方法中手动记录数据的变更。这种方式实现起来简单,不用费什么脑子。但却并不是最高效的。

public void updateorder(order order) {
    order oldorder = ordermapper.selectbyid(order.getid()); // 第一次查询
    // ... 一堆业务逻辑 ...
    ordermapper.updatebyid(order); // 业务更新
    logservice.savechangelog(oldorder, order); 
}

试想一下,如果修改数据的 service 方法很多,或者项目在开发快结束的时候临时决定需要额外添加数据变更记录。此时一个个的手动添加无疑是一件费时费力切低效的操作。这样做的后果是 严重侵入业务、重复代码泛滥、事务边界混乱、性能极差。

那有什么办法高效实现数据操作变更记录,而不需要一个一个地方去加呢?答案当然是有,今天小编就给大家介绍下如何高效实现数据的变更记录。

实现原理

我们主要通过拦截器 + 事件驱动架构实现。通过 mybatis-plus 的拦截器插件拦截新增、修改、删除操作,发布事件通知,由事件消费者进行消费,并记录到变更记录表中。

该方案的优势:

  • 高效,只需要编写拦截器与事件消费者即可,不需要一个个方法修改,避免漏写、写错的情况。
  • 无业务代码侵入,完全解耦。
  • 高性能,因为是通过异步消息实现记录,不会阻塞原有业务方法。

代码实战

第一步:自定义 mybatis 拦截器(捕获变更时机)

这是核心钩子,用于在数据发生变更时发布一个事件,而非直接记录。

@intercepts({
    @signature(type = executor.class, method = "update", 
               args = {mappedstatement.class, object.class})
})
@component
@slf4j
public class datachangeinterceptor implements interceptor {
    
    @autowired
    private applicationeventpublisher eventpublisher; // 事件发布器
    
    @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. 关键:在操作执行前,根据id查询旧数据(仅update需要)
                object olddata = null;
                if (commandtype == sqlcommandtype.update) {
                    olddata = fetcholddata(parameter, ms); // 根据主键查旧数据
                }
                
                // 4. 执行原始sql操作
                object result = invocation.proceed();
                
                // 5. 异步发布变更事件(不阻塞主流程)
                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,避免后续序列化问题
    public string getolddatajson() {
        return json.tojsonstring(olddata);
    }
    
    public string getnewdatajson() {
        return json.tojsonstring(newdata);
    }
}

第三步:异步事件监听器(真正执行记录)

这是性能关键,必须异步化,且要有降级策略。

@component
@slf4j
public class datachangeeventlistener {
    
    @async("datachangeexecutor") // 指定独立线程池,不占用业务资源
    @eventlistener
    @transactional(propagation = propagation.requires_new) // 新事务,与业务事务分离
    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. 持久化到数据库(或发送到消息队列)
            changelogmapper.insert(changelog);
            
        } catch (exception e) {
            // 4. 降级策略:记录失败时,至少打印日志或存入死信队列
            log.error("数据变更记录失败,事件内容:{}", json.tojsonstring(event), e);
            // 可在此处将事件发送至redis或kafka进行重试
        }
    }
}

第四步:关键配置与优化

1. 独立线程池

防止监听器阻塞影响主业务。

spring:
  task:
    execution:
      pool:
        data-change-executor:
          core-size: 2
          max-size: 5
          queue-capacity: 1000 # 缓冲区,抗瞬时峰值

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 '创建时间',
  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过滤器中设置
}

总结

  • 业务零侵入:业务开发无需关心记录逻辑,专注核心功能。
  • 性能无损:主流程仅增加一次事件发布(内存操作),记录过程完全异步化。
  • 数据完整:通过拦截器在执行前后捕获数据,保证变更前后的完整快照。
  • 架构扩展:事件驱动架构允许你轻松扩展,如:
    1. 将变更记录同时写入elasticsearch供快速检索。
    2. 将重大变更发送消息通知相关系统。
    3. 实现操作回放功能(通过旧数据+新数据反向操作)。

以上就是基于springboot + mybatis-plus高效实现数据变更记录的详细内容,更多关于springboot mybatis-plus数据变更记录的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com