在Spring事务中,我们通常会为了控制事务粒度,会把它进行拆分,为了避免大事务执行太久,占用资源太多,导致资源利用率低的问题。
我们曾经就遇到老系统因为大事务,把服务打死了。
问题出在一个大事务中有一个Excel文件解析的操作,有用户上传的某个文件,有1百多万个空行数据。
因为,这个事务一致不能结束,直接导致系统崩溃。
但是要拆分事务,是一个麻烦的事情,要考虑事务传播机制。
我相信很多朋友都遇到过事务不生效的情况,最常见的就是下面这种情况:
public class ServiceA
{
public void methodA(){
methodB();
}
@Transactional
public void methodB(){}
}
我相信有朋友已经开始笑了。
不要笑,相信很多朋友本能会犯这个错误,因为这种方式最简单。
上面的示例,事务是不会生效的,因为methodB直接被调用,是因为没有通过代理执行。
问题很简单,但是如何快速简单的解决问题呢?
有对事务传播机制比较熟悉的朋友,可能要提出下面的方案了:
public class ServiceA
{
@Transactional(propagation = Propagation.SUPPORTS)
public void methodA(){
methodB();
}
@Transactional
public void methodB(){}
}
既然,没有事务,我加上事务不加完了,SUPPORTS机制,没有事务就不创建,有事务就在事务中执行,
然后,methodB默认事务传播机制REQUIRED,没有就会创建事务。
所以,methodA没有事务,methodB直接创建事务执行,真是天才的想法啊。
问题是,实际情况真是这样吗?
比较遗憾,不是。
会有事务吗?会methodA会生成事务。
methodB会生成新的事务吗?不会,因为methodA已经有事务了。
会回滚吗?不会!有事务,但是不会回滚。
和不加@Transactional(propagation = Propagation.SUPPORTS)相比,只是会创建事务了。
为什么会出现这样的情况呢?
开的的时候,我以为是SUPPORTS没有回滚点的造成。
但是,我发现还是存在其他没有回滚点的事务传播机制,并且能够回滚
可以添加下面的代码打印看一下:
System.out.println(TransactionAspectSupport.currentTransactionStatus().hasSavepoint());
还有什么办法吗?
很多时候,我们没有得到正确的结果,可能是姿势不对,我们换个姿势试一试。
既然,直接调用不行,那我们通过ApplicationContext来调用,是否就可以触发事务了呢?
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public ApplicationContext getApplicationContext(){
return this.applicationContext;
}
public <T> T getService(Class<T> clazz){
return applicationContext.getBean(clazz);
}
}
public class ServiceA
{
@Resource
private ApplicationContextHolder applicationContextHolder;
public void methodA(){
ServiceA service = applicationContextHolder.getService(this.getClass());
service.methodB();
}
@Transactional
public void methodB(){}
}
答案是:不行,因为根本没有创建事务
public class ServiceA
{
@Resource
private ApplicationContextHolder applicationContextHolder;
@Transactional(propagation = Propagation.SUPPORTS)
public void methodA(){
ServiceA service = applicationContextHolder.getService(this.getClass());
service.methodB();
}
@Transactional
public void methodB(){}
}
这样可以吗?
答案是:可以
事务是methodA的事务,并且也回滚了。
这的确解决了我们的问题,但是也违背了我们的初衷:将事务粒度变小。
因为,绕了一大圈,发现还是相当于methodA上的事务了。
那有没有什么更靠谱的解决方案呢?
大概可以试一试:NESTED和REQUIRES_NEW事务吧
public class ServiceA
{
@Transactional(readOnly = true)
public void methodA(){
ServiceA service = applicationContextHolder.getService(this.getClass());
service.methodB();
}
@Transactional(propagation = Propagation.NESTED)
// @Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB(){}
}
可以回滚,但是:
感觉和直接调用没有太多的区别
@Service
public class ServiceA
{
@Resource
private ServiceB serviceB;
public void methodA(){
serviceB.methodB();
}
}
@Service
public class ServiceB
{
// @Transactional(propagation = Propagation.NESTED)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB(){}
}
上面的方式,也可以回滚:
事务传播机制