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。
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,正确的数据库连接将自动取得,你不需要书写 任何特定代码。
Spring提供两种方式的编程式事务管理
使用TransactionTemplate
直接使用PlatformTransactionManager一个实现
我们通常推荐使用第一种方式。
第二种方式类似使用JTA UserTransaction API (虽然异常处理少一点麻烦)。
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简化单元测试。
你也可以使用 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);
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性能是非常容易的。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中可用。
如果你只有很少的事务操作,使用编程式事务管理是很好的主意。例如, 如果你有一个WEB应用需要为某些更新操作提供事务,你可能不想用Spring或其 他技术设置一个事务代理。使用 TransactionTemplate可能是个很好的方法。
另一方面,如果你的应用有大量的事务操作,编程式(译注:应为声明式, 疑是作者笔误)事务管理通常是很有价值的。它使得事务管理从业务逻辑分离, 并且Spring中配置也不困难。使用Spring, 而不是EJB CMT,声明式事务管理配置的成本极大地降低。
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只有配置需要改变,你的代码 不需要。
开发着需要按照需求仔细的使用正确的 PlatformTransactionManager实现。
理解Spring事务抽象时如何和JTA全局事务一起工作是非常重要的。使用得当, 没有任何冲突:Spring仅仅提供一个简单的,可以移植的抽象层。
如果你使用全局事务,你必须为你的所有事务操作使用Spring的 org.springframework.transaction.jta.JtaTransactionManager。 否则Spring将试图在资源如容器数据源执行局部事务。这样的局部事务没有任何 意义,好的应用服务器会把这作为一个错误。