it编程 > 编程语言 > Java

基于SpringBoot+AOP+注解实现自动数据变更追踪

3人参与 2026-08-07 Java

数据变更追踪的痛点

在我们的日常开发工作中,经常会遇到这样的场景:

传统的做法往往是手动在每个业务方法中添加日志记录,不仅代码冗余,还容易遗漏。今天我们就用springboot + aop + 注解的方式来解决这个问题。

解决方案思路

今天我们要解决的,就是如何用aop实现自动化的数据变更追踪。

核心思路是:

  1. 自定义注解:标记需要追踪的方法
  2. aop切面:拦截被标记的方法
  3. 数据对比:比较变更前后的数据差异
  4. 变更记录:自动记录变更信息

技术选型

核心实现思路

1. 自定义注解定义

首先定义追踪注解:

/**
 * 数据变更追踪注解
 */
@target(elementtype.method)
@retention(retentionpolicy.runtime)
@documented
public @interface datachangetrack {
    /**
     * 业务类型
     */
    string businesstype() default "";
    
    /**
     * 业务id字段名
     */
    string businessidfield() default "id";
    
    /**
     * 实体类类型
     */
    class<?> entityclass();
    
    /**
     * 是否记录详细变更内容
     */
    boolean trackdetail() default true;
    
    /**
     * 忽略的字段列表
     */
    string[] ignorefields() default {};
}

2. 变更记录实体

定义变更记录的存储实体:

@entity
@table(name = "data_change_log")
@data
public class datachangelog {
    @id
    @generatedvalue(strategy = generationtype.identity)
    private long id;
    
    @column(name = "business_type")
    private string businesstype;
    
    @column(name = "business_id")
    private string businessid;
    
    @column(name = "operation_type")
    private string operationtype; // create, update, delete
    
    @column(name = "table_name")
    private string tablename;
    
    @column(name = "before_value", columndefinition = "text")
    private string beforevalue;  // 变更前的值
    
    @column(name = "after_value", columndefinition = "text")
    private string aftervalue;   // 变更后的值
    
    @column(name = "changed_fields", columndefinition = "text")
    private string changedfields; // 变更的字段列表
    
    @column(name = "changer_id")
    private string changerid;
    
    @column(name = "changer_name")
    private string changername;
    
    @column(name = "change_time")
    private localdatetime changetime;
    
    @column(name = "remark", columndefinition = "text")
    private string remark;
}

3. aop切面实现

创建核心的aop切面:

@aspect
@component
@slf4j
public class datachangetrackeraspect {
    
    @autowired
    private datachangelogservice logservice;
    
    @autowired
    private objectmapper objectmapper;
    
    /**
     * 环绕通知,拦截被@datachangetrack注解标记的方法
     */
    @around("@annotation(datachangetrack)")
    public object around(proceedingjoinpoint joinpoint, datachangetrack datachangetrack) throws throwable {
        string methodname = joinpoint.getsignature().getname();
        object[] args = joinpoint.getargs();
        
        log.debug("开始追踪方法: {}", methodname);
        
        // 获取操作前的数据
        map<string, object> beforedata = getbeforedata(joinpoint, datachangetrack);
        
        try {
            // 执行原方法
            object result = joinpoint.proceed();
            
            // 获取操作后的数据
            map<string, object> afterdata = getafterdata(joinpoint, result, datachangetrack);
            
            // 记录变更
            recordchange(datachangetrack, beforedata, afterdata, result);
            
            return result;
        } catch (exception e) {
            log.error("数据变更追踪发生异常", e);
            throw e;
        }
    }
    
    /**
     * 获取变更前的数据
     */
    private map<string, object> getbeforedata(proceedingjoinpoint joinpoint, datachangetrack annotation) {
        try {
            object[] args = joinpoint.getargs();
            if (args.length == 0) {
                return collections.emptymap();
            }
            
            // 假设第一个参数是要操作的实体对象
            object entity = args[0];
            if (entity == null) {
                return collections.emptymap();
            }
            
            // 序列化为map
            return objectmapper.convertvalue(entity, map.class);
        } catch (exception e) {
            log.error("获取变更前数据失败", e);
            return collections.emptymap();
        }
    }
    
    /**
     * 获取变更后的数据
     */
    private map<string, object> getafterdata(proceedingjoinpoint joinpoint, object result, datachangetrack annotation) {
        try {
            if (result == null) {
                return collections.emptymap();
            }
            
            // 如果返回值是实体对象,直接序列化
            if (annotation.entityclass().isassignablefrom(result.getclass())) {
                return objectmapper.convertvalue(result, map.class);
            }
            
            // 如果是更新操作,可能需要重新查询数据库获取最新数据
            if (result instanceof number) { // 假设返回值是影响的行数
                // 从参数中获取业务id,查询最新数据
                object entity = joinpoint.getargs()[0];
                string businessid = getbusinessid(entity, annotation.businessidfield());
                
                if (businessid != null) {
                    // 查询数据库获取最新数据
                    return querylatestdata(annotation.entityclass(), businessid);
                }
            }
            
            return objectmapper.convertvalue(result, map.class);
        } catch (exception e) {
            log.error("获取变更后数据失败", e);
            return collections.emptymap();
        }
    }
    
    /**
     * 记录数据变更
     */
    private void recordchange(datachangetrack annotation, map<string, object> beforedata, 
                             map<string, object> afterdata, object result) {
        try {
            datachangelog logentry = new datachangelog();
            logentry.setbusinesstype(annotation.businesstype());
            logentry.setoperationtype(determineoperationtype(beforedata, afterdata));
            logentry.settablename(annotation.entityclass().getsimplename());
            logentry.setbeforevalue(objectmapper.writevalueasstring(beforedata));
            logentry.setaftervalue(objectmapper.writevalueasstring(afterdata));
            logentry.setchangedfields(getchangedfields(beforedata, afterdata, annotation.ignorefields()));
            logentry.setchangerid(getcurrentuserid());
            logentry.setchangername(getcurrentusername());
            logentry.setchangetime(localdatetime.now());
            logentry.setremark("自动追踪");
            
            // 设置业务id
            string businessid = getbusinessidfromresult(result, annotation.businessidfield());
            if (businessid != null) {
                logentry.setbusinessid(businessid);
            }
            
            logservice.save(logentry);
        } catch (exception e) {
            log.error("记录数据变更失败", e);
        }
    }
    
    /**
     * 确定操作类型
     */
    private string determineoperationtype(map<string, object> beforedata, map<string, object> afterdata) {
        if (beforedata.isempty() && !afterdata.isempty()) {
            return "create";
        } else if (!beforedata.isempty() && afterdata.isempty()) {
            return "delete";
        } else {
            return "update";
        }
    }
    
    /**
     * 获取变更的字段
     */
    private string getchangedfields(map<string, object> beforedata, map<string, object> afterdata, string[] ignorefields) {
        set<string> ignored = new hashset<>(arrays.aslist(ignorefields));
        list<string> changed = new arraylist<>();
        
        set<string> allkeys = new hashset<>();
        allkeys.addall(beforedata.keyset());
        allkeys.addall(afterdata.keyset());
        
        for (string key : allkeys) {
            if (ignored.contains(key)) {
                continue;
            }
            
            object beforevalue = beforedata.get(key);
            object aftervalue = afterdata.get(key);
            
            if (!objects.equals(beforevalue, aftervalue)) {
                changed.add(key);
            }
        }
        
        return string.join(",", changed);
    }
    
    /**
     * 获取业务id
     */
    private string getbusinessid(object entity, string idfield) {
        try {
            field field = entity.getclass().getdeclaredfield(idfield);
            field.setaccessible(true);
            object value = field.get(entity);
            return value != null ? value.tostring() : null;
        } catch (exception e) {
            log.error("获取业务id失败", e);
            return null;
        }
    }
    
    /**
     * 从结果中获取业务id
     */
    private string getbusinessidfromresult(object result, string idfield) {
        if (result != null) {
            try {
                return getbusinessid(result, idfield);
            } catch (exception e) {
                log.error("从结果中获取业务id失败", e);
            }
        }
        return null;
    }
    
    /**
     * 查询最新数据
     */
    private map<string, object> querylatestdata(class<?> entityclass, string businessid) {
        // 这里需要根据实际情况实现查询逻辑
        // 可以通过反射调用repository方法
        return collections.emptymap();
    }
    
    /**
     * 获取当前用户id
     */
    private string getcurrentuserid() {
        // 从securitycontext或threadlocal获取当前用户信息
        authentication authentication = securitycontextholder.getcontext().getauthentication();
        if (authentication != null && authentication.getprincipal() instanceof userdetails) {
            return ((userdetails) authentication.getprincipal()).getusername();
        }
        return "system";
    }
    
    /**
     * 获取当前用户名
     */
    private string getcurrentusername() {
        // 实现获取当前用户名的逻辑
        return getcurrentuserid();
    }
}

4. 服务层实现

创建变更日志服务:

@service
@transactional
public class datachangelogservice {
    
    @autowired
    private datachangelogrepository repository;
    
    /**
     * 保存变更记录
     */
    public void save(datachangelog log) {
        repository.save(log);
    }
    
    /**
     * 根据业务类型和id查询变更记录
     */
    public list<datachangelog> findbybusinessid(string businesstype, string businessid) {
        return repository.findbybusinesstypeandbusinessidorderbychangetimedesc(businesstype, businessid);
    }
    
    /**
     * 分页查询变更记录
     */
    public page<datachangelog> findlogs(pageable pageable) {
        return repository.findall(pageable);
    }
    
    /**
     * 查询指定时间段内的变更记录
     */
    public list<datachangelog> findbytimerange(localdatetime starttime, localdatetime endtime) {
        return repository.findbychangetimebetween(starttime, endtime);
    }
}

5. repository接口

定义数据访问接口:

@repository
public interface datachangelogrepository extends jparepository<datachangelog, long> {
    
    list<datachangelog> findbybusinesstypeandbusinessidorderbychangetimedesc(string businesstype, string businessid);
    
    list<datachangelog> findbychangetimebetween(localdatetime starttime, localdatetime endtime);
    
    @query("select d from datachangelog d where d.businesstype = :businesstype and d.operationtype = :operationtype order by d.changetime desc")
    list<datachangelog> findbybusinesstypeandoperationtype(@param("businesstype") string businesstype, 
                                                         @param("operationtype") string operationtype);
}

6. 使用示例

在业务方法上使用注解:

@service
public class userservice {
    
    @autowired
    private userrepository userrepository;
    
    @datachangetrack(
        businesstype = "user_update",
        businessidfield = "id",
        entityclass = user.class,
        trackdetail = true,
        ignorefields = {"lastmodifiedtime", "version"}
    )
    public user updateuser(user user) {
        // 更新用户信息
        return userrepository.save(user);
    }
    
    @datachangetrack(
        businesstype = "user_create",
        businessidfield = "id", 
        entityclass = user.class
    )
    public user createuser(user user) {
        // 创建用户
        return userrepository.save(user);
    }
    
    @datachangetrack(
        businesstype = "user_delete",
        businessidfield = "id",
        entityclass = user.class
    )
    @transactional
    public void deleteuser(long userid) {
        // 删除用户
        userrepository.deletebyid(userid);
    }
}

@service
public class orderservice {
    
    @autowired
    private orderrepository orderrepository;
    
    @datachangetrack(
        businesstype = "order_status_update",
        businessidfield = "orderid",
        entityclass = order.class,
        trackdetail = true,
        ignorefields = {"updatetime", "version"}
    )
    public order updateorderstatus(order order) {
        // 更新订单状态
        return orderrepository.save(order);
    }
}

7. 控制器接口

提供查询接口:

@restcontroller
@requestmapping("/api/data-change")
public class datachangecontroller {
    
    @autowired
    private datachangelogservice logservice;
    
    /**
     * 查询业务对象的变更历史
     */
    @getmapping("/history")
    public result<list<datachangelog>> getchangehistory(
            @requestparam string businesstype,
            @requestparam string businessid) {
        
        list<datachangelog> history = logservice.findbybusinessid(businesstype, businessid);
        return result.success(history);
    }
    
    /**
     * 分页查询变更记录
     */
    @getmapping("/logs")
    public result<page<datachangelog>> getchangelogs(
            @requestparam(defaultvalue = "0") int page,
            @requestparam(defaultvalue = "10") int size) {
        
        pageable pageable = pagerequest.of(page, size, sort.by(sort.direction.desc, "changetime"));
        page<datachangelog> logs = logservice.findlogs(pageable);
        return result.success(logs);
    }
    
    /**
     * 查询时间范围内的变更记录
     */
    @getmapping("/logs/time-range")
    public result<list<datachangelog>> getlogsbytimerange(
            @requestparam @datetimeformat(iso = datetimeformat.iso.date_time) localdatetime starttime,
            @requestparam @datetimeformat(iso = datetimeformat.iso.date_time) localdatetime endtime) {
        
        list<datachangelog> logs = logservice.findbytimerange(starttime, endtime);
        return result.success(logs);
    }
}

性能优化策略

1. 异步处理

为了避免影响业务性能,可以将日志记录改为异步:

@async
public void saveasync(datachangelog log) {
    repository.save(log);
}

2. 批量处理

对于高频变更场景,可以采用批量处理:

@component
public class batchlogprocessor {
    
    private final list<datachangelog> logbuffer = new arraylist<>();
    private final object lock = new object();
    
    @scheduled(fixedrate = 5000) // 每5秒批量处理一次
    public void processbatch() {
        synchronized (lock) {
            if (!logbuffer.isempty()) {
                list<datachangelog> currentbatch = new arraylist<>(logbuffer);
                logbuffer.clear();
                
                // 批量保存到数据库
                logrepository.saveall(currentbatch);
            }
        }
    }
    
    public void addtobatch(datachangelog log) {
        synchronized (lock) {
            logbuffer.add(log);
        }
    }
}

优势分析

相比传统的手动记录方式,这种方案的优势明显:

  1. 无侵入性:只需添加注解,不影响业务代码
  2. 自动化:自动记录变更,无需手动编写日志代码
  3. 灵活性:可通过注解参数灵活配置
  4. 完整性:记录完整的变更历史
  5. 可追溯:支持按业务类型和id查询变更历史

注意事项

  1. 性能影响:aop切面会带来一定的性能开销
  2. 数据安全:注意敏感数据的脱敏处理
  3. 存储容量:变更日志会持续增长,需要定期清理
  4. 事务一致性:确保变更日志与业务操作在同一线程中

总结

通过springboot + aop + 注解的技术组合,我们可以轻松实现自动化的数据变更追踪。这种方式不仅减少了代码冗余,还提高了系统的可维护性和可追溯性。

在实际项目中,建议根据具体业务需求调整注解参数和切面逻辑,并考虑性能优化策略。

以上就是基于springboot+aop+注解实现自动数据变更追踪的详细内容,更多关于springboot+aop+注解数据变更追踪的资料请关注代码网其它相关文章!

(0)

您想发表意见!!点此发布评论

推荐阅读

Linux服务器下Java环境部署全攻略

08-07

MyBatis映射值报错的罪魁祸首竟然是Lombok的@Builder详解

08-07

Mybatis插件机制(拦截器)和缓存机制示例详解

08-07

使用SpringBoot构建一个轻量级的日志查看器

08-07

SpringBoot方法级耗时监控实现方案

08-07

SpringBoot中日志加载的全链路拆解

08-07

猜你喜欢

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

发表评论