项目中遇到的问题
一、Spring 事务问题
1.描述:service1 中的 a 调用 b,b 调用了 service2 中的 c ,c 调用了 service3 中的 d
期望:d 抛出异常时(我真实项目中抛出的是 Sql 异常),d,c 回滚,而 a,b 不回滚。
测试:考虑到 Spring 事务的自调用和 cglib 动态代理下的 spring 事务配置。添加了 <aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true"/>。
Demo:
自定义异常:
public class MyException extends SQLException { private static final long serialVersionUID = 1L; public MyException() { super(); } public MyException(String reason, String sqlState, int vendorCode, Throwable cause) { super(reason, sqlState, vendorCode, cause); } public MyException(String reason, String SQLState, int vendorCode) { super(reason, SQLState, vendorCode); } public MyException(String reason, String sqlState, Throwable cause) { super(reason, sqlState, cause); } public MyException(String reason, String SQLState) { super(reason, SQLState); } public MyException(String reason, Throwable cause) { super(reason, cause); } public MyException(String reason) { super(reason); } public MyException(Throwable cause) { super(cause); } }
Dao:
@Repository public class TxDao { @Autowired private JdbcTemplate jdbcTemplate; public void updateA() { String sql = "update tx_test set a_field = 1 where id = 1"; jdbcTemplate.update(sql); } public void updateB() { String sql = "update tx_test set b_field = 1 where id = 1"; jdbcTemplate.update(sql); } public void updateC() { String sql = "update tx_test set c_field = 1 where id = 1"; jdbcTemplate.update(sql); } public void updateD() { String sql = "update tx_test set d_field = 1 where id = 1"; jdbcTemplate.update(sql); } }
ABService:
@Service public class ABService { @Autowired private TxDao txDao; @Autowired private CService cService; @Transactional public void aMethod() throws MyException { System.out.println("aMethod"); txDao.updateA(); ((ABService) AopContext.currentProxy()).bMetod(); } @Transactional public void bMetod() throws MyException { System.out.println("bMethod"); txDao.updateB(); cService.cMethod(); } }
CService:
@Service public class CService { @Autowired private TxDao txDao; @Autowired private DService dService; @Transactional(rollbackFor=MyException.class) public void cMethod() throws MyException { System.out.println("cMethod..."); txDao.updateC(); dService.dMethod(); } }
DService:
@Service public class DService { @Autowired private TxDao txDao; @Transactional(rollbackFor=MyException.class) public void dMethod() throws MyException{ System.out.println("dMethod..."); txDao.updateD(); throw new MyException(); } }
测试代码:
@Test public void test() { ABService service = context.getBean(ABService.class); try { service.aMethod(); } catch(MyException e) { e.printStackTrace(); } }
(1)测试 rollbackFor 和 noRollbackFor
过程:
自定义了一个 Sql 异常 MyException 继承自 SQLException,从 d 抛出,一直向上抛。
对 a, b 的 @Transactional 的 noRollbackFor 属性设置为 MyException.class,而 c,d 的 rollbackFor 属性设置为 MyException.class。
控制台输出:
aMethod
bMethod
cMethod...
dMethod...
数据库输出:
测试结果: a,b,c,d 四个方法全部回滚。
原因查找:发现在容器初始化的时候就读取了所有的 事务方法的 RollBackFor 和 noRollBackFor 属性定义的 Value 值。
详细参见:org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(java.lang.Class<?>)
在某个事务方法抛出异常后,整个事务都进行了回滚,感觉与配置 noRollBackFor 没有关系。
详细参见:org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional. final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass); if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction and commit/rollback calls. TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try { // This is an around advice: Invoke the next interceptor in the chain. // This will normally result in a target object being invoked. retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // target invocation exception completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } commitTransactionAfterReturning(txInfo); return retVal; }
在标红的地方会去掉目标方法,如目标方法抛出异常,则会进入到 catch 块,执行完 catch 块的代码继续向上抛出。catch 块能捕获到 MyException。
通过断点跟踪发现在 completeTransactionAfterThrowing() 方法里进行了回滚,等回滚之后最后去调用了 commitTransactionAfterReturning() 方法。
这样看来 noRollBackFor 甚至没有什么作用,有哪位大神看到这里并且知道原理的话,请不吝赐教,谢谢。
(2)测试 Spring 事务的传播行为。
过程:将 c 的传播行为改为 REQUIRES_NEW,其他还是默认。同时在 c 方法中处理了 d 抛上来的异常。
控制台输出:
aMethod
bMethod
cMethod...
数据库输出:
发现根本就不会去执行 d 方法。这里就不是很明白了。
上面提供了两个测试,事实上测试了 n 种方式,都没有行的通。等以后对 spring 事务理解加深之后再来解析。这里直接说最终的解决办法。
解决办法:
1.手动控制事务
2.服务拆分,比如这里 a,b 单独事务,c,d 单独事务。