16.2.1 探索TransactionDefinition接口
正如之前所说的,TransactionDefinition接口控制着事务的属性。下面让我们进一步看看该接口及其方法,如代码清单16-1所示:
代码清单16-1 TransactionDefinition接口
接口中简单而明显的方法是getTimeout()方法,它返回一个事务必须完成的时间限制(以秒为单位),还有isReadOnly()方法,它表示事务是否只读。事务管理器的实现可以利用这个值来优化事务的执行,并确保事务只进行读取操作。
另外两个方法--getPropagationBehavior()与getIsolationLevel()需要深入讨论。我们先介绍getIsolationLevel(),它对其他事务所能看到的数据变化进行控制。表16-1列出了你可以使用的事务隔离级别,并说明了其他事务可以访问的当前事务变化。
表16-1 事务隔离级别
隔离级别 |
说明 |
TransactionDefinition. ISOLATION_DEFAULT |
PlatformTransactionManager 的默认隔离级别(对大多数数据库 来说就是ISOLATION_ READ_COMMITTED) |
TransactionDefinition. ISOLATION_READ_UNCOMMITTED |
最低的隔离级别。事实上我们不应该 称其为隔离级别,因为在事务完成前, 其他事务可以看到该事务所修改的数据。 而在其他事务提交前,该事务也可以看到 其他事务所做的修改 |
TransactionDefinition. ISOLATION_READ_COMMITTED |
大多数数据库的默认级别。在事务完成前, 其他事务无法看到该事务所修改的数据。 遗憾的是,在该事务提交后,你就可以查 看其他事务插入或更新的数据。这意味 着在事务的不同点上,如果其他事务修改 了数据,你就会看到不同的数据 |
TransactionDefinition. ISOLATION_REPEATABLE_READ |
比ISOLATION_READ_COMMITTED更严格, 该隔离级别确保如果在事务中查询了某个 数据集,你至少还能再次查询到相同的数 据集,即使其他事务修改了所查询的数据。 然而如果其他事务插入了新数据, 你就可以查询到该新插入的数据 |
TransactionDefinition. ISOLATION_SERIALIZABLE |
代价最大、可靠性最高的隔离级别, 所有的事务都是按顺序一个接一个地执行 |
选择合适的隔离级别对于保证数据的一致性非常重要,并且所作出的选择会对性能产生重大影响。最高的隔离级别TransactionDefinition.ISOLATION_SERIALIZABLE开销最大。
getPropagationBehavior()方法指定了当代码请求一个新的事务时Spring所做的事情。表16-2列出了这个方法的常量值。
表16-2 传播行为值
传播行为 |
说明 |
TransactionDefinition. PROPAGATION_REQUIRED |
当前如果有事务,Spring就会使用该事务; 否则会开始一个新事务 |
TransactionDefinition. PROPAGATION_SUPPORTS |
当前如果有事务,Spring就会使用该事务; 否则不会开始一个新事务 |
TransactionDefinition. PROPAGATION_MANDATORY |
当前如果有事务,Spring就会使用该事务; 否则会抛出异常 |
TransactionDefinition. PROPAGATION_REQUIRES_NEW |
Spring总是开始一个新事务。如果当 前有事务,则该事务挂起 |
TransactionDefinition. PROPAGATION_NOT_SUPPORTED |
Spring不会执行事务中的代码。代码总 是在非事务环境下执行,如果当前 有事务,则该事务挂起 |
TransactionDefinition. PROPAGATION_NEVER |
即使当前有事务,Spring也会在非 事务环境下执行。如果当前有事务, 则抛出异常 |
TransactionDefinition. PROPAGATION_NESTED |
如果当前有事务,则在嵌套 事务中执行。如果没有,那么 执行情况 与 Transaction- Definition. PROPAGATION_REQUIRED一样 |
16.2.2 使用TransactionStatus接口
如代码清单16-2所示,TransactionStatus接口可以让事务管理器控制事务的执行,可以检查事务是不是一个新事务,或者是否只读。TransactionStatus还可以初始化回滚操作。
代码清单16-2 TransactionStatus 声明
TransactionStatus接口中的方法很容易理解;最引人注目的就是setRollbackOnly(),它将一个事务标识为不可提交的。换句话说,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。在大多数情况下,事务管理器会检测到这一点,在它发现事务要提交时会立刻结束事务。代码清单16-3中的伪代码说明了这一点。
代码清单16-3 setRollbackOnly()方法的使用
在调用完setRollbackOnly()后,大多数数据库可以继续执行下一条select语句,但不允许执行update语句--执行update是没有意义的,因为事务只可以进行读取操作,任何修改都不会被提交。
16.2.3 PlatformTransactionManager的实现
PlatformTransactionManager接口使用TransactionDefinition和TransactionStatus接口,创建并管理事务。该接口的实现必须对事务管理器有深入理解。DataSourceTransactionManager控制着从DataSource中获得的JDBC Connection上的事务的执行;HibernateTransactionManager控制着Hibernate session上的事务的执行;JdoTransactionManager管理着JDO事务;JtaTransactionManager将事务管理委托给JTA。
16.3 对一个事务管理示例的探索(1)
本章的第一部分已经对Spring中的事务基础架构进行了概览,但我们认为,仍然需要给出一个能说明如何使用Spring的事务支持的示例。
使用事务操作的方式有3种基本方式:可以使用声明式事务,只需声明某个方法需要事务就行了;可以使用源码级的元素据来说明某个方法需要一个事务;还可以用编写事务代码的方式来实现。Spring对这3种方式都提供了支持,我们从最灵活最方便的方式开始,即声明式事务管理。
首先我们将展示几种事务支持的实现方式。浏览一下图16-1中的UML类图,对该示例应用中需要保存的数据有一个直观印象。
尽管该类图看起来有些复杂,但我们只是实现一个简单的银行应用程序。我们有一个用来维护账户余额的Account类(这是真实银行账户的简化版,但对于示例来说足够用了)。我们将AccountIdentity作为银行账户的唯一标识。BalanceMutator接口只有一个mutate(BigDecimal)方法,我们在BankService- Support中实现该接口。CreditBalanceMutator将贷款数额加到了balance参数中。DebitBalanceMutator稍微有点复杂,它检查账户中是否有足够的钱可以借出。若想进一步探究这个概念,例如可以修改DebitBalanceMutator以允许透支。
AccountDao定义了一个数据访问接口,该接口由JdbcAccountDao类实现。接下来,我们在BankService接口中定义了transfer和getBalance方法。BankServiceSupport实现了这两个方法,它用两个以do开头的protected方法进行实现。我们将使用BankServiceSupport子类来演示各种不同的事务管理技术。
(点击查看大图)图16-1 带有事务的银行应用示例的UML类图 |
非事务性代码
在讨论事务管理之前,我们得编写必要的支持代码。让我们以代码清单16-4中的SQL代码开始吧,这些代码创建了t_account表并插入几个测试账户。
代码清单16-4 样例账户应用的SQL 代码
这些SQL代码没什么特别的。如果你没有使用Oracle 10g数据库,那么你可能需要修改一下SQL语法使之适合你的数据库。接下来,我们看看Account与AccountIdentity类及BalanceMutator接口。我们在代码清单16-5中列出了这几个类和接口的代码。
代码清单16-5 Account、AccountIdentity和BalanceMutator
你可以看到Account类有一个我们赋予的识别器(Long id)。该识别器就是实现的细节问题了:我们的代码需要这个ID,但它对用户来说是没有任何意义的。用户使用账户号和分类号来标识其账户。Account类只有一个重要的方法:changeBalance(BalanceMutator)。这个方法使用BalanceMutator实例来更新账户的余额。
说明 我们选择使用BalanceMutator来更新账户的余额,因为我们觉得管理余额的规则可能会非常复杂。使用一个接口(该接口在服务层实现)会比在Account类中简单地定义credit(BigDecimal)和debit(BigDecimal)方法更灵活。
接下来,我们看看该示例应用的数据访问层。简单起见,我们使用SimpleJdbcTemplate(请查看第9章以了解关于Spring JDBC支持的详细内容)。我们采取了Spring应用所使用的一种典型方式:创建AccountDao接口及其实现--JdbcAccountDao。代码清单16-6列出了接口和实现的源代码。
代码清单16-6 AccountDao接口和JdbcAccountDao实现
我们尽量保持代码的简单性。记住我们并不是在展示Spring JDBC,而仅仅是实现数据访问层代码以说明Spring的事务管理。最后,我们实现了BankServiceSupport,将该类作为事务性的BankService实现的父类。这样,BankServiceSupport就用以do开头的protected方法去实现BankService中的方法。代码清单16-7显示了BankServiceSupport的实现。
代码清单16-7 BankServiceSupport代码