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

事务已关闭无法提交(500 错误)

突然遭遇了 HTTP 500 内部服务器错误,日志中明确提示 Cannot commit, transaction is already closed(无法提交,事务已关闭)。排查后发现,问题出在最基础的 DAO 层代码逻辑上 

一、问题复现:转账功能触发 500 错误

1. 错误现象

开发银行转账核心功能时,前端发起转账请求后直接返回 500 错误,服务器日志关键信息如下:

HTTP Status 500 - Internal Server Error
Root Cause: org.apache.ibatis.executor.ExecutorException: Cannot commit, transaction is already closed

错误堆栈指向 DAO 层的 selectAccountByActno 和 updateByActbo 方法,正是这两个查询和更新账户的核心方法出了问题。

2. 错误代码(DAO 层)

当时写的 DAO 层代码如下,看似完成了查询和更新功能

package com.powernode.bank.dao.impl;import com.powernode.bank.pojo.Account;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;public class AccountDao implements com.powernode.bank.dao.AccountDao {@Overridepublic Account selectAccountByActno(String actno) {// 每个方法独立创建SqlSessionSqlSession sqlSession = SqlSessionUtil.openSession();Account account = sqlSession.selectOne("account.selectByActno", actno);// 先关闭SqlSession,再提交事务sqlSession.close();sqlSession.commit();return account;}@Overridepublic int updateByActbo(Account account) {SqlSession sqlSession = SqlSessionUtil.openSession();int count = sqlSession.update("account.updateByActno", account);// 同样犯了“先关闭后提交”的错误sqlSession.close();sqlSession.commit();return count;}
}

2 个致命错误导致事务崩溃

1. 顺序颠倒:先关闭 SqlSession,再提交事务(最直接原因)

MyBatis 中,SqlSession 是事务的载体,其生命周期与事务强绑定:

  • sqlSession.close():关闭会话时,会自动终止当前事务(无论是否提交),释放数据库连接;
  • 错误代码中先执行 close(),再调用 commit()—— 此时事务已被终止,自然会触发 “事务已关闭无法提交” 的异常。

这就像 “先关火再炒菜”,完全违背了操作逻辑,是最基础也最致命的语法顺序错误。

2. 事务粒度错误:DAO 层独立管理 SqlSession,破坏事务原子性

转账业务的核心要求是 “原子性”:查询转出账户、扣款、查询转入账户、入账,这 4 个操作必须在同一个事务中(要么全成功,要么全失败)。

但错误代码中,每个 DAO 方法都独立创建 SqlSession、关闭、提交 —— 意味着每个 DAO 操作都是一个独立事务:

  • 查询转出账户是 “事务 1”(已关闭);
  • 扣款是 “事务 2”(已关闭);
  • 入账是 “事务 3”(已关闭);
  • 后果:若扣款成功后入账失败,“事务 2” 已提交无法回滚,会导致用户余额减少但对方未到账的严重 bug,同时重复的关闭 + 提交操作会让事务状态彻底混乱。

三、解决方案:重构代码,统一事务管理

核心修复思路:事务管理移至 Service 层,DAO 层只负责执行 SQL,不管理 SqlSession 生命周期。让一个业务(如转账)共用一个 SqlSession,确保事务统一。

第一步:修正 DAO 层 —— 只执行 SQL,不管理事务

DAO 层的职责是 “数据访问”,不应涉及事务控制。修改后去掉 close() 和 commit(),由 Service 层传入 SqlSession,保证所有操作共用一个会话:

package com.powernode.bank.dao.impl;import com.powernode.bank.pojo.Account;
import org.apache.ibatis.session.SqlSession;public class AccountDao implements com.powernode.bank.dao.AccountDao {// 新增SqlSession参数,由Service层传入@Overridepublic Account selectAccountByActno(SqlSession sqlSession, String actno) {// 只执行查询SQL,不创建、不关闭、不提交SqlSessionreturn sqlSession.selectOne("account.selectByActno", actno);}@Overridepublic int updateByActbo(SqlSession sqlSession, Account account) {// 只执行更新SQL,不管理事务return sqlSession.update("account.updateByActno", account);}
}

第二步:完善 SqlSession 工具类 —— 确保事务手动控制

SqlSessionUtil 需提供 “不自动提交” 的 SqlSession(MyBatis 默认不自动提交),让 Service 层手动控制提交 / 回滚:

package com.powernode.bank.utils;import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;
import java.io.InputStream;public class SqlSessionUtil {private static SqlSessionFactory sqlSessionFactory;// 静态代码块初始化SqlSessionFactorystatic {try {InputStream is = Resources.getResourceAsStream("mybatis-config.xml");sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);} catch (IOException e) {throw new RuntimeException("MyBatis配置文件加载失败", e);}}// 打开会话(false=不自动提交,手动控制事务)public static SqlSession openSession() {return sqlSessionFactory.openSession(false);}
}

第三步:Service 层统一管理事务 —— 保证业务原子性

Service 层是业务逻辑层,负责组合 DAO 操作并统一控制事务。以转账业务为例,用 try-catch-finally 确保:

  • 无异常:提交事务;
  • 有异常:回滚事务;
  • 最终:关闭 SqlSession 释放资源。
package com.powernode.bank.service.impl;import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;public class AccountServiceImpl implements AccountService {private AccountDao accountDao = new AccountDaoImpl();@Overridepublic void transfer(String fromActno, String toActno, double amount) {SqlSession sqlSession = null;try {// 1. 统一获取SqlSession(整个转账业务共用一个会话=同一个事务)sqlSession = SqlSessionUtil.openSession();// 2. 校验转出账户Account fromAccount = accountDao.selectAccountByActno(sqlSession, fromActno);if (fromAccount == null) {throw new RuntimeException("转出账户不存在!");}if (fromAccount.getBalance() < amount) {throw new RuntimeException("余额不足!");}// 3. 转出账户扣款fromAccount.setBalance(fromAccount.getBalance() - amount);accountDao.updateByActbo(sqlSession, fromAccount);// 4. 校验转入账户Account toAccount = accountDao.selectAccountByActno(sqlSession, toActno);if (toAccount == null) {throw new RuntimeException("转入账户不存在!");}// 5. 转入账户入账toAccount.setBalance(toAccount.getBalance() + amount);accountDao.updateByActbo(sqlSession, toAccount);// 6. 无异常提交事务sqlSession.commit();System.out.println("转账成功!");} catch (Exception e) {// 7. 有异常回滚事务(关键:保证原子性)if (sqlSession != null) {sqlSession.rollback();}System.out.println("转账失败:" + e.getMessage());throw e; // 向上抛出异常,便于前端处理} finally {// 8. 最终关闭SqlSession(释放资源,必须在finally中执行)if (sqlSession != null) {sqlSession.close();}}}
}

四、测试验证:事务正常工作

  1. 正常场景:转出账户余额充足、转入账户存在时,转账成功,两个账户余额正确更新,事务提交。
  2. 异常场景
    • 转出账户不存在 / 余额不足:事务回滚,账户余额无变化;
    • 入账时抛出异常:扣款操作会随事务回滚,不会出现 “单边账”。
  3. 错误日志消失:不再出现 “事务已关闭无法提交” 的 500 错误,功能稳定运行。

五、踩坑总结:3 个关键经验

  1. 事务管理层级:Service 层负责,DAO 层不插手DAO 层的核心职责是 “执行 SQL”,事务控制必须放在 Service 层 —— 因为 Service 层是业务逻辑的聚合点,能确保多个 DAO 操作在同一个事务中,保证原子性。

  2. SqlSession 生命周期:先提交 / 回滚,再关闭牢记 MyBatis 事务操作顺序:获取SqlSession → 执行业务 → 提交/回滚 → 关闭SqlSession,关闭后绝对不能再操作事务。

  3. 事务原子性:核心业务必须共用一个 SqlSession涉及多步数据库操作的业务(如转账、下单支付),必须让所有操作共用一个 SqlSession,否则无法保证 “要么全成,要么全败”,容易出现数据一致性问题。

http://www.dtcms.com/a/590639.html

相关文章:

  • 人工智能训练师考试1.1.1
  • 自己做免费网站的视频搭建一个视频网站
  • php网站开发环境配置企业网站建设的基本标准
  • 建电子商务网站注意事项自己怎么做网站免费的
  • 咖啡店网站建设模版上海闵行区邮编
  • 湖南省建设银行网站6大连做网站比较好的公司
  • 三亚做网站多少钱一平方网站建设项目推文
  • 一键安装Claude Code脚本
  • 太平洋保险网站域名建设网站
  • 分类信息网站建设模板wordpress 显示小工具栏
  • 网站不备案做seo没用网站顶部怎么做新浪链接
  • 平台类网站建设胡方案明细wordpress 文章 插件
  • C语言算法入门:从生活到编程的思维转变
  • 硅基计划6.0 JavaEE 肆 网络网络编程
  • 南宁网站建设建站系统wordpress仿微博发文插件
  • 邯郸推广网站建设哪个好济南网站免费制作
  • 在线制作图片热区搜索引擎优化的主题
  • 字符串贪心:字典序 回文 括号
  • 实现AI和BI整合的初步思路和探索
  • 徐州网站关键词各类手机网站建设
  • 做音乐网站赚钱吗中信云做网站
  • 兰州网站建设平台分析指数函数图像
  • 从零掌握U-Net数据集训练:原理到实战的完整指南
  • 石家庄行业网站建设阿里巴巴官网入口
  • 来广营做网站公司游戏平台搭建
  • 【数值分析】12-非线性方程的求根方法-习题(1-8)
  • 焦作网站建设设计公司上海建设工程招投标在什么网站
  • 惠州建设网站开发汕头达濠
  • 位置编码演进史:SIN → ALiBi → RoPE → PI → NTK → YARN
  • 网站建设是必须的吗苏州工业园区两学一做教育网站