深入理解事务机制

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

什么是事务

事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。

事务有四个特性:ACID

  • 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
  • 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。( 一致性是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。)
  • 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
  • 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

事务的隔离级别

(1)并发事务引起的问题

  • 脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。

  • 不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。

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

  • 丢失更新 ()—— 两个事务同时读取同一条记录,A先修改记录,B也修改记录(B是不知道A修改过),B提交数据后B的修改结果覆盖了A的修改结果。

    不可重复读与幻读的区别在于: 不可重复读的重点是修改,同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 .;在幻读中,在一个事务中不同时间段查询,记录数不同。与不可重复读的主要区别是:在幻读中,已经读取的数据不会改变,只是与以前相比,会有更多的数据满足查询条件。

为了处理这几种问题,SQL定义了下面的4个等级的事务隔离级别:

  • 未提交读(READ UNCOMMITTED ):最低隔离级别,一个事务能读取到别的事务未提交的更新数据,很不安全,可能出现丢失更新、脏读、不可重复读、幻读;
  • 提交读(READ COMMITTED):一个事务能读取到别的事务提交的更新数据,不能看到未提交的更新数据,不会出现丢失更新、脏读,但可能出现不可重复读、幻读;
  • 可重复读(REPEATABLE READ):保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务影响,不可能出现丢失更新、脏读、不可重复读,但可能出现幻读;
  • 序列化(SERIALIZABLE):最高隔离级别,不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读,但是效率最低。

隔离级别越高,数据库事务并发执行性能越差,能处理的操作越少。所以一般地,推荐使用REPEATABLE READ级别保证数据的读一致性。对于幻读的问题,可以通过加锁来防止。

MySQL支持这四种事务等级,默认事务隔离级别是REPEATABLE READ。Oracle数据库支持READ COMMITTED 和 SERIALIZABLE这两种事务隔离级别,所以Oracle数据库不支持脏读。Oracle数据库默认的事务隔离级别是READ COMMITTED。

数据库事务

数据库锁机制

下面我们介绍一下Oracle数据库常用的5种锁定。

  • 行共享锁定:一般通过SELECT FOR UPDATE语句隐式获得行共享锁定,在Oracle中用户也可以通过LOCK TABLE IN ROW SHARE MODE语句显式获得行共享锁定。行共享锁定并不防止对数据行进行更改的操作,但是可以防止其他会话获取独占性数据表锁定。允许进行多个并发的行共享和行独占性锁定,还允许进行数据表的共享或者采用共享行独占锁定。
  • 行独占锁定:通过一条INSERT、UPDATE或DELETE语句隐式获取,或者通过一条LOCK TABLE IN ROW EXCLUSIVE MODE语句显式获取。这个锁定可以防止其他会话获取一个共享锁定、共享行独占锁定或独占锁定。
  • 表共享锁定:通过LOCK TABLE IN SHARE MODE语句显式获得。这种锁定可以防止其他会话获取行独占锁定(INSERT、UPDATE或DELETE),或者防止其他表共享行独占锁定或表独占锁定,它允许在表中拥有多个行共享和表共享锁定。该锁定可以让会话具有对表事务级一致性访问,因为其他会话在用户提交或者回溯该事务并释放对该表的锁定之前不能更改这个被锁定的表。
  • 表共享行独占:通过LOCK TABLE IN SHARE ROW EXCLUSIVE MODE语句显式获得。这种锁定可以防止其他会话获取一个表共享、行独占或者表独占锁定,它允许其他行共享锁定。这种锁定类似于表共享锁定,只是一次只能对一个表放置一个表共享行独占锁定。如果A会话拥有该锁定,则B会话可以执行SELECT FOR UPDATE操作,但如果B会话试图更新选择的行,则需要等待。
  • 表独占:通过LOCK TABLE IN EXCLUSIVE MODE显式获得。这个锁定防止其他会话对该表的任何其他锁定。

总结一下MySQL中的锁:

  • 乐观锁,顾名思义就是非常乐观,非常相信真善美,每次去读数据都认为其它事务没有在写数据,所以就不上锁,快乐的读取数据,而只在提交数据的时候判断其它事务是否搞过这个数据了,如果搞过就rollback。乐观锁相当于一种检测冲突的手段,可通过为记录添加版本或添加时间戳来实现。
  • 悲观锁,对其它事务抱有保守的态度,每次去读数据都认为其它事务想要作祟,所以每次读数据的时候都会上锁,直到取出数据。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性,但随之而来的是各种开销。悲观锁相当于一种避免冲突的手段。 选择标准:如果并发量不大,或数据冲突的后果不严重,则可以使用乐观锁;而如果并发量大或数据冲突后果比较严重(对用户不友好),那么就使用悲观锁。

从读写角度,分共享锁(S锁,Shared Lock)和排他锁(X锁,Exclusive Lock),也叫读锁(Read Lock)和写锁(Write Lock)。 理解:

  • 持有S锁的事务只读不可写。如果事务A对数据D加上S锁后,其它事务只能对D加上S锁而不能加X锁。
  • 持有X锁的事务可读可写。如果事务A对数据D加上X锁后,其它事务不能再对D加锁,直到A对D的锁解除。

从锁的粒度角度,主要分为表级锁(Table Lock)和行级锁(Row Lock)。

  • 表级锁将整个表加锁,性能开销最小。用户可以同时进行读操作。当一个用户对表进行写操作时,用户可以获得一个写锁,写锁禁止其他的用户读写操作。写锁比读锁的优先级更高,即使有读操作已排在队列中,一个被申请的写锁仍可以排在所队列的前列。
  • 行级锁仅对指定的记录进行加锁,这样其它进程可以对同一个表中的其它记录进行读写操作。行级锁粒度最小,开销大,能够支持高并发,可能会出现死锁。

2.理解SQL SERVER中的隔离级别

输入图片说明

3.JDBC 对事务的支持

并不是所有的数据库都支持事务,即使支持事务的数据库也并非支持所有的事务隔离级别,用户可以通过Connection#getMetaData()方法获取DatabaseMetaData对象,并通过该对象的supportsTransactions()、supportsTransactionIsolationLevel(int level)方法查看底层数据库的事务支持情况。

Connection默认情况下是自动提交的,也即每条执行的SQL都对应一个事务,为了能够将多条SQL当成一个事务执行,必须先通过Connection#setAutoCommit(false)阻止Connection自动提交,并可通过Connection#setTransactionIsolation()设置事务的隔离级别,Connection中定义了对应SQL 92标准4个事务隔离级别的常量。通过Connection#commit()提交事务,通过Connec tion#rollback()回滚事务。下面是典型的JDBC事务数据操作的代码: 代码清单9-1 JDBC事务代码

Connection conn ;  
try{  
     conn = DriverManager.getConnection();//①获取数据连接  
     conn.setAutoCommit(false); //②关闭自动提交的机制  
     conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); //③设置事务隔离级别  
  
     Statement stmt = conn.createStatement();   
      
     int rows = stmt.executeUpdate( "INSERT INTO t_topic VALUES(1,’tom’) " );  
     rows = stmt.executeUpdate( "UPDATE t_user set topic_nums = topic_nums +1 "+  
"WHERE user_id = 1");   
       
     conn.commit();//④提交事务  
}catch(Exception e){  
     …  
     conn.rollback();//⑤回滚事务  
}finally{  
   …  
}  

在JDBC 2.0中,事务最终只能有两个操作:提交和回滚。但是,有些应用可能需要对事务进行更多的控制,而不是简单地提交或回滚。JDBC 3.0(JDK 1.4及以后的版本)引入了一个全新的保存点特性,Savepoint 接口允许用户将事务分割为多个阶段,用户可以指定回滚到事务的特定保存点,而并非像JDBC 2.0一样只回滚到开始事务的点,如图9-1所示。 深入理解事务机制_第1张图片

下面的代码使用了保存点的功能,在发生特定问题时,回滚到指定的保存点,而非回滚整个事务,如代码清单9-2所示:

代码清单9-2 使用保存点的事务代码

…  
Statement stmt = conn.createStatement();   
int rows = stmt.executeUpdate( "INSERT INTO t_topic VALUES(1,’tom’)");  
  
Savepoint svpt = conn.setSavepoint("savePoint1");//①设置一个保存点  
rows = stmt.executeUpdate( "UPDATE t_user set topic_nums = topic_nums +1 "+  
          "WHERE user_id = 1");   
…  
//②回滚到①处的savePoint1,①之前的SQL操作,在整个事务提交后依然提交,  
//但①到②之间的SQL操作被撤销了  
conn.rollback(svpt);   
…  
conn.commit();//③提交事务 

并非所有数据库都支持保存点功能,用户可以通过DatabaseMetaData#supportsSavepoints()方法查看是否支持。

spring 事务

2 spring 的核心接口

输入图片说明

2.1 事务管理器

Spring 并不直接管理事务,而是提供了多种事务管理器,将事务管理的职责委托给了Hibernate或者JPs等持久化机制所提供的相关平台框架的事务来实现。Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。此接口的内容如下:

Public interface PlatformTransactionManager()  {  
    // 由TransactionDefinition得到TransactionStatus对象
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 
    // 提交
    Void commit(TransactionStatus status) throws TransactionException;  
    // 回滚
    Void rollback(TransactionStatus status) throws TransactionException;  
    } 

2.1.1 JDBC事务

如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:

    
        
    

实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。

2.1.2 Hibernate事务

如果应用程序的持久化是通过Hibernate实习的,那么你需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的声明:

  
      

sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。

2.1.3 mybatis事务

2.1.4 JPA事务

2.1.5 JTA事务

2.2 事务属性的定义

上面讲到的事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。 那么什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面,如图所示: 输入图片说明

而TransactionDefinition接口内容如下:

public interface TransactionDefinition {  
    int getPropagationBehavior(); // 返回事务的传播行为  
    int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据  
    int getTimeout();  // 返回事务必须在多少秒内完成  
    boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的  
} 

2.2.1 传播行为

事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:

  • PROPAGATION_REQUIRED : 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务。
  • PROPAGATION_SUPPORTS:表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行
  • PROPAGATION_MANDATORY:表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常
  • PROPAGATION_REQUIRED_NEW:表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
  • PROPAGATION_NOT_SUPPORTED: 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
  • PROPAGATION_NEVER:表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常
  • PROPAGATION_NESTED:表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务

PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

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

使用spring声明式事务,spring使用AOP来支持声明式事务,会根据事务属性,自动在方法调用之前决定是否开启一个事务,并在方法执行之后决定事务提交或回滚事务。

单独调用methodB方法:

main{   
    metodB();   
}   
Main{   
    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.执行效果相当于:

main{   
    Connection con = null;   
    try{   
        con = getConnection();   
        methodA();   
        con.commit();   
    } catch(RuntimeException ex) {   
        con.rollback();   
    } finally {      
        closeCon();   
    }    
} 

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

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

//事务属性 PROPAGATION_REQUIRED  
methodA(){  
  methodB();  
}  
  
//事务属性 PROPAGATION_SUPPORTS  
methodB(){  
  ……  
}  
  单纯的调用methodB时,methodB方法是非事务的执行的。当调用methdA时,methodB则加入了methodA的事务中,事务地执行。

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

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

当单独调用methodB时,因为当前没有一个活动的事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);当调用methodA时,methodB则加入到methodA的事务中,事务地执行。

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

//事务属性 PROPAGATION_REQUIRED  
methodA(){  
    doSomeThingA();  
    methodB();  
    doSomeThingB();  
}  
  
//事务属性 PROPAGATION_REQUIRES_NEW  
methodB(){  
    ……  
}  
调用A方法:
main(){  
    methodA();  
}
 相当于
main(){  
    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作为事务管理器。

PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。

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

PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持 DataSourceTransactionManager作为事务管理器。需要JDBC 驱动的java.sql.Savepoint类。有一些JTA的事务管理器实现可能也提供了同样的功能。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true;而 nestedTransactionAllowed属性值默认为false。

//事务属性 PROPAGATION_REQUIRED  
methodA(){  
    doSomeThingA();  
    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_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等.当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.

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

隔离级别

  • ISOLATION_DEFAULT:使用后端数据库默认的隔离级别。
  • ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
  • ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的

只读

事务的第三个特性是它是否为只读事务。如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。

事务超时

为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。

回滚规则

事务五边形的最后一个方面是一组规则,这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的) 。但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。

2.3 事务状态

上面讲到的调用PlatformTransactionManager接口的getTransaction()的方法得到的是TransactionStatus接口的一个实现,这个接口的内容如下:

public interface TransactionStatus{  
    boolean isNewTransaction(); // 是否是新的事物  
    boolean hasSavepoint(); // 是否有恢复点  
    void setRollbackOnly();  // 设置为只回滚  
    boolean isRollbackOnly(); // 是否为只回滚  
    boolean isCompleted; // 是否已完成  
} 

spring @Transactional注解参数详解

当标于类前时, 标示类中所有方法都进行事物处理 ,

/** 事务标注于类前*/
@Transactional 
public class TestServiceBean implements TestService {}

/**当类中某些方法不需要事物时*/
@Transactional 
 public class TestServiceBean implements TestService { 
        private TestDao dao; 
        public void setDao(TestDao dao) { 
                    this.dao = dao; 
        } 

         @Transactional(propagation =Propagation.NOT_SUPPORTED)
          public List getAll() { 
                      return null; 
           } 
 }

事物传播行为使用介绍:

    - @Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)

  - @Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务   - @Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务   - @Transactional(propagation=Propagation.MANDATORY) :必须在一个已有的事务中执行,否则抛出异常   - @Transactional(propagation=Propagation.NEVER) :必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)   - @Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.

事物超时设置:

    @Transactional(timeout=30) //默认是30秒

事务隔离级别:

    - @Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用

  - @Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)   - @Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)   - @Transactional(isolation = Isolation.SERIALIZABLE):串行化

@Transactional注解中常用参数说明 参数名称

功能描述

readOnly

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

rollbackFor

该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:

指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)

指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})

rollbackForClassName

该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:

指定单一异常类名称:@Transactional(rollbackForClassName="RuntimeException")

指定多个异常类名称:@Transactional(rollbackForClassName={"RuntimeException","Exception"})

noRollbackFor

该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:

指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)

指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})

noRollbackForClassName

该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:

指定单一异常类名称:@Transactional(noRollbackForClassName="RuntimeException")

指定多个异常类名称:

@Transactional(noRollbackForClassName={"RuntimeException","Exception"})

propagation

该属性用于设置事务的传播行为,具体取值可参考表6-7。

例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)

isolation

该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置

timeout

该属性用于设置事务的超时秒数,默认值为-1表示永不超时

注意的几点:

  1、@Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.   2、用 spring 事务管理器,由spring来负责数据库的打开,提交,回滚.默认遇到运行期例外(throw new RuntimeException("注释");)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception("注释");)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .如果让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class) 如下:

复制代码 @Transactional(rollbackFor=Exception.class) //指定回滚,遇到异常Exception时回滚 public void methodName() {    throw new Exception("注释"); } @Transactional(noRollbackFor=Exception.class)//指定不回滚,遇到运行期例外(throw new RuntimeException("注释");)会回滚 public ItimDaoImpl getItemDaoImpl() {    throw new RuntimeException("注释"); } 复制代码 3、@Transactional 注解应该只被应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。   4、@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。然而,请注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据,能够被可以识别 @Transactional 注解和上述的配置适当的具有事务行为的beans所使用。上面的例子中,其实正是 元素的出现 开启 了事务行为。   5、Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解。

数据库事务与spring事务的关系

数据库事务与spring事务的联系

数据库事务与spring事务的区别

  • 在没有使用spring事务是,每次调用数据库,会立即提交数据库事务,
  • 在使用了spring事务时,在配置的类的方法执行完成后在进行事务提交,如果一个方法中同时进行了多次的插入、修改或删除操作,会统一的进行提交或回滚,保证数据操作会同时成功或失败,保证数据的完整性。

转载于:https://my.oschina.net/u/3421984/blog/1587505

你可能感兴趣的:(深入理解事务机制)