zoukankan      html  css  js  c++  java
  • 项目中遇到的问题

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

  • 相关阅读:
    背景颜色的渐变设置--实现丰富多彩的背景效果
    CSS-sprit 雪碧图
    背景图片的相关设置
    关于阿里云图标的使用 iconfont
    HTML四种定位-粘滞定位
    HTML四种定位-固定定位
    HTML四种定位-绝对定位
    HTML四种常见的定位-相对定位
    水平居中
    CentOS8更换国内镜像源
  • 原文地址:https://www.cnblogs.com/solverpeng/p/5912270.html
Copyright © 2011-2022 走看看