9.3. 关键抽象

Spring Framework

9.3. 关键抽象

Spring事务抽象的关键是事务策略的概念。这个概念由org.springframework.transaction.PlatformTransactionManager接口定义如下:

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition)
        throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

这首先是一个SPI接口,虽然它也可以在 编程 中使用。注意按照Spring框架的哲学,PlatformTransactionManager 是一个 接口。因而如果需要它可以很容易地被模拟和桩化。它也没有和一个查找策略如JNDI捆绑在一起:PlatformTransactionManager 的实现定义和其他Spring IoC容器中的对象一样。这个好处使得即使使用JTA,也是一个很有价值的抽象:事务代码可以比直接使用JTA更加容易测试。

继续Spring哲学,可由任何 PlatformTransactionManager 的接口方法抛出的 TransactionException 是unchecked exception(继承自java.lang.RuntimeException)的。底层的事务失败几乎总是致命的。很少情况下应用程序代码可以从它们中恢复,不过应用开发者依然可以捕获并处理TransactionException,他们可以自由决定怎么干。

getTransaction(..)方法根据一个类型为 TransactionDefinition 的参数返回一个 TransactionStatus 对象。返回的 TransactionStatus 对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务。如同J2EE事务环境,一个 TransactionStatus 也是和执行 线程 绑定的)。

TransactionDefinition接口指定:

  • 事务隔离:当前事务和其它事务的隔离的程度。例如,这个事务能否看到其他事务未提交的写数据?

  • 事务传播:通常在一个事务中执行的所有代码都会在这个事务中运行。但是,如果一个事务上下文已经存在,有几个选项可以指定一个事务性方法的执行行为:例如,简单地在现有的事务中继续运行(大多数情况);或者挂起现有事务,创建一个新的事务。Spring提供EJB CMT中常见的事务传播选项

  • 事务超时: 事务在超时前能运行多久(自动被底层的事务基础设施回滚)。

  • 只读状态: 只读事务不修改任何数据。只读事务在某些情况下(例如当使用Hibernate时),是一种非常有用的优化。

这些设置反映了标准概念。如果需要,请查阅讨论事务隔离层次和其他核心事务概念的资源:理解这些概念在使用Spring框架和其他事务管理解决方案时是非常关键的。

TransactionStatus 接口为处理事务的代码提供一个简单的控制事务执行和查询事务状态的方法。这个概念应该是熟悉的,因为它们在所有的事务API中是相同的:

public interface TransactionStatus {

    boolean isNewTransaction();

    void setRollbackOnly();

    boolean isRollbackOnly();
}

使用Spring时,无论你选择编程式还是声明式的事务管理,定义一个正确的 PlatformTransactionManager 实现都是至关重要的。按照Spring的良好风格,这种重要定义都是通过IoC实现的。

一般来说,选择PlatformTransactionManager实现时需要知道当前的工作环境,如JDBC、JTA、Hibernate等。下面的例子来自Spring示例应用——jPetStore——中的dataAccessContext-local.xml文件,其中展示了一个局部PlatformTransactionManager实现是怎么定义的(仅限于纯粹JDBC环境)

我们必须先定义一个JDBC DataSource,然后使用Spring的DataSourceTransactionManager,并传入指向DataSource的引用。

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  <property name="driverClassName" value="${jdbc.driverClassName}" />
  <property name="url" value="${jdbc.url}" />
  <property name="username" value="${jdbc.username}" />
  <property name="password" value="${jdbc.password}" />
</bean>

PlatformTransactionManager bean的定义如下:

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>

如果我们在J2EE容器里使用JTA,就像示例中 'dataAccessContext-jta.xml' 文件所示,我们将通过JNDI和Spring的 JtaTransactionManager 来获取一个容器管理的 DataSourceJtaTransactionManager 不需要知道 DataSource 和其他特定的资源,因为它将使用容器提供的全局事务管理。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">

  <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>

  <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />

  <!-- other <bean/> definitions here -->

</beans>

注意

上面 'dataSource' 的bean定义使用了 'jee' 名称空间下的 <jndi-lookup/> 标签。想了解更多的配置信息, 请看 附录 A, XML Schema-based configuration,关于 <jee/> 标签的信息,可参考 第 A.2.3 节 “The jee schema” 节。

我们也可以很容易地使用Hibernate局部事务,就像下面的Spring框架的 PetClinic 示例应用中的例子一样)。这种情况下,我们需要定义一个Hibernate的 LocalSessionFactoryBean,应用程序从中获取到Hibernate Session 实例。

DataSource 的bean定义同上例类似(这里不再展示)。不过,如果是一个JEE容器提供的 DataSource,它将由JEE容器自身,而不是Spring框架来管理事务。

这种情况中'txManager' bean的类型为 HibernateTransactionManager。同样地,DataSourceTransactionManager 需要一个指向 DataSource 的引用,而 HibernateTransactionManager 需要一个指向 SessionFactory 的引用。

<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="mappingResources">
    <list>
      <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
    </list>
  </property>
  <property name="hibernateProperties">
    <value>
	  hibernate.dialect=${hibernate.dialect}
	</value>
  </property>
</bean>

<bean id="txManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
  <property name="sessionFactory" ref="sessionFactory" />
</bean>

我们可以简单地使用 JtaTransactionManager 来处理Hibernate事务和JTA事务,就像我们处理JDBC,或者任何其它的资源策略一样。

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

注意任何资源的JTA配置都是这样的,因为它们都是全局事务,可以支持任何事务性资源。

在所有这些情况下,应用程序代码根本不需要做任何改动。我们仅仅通过改变配置就可以改变事务管理方式,即使这些更改是在局部事务和全局事务间切换。