全局事务

全局事务支持对多个事务性资源的操作,通常是关系型数据库和消息队列。应用服务器通过JTA管理全局性事务,API非常烦琐。UserTransaction通常需要从JNDI获取,意味着需要与JNDI绑定在一起,且JTA一般只在应用服务器可用,降低了应用代码的可重用性。

本地事务

本地事务面向具体的资源,例如与JDBC连接关联的事务。本地事务易于使用,但不能跨多个事务性资源。使用JDBC管理事务的代码不能在全局JTA事务中运行,因此不能确保跨多个资源的正确性。且本地事务侵入了编程模型。

Spring的一致性编程模型

Spring让开发者在任何环境下都可以使用一致性的编程模型,编写一次代码,就可以在不同环境的不同事务管理策略中运行。Spring提供声明式和编程式事务管理,大多数人选择声明式,这也是推荐的方式。

使用编程式事务管理时,开发者使用的是Spring框架的事务抽象,它可以运行在任何底层事务设施之上。使用声明式事务管理,开发者只需要编写少量甚至不需要编写事务相关代码,因此应用代码不依赖Spring框架事务API或其它事务API。

Spring的事务管理支持从传统规则到应用服务器的转换。

通常只有在通过EJB进行声明式事务时才真的需要应用服务器。实际上即使应用服务器有JTA的功能,也可以认为Spring框架的声明式事务比EJB CMT更加强大,更加能提高效率。

只有在应用需要跨多个资源处理事务时才需要应用服务器的JTA功能。许多高级应用使用单个高可扩展的数据库(例如Oracle RAC)来代替。独立的事务管理器也是一种方案,例如Atomikos Transactions、JOTM。应用也可能需要其它的应用服务器功能,例如JMS、JCA。

Spring框架让开发者决定何时把应用扩展为完整的应用服务器,只需要在配置文中修改某些Bean定义即可。

理解Spring框架的事务抽象

事务策略是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),虽然可以在应用中编程式地使用。由于这是一个接口,所以可以很容易被模拟出来。它并不与JNDI这样的查找策略绑定在一起。它可以像其它bean一样在Spring框架IoC容器中被定义。即使在使用JTA的场景下,也可以从Spring框架的事务中受益,因为Spring中的事务代码比直接使用JTA的更加容易测试。

TransactionException是作为非检查异常抛出,它扩展java.lang.RuntimeException类,因为在Spring的角度看来,事务设施故障几乎总是致命的,很少情况下应用可以从事务故障中恢复。开发者可以选择捕获这个异常并作处理,但并不强制这样做。

getTransaction(…)根据TransactionDefinition类型的参数返回一个TransactionStatus对象,所返回的对象可能代表一个新的事务,也可能是一个当前调用栈中已经存在的事务。后一种情况表示,在使用Java EE事务上下文时,一个TransactionStatus是与执行线程关联的。

TransactionDefinition接口

TransactionDefinition接口指定了:

  • 隔离级别(Isolation):一个事务与其它事务的隔离级别,例如一个事务是否可看到其它事务未提交的写操作
  • 传播性(Propagation):一般所有在一个事务中运行的代码都会处于一个事务中,但当执行一个方法并且事务环境已经存在时,可以指定此时的行为。例如所调用方法内的代码继续运行在已经存在的事务中(常见情况),或者暂停当前事务并创建一个新的事务来运行所调用方法内的代码。Spring提供了所有EJB CMT所提供的事务传播选项。
  • 超时(Timeout):在事务超时并被底层事务设施自动回滚前,这个事务可以运行多久。
  • 只读状态(Read-only):当代码不修改数据时,可以使用只读事务。只读事务在某些场景下很有用,例如使用Hibernate时。

以上设置是标准的事务概念,指的就是事务隔离级别及其它核心事务概念。理解这些概念是使用Spring框架事务管理或其它事务解决方案的基础。

TransactionStatus接口

TransactionStatus接口为事务性代码提供控制事务执行和查询事务状态的简便方式。这些概念必须要熟悉,它们是所有事务API都共同的东西:

public interface TransactionStatus extends SavepointManager {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    void flush();
    boolean isCompleted();
}

在Spring中不管理使用声明式事务还是编程式事务,定义正确的PlatformTransactionManager实现都是必须的,一般通过DI进行定义。

PlatformTransactionManager一般只需要知道自己的工作环境:JDBC、JTA、Hibernate等等。下面是定义一个本地PlatformTransactionManager实现的例子:

定义JDBC数据源:

<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引用DataSource定义:

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



如果在Java EE容器中使用容器DataSource,则通过JNDI获取,并与Spring的JtaTransactionManager关联:



<?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.xsd        http://www.springframework.org/schema/jee        http://www.springframework.org/schema/jee/spring-jee.xsd">
    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>
    <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
    <!-- other <bean/> definitions here -->
</beans>

Hibernate事务配置

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.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.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>


 

如果使用Hibernate和Java EE容器管理的JTA事务,则与前面对JDBC的JTA配置一样:

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.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.transaction.jta.JtaTransactionManager"/>



如果使用JTA,则不管使用什么样的数据访问技术(JDBC、Hibernate JPA、或其它支持的技术),事务管理器定义会以相同的方式进行查找,因为JTA事务是全局性的,它会证募所有的事务性资源(JDBC connection、SessionFactory等等)。



 



通过这种配置方式,本地和全局事务的转换不需要修改应用代码,只需要在配置文件上做修改。



 



使用事务同步资源

高级别同步方式



优先使用的是Spring高级别的基于模板的持久化集成API,或使用带有事务感知的工厂Bean的原生ORM API,或者管理原生资源工厂的代理。这些事务感知的方案在内部处理对资源的创建、重用、清理、可选的资源事务同步、异常映射,因此用户代码不需要做这些工作,只需要关注于自身的持久化逻辑。一般对JDBC的访问使用的是JdbcTemplate模板方式,或ORM原生API。



低级别同步方式

JDBC的DataSourceUtils、JPA的EntityManagerFactoryUtils、Hibernate的SessionFactoryUtils、JDO的PersistemceManagerFactoryUtils是低级别的操作工具。如果应用代码需要直接处理原生持久化API的资源类型时,可以使用这些类来确保获取正确的由Spring框架管理的实例、事务被正确同步、异常被正确映射到一致的API。

例如对于传统的JDBC,可以使用下面的代码代替对DataSource的getConnection()方法的调用:

Connection conn = DataSourceUtils.getConnection(dataSource);



上面的代码,如果一个已经存在的事务已经有一个同步connection与其关联,则这个connection会被返回,否则会创建一个新的连接,并且会同步到已经存在的事务,且在后继对同一个事务的使用中可以重用这个连接。上面调用中,底层抛出的SQLException被Spring框架包装成CannotGetJdbcConnectionException,一个非检查DataAccessException,这比单纯的SQLException能够提供更多的信息,并确保跨数据库甚至跨不同持久化持久的可移植性。



 



上面的代码在没有Spring事务管理的情况下也是可用的。



 



在使用Spring的JDBC支持、JPA支持、Hibernate支持时,多数人更愿意使用Spring的事务抽象,而不是直接使用类似DataSourceUtils这样的辅助类。例如使用JdbcTemplate来简化JDBC的使用,它会自动为应用获取正确的连接。



 



TransactionAwareDataSourceProxy

在最低层存在一个TransactionAwareDataSourceProxy类,它是DataSource的代理类,包装了DataSource,以添加Spring事务管理的感知功能,类似于由Java EE服务器提供的事务性JNDI DataSource。

除非现存代码必须通过传递一个标准的JDBC DataSource实现进行调用,否则这个类不应该被使用。