part4-datasource-transaction-manager
约 4381 字大约 15 分钟
2026-05-24
系列文章:《深入Spring事务与数据源:从注解到数据库连接的全链路解析》
系列一第4篇:DataSourceTransactionManager源码深度解析
基于版本:Spring Framework 5.3.x
前置阅读:第三篇:AbstractPlatformTransactionManager的事务生命周期
前言
上篇文章咱们把AbstractPlatformTransactionManager的源码翻了个底朝天,七种传播行为、事务挂起恢复、提交回滚流程,全都搞清楚了。
但是有个关键问题,咱们只是一笔带过——数据源(DataSource)是怎么参与进来的?
你还记得上篇文章看到的doBegin()方法吗?里头有这么一行:
Connection newCon = obtainDataSource().getConnection();这行代码看着简单,但是背后涉及的东西可不少:
obtainDataSource()返回的是什么?getConnection()是怎么获取连接的?- 连接获取后,是怎么和事务绑定的?
- ThreadLocal在这里头起了什么作用?
今天,咱们就来深入DataSourceTransactionManager的源码,看看它是怎么把事务管理和数据源连接串在一起的。这篇文章,就是连接"事务"和"数据源"两个世界的桥梁!
一、Spring Boot中DataSource的自动配置
在深入DataSourceTransactionManager之前,咱们还是得先搞明白:DataSource是从哪儿来的?
你又得回到Spring Boot的自动配置了。别嫌我啰嗦,这个真的很重要!
核心入口1:DataSourceAutoConfiguration
文件位置:spring-boot-autoconfigure/DataSourceAutoConfiguration.java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(DataSource.class) // 如果没有手动配置DataSource
@EnableConfigurationProperties(DataSourceProperties.class) // 绑定配置属性
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {
}
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class,
DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class,
DataSourceConfiguration.Generic.class,
DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {
}
}看到没?这里根据条件导入了不同的配置。如果你用的是HikariCP(Spring Boot 2.x默认),就会导入DataSourceConfiguration.Hikari。
核心入口2:DataSourceConfiguration.Hikari
文件位置:spring-boot-autoconfigure/DataSourceConfiguration.java
static class Hikari {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public HikariDataSource dataSource(DataSourceProperties properties) {
// 创建HikariDataSource
HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
}如果你用的是Druid,配置就得更灵活一些:
扩展场景1:如何在Spring Boot中使用Druid数据源?
@Configuration
public class DruidDataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid")
public DataSource dataSource() {
return new DruidDataSource();
}
}然后在application.yml中配置:
spring:
datasource:
druid:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
initial-size: 5
max-active: 20
min-idle: 5扩展场景2:多数据源怎么配置?
@Configuration
public class MultiDataSourceConfig {
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return new DruidDataSource();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return new DruidDataSource();
}
@Bean
@Primary
public PlatformTransactionManager primaryTxManager() {
return new DataSourceTransactionManager(primaryDataSource());
}
@Bean
public PlatformTransactionManager secondaryTxManager() {
return new DataSourceTransactionManager(secondaryDataSource());
}
}每个数据源对应一个事务管理器,用的时候通过@Transactional("primaryTxManager")指定。
二、DataSourceTransactionManager的整体结构
好了,DataSource的来源搞清楚了,现在正式进入咱们的主题——DataSourceTransactionManager。
先看看它的类结构:
核心类:DataSourceTransactionManager
文件位置:spring-jdbc/DataSourceTransactionManager.java
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, InitializingBean, Serializable {
// 数据源(核心成员变量)
@Nullable
private DataSource dataSource;
// 是否恩将仇报
private boolean enforceReadOnly = false;
// ========== 构造方法 ==========
public DataSourceTransactionManager() {
setNestedTransactionAllowed(true); // 默认支持嵌套事务
}
public DataSourceTransactionManager(DataSource dataSource) {
this();
setDataSource(dataSource); // 设置数据源
afterPropertiesSet();
}
// ========== 设置数据源 ==========
public void setDataSource(@Nullable DataSource dataSource) {
this.dataSource = dataSource;
}
@Nullable
public DataSource getDataSource() {
return this.dataSource;
}
// ========== 抽象方法实现 ==========
@Override
protected Object doGetTransaction() {
// 获取当前线程的事务对象
}
@Override
protected boolean isExistingTransaction(Object transaction) {
// 判断是否存在现有事务
}
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
// 开启事务
}
@Override
protected void doCommit(DefaultTransactionStatus status) {
// 提交事务
}
@Override
protected void doRollback(DefaultTransactionStatus status) {
// 回滚事务
}
// ... 其他方法
}看到没?这个类干的事儿其实很简单:
- 持有一个DataSource引用
- 实现父类的抽象方法(doGetTransaction、doBegin、doCommit等)
- 从DataSource获取连接
- 把连接和事务绑定到一起
就这么简单!
三、doGetTransaction:获取事务对象
好,咱们从第一个抽象方法开始:
核心方法1:doGetTransaction
文件位置:spring-jdbc/DataSourceTransactionManager.java
@Override
protected Object doGetTransaction() {
// ========== 第一步:创建事务对象 ==========
// 创建一个空的DataSourceTransactionObject
// 这个对象用来保存连接持有者(ConnectionHolder)
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
// 设置是否允许嵌套事务
// 这个值在构造方法里已经设置为true
txObject.setNestedTransactionAllowed(isNestedTransactionAllowed());
// ========== 第二步:检查ThreadLocal中是否已有连接 ==========
// ★★★★★ 核心:从TransactionSynchronizationManager获取当前线程的连接 ★★★★★
// 如果当前线程已经开启了事务,这里就能获取到连接
// 如果没有,就返回null
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager
.getResource(this.dataSource);
// ========== 第三步:设置连接持有者 ==========
// 如果获取到连接,设置到事务对象上
// 注意:这里只是引用,不是新建
txObject.setConnectionHolder(conHolder, false);
return txObject;
}这个方法看着简单,但是有几个关键点:
关键点1:DataSourceTransactionObject是啥?
核心类:DataSourceTransactionObject
文件位置:spring-jdbc/DataSourceTransactionManager.java
private static class DataSourceTransactionObject extends JdbcTransactionObjectSupport {
// 是否是新连接持有者
private boolean newConnectionHolder;
// 是否必须回滚
private boolean mustRestoreAutoCommit;
public DataSourceTransactionObject() {
}
// 设置连接持有者
public void setConnectionHolder(@Nullable ConnectionHolder connectionHolder,
boolean newConnectionHolder) {
// 调用父类方法设置连接持有者
super.setConnectionHolder(connectionHolder);
// 标记是否是新连接
this.newConnectionHolder = newConnectionHolder;
}
// 判断是否是新连接
public boolean isNewConnectionHolder() {
return this.newConnectionHolder;
}
// ... 其他方法
}这个对象其实就是个容器,用来装ConnectionHolder。
关键点2:ConnectionHolder又是啥?
核心类:ConnectionHolder
文件位置:spring-jdbc/ConnectionHolder.java
public class ConnectionHolder extends ResourceHolderSupport {
// 当前连接
@Nullable
private Connection connection;
// 是否持有连接
private boolean connectionActive;
// 保存的savepoint(用于嵌套事务)
@Nullable
private Savepoint savepoint;
public ConnectionHolder(Connection connection) {
this.connection = connection;
this.connectionActive = true;
}
@Nullable
public Connection getConnection() {
return this.connection;
}
public void setConnection(@Nullable Connection connection) {
this.connection = connection;
this.connectionActive = (connection != null);
}
public boolean hasConnection() {
return (this.connection != null);
}
public boolean isTransactionActive() {
return this.connectionActive;
}
// ... 其他方法
}ConnectionHolder就是个连接的包装类,里头保存了实际的JDBC连接。
关键点3:TransactionSynchronizationManager.getResource()是咋工作的?
这个问题咱们留到第5篇详细讲,这里先简单说一下:
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
// 从ThreadLocal中获取
Object value = doGetResource(actualKey);
return value;
}
private static Object doGetResource(Object actualKey) {
// 获取ThreadLocal中的Map
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
// 以dataSource为key,获取ConnectionHolder
Object value = map.get(actualKey);
return value;
}简单说,就是以DataSource为key,从ThreadLocal的Map中获取ConnectionHolder。
扩展场景3:为什么同一个线程的多个事务方法能共享同一个连接?
就是因为doGetTransaction()会从ThreadLocal中检查是否已有连接。如果有,就直接用;如果没有,才会创建新的。
@Transactional
public void methodA() {
// 第一次调用,ThreadLocal中没有连接
// doBegin()会创建新连接,绑定到ThreadLocal
methodB(); // 调用另一个事务方法
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// doGetTransaction()会从ThreadLocal获取methodA创建的连接
// 所以两个方法用的是同一个连接,同一个事务
}四、doBegin:开启事务并绑定连接
好,现在来看最核心的方法——doBegin():
核心方法2:doBegin
文件位置:spring-jdbc/DataSourceTransactionManager.java
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
// 类型转换
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
// ========== 第一步:检查是否需要获取新连接 ==========
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
// ★★★★★ 核心:从数据源获取新连接 ★★★★★
// 这个调用会:
// 1. 从连接池获取连接
// 2. 或者创建新连接(如果没有连接池)
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
// 包装成ConnectionHolder
// 第二个参数true表示这是新创建的连接持有者
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
// ========== 第二步:标记连接为事务同步 ==========
// 这个标记表示连接已经和事务同步了
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
// 获取实际的JDBC连接
con = txObject.getConnectionHolder().getConnection();
// ========== 第三步:设置只读和隔离级别 ==========
// 保存旧的只读标记(用于恢复)
Boolean previousReadOnly = determinePreviousReadOnly(txObject, con);
// 设置只读标记
if (definition.isReadOnly()) {
try {
con.setReadOnly(true);
}
catch (SQLException ex) {
// 某些数据库不支持设置只读
// 比如MySQL,会忽略这个设置
logger.debug("Could not set read-only for transaction", ex);
}
}
// 保存旧的隔离级别(用于恢复)
Integer previousIsolationLevel = determinePreviousIsolationLevel(txObject, con);
// 设置隔离级别(如果指定了)
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
int currentIsolation = con.getTransactionIsolation();
if (currentIsolation != definition.getIsolationLevel()) {
// 保存旧值
txObject.setPreviousIsolationLevel(currentIsolation);
// 设置新值
con.setTransactionIsolation(definition.getIsolationLevel());
}
}
// ========== 第四步:关闭自动提交(最关键!)==========
try {
// ★★★★★ 核心:关闭自动提交,开启手动事务管理 ★★★★★
// 这一步是事务的起点!
// 之后所有的SQL都不会自动提交,必须手动commit或rollback
con.setAutoCommit(false);
}
catch (Throwable ex) {
logger.debug("Could not set autocommit", ex);
// 如果设置失败,释放连接
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
throw new CannotCreateTransactionException(
"Could not set autocommit: " + ex.getMessage(), ex);
}
// ========== 第五步:设置超时时间 ==========
// 如果配置了超时时间,设置到ConnectionHolder上
// 超时后会强制回滚
txObject.getConnectionHolder().setTimeoutInSeconds(
determineTimeout(definition));
// 如果需要,恢复只读标记
if (previousReadOnly != null) {
txObject.setPreviousReadOnly(previousReadOnly);
}
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
// ========== 第六步:绑定连接到ThreadLocal ==========
// ★★★★★ 核心:将连接绑定到TransactionSynchronizationManager ==========
// 这样后续的所有操作都能获取到同一个连接
// 这是事务传播的基础!
TransactionSynchronizationManager.bindResource(
obtainDataSource(), txObject.getConnectionHolder());
// ========== 第七步:注册同步回调 ==========
// 注册一个连接同步回调,用于在事务完成时清理连接
prepareTransactionalConnection(con, definition, txObject.getConnectionHolder());
// ========== 第八步:标记事务活跃 ==========
// 标记连接持有者为事务活跃状态
txObject.getConnectionHolder().setTransactionActive(true);
}
catch (Throwable ex) {
// 如果开启事务失败,释放连接
if (!txObject.hasConnectionHolder()) {
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException(
"Could not open JDBC Connection for transaction", ex);
}
}这个方法可以说是整个JDBC事务管理的核心!咱们一步一步拆解:
4.1 第一步:获取连接
Connection newCon = obtainDataSource().getConnection();这行代码做了啥?
obtainDataSource():获取DataSourcegetConnection():从DataSource获取连接
对于连接池来说,getConnection()会从连接池里拿一个空闲连接;如果没有连接池,就会创建一个新连接。
扩展场景4:从连接池获取连接和直接创建连接有什么区别?
// 不使用连接池(直接创建连接)
Connection con = DriverManager.getConnection(url, username, password);
// 每次都要建立TCP连接,很慢!
// 使用连接池
Connection con = dataSource.getConnection();
// 从池中获取已建立的连接,很快!连接池的好处是:
- 复用连接:避免频繁建立/断开TCP连接
- 控制连接数:防止连接数过多导致数据库崩溃
- 连接检测:自动检测无效连接
4.2 第四步:关闭自动提交
con.setAutoCommit(false);这一步是事务的起点!
MySQL默认的autocommit是true,每条SQL都会自动提交。关闭后,所有的SQL都必须手动提交:
// autocommit = true(默认)
INSERT INTO user VALUES (1, '张三'); // 自动提交
INSERT INTO user VALUES (2, '李四'); // 自动提交
// autocommit = false
INSERT INTO user VALUES (1, '张三'); // 不提交
INSERT INTO user VALUES (2, '李四'); // 不提交
connection.commit(); // 一起提交4.3 第六步:绑定到ThreadLocal
TransactionSynchronizationManager.bindResource(
obtainDataSource(), txObject.getConnectionHolder());这一步是事务传播的基础!
绑定后,同一个线程内的其他事务方法,都能通过TransactionSynchronizationManager.getResource(dataSource)获取到这个连接。
扩展场景5:如果不绑定到ThreadLocal会怎样?
那事务传播就废了!每个方法都会获取新连接,根本没法共享事务。
// 没有ThreadLocal绑定
@Transactional
public void methodA() {
Connection con1 = dataSource.getConnection(); // 连接1
methodB();
}
@Transactional
public void methodB() {
Connection con2 = dataSource.getConnection(); // 连接2(新的!)
// con1和con2是不同的连接,不在同一个事务中!
}有了ThreadLocal绑定:
// 有ThreadLocal绑定
@Transactional
public void methodA() {
// doBegin()获取连接1,绑定到ThreadLocal
methodB();
}
@Transactional
public void methodB() {
// doGetTransaction()从ThreadLocal获取连接1
// 所以两个方法用的是同一个连接!
}五、doCommit:提交事务
提交事务就简单多了:
核心方法3:doCommit
文件位置:spring-jdbc/DataSourceTransactionManager.java
@Override
protected void doCommit(DefaultTransactionStatus status) {
// 获取事务对象
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
// 获取连接
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
// ★★★★★ 核心:调用JDBC的commit方法 ★★★★★
con.commit();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not commit JDBC transaction", ex);
}
}就一行关键代码:con.commit(),把事务中的所有修改提交到数据库。
六、doRollback:回滚事务
回滚同样简单:
核心方法4:doRollback
文件位置:spring-jdbc/DataSourceTransactionManager.java
@Override
protected void doRollback(DefaultTransactionStatus status) {
// 获取事务对象
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
// 获取连接
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
// ★★★★★ 核心:调用JDBC的rollback方法 ★★★★★
con.rollback();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}一行关键代码:con.rollback(),撤销事务中的所有修改。
七、doCleanupAfterCompletion:清理事务
事务完成后,需要清理资源:
核心方法5:doCleanupAfterCompletion
文件位置:spring-jdbc/DataSourceTransactionManager.java
@Override
protected void doCleanupAfterCompletion(Object transaction) {
// 类型转换
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// ========== 第一步:从ThreadLocal中移除连接 ==========
// ★★★★★ 核心:解绑连接 ★★★★★
// 如果不清理,会导致内存泄漏!
TransactionSynchronizationManager.unbindResourceIfPossible(obtainDataSource());
// ========== 第二步:恢复连接状态 ==========
Connection con = txObject.getConnectionHolder().getConnection();
try {
// 恢复自动提交
// ★★★★★ 重要:连接要归还到连接池,必须恢复默认状态 ★★★★★
con.setAutoCommit(true);
// 恢复只读标记
if (txObject.isMustRestoreAutoCommit()) {
con.setReadOnly(false);
}
// 恢复隔离级别
if (txObject.getPreviousIsolationLevel() != null) {
con.setTransactionIsolation(txObject.getPreviousIsolationLevel());
}
}
catch (Throwable ex) {
logger.debug("Could not reset JDBC Connection after transaction", ex);
}
// ========== 第三步:释放连接 ==========
if (txObject.isNewConnectionHolder()) {
// 如果是新连接,释放到连接池
DataSourceUtils.releaseConnection(con, obtainDataSource());
}
// ========== 第四步:清理连接持有者 ==========
// 清空ConnectionHolder中的连接引用
txObject.getConnectionHolder().clear();
}清理做了四件事:
- 从ThreadLocal解绑(防止内存泄漏)
- 恢复连接状态(autocommit=true)
- 释放连接(归还到连接池)
- 清空引用(帮助GC回收)
扩展场景6:如果不清理ThreadLocal会怎样?
会导致内存泄漏!
// 错误示例:没有清理ThreadLocal
TransactionSynchronizationManager.bindResource(dataSource, connectionHolder);
// 方法结束,但没有unbindResource
// 结果:
// 1. ThreadLocal中一直持有ConnectionHolder的引用
// 2. ConnectionHolder持有Connection的引用
// 3. Connection是本地方法创建的对象,不会被GC回收
// 4. 内存泄漏!所以,清理ThreadLocal是必须的!
八、isExistingTransaction:判断是否有现有事务
这个方法很简单:
核心方法6:isExistingTransaction
文件位置:spring-jdbc/DataSourceTransactionManager.java
@Override
protected boolean isExistingTransaction(Object transaction) {
// 类型转换
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// 判断条件:
// 1. 有ConnectionHolder
// 2. 连接是事务活跃的
return (txObject.getConnectionHolder() != null
&& txObject.getConnectionHolder().isTransactionActive());
}就两个条件,很简单。
九、doSuspend和doResume:挂起和恢复事务
挂起和恢复也很简单:
核心方法7:doSuspend
文件位置:spring-jdbc/DataSourceTransactionManager.java
@Override
protected Object doSuspend(Object transaction) {
// 类型转换
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// 清空事务对象的连接引用
txObject.setConnectionHolder(null);
// ★★★★★ 核心:从ThreadLocal解绑连接并返回 ★★★★★
// 这个连接会被保存到SuspendedResourcesHolder中
return TransactionSynchronizationManager.unbindResource(obtainDataSource());
}核心方法8:doResume
文件位置:spring-jdbc/DataSourceTransactionManager.java
@Override
protected void doResume(@Nullable Object transaction, Object suspendedResources) {
// 类型转换
ConnectionHolder conHolder = (ConnectionHolder) suspendedResources;
// ★★★★★ 核心:将连接重新绑定到ThreadLocal ★★★★★
TransactionSynchronizationManager.bindResource(obtainDataSource(), conHolder);
}挂起就是解绑,恢复就是重新绑定,就这么简单!
十、完整的执行流程图
咱们画个图,把DataSourceTransactionManager的完整流程串起来:
调用:tm.getTransaction(txAttr)
↓
┌──────────────────────────────────────────┐
│ DataSourceTransactionManager │
│ │
│ 1. doGetTransaction() │
│ → 创建DataSourceTransactionObject │
│ → 从ThreadLocal获取ConnectionHolder │
│ → 如果有,说明已有事务 │
└─────────────┬────────────────────────────┘
↓
有现有事务?
↙ ↘
是 否
↓ ↓
┌─────────────┐ ┌─────────────────────┐
│ 加入现有 │ │ doBegin() │
│ 事务 │ │ │
│ │ │ 1. getConnection() │
│ │ │ → 从连接池获取 │
│ │ │ │
│ │ │ 2. setAutoCommit( │
│ │ │ false) │
│ │ │ │
│ │ │ 3. bindResource() │
│ │ │ → 绑定到Thread │
│ │ │ Local │
└──────┬──────┘ └──────────┬──────────┘
↓ ↓
└────────┬───────────┘
↓
┌──────────────────────────────────────┐
│ 执行业务方法 │
│ → 使用同一个Connection │
│ → 所有SQL在同一个事务中 │
└─────────────┬────────────────────────┘
↓ (commit)
┌──────────────────────────────────────┐
│ doCommit() │
│ → con.commit() │
│ → 提交所有修改 │
└──────────────────────────────────────┘
↓ (rollback)
┌──────────────────────────────────────┐
│ doRollback() │
│ → con.rollback() │
│ → 撤销所有修改 │
└──────────────────────────────────────┘
↓ (cleanup)
┌──────────────────────────────────────┐
│ doCleanupAfterCompletion() │
│ │
│ 1. unbindResource() │
│ → 从ThreadLocal移除 │
│ │
│ 2. setAutoCommit(true) │
│ → 恢复自动提交 │
│ │
│ 3. releaseConnection() │
│ → 归还到连接池 │
│ │
│ 4. clear() │
│ → 清空引用 │
└──────────────────────────────────────┘十一、关键问题解答
Q1:为什么要用ConnectionHolder包装Connection?
答案:因为需要保存额外的状态信息,比如:
- 是否是事务活跃状态
- 超时时间
- savepoint(嵌套事务用)
- 旧的隔离级别(用于恢复)
如果直接用Connection,这些信息没法保存。
Q2:ThreadLocal的key为什么是DataSource,而不是Connection?
答案:因为一个DataSource对应一个事务。如果用Connection做key,那不同的Connection就没法共享事务了。
Q3:多数据源场景下,ThreadLocal怎么管理多个连接?
答案:ThreadLocal里头存的是一个Map,key是DataSource,value是ConnectionHolder。所以可以存多个:
ThreadLocal<Map<Object, Object>> resources = new ThreadLocal<>();
// 主数据源的连接
map.put(dataSource1, connectionHolder1);
// 副数据源的连接
map.put(dataSource2, connectionHolder2);Q4:为什么要在finally块中清理资源?
答案:防止异常导致资源泄漏。不管方法是正常结束还是异常退出,finally块都会执行,确保资源被清理。
十二、总结与伏笔
通过这篇文章的分析,咱们搞清楚了DataSourceTransactionManager是怎么工作的:
- 获取事务:从ThreadLocal检查是否已有连接
- 开启事务:从数据源获取连接,关闭autocommit,绑定到ThreadLocal
- 提交事务:调用
con.commit() - 回滚事务:调用
con.rollback() - 清理资源:解绑ThreadLocal,恢复autocommit,释放连接
但是,咱们还有两个问题没深入:
问题1:getConnection()到底是怎么获取连接的?连接池是怎么工作的? 问题2:TransactionSynchronizationManager的ThreadLocal是怎么管理的?内存泄漏怎么防止?
问题1会在下一个系列《数据源与连接池全链路源码解析》中详细讲解,到时候咱们会深入到Druid、HikariCP的源码。
问题2则是咱们系列一最后一篇的主题——下一篇文章,咱们就来揭秘TransactionSynchronizationManager的ThreadLocal魔法,看看它是怎么做到线程安全、怎么防止内存泄漏、怎么支持事务同步回调的。
敬请期待系列一最终篇!
参考资料
- Spring Framework 5.3.x 源码
spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceTransactionManager.javaspring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHolder.java
- Spring Boot 2.7.x 源码
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java
- 《Spring源码深度解析》