MySQL事务与Spring事务的关系
- 1. MySQL事务(数据库层)
- 2. Spring事务(应用层)
- 3. 两者关系详解
- 3.1 层级关系
- 3.2 Spring对MySQL事务的封装
- 4.Spring事务的工作流程
- 4.1 Spring在开启事务时做了什么?
- 4.2 Spring何时关闭自动提交?
- 4.3 Spring对自动提交的设置是会话级别的吗?
- 4.4 与手动使用命令行的对比
- 5.Spring事务的本质是线程与连接的绑定吗?
MySQL事务是数据库层面的原生事务机制,而Spring事务是应用层基于MySQL事务(或其他数据库事务)构建的声明式/编程式事务抽象,也就是说,Sprnig事务是 数据库事务在 Java框架层面的封装和增强,本质上还是依赖数据库的事务机制。
1. MySQL事务(数据库层)
-- MySQL原生事务示例STARTTRANSACTION;UPDATEaccountsSETbalance=balance-100WHEREid=1;UPDATEaccountsSETbalance=balance+100WHEREid=2;COMMIT;-- 或 ROLLBACKMySQL的事务模型:
- 自动提交模式(
autocommit):MySQL默认每个SQL语句都是一个独立事务,执行后自动提交。这就是“自动提交”模式(autocommit=1)。 - 显式开启事务:当你执行
BEGIN、START TRANSACTION或 设置autocommit=0时,就进入了显式事务模式。后续多个SQL将成为一个整体,直到你执行COMMIT或ROLLBACK。
2. Spring事务(应用层)
这篇文章的最后会说明使用Spring事务的3种方式(本质上就两种,声明式和编程式)
- 事务模板TransactionTemplate、事务管理器和DataSource数据源三者的关系
下面给出一个例子,使用的是 Spring的声明式事务(@Transactional)
@Service@Transactional// Spring事务注解publicclassTransferService{@AutowiredprivateAccountRepositoryrepository;publicvoidtransfer(Longfrom,Longto,BigDecimalamount){repository.debit(from,amount);repository.credit(to,amount);}}3. 两者关系详解
3.1 层级关系
应用层:Spring事务管理器 (PlatformTransactionManager) ↓ 驱动层:JDBC事务 (Connection.setAutoCommit(false)) ↓ 数据库层:MySQL事务引擎 (InnoDB)3.2 Spring对MySQL事务的封装
// Spring的事务管理器实际是对JDBC连接的封装publicclassDataSourceTransactionManagerimplementsPlatformTransactionManager{@OverrideprotectedvoiddoBegin(Objecttransaction,TransactionDefinitiondefinition){Connectioncon=dataSource.getConnection();con.setAutoCommit(false);// 关闭自动提交 → 开启MySQL事务con.setTransactionIsolation(definition.getIsolationLevel());}}4.Spring事务的工作流程
4.1 Spring在开启事务时做了什么?
在【3.2 Spring对MySQL事务的封装】小节,我们可以看到 事务管理器DataSourceTransactionManager 中有这样一行代码:
con.setAutoCommit(false);// 关闭自动提交 → 开启MySQL事务Q:Spring在开启事务时做了什么?
A:Spring会(在必要时)帮我们 关闭自动提交,但处理得非常智能。
整个过程如下图所示,清晰地展示了Spring如何在一个事务生命周期内,精细地管理 数据库连接 的自动提交状态:
上面这个Spring事务流程,我们可以从【事务模板TransactionTemplate】的核心方法execute(TransactionCallback action)看起,
TransactionTemplate#execute()// 1- 入口↓AbstractPlatformTransactionManager#getTransaction()// 获取事务↓AbstractPlatformTransactionManager#startTransaction()↓DataSourceTransactionManager#doBegin()// 这个方法内部关闭自动提交if(con.getAutoCommit()){txObject.setMustRestoreAutoCommit(true);con.setAutoCommit(false);}↓TransactionTemplate#execute()// 2- 这个方法会执行回调(也就是我们的业务逻辑)↓// 成功则执行commit(); 3- 失败则执行rollback↓DataSourceTransactionManager#doCleanupAfterCompletion// 4- 不管是commit还是rollback,都会调用这个方法,而这个方法内部会开启自动提交if(txObject.isMustRestoreAutoCommit()){con.setAutoCommit(true);}4.2 Spring何时关闭自动提交?
当一个需要事务的方法被调用(例如,传播行为为REQUIRED且当前没有事务),事务管理器(DataSourceTransactionManager)在doBegin()方法中,会从连接池拿到一个连接,然后立即执行con.setAutoCommit(false)。这对应于MySQL的START TRANSACTION。
4.3 Spring对自动提交的设置是会话级别的吗?
- 是,但Spring做了完美的封装和恢复。
setAutoCommit(false)确实是针对这个Connection会话的设置。- 关键点:Spring不会让这个设置“污染”连接。在事务结束(提交或回滚)后,Spring会立刻执行
con.setAutoCommit(true),将连接恢复为默认的自动提交模式,然后再将连接归还给连接池。 - 为什么这么做?因为连接池中的连接是重用的。如果连接带着
autoCommit=false的状态被其他不声明事务的业务代码拿到,它的所有操作都会意外地处于一个未提交的长事务中,导致数据看不到、锁无法释放等严重问题。Spring的“恢复”机制保证了连接状态的干净。
4.4 与手动使用命令行的对比
| 操作 | 手动命令行 | Spring 声明式事务 |
|---|---|---|
| 开启事务 | START TRANSACTION;或SET autocommit=0; | DataSourceTransactionManager.doBegin()中调用con.setAutoCommit(false) |
| 提交事务 | COMMIT; | 事务成功后,调用con.commit() |
| 回滚事务 | ROLLBACK; | 发生异常时,调用con.rollback() |
| 恢复状态 | 需手动SET autocommit=1; | 自动在提交/回滚后调用con.setAutoCommit(true) |
总结:Spring事务是基于MySQL事务的【高级抽象】,提供了更便捷的声明式编程模型和额外的企业级特性,但最终都是通过MySQL的事务引擎实现数据一致性保证。
5.Spring事务的本质是线程与连接的绑定吗?
完全正确,这是Spring管理事务的基石。
- 1. 核心机制:
- Spring事务管理的核心,就是通过
事务上下文TransactionSynchronizationManager,将当前开启的事务对应的 数据库连接(Connection)绑定到 当前执行线程(ThreadLocal)上。 - Spring 事务上下文(TransactionSynchronizationManager)基于 ThreadLocal 实现,无法跨线程传播。所以说,在使用线程池时要注意:
提交到线程池的任务,其事务管理完全独立于主线程,多线程会带来多个独立的事务(因为每个线程有自己独立的数据库连接和事务上下文)。 - 由此可知,Spring事务传播行为解决的是在单一线程中,多个事务方法(如
methodA调用了methodB)相互调用时,事务边界应该如何定义和传播。 - DataSourceTransactionManager#doGetTransaction()。
- Spring事务管理的核心,就是通过
- 2. 后续所有操作:在同一线程、同一事务内的所有数据库操作(通过MyBatis、JdbcTemplate等),都会从
ThreadLocal中获取这个被绑定的、唯一的连接,从而保证它们都在同一个数据库事务中执行。 - 3. 动态数据源联动:
DataSourceAspect先于事务拦截器执行,将数据源Key(如SHARDING)设置到ThreadLocal。当事务管理器获取连接时,动态数据源通过determineCurrentLookupKey()读取这个Key,从而路由到正确的物理连接,再把这个物理连接绑定到线程。关于数据源动态路由见文章:Sharding分库分表复杂SQL之数据源路由
下面这个是上面Spring事务工作流程图的 mermaid代码:
// mermaidsequenceDiagram participant SC asServiceCodeparticipant TI asTransactionInterceptorparticipant TM asDataSourceTransactionManagerparticipant CP as 数据库连接池 participantConnas 物理ConnectionparticipantMySQLSC->>TI:调用@Transactional方法 TI->>TM:getTransaction()TM->>CP:获取连接(getConnection)CP->>Conn:取出/新建连接NoteoverConn,MySQL:连接初始状态:<br/>autoCommit=true(默认)TM->>Conn:conn.setAutoCommit(false)Noteover TM,Conn:关键步骤:关闭自动提交<br/>(开启实物事务) TM->>TM:将连接绑定到当前线程 TM-->>TI:返回事务状态 TI->>SC:执行业务SQL SC->>TM:(框架)获取线程绑定的连接 TM-->>SC:返回连接 SC->>MySQL:执行INSERT/UPDATE...SC-->>TI:业务执行完毕 alt 成功 TI->>TM:commit()TM->>Conn:conn.commit()TM->>Conn:conn.setAutoCommit(true)else失败 TI->>TM:rollback()TM->>Conn:conn.rollback()TM->>Conn:conn.setAutoCommit(true)endNoteover TM,Conn:关键清理:恢复连接的原始状态 TM->>TM:解绑连接,释放线程资源 TM->>CP:归还连接(close)NoteoverConn,CP:连接状态已恢复为<br/>autoCommit=true,可供下次使用