美文网首页
【JAVA】Spring事务管理详解

【JAVA】Spring事务管理详解

作者: Y了个J | 来源:发表于2018-08-30 15:09 被阅读17次

    事物的特性(ACID)

    原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
    一致性: 执行事务前后,数据保持一致;
    隔离性: 并发访问数据库时,一个用户的事物不被其他事物所干扰,各并发事务之间数据库是独立的;
    持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
    

    我们在使用JDBC或者Mybatis进行数据持久化操作时,我们的xml配置通常如下:

        <!-- 事务管理器 -->
        <bean id="transactionManager"
            class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 数据源 -->
            <property name="dataSource" ref="dataSource" />
        </bean>
    
        <!-- 开启事务行为 -->
        <tx:annotation-driven transaction-manager="transactionManager" />
    

    并发事务带来的问题

    在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但可能会导致一下的问题。

    • 脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。

    • 丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。
      例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。

    • 不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

    • 幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

    不可重复度和幻读区别:
    不可重复读的重点是修改,幻读的重点在于新增或者删除。

    spring事务的传播性、隔离性

    @Transactional
    (1)这里说明一下,有的把这个注解放在类名称上面了,这样你配置的这个@Transactional 对这个类中的所有public方法都起作用.
    (2)@Transactional 方法方法名上,只对这个方法有作用,同样必须是public的方法

    我们在使用Spring声明式事务时,有一个非常重要的概念就是事务的属性。事务属性通常由事务的传播行为、事务的隔离级别、事务的超时值和事务的只读标识组成。我们在进行事务划分时,需要进行事务定义,也就是配置事务的属性。

    屏幕快照 2018-08-30 下午2.15.21.png

    Spring在TransactionDefinition接口中定义这些属性,以供PlatfromTransactionManager使用, PlatfromTransactionManager是spring事务管理的核心接口。

    public interface TransactionDefinition {
        int PROPAGATION_REQUIRED = 0;
        int PROPAGATION_SUPPORTS = 1;
        int PROPAGATION_MANDATORY = 2;
        int PROPAGATION_REQUIRES_NEW = 3;
        int PROPAGATION_NOT_SUPPORTED = 4;
        int PROPAGATION_NEVER = 5;
        int PROPAGATION_NESTED = 6;
        int ISOLATION_DEFAULT = -1; //默认的隔离级别
        int ISOLATION_READ_UNCOMMITTED = 1;
        int ISOLATION_READ_COMMITTED = 2;
        int ISOLATION_REPEATABLE_READ = 4;
        int ISOLATION_SERIALIZABLE = 8;
        int TIMEOUT_DEFAULT = -1;
    
        int getPropagationBehavior();
    
        int getIsolationLevel();
    
        int getTimeout();
    
        boolean isReadOnly();
    
        String getName();
    }
    

    getTimeout()方法,它返回事务必须在多少秒内完成。
    isReadOnly(),事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的。
    getIsolationLevel()方法返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据。

    隔离级别

    在TransactionDefinition接口中定义了五个不同的事务隔离级别 :
    a) ISOLATION_DEFAULT:(PlatfromTransactionManager的)默认的隔离级别。使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应 。
    b)ISOLATION_READ_UNCOMMITTED:这是事务最低的隔离级别,它允许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。

    例如:
    Mary的原工资为1000,财务人员将Mary的工资改为了8000,但未提交事务

    Connection con1 = getConnection();  
    con1.setAutoCommit(false);  
    update employee set salary = 8000 where empId ="Mary"; 
    

    与此同时,Mary正在读取自己的工资 。

    Connection con2 = getConnection();  
    select salary from employee where empId ="Mary";  
    con2.commit();
    

    Mary发现自己的工资变为了8000,欢天喜地!
    而财务发现操作有误,而回滚了事务,Mary的工资又变为了1000 。

    con1.rollback(); 
    

    像这样,Mary记取的工资数8000是一个脏数据。

    c)ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
    d)ISOLATION_REPEATABLE_READ : 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。

    例如:
    在事务1中,Mary 读取了自己的工资为1000,操作并没有完成 :

    Connection con1 = getConnection();  
    con1.setAutoCommit(false);  
    select salary from employee empId ="Mary"; 
    

    在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务.

    Connection con2 = getConnection();  
    update employee set salary = 2000;  
    con2.commit();  
    

    在事务1中,Mary 再次读取自己的工资时,工资变为了2000

    //con1 
    select salary from employee empId ="Mary";  
    

    在一个事务中前后两次读取的结果并不致,导致了不可重复读。
    使用ISOLATION_REPEATABLE_READ可以避免这种情况发生。

    e)ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。

    例如:
    目前工资为1000的员工有10人。
    事务1,读取所有工资为1000的员工。共读取10条记录

    con1 = getConnection();  
    Select * from employee where salary =1000; 
    

    这时另一个事务向employee表插入了一条员工记录,工资也为1000

    con2 = getConnection();  
    Insert into employee(empId,salary) values("Lili",1000);  
    con2.commit();  
    

    事务1再次读取所有工资为1000的员工

    //con1
    select * from employee where salary =1000;  
    

    共读取到了11条记录,这就产生了幻像读。
    ISOLATION_SERIALIZABLE能避免这样的情况发生。但是这样也耗费了最大的资源。

    事务的传播性

    在TransactionDefinition接口中定义了七个事务传播行为。
    假如我写了两个service类。名字分别为ServiceA和ServiceB。如下:

    @Service("ServiceA")
    public class ServiceA {
    
        @Resource(name="ServiceB")
        private ServiceB serviceB;
    
        @Transactional(propagation=Propagation.REQUIRED)
        public methodA(){
           //doSomething
           serviceB.methodB(); 
           //doSomething
        }
    }
    
    @Service("ServiceB")
    public class ServiceB {
    
        @Transactional(propagation=Propagation.REQUIRED)
        public methodB(){
           //doSomething
        }
    }
    

    a)PROPAGATION_REQUIRED:如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
    如果单独调用serviceB.methodB方法:

    public static void main(String[] args) {
       serviceB.methodB();
    }
    

    相当于:

    public static void main(String[] args) {
      Connection con=null;  
      try{  
          con = getConnection();  
          con.setAutoCommit(false);  
          //方法调用  
          methodB();  
          //提交事务  
          con.commit();  
     }Catch(RuntimeException ex){  
        //回滚事务  
        con.rollback();    
     }finally{  
        //释放资源  
        closeCon();  
     }  
    }  
    

    Spring保证在methodB方法中所有的调用都获得到一个相同的连接。在调用methodB时,没有一个存在的事务,所以获得一个新的连接,开启了一个新的事务。

    如果单独调用MethodA时,在MethodA内又会调用MethodB.
    执行效果相当于:

    public static void main(String[] args) {
       Connection con = null;  
       try{  
          con = getConnection();  
          con.setAutoCommit(false);  
          methodA();  
          con.commit();  
       }cathc(RuntimeException ex){  
          con.rollback();  
       }finally{  
          closeCon();  
       }   
    }  
    

    调用MethodA时,环境中没有事务,所以开启一个新的事务.
    当在MethodA中调用MethodB时,环境中已经有了一个事务,所以methodB就加入当前事务。

    b)PROPAGATION_SUPPORTS :如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。

    //事务属性 PROPAGATION_REQUIRED   
    methodA(){  
      serviceB.methodB();  
    }  
    
    //事务属性 PROPAGATION_SUPPORTS   
    methodB(){  
      ……  
    } 
    

    单纯的调用methodB时,methodB方法是非事务的执行的。
    当调用methdA时,methodB则加入了methodA的事务中,事务地执行。

    c)PROPAGATION_MANDATORY :如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

    //事务属性 PROPAGATION_REQUIRED   
    methodA(){  
      serviceB.methodB();  
    }  
    
    //事务属性 PROPAGATION_MANDATORY   
    methodB(){  
      ……  
    }  
    

    当单独调用methodB时,因为当前没有一个活动的事务,则会抛出异常
    当调用methodA时,methodB则加入到methodA的事务中,事务地执行。

    d)PROPAGATION_REQUIRES_NEW :总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

    //事务属性 PROPAGATION_REQUIRED   
    methodA(){  
       doSomeThingA();  
       serviceB.methodB();  
       doSomeThingB();  
    }  
    
    //事务属性 PROPAGATION_REQUIRES_NEW   
    methodB(){  
      ……  
    }  
    

    当单独调用methodB时,相当于把methodB声明为REQUIRED。开启一个新的事务,事务地执行。
    当调用methodA时,情况就大不一样了,相当于下面的效果。

    public static void main(String[] args) {
     TransactionManager tm = null;  
     try{  
      //获得一个JTA事务管理器  
       tm = getTransactionManager();  
       tm.begin();//开启一个新的事务  
       Transaction ts1 = tm.getTransaction();  
       doSomeThing();  
       tm.suspend();//挂起当前事务  
       try{  
         tm.begin();//重新开启第二个事务  
         Transaction ts2 = tm.getTransaction();  
         methodB();  
         ts2.commit();//提交第二个事务  
       }Catch(RunTimeException ex){  
         ts2.rollback();//回滚第二个事务  
       }finally{  
        //释放资源  
       }  
       //methodB执行完后,复恢第一个事务  
       tm.resume(ts1);  
       doSomeThingB();  
       ts1.commit();//提交第一个事务  
     }catch(RunTimeException ex){  
      ts1.rollback();//回滚第一个事务  
     }finally{  
      //释放资源  
     }  
    }  
    

    在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了methodB之外的其它代码导致的结果却被回滚了。
    使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作为事务管理器。

    e)PROPAGATION_NOT_SUPPORTED: 总是非事务地执行,并挂起任何存在的事务。

    事务属性 PROPAGATION_REQUIRED   
    methodA(){  
      doSomeThingA();  
      serviceB.methodB();  
      doSomeThingB();  
    }  
    
    事务属性 PROPAGATION_NOT_SUPPORTED   
    methodB(){  
      ……  
    }  
    

    当单独调用methodB时,不启用任何事务机制,非事务地执行。
    当调用methodA时,相当于下面的效果

    public static void main(String[] args) {
     TransactionManager tm = null;  
     try{  
      //获得一个JTA事务管理器  
       tm = getTransactionManager();  
       tm.begin();//开启一个新的事务  
       Transaction ts1 = tm.getTransaction();  
       doSomeThing();  
       tm.suspend();//挂起当前事务  
       methodB();  
       //methodB执行完后,复恢第一个事务  
       tm.resume(ts1);  
       doSomeThingB();  
       ts1.commit();//提交第一个事务  
     }catch(RunTimeException ex){  
       ts1.rollback();//回滚第一个事务  
     }finally{  
      //释放资源  
     }  
    }  
    

    使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。

    f)PROPAGATION_NEVER :总是非事务地执行,如果存在一个活动事务,则抛出异常:

    事务属性 PROPAGATION_REQUIRED   
    methodA(){  
      doSomeThingA();  
      seviceB.methodB();  
      doSomeThingB();  
    }  
    
    事务属性 PROPAGATION_NEVER   
    methodB(){  
      ……  
    }
    

    单独调用methodB,则非事务的执行。 调用methodA则会抛出异常

    g)PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行

    这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC 驱动的java.sql.Savepoint类。有一些JTA的事务管理器实现可能也提供了同样的功能。

    使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true;
    而nestedTransactionAllowed属性值默认为false;

    如下:

    <bean id="system.platformTransactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">  
            <property name="sessionFactory" ref="system.sessionFactory"/>  
            <property name="nestedTransactionAllowed" value="true"/>  
     </bean>
    
    事务属性 PROPAGATION_REQUIRED   
    methodA(){  
      doSomeThingA();  
      serviceB.methodB();  
      doSomeThingB();  
    }  
    
    事务属性 PROPAGATION_NESTED  
    methodB(){  
      ……  
    } 
    

    如果单独调用methodB方法,则按REQUIRED属性执行。
    如果调用methodA方法,相当于下面的效果

    main(){  
    Connection con = null;  
    Savepoint savepoint = null;  
    try{  
      con = getConnection();  
      con.setAutoCommit(false);  
      doSomeThingA(); 
      //创造一个事务的保存点 
      savepoint = con2.setSavepoint();  
      try  
          methodB();  
      }catch(RuntimeException ex){  
         con.rollback(savepoint);  
      }finally{  
        //释放资源  
      }  
      doSomeThingB();  
      con.commit();  
    }catch(RuntimeException ex){  
      con.rollback();  
    }finally{  
      //释放资源  
    }  
    } 
    

    当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码doSomeThingB()方法调用失败,则回滚包括methodB方法的所有操作。

    嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。

    PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。使用PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA事务管理器的支持。
    使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED时,需要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTA TrasactionManager实现可能有不同的支持方式。

    PROPAGATION_REQUIRED应该是我们首先的事务传播行为。它能够满足我们大多数的事务需求。

    事务的超时性、回滚和只读

    • 超时:
      @Transactional(timeout=30) //默认是30秒
      异常回滚:
      指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)
      指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
      该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。

    正常的情况下也可以回滚:
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

    • 只读:
      @Transactional(readOnly=true)
      该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。

    相关文章

      网友评论

          本文标题:【JAVA】Spring事务管理详解

          本文链接:https://www.haomeiwen.com/subject/slgrwftx.html