概述
事务传播行为定义了事务的作用范围的:是否需要事务、事务是否可以传递、是否可以嵌套等,
不管你直接采用JDBC进行数据库事务操作,还是使用基于Spring或EJB的声明式事务功能进行事务管理,它们最终都是使用底层数据库的事务管理功能 完成最终任务的。数据库事务拥有许多语义概念,它们是你进行事务编程和事务管理的基础,只有掌握好数据库事务的基础知识,才能深刻理解数据库事务,进而开 发出正确合理的应用。
文将对应用开发中所涉及的数据库事务知识进行概括性的讲解,相信这些知识会给你的数据库应用开发带来帮助。
何为数据库事务
“一荣俱荣,一损俱损”这句话很能体现事务的思想,很多复杂的事物要分步进行,但它们组成一个整体,要么整体生效,要么整体失效。这种思想反映到数据库上,就是多个SQL语句, 要么所有执行成功,要么所有执行失败 。
数据库事务 有严格的定义,它必须同时满足四个特性: 原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)和持久性(Durabiliy) ,简称为ACID。下面是对每个特性的说明:
原子性 :表示 组成一个事务的多个数据库操作是一个不可分隔的原子单元 ,只有所有的操作执行成功,整个事务才提交,事务中任何一个数据库操作失败,已经执行的任何操作都必须撤销,让数据库返回到初始状态;
一致性 : 事务操作成功后,数据库所处的状态和它的业务规则是一致的,即数据不会被破坏 。如从A账户转账100元到B账户,不管操作成功与否,A和B的存款总额是不变的;
隔离性 :在并发数据操作时, 不同的事务拥有各自数据空间,它们的操作不会对对方产生干扰 。准确的说,并非要求做到完全无干扰,数据库规定了 多种事务隔离级别 ,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性越好,但并发性越弱;
持久性 :一旦事务提交成功后,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也 必须能保证能够通过某种机制恢复数据 。
在这些事务特性中,数据 “一致性”是最终目标 ,其它的特性都是为达到这个目标的措施、要求或手段。
数 据库管理系统一般采用 重执行日志 保证原子性、一致性和持久性,重执行日志记录了数据库变化的每一个动作,数据库在一个事务中执行一部分操作后发生错误退 出,数据库即可以根据 重执行日志撤销已经执行的操作 。此外,对于已经提交的事务,即使数据库崩溃,在重启数据库时也能够根据日志对尚未持久化的数据进行相 应的重执行操作。
和Java程序采用 对象锁机制 进行线程同步类似,数据库管理系统采用 数据库锁机制 保证事务的隔离性。当多个事务试图对相同的数据进行操作时, 只有持有锁的 事务才能操作数据 ,直到前一个事务完成后,后面的事务才有机会对数据进行操作。Oracle数据库还使用了数据版本的机制,在回滚段为数据的每个变化都保 存一个版本,使数据的更改不影响数据的读取。
数据并发的问题
一个数据库可能拥有多个访问客户端,这些客户端都可以并发方式访问数据库。数据库中的相同数据可能同时被多个事务访问,如果没有采取必要的隔离措施,就会 导致各种并发问题,破坏数据的完整性。这些问题可以归结为5类,包括 3类数据读问题 (脏读、幻象读和不可重复读)以及 2类数据更新问题 (第一类丢失更新和 第二类丢失更新)。下面,我们分别通过实例讲解引发问题的场景。
脏读(dirty read)
在讲解脏读前,我们先讲一个笑话:一个有结巴的人在饮料店柜台前转悠,老板很热情地迎上来:“喝一瓶?”,结巴连忙说:“我…喝…喝…”,老板麻利地打开 易拉罐递给结巴,结巴终于憋出了他的那句话:“我…喝…喝…喝不起啊!”。在这个笑话中,饮料店老板就对结巴进行了脏读。
A事务读取B事务尚未提交的更改数据,并在这个数据的基础上操作。如果恰巧B事务回滚,那么A事务读到的数据根本是不被承认的。来看取款事务和转账事务并发时引发的脏读场景:
时间
|
转账事务A
|
取款事务B
|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 取出500元把余额改为500元 | |
T5 | 查询账户余额为500元(脏读) | |
T6 | 撤销事务余额恢复为1000元 | |
T7 | 汇入100元把余额改为600元 | |
T8 | 提交事务 |
在这个场景中,B希望取款500元而后又撤销了动作,而A往相同的账户中转账100元,就因为A事务读取了B事务尚未提交的数据,因而造成账户白白丢失了500元。在Oracle数据库中,不会发生脏读的情况。
不可重复读(unrepeatable read)
不可重复读是指A事务读取了B事务已经提交的更改数据。假设A在取款事务的过程中,B往该账户转账100元,A两次读取账户的余额发生不一致:
时间
|
取款事务A
|
转账事务B
|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出100元把余额改为900元 | |
T6 | 提交事务 | |
T7 | 查询账户余额为900元(和T4读取的不一致) |
在同一事务中,T4时间点和T7时间点读取账户存款余额不一样。
幻象读(phantom read)
A事务读取B事务提交的新增数据,这时A事务将出现幻象读的问题。幻象读一般发生在计算统计数据的事务中,举一个例子,假设银行系统在同一个事务中,两 次统计存款账户的总金额,在两次统计过程中,刚好新增了一个存款账户,并存入100元,这时,两次统计的总金额将不一致:
时间
|
统计金额事务A
|
转账事务B
|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 统计总存款数为10000元 | |
T4 | 新增一个存款账户,存款为100元 | |
T5 | 提交事务 | |
T6 | 再次统计总存款数为10100元(幻象读) |
如果新增数据刚好满足事务的查询条件,这个新数据就进入了事务的视野,因而产生了两个统计不一致的情况。
幻象读和不可重复读是 两个容易混淆的概念,前者是指读到了其它已经提交事务的新增数据,而后者是指读到了已经提交事务的更改数据(更改或删除),为了避免这两种情况,采取的对 策是不同的,防止读取到更改数据,只需要对操作的数据添加行级锁,阻止操作中的数据发生变化,而防止读取到新增数据,则往往需要添加表级锁——将整个表锁 定,防止新增数据(Oracle使用多版本数据的方式实现)。
第一类丢失更新
A事务撤销时,把已经提交的B事务的更新数据覆盖了。这种错误可能造成很严重的问题,通过下面的账户取款转账就可以看出来:
时间
|
取款事务A
|
转账事务B
|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 汇入100元把余额改为1100元 | |
T6 | 提交事务 | |
T7 | 取出100元把余额改为900元 | |
T8 | 撤销事务 | |
T9 | 余额恢复为1000元(丢失更新) |
A事务在撤销时,“不小心”将B事务已经转入账户的金额给抹去了。
第二类丢失更新
A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失:
时间
|
转账事务A
|
取款事务B
|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出100元把余额改为900元 | |
T6 | 提交事务 | |
T7 | 汇入100元 | |
T8 | 提交事务 | |
T9 | 把余额改为1100元(丢失更新) |
上面的例子里由于支票转账事务覆盖了取款事务对存款余额所做的更新,导致银行最后损失了100元,相反如果转账事务先提交,那么用户账户将损失100元。
数据库锁机制
数据并发会引发很多问题,在一些场合下有些问题是允许的,但在另外一些场合下可能却是致命的。数据库通过锁的机制解决并发访问的问题,虽然不同的数据库在实现细节上存在差别,但原理基本上是一样的。
按锁定的对象的不同 ,一般可以分为 表锁定 和 行锁定 ,前者对整个表进行锁定,而后者对表中特定行进行锁定。 从并发事务锁定的关系上看 ,可以分为 共享锁定 和 独 占锁定 。共享锁定会防止独占锁定,但允许其它的共享锁定。而独占锁定既防止其它的独占锁定,也防止其它的共享锁定。为了更改数据,数据库必须在进行更改的 行上施加行独占锁定,INSERT、UPDATE、DELETE和SELECT FOR UPDATE语句都会隐式采用必要的行锁定。下面我们介绍一下 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显式获得。这个锁定防止其它会话对该表的任何其它锁定。
事务隔离级别
尽管数据库为用户提供了锁的DML操作方式,但直接使用锁管理是非常麻烦的,因此数据库为用户提供了自动锁机制。只要用户指定会话的事务隔离级别,数据库 就会分析事务中的SQL语句,然后自动为事务操作的数据资源添加上适合的锁。此外数据库还会维护这些锁,当一个资源上的锁数目太多时,自动进行锁升级以提 高系统的运行性能,而这一过程对用户来说完全是透明的。
ANSI/ISO SQL 92标准定义了 4个等级的事务隔离级别 ,在相同数据环境下,使用相同的输入,执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同事务隔离级别能够解决的数据并发问题的能力是不同的。
表 1 事务隔离级别对并发问题的解决情况 (读取未提交、读取已提交、可重复读、序列化)
隔离级别
|
脏读
|
不可
重复读
|
幻象读
|
第一类丢失更新
|
第二类丢失更新
|
READ UNCOMMITED |
允许
|
允许
|
允许
|
不允许
|
允许
|
READ COMMITTED |
不允许
|
允许
|
允许
|
不允许
|
允许
|
REPEATABLE READ |
不允许
|
不允许
|
允许
|
不允许
|
不允许
|
SERIALIZABLE |
不允许
|
不允许
|
不允许
|
不允许
|
不允许
|
事务的隔离级别和数据库并发性是对立的,两者此增彼长。一般来说,使用READ UNCOMMITED隔离级别的数据库拥有最高的并发性和吞吐量,而使用SERIALIZABLE隔离级别的数据库并发性最低。
SQL 92定义READ UNCOMMITED主要是为了提供非阻塞读的能力,Oracle虽然也支持READ UNCOMMITED,但它不支持脏读,因为Oracle使用多版本机制彻底解决了在非阻塞读时读到脏数据的问题并保证读的一致性,所以,Oracle的 READ COMMITTED隔离级别就已经满足了SQL 92标准的REPEATABLE READ隔离级别。
SQL 92推荐使用REPEATABLE READ以保证数据的读一致性,不过用户可以根据应用的需要选择适合的隔离等级。
JDBC对事务的支持
并不是所有的数据库都支持事务,即使支持事务的数据库也并非支持所有的事务隔离级别,你可以通过Connection# getMetaData()方法获取DatabaseMetaData对象,并通过该对象的supportsTransactions()、 supportsTransactionIsolationLevel(int level)方法查看底层数据库的事务支持情况。Connection默认情况下是自动提交的,也即每条执行的SQL都对应一个事务,为了能够将多条 SQL当成一个事务执行,必须先通过Connection#setAutoCommit(false)阻止Connection自动提交,并可通过 Connection#setTransactionIsolation()设置事务的隔离级别,Connection中定义了对应SQL 92标准4个事务隔离级别的常量。通过Connection#commit()提交事务,通过Connection#rollback()回滚事务。下面 是典型的JDBC事务数据操作的代码:
代码清单 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 { … }
图 1 带Savepoint的事务
下面的代码使用了保存点的功能,在发生特定问题时,回滚到指定的保存点,则非回滚整个事务,如代码清单 2所示:
代码清单 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 " ); … conn.rollback(svpt); ②回滚到①处的savePoint1,①之前的SQL操作,在整个事务提交 后依然提交,但①到②之间的SQL操作被撤销了 … conn.commit();③提交事务
并非所有数据库都支持保存点功能,你可以通过DatabaseMetaData#supportsSavepoints()方法查看是否支持。
小结
数据一致性和访问的并发性两者之间的最佳平衡 永远是数据库应用程序开发所追求的终极目录。 数据事务是保证数据访问一致性的不二法门 ,使用API进行数据库 事务应用开发时,必须深刻了解事务控制属性对应用性能和数据一致性两者的影响,并根据项目实际需要进行合理的设置。
一、 数据库事务概念
数据库事务的特征: ACID
Atomic (原子性)、 Consistency (一致性)、 Isolation (隔离性)和 Durability (持久性)。DBMS 用日志来保证数据的原子性、一致性和持久性;用锁的机制来保证数据的隔离性。
二、 事务的边界
数据库支持 2 种事务模式:自动提交和手动提交。
JDBC API 的事务边界
{
Connection conn = java.sql.DriverManager,.getConnection(dbUrl,dbUser,dbPwd);
conn.setAutoCommit( false ); // 设置成手动模式
stmt = conn.createStatement();
stmt.executeUpdate( " . " ); // 数据库更新1
stmt.executeUpdate( " . " ); // 数据库更新2
conn.commit();
}
catch (Exception e)
{
conn.rollback();
}
finally
{
stmt.close();
conn.close();
}
Hibernate API 声明事务边界
Transaction tx;
try
{
tx = session.beginTransaction(); // 开始事务
// 执行操作
。。。。。
tx.commit();
}
catch (Exception e)
{
if (tx != null )
{
tx.rollback();
}
}
finally
{
session.close();
}
注:一个 session 可以对应多个事务,但是推荐的做法是一个 session 对应一个事务。
三、 多事务的并发问题
当多个事务同时访问相同的数据的时候,程序如果没有采取适当的隔离措施,就会发生数据库的并发问题。常见的并发问题有:
第一类丢失更新:撤消事务的时候,把其他的事务已经提交的数据给覆盖了;
脏读;读了没有提交的数据;
虚读:一个事务读到另外一个事务已经提交的新插入的数据;
不可重复读:一个事务读到另外一个事务已经提交的更新的数据;
第二类丢失更新:一个事务覆盖另外一个事务已经提交的更新数据。
四、 锁
一般地,大型的 DBMS 都会自动的管理锁定机制,但是在对数据的安全性、完整性和一致性有特殊要求的地方,可以由事务本身来管理琐的机制。
有一点要关注的是:锁的粒度越大,隔离性越好,并发性越差。
按照锁的程度来分有:
共享锁:用读操作,非独占的,其他事务可以读,但是不能更新,并发性好;
独占锁:用与 insert update 和 delete 等语句,其他事务不能读,也不能改,并发性差;
更新锁:执行 update 的时候,加锁。
死琐:多是事务分别锁定了一个资源,又请求锁定对方已经锁定的资源,就造成了请求环。
降低死锁的最好办法是使用短事务。
五、 数据库的事务隔离级别
数据库提供 4 种事务隔离级别:
Serializable :串行化;(隔离级别最高) 8
Repeatable Read :可重复读;4
Read Commited :读已提交数据;2
Read Uncommited :读未提交数据;(隔离级别最低)1
Hiberate 中的隔离级别的设置
在 Hibernate 的配置文件中 hibernate.connection.isolation=2
<property name=”hibernate.connection.isolation”>4</property>
六、 悲观锁和乐观琐
从应用程序的角度来看,锁分为悲观锁和乐观锁。
悲观锁:显示的为程序加锁,但是降低并发性。
Select ……. For update;
在 Hibernate 中的代码
Session.get(Account.class,net Long(1),LockMode.UPGRADE) ; // 程序采用悲观锁
乐观锁:依靠 DBMS 来管理锁,程序依靠版本控制来避免并发问题。
在对象 - 关系映射的文件中,用 <version> 或者 <timestamp> 可以管理并发。乐观琐比悲观琐有更好的并发性,优先考虑乐观琐。
==========================传播行为======================
7个传播行为,4个隔离级别,
Spring事务的传播行为和隔离级别[transaction behavior and isolated level]2007-08-01 16:33事务的传播行为和隔离级别[transaction behavior and isolated level]
Spring中事务的定义:
一、Propagation :
key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用:
PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
很多人看到事务的传播行为属性都不甚了解,我昨晚看了j2ee without ejb的时候,看到这里也不了解,甚至重新翻起数据库系统的教材书,但是也没有找到对这个的分析。今天搜索,找到一篇极好的分析文章,虽然这篇文章是重点分析PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRED_NESTED的
====================================================
解惑 spring 嵌套事务
/**
* @date 2006-11-24
* @note 转载自http://www.javaeye.com/topic/35907?page=1
*/
********TransactionDefinition 接口定义*******************
/**
* Support a current transaction, create a new one if none exists.
* Analogous to EJB transaction attribute of the same name.
*
This is typically the default setting of a transaction definition.
*/
int PROPAGATION_REQUIRED = 0;
/**
* Support a current transaction, execute non-transactionally if none exists.
* Analogous to EJB transaction attribute of the same name.
*
Note: For transaction managers with transaction synchronization,
* PROPAGATION_SUPPORTS is slightly different from no transaction at all,
* as it defines a transaction scopp that synchronization will apply for.
* As a consequence, the same resources (JDBC Connection, Hibernate Session, etc)
* will be shared for the entire specified scope. Note that this depends on
* the actual synchronization configuration of the transaction manager.
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
*/
int PROPAGATION_SUPPORTS = 1;
/**
* Support a current transaction, throw an exception if none exists.
* Analogous to EJB transaction attribute of the same name.
*/
int PROPAGATION_MANDATORY = 2;
/**
* Create a new transaction, suspend the current transaction if one exists.
* Analogous to EJB transaction attribute of the same name.
*
Note: Actual transaction suspension will not work on out-of-the-box
* on all transaction managers. This in particular applies to JtaTransactionManager,
* which requires the javax.transaction.TransactionManager to be
* made available it to it (which is server-specific in standard J2EE).
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
*/
int PROPAGATION_REQUIRES_NEW = 3;
/**
* Execute non-transactionally, suspend the current transaction if one exists.
* Analogous to EJB transaction attribute of the same name.
*
Note: Actual transaction suspension will not work on out-of-the-box
* on all transaction managers. This in particular applies to JtaTransactionManager,
* which requires the javax.transaction.TransactionManager to be
* made available it to it (which is server-specific in standard J2EE).
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
*/
int PROPAGATION_NOT_SUPPORTED = 4;
/**
* Execute non-transactionally, throw an exception if a transaction exists.
* Analogous to EJB transaction attribute of the same name.
*/
int PROPAGATION_NEVER = 5;
/**
* Execute within a nested transaction if a current transaction exists,
* behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB.
*
Note: Actual creation of a nested transaction will only work on specific
* transaction managers. Out of the box, this only applies to the JDBC
* DataSourceTransactionManager when working on a JDBC 3.0 driver.
* Some JTA providers might support nested transactions as well.
* @see org.springframework.jdbc.datasource.DataSourceTransactionManager
*/
int PROPAGATION_NESTED = 6;
*************************************************************************
在这篇文章里,他用两个嵌套的例子辅助分析,我这里直接引用了。
********************sample***********************
ServiceA {
/**
* 事务属性配置为 PROPAGATION_REQUIRED
*/
void methodA() {
ServiceB.methodB();
}
}
ServiceB {
/**
* 事务属性配置为 PROPAGATION_REQUIRED
*/
void methodB() {
}
}
*************************************************
1: PROPAGATION_REQUIRED
加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务
比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,
ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA
的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被
提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚
2: PROPAGATION_SUPPORTS
如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行
3: PROPAGATION_MANDATORY
必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常
4: PROPAGATION_REQUIRES_NEW
这个就比较绕口了。 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,
那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,
他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在
两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,
如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。
5: PROPAGATION_NOT_SUPPORTED
当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,
那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。
6: PROPAGATION_NEVER
不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,
那么ServiceB.methodB就要抛出异常了。
7: PROPAGATION_NESTED
理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,
而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。
而Nested事务的好处是他有一个savepoint。
*****************************************
ServiceA {
/**
* 事务属性配置为 PROPAGATION_REQUIRED
*/
void methodA() {
try {
//savepoint
ServiceB.methodB(); //PROPAGATION_NESTED 级别
} catch (SomeException) {
// 执行其他业务, 如 ServiceC.methodC();
}
}
}
********************************************
也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA也会回滚到savepoint点上,ServiceA.methodA可以选择另外一个分支,比如
ServiceC.methodC,继续执行,来尝试完成自己的事务。
但是这个事务并没有在EJB标准中定义。