53-审计三表:操作/行级/字段级
谁在什么时候改了哪条数据的哪个字段------从旧值变成了什么新值。19.1KB的AuditService把审计拆成三张表:ZE01操作头、ZE02行快照、ZE03字段diff。@Audited(level=)注解选级别、_o原始值免费服务diff生成。这篇拆三表模型、快照对比算法、批量JDBC插入。
文章目录
- 53-审计三表:操作/行级/字段级
-
- 一、三张表的分工
- [二、两级审计:OPERATION vs SNAPSHOT](#二、两级审计:OPERATION vs SNAPSHOT)
- 三、字段diff的生成:_o的免费午餐
- 四、查库拍快照:审计不能信内存
- 五、baz002/baz003:ThreadLocal+AtomicLong序列
- 六、批量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清理。