spring事务里面开启线程插入,报错了是否会回滚?

1.前言

一道非常有意思的面试题目。大概是这样子的,如果在一个事务中,开启线程进行插入更新等操作,如果报错了,事务是否会进行回滚

2.代码

示例1

@RequestMapping("/test/publish/submit") public String testPublish1() {  	log.info("start..."); 	transactionTemplate.execute(new TransactionCallback<String>() { 		@Override 		public String doInTransaction(TransactionStatus status)  {  			TElement element = new TElement(); 			element.setfElementId(10L); 			element.setfElementName("111"); 			mapper.insertSelective(element);   			element = new TElement(); 			element.setfElementId(10L); 			element.setfElementName("222"); 			mapper.insertSelective(element);  			return "OK"; 		} 	}); 	log.info("end...");  	return "ok"; } 

示例2

@RequestMapping("/test/publish/submit2") public String testPublish2() {  	log.info("start..."); 	transactionTemplate.execute(new TransactionCallback<String>() { 		@Override 		public String doInTransaction(TransactionStatus status)  { 			es.submit(() -> { 				TElement element = new TElement(); 				element.setfElementId(10L); 				element.setfElementName("111"); 				mapper.insertSelective(element); 			});  			es.submit(() -> { 				TElement element = new TElement(); 				element.setfElementId(10L); 				element.setfElementName("222"); 				mapper.insertSelective(element); 			});  			return "OK"; 		} 	}); 	log.info("end...");  	return "ok"; } 

3.结论

示例1

element.setfElementId(10L); 为主键。SQL在第一次插入id=10的时候是没有问题的,在第二次插入id=10的时候,由于主键冲突了,导致报错,然后整个事务都会进行回滚,这是没有问题的。是spring的事务帮助我们来进行回滚等操作的。我们可以看到如下代码,他是对整个result = action.doInTransaction(status);进行了try catch。如果抛异常,就会回滚

@Override @Nullable public <T> T execute(TransactionCallback<T> action) throws TransactionException { 	Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");  	if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { 		return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); 	} 	else { 		TransactionStatus status = this.transactionManager.getTransaction(this); 		T result; 		try { 			result = action.doInTransaction(status); 		} 		catch (RuntimeException | Error ex) { 			// Transactional code threw application exception -> rollback 			rollbackOnException(status, ex); 			throw ex; 		} 		catch (Throwable ex) { 			// Transactional code threw unexpected exception -> rollback 			rollbackOnException(status, ex); 			throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); 		} 		this.transactionManager.commit(status); 		return result; 	} } 

示例2

示例2首先是transactionTemplate.execute是一个主main线程。然后在第一个子线程插入了一个数据,第二个子线程也插入了一个数据。那么现在就是有三个线程,一个是main线程,一个是A线程,一个是B线程。
main线程正常执行不报错,A线程正常插入不报错,B线程由于主键冲突报错。
我们可以通过上面action.doInTransaction(status);看出来,他对这块代码进行了try catch。也就是主线程进行了try catch。那么也就是只要主线程没有报错,这个事务就不会被捕获,也就不会回滚了。无论你A,B还是CDEFG子线程出问题了,只要不影响main线程,那事务就不会回滚呢?
因此我们可以得出一个结论,在示例2中,A线程会插入成功,B线程插入失败,事务不会回滚,最终插入成功。这个其实与我们平常的想法所违背了。
因此如果想要主线程抛出异常,得让主线程感知到子线程异常了,主动地去throw异常。比如我们可以设置一个flag,子线程报错了 flag=true。主线程检测到flag为true,就主动抛出一个exception

4.最后

这道面试题非常有意思,起初以为会回滚,没想到不会回滚。查看代码得知,原来是catch住的是主线程,并不是子线程。同样注解式事务类似。因此如果想要事务生效,尽量避免在事务中使用多线程来进行插入更新等操作

发表评论

评论已关闭。

相关文章