文档章节

【排坑】多线程事务引发的问题(二)

大白来袭
 大白来袭
发布于 2018/04/12 14:56
字数 1092
阅读 7
收藏 0

【前提】:TestReqJsonProcess位于Service层,其中doProcess方法用于更新指定主键的remark数据。后期新需求要求在执行所有更新Service时调用三方接口,并将remark转义为中文作为参数传输给三方。类似TestReqJsonProcess的Service有很多个,计划利用注解注入BusiOptUtil工具类来实现。

【代码】:

@Service
@Transactional
public class TestReqJsonProcess implements JsonProcessInterface {
	private Logger logger = LoggerFactory.getLogger( getClass() );

	@PersistenceContext
	private EntityManager em;
	
	@Autowired
	private BusiOptUtil busiOptUtil;
	
	@Autowired
	private RTmAppMain rTmAppMain;
	
	@Override
	public void doProcess( HttpServletRequest request ) throws ProcessException {
		logger.debug("--------------执行开始-主方法用于更新保存appNo为主键的单条数据--------------");
		try {
			String appNo = request.getAppNo();
			String remark = request.getRemark();
			
			//appNo 作为TmAppMain表的主键,使用JPA方式读取数据库
			QTmAppMain qTmAppMain = QTmAppMain.tmAppMain;
			TmAppMain tmAppMain = new JPAQuery( em ).from( qTmAppMain )
					.where( qTmAppMain.appNo.eq( appNo ) )
					.singleResult( qTmAppMain );
			
			logger.debug("主方法首次从数据库获取remark="+tmAppMain.getRemark());
			
			//更新remark
			tmAppMain.setRemark(remark);
			
			logger.debug("主方法首次更改remark="+tmAppMain.getRemark());
			
			//@Autowired BusiOptUtil 调用三方接口传输数据的工具类
			//方法内为匹配三方接口的参数要求,更新了remark参数用于示例
			busiOptUtil.opt(appNo);
			
			logger.debug("主方法显示主方法remark="+tmAppMain.getRemark());
			
			//更新后保存
			rTmAppMain.save(tmAppMain);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		logger.debug("--------------执行结束-主方法用于更新保存appNo为主键的单条数据--------------");
	}
}
@Component
public class BusiOptUtil {

	Logger logger = LoggerFactory.getLogger( this.getClass() );
	
	@PersistenceContext                              
	private EntityManager em;
	
	public void opt(String appNo){
		
		QTmAppMain qTmAppMain = QTmAppMain.tmAppMain;
		TmAppMain tmAppMain = new JPAQuery( em ).from( qTmAppMain ).where( 
				qTmAppMain.appNo.eq( appNo ))
				.singleResult( qTmAppMain );
		
		logger.debug("工具类里从数据库获取remark="+tmAppMain.getRemark());
		
		if(StringUtils.isNotBlank(tmAppMain.getRemark())){
			tmAppMain.setRemark("中国");
		}else{
			tmAppMain.setRemark("");
		}
		
		logger.debug("工具类里更改remark="+tmAppMain.getRemark());
		
		//省略调用三方接口,将tmAppMain转化为JSON字符串传输
	}
}
请求参数:
appNo  = 123456
remark = China

【结果】 :appNo为123456这个单号记录,保存在数据库中remark值为"中国",并非“China”

【原因】:TestReqJsonProcess类上面有@Transactional声明式事务。在首次查询完数据库没有commit之前,数据会存在缓存中(主键查询才会缓存),当再次通过相同主键去查询,会优先检查缓存中是否已经存在,存在将不会再去数据库查询。所以就会出现上述的情况,问题比较容易理解,但实际场景中很容易忽略。(示例中事务使用 Spring 提供的默认事务传播行为  PROPAGATION_REQUIRED )

【扩展】:顺便整理下事务使用过程遇到的问题,使用事务默认的传播行为PROPAGATION_REQUIRED ,方法A和方法B同时通过相同主键获取同一数据对象。

(一)单线程事务或嵌套事务

1、方法A有事务,方法B无事务,A中调用B,B先commit,结束后A再commit;

2、方法A有事务,方法B有事务,A中调用B,B先commit,结束后A再commit;

都不会报错。B若无事务,A的事务将传播到B方法;若B有事务,B将加入A方法现有的事务;当B方法commit后,A方法commit时发现对象没有改变,A方法就不会再commit

(二)多线程事务并发

多线程事务要考虑Spring框架提供的事务隔离级别

ISOLATION_DEFAULT
ISOLATION_READ_UNCOMMITTED
ISOLATION_READ_COMMITTED
ISOLATION_REPEATABLE_READ
ISOLATION_SERIALIZABLE

后四种隔离级别具体隔离何种数据读取,我们使用默认的 ISOLATION_DEFAULT ,这个默认隔离级别是与具体的数据库相关的,采取的是具体数据库的默认隔离级别,不同的数据库是不一样的。

SELECT @@global.tx_isolation;#查询Mysql全局事务隔离级别
SELECT @@session.tx_isolation;#查询当前连接上的事务隔离级别
SELECT @@tx_isolation; #查询下一个未开始的事务隔离级别
-------------------------------------
REPEATABLE-READ 

查询结果为可重复读,该种情况会出现幻读。 InnoDB和Falcon存储引擎通过多版本并发控制机制解决了该问题。数据库表加了乐观锁JPA_VERSION(Spring中映射对象使用@Version支持乐观锁)。

方法A有事务,方法B有事务,线程1执行A获取对象并线程睡眠,此时线程2执行B获取相同对象并先commit,待A线程恢复后commit;

会报错,Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction

 

© 著作权归作者所有

大白来袭
粉丝 4
博文 41
码字总数 13667
作品 0
海淀
程序员
私信 提问
MySQL5.7 核心技术揭秘:MySQL Group Commit

一、大纲 一阶段提交 二阶段提交 三阶段提交 组提交总结 二、一阶段提交 2.1 什么是一阶段提交 先了解下含义,其实官方并没有定义啥是一阶段,这里只是我为了上下文和好理解,自己定义的一阶...

兰春
2018/07/25
0
0
JavaWeb13-HTML篇笔记(二)

1.1.1.3 DBUtils实现事务管理: Ø 没有事务管理: Ø 有事务管理: 1.1.1 总结:1.1.1.1 事务特性: Ø 原子性:强调事务的不可分割. Ø 一致性:强调的是事务的执行的前后,数据的完整性要保持一...

我是小谷粒
2018/05/29
0
0
一个多线程bug引发的测试思考

背景 今天收到测试的一个bug反馈,bug描述:author表中存在重复的作者信息,在提测时我有说明task解析页面后获得作者信息会按authorId 判断DB中是否已经存在,存在了则不往DB中插入,这本是一...

测试开发栈
2018/06/04
0
0
你了解Spring事物控制特性吗

一、事务特性 原子性:强调事务的不可分割 一致性:强调的是事务的执行的前后,数据的完整性要保持一致 隔离性:一个事务的执行不应该受到其他事务的干扰 持久性:事务一旦结束(提交/回滚)数...

骚年锦时
05/23
14
0
将不确定变为确定~transactionscope何时提升为分布式事务~续

相关文章 将不确定变为确定~transactionscope何时提升为分布式事务 将不确定变为确定~transactionscope何时提升为分布式事务~续 将不确定变为确定~transactionscope何时提升为分布式事务~再续...

mcy247
2017/12/04
0
0

没有更多内容

加载失败,请刷新页面

加载更多

CSS--列表

一、列表标识项 list-style-type none:去掉标识项 disc:默认实心圆 circle:空心圆 squire:矩形 二、列表项图片 list-style-img: 取值:url(路径) 三、列表项位置 list-style-position:...

wytao1995
今天
6
0
linux 命令-文本比较comm、diff、patch

本文原创首发于公众号:编程三分钟 今天学了三个文本比较的命令分享给大家。 comm comm 命令比较相同的文本 $ cat charabc$ cat chardiffadc 比如,我有两个文件char和chardiff如上,...

编程三分钟
今天
7
0
QML教程

https://blog.csdn.net/qq_40194498/article/category/7580030 https://blog.csdn.net/LaineGates/article/details/50887765...

shzwork
今天
5
0
HA Cluster之5

对于使用heartbeat v2版的CRM配置的集群信息都是保存在一个名为cib.xml的配置文件中,存放在/var/lib/heartbeat/crm/下。CIB:Cluster Information Base,由于xml文件配置不是那么方便,所以...

lhdzw
今天
6
0
玩转Redis-Redis基础数据结构及核心命令

  《玩转Redis》系列文章主要讲述Redis的基础及中高级应用,文章基于Redis5.0.4+。本文主要讲述Redis的数据结构String,《玩转Redis-Redis基础数据结构及核心命令》相关操作命令为方便对比...

zxiaofan666
今天
11
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部