嵌套事务在异常后还需进行数据库操作要确认事务是否已提交/回滚

51 阅读1分钟

在TransactionTemplate回调中执行的方法也可能在内部再次被TransactionTemplate嵌套,且使用REQUIRED等复用外层事务的方式,此时如内层事务发生回滚,则在外层即便捕获了异常后再更新数据库也是失败的(如插入异常日志场景);

此时可嵌套新事物保障插入成功

//测试代码
package local.my.sb.demospringboot

import org.junit.jupiter.api.Test
import org.springframework.beans.factory.InitializingBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.stereotype.Component
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.UnexpectedRollbackException
import org.springframework.transaction.support.TransactionTemplate

@SpringBootTest @Component
class DoTestJdbcTrGroovy implements InitializingBean{
    @Autowired JdbcTemplate jdbcTemp;@Autowired PlatformTransactionManager trM
    /**复用已有事务*/
    def trReq = new TransactionTemplate().tap{propagationBehavior=PROPAGATION_REQUIRED}
    /**暂停已有事务并开启新事物*/
    def trNew = new TransactionTemplate().tap {propagationBehavior=PROPAGATION_REQUIRES_NEW}
    /**如果已有事务可用就复用,否则开新事务*/
    def trNes = new TransactionTemplate().tap {propagationBehavior=PROPAGATION_NESTED}
    void afterPropertiesSet() throws Exception {
        trReq.transactionManager=trNew.transactionManager=trNes.transactionManager=trM
        jdbcTemp.execute('drop table if exists tab;create table tab(id int)')
    }
    @Test //错误场景
    void testErr() {
        try {
            trReq.execute {
                try{
                    trReq.execute {
                        jdbcTemp.execute('insert into tab values(1)')
                        throw new RuntimeException()
                    }
                }catch (ignore){
                    jdbcTemp.execute('insert into tab values(2)')//此处插入将失败,将导致trReq抛异常
                }
                assert it.rollbackOnly
            }
        }catch (UnexpectedRollbackException e){
            assert e.message.contains('rollback-only')//事务已回滚
        }
        assert jdbcTemp.queryForList('select id from tab',Integer).empty //一条都没插入成功
    }
    @Test //正确场景
    void testOk(){
        //REQUIRES_NEW和NESTED都可以保障新事务,根据业务逻辑独立性需要选择,一般要求独立事务则用REQUIRES_NEW
        [trNew,trNes].forEach {
            try{
                trReq.execute {
                    try{
                        trReq.execute {
                            jdbcTemp.execute('insert into tab values(1)')
                            throw new RuntimeException()
                        }
                    }catch (ignore){
                        trNew.execute {jdbcTemp.execute('insert into tab values(2)')}
                    }
                    assert it.rollbackOnly
                }
            }catch (ignore){}
            assert [2]==jdbcTemp.queryForList('select id from tab',Integer)//1插入失败,但2插入成功
            jdbcTemp.execute('truncate table tab')
        }
    }
}