Spring 说明文档 Chapter 7. 事务管理

Chapter 7. 事务管理

7.1. Spring事务抽象

Spring提供了一致的事务管理抽象。这个抽象是Spring最重要的抽象之一, 提供如下的优点:

  • 提供跨越不同的事务API,如JTA、JDBC、Hibernate、iBATIS数据库层 和JDO的一致的编程模型

  • 提供比大多数事务API更简单的,易用的编程式事务管理API

  • 整合Spring数据访问抽象。

  • 支持Spring声明式事务管理

传统上,J2EE开发者有两个事务管理的选择: 全局局部事务。全局事务由应用服务器管理,使用JTA。局部 事务是资源相关的:例如,一个和JDBC连接关联的事务。这个选择有深刻的含义。 全局事务提供和多个参与事务的资源(需要指出的是多数应用使用单一参与事务 的资源)。使用局部事务,应用服务器不需要参与事务管理,并且不能帮助确保 跨越多个资源的正确性。

全局事务有一个显著的不利方面,代码需要使用JTA:一个笨重的API(部分是 因为它的异常模型)。此外,JTA UserTransaction通常需 要从JNDI获得,意味着我需要同时使用JNDI和JTA来使用 JTA。显然全部使用全局事务限制了应用代码的重用性,因为JTA通常只在应用服 务器的环境中才能使用。

首选的使用全局事务的方式是通过EJB的CMT (容器管理的事务): 一种形式的 声明式事务管理(区别于编程式事务管理 )。EJB的CMT消除了事务相关的JNDI查找的需求,虽然使用EJB本身 肯定需要使用JNDI。它消除大多数――不是全部――书写Java代码控制事务的需求。 显著的不利方面是CMT和JTA以及应用服务器环境捆绑在一起,并且只有我们选择 使用EJB实现业务逻辑时才能使用,或者至少使用在一个事务化EJB的外观 (Fa?ade)后。EJB有如此多的诟病,当存在可供选择的声明式事务管理时, EJB不是一个吸引人的建议。

局部事务容易使用,但也有明显的不利方面:它们不能跨越多个参与事务的资 源使用,并且趋向侵入的编程模型。例如,使用JDBC连接事务管理的代码不能使 在全局的JTA事务中。

Spring解决了这些问题。它使应用开发者能够使用在任何环境 下使用一致的编程模型。你可以只写一次你的代码,这些代码可以从在不同环境 下的不同事务管理策略中获益。Spring同时提供声明式和编程式事务管理。

使用编程式事务管理,开发者使用Spring事务抽象,这个抽象可以使用的任何 底层事务基础之上。使用首选的声明式模型,开发者通常书写很少的事务相关代 码,因此不依赖Spring或任何其他事务API。

7.2. 事务策略

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的哲学, 这是一个接口。因而如果需要它可以很容易地被模拟和 桩化。它也没有和一个查找策略如JNDI捆绑在一起:PlatformTransactionManager 的实现定义和其他Spring IoC容器中的对象一样。这个好处使得即使使用JTA这也 是一个很有价值的抽象:事务代码可以比直接使用JTA更加容易测试。

继续Spring哲学,TransactionException是不被检查的。 低层的事务失败几乎都是一成不变地致命。很少情况下应用程序代码可以从它们 中恢复,不过应用开发者依然可以捕获并处理 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数据源,然后使用DataSourceTransactionManager,提供给 它的一个数据源引用。

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

PlatformTransactionManager定义如下:

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

如果我们使用JTA,如同范例中dataAccessContext-jta.xml, 我们需要使用容器数据源,通过JNDI获得,还需要一个JtaTransactionManager实 现,JtaTransactionManager不需要知道数据源,或任何其他指定资源,因为它将 使用容器的全局事务管理。

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName"><value>jdbc/jpetstore</value></property>
</bean>

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

我们可以很容易地使用Hibernate局部事务,如同下面Spring PetClinic 范例中的例子展现的一样。

在这种情况下,我们需要定义一个Hibernate的LocalSessionFactory,应用程 序将使用它获得Hibernate Sessions。

数据源bean定义和上面例子类似,这里不在罗列(如果这是一个不需事务的容 器数据源,Spring而不是容器将管理事务)。

这种情况“transactionManager” bean的类型是HibernateTransactionManager。 和DataSourceTransactionManager拥有一个数据源的引用一样, HibernateTransactionManager需要一个 SessionFactory的引用。

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

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

使用Hibernate和JTA事务我们可以简单地使用JtaTransactionManager, 如同JDBC或任何其他资源策略。

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

注意这是和任何其他使用JTA配置的其他资源一样的,因为它们都是全局事务。

在所有这些情况下,应用程序代码不需要任何更改。我们可以仅仅更改配置 来更改事务方式,甚至这些更改意味这从局部事务转换到全局事务或者相反方向 的转换。

如果不使用全局事务,你需要采用一个特定的代码规范。幸运的这是非常简单 的。你需要以一个特定的方式获得连接或者session资源,允许相关的 PlatformTransactionManager实现跟踪连接的使用,并且当需要时应用事务管理。

例如,如果使用JDBC,你不应该调用一个数据源的 getConnection()方法,而必须使用Spring的 org.springframework.jdbc.datasource.DataSourceUtils类,如下:

Connection conn = DataSourceUtils.getConnection(dataSource);

这将提供额外的好处,任何SQLException都被Spring的 CannotGetJdbcConnectionException――Spring的不被检查 的DataAccessException的类层次之一包装起来。这给你比 简单地从SQLException获得更多的信息,并且确保跨数据 库,甚至跨越不同持久化技术的可移植性。

这将很好地在没有Spring事务管理的情况下工作,因此无论使用还是不使用 Spring事务管理,你都可以使用它。

当然,一旦你使用Spring JDBC支持或Hibernate支持,你将不必使用 DataSourceUtils或其他帮助类,因为你将比直接使用相关 API更加愉快地使用Spring的抽象。例如,如果你使用Spring JdbcTemplate或 jdbc.object包来简化使用JDBC,正确的数据库连接将自动取得,你不需要书写 任何特定代码。

7.3. 编程式事务管理

Spring提供两种方式的编程式事务管理

  • 使用TransactionTemplate

  • 直接使用PlatformTransactionManager一个实现

我们通常推荐使用第一种方式。

第二种方式类似使用JTA UserTransaction API (虽然异常处理少一点麻烦)。

7.3.1. 使用TransactionTemplate

TransactionTemplate采用和其他Spring模板 如JdbcTemplate和 HibernateTemplate一样的方法。它使用回调方法,把应用 程序代码从处理取得和释放资源中解脱出来(不再有try/catch/finally)。如同 其他模板,TransactionTemplate是线程安全的。

必须在事务上下文中执行的应用代码看起来像这样,注意使用 TransactionCallback可以返回一个值:

Object result = tt.execute(new TransactionCallback() {
    public Object doInTransaction(TransactionStatus status) {
        updateOperation1();
        return resultOfUpdateOperation2();
    }
});

如果没有返回值,使用TransactionCallbackWithoutResult, 如下:

tt.execute(new TransactionCallbackWithoutResult() {
    protected void doInTransactionWithoutResult(TransactionStatus status) {
        updateOperation1();
        updateOperation2();
    }
});

回调中的代码可以调用TransactionStatus的对象 setRollbackOnly()方法来回滚事务。

想要使用TransactionTemplate的应用程序类必须访问一 个PlatformTransactionManager:通常通过一个javabean属 性或构造函数参数暴露出来。

使用模拟或桩化的PlatformTransactionManager单元测试 这些类很简单。没有JNDI查找和静态魔术:它只是一个简单的接口。和平常一样, 你可以使用Spring简化单元测试。

7.3.2. 使用PlatformTransactionManager

你也可以使用 org.springframework.transaction.PlatformTransactionManager 直接管理事务。简单地通过一个bean引用传递一个你使用的 PlatformTransactionManager实现到你的bean,然后, 使用TransactionDefinition和 TransactionStatus对象就可以发起事务,回滚和提交。

DefaultTransactionDefinition def = new DefaultTransactionDefinition()
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = transactionManager.getTransactionDefinition(def);

try {
    // execute your business logic here
} catch (MyException ex) {
    transactionManager.rollback(status);
    throw ex;
}
transactionManager.commit(status);

7.4. 声明式事务管理

Spring也提供了声明式事务管理。这是通过Spring AOP实现的。

大多数Spring用户选择声明式事务管理。这是最少的影响应用代码的选择, 因而这是和非入侵的轻量级容器的观念是一致的。

从考虑EJB CMT和Spring声明式事务管理的相似以及不同之处出发是很有益的。 基本方法是一致的:都可以指定事务管理到单独的方法;如果需要可以在事务上 下文调用setRollbackOnly()方法。不同之处:

  • 不同于EJB CMT绑定在JTA上,Spring声明式事务管理可以在任何环境下使用。 只需更改配置文件,它就可以和JDBC、JDO、Hibernate或其他的事务机制一起工作

  • Spring可以使声明式事务管理应用到POJO,不仅仅是特定的类,如EJB

  • Spring提供声明式回滚规则:EJB没有对应的特性, 我们将在下面讨论这个特性。回滚可以声明式控制,不仅仅是编程式的

  • Spring通过AOP提供定制事务行为的机会。例如,如果需要,你可以在事务 回滚中插入定制的行为。你也可以增加事务通知一样增加任意的通知。使用 EJB CMT,除了使用setRollbackOnly()没有其他的办法能 够影响容器的事务管理

  • Spring不提供高端应用服务器提供的跨越远程调用的事务上下文传播。如 果你需要这些特性,我们推荐你使用EJB。然而,不要过度使用这些特性。通常我 们并不希望事务跨越远程调用

回滚规则的概念是很重要的:它们使得我们可以指定那些异常应该发起一个自 动回滚。我们在配置文件中而不是Java代码中指定这些声明。因此,虽然我们仍 然可以编程式调用TransactionStatus对象的 setRollbackOnly()方法来回滚当前事务,多数时候我们可以 指定规则如MyApplicationException应该用于导致一个回滚。 这有显著的优点,业务对象不需要依赖事务基础设施。例如,他们通常不需要引 入任何Spring API,事务或其他任何东西。

EJB的默认行为是遇到系统异常(通常是运行时异常) EJB容器自动回滚事务。EJB CMT遇到应用程序异常 (除了java.rmi.RemoteException外被检查的异常)时不 会自动回滚事务。虽然Spring声明式事务管理沿用EJB CMT约定(遇到不被检查的 异常自动回滚事务),但是这是可以定制的。

按照我们的基准,Spring声明式事务管理的性能要胜过EJB CMT。

通常设置Spring事务代理的方法是通过TransactionProxyFactoryBean。我们需 要一个包装在事务代理中目标对象。这个目标对象一般是一个POJO的bean。当我 们定义TransactionProxyFactoryBean时,必须提供一个相关的 PlatformTransactionManager的引用和事务属性事务属性含有上面描述的事务定义。

<bean id="petStore" 
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager"><ref bean="transactionManager"/></property>
    <property name="target"><ref bean="petStoreTarget"/></property>
    <property name="transactionAttributes">
        <props>
            <prop key="insert*">PROPAGATION_REQUIRED,-MyCheckedException</prop>
            <prop key="update*">PROPAGATION_REQUIRED</prop>
            <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
        </props>
    </property>
</bean>

事务代理会实现目标对象的接口:这里是id为petStoreTarget.的bean(使用 CGLIB可以实现具体类的代理,只要设置proxyTargetClass属性为true就可以。这 将自动设置,如果目标对象没有实现任何接口。通常,我们希望面向接口而不是 类编程)。可以(一般来说是好的想法)限定事务代理使用proxyInterfaces来代 理指定的接口。也可以通过继承至 org.springframework.aop.framework.ProxyConfig几个属 性来定制TransactionProxyFactoryBean的行为,并在所有的AOP代理工厂中共享。

这里的transactionAttributes通过定在 org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource 类中的属性格式来设置。这个包括通配符的方法名称映射是很直观的。注意 insert*的映射的值包括回滚规则。添加-MyCheckedException 指定如果方法抛出MyCheckedException或它的子类,事务将 会自动回滚。可以指定个回滚规则,用逗号分隔。-前缀强制回滚,+前缀指定提 交(这允许即使抛出未被检查的异常时也可以提交事务,当然你自己要明白自己 在做什么)。

TransactionProxyFactoryBean允许你通过 “preInterceptors”和“postInterceptors”属性设置“前”或“后”通知来提供额外的 拦截行为。可以设置任意数量的“前”和“后”通知,它们的类型可以是 Advisor(可以包含一个pointcut), MethodInterceptor或被当前Spring配置支持的通知类型 (例如ThrowAdvice, AfterReturningtAdvice或BeforeAdvice, 这些都是默认支持的)。这些通知必须支持实例共享模式。如果你需要高级AOP特 性来使用事务,如有状态的混入,那最好使用通用的 org.springframework.aop.framework.ProxyFactoryBean, 而不是TransactionProxyFactoryBean代理创建者。

也可以设置自动代理:配置AOP框架,不需要单独的代理定义类就可以生成类的 代理。

更多信息和实例请参阅AOP章节。

你不需要成为AOP专家,当然,了解更多的AOP知识,可以更有效地使用Spring 声明式事务管理。但是,如果你想成为Spring AOP的高级用户,你会发现整合声明 式事务管理和强大的AOP性能是非常容易的。

7.4.1. BeanNameAutoProxyCreator,另一种声明方式

TransactionProxyFactoryBean非常有用,并且当事 务代理包装对象时,你有全部的支配权。如果你需要用一致的方式(例如,一个 样板文件,“使所有的方法事务化”)包装大量的bean,使用一个 BeanFactoryPostProcessor调用 BeanNameAutoProxyCreator可以提供另外一种方法, 一个在这种简单的情况下更加简化的方法。

重述一下,一旦ApplicationContext读完它的初始化信息,它将初始化所有实 现BeanPostProcessor的bean,并且给所有其他 ApplicationContext中beans一个后期处理的机会。所以使用这种机制,一个正 确配置的BeanNameAutoProxyCreator可以用来后期处 理所有ApplicationContext中所有其他的bean(通过名称来识别),并且把他 们用事务代理包装起来。真正事务代理的生成和使用 TransactionProxyFactoryBean基本一致,这里不再 讨论。

让我们看下面的配置示例:

  <!-- Transaction Interceptor set up to do PROPOGATION_REQUIRED on all methods -->
  <bean id="matchAllWithPropReq" 
      class="org.springframework.transaction.interceptor.MatchAlwaysTransactionAttributeSource">
    <property name="transactionAttribute"><value>PROPAGATION_REQUIRED</value></property>
  </bean>
  <bean id="matchAllTxInterceptor" 
      class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager"><ref bean="transactionManager"/></property>
    <property name="transactionAttributeSource"><ref bean="matchAllWithPropReq"/></property>
  </bean>

  <!-- One BeanNameAutoProxyCreator handles all beans where we want all methods to use 
       PROPOGATION_REQUIRED -->
  <bean id="autoProxyCreator" 
      class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="interceptorNames">
      <list>
        <idref local="matchAllTxInterceptor"/>
        <idref bean="hibInterceptor"/>
      </list>
    </property>
    <property name="beanNames">
      <list>
        <idref local="core-services-applicationControllerSevice"/>
        <idref local="core-services-deviceService"/>
        <idref local="core-services-authenticationService"/>
        <idref local="core-services-packagingMessageHandler"/>
        <idref local="core-services-sendEmail"/>
        <idref local="core-services-userService"/>
      </list>
    </property>
  </bean>

假设我们已经有一个TransactionManager的实例 在ApplicationContext中,我们首先要做的使创建一个 TransactionInterceptor实例。 ransactionInterceptor基于一个实现 TransactionAttributeSource接口的对象决定哪个 方法被拦截, 这个对象作为属性传递进来的。这个例子中,我们希望处理匹配 所有方法这种最简单的情况。这个不是最有效的方式,但设置非常迅速,因为 我可以使用预先定义的匹配所有方法的 MatchAlwaysTransactionAttributeSource类。如果我 们需要特定的方式,可以使用 MethodMapTransactionAttributeSource, NameMatchTransactionAttributeSource或 AttributesTransactionAttributeSource。

现在我们已经有了事务拦截器,我们只需把它和另外6个我们希望以一致方式 包装的AppliactonContext中的bean一起放到我们定义的 BeanNameAutoProxyCreator实例中。你可以看到,这 个结果比用TransactionProxyFactoryBean以一种方式单独包装6个bean简捷很 多。包装第7个bean只需添加一行配置。

你也许注意到可以应用多个拦截器。在这个例子中,我们也可以应用一个预 先定义的HibernateInterceptor (bean id=hibInterceptor),这个为我们管理 Hibernare的Sessions。

有一件事值得注意,就是在TransactionProxyFactoryBean, 和BeanNameAutoProxyCreator切换时bean的名称。 第一种情况,你只需给你想要包装的bean一个类似 myServiceTarget的id, 给代理对象一个类似myService的id, 然后所有代理对象的用户只需引用代理对象, 如myService(这些是通用命名规范, 要点是目标对象要有和代理对象不同的名称, 并且他们都要在ApplicationContext中可用)。然而, 使用BeanNameAutoProxyCreator时, 你命名目标对象为myService。 然后当BeanNameAutoProxyCreator后期处理目标对象 并生成代理时,它导致代理以和原始对象相同的名称被插入到 ApplicationContext中。从这一点看,只有代理(含有被包装的对象)在Application中可用。

7.5. 编程式还是声明式事务管理

如果你只有很少的事务操作,使用编程式事务管理是很好的主意。例如, 如果你有一个WEB应用需要为某些更新操作提供事务,你可能不想用Spring或其 他技术设置一个事务代理。使用 TransactionTemplate可能是个很好的方法。

另一方面,如果你的应用有大量的事务操作,编程式(译注:应为声明式, 疑是作者笔误)事务管理通常是很有价值的。它使得事务管理从业务逻辑分离, 并且Spring中配置也不困难。使用Spring, 而不是EJB CMT,声明式事务管理配置的成本极大地降低。

7.6. 你需要应用服务器管理事务吗?

Spring的事务管理能力――尤其声明式事务管理极大地改变了J2EE应用程序需要 应用服务器的传统想法。

特别地,你不需要应用服务器仅仅为了通过EJB声明事务。事实上,即使你拥 有强大JTA支持的应用服务器,你也可以决定使用Spring声明式事务管理提供比 EJB CMT更强大更高效的编程模型。

只有需要支持多个事务资源时你才需要应用服务器的JTA支持。许多应用没有 这个需求。例如许多高端应用使用单一的高度可升级的数据库,如Oracle 9i RAC。

当然也许你需要其他应用服务器的功能,如JMS和JCA。但是如果你只需使用 JTA,你可以考虑开源的JTA实现,如JOTM(Spring整合了JOTM)。但是, 2004年早期,高端的应用服务器提供更健壮的XA资源支持。

最重要一点,使用Spring,你可以选择何时将你的应用迁移到完整 应用服务器。除了使用EJB CMT和JTA只能书写代码使用局部事务, 例如JDBC连接的事务,而且如果曾经需要全局的容器管理的事务,还面临着繁重 的改写过程,这些日子一去不复返了。使用Spring只有配置需要改变,你的代码 不需要。

7.7. 公共问题

开发着需要按照需求仔细的使用正确的 PlatformTransactionManager实现。

理解Spring事务抽象时如何和JTA全局事务一起工作是非常重要的。使用得当, 没有任何冲突:Spring仅仅提供一个简单的,可以移植的抽象层。

如果你使用全局事务,你必须为你的所有事务操作使用Spring的 org.springframework.transaction.jta.JtaTransactionManager。 否则Spring将试图在资源如容器数据源执行局部事务。这样的局部事务没有任何 意义,好的应用服务器会把这作为一个错误。

你可能感兴趣的:(spring,bean,应用服务器,Hibernate,ejb,文档)