Spring - Java/J2EE Application Framework 应用框架 第 7 章 事务管理

岑经纶
2023-12-01

第 7 章 事务管理

7.1. Spring事务抽象

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

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

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

  • 整合Spring数据访问抽象

  • 支持Spring声明式事务管理

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

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

使用全局事务的比较好的方法是通过EJB的CMT (容器管理的事务): 声明式事务管理的一种形式(区别于编程式事务管理 )。EJB的CMT不需要任何和事务相关的JNDI查找,虽然使用EJB本身 肯定需要使用JNDI。它消除大多数——不是全部——书写Java代码控制事务的需求。 显著的缺点是CMT绑定在JTA和应用服务器环境上,并且只有我们选择 使用EJB实现业务逻辑,或者至少处于一个事务化EJB的外观(Facade)后 才能使用它。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是unchecked的。 低层的事务失败几乎都是致命。很少情况下应用程序代码可以从它们 中恢复,不过应用开发者依然可以捕获并处理 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的会话。

数据源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配置都是这样的,因为它们都是全局事务。

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

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

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

Connection conn = DataSourceUtils.getConnection(dataSource);

这将提供额外的好处,任何SQLException都被Spring的 CannotGetJdbcConnectionException封装起来,它属于Spring的unchecked 的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引用给你的bean传递一个你使用的 PlatformTransactionManager实现。然后, 使用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可以使声明式事务管理应用到普通Java对象,不仅仅是特殊的类,如EJB

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

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

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

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

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

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

通常通过TransactionProxyFactoryBean设置Spring事务代理。我们需 要一个目标对象包装在事务代理中。这个目标对象一般是一个普通Java对象的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就可以。 如果目标对象没有实现任何接口,这将自动设置该属性为true。通常,我们希望面向接口而不是 类编程。)使用proxyInterfaces属性来限定事务代理来代 理指定接口也是可以的(一般来说是个好想法)。也可以通过从 org.springframework.aop.framework.ProxyConfig继承或所有AOP代理工厂共享 的属性来定制TransactionProxyFactoryBean的行为。

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

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

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

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

无论你是是否是AOP专家,都可以更有效地使用Spring的 声明式事务管理。但是,如果你想成为Spring AOP的高级用户,你会发现整合声明 式事务管理和强大的AOP性能是非常容易的。

7.4.1. BeanNameAutoProxyCreator,另一种声明方式

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

重述一下,一旦ApplicationContext读完它的初始化信息,它将初始化所有实 现BeanPostProcessor接口的bean,并且让它们后处理 ApplicationContext中所有其他的bean。所以使用这种机制,一个正 确配置的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>

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

现在我们已经有了事务拦截器,我们只需把它交给我们定义的 BeanNameAutoProxyCreator实例中,这样AppliactonContext中 定义的6个bean以同样的方式被封装。你可以看到,这 比用TransactionProxyFactoryBean以一种方式单独封装6个bean简洁很 多。封装第7个bean只需添加一行配置。

你也许注意到可以应用多个拦截器。在这个例子中,我们还应用了一个 前面定义的HibernateInterceptor (bean id=hibInterceptor),它为我们管理 Hibernare的会话。

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

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将试图在象容器数据源这样的资源上执行局部事务。这样的局部事务没有任何 意义,好的应用服务器会把这作为一个错误。


from: http://docs.huihoo.com/spring/zh-cn/transaction.html

 类似资料: