53-审计三表

53-审计三表:操作/行级/字段级

谁在什么时候改了哪条数据的哪个字段------从旧值变成了什么新值。19.1KB的AuditService把审计拆成三张表:ZE01操作头、ZE02行快照、ZE03字段diff。@Audited(level=)注解选级别、_o原始值免费服务diff生成。这篇拆三表模型、快照对比算法、批量JDBC插入。

文章目录

源码:browise-audit/src/main/java/com/browise/audit/config/AuditService.java(478行)

注解:annotation/Audited.java、AuditLevel.java、AuditOverride.java

监听:AuditEntityLifecycleListener.java(3.5KB------第24篇生命周期钩子的审计实现)

清理:AuditThreadLocalCleanupFilter.java


一、三张表的分工

复制代码
ZE01 操作头------一次业务动作一行
  baz001 操作ID、操作人、操作时间、操作类型(增/改/删)、目标表
  
ZE02 行级快照------这次动作涉及的每行一条
  baz002 行ID → ze01的baz001关联
  record_pk 行主键值
  OLD_SNAPSHOT 旧行完整JSON
  NEW_SNAPSHOT 新行完整JSON
  
ZE03 字段级变更------每行的每个变更字段一条
  baz003 字段ID → ze02的baz002关联
  field_name、old_value、new_value

粒度逐层下钻------操作→行→字段。查账的三种姿势:

复制代码
"谁动过SYS_USER"      → 查ZE01(按表+时间范围)
"这行数据改过几次"    → 查ZE02(按record_pk)
"psnName从什么改成什么" → 查ZE03(按field_name)

二、两级审计:OPERATION vs SNAPSHOT

java 复制代码
public AuditLevel resolveLevel(Class<?> entityClass) {
    Audited audited = entityClass.getAnnotation(Audited.class);
    if (audited != null) return audited.level();      // 注解优先
    // 无注解→全局默认(browise.audit.default-level配置)
    switch (properties.getDefaultLevel()) { ... }
}
级别 写什么 成本 场景
OPERATION 只ZE01 一行insert 一般业务表
SNAPSHOT ZE01+02+03全写 一次动作N行×M字段 待遇/基金类高敏表
NONE 不审计 零 字典/日志类

@Audited(level=SNAPSHOT)标在实体上 ------级别跟着实体走(敏感表单独升级),全局默认兜底普通表。@AuditOverride------方法级覆盖(同一实体某个操作不审计/降级)。


三、字段diff的生成:_o的免费午餐

AuditEntityLifecycleListener挂在BaseEntity的save前(第24篇beforeSave钩子):

java 复制代码
public void beforeSave(Object entity) {
    if (entity instanceof BaseEntity) {
        BaseEntity be = (BaseEntity) entity;
        auditService.auditUpdate(be);       // 或auditInsert/auditDelete
    }
}

diff的数据源 ------BaseEntity的getOriginals()(第20篇的_o)已经记录了"改过的字段的旧值"------审计不用自己再拍快照对比:

java 复制代码
// AuditService生成ZE03的核心(简化)
for (Map.Entry<String, Object> e : entity.getOriginals().entrySet()) {
    Ze03 z3 = new Ze03();
    z3.setFieldName(e.getKey());
    z3.setOldValue(String.valueOf(e.getValue()));                    // _o的旧值
    z3.setNewValue(String.valueOf(entity.getItemValue(e.getKey()))); // 当前新值
    ze03List.add(z3);
}

_o只含改过的字段 ------遍历_o天然就是变更清单------没有变更的审计记录一条都不产生 。脏标记协议(第17篇)与审计的这次协作是整个data模块设计的复利------同一份_o服务了reject回滚(第19篇)、乐观锁WHERE(第23篇)、审计diff(本篇)三个场景。

UPDATE之外的级别------INSERT没有_o(新行)→ZE03记"全部字段新增";DELETE→ZE02只存OLD_SNAPSHOT(没有NEW)。


四、查库拍快照:审计不能信内存

java 复制代码
// 保存前从数据库读当前真实值(防内存中的entity不是最新)
try (Connection conn = dataSource.getConnection();
     PreparedStatement ps = conn.prepareStatement(
         "SELECT * FROM " + tableName + " WHERE " + pkColumn + " = ?")) {
    ps.setObject(1, pkValue);
    try (ResultSet rs = ps.executeQuery()) {
        if (rs.next()) {
            ResultSetMetaData md = rs.getMetaData();
            for (int i = 1; i <= md.getColumnCount(); i++) {
                oldSnapshot.put(md.getColumnLabel(i).toLowerCase(),
                                rs.getObject(i));
            }
        }
    }
}

为什么要再查一次库 ------entity._o记录的是"这个实例 上次加载以来的变更"------但如果另一个会话 改过这行(本实例不知道),_o的旧值已不是库里真值。OLD_SNAPSHOT必须以数据库为准 ------SELECT *实时读。这是审计的司法级严谨------记录的是数据库真实发生了什么,不是应用以为发生了什么。


五、baz002/baz003:ThreadLocal+AtomicLong序列

java 复制代码
private final ThreadLocal<AtomicLong> baz002Seq = ThreadLocal.withInitial(() -> new AtomicLong(0));
private final ThreadLocal<AtomicLong> baz003Seq = ThreadLocal.withInitial(() -> new AtomicLong(0));

行级/字段级ID的生成 ------不用数据库序列(一次审计动作可能产生几百条ZE03,逐条调序列太慢)------线程内自增(前缀+时间戳+计数器)。ThreadLocal保证多线程不冲突、AtomicLong保证同线程内原子。

AuditThreadLocalCleanupFilter ------请求结束清ThreadLocal------第26篇finally清CurrentUser的同款纪律(ThreadLocal不清理在线程池里跨请求泄漏)。


六、批量JDBC插入

java 复制代码
// ZE03的批量写入(batch-size配置默认500)
try (PreparedStatement ps = conn.prepareStatement(ze03InsertSql)) {
    for (Ze03 z : batch) {
        ps.setString(1, z.getBaz003());
        // ...
        ps.addBatch();
        if (++count % batchSize == 0) ps.executeBatch();   // 分批flush
    }
    ps.executeBatch();   // 尾批
}

不用Mapper逐条insert ------快照审计一次可能几百条明细------JDBC batch一次网络往返。batchSize=500的折中(太大撑内存、太小多往返)。与第21篇TypedRowSet的batchInsert同一个性能模式。


✅ 亮点:三表粒度下钻模型、@Audited注解优先+全局默认的两级配置、_o复利服务审计diff(第三场景)、SELECT *实时拍快照的司法级严谨、ThreadLocal序列避开数据库序列、batch-size分批flush。适合做数据审计的人。扩展方向:第20篇_o、第24篇生命周期监听、第26篇ThreadLocal清理。

相关推荐
重生之小比特1 小时前
【C++进阶】红黑树的实现
java·开发语言·c++
程序猿乐锅1 小时前
【黑马点评 | 第九篇】秒杀优化-异步实现
java·spring boot·redis·后端·spring·中间件·mybatis
阳明山水2 小时前
因果一致性正则化实战解析
人工智能·深度学习·算法·机器学习·架构
FYKJ_20103 小时前
springboot助农产品销售商城05829-计算机课程设计、毕业设计
java·spring boot·python·mysql·spark·django
DolphinDB4 小时前
数据库自带 Agent:10 分钟搭起 DolphinX-Web 数据入库与分析框架
后端·架构
弈栈录4 小时前
基于 Spring Boot 构建生产级 AI 应用平台
后端·面试·架构
user_admin_god5 小时前
第 10 篇:拼上下文与生成——预算、边界与防幻觉
java·人工智能·spring boot·语言模型
用户3126874877205 小时前
GC 算法与垃圾收集器演进:从 Serial 到 ZGC
java
天天被压力5 小时前
【别再到处找免费股票数据API了:官方204个接口,32篇一次讲透 #02】涨停跌停与特色股池:5类股池接口一次拉全
java·人工智能·python