您的位置:首页 > 编程语言 > Java开发

谈 Spring-Transaction(Spring事务管理 第二篇)

2012-08-15 11:40 393 查看

9.4. 使用资源同步的事务

现在应该比较清楚的是:不同的事务管理器是如何创建的,以及它们如何被连接到相应的需要被同步到事务的资源上(例如,DataSourceTransactionManager 对应到JDBC
DataSource, HibernateTransactionManager 对应到Hibernate的
SessionFactory 等)。可是,剩下的问题是,直接或间接地使用一种持久化API(JDBC、Hibernate、JDO等)的应用代码,如何确保通过相关的
PlatformTransactionManager 来恰当地获取并操作资源,来满足事务同步,这些操作包括:创建、复用、清理 和 触发(可能没有)。

9.4.1. 高层次方案

首选的方法是使用Spring的高层持久化集成API。这种方式不会替换原始的API,而是在内部封装了资源创建、复用、清理、事务同步以及异常映射等功能,这样用户的数据访问代码就不必关心这些,而集中精力于自己的持久化逻辑。通常,对所有持久化API都采用这种
模板 方法,包括 JdbcTemplate、HibernateTemplate和JdoTemplate类(这些在这份参考文档后面的章节中详细叙述)。

9.4.2. 低层次方案

在较低层次上,有以下这些类:DataSourceUtils(针对JDBC),SessionFactoryUtils(针对Hibernate),PersistenceManagerFactoryUtils(针对JDO)等等。当对应用代码来说,直接同原始持久化API特有的资源类型打交道是更好的选择时,这些类确保应用代码获取到正确的Spring框架所管理的bean,事务被正确同步,处理过程中的异常被映射到一致的API。

例如,在JDBC环境下,你不再使用传统的调用 DataSource 的
getConnection() 方法的方式,而是使用Spring的 org.springframework.jdbc.datasource.DataSourceUtils,像这样:

Connection conn = DataSourceUtils.getConnection(dataSource);

如果已有一个事务及与之关联的connection存在,该实例将被返回。否则,该方法调用将触发起一个新的connection的创建动作,该connection(可选地)被同步到任何现有的事务,并可以在同一事务范围内被后续的调用复用。正如上面提到的,这个过程有一个额外的好处,就是任何
SQLException将被包装为Spring框架的
CannotGetJdbcConnectionException,该类是Spring框架的unchecked的DataAccessExceptions层次体系中的一员。这将给你比从
SQLException 中简单所得更多的信息,而且保证了跨数据库——甚至其他持久化技术——的移植性。

应该指出的是,这些类同样可以在没有Spring事务管理的环境中工作良好(事务同步能力是可选的),所以无论你是否使用Spring的事务管理,你都可以使用这些类。

当然,一旦你用过Spring的JDBC支持或Hibernate支持,你一般就不再会选择 DataSourceUtils 或是别的辅助类了,因为你会更乐意与Spring抽象一起工作,而不是直接使用相关的API。例如,如果你使用Spring的
JdbcTemplate 或 jdbc.object 包来简化使用JDBC,Spring会在幕后替你正确地获取连接,而你不需要写任何特殊代码。

9.4.3. TransactionAwareDataSourceProxy

工作在最底层的是 TransactionAwareDataSourceProxy 类。这是一个对目标
DataSource 的代理,它包装了目标 DataSource,提供对Spring管理事务的可知性。在这点上,它类似于一个J2EE服务器提供的事务性JNDI
DataSource。

该类应该永远不需要被应用代码使用,除非现有代码存在需要直接传递一个标准的JDBC的 DataSource 的情况。这时可以通过参与Spring管理事务让这些代码仍然有用。书写新的代码时,首选的方法是采用上面提到的Spring高层抽象。

9.5. 声明式事务管理

大多数Spring用户选择声明式事务管理。这是对应用代码影响最小的选择,因此也最符合
非侵入式 轻量级容器的理念。

Spring的声明式事务管理是通过Spring AOP实现的,因为事务方面的代码与Spring绑定并以一种样板式风格使用,不过尽管如此,你一般并不需要理解AOP概念就可以有效地使用Spirng的声明式事务管理。

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

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

Spring的声明式事务管理可以被应用到任何类(以及那个类的实例)上,不仅仅是像EJB那样的特殊类。

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

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

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

TransactionProxyFactoryBean在哪儿?
Spring2.0及以后的版本中声明式事务的配置与之前的版本有相当大的不同。主要差异在于不再需要配置TransactionProxyFactoryBean了。

Spring2.0之前的旧版本风格的配置仍然是有效的;你可以简单地认为新的<tx:tags/>替你定义了TransactionProxyFactoryBean。

回滚规则的概念比较重要:它使我们能够指定什么样的异常(和throwable)将导致自动回滚。我们在配置文件中声明式地指定,无须在Java代码中。同时,我们仍旧可以通过调用
TransactionStatus 的 setRollbackOnly() 方法编程式地回滚当前事务。通常,我们定义一条规则,声明
MyApplicationException 必须总是导致事务回滚。这种方式带来了显著的好处,它使你的业务对象不必依赖于事务设施。典型的例子是你不必在代码中导入Spring API,事务等。

对EJB来说,默认的行为是EJB容器在遇到 系统异常(通常指运行时异常)时自动回滚当前事务。EJB CMT遇到
应用异常(例如,除了 java.rmi.RemoteException 外别的checked exception)时并不会自动回滚。默认式Spring处理声明式事务管理的规则遵守EJB习惯(只在遇到unchecked exceptions时自动回滚),但通常定制这条规则会更有用。

9.5.1. 理解Spring的声明式事务管理实现

本节的目的是消除与使用声明式事务管理有关的神秘性。简单点儿总是好的,这份参考文档只是告诉你给你的类加上@Transactional注解,在配置文件中添加('<tx:annotation-driven/>')行,然后期望你理解整个过程是怎么工作的。此节讲述Spring的声明式事务管理内部的工作机制,以帮助你在面对事务相关的问题时不至于误入迷途,回朔到上游平静的水域。


Tip
阅读Spring源码是理解清楚Spring事务支持的一个好方法。Spring的Javadoc提供的信息丰富而完整。我们建议你在开发自己的Spring应用时把日志级别设为'DEBUG'级,这样你能更清楚地看到幕后发生的事。

在理解Spring的声明式事务管理方面最重要的概念是:Spring的事务管理是通过AOP代理实现的。其中的事务通知由元数据(目前基于XML或注解)驱动。代理对象与事务元数据结合产生了一个AOP代理,它使用一个PlatformTransactionManager实现品配合TransactionInterceptor,在方法调用前后实施事务。


Note
尽管使用Spring声明式事务管理不需要AOP(尤其是Spring AOP)的知识,但了解这些是很有帮助的。你可以在
Chapter 6, 使用Spring进行面向切面编程(AOP) 章找到关于Spring AOP的全部内容。

概念上来说,在事务代理上调用方法的工作过程看起来像这样:



9.5.2. 第一个例子

请看下面的接口和它的实现。这个例子的意图是介绍概念,使用 Foo 和
Bar 这样的名字只是为了让你关注于事务的用法,而不是领域模型。

<!-- 我们想做成事务性的服务接口 -->

package x.y.service;

public interface FooService {

Foo getFoo(String fooName);

Foo getFoo(String fooName, String barName);

void insertFoo(Foo foo);

void updateFoo(Foo foo);

}

<!-- 上述接口的一个实现 -->

package x.y.service;

public class DefaultFooService implements FooService {

public Foo getFoo(String fooName) {
throw new UnsupportedOperationException();
}

public Foo getFoo(String fooName, String barName) {
throw new UnsupportedOperationException();
}

public void insertFoo(Foo foo) {
throw new UnsupportedOperationException();
}

public void updateFoo(Foo foo) {
throw new UnsupportedOperationException();
}
}

(对该例的目的来说,上例中实现类(DefaultFooService)的每个方法在其方法体中抛出
UnsupportedOperationException 的做法是恰当的,我们可以看到,事务被创建出来,响应
UnsupportedOperationException 的抛出,然后回滚。)

我们假定,FooService的前两个方法(getFoo(String)和getFoo(String, String))必须执行在只读事务上下文中,其余方法(insertFoo(Foo)和updateFoo(Foo))必须执行在读写事务上下文中。

使用XML方式元数据的声明式配置的话,你得这么写(不要想着一次全部理解,所有内容会在后面的章节详细讨论):

<!-- 'context.xml'文件的内容如下: -->

<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> 
<!-- 这是我们将要配置并使它具有事务性的Service对象 -->

<bean id="fooService" class="x.y.service.DefaultFooService"/>

<!-- the transactional advice (i.e. what 'happens'; see the <aop:advisor/> bean below) -->

<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->

<tx:attributes>
<!-- all methods starting with 'get' are read-only -->

<tx:method name="get*" read-only="true"/>
<!-- other methods use the default transaction settings (see below) -->

<tx:method name="*"/>
</tx:attributes>
</tx:advice>

<!-- ensure that the above transactional advice runs for any execution
of an operation defined by the FooService interface -->

<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>

<!-- don't forget the DataSource -->

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>

<!-- similarly, don't forget the (particular) PlatformTransactionManager -->

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

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

</beans>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐