前言

最近看了一下网上总结的spring事务生效的N个场景,网上列出来的场景有如下

  • 数据库引擎不反对事务
  • 没有被 Spring 治理
  • 办法不是 public 的
  • 本身调用问题
  • 数据源没有配置事务管理器
  • 不反对事务
  • 异样被吃了
  • 异样类型谬误

其中有条异样被吃了,会导致事务无奈回滚,这个引起我的好奇,是否真的是这样,刚好也没写文素材了,就来聊聊事务与异样在某些场景产生的化学反应

示例素材

1、一张没啥业务含意的表,就单纯用来演示用
CREATE TABLE `tx_test` (  `id` bigint NOT NULL AUTO_INCREMENT,  `tx_id` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
2、一份不按编码标准来的service接口
public interface TxTestService {    void saveTxTestA();    void saveTxTestB();}
3、一份非必需品的单元测试
@SpringBootTestclass TransactionDemoApplicationTests {    @Autowired    private TxTestService txTestService;    @Test    void testTxA() {        txTestService.saveTxTestA();    }    @Test    void testTxB() {        txTestService.saveTxTestB();    }}

注: 用的是junit5,所以不必加上

@RunWith(SpringRunner.class)

就能够主动注入

正餐

注: 每个示例演示完,我会先做清表操作,再演示下个例子

场景一:异样被吃

1、示例一:代码如下
private String addSql = "INSERT INTO tx_test (tx_id) VALUES (?);";  @Override    @Transactional(rollbackFor = Exception.class)    public void saveTxTestA() {      jdbcTemplate.update(addSql, "TX-A");        try {            int i = 1 % 0;        } catch (Exception e) {            e.printStackTrace();        }    }

问题思考:

jdbcTemplate.update(addSql, "TX-A");

这句是否是否插入数据胜利?

  • 运行单元测试办法
@Test    void testTxA() {        txTestService.saveTxTestA();    }

失去如下后果

答案: 是能够插入

起因:

if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {            // Standard transaction demarcation with getTransaction and commit/rollback calls.            TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);            Object retVal;            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);            }

这个是spring Transaction的局部源码,当咱们业务代码进行捕捉时,他是执行不到completeTransactionAfterThrowing(txInfo, ex);这个办法,这个办法外面就是执行相应的回滚操作,相干源码如下

if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {                try {                    txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());                }                catch (TransactionSystemException ex2) {                    logger.error("Application exception overridden by rollback exception", ex);                    ex2.initApplicationException(ex);                    throw ex2;                }                catch (RuntimeException | Error ex2) {                    logger.error("Application exception overridden by rollback exception", ex);                    throw ex2;                }
2、示例代码二
 @Autowired    private JdbcTemplate jdbcTemplate;    private String addSql = "INSERT INTO tx_test (tx_id) VALUES (?);";    @Autowired    private TxTestServiceImpl txTestService;    @Override    @Transactional    public void saveTxTestA() {      jdbcTemplate.update(addSql, "TX-A");        try {            txTestService.saveTxTestC();        } catch (RuntimeException e) {            e.printStackTrace();        }    }    @Transactional    public void saveTxTestC() {        jdbcTemplate.update(addSql, "TX-C");        throw new RuntimeException("异样了");    }

问题思考:

jdbcTemplate.update(addSql, "TX-A");

这句是否是否插入数据胜利?

  • 运行单元测试办法
@Test    void testTxA() {        txTestService.saveTxTestA();    }

失去如下后果

答案: 产生了回滚,无奈插入胜利

看到这个答案,可能有些敌人会一脸懵逼,为啥上个例子把异样捕捉了,数据能够插入胜利,这次也是同样把异样捕捉,数据却无奈插入胜利

起因: 这就得从spring事务的流传行为说起了,spring事务的默认流传行为是REQUIRED。依照REQUIRED这个八股文的含意是如果以后存在事务,则退出该事务,如果以后不存在事务,则创立一个新的事务

在示例中

   @Transactional    public void saveTxTestC() {        jdbcTemplate.update(addSql, "TX-C");        throw new RuntimeException("异样了");    }

saveTxTestC会退出到saveTxTestA的事务中,即saveTxTestC和saveTxTestA是属于同一个事务,因而saveTxTestC抛异样回滚,依据事务的原子性,saveTxTestA也会产生回滚

问题延长: 如果想saveTxTestC抛出异样了,saveTxTestA还能插入,有没有什么解决办法

答案: 在saveTxTestC加上如下注解

  @Transactional(propagation = Propagation.REQUIRES_NEW)

REQUIRES_NEW它会开启一个新的事务。如果一个事务曾经存在,则先将这个存在的事务挂起

场景二:接着上一场景的延长

示例:在办法上加了Propagation.REQUIRES_NEW注解
 @Autowired    private JdbcTemplate jdbcTemplate;    private String addSql = "INSERT INTO tx_test (tx_id) VALUES (?);";    @Autowired    private TxTestServiceImpl txTestService;       @Override    @Transactional    public void saveTxTestB() {        jdbcTemplate.update(addSql, "TX-B");        txTestService.saveTxTestD();    }    @Transactional(propagation = Propagation.REQUIRES_NEW)    public void saveTxTestD() {        jdbcTemplate.update(addSql, "TX-D");        throw new RuntimeException("异样了");    }

问题思考:

jdbcTemplate.update(addSql, "TX-B");

这句是否是否插入数据胜利?

  • 运行单元测试办法
   @Test    void testTxB() {        txTestService.saveTxTestB();    }

失去如下后果


答案: 产生了回滚,无奈插入胜利

看到这个答案,可能有敌人会说,你这是在逗我吗,你方才不是说加了REQUIRES_NEW它会开启一个新的事务,即saveTxTestD和saveTxTestB曾经是不同事务了,saveTxTestD回滚,关saveTxTestB啥事件,saveTxTestB讲道理是要插入才对

起因: 加了REQUIRES_NEW,saveTxTestD和saveTxTestB的确是不同事务,saveTxTestD回滚,的确影响不了saveTxTestB。saveTxTestB会回滚,纯正是因为saveTxTestD抛出的异样,传递到了saveTxTestB,导致saveTxTestB也因为RuntimeException产生了回滚了

问题延长: 如果想saveTxTestD抛出异样了,saveTxTestB还能插入,有没有什么解决办法

答案如下:

 @Override    @Transactional    public void saveTxTestB() {        jdbcTemplate.update(addSql, "TX-B");        try {            txTestService.saveTxTestD();        } catch (Exception e) {            e.printStackTrace();        }    }

就是在saveTxTestB中,捕捉一下saveTxTestD抛出来的异样

再次运行单元测试,失去如下后果

总结

咱们在平时可能会为了面试背了一些八股文,但理论场景可能会远比这些八股文简单多,因而咱们在看这些八股文时,能够多加思考,可能会失去一些咱们平时疏忽的货色