jdbc一个connection对应的是一个事物
Spring事务管理中的Connection-Passing
对于层次划分清晰的应用来说,我们通常将事务管理放在Service层,而将数据访问逻辑放在Dao层,这样做的目的是不用因为将事务管理代码放在DAO层,而降低数据访问逻辑的重要性,也可以将Service层根据相应逻辑,来决定提交或者回滚事务。一般的Service对象可能需要在同一个业务方法中调用多个数据访问对象的方法。比如:
|
|
因为JDBC局部事务是控制是由java.sql.Connection来完成的,要保证两个DAO的数据访问处于一个事务中,我们需要保证他们使用的是同一个java.sql.Connection.
通常采用称为connection-passing的方式,即为当前同一个事务的各个dao的数据访问方法传递当前事务对应的同一个Connection。
传递java.sql.Connection,最好的办法是整个事务对应的java.sql.Connection实例放到统一的一个地方,但要保证每个业务请求的Connection又能各不干扰。或许你已经想到了ThreadLocal。对该类不理解的可以去看我之前的那篇文章ThreadLocal的一些理解。今天我们看看Spring是如何控制ThreadLocal为其服务的。
首先从开始事务进行分析
|
|
上面方法主要就是获取连接并设置事务的各种属性信息,关键的是将DataSource和txObject.getConnectionHolder()传入了bindResource中,ConnectionHolder对象就包装着本次事务所获取的连接。我们来看看bindResource
方法
##该方法在TransactionSynchronizationManager类中##
|
|
该方法我就不逐步分析了,如果熟悉ThreadLocal机制的同学一定也会很快理解。总的来说,该方法就是将传进来的key和value作为键值对存储在HashMap中,再把HashMap存到ThreadLocal中。此后每个线程从该ThreadLocal中get到的一定是属于自己线程的HashMap,从而取值。
ok,现在我们知道了Connection是如何绑定线程并放在Spring容器中,继续看是在何时需要获取该Connection的吧,我们给TransactionSynchronizationManager类中的getResource方法打上断点。
调用getResource方法来获取ConnectionHandler的时间点有下面这些:
- 执行sql的时候,会通过DataSourceUtils#doGetConnection方法调用getResource的连接,这里就不放代码了。
- 事务开启、提交或者回滚。
- 连接的释放
- 在获取DataSourceTransactionObject时,代码如下。
1 2 3 4 5 6 7 8 9
protected Object doGetTransaction() { DataSourceTransactionManager.DataSourceTransactionObject txObject = new DataSourceTransactionManager.DataSourceTransactionObject(); txObject.setSavepointAllowed(this.isNestedTransactionAllowed()); ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.dataSource); //跟踪可以发现其实从ThreadLocal中获得了与该线程绑定的Resource,而该Resource就是之前doBegin中bindResource的ConnectionHolder实例。 txObject.setConnectionHolder(conHolder, false); //将该ConnectionHolder实例注入到txObject return txObject; }
返回的DataSourceTransactionObject将作为判断是否存在当前事务的主要依据。代码如下:
|
|
DataSourceTransactionManager判断是否存在当前事务的两个标准就是ConnectionHolder是否为空和TransactionActive是否为true,如果之前该连接上调用了doBegin创建事务,则这里肯定会返回true。
完成本次事务的所有业务逻辑之后则会在提交事务完成后,调用TransactionSynchronizationManager
类的doUnbindResource
方法
|
|
该方法就是移除指定资源或者Map。
总结:
因为代理的原因,Spring的Connection-Passing机制确保每个被代理事务管理的方法中所有同一个线程(为什么要强调这个,因为事务过程的Connection就是用ThreadLocal管理的)
的对数据库操作都在同一个Connection(Session会话)中执行,因为提交和回滚都以Connection为单位。即不同的Connection提交和回滚不会影响另一个Connection的执行过程。
以上就是Spring利用ThreadLocal来保证每个线程调用的每个业务方法中使用的是同一个Connection,以确保事务的控制。
思考一下
|
|
如上会回滚注释1处的执行代码吗?
答案是不会。前面我们说过Connection是绑定在线程的。transfer方法是一个事务方法。Spring事务管理器在事务方法和事务结束
过程中都会获得绑定在该线程的Connection,因此事务的提交和回滚只针对该Connection有效,也就是说其他线程调用的数据访问方法
不会由当前事务方法的Connection管理。因此如上所示,注释1处的代码在另一个线程中执行,其Connection和当前transfer方法的事务
Connection大概率不是同一个。*(也有可能是同一个,有可能事务rollback之后释放连接,刚好轮到该线程获取上次释放的连接,我们可以设置执行延迟,
但无论如何已经是两个事务边界了。)因此,在断电之后,注释1处的代码并没有回滚。
理解Spring事务管理是由JDBC的Connection来确定事务边界的有助于理解后续的Spring事务处理分布式事务的局限性。因此分布式情况下,可能有多个操作
都运行在不同机器上的服务方法组合,因为我们需要知道所有方法的结果,并且进行全部提交和全部回滚,以确保一致性,而普通的事务管理无法做到这些,
如何有效地确保这些方法执行的正确性,当然这就属于分布式事务的范畴了,我们这里不做讨论。
面试题:
一个Controller调用两个Service,这两Service又都分别调用两个Dao,问其中用到了几个数据库连接池的连接?
分情况讨论:
- 如果这两个service,每个Service调用的两个dao都是在同一个线程同一个数据源并且开启了事务管理,则每个service都会使用一个Connection。
- 如果不在Spring事务管理下,则无论在不在同一个数据源在不在同一个线程,每次调用Dao执行sql,都会使用DataSourceUtils.doGetConnection获取一个连接,执行完之后释放。
所以该题目应该是用到2-4个数据库连接。数据源就是我们配置的DataSource。(即便一个数据源使用了多个Mysql数据库也是在一个连接中,url不指定Database,sql语句中指定Database,
例如spring.datasource.url=jdbc:mysql://localhost:3306
,此时就可以sql操作多个数据库,通过database.table,此时在同一个事务管理下的service,即便使用了两个Dao,
操作两个完全不同的数据库,d1.t1,d2.t2,但是因为在同一个数据源中,同一个线程中,则也会共用一个数据库连接,事务也会生效)
(分布式事务初了解)[http://www.importnew.com/26349.html]
(浅谈事务和一致性:刚性or柔性?)[https://juejin.im/post/5aa8b8636fb9a028c67567c6]
推荐阅读
-
Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?
-
Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?
-
jdbc一个connection对应的是一个事物
-
一个获得jdbc方式Connection的静态方法
-
javascript - 京东一个商品多个属性对应多个页面是怎么实现的
-
DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库
-
京东一个商品多个属性对应多个页面是怎么实现的
-
单例 - PHP程序运行的时候,既然是每一个HTTP请求对应一个数据库PDO连接对象实例
-
javascript - 京东一个商品多个属性对应多个页面是怎么实现的
-
一个jdbc connection连接对应一个事务