1

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

 1 year ago
source link: https://www.cnblogs.com/wenbochang/p/17311858.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

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

示例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";
}

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首先是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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK