本文通过分析Spring 事务的源码,说明@Transactional Timeout 参数设置的一些问题。
从问题开始,下面两段代码,事务是否都能正常的回滚?
timeout 参数大于3
代码片段1
@Transactional(rollbackFor = Exception.class , propagation = Propagation.REQUIRED,timeout = 3)
public int timeout(int timeout,int blogId){
jdbcTemplate.execute("DELETE FROM blog where id = "+blogId);
Sleep.second(timeout);
jdbcTemplate.execute("SELECT 1");
return timeout;
}
代码片段2
@Transactional(rollbackFor = Exception.class , propagation = Propagation.REQUIRED,timeout = 3)
public int timeout(int timeout , int blogId){
jdbcTemplate.execute("DELETE FROM blog where id = "+blogId);
Sleep.second(timeout);
return timeout;
}
按照我们的认知,上面的代码都应该因为超时抛出异常从而触发 rollback ,但是实际运行结果是 代码片段2 不会回滚,如果在生产环境意味着数据会真实的被删除。
带着问题来看源码
- 首先我们从Timeout的异常堆栈追踪到异常抛出的代码
![](https://img.haomeiwen.com/i1639948/bad6553e2ca27260.png)
- 定位到ResourceHolderSupport#checkTransactionTimeout 方法,查看源码
![](https://img.haomeiwen.com/i1639948/2307ba159eb2b9b8.png)
通过源码可以看到,源码通过一个 dealline (Date) 与当前时间比较来判断是否超时,那么这会就产生了两个疑问:
- dealline 在哪里设置的?
- 什么时候调用checkTransactionTimeout?
- 跟踪 dealline 属性,找到设置的方法 ResourceHolderSupport#setTimeoutInMillis,通过Debug面板,追踪调用堆栈,可以定位到DataSourceTransactionManager#doBegin方法
![](https://img.haomeiwen.com/i1639948/030b73fa10ccce65.png)
-
查看 DataSourceTransactionManager#doBegin 方法,定位到设置timeout的代码端,到这里我们第一个问题得到了答案
4.png
-
通过ResourceHolderSupport#checkTransactionTimeout堆栈找到调用方法
- DataSourceUtils#applyTimeout
- JdbcTemplate#applyStatementSettings
- JdbcTemplate#execute
![](https://img.haomeiwen.com/i1639948/a5e6ee134908e501.png)
![](https://img.haomeiwen.com/i1639948/20dbab2754b43b98.png)
![](https://img.haomeiwen.com/i1639948/5c17e34e10513ce1.png)
从这里就能清楚的知道, checkTransactionTimeout 是在每次执行SQL的时候被调用的,这也就能说明为什么代码片段1可以正常回滚了,因为代码片段1在最后执行了一条无意义的SQL “SELECT 1” 触发了 checkTransactionTimeout 的检查。
整个检查过程抽象为流程图就是下面这样
![](https://img.haomeiwen.com/i1639948/d9327800be33e6e1.png)
网友评论