事务的四种特性(ACID)
事务具备 ACID 四种特性,ACID 是 Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)和 Durability(持久性)的英文缩写。
原子性(Atomicity)
事务最基本的操作单元,要么全部成功,要么全部失败,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性(Consistency)
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
隔离性(Isolation)
指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
持久性(Durability)
指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
TransactionDefinition - 五个隔离级别
ISOLATION_DEFAULT
这是一个 PlatformTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应;
ISOLATION_READ_UNCOMMITTED
这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。
ISOLATION_READ_COMMITTED
保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
ISOLATION_REPEATABLE_READ
这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
ISOLATION_SERIALIZABLE
这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。
事务的并发问题
Dirty reads - 脏读
指当一个事务正字访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。
non-repeatable reads - 不可重复读
指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。
phantom reads - 幻读
这个和 non-repeatable reads 相似,也是同一个事务中多次读不一致的问题。但是 non-repeatable reads 的不一致是因为他所要取的数据集被改变了(比如 total 的数据),但是 phantom reads 所要读的数据的不一致却不是他所要读的数据集改变,而是他的条件数据集改变。比如 Select account.id where account.name="ppgogo*",第一次读取了 6 个符合条件的 id,第二次读取的时候,由于事务 b 把一个帐号的名字由"dd"改成"ppgogo1",结果取出来了 7 个数据。概括:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读。
TransactionDefinition - 七个事务传播行为:
PROPAGATION_REQUIRED
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
// 事务属性 PROPAGATION_REQUIRED
methodA {
……
methodB();
……
}
// 事务属性 PROPAGATION_REQUIRED
methodB{……}
使用 spring 声明式事务,spring 使用 AOP 来支持声明式事务,会根据事务属性,自动在方法调用之前决定是否开启一个事务,并在方法执行之后决定事务提交或回滚事务。
单独调用 methodB 方法相当于:
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,执行效果相当于:
public static void main(String[] args) {
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 {
……
methodB();
……
}
// 事务属性 PROPAGATION_REQUIRES_NEW
methodB{……}
调用 methodA 时,相当于:
public static void main(String[] args) {
TransactionManager tm = null;
try {
tm = getTransactionManager(); //获得一个JTA事务管理器
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 {
//释放资源
}
tm.resume(ts1); // methodB执行完后,复恢第一个事务
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 {
……
methodB();
……
}
// 事务属性 PROPAGATION_NESTED
methodB{……}
如果单独调用 methodB 方法,则按 REQUIRED 属性执行。如果调用methodA方法,相当于下面的效果:
public static void main(String[] args) {
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 应该是我们首先的事务传播行为。它能够满足我们大多数的事务需求。
spring 事务的四种实现方式
编程式事务
编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用 beginTransaction()、commit()、rollback() 等事务管理相关的方法,这就是编程式事务管理。基本上废弃掉了。
基于 TransactionProxyFactoryBean 的声明式事务管理
基于 @Transactional 的声明式事务管理
基于 Aspectj AOP 配置事务
参考
Spring事务管理之几种方式实现事务