Spring @Transactional踩坑记
@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。细细分析,我终于知道为啥了
Spring @Transactional踩坑记的更多相关文章
- spring mvc踩坑记
前言 主要介绍自己在学习spring mvc过程中踩到的一些坑,涉及到当时遇到这个错误是如何思考的,对思路进行总结,下次遇到类似的错误能够提供一些思路甚至快速解决. 环境准备 jdk8,spring4 ...
- spring boot踩坑记
Resolved exception caused by handler execution: org.springframework.http.converter.HttpMessageNotWri ...
- 记一次 Spring 事务配置踩坑记
记一次 Spring 事务配置踩坑记 问题描述:(SpringBoot + MyBatisPlus) 业务逻辑伪代码如下.理论上,插入数据 t1 后,xxService.getXxx() 方法的查询条 ...
- Spark踩坑记——Spark Streaming+Kafka
[TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ...
- Spark踩坑记——数据库(Hbase+Mysql)
[TOC] 前言 在使用Spark Streaming的过程中对于计算产生结果的进行持久化时,我们往往需要操作数据库,去统计或者改变一些值.最近一个实时消费者处理任务,在使用spark streami ...
- 【踩坑记】从HybridApp到ReactNative
前言 随着移动互联网的兴起,Webapp开始大行其道.大概在15年下半年的时候我接触到了HybridApp.因为当时还没毕业嘛,所以并不清楚自己未来的方向,所以就投入了HybridApp的怀抱. Hy ...
- Spark踩坑记——共享变量
[TOC] 前言 Spark踩坑记--初试 Spark踩坑记--数据库(Hbase+Mysql) Spark踩坑记--Spark Streaming+kafka应用及调优 在前面总结的几篇spark踩 ...
- Spark踩坑记——从RDD看集群调度
[TOC] 前言 在Spark的使用中,性能的调优配置过程中,查阅了很多资料,之前自己总结过两篇小博文Spark踩坑记--初试和Spark踩坑记--数据库(Hbase+Mysql),第一篇概况的归纳了 ...
- djangorestframework+vue-cli+axios,为axios添加token作为headers踩坑记
情况是这样的,项目用的restful规范,后端用的django+djangorestframework,前端用的vue-cli框架+webpack,前端与后端交互用的axios,然后再用户登录之后,a ...
随机推荐
- linux bash变量替换(# ## % %% / //)
VAR=hahaha echo ${VAR#*h} # ahaha 从前向后匹配删除 VAR=hahaha echo ${VAR##*h} # a 贪婪模式,从前向后匹配删除所有 VAR=hahaha ...
- SQL LEAD()函数 LAG()函数
lag ,lead 分别是向前,向后:lag 和lead 有三个参数,第一个参数是列名,第二个参数是偏移的offset,第三个参数是 超出记录窗口时的默认值) SQL> select id,na ...
- WPF圆角按钮
<ControlTemplate x:Key="CornerButton" TargetType="{x:Type Button}"> <Bo ...
- 【总结】 BZOJ前100题总结
前言 最近发现自己trl,所以要多做题目但是Tham布置的题目一道都不会,只能来写BZOJ HA(蛤)OI 1041 复数可以分解成两个点,所以直接把\(R^2\)质因数分解一下就可以了,注意计算每一 ...
- Android Bug BaseExpandableListAdapter, getChildView
@Override public View getChildView(final int groupPosition, final int childPosition, boolean isLastC ...
- 考取RHCE认证的历程,总结的经验
昨天去考试的,今天下午结果出来了,达到了我的预期.成功的获取了RHCE认证,以后我也是有证的人咯~,开个玩笑. 其实去年的时候我就曾经想要去考取的,我原来一直以为考取RHCE认证时考题都是英文的呢?因 ...
- Java - io输入输出流 --转换流
转换流 转换输出流 OutputStreamWriter: 说明: /* * OutputStreamWriter 这个类的作用 * 就是指定输出流的编码格式 * 这个类的构造方法 需要传递 一个输 ...
- C# RDLC报表不出现预览窗体直接输出到打印机
#region 直接打印区域 /// <summary> /// 直接打印到打印机 /// </summary> /// <param name="report ...
- 中山纪念中学培训DAY1
哇啊啊啊啊啊啊$……$ 并不像说环境怎么样. $Day1$模拟赛 稳重一点选了提高$B$ 然后$5min$后: $t1$装压$DP$最短路 $t2$裸地贪心 $t3……$哇$t3$怎么做啊啊啊啊. $ ...
- JDBC技术(汇聚页)
JDBC代表Java数据库连接(Java Database Connectivity),它是用于Java编程语言和数据库之间的数据库无关连接的标准Java API, 换句话说:JDBC是用于在Java ...