当前位置: 首页 > news >正文

# 事务提交时原子写审计日志:commit里调存储过程,业务和日志同生共死

事务提交时原子写审计日志:commit里调存储过程,业务和日志同生共死

文章标签:#java #政务 #审计日志 #事务管理 #MyBatis #自研框架

非科班野生程序员,深耕政务信息化20年。从VC到PB再到Java,自研框架browise也打磨了十几年。最近整理框架代码,发现不少有趣的决策,写出来和大家聊聊。最后感谢豆包、智谱、OpenCode,决策是我做的,代码是我搓的,文字是他们总结的。


一、政务审计的核心痛点:原子性

政务系统有强合规、强审计要求:谁、在什么时间、对哪条数据、做了什么操作、结果如何,必须完整留痕、不可篡改、不可丢失。

最大难点:如何保证业务数据与审计日志的强一致、同生共死

  • 业务提交成功 → 日志必须落库
  • 业务回滚 → 日志必须一起消失
  • 绝不允许:业务成功、日志丢失;或日志写入、业务回滚

常见方案的缺陷

  • 双事务分别写:无原子性,极易不一致
  • 分布式事务(XA):太重、性能差、配置复杂
  • 消息队列异步写:有延迟,不满足实时审计
  • 业务代码里硬编码写日志:侵入强、易漏、难维护

我的极简方案

在同一个事务、同一个数据库连接内:commit 阶段先写审计日志,再提交事务
日志与业务天然同事务、同原子,不引入任何中间件,轻量、可靠、合规。


二、核心设计思路

  1. 业务增删改执行完毕,但事务未提交
  2. 重写commit()方法:先调用审计存储过程,再执行事务提交
  3. 存储过程与业务共用SqlSession,同一连接、同一事务
  4. 日志写入失败 → 抛异常 → 事务不提交 → 整体回滚
  5. 日志写入成功 → 执行session.commit()→ 业务与日志一起落库

一句话:日志写不成功,业务绝对不提交


三、完整优化代码(可直接上线)

1. BusinessSession 事务会话封装

importorg.apache.ibatis.session.SqlSession;importjava.util.HashMap;importjava.util.Map;/** * 事务会话封装:commit 时原子写入审计日志 * 保证业务与审计日志同事务、同生共死 * @author yuhou25 */publicclassBusinessSessionimplementsAutoCloseable{// 操作唯一IDprivateStringBAZ001;// MyBatis 会话(同一数据库连接)privatefinalSqlSessionsession;// 是否开启审计日志privatebooleansaveLog=true;// 当前登录用户privateUserdao;// 业务类型/交易类型privateStringBAZ099;privateStringBAZ098;/** * 构造方法 * @param isModify 是否为修改类操作(是则生成操作ID) */publicBusinessSession(booleanisModify)throwsUtilException{this.session=DBUtil.getSqlSession();if(isModify){// 生成全局唯一操作序列号this.BAZ001=SystemUtil.getSeq("S_SEQ_BAZ001");}}/** * 【核心】重写提交:先写审计日志,再提交事务 */publicvoidcommit()throwsUtilException{// 开启审计时,先调用存储过程写日志if(saveLog){Map<String,Object>param=newHashMap<>();param.put("PRM_OPERATEID",BAZ001);param.put("PRM_AAE011",dao==null?"system":dao.getPsn_name());param.put("PRM_BAZ099",BAZ099);param.put("PRM_BAZ098",BAZ098);// 存储过程内部取系统时间param.put("PRM_APPCODE",0);param.put("PRM_ERRORMSG","");try{// 调用审计存储过程(同事务)session.selectOne("com.browise.core.util.mapper.sysUtilMapper.DO_TRANSACTION",param);}catch(Exceptione){thrownewUtilException("审计日志写入异常:"+e.getMessage(),-100);}// 判断存储过程返回码intappCode=Integer.parseInt(String.valueOf(param.get("PRM_APPCODE")));StringerrorMsg=String.valueOf(param.get("PRM_ERRORMSG"));if(appCode<0){thrownewUtilException("审计日志校验失败:"+errorMsg,appCode);}}// 日志校验通过,正式提交事务session.commit();}/** * 回滚:业务与日志一起回滚 */publicvoidrollback(){session.rollback();}/** * 关闭会话 */@Overridepublicvoidclose(){session.close();}// ------------------- 工具方法 -------------------public<T>TgetMapper(Class<T>clazz){returnsession.getMapper(clazz);}publicvoidsetSaveLog(booleansaveLog){this.saveLog=saveLog;}publicvoidsetUser(Useruser){this.dao=user;}publicvoidsetBizType(StringBAZ099,StringBAZ098){this.BAZ099=BAZ099;this.BAZ098=BAZ098;}}

2. MyBatis Mapper 调用存储过程

<!-- sysUtilMapper.xml --><mappernamespace="com.browise.core.util.mapper.sysUtilMapper"><selectid="DO_TRANSACTION"statementType="CALLABLE"parameterType="map">{call PKG_AUDIT.DO_TRANSACTION( #{PRM_OPERATEID,mode=IN,jdbcType=VARCHAR}, #{PRM_AAE011,mode=IN,jdbcType=VARCHAR}, #{PRM_BAZ099,mode=IN,jdbcType=VARCHAR}, #{PRM_BAZ098,mode=IN,jdbcType=VARCHAR}, #{PRM_APPCODE,mode=OUT,jdbcType=INTEGER}, #{PRM_ERRORMSG,mode=OUT,jdbcType=VARCHAR} ) }</select></mapper>

3. 标准业务调用模板

publicvoidupdateBusiness(DataVOdata){// 开启修改操作会话try(BusinessSessionbs=newBusinessSession(true)){// 设置当前用户、业务类型bs.setUser(UserContext.getCurrentUser());bs.setBizType("USER_MANAGE","MODIFY");// 执行业务逻辑MyMappermapper=bs.getMapper(MyMapper.class);mapper.updateById(data);// commit 自动写审计日志,同事务提交bs.commit();}catch(UtilExceptione){// 异常自动回滚:业务 + 日志一起撤销log.error("业务执行失败",e);throwe;}}

四、存储过程 DO_TRANSACTION 做了什么

Oracle 存储过程,与业务同事务

  1. 接收操作ID、操作人、业务类型、交易类型
  2. 从临时表读取本次操作影响的表、主键、变更前后数据
  3. 组装审计日志,写入正式审计表
  4. 通过PRM_APPCODE返回结果:0=成功,<0=失败
  5. 异常时返回错误信息,让 Java 层触发回滚

关键点:存储过程不加自治事务,完全依附于业务事务,保证强一致。


五、关键细节:saveLog 开关

privatebooleansaveLog=true;
  • 默认开启审计
  • 日志本身、监控、定时任务等不需要递归审计的场景,可关闭
// 示例:日志工具类不记录自身session.setSaveLog(false);

六、为什么这套方案能拿90+

  1. 强原子性:同连接、同事务,业务与日志绝对一致
  2. 零侵入:业务代码只写逻辑,不用关心日志
  3. 零依赖:不引入MQ、XA、Seata等中间件
  4. 高性能:无分布式开销,一次提交完成
  5. 合规可追溯:满足政务、等保、审计要求
  6. 易维护:统一收口,一处修改,全局生效

七、对比传统方案

方案一致性性能复杂度合规
双事务写❌ 可能不一致❌ 不满足
分布式事务✅ 强一致✅ 满足
消息队列⚠️ 最终一致⚠️ 实时性不足
本方案✅ 强一致极低✅ 满足

八、小结

这套审计日志方案仅130行左右核心代码,靠commit 阶段写入+同事务执行,实现业务与审计日志的强一致。
没有花里胡哨的架构,没有复杂中间件,极简、稳定、能过审,非常适合政务、金融、医疗等高合规场景。

审计日志的写入时机大家是怎么处理的?评论区聊聊。

http://www.jsqmd.com/news/647785/

相关文章:

  • C语言实战:两种算法解析行列式计算
  • 被90%团队忽略的模态间语义鸿沟:SITS2026首次公布跨模态对抗样本库(含17类高危攻击向量)
  • 慧源流GEO——EEAT原则在B2B制造行业的实战落地
  • π3:当视觉几何遇见置换等变,如何重塑三维重建的底层逻辑?
  • TVBoxOSC终极指南:如何快速打造全能电视盒子媒体中心
  • Python Flask路由怎么限制方法_methods列表配置仅允许GET或POST限制接口非法请求
  • 2026年TCT亚洲展海外观众增长50% 正在成为全球“走进中国”的第一站——上海
  • 2025-2026年访客机品牌推荐:五大口碑产品评测对比顶尖工厂安全准入繁琐案例 - 品牌推荐
  • Ubuntu 22.04 下,从零构建 Isaac Sim 与 Isaac Lab 一体化机器人开发环境
  • 从单体到微服务:飞控仿真台架构演进之路
  • 如何永久保存微信聊天记录?终极免费工具使用指南
  • 多模态大模型容灾备份策略(NASA级冗余设计白皮书首次公开)
  • 2025-2026年访客机品牌推荐:五大口碑产品评测对比顶尖工厂访客登记繁琐耗时注意事项 - 品牌推荐
  • 从AHB Burst到APB传输:手把手分析桥接设计中的psel/penable时序与反压策略
  • QHeaderView进阶应用:自定义QTableWidget表头样式与功能
  • Mac长期连移动硬盘,修改这4个关键设置,避免伤盘
  • Windows Defender SmartScreen 提示拦截,但没有“解除锁定”按钮的原因与解决方案
  • 2026年智己品牌深度解析:从股东背景与品牌档次看高端新能源格局. - 品牌推荐
  • WebToEpub:5分钟免费将网页小说转为EPUB电子书的终极指南
  • 云原生网络架构实践
  • 大模型多模态推理功耗飙升的“静默杀手”:跨模态注意力头冗余、特征图内存拷贝、非对称模态采样率失配(附Perfetto+Nsight深度追踪教程)
  • 基于Python的影城会员管理系统
  • AEUX终极指南:5分钟掌握Figma/Sketch到After Effects的无缝转换
  • 15分钟掌握libIEC61850:电力自动化通信的标准化解决方案
  • 告别终端黑框:用Open WebUI给Mac上的DeepSeek模型加个漂亮界面
  • 破解Google SynthID:AI水印逆向工程
  • BCrypt密码加密
  • 某上市炼化企业人才培养及引进成功案例纪实
  • 如果你很懒,那这种一定很适合你:CSGO游戏搬砖,不需要玩游戏就能赚钱
  • 多模态游戏AI不是升级,是重定义:2026奇点大会发布的《实时语义-物理耦合引擎》标准草案(全球首次公开)