Spring声明式事务

Spring声明式事务

所有的事物都由PlatformTransactionManager负责管理。事务由TransactionStatus负责表示。之所以抽象出这两个玩意儿,是因为JavaEE除了提供JDBC事务外,还支持分布式事务JTA(Java Transation API)。然而分布式事务非常慢,所以使用率不高。Spring为了同时支持JDBC和JTA两种事物类型,就抽象出了上面的两个玩意儿。

Spring事务注解

注解翻译作用
@EnableTransactionManagement启动事务管理用在启动类AppConfig上,启用声明式事物,其原理仍然是AOP代理,即通过自动创建Bean的Proxy实现,因此,启动类上添加了这个注解后,不需要再添加@EnableAspectJAutoProxy
@Transactional交易的,事务的用在需要事务支持的方法上或者Bean的class处,表示所有public方法都具有事务支持。需要回滚事务,只需要抛出RuntimeException。更多内容本小节**@Transactional注解参数**。

@Transactional注解参数

  • 如果要针对Checked Exception回滚事务,需要在@Transactional注解中写出来,如IOException.class,使用方法形如

    @Transactional(rollbackFor = {RuntimeException.class, IOException.class})
    public buyProducts(long productId, int num) throws IOException {
        ...
    }
    

    上述代码表示在抛出RuntimeExceptionIOException时,事务将回滚。为了简化代码,我们强烈建议业务异常体系从RuntimeException派生,这样就不必声明任何特殊异常即可让Spring的声明式事务正常工作:

    public class BusinessException extends RuntimeException {
        ...
    }
    
    public class LoginException extends BusinessException {
        ...
    }
    
    public class PaymentException extends BusinessException {
        ...
    }
    
  • 定义事务的传播级别

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Product createProduct() {
        ...
    }
    

Spring事务传播级别

级别翻译作用
REQUIRED要求;要求具备;规定默认传播级别**(满足大部分需求)**
SUPPORTS支持表示如果有事务,就加入到当前事务,如果没有,那也不开启事务执行。这种传播级别可用于查询方法,因为SELECT语句既可以在事务内执行,也可以不需要事务;(少数情况用到)
MANDATORY强制的;法定的表示必须要存在当前事务并加入执行,否则将抛出异常。这种传播级别可用于核心更新逻辑,比如用户余额变更,它总是被其他事务方法调用,不能直接由非事务方法调用;
REQUIRES_NEW需要;要求具备_新表示不管当前有没有事务,都必须开启一个新的事务执行。如果当前已经有事务,那么当前事务会挂起,等新事务完成后,再恢复执行;(少数情况用到)
NOT_SUPPORTED不支持的表示不支持事务,如果当前有事务,那么当前事务会挂起,等这个方法执行完成后,再恢复执行;
NEVER从不;绝不NOT_SUPPORTED相比,它不但不支持事务,而且在监测到当前有事务时,会抛出异常拒绝执行;
NESTED相互套叠表示如果当前有事务,则开启一个嵌套级别事务,如果当前没有事务,则开启一个新事务。

上面加黑的部分会用到,其它的基本不会用到,因为把事务搞的越复杂,不仅逻辑跟着复杂,而且速度也会越慢。

Spring如何传播事务

Spring使用声明式事务,最终也是通过执行JDBC事务来实现功能的,那么,一个事务方法,如何获知当前是否存在事务?

答案是使用ThreadLocal。Spring总是把JDBC相关的ConnectionTransactionStatus实例绑定到ThreadLocal。如果一个事务方法从ThreadLocal未取到事务,那么它会打开一个新的JDBC连接,同时开启一个新的事务,否则,它就直接使用从ThreadLocal获取的JDBC连接以及TransactionStatus

因此,事务能正确传播的前提是,方法调用是在一个线程内才行。如果像下面这样写:

@Transactional
public User register(String email, String password, String name) { // BEGIN TX-A
    User user = jdbcTemplate.insert("...");
    new Thread(() -> {
        // BEGIN TX-B:
        bonusService.addBonus(user.id, 100);
        // END TX-B
    }).start();
} // END TX-A

在另一个线程中调用BonusService.addBonus(),它根本获取不到当前事务,因此,UserService.register()BonusService.addBonus()两个方法,将分别开启两个完全独立的事务。

换句话说,事务只能在当前线程传播,无法跨线程传播。

那如果我们想实现跨线程传播事务呢?原理很简单,就是要想办法把当前线程绑定到ThreadLocalConnectionTransactionStatus实例传递给新线程,但实现起来非常复杂,根据异常回滚更加复杂,不推荐自己去实现。

小结

Spring提供的声明式事务极大地方便了在数据库中使用事务,正确使用声明式事务的关键在于确定好事务边界,理解事务传播级别。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值