第19章 Spring事务王国的架构(二)

第19章 Spring事务王国的架构

19.2 和平年代

Spring的事务抽象包括3个主要接口,即PlatformTransactionManager、TransactionDefinition以及TransactionStatus,它们之间的关系如图19-4所示。

第19章 Spring事务王国的架构(二)_第1张图片

这3个接口以PlatformTransactionManager为中心,互为犄角,多少有点儿“晋西北铁三角”的味道。

PlatformTransactionManager负责界定事务边界。TransactionDefinition负责定义事务相关属性,包括隔离级别、传播行为等。

PlatformTransactionManager将参照TransactionDefinition的属性定义来开启相关事务。事务开启之后到事务结束期间的事务状态由TransactionStatus负责,我们也可以通过TransactionStatus对事务进行有限的控制。

19.2.1 TransactionDefinition

1. TrangactionDefinition简介

TransactionDefinition主要定义了有哪些事务属性可以指定,这包括:

  • 事务的隔离(Isolation)级别

  • 事务的传播行为(Propagation Behavior)

  • 事务的超时时间(Timeout)

  • 是否为只读(ReadOnly)

事务TransactionDefinition内定义了如下5个常量用于标志可供选择的隔离级别。

  • ISOLATION_DEFAULT。如果指定隔离级别为ISOLATION_DEFAULT,则表示使用数据库默认的隔离级别。

  • ISOLATION_READ_UNCOMMITTED。对应ReadUncommitted隔离级别,无法避免脏读,不可重复读和幻读。

  • ISOLATION_READ_COMMITTED。对应ReadCommitted隔离级别,可以避免脏读,但无法避免不可重复读和幻读。

  • ISOLATION_REPEATABLE_READ。对应Repeatableread隔离级别,可以避免脏读和不可重复读,但不能避免幻读。

  • ISOLATION_SERIALIZABLE。对应Serializable隔离级别,可以避免所有的脏读,不可重复读以及幻读,但并发性效率最低。

事务的传播行为(Propagation Behavior)我们之前没有提到,如果你之前接触过EJB的CMT的话,对它应该也不会陌生。事务的传播行为表示整个事务处理过程所跨越的业务对象,将以什么样的行为参与事务(我们将在声明式事务中更多地依赖于该属性)。比如,当有FoobarService调用FooService和BarService两个方法的时候,FooService的业务方法和BarService的业务方法可以指定它们各自的事务传播行为(如图19-5所示)。

第19章 Spring事务王国的架构(二)_第2张图片

FooService的业务方法的传播行为被我们指定为Required,表示如果当前存在事务的话,则加入当前事务。因为FoobarService在调用FooService的业务方法的时候已经启动了一个事务,所以,FooSerivce的业务方法会直接加入FoobarService启动的事务1中。BarService的业务方法0的传播行为被指定为Required New,表示无论当前是否存在事务,都需要为其重新启动一个事务,所以,它使用的是自己启动的事务2。

针对事务的传播行为,TransactionDefinition提供了以下几种选择,除了PROPAGATION_NESTED是Spring特有的外,其他传播行为的语义与CMT基本相同。

  • PROPAGATION_REQUIRED。如果当前存在一个事务,则加入当前事务。如果不存在任何事务,则创建一个新的事务。总之,要至少保证在一个事务中运行。PROPAGATION_REQUIRED通常作为默认的事务传播行为。

  • PROPAGATION_SUPPORTS。如果当前存在一个事务,则加入当前事务。如果当前不存在事务,则直接执行。对于一些查询方法来说,PROPAGATION_SUPPORTS通常是比较合适的传播行为选择。 如果当前方法直接执行,那么不需要事务的支持。如果当前方法被其他方法调用,而其他方法启动了一个事务,使用PROPAGATION_SUPPORTS可以保证当前方法能够加入当前事务,并洞察当前事务对数据资源所做的更新。比如,A.service()会首先更新数据库,然后调用B.service()进行查询,那么,如果B.service()是PROPAGATION_SUPPORTS的传播行为,就可以读取A.service()之前所做的最新更新结果(如图19-6所示)。而如果使用稍后所提到的PROPAGATION_NOT_SUPPORTED,则B.service()将无法读取最新的更新结果,因为A.service()的事务在这时还没有提交(除非隔离级别是Read Uncommitted)。

第19章 Spring事务王国的架构(二)_第3张图片
  • PROPAGATION_MANDATORY。PROPAGATION_MANDATORY强制要求当前存在一个事务,如果不存在,则抛出异常。如果某个方法需要事务支持,但自身又不管理事务提交或者回滚,那么比较适合使用PROPAGATION_MANDATORY。 可以参照Java Transaction Design Strategies一书中对REQUIRED和MANDATORY两种传播行为的比较,来更深入地了解PROPAGATION_MANDATORY可能的应用场景。
  • PROPAGATION_REQUIRES_NEW。不管当前是否存在事务,都会创建新的事务。如果当前存在事务,会将当前的事务挂起(Suspend)。如果某个业务对象所做的事情不想影响到外层事务,PROPAGATION_REQUIRES_NEW应该是合适的选择。 比如,假设当前的业务方法需要向数据库中更新某些日志信息,但即使这些日志信息更新失败,我们也不想因为该业务方法的事务回滚,而影响到外层事务的成功提交。因为这种情况下,当前业务方法的事务成功与否对外层事务来说是无关紧要的。
  • PROPAGATION_NOT_SUPPORTED。不支持当前事务,而是在没有事务的情况下执行。如果当前存在事务的话,当前事务原则上将被挂起(Suspend),但这要看对应的PlatformTransactionManager实现类是否支持事务的挂起。更多情况请参照TransactionDefinition的Javadoc文档。PROPAGATION_NOT_SUPPORTED与PROPAGATION_SUPPORTS之间的区别,可以参照PROPAGATION_SUPPORTS部分的实例内容。
  • PROPAGATION_NEVER。永远不需要当前存在事务,如果存在当前事务,则抛出异常。
  • PROPAGATION_NESTED。如果存在当前事务,则在当前事务的一个嵌套事务中执行,否则与PROPAGATION_REQUIRED的行为类似,即创建新的事务,在新创建的事务中执行。PROPAGATION_NESTED粗看起来好像与PROPAGATION_REQUIRES_NEW的行为类似,实际上二者是有差别的。PROPAGATION_REQUIRES_NEW创建的新事务与外层事务属于同一个“档次”,即二者的地位是相同的。当新创建的事务运行的时候,外层事务将被暂时挂起。而PROPAGATIONNESTED创建的嵌套事务则不然,它是寄生于当前外层事务的,它的地位比当前外层事务的地位要小一号。当内部嵌套事务运行的时候,外层事务也是处于active状态,图19-7演示了PROPAGATION_REQUIRES_NEW和PROPAGATION_NESTED中涉及的多个事务相互之间的地位。
第19章 Spring事务王国的架构(二)_第4张图片

也就是说,虽然PROPAGATION_REQUIRES_NEW新创建的事务是在当前外层事务内执行,但新创建的事务是独立于当前外层事务而存在的,二者拥有各自独立的状态而互不干扰。而PROPAGATION_NESTED创建的事务属于当前外层事务的内部子事务(Sub-transaction),内部子事务的处理内容属于当前外层事务的一部分,不能独立于外层事务而存在,并且与外层事务共有事务状态。我想这也就是为什么称其为内部嵌套事务的原因。

PROPAGATION_NESTED可能的应用场景在于,你可以将一个大的事务划分为多个小的事务来处理,并且外层事务可以根据各个内部嵌套事务的执行结果,来选择不同的执行流程。 比如,某个业务对象的业务方法A.service(),可能调用其他业务方法B.service()向数据库中插入一批业务数据,但当插入数据的业务方法出现错误的时候(比如主键冲突),我们可以在当前事务中捕捉前一个方法抛出的异常,然后选择另一个更新数据的业务方法c.service()来执行。这时,我们就可以把B.service()和C.serivce()方法的传播行为指定为PROPAGATION_NESTED。如果用伪代码演示的话,看起来如代码清单19-4所示。

/★★
* PROPAGATIÇN_REQUIRED
*/
A.service() {
try {
	// PROPAGATION_NESTED
	B.service();
}
catch(Exception e) {
// PROPAGATION_NESTED
	c.service();
}

不过,并非所有的PlatformTransactionManager实现都支持PROPAGATION_NESTED类型的传播行为。现在只有org.springframework.jdbc.datasource.DataSourceTransactionManager在使用JDBC3.0数据库驱动的情况下才支持(当然,数据库和相应的驱动程序也需要提供支持)。另外,某些JtaTransactionManager也可能提供支持,但是JTA规范并没有要求提供对嵌套事务的支持。

对TransactionDefinition所提供的这几个传播行为选项的使用,最好是建立在充分理解的基础上。当然,除非特殊的场景,通常情况下,PROPAGATION_REQUIRED将是我们最常用的选择。

TransactionDefinition提供了TIMEOUT_DEFAULT常量定义,用来指定事务的超时时间。TIMEOUT_DEFAULT默认值为-1,这会采用当前事务系统默认的超时时间。我们可以通过TransactionDefinition的具体实现类提供自定义的事务超时时间。

TransactionDefinition提供的最后一个重要信息就是将要创建的是否是一个只读(ReadOnly)的事务。如果需要创建一个只读的事务的话,可以通过TransactionDefinition的相关实现类进行设置。只读的事务仅仅是给相应的ResourceManager提供一种优化的提示,但最终是否提供优化,则由具体的ResourceManager来决定。对于一些查询来说,我们通常会希望它们采用只读事务。

2.TransactionDefinition相关实现

TransactionDefinition只是一个接口定义,要为PlatformTransactionManager创建事务提供信息,需要有相应的实现类提供支持。虽然TransactionDefinition的相关实现类不多,但为了便于理解,我们依然将它们划分为“两派”,如图19-8所示。

第19章 Spring事务王国的架构(二)_第5张图片

我们将TransactionDefinition的相关实现类按照编程式事务场景声明式事务场景划分为两个分支。这只是出于每个类在相应场景中出现的频率这一因素考虑的,而不是说声明式事务场景的实现类不能在编程式事务场景中使用。org.springframework.transaction.support.DefaultTransactionDefinition是TransactionDefinition接口的默认实现类,它提供了各事务属性的默认值,并且通过它的setter方法,我们可以更改这些默认设置。这些默认值包括:

  • propagationBehavior=PROPAGATION_REQUIRED

  • isolationLevel=ISOLATION_DEFAULT

  • timeout=TIMEOUT_DEFAULT

  • readOnly=false

org.springframework.transaction.support.TransactionTemplate是Spring提供的进行编程式事务管理的模板方法类(我们将稍后提到该类的使用),它直接继承了DefaultTransactionDefinition。所以,我们在使用TransactionTemplate的时候就可以直接通过TransactionTemplate本身提供事务控制属性。

org.springframework.transaction.interceptor.TransactionAttribute是继承自TransactionDefinition的接口定义,主要面向使用SpringAOP进行声明式事务管理的场合。 它在TransactionDefinition定义的基础上添加了一个rollbackOn方法,如下所示:

boolean rollbackOn(Throwable ex);

这样,我们可以通过声明的方式指定业务方法在抛出哪些的异常的情况下可以回滚事务。 TransactionAttribute的默认实现类是DefaultTransactionAttribute,它同时继承了DefaultTransactionDefinition。在DefaultTransactionDefinition的基础上增加了rollbackOn的实现,DefaultTransactionAttribute的实现指定了,当异常类型为unchecked exception的情况下将回滚事务。

DefaultTransactionAttribute下有两个实现类,即RuleBasedTransactionAttributeDelegatingTransactionAttribute。RuleBasedTransactionAttribute允许我们同时指定多个回滚规则,这些规则以包含org.springframework.transaction.interceptor.RollbackRuleAttribute或者org.springframework.transaction.interceptor.NoRollbackRuleAttribute的List形式提供。RuleBasedTransactionAttribute的rollbackon将使用传入的异常类型与这些回滚规则进行匹配,然后再决定是否要回滚事务。

DelegatingTransactionAttribute是抽象类,它存在的目的就是被子类化, DelegatingTransactionAttribute会将所有方法调用委派给另一个具体的TransactionAttribute实现类,比如DefaultTransactionAttribute或者RuleBasedTransactionAttribute。不过,除非不是简单的直接委派(什么附加逻辑都不添加),否则,实现一个DelegatingTransactionAttribute是没有任何意义的。

你可能感兴趣的:(Spring揭秘学习笔记,spring,架构,java)