spring @Transactional的理解

Posted mangues

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring @Transactional的理解相关的知识,希望对你有一定的参考价值。

文章目录

@Transactional 注解的属性信息

属性名说明
name当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
propagation事务的传播行为,默认值为 REQUIRED。
isolation事务的隔离度,默认值采用 DEFAULT
timeout事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
read-only指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollback-for用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
no-rollback- for抛出 no-rollback-for 指定的异常类型,不回滚事务。

@Transactional 只能应用到 public 方法才有效

避免 Spring 的 AOP 的自调用问题

在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。

@Service
public class OrderService 
    private void insert() 
         insertOrder();
    
    @Transactional
    public void insertOrder() 
     
    

insertOrder 尽管有@Transactional 注解,但它被内部方法 insert 调用,事务被忽略,出现异常事务不会发生回滚。

上面的两个问题@Transactional 注解只应用到 public 方法和自调用问题,是由于使用 Spring AOP 代理造成的。为解决这两个问题,使用 AspectJ 取代 Spring AOP 代理。

<tx:annotation-driven mode="aspectj" />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
</bean
class="org.springframework.transaction.aspectj.AnnotationTransactionAspect"
factory-method="aspectOf">
<property name="transactionManager" ref="transactionManager" />
</bean>

同时在 Maven 的 pom 文件中加入 spring-aspects 和 aspectjrt 的 dependency 以及 aspectj-maven-plugin。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.9</version>
</dependency>
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.9</version>
    <configuration>
        <showWeaveInfo>true</showWeaveInfo>
        <aspectLibraries>
            <aspectLibrary>
             <groupId>org.springframework</groupId>
             <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
    </configuration>
    <executions>
        <execution>
        <goals>
            <goal>compile</goal>
            <goal>test-compile</goal>
        </goals>
        </execution>
    </executions>
</plugin>

事务传播行为

  • PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务,就加入到这个事务中。这是最常见的选择。

  • PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行

  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

  • PROPAGATION_MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。

  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

  • PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。

  • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

嵌套事务

带有事务的方法调用其他事务的方法,此时执行的情况取决配置的事务的传播属性

1. PROPAGATION_REQUIRES_NEW :

启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.

2. PROPAGATION_NESTED :

如果外部事务 commit, 嵌套事务也会被 commit;如果外部事务 roll back, 嵌套事务也会被 roll back 。开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交

列子理解 REQUIRED、REQUIRES_NEW、NESTED

    public void testTractration() 
      
    
    public void testTractration1() 
        
    
    public void testTractration2() 
    
    

1、不管 testTractration、testTractration1、testTractration2 哪边报错全部回滚,默认使用REQUIRED

  @Override
    @Transactional(rollbackFor = Exception.class)
    public void testTransactional() 
        contractServiceService.testTransactional1();
        contractServiceService.testTransactional2();
         int i = 1/0; //三处错误都可以全部回滚
    
    @Transactional(rollbackFor = Exception.class)
    public void testTransactional1() 
        int i = 1/0;//三处错误都可以全部回滚
    
    @Transactional(rollbackFor = Exception.class)
    public void testTransactional2() 
        int i = 1/0;//三处错误都可以全部回滚
    

2、testTractration1 被隔离,其他错误不影响其commit,REQUIRES_NEW

 @Override
    @Transactional(rollbackFor = Exception.class)
    public void testTransactional() 
        contractServiceService.testTransactional1();
        contractServiceService.testTransactional2();

    
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void testTransactional1() 
  
    
    @Transactional(rollbackFor = Exception.class)
    public void testTransactional2() 
        int i = 1/0;
    

3、testTractration2 被隔离,错误不影响其它commit,REQUIRES_NEW

 @Override
    @Transactional(rollbackFor = Exception.class)
    public void testTransactional() 
        contractServiceService.testTransactional1();
        //防止抛出的异常影响到 testTransactional的事物
        //并且因为testTransactional1 默认REQUIRED 会使用testTransactional的事物 继续回滚
        try
             contractServiceService.testTransactional2(); 
        catch(Exception e)
            
        
    
    @Transactional(rollbackFor = Exception.class)
    public void testTransactional1() 
  
    
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void testTransactional2() 
        int i = 1/0;
    

4、所有的成功commit,testTractration1才成功,否则回滚(联合成功);testTractration1失败回滚,不影响其他,其他正常commit,NESTED

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testTransactional() 
        //防止抛出错误影响testTransactional事物
        //或者放到testTransactional2下面执行
        try
             contractServiceService.testTransactional1();
        catch(Exception e)
            
        
        contractServiceService.testTransactional2(); 
        int i = 1/0; //出错影响testTransactional1 commit
    
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
    public void testTransactional1() 
         //出错不影响testTransactional2 commit
         int i = 1/0;
    
    @Transactional(rollbackFor = Exception.class)
    public void testTransactional2() 
       
    

数据库的四种隔离级别

READ UNCIMMITTED(未提交读)(脏读,不可重复读,幻读)(“共享写锁”)

事务中的修改,即使没有提交,其他事务也可以看得到,可能读取脏数据

比如购票系统,里面有三张票,A,B售货员。A卖出去三张票,正在写入系统还没commit,B查询系统会显示没有余票,结果A的commit失败了,结果一张票都没有卖出去。

脏读原因:在读取时是不会加锁的,但在更新数据时,对其加行级共享锁(其它事务不能更改,但可以读取,导致脏读)


READ COMMITTED(提交读)( 不可重复读,幻读 )(“共享读锁"和"排他写锁”)

事务中的修改,只有提交成功,其他事务才能看得到,大多数数据库系统的默认隔离级别。可能出现读取旧数据的现象

比如购票系统,里面有三张票,A,B售货员。A卖出去三张票,正在写入系统还没commit,B查询系统会显示有余票,B也就继续卖出了,结果A继续执行的话,余票就是0,可是还是卖出去了。就会多卖出去票

解决方案:

写数据加行级排他锁,这样写过程是无法读取的,直到事务处理完毕才释放排他锁,给读的数据加行级共享锁,这样读的时候也是无法写的。

不可重复读原因:一旦读完该行就释放共享锁,再查询这行就会读到修改过commit的数据,这种模式下虽然处理了脏读,但是并没有处理丢失更新和不可重复读的问


REPEATABLE READ(可重复读)(幻读 )(“共享读锁"和"排他写锁”)

突然蹦出来的行数据。指的就是某个事务在读取某个范围的数据,但是另一个事务又向这个范围的数据去插入数据,这个事务再读取的时候,数据的行数不一致。

解决方案:

给写的数据加行级排他锁,事务结束释放,给读的数据加行级共享锁,事务结束后释放。这种模式还是没有处理幻读的问题

幻读原因:排他锁 增加或者删除数据的话,这数据查询时候是没有共享锁的,就会导致多了数据或者少了数据


SERIALIZABLE(可串行化)(锁表)

它通过强制事务串行执行(注意是串行),避免了前面的幻读情况,由于他大量加上锁,导致大量的请求超时,因此性能会比较底下,再特别需要数据一致性且并发量不需要那么大的时候才可能考虑这个隔离级别

不可重复读,幻读区别

  • 不可重复读的重点是修改 :
    同样的条件 , 你读取过的数据 ,再次读取出来发现值不一样了
  • 幻读的重点在于 新增或者删除
    同样的条件 ,第 1 次和第 2 次读出来的记录数不一样

共享锁

又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。

  1. 一个事务获取了共享锁,在其他查询中也只能加共享锁或不加锁。

排他锁

又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

  1. 排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁.

  2. mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型

  3. 如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句

  4. 所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。

  5. 排他锁update数据,没有commit,select的数据还是老数据

spring @transactional 和synchronized同时使用的问题

public class Demo
     @Transactional
     public void synchronized build() 
          
     

是无法保证数据的一致性
由于spring@Transactional注解使用的是AOP来实现,会在update方法之前开启事务,之后再加锁,当锁住的代码执行完成后,在提交事务,因此synchronized代码块执行是在事务之内执行的,可以推断在代码块执行完时,事务还未提交,

当一个线程执行完该方法并释放锁后,代理类还没有提交事务前,别的线程是有机会进入到该方法中的

可以在build方法之前加上synchronized,在还没有开事务之间就加锁,那么就可以保证线程同步

public class Demo
     @Transactional
     public void build() 
     
     public void synchronized init() 
          build();
     

参考链接

用法

  1. http://sharajava.iteye.com/blog/78270
  2. https://blog.csdn.net/mingyundezuoan/article/details/79017659
  3. https://www.cnblogs.com/cnmenglang/p/6410848.html

数据库隔离

  1. https://www.cnblogs.com/s-b-b/p/5845096.html
  2. https://blog.csdn.net/universsky2015/article/details/77965393
  3. https://blog.csdn.net/eddie_520/article/details/47121571

synchronized

  1. https://blog.csdn.net/u011186019/article/details/52348624
  2. https://blog.csdn.net/luckyxl029/article/details/81282815

数据库的四种隔离级别的实现方式

  1. https://blog.csdn.net/zk3326312/article/details/79457488

mysql共享锁与排他锁

  1. https://www.cnblogs.com/boblogsbo/p/5602122.html

悲观锁,乐观锁,行锁,表锁,页锁,共享锁,排他锁

https://blog.csdn.net/xiangwanpeng/article/details/55106732

以上是关于spring @Transactional的理解的主要内容,如果未能解决你的问题,请参考以下文章

关于Spring事务<tx:annotation-driven/>的理解(Controller可以使用@Transactional)

Spring学习记录1--@Transactional Propagation

初步理解@Transactional注解

@transactional 回滚以后方法执行吗

spring事务传播性理解

Grails @Transactional 与 Spring @Transactional 注释之间的差异