@Transactional踩坑记

总述

​ Spring在1.2引入@Transactional注解, 该注解的引入使得我们可以简单地通过在方法或者类上添加@Transactional注解,实现事务控制。 然而看起来越是简单的东西,背后的实现可能存在很多默认规则和限制。而对于使用者如果只知道使用该注解,而不去考虑背后的限制,就可能事与愿违,到时候线上出了问题可能根本都找不出啥原因。


踩坑记

1. 多数据源

事务不生效

背景介绍

​ 由于数据量比较大,项目的初始设计是分库分表的。于是在配置文件中就存在多个数据源配置。大致的配置类似下面:

<!-- 数据源A和事务配置 -->
<bean id="dataSourceA" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
....
</bean>
<bean id="dataSourceTxManagerA"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceA" />
</bean>
<!-- mybatis自动扫描生成Dao类的代码 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="annotationClass" value="com.rampage.mybatis.annotation.mapperDao" />
<property name="nameGenerator" ref="sourceANameGenerator" />
<property name="sqlSessionFactoryBeanName" value="sourceAsqlSessionFactory" />
<property name="basePackage" value="com.rampage" />
</bean>
<!-- 自定义的Dao名称生成器,prefix属性指定在bean名称前加上对应的前缀生成Dao -->
<bean id="sourceANameGenerator" class="com.rampage.mybatis.dao.namegenerator.MyNameGenerator">
<property name="prefix" value="sourceA" />
</bean>
<bean id="sourceAsqlSessionFactory" class="org.mybatis.spring.PathSqlSessionFactoryBean">
<property name="dataSource" ref="dataSourceA" />
</bean> <!-- 数据B和事务配置 -->
<bean id="dataSourceB" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
....
</bean>
<bean id="dataSourceTxManagerB"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceB" />
</bean>

​ 但是在实际部署的时候,因为是单机部署的,多个数据源实际上对应的是同一个库,不存在分布式事务的问题。所以在代码编写的时候,直接通过在@Transactional注解来实现事务。具体代码样例大致如下:

@Service
public class UserService {
@Resource("sourceBUserDao") // 其实这时候Dao对应的是sourceB
private UserDao userDao; @Transactional
public void update(User user) {
userDao.update(user);
// Other db operations
...
}
}

​ 这中写法的代码一直在线上运行了一两年,没有出过啥问题.....反而是我在做一个需求的时候,考虑到@Transactional注解里面的 数据库操作,如果没有同时成功或者失败的话,数据会出现混乱的情况。于是自己测试了一下,开启了这段踩坑之旅.....

原因分析:

​ 开始在网上搜了一下Transactional注解不支持多数据源, 于是我当时把所有数据库操作都采用sourceB作为前缀的Dao进行操作。结果测试一遍发现还是没有事务效果。没有什么是源码解决不了的,于是就开始debug源码,发现最终启动的事务管理器竟然是dataSourceTxManagerA。 难道和事务管理器声明的顺序有关?于是我调整了下xml配置文件中,事务管理器声明的顺序,发现事务生效了,因此得证。

​ 具体来说原因有以下两点:

  • @Transactional注解不支持多数据源的情况
  • 如果存在多个数据源且未指定具体的事务管理器,那么实际上启用的事务管理器是最先在配置文件中指定的(即先加载的)
解决办法:

​ 对于多数据下的事务解决办法如下:

  • @Transactional注解添加的方法内,数据库更新操作统一使用一个数据源下的Dao,不要出现多个数据源下的Dao的情况
  • 统一了方法内的数据源之后,可以通过@Transactional(transactionManager = "dataSourceTxManagerB")显示指定起作用的事务管理器,或者在xml中调节事务管理器的声明顺序

死循环问题

​ 这个问题其实也是多数据源导致的,只是更难分析原因。具体场景是:假设我的货仓里有1000个货物,我现在要给用户发货。每批次只能发100个。我的货物有一个字段来标识是否已经发过了,对于已经发过的货不能重新发(否则只能哭晕在厕所)!代码的实现是外层有一个while(true)循环去扫描是否还有未发过的货物,而发货作为整体的一个事务,具体代码如下:

@Transactional
public void deliverGoods(List<Goods> goodsList) { // 传入的参数是前面循环查出来的未发货的100个货物,作为一个批次统一发货
updateBatchId(goodsList, batchId); // 更新同批次货物的批次号字段
// do other things
updateGoodsStatusByBatchId(batchId, delivered); // 根据前面更新的批次号取修改数据库相关货物的发送状态为已发送
}

​ 从整体上来看,这段代码逻辑上没有任何问题。实际运行的时候却发现出现了死循环。还好测试及时发现,没有最终上线。那么具体原因是咋样的呢?

​ 出现这个问题的时候,配置文件的配置还是同前面一个问题一样的配置。即实际上@Transactional注解默认起作用的事务是针对dataSourceA的。然后跟进updateBatchId方法,发现其最终调用的方法采用的Dao是sourceA为前缀的Dao,而updateGoodsStatusByBatchId方法最终调用的Dao是sourceB为前缀的Dao。细细分析,我终于知道为啥了

最新文章

  1. HDU 5510---Bazinga(指针模拟)
  2. Provider:SSL提供程序,error:0 - 接收到的消息异常,或格式不正确
  3. MyEclipse破解(MEGen.java)
  4. PHP 的 HMAC_SHA1算法 实现
  5. 第三方登录(2)Android客户瑞上第三方登录百度教程
  6. hdu 3068(最长回文)
  7. 20150503-struts2入门-标签
  8. 採集和输出 DeckLink Studio 4K
  9. webview 上 postUrl 发送参数过程中数据丢失或错误 的问题
  10. 剑指offer-面试题13.在O(1)时间删除链表节点
  11. C#邮件收发
  12. vmware 解决 authentication token manipulation error
  13. 用 virtualenv 创建隔离的 Python 运行环境
  14. jQuery $.ajax传递数组的traditional参数传递必须true
  15. (PMP)第8章-----项目质量管理
  16. Codeforces.100633J.Ceizenpok&#39;s formula(扩展Lucas)
  17. tensorflow实战系列(一)
  18. NSString 拼接字符串
  19. Linux C ftruncate 函数清空文件注意事项(要使用 lseek 重置偏移量)
  20. CAN总线实际运用分析问题。

热门文章

  1. SQL Server数据类型总结
  2. 域名通过infopath访问webservice出现401错误
  3. [SSH]struts2-spring-plugin.jar了解
  4. leecode刷题(20)-- 删除链表中的节点
  5. jQuery入门讲解
  6. 记一次升级Ubuntu内核
  7. 代码 | 自适应大邻域搜索系列之(4) - Solution定义和管理的代码实现解析
  8. leetcode-200-岛屿的个数(dfs找所有的连通分量)
  9. leetcode-74-搜索二维矩阵
  10. leetcode-268-Missing Number(异或)