Spring | UnexpectedRollbackException 异常(已解决)

项目中遇到了这样的异常:

1
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

项目概要

调用逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 最外层 Service
public class MainService {
	public void executeAAA() {
		try {
			...
			nestedService.executeBBB();
			...
		} catch (Exception e) {
			// 更新操作日志表
			updateTblOperationLog(e);
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
pulblic class NestedService {
	public void executeBBB() {
		try {
			...
			nestedNestedService.sendMsg();
			...
		
		} catch (Exception e) {
			throws new BusinessException(e);
		}
	}
}
1
2
3
4
5
6
7
8
9
pulblic class NestedNestedService {
	public void sendMsg() throws BusinessException {
		try {
			...
		} catch (Exception e) {
			throws new BusinessException(e);
		}
	}
}

事务配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<aop:config proxy-target-class="true">
	<aop:pointcut id="servicePointcut" 
		expression="execution(* com.XXX..*service..*(..))" />
	<aop:advisor pointcut-ref="servicePointcut" advice-ref="txAdvice" order="1" />
</aop:config>
<tx:annotation-driven transaction-manager = "transactionManager" />
<tx:advice id="txAdvice" transaction-manager="transactionManager">
	<tx:attributes>
		<tx:method name="save*" propagation="REQUIRED" rollback-for="BusinessException"/>
		<tx:method name="add*" propagation="REQUIRED" rollback-for="BusinessException"/>
		<tx:method name="insert*" propagation="REQUIRED" rollback-for="BusinessException"/>
		<tx:method name="update*" propagation="REQUIRED" rollback-for="BusinessException"/>
		<tx:method name="delete*" propagation="REQUIRED" rollback-for="BusinessException"/>
		<tx:method name="validate*" propagation="REQUIRED" rollback-for="BusinessException"/>
		<tx:method name="calculate*" propagation="REQUIRED" rollback-for="BusinessException"/>
		<tx:method name="execute*" propagation="REQUIRED" rollback-for="BusinessException"/>
		<tx:method name="find*" read-only="true" />
		<tx:method name="get*" read-only="true" />
		<tx:method name="load*" read-only="true" />
		<tx:method name="*" read-only="true"/>
	</tx:attributes>
</tx:advice>

出现这种没碰过的问题,第一反应当然就是 Google 了。
翻了很多 Blog 和 论坛,也尝试了很多办法,但都失败了。

尝试

原因已经找到:通过查看日志,知道问题是出现在调用 nestedNestedService.sendMsg() 时。由于对方服务不通,导致这个方法出现异常。

尝试1

总的来说就是事务嵌套的问题。网上说可以为每一个方法都起一个事务,修改事务配置文件:

1
<tx:method name="execute*" propagation="REQUIRED" rollback-for="BusinessException"/>

改为

1
<tx:method name="execute*" propagation="REQUIRES_NEW" rollback-for="BusinessException"/>

这个不行,还是会报一样的错误。

尝试2

考虑到出错的那个方法 sendMsg() 不在事务的预定义里面,属于 * 的范畴。

1
<tx:method name="*" read-only="true"/>

改成开启独立事务的模式(挂起前一个,当前方法新起一个事务):

1
<tx:method name="*" propagation="REQUIRES_NEW"/>

不行。

改成不用事务的,因为该用事务的前面定义的差不多了:

1
<tx:method name="*" propagation="NOT_SUPPORTED"/>

还是不行。报错说“事务是必须的”……

那就改成 SUPPORTS 的吧,意思是有事务就用,没有也没关系

1
<tx:method name="*" propagation="SUPPORTS"/>

还是不行。跟一开始一样的错。

* 不行,那就专门给以 send 开头的方法设置一个。
跟上面一样,尝试了 REQUIRES_NEWNOT_SUPPORTED ,都以失败告终。

解决办法

修改配置不行。那就只能从代码入手了。
看了这么多资料,做了这么多尝试,问题点差不多找到了:方法 nestedService.executeBBB() 抛出了 BusinessException 异常导致的。即使最外层的 Service 的 mainService.executeAAA() 做了 try-catch 也不行。

由于项目中的 Service 类里有很多 execute 开头的方法,所以不能随意的更改 rollback-for 配置。
事务里定义了如果遇到 BusinessException 就回滚。
怎样让它不回滚呢?
那就新增一个异常类吧。

  1. 新增异常类:
    1
    public class ServiceException extends Exception {}
  2. 修改 NestedNestedService 类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    pulblic class NestedNestedService {
    	public void sendMsg() throws Exception {
    		try {
    			...
    		} catch (Exception e) {
    			//throws new BusinessException(e);
    			// 直接 throw ,就不再封装了
    			throws e;
    		}
    	}
    }
  3. 修改 NestedService 类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    pulblic class NestedService {
    	public void executeBBB() {
    		try {
    			...
    			nestedNestedService.sendMsg();
    			...
    		
    		} catch (Exception e) {
    			// throws new BusinessException(e);
    			
    			// 换成 ServiceException 异常
    			throws new ServiceException(e);
    		}
    	}
    }

其实之前都是抱着试试看的态度。
问题就这样解决了。

 

加入讨论

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据