@Transactional注解-lili

本文详细介绍了Spring事务管理的核心概念,包括编程式和声明式事务的区别,事务的隔离级别、传播行为以及超时和只读属性。同时,深入探讨了事务回滚规则,@Transactional注解的使用及局限性,并提供了多个问题场景的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 概念介绍
    • 当出现异常情况时,可以保证数据的一致性;
    • Spring支持两种事物方式:
      • 编程式事物:使用的是TransactionTemplate(或者org.springframework.transaction.PlatformTransactionManager接口)
      • 声明式事物:使用Transactional注解或者xml配置,建立在AOP之上的。本质就是对方法前后进行拦截,然后在目标方法开始之前创建或者添加一个事物,在执行完目标方法之后根据执行情况提交或者回滚事物;
      • 声明式事物的优缺点:
        • 使正常的业务代码不受污染,是spring倡导的非入侵开发方式,简直不要太方便哦;
        • 缺点:最细的颗粒度也只能做到方法级别的,无法做到代码块级别;有这样的需求,我们将代码块方到方法里就好了;
    • 自动提交(autoCommit)
      • 默认的情况下,数据库是处于自动提交模式的,每一条语句处于一个单独的事物中,执行完了就提交;但是在实际的业务开发的事物管理中,一般是一组相关的操作都放在同一个事物之中,因此,必须关闭数据库的自动提交模式,Spring会将底层的自动提交属性设置为false(DataSourceTransactionManager.java中);
      • 某些数据库链接池提供了关闭事物的自动提交设置,最好的是在链接池就将其关闭,但是C3P0不支持这一设置;
    • 连接关闭时的是否自动提交
      • 当一个连接关闭时,如果有未提交的事务应该如何处理?JDBC规范没有提及,C3P0默认的策略是回滚任何未提交的事务。这是一个正确的策略,但JDBC驱动提供商之间对此问题并没有达成一致。
      • C3P0的autoCommitOnClose属性默认是false,没有十分必要不要动它。或者可以显式的设置此属性为false,这样会更明确。
    • Mybatis与Spring的事物整合:
      • MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
    • 事务的隔离级别(由TransactionDefinition接口定义):
      • ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是ISOLATION_READ_COMMITTED。
      • ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
      • ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
      • ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
      • ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
    • 为什么会发生隔离级别这个概念? -- 因为数据库并发事务的存在
      • 脏读/幻读/不可重复读(从A事务的角度看)
        • 脏读:A事物读取了B事物尚未提交的数据。---取款例子;
        • 不可重复读:A事物读取了B事物已经提交的更改数据(注意:是更改数据)。
          • 解决的方式是:加行级锁,防止数据变化;
        • 幻读:A事物读取了B事物提交的新增数据(注意:是新增数据)。
          • 解决的方法:加表级锁,防止新增数据;
      • 数据库并发事物引起的其他问题 -- :(从B事务的角度看)
        • 第一类丢失更新:A事物撤销时,将B事物已经提交的更新数据覆盖了;
        • 第二类丢失更新:A事物提交时,将B事务已经提交的跟新数据覆盖了;
      • 为了解决上述问题,数据库通过锁机制解决并发访问的问题。根据锁定对象不同:分为行级锁和表级锁;根据并发事务锁定的关系上看:分为共享锁定和独占锁定,共享锁定会防止独占锁定但允许其他的共享锁定。而独占锁定既防止共享锁定也防止其他独占锁定。为了更改数据,数据库必须在进行更改的行上施加行独占锁定,insert、update、delete和selsct for update语句都会隐式采用必要的行锁定。
      • 但是直接使用锁机制管理是很复杂的,基于锁机制,数据库给用户提供了不同的事务隔离级别,只要设置了事务隔离级别,数据库就会分析事务中的sql语句然后自动选择合适的锁
      • 注意:事务的隔离级别和并发处理性能是成反比的;
    • 事务的传播行为:当前事务开启之前,一个事务已经存在了,此时我们可配置若干选项来指定当前新事务的执行行为 (TransactionDefinition定义接口定义)
      • PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
      • PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
      • PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
      • PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
      • PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
      • PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
      • PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。
    • 事务超时:指当前事务允许执行的最长时间,超时未完成则回滚;
      • 在TransactionDefinition中以int值来表示超时时间,单位秒,未完成则回滚,注解中的属性是timeout;
      • 默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
    • 事务只读属性:代码只读但不修改的属性;readOnly = true
    • Spring 的事务回滚规则:
      • 指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
      • 默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。
        • checked: 一般是指程序不能直接控制的外界情况,是指在编译的时候就需要检查的一类exception,用户程序中必须采用try catch机制处理或者通过throws交由调用者来处理。这类异常,主要指除了Error以及RuntimeException及其子类之外的异常。
        • unchecked:是指那些不需要在编译的时候就要处理的一类异常。在java体系里,所有的Error以及RuntimeException及其子类都是unchecked异常。再形象直白的理解为不需要try catch等机制处理的异常,可以认为是unchecked的异常。
      • 可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。
      • 还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
    • @Transactional 注解相关属性
      • value String 可选的限定描述符,指定使用的事务管理器
      • propagation enum: Propagation 可选的事务传播行为设置
      • isolation enum: Isolation 可选的事务隔离级别设置
      • readOnly boolean 读写或只读事务,默认读写
      • timeout int (in seconds granularity) 事务超时时间设置
      • rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
      • rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
      • noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
      • noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组
    • @Transactional 注解的用法:
      • 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
      • Spring 建议不要在接口或者接口方法上使用该注解;为什么呢?
        • 因为此时只用在使用基于接口的代理时才会生效;
      • 只被应用到 public 方法上,由 Spring AOP 的本质决定。如果在 protected、private 或者默认可见性的方法上使用将被忽略,也不会抛出任何异常。
      • 类内部方法调用本类内部的其他方法并不会引起事务行为
        • 原因:
          • SpringAop的实现方式由两种:java代理(优点:不用依赖第三方jar包,但只能代理接口,不能代理类)和Cglib动态增强,这两种方式在Spring中无缝切换。
          • Spring通过AopProxy(['prɒksɪ])接口抽象了这两种实现,实现了一致的AOP方式;(AopProxy:[jdkDynamicAopProxy, CglibAopProxy])
          • Spring的aop实现时:以CgLIb方式增强的AOP目标类,会创建一个Bean实例本身和一个Cglib增强代理对象;这是因为Spring会抹杀CgLib能够直接创建普通类的增强子类的能力,他把CgLib动态生成的子类当成了普通的代理类;
          • 此时,我们来梳理下SpringAop代理类的实际调用过程
            • 客户端通过applicationContext获得代理类的调用,代理类在调用targetBean
          • 这样的后果是:当你在targetBean中再次调用内部方法时,他就不会走到AopProxy代理类,而Transactional注解的实现是严格依赖于Aop的通知的,所以,我们看到的效果是,在同一个类的内部调用事务注解的方法时,事务注解不生效;其实不仅仅是事务的通知,所用利用Spring实现的Aop的通知,都同样会受到限制;
  • 问题场景
    • 问题一:同一个类中一个普通方法调用另一个有事务的方法,这时候,事务时会不会起作用?
      • 思考为什么??
        • @Transactional的事务开启 ,或者是基于接口的或者是基于类的代理被创建。所以在同一个类中一个方法调用另一个方法有事务的方法,事务是不会起作用的
      • 解决方法:将事务方法放入另一个类中;
    • 问题二:
      • @Transactional 注解只能应用到 public 可见度的方法上。其他可见度的的方法上使用,它也不会报错,事务也不生效。
    • 问题三:
      • 当注释的方法的代码被Try了,那么catch中必须throw异常,否则无法rollback;
    • 问题四;
      • 方法A声明事务为required,A中调用方法B,C(B,C均为required),方法C抛异常,最后事务的执行情况?
        • 全回滚了
    • 问题五:
      • 方法A声明事务为required,A中调用方法B,方法B是REQUIRES_NEW,此时方法B抛异常了,最后的事务执行情况?
        • 均回滚
    • 问题六:
      • 方法A声明事务为required,A中调用方法B,C(B为required,C为REQUIRES_NEW),此时C方法抛异常,最后事务的执行情况?
        • 全回滚
    • 问题七:
      • 方法A声明事务为required,A中调用方法B,C(B为REQUIRES_NEW,C为required),此时C方法抛异常,最后事务的执行情况?
        • 方法B事务执行,方法A事务回滚;
    • transactionManager && gaotuTransactionManager
      • transactionManager --> super_class
      • gaotuTransactionManager --> gaotu
  • @Transactional 万恶的根源均来自于SpringAop的局限性;
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值