项目中遇到的问题

一、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);
    }

    

}
MyException.java

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);
    }
}
TxDao.java

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();
    }
    
    
}
ABService.java

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();
    }
}
CService.java

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();
    }
}
DService.java

测试代码:

@Test
public void test() {
    ABService service = context.getBean(ABService.class);
    try {
        service.aMethod();
    } catch(MyException e) {
        e.printStackTrace();
    }
}
View Code

(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 单独事务。

 

posted @ 2016-09-27 15:22  solverpeng  阅读(542)  评论(0编辑  收藏  举报