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 ...
随机推荐
- 一.Jenkins的windows安装
1,打开jinkens官网: https://jenkins.io/ ,点击 Download 进入下载界面 2,选择 Long-term Support 版本(推荐选择长期支持版本)->Win ...
- mysql5.7 创建新表时提示时间戳非法
# 背景 mysql版本5.7.8,需要创建新表,研发提供的sql文件,执行后报错如下: ERROR (): Invalid default value for 'deleted_at' 就猜测到时因 ...
- angular响应式表单 - 状态字段
用于表单验证的过程: touched,untoched pristine,dirty pending
- EF6 使用SQLite Code First
SQLite是一款轻型关系型数据库,做一个小网站,用来替代sql server或者access数据库应该会是一个不错的选择. ASP.NET Entity Framework 6是微软平台的著名ORM ...
- 如何实现一个IOS网络监控组件
此文由作者朱志强授权网易云社区发布. Mobile Application Monitor IOS组件设计技术分享 背景 应用程序性能管理Application Performance Managem ...
- FasterRunner在Centos7.6服务器部署
前言: 测试工作,就是要保障软件产品质量,如何保障软件产品质量,是一个博大精深的问题.功能测试,性能测试,接口测试,安全测试等.而在现实的项目过程中,软件版本的快速迭代,给测试的时间会越来越少.特别是 ...
- “全栈2019”Java第一百零七章:匿名内部类与构造方法注意事项
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- 一篇文章搞懂Linux安全!
Linux是开放源代码的免费正版软件,同时也是因为较之微软的Windows NT网络操作系统而言,Linux系统具有更好的稳定性.效率性和安全性. 在Internet/Intranet的大量应用中,网 ...
- css编写规范最佳实践
最初,在编写CSS的时候,我们往往想到哪儿就写到哪儿,它们之间的关联性和有序性并不在考虑之中.但随着代码量的增加,亦或是多人共同开发,CSS的编写规范变得重要起来了.本文通过三个方面,总结出CSS编写 ...
- 由button标签在 IE 8.0 下的异常表现引发的一场血案
写在最前的最后:整篇文章絮絮叨叨说了半天,我得出一个最佳实践:和button标签say goodbay,用 a 标签模拟之. 首先看一个在chrome 下的简单demo 这样的布局在组件开发中再常见不 ...