Spring 框架参考文档(四)-数据访问之(事务管理)

来源:互联网 发布:算法统宗 pdf 编辑:程序博客网 时间:2024/04/29 20:29



Spring 框架参考文档(四)-数据访问之(事务管理)

Part IV. 数据访问

这部分参考文档介绍有关于数据访问和数据访问层和业务层或服务层之间的相互作用.

Spring的综合事务管理支持也在一些地方被提到, 就在介绍了Spring Framework支持集成的大量数据访问框架 和技术之后.

  • Chapter 11, 事务管理
  • Chapter 12, DAO 支持
  • Chapter 13, Data access with JDBC
  • Chapter 14, 对象关系映射(ORM)数据访问
  • Chapter 15, Marshalling XML using O/X Mappers



no-rollback-for

不是

 

不会触发回滚的Exception(可能不唯一); 使用逗号分隔. 例如,com.foo.MyBusinessException,ServletException.


11.5.6 @Transactional 的使用

作为使用基于XML配置声明式事务配置方法的补充, 你可以使用一种基于注解的方法. 直接在Java代码中声明事务 语义声明使得声明更加靠近生效的代码. 这不存在过度危险的耦合, 因为不管怎么说开发代码的就意味着这样 被事务化地使用.

@Transactional注解所提供的易用性将使用后面文本中的例子进行说明. 参考下面声明的类:

// 我们想要支持事务的服务类@Transactionalpublic class DefaultFooService implements FooService {    Foo getFoo(String fooName);    Foo getFoo(String fooName, String barName);    void insertFoo(Foo foo);    void updateFoo(Foo foo);}

当在Spring IoC容器中定义上面的POJO时, 这个bean的实例就仅仅需要在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.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <!-- 这就是我们想要使之支持事务的对象 -->    <bean id="fooService" class="x.y.service.DefaultFooService"/>    <!-- 使使用注解配置的事务行为生效 -->    <tx:annotation-driven transaction-manager="txManager"/><!-- 仍然需要一个PlatformTransactionManager -->    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <!-- (这个需要的对象是在其他地方定义的) -->        <property name="dataSource" ref="dataSource"/>    </bean>    <!-- 其他<bean/>的定义 --></beans>
[Tip]

如果你想在<tx:annotation-driven/>标签里面的transaction-manager属性值写的 PlatformTransactionManager对象的bean名字是transactionManager的话可以忽略. 如果你需要 依赖注入的PlatformTransactionManager`bean的名字是另外的, 你需要想前面例子中的那样使用 `transaction-manager属性来指定.

[Note]

如果你是使用基于Java的配置的话那么@EnableTransactionManagement注解提供了等效的支持. 只需要简单的 在类上添加@Configuration.在API文档中查看详细信息.

你可以把@Transactional注解添加在接口定义、接口中的方法定义、类定义、或者一个类中public方法的 前面. 然而, 仅仅有@Transactional注解的存在还不足以使事务的行为生效. @Transactional注解仅仅是 一个用来让某些运行期@Transactional-发现的基础构件来发现的元数据, 并且这些发现还会使用这个元数据 来配置bean的事务行为. 在前面的例子中, 元素`<tx:annotation-driven/>`开启了事务行为.

[Tip]

Spring建议你只为具体类(以及具体类的方法)添加@Transactional注解, 而不要给接口添加注解. 你当然 也可以给接口(或者接口中的方法)添加注解, 但是这只会在你期望的使用的代理时基于接口的时候工作. Java中的 注解不会从接口继承的事实意味着如果你是使用的基于类的代理(proxy-target-class="true")或者基于 编织的方面( mode="aspectj"), 那么关于事务的设置不会被代理或者编织的基础设施注册, 并且对象就不会 被事务型的代理包装, 而这当然是不好的.

[Note]

在代理模式下(默认值), 只有来自外部方法的调用才会被代理拦截. 这意味着自我调用, 在效果上是, 目标对象的 一个方法调用了目标对象的另一个方法, 不会导致产生运行期的事务, 即使被调用的方法被@Transactional 标记了.

如果你想要自我调用也同样被事务包装的话, 参考AspectJ模式的使用(查看下面表格中的模式属性). 在这种情况 下, 首先将不再会有代理; 取而代之, 目标类将会被编织(也就是说, 它的字节码会被修改)来使得 @Transactional成为任何方法在运行期的行为.

Table 11.2. 基于注解的事务设置

XML属性注解属性默认值描述

transaction-manager

N/A (查看TransactionManagementConfigurer的API文档)

transactionManager

要使用的事务管理的名字. 只有在事务管理的名字不是transactionManager的时候是必须的, 就像上面的 例子一样.

mode

mode

proxy

默认值"proxy"使得注解了的bean使用Spring的AOP框架来代理(依照代理的语义, 就像上面讨论的, 只有在 通过代理的方法调用时生效). 候选的"aspectj"模式使用了Spring的AspectJ的事务方面来编织了的类来 替换编织, 修改目标类的字节码来对任何方法调用都会应用. AspectJ编织需要spring-aspects.jar存在于 classpath中如果加载时编织(或者编译时编织)开启了.(查看the section called “Spring configuration”了解关于如何设置 加载时编织的详细信息.)

proxy-target-class

proxyTargetClass

false

只在代理模式生效. 控制使用@Transactional注解的类将会创建什么类型的事务型代理. 如果proxy-target-class 属性设置为true, 那么将会创建基于类的代理. 如果proxy-target-class设置为false或者这个属性 被忽略了, 那么将会创建标准的JDK的基于接口的代理. (查看Section 9.6, “Proxying mechanisms”了解不同代理类型的详细 信息.)

order

order

Ordered.LOWEST_PRECEDENCE

定义应用在添加了@Transactional注解的bean上的事务通知的顺序. (更多关于AOP通知的顺序的信息, 请 查看the section called “Advice ordering”.) 不指定顺序意味着由AOP底层系统决定通知的顺序.


[Note]

proxy-target-class属性控制着使用@Transactional注解的类将会创建什么类型的事务型代理. 如果 proxy-target-class被设置为true, 就会创建基于类的代理. 如果proxy-target-class设置为false 或者这个属性被忽略了, 将会创建标准的JDK的基于接口的代理代理. (查看Section 9.6, “Proxying mechanisms”了解关于不同 代理类型的讨论.)

[Note]

@EnableTransactionManagement<tx:annotation-driven/>只会查找在同一个应用上下文中定义的 bean的@Transactional注解. 这意味着, 如果你在一个DispatcherServletWebApplicationContext 中添加了注解驱动的配置, 那么只会在控制器中查找`@Transactional`bean, 而不会查找服务层. 查看 Section 16.2, “The DispatcherServlet”了解更多.

在决定方法的事务设置时, 最精确的配置优先. 在下面的例子中, DefaultFooService是一个在类级别使用只读 事务设置的类, 但是在同一个类的updateFoo(Foo)方法上的@Transactional注解优先于在类级别的事务设置.

@Transactional(readOnly = true)public class DefaultFooService implements FooService {    public Foo getFoo(String fooName) {        // do something    }    // 该方法的设置更优先    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)    public void updateFoo(Foo foo) {        // do something    }}

@Transactional 设置

@Transactional注解是一个用来定义一个接口、类或者方法必须具备事务化语义的元数据; 例如, "在调用 该方法时挂起所有已经存在的事务,开始一个新的只读事务". 下面是@Transactional注解的默认设置:

  • 传播设置是PROPAGATION_REQUIRED.
  • 隔离等级是ISOLATION_DEFAULT.
  • 事务是可读可写的.
  • 事务超时是使用底层事务系统的默认值, 或者在不支持时没有.
  • 任何的RuntimeException触发回滚, 并且所有的检查的Exception不触发.

这些默认设置都是可以修改的; @Transactional注解的各种属性都整理在下面的表格中了:

Table 11.3. @Transactional

属性类型描述

value

String

指定事务管理器使用的可选限定符.

propagation

enum: Propagation

指定传播属性设置.

isolation

enum: Isolation

指定隔离等级.

readOnly

boolean

设置事务是可读可写还是只读

timeout

int (用秒作为粒度)

事务超时.

rollbackFor

Class对象数组, 必须是继承自Throwable.

指定 必定会触发回滚的类的数组.

rollbackForClassName

类名的数组. 类必须是继承自Throwable.

指定 必定会触发回滚的异常类的数组.

noRollbackFor

Class对象的数组, 这些类必须是继承自Throwable.

指定 必定不会触发回滚的类的数组.

noRollbackForClassName

类的字符串数组, 这些类都必须继承自Throwable.

指定 必定不会触发回滚的异常类的数组.


当前你还不可能明确拥有通过名称对事务的控制力, 如果可以的话(例如, WebLogic的事务记录器), 这个名称 指的是在事务记录器中显示的事务名称, 也是在日志输出中. 对于声明式事务, 事物的名称总是类的完整限定名+ "."+事务化通知配置的方法名. 例如, 如果BusinessService类的handlePayment(..)方法打开了一个 事务, 那么名字就会是: com.foo.BusinessService.handlePayment.

@Transactional 使用多个事务管理器

大多数的Spring应用都只需要一个事务管理器, 但也存在你需要在一个单一应用中使用多个不同的事务管理器的情况. @Transactional注解的value属性可以用来指定要使用的不同的PlatformTransactionManager. 这可以是 bean的名称或者是事务管理器bean的修饰值. 例如, 要使用修饰符号, 下面的Java代码

public class TransactionalService {    @Transactional("order")    public void setSomething(String name) { ... }    @Transactional("account")    public void doSomething() { ... }}

可以和下面在应用上下文声明的事务管理器的bean进行绑定.

<tx:annotation-driven/>    <bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        ...        <qualifier value="order"/>    </bean>    <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        ...        <qualifier value="account"/>    </bean>

在这种情况下, TransactionalService中的两个方法将会分别运行在独立的事务管理器中, 通过"order"和 "account"的修饰符来区分. 默认的<tx:annotation-driven>的目标bean的名称transactionManager 仍然将会在指定的PlatformTransactionManager的bean的修饰符号没有被找到的时候使用.

自定义快捷注解

如果你发现你反复在许多不同的方法上使用@Transactional注解相同的属性, 那么 Spring的基础注解支持将允许你针对你的特定使用场景自定义快捷注解. 例如,定义如下的注解

@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Transactional("order")public @interface OrderTx {}@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Transactional("account")public @interface AccountTx {}

将允许我们将前面章节的例子改写为

public class TransactionalService {    @OrderTx    public void setSomething(String name) { ... }    @AccountTx    public void doSomething() { ... }}

这里我们使用了定义事务管理器的语法,但是我们完全可以包含进传播性行为、回滚规则、超时等等.

11.5.7 事务传播性

本部分讲解一些关于Spring事务传播性行为的语义. 请记住本部分并不是介绍事务的传播性本身; 尽管它比Spring 中的事务传播性更加详细.

在Spring的受管事务中, 存在物理逻辑事务的差别, 以及还有传播性的设置是怎样在这种差别上生效的.

需要 Required

Figure 11.2. 

tx prop required

PROPAGATION_REQUIRED

当传播属性设置为PROPAGATION_REQUIRED时, 将会为设置应用到的每一个方法创建一个逻辑上的事务 作用域. 这每一个单独的逻辑事务作用域可以单独的确定回滚状态, 在逻辑上独立于事务范围的外部事务范围. 当然, 考虑到标准的PROPAGATION_REQUIRED的行为, 所有的这些作用域都将会映射到相同的物理事务上. 因此, 在内部事务作用域中作的事务回滚标记确实会影响到外部事物实际上提交的可能性(这和你所期待的一样).

然而, 在内部事务作用域中标记了回滚, 外部事物决定它自己不回滚的情况下, 这样的回滚(由内部事务作用域 静默触发)就不是期待的了. 一个对应的UnexpectedRollbackException将会在在那里抛出. 这是一个异常 行为, 所以事务的调用者将不可能会在事务其实没有提交的时候被误导为假设提交了. 所以对于内部事务作用域 (在外部调用者没有发觉时)静默的标记了回滚的情况下, 外部调用者调用了提交. 那么外部调用者需要收到一个 UnexpectedRollbackException来清楚的知道需要用一个回滚来取而代之(提交).

需要新的 RequiresNew

Figure 11.3. 

tx prop requires new

PROPAGATION_REQUIRES_NEW

相比较于PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEW对每一个受影响的事务作用域都使用完全 独立的事务. 这样, 物理上的事务就不同了并且可以独立的提交或者回滚, 外部事物不会影响到内部事务的回滚 状态.

嵌套 Nested

PROPAGATION_NESTED对多个可以回滚到的保存点使用了一个单独的底层事务. 这种局部化的回滚允许一个 内部事务触发一个针对它的作用域的回滚, 尽管一些操作已经回滚了, 但外部事物还是可以继续物理上的事务. 这个设置通常都和JDBC的保存点对应, 所以只会在JDBC的资源的事务上有作用. 请查看Spring的DataSourceTransactionManager.

11.5.8 通知事务操作

假设你想要同时执行事务型的一些基本的分析通知. 你怎样在<tx:annotation-driven/>的上下文 中体现?

当你执行updateFoo(Foo)方法时, 你期望看到下面的动作:

  • 配置了分析通知的切面启动.
  • 事务通知执行.
  • 被添加了通知的对象的方法执行.
  • 提交事务.
  • 分析切面报告整个事务方法执行的准确时间.
[Note]

本章节不会详细阐述AOP(除了适用于事务). 查看Chapter 9, Aspect Oriented Programming with Spring了解下面有关AOP配置的详细信息以及AOP的其他信息.

这里是上面讨论的简单分析切面的代码. 通知的排序由Ordered接口控制. 完整的通知排序的信息请查看 the section called “Advice ordering”.

package x.y;import org.aspectj.lang.ProceedingJoinPoint;import org.springframework.util.StopWatch;import org.springframework.core.Ordered;public class SimpleProfiler implements Ordered {    private int order;    // 允许我们对通知排序    public int getOrder() {        return this.order;    }    public void setOrder(int order) {        this.order = order;    }    // 这个方法关于通知    public Object profile(ProceedingJoinPoint call) throws Throwable {        Object returnValue;        StopWatch clock = new StopWatch(getClass().getName());        try {            clock.start(call.toShortString());            returnValue = call.proceed();        } finally {            clock.stop();            System.out.println(clock.prettyPrint());        }        return returnValue;    }}
<?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.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <bean id="fooService" class="x.y.service.DefaultFooService"/>    <!-- 这是切面 -->    <bean id="profiler" class="x.y.SimpleProfiler">        <!-- 在事务通知之前执行(更低的排序) -->        <property name="order" __value="1"__/>    </bean>    <tx:annotation-driven transaction-manager="txManager" __order="200"__/>    <aop:config>            <!-- 这个通知将会在事务通知执行时执行 -->            <aop:aspect id="profilingAspect" ref="profiler">                <aop:pointcut id="serviceMethodWithReturnValue"                        expression="execution(!void x.y..*Service.*(..))"/>                <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>            </aop:aspect>    </aop:config>    <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>    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <property name="dataSource" ref="dataSource"/>    </bean></beans>

上面配置的结果就是对一个叫做fooService的bean进行分析并且也在合适的顺序应用了事务切面. 你也 可以使用相似的方法配置任何数字的补充切面.

下面的例子和上面的有一样的作用, 但是使用了纯粹的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.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <bean id="fooService" class="x.y.service.DefaultFooService"/>    <!-- 分析通知 -->    <bean id="profiler" class="x.y.SimpleProfiler">        <!-- 在事务通知之前执行(更低的排序数字) -->        __<property name="order" value="1__"/>    </bean>    <aop:config>        <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>        <!-- 将在分析的通知执行之后执行(注意order属性) -->        <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" __order="2__"/>        <!-- order的值比分析切面的高 -->        <aop:aspect id="profilingAspect" ref="profiler">            <aop:pointcut id="serviceMethodWithReturnValue"                    expression="execution(!void x.y..*Service.*(..))"/>            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>        </aop:aspect>    </aop:config>    <tx:advice id="txAdvice" transaction-manager="txManager">        <tx:attributes>            <tx:method name="get*" read-only="true"/>            <tx:method name="*"/>        </tx:attributes>    </tx:advice>    <!-- 其他的<bean/>的定义, 如DataSource和PlatformTransactionManager --></beans>

上面配置的结果就是分析一个叫做fooService的bean并且在他之上应用了指定的事务切面. 如果你想让 你的分析通知在进入的时候在事务通知之后执行, 并且在出去的时候在事务通知之前执行, 你只需要 简单的交换分析切面bean的order属性的值, 使得它比事务通知的排序值更高就行.

你配置其他切面也使用类似的方式.

11.5.9 与AspectJ一起使用@Transactional

使用Spring Framework的@Transactional来支持Spring容器外由AspectJ切面所定义的事务也是可以的. 要这样做, 你首先需要在你的类(或者针对可选的类的方法)上使用@Transactional注解进行注解, 然后使用 spring-aspects.jar文件里面定义的org.springframework.transaction.aspectj.AnnotationTransactionAspect对应用进行链接(编织). 指定的切面也必须使用事务管理器进行配置. 你当然可以使用Spring Framework的IoC容器来负责进行切面的 依赖注入.配置事务管理器切面的最简单方法是使用<tx:annotation-driven/>元素并且对属性mode使用 Section 11.5.6, “@Transactional 的使用”里面描述的aspectj. 因为我们这里关注的是程序运行在 Spring容器外部, 所以我们将向你展示编程式的方法.

[Note]

在开始之前, 你可能需要先去阅读Section 11.5.6, “@Transactional 的使用”和Chapter 9, Aspect Oriented Programming with Spring.

// 构造一个适当的事务管理器DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());// 配置AnnotationTransactionAspect去使用它; 这个操作必须在执行事务方法之前进行AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);
[Note]

当使用切面时, 你必须在实现类(以及/或者说这些类的方法)上进行注解, 不要在实现类的接口(如果有)上 注解. AspectJ遵循Java的规则在接口上的注解不会继承.

在类上的@Transactional注解定义了执行类中所有方法的默认事务行为.

在类中方法上的@Transactional注解覆盖了由类上的注解(存在的话)定义的默认事务行为. 任何方法均可以 被注解, 不论其访问可见性如何.

为了使用AnnotationTransactionAspect编织你的应用你必须连同AspectJ进行构建(查看 AspectJ开发指南)或者使用 加载时编织. 查看Section 9.8.4, “Load-time weaving with AspectJ in the Spring Framework”了解关于使用AspectJ进行加载时编织的讨论.

11.6 编程式事务管理

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

  • 使用TransactionTemplate.
  • 直接使用PlatformTransactionManager的一个实现.

Spring一般都推荐使用TransactionTemplate来进行编程式事务管理. 第二种方式有点类似于使用JTA的 UserTransaction接口, 尽管异常处理没有那么复杂化了.

11.6.1 使用TransactionTemplate

TransactionTemplate采用了像JdbcTemplate等其他Spring的templates的一样的方式. 它才用了 回调的方式来使得应用的代码脱离需要的样本采集和食物资源释放, 并且在代码中表现为意图驱动, 这样代码的 编写就可以完全只关注于开发人员想要做的部分.

[Note]

就像你将在下面的例子中看到的那样, 使用TransactionTemplate会把你和Spring的事务基础设施和API绑定 在一起. 用与不用编程式事务管理是不是符合你们开发的需求取决于你自己.

应用代码必须在事务上下文中执行, 并且必须显示地使用TransactionTemplate, 看起来就像下面这样. 你, 作为一名开发者, 编写一个TransactionCallback的实现类(通常表现为一个匿名内部类)来包含需要在事务 上下文中执行的代码. 然后你需要将你自定义的TransactionCallback的实例传递给在TransactionTemplate 中暴露出来的execute(..)方法.

public class SimpleService implements Service {    // 在这个实例而的所有方法中共享的TransactionTemplate单例    private final TransactionTemplate transactionTemplate;    // 使用构造器注入PlatformTransactionManager    public SimpleService(PlatformTransactionManager transactionManager) {        Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");        this.transactionTemplate = new TransactionTemplate(transactionManager);    }    public Object someServiceMethod() {        return transactionTemplate.execute(new TransactionCallback() {            // 本方法的代码在事务上下文中执行            public Object doInTransaction(TransactionStatus status) {                updateOperation1();                return resultOfUpdateOperation2();            }        });    }}

如果没有返回值的话, 使用方便的TransactionCallbackWithoutResult的匿名类就像下面这样:

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

在回调方法中可以通过调用TransactionStatus对象提供的setRollbackOnly()方法来回滚事务:

transactionTemplate.execute(new TransactionCallbackWithoutResult() {    protected void doInTransactionWithoutResult(TransactionStatus status) {        try {            updateOperation1();            updateOperation2();        } catch (SomeBusinessExeption ex) {            status.setRollbackOnly();        }    }});

指定事务设置

你可以指定事务设置, 如传播类型、隔离等级、超时, 都可以通过TransactionTemplate设置, 不管是编程式 还是配置文件. TransactionTemplate的实例默认拥有 默认事务配置. 下面的代码展示了对 TransactionTemplate的事务配置的定制方法:

public class SimpleService implements Service {    private final TransactionTemplate transactionTemplate;    public SimpleService(PlatformTransactionManager transactionManager) {        Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");        this.transactionTemplate = new TransactionTemplate(transactionManager);        // 如果需要, 可以将需要的配置明确的设置在这里        this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);        this.transactionTemplate.setTimeout(30); // 30 秒        // 还可以有更多...    }}

下面的代码使用Spring XML配置文件使用自定义的事务设置定义了一个TransactionTemplatesharedTransactionTemplate可以被注入到很多需要的服务里面.

<bean id="sharedTransactionTemplate"        class="org.springframework.transaction.support.TransactionTemplate">    <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>    <property name="timeout" value="30"/></bean>"

最后, TransactionTemplate类的实例时线程安全的, 在一个实例里面不会维护任何会话的状态. 但是 TransactionTemplate的实例却维护配置的状态, 所以尽管许多的类可以共享一个 TransactionTemplate的单例, 但如果某一个类需要TransactionTemplate拥有不一样的设置(例如, 不一样的隔离等级), 那么你需要创建两个TransactionTemplate的不同实例.

11.6.2 PlatformTransactionManager的使用

你也可以直接使用org.springframework.transaction.PlatformTransactionManager来管理你的事务. 只需简单的将你所使用的PlatformTransactionManager的实现类通过一个bean引用传递给你的bean. 然后, 你就可以使用TransactionDefinitionTransactionStatus对象来新建、回滚、提交事务了.

DefaultTransactionDefinition def = new DefaultTransactionDefinition();// 显示指定事务名称是只能通过编程式才能做到的def.setName("SomeTxName");def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);TransactionStatus status = txManager.getTransaction(def);try {    // 在这里执行你的业务逻辑}catch (MyException ex) {    txManager.rollback(status);    throw ex;}txManager.commit(status);

11.7 在编程式和声明式事务管理中选择

编程式事务管理只在你只有很小数量的事务操作的时候是好主意. 例如, 如果你有一个web应用只有确定的更新 操作需要事务, 你就有可能不想通过Spring或者其他技术来建立事务代理. 在这种情况下, 使用 TransactionTemplate可能是一个好的方法. 可以明确的设置事务的名字也是只有编程式事务管理才能 做的一些事情之一.

另一方面, 如果你有大量的事务操作, 那么声明式事务管通常是值得的. 它让事务管理和业务逻辑分开, 并且也 不难配置. 当使用Spring Framework时, 不想EJB CMT, 声明式事务管理的配置大大的减少了.

11.8 应用服务器的特异性集成

Spring的事务抽象通常都是和应用服务器无关的. 补充一下, Spring的JtaTransactionManager类, 能够 可选地执行一个JNDI发现来自动地定位由于应用服务器改变的JTA的UserTransactionTransactionManager 的最新对象. 通过访问JTA的TransactionManager允许操作事务语义, 以一种特殊的方式来支持事务悬停. 查看JtaTransactionManager的API来了解详情.

Spring的JtaTransactionManager是运行在Java EE应用服务器上面的标准选择, 并且也是已知的能够在 所有的通用服务器上很好工作的. 高级功能, 如事务悬浮, 能够在许多服务器上很好工作————包括GlassFish, JBoss以及Geronimo————不需要额外的特殊配置. 然而, 为了完整的支持事务悬浮和更多的高级特性的集成, Spring为WebLogic Server和WebSphere提供了特殊的适配器. 关于这些适配器将在下面的章节中讨论.

在标准场景下, 包括WebLogic Server和WebSphere, 考虑使用方便的<tx:jta-transaction-manager/> 配置元素. 当配置之后, 这个元素就会自动探测底层服务器并且选择针对平台可用的最佳的事务管理器. 这也 意味着你不需要单独明确配置针对服务器的适配器类(例如下面将要讨论的); 对应的, 它们是由标准的 JtaTransactionManager使用默认的值自动选择的.

11.8.1 IBM WebSphere

在WebSphere 6.1.0.9及更高版本中, 推荐使用的Spring JTA事务管理器是 WebSphereUowTransactionManager. 这个特殊的适配器使用了IBM的UOWManager API, 这个实在WebSphere 应用服务器的6.0.2.19及更高版本和6.1.0.9及更高版本中可用的. 通过这个适配器, Spring提供的事务悬浮 (如PROPAGATION_REQUIRES_NEW开启的挂起/恢复)就是由IBM官方支持的了!

11.8.2 Oracle WebLogic Server

在WebLogic Server 9.0或更高版本中, 你通常应该使用WebLogicJtaTransactionManager来代替备用的 JtaTransactionManager类. 这个特殊的有WebLogic声明的正常的JtaTransactionManager的子类支持 全部的在WebLogic管理的环境中的Spring的事务定义, 超越了标准的JTA语义: 特性包括事务名称, 每个事务的 隔离等级, 以及在所有场景中的事务的正常恢复.

11.9 通用问题的解决

11.9.1 特殊DataSource使用错误的事务管理器

使用正确的 PlatformTransactionManager实现取决于你对事务技术的选择和需求.使用合适了, Spring Framework仅仅是提供了简单又编写的抽象. 如果你使用全局的事务, 你必须在所有的事务操作中 使用org.springframework.transaction.jta.JtaTransactionManager类(或者是它 应用服务器定义的子类).否则事务基础设施会尝试在 如容器的DataSource等资源上执行本地事务. 这些本地事务不会有效果, 并且好的应用服务器会把他们当做 错误.

11.10 更多资源

关于Spring Framework的事务支持的更多信息:

  • Spring 的分布式事务, 使用和不使用XA是一个JavaWorld 的演示, 通过7个在Spring应用中的分布式事务, Spring的David Syer引导你, 3个使用了XA, 4个没有使用.
  • Java事务设计策略是一本来自 InfoQ的书籍, 使用了很好的节奏来介绍介绍了Java中的事务. 它也包含了怎样配置和使用Spring Framework和EJB3事务的对比例子.

no-rollback-for

不是

 

不会触发回滚的Exception(可能不唯一); 使用逗号分隔. 例如,com.foo.MyBusinessException,ServletException.


11.5.6 @Transactional 的使用

作为使用基于XML配置声明式事务配置方法的补充, 你可以使用一种基于注解的方法. 直接在Java代码中声明事务 语义声明使得声明更加靠近生效的代码. 这不存在过度危险的耦合, 因为不管怎么说开发代码的就意味着这样 被事务化地使用.

@Transactional注解所提供的易用性将使用后面文本中的例子进行说明. 参考下面声明的类:

// 我们想要支持事务的服务类@Transactionalpublic class DefaultFooService implements FooService {    Foo getFoo(String fooName);    Foo getFoo(String fooName, String barName);    void insertFoo(Foo foo);    void updateFoo(Foo foo);}

当在Spring IoC容器中定义上面的POJO时, 这个bean的实例就仅仅需要在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.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <!-- 这就是我们想要使之支持事务的对象 -->    <bean id="fooService" class="x.y.service.DefaultFooService"/>    <!-- 使使用注解配置的事务行为生效 -->    <tx:annotation-driven transaction-manager="txManager"/><!-- 仍然需要一个PlatformTransactionManager -->    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <!-- (这个需要的对象是在其他地方定义的) -->        <property name="dataSource" ref="dataSource"/>    </bean>    <!-- 其他<bean/>的定义 --></beans>
[Tip]

如果你想在<tx:annotation-driven/>标签里面的transaction-manager属性值写的 PlatformTransactionManager对象的bean名字是transactionManager的话可以忽略. 如果你需要 依赖注入的PlatformTransactionManager`bean的名字是另外的, 你需要想前面例子中的那样使用 `transaction-manager属性来指定.

[Note]

如果你是使用基于Java的配置的话那么@EnableTransactionManagement注解提供了等效的支持. 只需要简单的 在类上添加@Configuration.在API文档中查看详细信息.

你可以把@Transactional注解添加在接口定义、接口中的方法定义、类定义、或者一个类中public方法的 前面. 然而, 仅仅有@Transactional注解的存在还不足以使事务的行为生效. @Transactional注解仅仅是 一个用来让某些运行期@Transactional-发现的基础构件来发现的元数据, 并且这些发现还会使用这个元数据 来配置bean的事务行为. 在前面的例子中, 元素`<tx:annotation-driven/>`开启了事务行为.

[Tip]

Spring建议你只为具体类(以及具体类的方法)添加@Transactional注解, 而不要给接口添加注解. 你当然 也可以给接口(或者接口中的方法)添加注解, 但是这只会在你期望的使用的代理时基于接口的时候工作. Java中的 注解不会从接口继承的事实意味着如果你是使用的基于类的代理(proxy-target-class="true")或者基于 编织的方面( mode="aspectj"), 那么关于事务的设置不会被代理或者编织的基础设施注册, 并且对象就不会 被事务型的代理包装, 而这当然是不好的.

[Note]

在代理模式下(默认值), 只有来自外部方法的调用才会被代理拦截. 这意味着自我调用, 在效果上是, 目标对象的 一个方法调用了目标对象的另一个方法, 不会导致产生运行期的事务, 即使被调用的方法被@Transactional 标记了.

如果你想要自我调用也同样被事务包装的话, 参考AspectJ模式的使用(查看下面表格中的模式属性). 在这种情况 下, 首先将不再会有代理; 取而代之, 目标类将会被编织(也就是说, 它的字节码会被修改)来使得 @Transactional成为任何方法在运行期的行为.

Table 11.2. 基于注解的事务设置

XML属性注解属性默认值描述

transaction-manager

N/A (查看TransactionManagementConfigurer的API文档)

transactionManager

要使用的事务管理的名字. 只有在事务管理的名字不是transactionManager的时候是必须的, 就像上面的 例子一样.

mode

mode

proxy

默认值"proxy"使得注解了的bean使用Spring的AOP框架来代理(依照代理的语义, 就像上面讨论的, 只有在 通过代理的方法调用时生效). 候选的"aspectj"模式使用了Spring的AspectJ的事务方面来编织了的类来 替换编织, 修改目标类的字节码来对任何方法调用都会应用. AspectJ编织需要spring-aspects.jar存在于 classpath中如果加载时编织(或者编译时编织)开启了.(查看the section called “Spring configuration”了解关于如何设置 加载时编织的详细信息.)

proxy-target-class

proxyTargetClass

false

只在代理模式生效. 控制使用@Transactional注解的类将会创建什么类型的事务型代理. 如果proxy-target-class 属性设置为true, 那么将会创建基于类的代理. 如果proxy-target-class设置为false或者这个属性 被忽略了, 那么将会创建标准的JDK的基于接口的代理. (查看Section 9.6, “Proxying mechanisms”了解不同代理类型的详细 信息.)

order

order

Ordered.LOWEST_PRECEDENCE

定义应用在添加了@Transactional注解的bean上的事务通知的顺序. (更多关于AOP通知的顺序的信息, 请 查看the section called “Advice ordering”.) 不指定顺序意味着由AOP底层系统决定通知的顺序.


[Note]

proxy-target-class属性控制着使用@Transactional注解的类将会创建什么类型的事务型代理. 如果 proxy-target-class被设置为true, 就会创建基于类的代理. 如果proxy-target-class设置为false 或者这个属性被忽略了, 将会创建标准的JDK的基于接口的代理代理. (查看Section 9.6, “Proxying mechanisms”了解关于不同 代理类型的讨论.)

[Note]

@EnableTransactionManagement<tx:annotation-driven/>只会查找在同一个应用上下文中定义的 bean的@Transactional注解. 这意味着, 如果你在一个DispatcherServletWebApplicationContext 中添加了注解驱动的配置, 那么只会在控制器中查找`@Transactional`bean, 而不会查找服务层. 查看 Section 16.2, “The DispatcherServlet”了解更多.

在决定方法的事务设置时, 最精确的配置优先. 在下面的例子中, DefaultFooService是一个在类级别使用只读 事务设置的类, 但是在同一个类的updateFoo(Foo)方法上的@Transactional注解优先于在类级别的事务设置.

@Transactional(readOnly = true)public class DefaultFooService implements FooService {    public Foo getFoo(String fooName) {        // do something    }    // 该方法的设置更优先    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)    public void updateFoo(Foo foo) {        // do something    }}

@Transactional 设置

@Transactional注解是一个用来定义一个接口、类或者方法必须具备事务化语义的元数据; 例如, "在调用 该方法时挂起所有已经存在的事务,开始一个新的只读事务". 下面是@Transactional注解的默认设置:

  • 传播设置是PROPAGATION_REQUIRED.
  • 隔离等级是ISOLATION_DEFAULT.
  • 事务是可读可写的.
  • 事务超时是使用底层事务系统的默认值, 或者在不支持时没有.
  • 任何的RuntimeException触发回滚, 并且所有的检查的Exception不触发.

这些默认设置都是可以修改的; @Transactional注解的各种属性都整理在下面的表格中了:

Table 11.3. @Transactional

属性类型描述

value

String

指定事务管理器使用的可选限定符.

propagation

enum: Propagation

指定传播属性设置.

isolation

enum: Isolation

指定隔离等级.

readOnly

boolean

设置事务是可读可写还是只读

timeout

int (用秒作为粒度)

事务超时.

rollbackFor

Class对象数组, 必须是继承自Throwable.

指定 必定会触发回滚的类的数组.

rollbackForClassName

类名的数组. 类必须是继承自Throwable.

指定 必定会触发回滚的异常类的数组.

noRollbackFor

Class对象的数组, 这些类必须是继承自Throwable.

指定 必定不会触发回滚的类的数组.

noRollbackForClassName

类的字符串数组, 这些类都必须继承自Throwable.

指定 必定不会触发回滚的异常类的数组.


当前你还不可能明确拥有通过名称对事务的控制力, 如果可以的话(例如, WebLogic的事务记录器), 这个名称 指的是在事务记录器中显示的事务名称, 也是在日志输出中. 对于声明式事务, 事物的名称总是类的完整限定名+ "."+事务化通知配置的方法名. 例如, 如果BusinessService类的handlePayment(..)方法打开了一个 事务, 那么名字就会是: com.foo.BusinessService.handlePayment.

@Transactional 使用多个事务管理器

大多数的Spring应用都只需要一个事务管理器, 但也存在你需要在一个单一应用中使用多个不同的事务管理器的情况. @Transactional注解的value属性可以用来指定要使用的不同的PlatformTransactionManager. 这可以是 bean的名称或者是事务管理器bean的修饰值. 例如, 要使用修饰符号, 下面的Java代码

public class TransactionalService {    @Transactional("order")    public void setSomething(String name) { ... }    @Transactional("account")    public void doSomething() { ... }}

可以和下面在应用上下文声明的事务管理器的bean进行绑定.

<tx:annotation-driven/>    <bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        ...        <qualifier value="order"/>    </bean>    <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        ...        <qualifier value="account"/>    </bean>

在这种情况下, TransactionalService中的两个方法将会分别运行在独立的事务管理器中, 通过"order"和 "account"的修饰符来区分. 默认的<tx:annotation-driven>的目标bean的名称transactionManager 仍然将会在指定的PlatformTransactionManager的bean的修饰符号没有被找到的时候使用.

自定义快捷注解

如果你发现你反复在许多不同的方法上使用@Transactional注解相同的属性, 那么 Spring的基础注解支持将允许你针对你的特定使用场景自定义快捷注解. 例如,定义如下的注解

@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Transactional("order")public @interface OrderTx {}@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Transactional("account")public @interface AccountTx {}

将允许我们将前面章节的例子改写为

public class TransactionalService {    @OrderTx    public void setSomething(String name) { ... }    @AccountTx    public void doSomething() { ... }}

这里我们使用了定义事务管理器的语法,但是我们完全可以包含进传播性行为、回滚规则、超时等等.

11.5.7 事务传播性

本部分讲解一些关于Spring事务传播性行为的语义. 请记住本部分并不是介绍事务的传播性本身; 尽管它比Spring 中的事务传播性更加详细.

在Spring的受管事务中, 存在物理逻辑事务的差别, 以及还有传播性的设置是怎样在这种差别上生效的.

需要 Required

Figure 11.2. 

tx prop required

PROPAGATION_REQUIRED

当传播属性设置为PROPAGATION_REQUIRED时, 将会为设置应用到的每一个方法创建一个逻辑上的事务 作用域. 这每一个单独的逻辑事务作用域可以单独的确定回滚状态, 在逻辑上独立于事务范围的外部事务范围. 当然, 考虑到标准的PROPAGATION_REQUIRED的行为, 所有的这些作用域都将会映射到相同的物理事务上. 因此, 在内部事务作用域中作的事务回滚标记确实会影响到外部事物实际上提交的可能性(这和你所期待的一样).

然而, 在内部事务作用域中标记了回滚, 外部事物决定它自己不回滚的情况下, 这样的回滚(由内部事务作用域 静默触发)就不是期待的了. 一个对应的UnexpectedRollbackException将会在在那里抛出. 这是一个异常 行为, 所以事务的调用者将不可能会在事务其实没有提交的时候被误导为假设提交了. 所以对于内部事务作用域 (在外部调用者没有发觉时)静默的标记了回滚的情况下, 外部调用者调用了提交. 那么外部调用者需要收到一个 UnexpectedRollbackException来清楚的知道需要用一个回滚来取而代之(提交).

需要新的 RequiresNew

Figure 11.3. 

tx prop requires new

PROPAGATION_REQUIRES_NEW

相比较于PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEW对每一个受影响的事务作用域都使用完全 独立的事务. 这样, 物理上的事务就不同了并且可以独立的提交或者回滚, 外部事物不会影响到内部事务的回滚 状态.

嵌套 Nested

PROPAGATION_NESTED对多个可以回滚到的保存点使用了一个单独的底层事务. 这种局部化的回滚允许一个 内部事务触发一个针对它的作用域的回滚, 尽管一些操作已经回滚了, 但外部事物还是可以继续物理上的事务. 这个设置通常都和JDBC的保存点对应, 所以只会在JDBC的资源的事务上有作用. 请查看Spring的DataSourceTransactionManager.

11.5.8 通知事务操作

假设你想要同时执行事务型的一些基本的分析通知. 你怎样在<tx:annotation-driven/>的上下文 中体现?

当你执行updateFoo(Foo)方法时, 你期望看到下面的动作:

  • 配置了分析通知的切面启动.
  • 事务通知执行.
  • 被添加了通知的对象的方法执行.
  • 提交事务.
  • 分析切面报告整个事务方法执行的准确时间.
[Note]

本章节不会详细阐述AOP(除了适用于事务). 查看Chapter 9, Aspect Oriented Programming with Spring了解下面有关AOP配置的详细信息以及AOP的其他信息.

这里是上面讨论的简单分析切面的代码. 通知的排序由Ordered接口控制. 完整的通知排序的信息请查看 the section called “Advice ordering”.

package x.y;import org.aspectj.lang.ProceedingJoinPoint;import org.springframework.util.StopWatch;import org.springframework.core.Ordered;public class SimpleProfiler implements Ordered {    private int order;    // 允许我们对通知排序    public int getOrder() {        return this.order;    }    public void setOrder(int order) {        this.order = order;    }    // 这个方法关于通知    public Object profile(ProceedingJoinPoint call) throws Throwable {        Object returnValue;        StopWatch clock = new StopWatch(getClass().getName());        try {            clock.start(call.toShortString());            returnValue = call.proceed();        } finally {            clock.stop();            System.out.println(clock.prettyPrint());        }        return returnValue;    }}
<?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.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <bean id="fooService" class="x.y.service.DefaultFooService"/>    <!-- 这是切面 -->    <bean id="profiler" class="x.y.SimpleProfiler">        <!-- 在事务通知之前执行(更低的排序) -->        <property name="order" __value="1"__/>    </bean>    <tx:annotation-driven transaction-manager="txManager" __order="200"__/>    <aop:config>            <!-- 这个通知将会在事务通知执行时执行 -->            <aop:aspect id="profilingAspect" ref="profiler">                <aop:pointcut id="serviceMethodWithReturnValue"                        expression="execution(!void x.y..*Service.*(..))"/>                <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>            </aop:aspect>    </aop:config>    <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>    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <property name="dataSource" ref="dataSource"/>    </bean></beans>

上面配置的结果就是对一个叫做fooService的bean进行分析并且也在合适的顺序应用了事务切面. 你也 可以使用相似的方法配置任何数字的补充切面.

下面的例子和上面的有一样的作用, 但是使用了纯粹的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.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <bean id="fooService" class="x.y.service.DefaultFooService"/>    <!-- 分析通知 -->    <bean id="profiler" class="x.y.SimpleProfiler">        <!-- 在事务通知之前执行(更低的排序数字) -->        __<property name="order" value="1__"/>    </bean>    <aop:config>        <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>        <!-- 将在分析的通知执行之后执行(注意order属性) -->        <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" __order="2__"/>        <!-- order的值比分析切面的高 -->        <aop:aspect id="profilingAspect" ref="profiler">            <aop:pointcut id="serviceMethodWithReturnValue"                    expression="execution(!void x.y..*Service.*(..))"/>            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>        </aop:aspect>    </aop:config>    <tx:advice id="txAdvice" transaction-manager="txManager">        <tx:attributes>            <tx:method name="get*" read-only="true"/>            <tx:method name="*"/>        </tx:attributes>    </tx:advice>    <!-- 其他的<bean/>的定义, 如DataSource和PlatformTransactionManager --></beans>

上面配置的结果就是分析一个叫做fooService的bean并且在他之上应用了指定的事务切面. 如果你想让 你的分析通知在进入的时候在事务通知之后执行, 并且在出去的时候在事务通知之前执行, 你只需要 简单的交换分析切面bean的order属性的值, 使得它比事务通知的排序值更高就行.

你配置其他切面也使用类似的方式.

11.5.9 与AspectJ一起使用@Transactional

使用Spring Framework的@Transactional来支持Spring容器外由AspectJ切面所定义的事务也是可以的. 要这样做, 你首先需要在你的类(或者针对可选的类的方法)上使用@Transactional注解进行注解, 然后使用 spring-aspects.jar文件里面定义的org.springframework.transaction.aspectj.AnnotationTransactionAspect对应用进行链接(编织). 指定的切面也必须使用事务管理器进行配置. 你当然可以使用Spring Framework的IoC容器来负责进行切面的 依赖注入.配置事务管理器切面的最简单方法是使用<tx:annotation-driven/>元素并且对属性mode使用 Section 11.5.6, “@Transactional 的使用”里面描述的aspectj. 因为我们这里关注的是程序运行在 Spring容器外部, 所以我们将向你展示编程式的方法.

[Note]

在开始之前, 你可能需要先去阅读Section 11.5.6, “@Transactional 的使用”和Chapter 9, Aspect Oriented Programming with Spring.

// 构造一个适当的事务管理器DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());// 配置AnnotationTransactionAspect去使用它; 这个操作必须在执行事务方法之前进行AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);
[Note]

当使用切面时, 你必须在实现类(以及/或者说这些类的方法)上进行注解, 不要在实现类的接口(如果有)上 注解. AspectJ遵循Java的规则在接口上的注解不会继承.

在类上的@Transactional注解定义了执行类中所有方法的默认事务行为.

在类中方法上的@Transactional注解覆盖了由类上的注解(存在的话)定义的默认事务行为. 任何方法均可以 被注解, 不论其访问可见性如何.

为了使用AnnotationTransactionAspect编织你的应用你必须连同AspectJ进行构建(查看 AspectJ开发指南)或者使用 加载时编织. 查看Section 9.8.4, “Load-time weaving with AspectJ in the Spring Framework”了解关于使用AspectJ进行加载时编织的讨论.

11.6 编程式事务管理

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

  • 使用TransactionTemplate.
  • 直接使用PlatformTransactionManager的一个实现.

Spring一般都推荐使用TransactionTemplate来进行编程式事务管理. 第二种方式有点类似于使用JTA的 UserTransaction接口, 尽管异常处理没有那么复杂化了.

11.6.1 使用TransactionTemplate

TransactionTemplate采用了像JdbcTemplate等其他Spring的templates的一样的方式. 它才用了 回调的方式来使得应用的代码脱离需要的样本采集和食物资源释放, 并且在代码中表现为意图驱动, 这样代码的 编写就可以完全只关注于开发人员想要做的部分.

[Note]

就像你将在下面的例子中看到的那样, 使用TransactionTemplate会把你和Spring的事务基础设施和API绑定 在一起. 用与不用编程式事务管理是不是符合你们开发的需求取决于你自己.

应用代码必须在事务上下文中执行, 并且必须显示地使用TransactionTemplate, 看起来就像下面这样. 你, 作为一名开发者, 编写一个TransactionCallback的实现类(通常表现为一个匿名内部类)来包含需要在事务 上下文中执行的代码. 然后你需要将你自定义的TransactionCallback的实例传递给在TransactionTemplate 中暴露出来的execute(..)方法.

public class SimpleService implements Service {    // 在这个实例而的所有方法中共享的TransactionTemplate单例    private final TransactionTemplate transactionTemplate;    // 使用构造器注入PlatformTransactionManager    public SimpleService(PlatformTransactionManager transactionManager) {        Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");        this.transactionTemplate = new TransactionTemplate(transactionManager);    }    public Object someServiceMethod() {        return transactionTemplate.execute(new TransactionCallback() {            // 本方法的代码在事务上下文中执行            public Object doInTransaction(TransactionStatus status) {                updateOperation1();                return resultOfUpdateOperation2();            }        });    }}

如果没有返回值的话, 使用方便的TransactionCallbackWithoutResult的匿名类就像下面这样:

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

在回调方法中可以通过调用TransactionStatus对象提供的setRollbackOnly()方法来回滚事务:

transactionTemplate.execute(new TransactionCallbackWithoutResult() {    protected void doInTransactionWithoutResult(TransactionStatus status) {        try {            updateOperation1();            updateOperation2();        } catch (SomeBusinessExeption ex) {            status.setRollbackOnly();        }    }});

指定事务设置

你可以指定事务设置, 如传播类型、隔离等级、超时, 都可以通过TransactionTemplate设置, 不管是编程式 还是配置文件. TransactionTemplate的实例默认拥有 默认事务配置. 下面的代码展示了对 TransactionTemplate的事务配置的定制方法:

public class SimpleService implements Service {    private final TransactionTemplate transactionTemplate;    public SimpleService(PlatformTransactionManager transactionManager) {        Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");        this.transactionTemplate = new TransactionTemplate(transactionManager);        // 如果需要, 可以将需要的配置明确的设置在这里        this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);        this.transactionTemplate.setTimeout(30); // 30 秒        // 还可以有更多...    }}

下面的代码使用Spring XML配置文件使用自定义的事务设置定义了一个TransactionTemplatesharedTransactionTemplate可以被注入到很多需要的服务里面.

<bean id="sharedTransactionTemplate"        class="org.springframework.transaction.support.TransactionTemplate">    <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>    <property name="timeout" value="30"/></bean>"

最后, TransactionTemplate类的实例时线程安全的, 在一个实例里面不会维护任何会话的状态. 但是 TransactionTemplate的实例却维护配置的状态, 所以尽管许多的类可以共享一个 TransactionTemplate的单例, 但如果某一个类需要TransactionTemplate拥有不一样的设置(例如, 不一样的隔离等级), 那么你需要创建两个TransactionTemplate的不同实例.

11.6.2 PlatformTransactionManager的使用

你也可以直接使用org.springframework.transaction.PlatformTransactionManager来管理你的事务. 只需简单的将你所使用的PlatformTransactionManager的实现类通过一个bean引用传递给你的bean. 然后, 你就可以使用TransactionDefinitionTransactionStatus对象来新建、回滚、提交事务了.

DefaultTransactionDefinition def = new DefaultTransactionDefinition();// 显示指定事务名称是只能通过编程式才能做到的def.setName("SomeTxName");def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);TransactionStatus status = txManager.getTransaction(def);try {    // 在这里执行你的业务逻辑}catch (MyException ex) {    txManager.rollback(status);    throw ex;}txManager.commit(status);

11.7 在编程式和声明式事务管理中选择

编程式事务管理只在你只有很小数量的事务操作的时候是好主意. 例如, 如果你有一个web应用只有确定的更新 操作需要事务, 你就有可能不想通过Spring或者其他技术来建立事务代理. 在这种情况下, 使用 TransactionTemplate可能是一个好的方法. 可以明确的设置事务的名字也是只有编程式事务管理才能 做的一些事情之一.

另一方面, 如果你有大量的事务操作, 那么声明式事务管通常是值得的. 它让事务管理和业务逻辑分开, 并且也 不难配置. 当使用Spring Framework时, 不想EJB CMT, 声明式事务管理的配置大大的减少了.

11.8 应用服务器的特异性集成

Spring的事务抽象通常都是和应用服务器无关的. 补充一下, Spring的JtaTransactionManager类, 能够 可选地执行一个JNDI发现来自动地定位由于应用服务器改变的JTA的UserTransactionTransactionManager 的最新对象. 通过访问JTA的TransactionManager允许操作事务语义, 以一种特殊的方式来支持事务悬停. 查看JtaTransactionManager的API来了解详情.

Spring的JtaTransactionManager是运行在Java EE应用服务器上面的标准选择, 并且也是已知的能够在 所有的通用服务器上很好工作的. 高级功能, 如事务悬浮, 能够在许多服务器上很好工作————包括GlassFish, JBoss以及Geronimo————不需要额外的特殊配置. 然而, 为了完整的支持事务悬浮和更多的高级特性的集成, Spring为WebLogic Server和WebSphere提供了特殊的适配器. 关于这些适配器将在下面的章节中讨论.

在标准场景下, 包括WebLogic Server和WebSphere, 考虑使用方便的<tx:jta-transaction-manager/> 配置元素. 当配置之后, 这个元素就会自动探测底层服务器并且选择针对平台可用的最佳的事务管理器. 这也 意味着你不需要单独明确配置针对服务器的适配器类(例如下面将要讨论的); 对应的, 它们是由标准的 JtaTransactionManager使用默认的值自动选择的.

11.8.1 IBM WebSphere

在WebSphere 6.1.0.9及更高版本中, 推荐使用的Spring JTA事务管理器是 WebSphereUowTransactionManager. 这个特殊的适配器使用了IBM的UOWManager API, 这个实在WebSphere 应用服务器的6.0.2.19及更高版本和6.1.0.9及更高版本中可用的. 通过这个适配器, Spring提供的事务悬浮 (如PROPAGATION_REQUIRES_NEW开启的挂起/恢复)就是由IBM官方支持的了!

11.8.2 Oracle WebLogic Server

在WebLogic Server 9.0或更高版本中, 你通常应该使用WebLogicJtaTransactionManager来代替备用的 JtaTransactionManager类. 这个特殊的有WebLogic声明的正常的JtaTransactionManager的子类支持 全部的在WebLogic管理的环境中的Spring的事务定义, 超越了标准的JTA语义: 特性包括事务名称, 每个事务的 隔离等级, 以及在所有场景中的事务的正常恢复.

11.9 通用问题的解决

11.9.1 特殊DataSource使用错误的事务管理器

使用正确的 PlatformTransactionManager实现取决于你对事务技术的选择和需求.使用合适了, Spring Framework仅仅是提供了简单又编写的抽象. 如果你使用全局的事务, 你必须在所有的事务操作中 使用org.springframework.transaction.jta.JtaTransactionManager类(或者是它 应用服务器定义的子类).否则事务基础设施会尝试在 如容器的DataSource等资源上执行本地事务. 这些本地事务不会有效果, 并且好的应用服务器会把他们当做 错误.

11.10 更多资源

关于Spring Framework的事务支持的更多信息:

  • Spring 的分布式事务, 使用和不使用XA是一个JavaWorld 的演示, 通过7个在Spring应用中的分布式事务, Spring的David Syer引导你, 3个使用了XA, 4个没有使用.
  • Java事务设计策略是一本来自 InfoQ的书籍, 使用了很好的节奏来介绍介绍了Java中的事务. 它也包含了怎样配置和使用Spring Framework和EJB3事务的对比例子.

文章摘自:http://spring.cndocs.tk/index.html

no-rollback-for

不是

 

不会触发回滚的Exception(可能不唯一); 使用逗号分隔. 例如,com.foo.MyBusinessException,ServletException.


11.5.6 @Transactional 的使用

作为使用基于XML配置声明式事务配置方法的补充, 你可以使用一种基于注解的方法. 直接在Java代码中声明事务 语义声明使得声明更加靠近生效的代码. 这不存在过度危险的耦合, 因为不管怎么说开发代码的就意味着这样 被事务化地使用.

@Transactional注解所提供的易用性将使用后面文本中的例子进行说明. 参考下面声明的类:

// 我们想要支持事务的服务类@Transactionalpublic class DefaultFooService implements FooService {    Foo getFoo(String fooName);    Foo getFoo(String fooName, String barName);    void insertFoo(Foo foo);    void updateFoo(Foo foo);}

当在Spring IoC容器中定义上面的POJO时, 这个bean的实例就仅仅需要在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.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <!-- 这就是我们想要使之支持事务的对象 -->    <bean id="fooService" class="x.y.service.DefaultFooService"/>    <!-- 使使用注解配置的事务行为生效 -->    <tx:annotation-driven transaction-manager="txManager"/><!-- 仍然需要一个PlatformTransactionManager -->    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <!-- (这个需要的对象是在其他地方定义的) -->        <property name="dataSource" ref="dataSource"/>    </bean>    <!-- 其他<bean/>的定义 --></beans>
[Tip]

如果你想在<tx:annotation-driven/>标签里面的transaction-manager属性值写的 PlatformTransactionManager对象的bean名字是transactionManager的话可以忽略. 如果你需要 依赖注入的PlatformTransactionManager`bean的名字是另外的, 你需要想前面例子中的那样使用 `transaction-manager属性来指定.

[Note]

如果你是使用基于Java的配置的话那么@EnableTransactionManagement注解提供了等效的支持. 只需要简单的 在类上添加@Configuration.在API文档中查看详细信息.

你可以把@Transactional注解添加在接口定义、接口中的方法定义、类定义、或者一个类中public方法的 前面. 然而, 仅仅有@Transactional注解的存在还不足以使事务的行为生效. @Transactional注解仅仅是 一个用来让某些运行期@Transactional-发现的基础构件来发现的元数据, 并且这些发现还会使用这个元数据 来配置bean的事务行为. 在前面的例子中, 元素`<tx:annotation-driven/>`开启了事务行为.

[Tip]

Spring建议你只为具体类(以及具体类的方法)添加@Transactional注解, 而不要给接口添加注解. 你当然 也可以给接口(或者接口中的方法)添加注解, 但是这只会在你期望的使用的代理时基于接口的时候工作. Java中的 注解不会从接口继承的事实意味着如果你是使用的基于类的代理(proxy-target-class="true")或者基于 编织的方面( mode="aspectj"), 那么关于事务的设置不会被代理或者编织的基础设施注册, 并且对象就不会 被事务型的代理包装, 而这当然是不好的.

[Note]

在代理模式下(默认值), 只有来自外部方法的调用才会被代理拦截. 这意味着自我调用, 在效果上是, 目标对象的 一个方法调用了目标对象的另一个方法, 不会导致产生运行期的事务, 即使被调用的方法被@Transactional 标记了.

如果你想要自我调用也同样被事务包装的话, 参考AspectJ模式的使用(查看下面表格中的模式属性). 在这种情况 下, 首先将不再会有代理; 取而代之, 目标类将会被编织(也就是说, 它的字节码会被修改)来使得 @Transactional成为任何方法在运行期的行为.

Table 11.2. 基于注解的事务设置

XML属性注解属性默认值描述

transaction-manager

N/A (查看TransactionManagementConfigurer的API文档)

transactionManager

要使用的事务管理的名字. 只有在事务管理的名字不是transactionManager的时候是必须的, 就像上面的 例子一样.

mode

mode

proxy

默认值"proxy"使得注解了的bean使用Spring的AOP框架来代理(依照代理的语义, 就像上面讨论的, 只有在 通过代理的方法调用时生效). 候选的"aspectj"模式使用了Spring的AspectJ的事务方面来编织了的类来 替换编织, 修改目标类的字节码来对任何方法调用都会应用. AspectJ编织需要spring-aspects.jar存在于 classpath中如果加载时编织(或者编译时编织)开启了.(查看the section called “Spring configuration”了解关于如何设置 加载时编织的详细信息.)

proxy-target-class

proxyTargetClass

false

只在代理模式生效. 控制使用@Transactional注解的类将会创建什么类型的事务型代理. 如果proxy-target-class 属性设置为true, 那么将会创建基于类的代理. 如果proxy-target-class设置为false或者这个属性 被忽略了, 那么将会创建标准的JDK的基于接口的代理. (查看Section 9.6, “Proxying mechanisms”了解不同代理类型的详细 信息.)

order

order

Ordered.LOWEST_PRECEDENCE

定义应用在添加了@Transactional注解的bean上的事务通知的顺序. (更多关于AOP通知的顺序的信息, 请 查看the section called “Advice ordering”.) 不指定顺序意味着由AOP底层系统决定通知的顺序.


[Note]

proxy-target-class属性控制着使用@Transactional注解的类将会创建什么类型的事务型代理. 如果 proxy-target-class被设置为true, 就会创建基于类的代理. 如果proxy-target-class设置为false 或者这个属性被忽略了, 将会创建标准的JDK的基于接口的代理代理. (查看Section 9.6, “Proxying mechanisms”了解关于不同 代理类型的讨论.)

[Note]

@EnableTransactionManagement<tx:annotation-driven/>只会查找在同一个应用上下文中定义的 bean的@Transactional注解. 这意味着, 如果你在一个DispatcherServletWebApplicationContext 中添加了注解驱动的配置, 那么只会在控制器中查找`@Transactional`bean, 而不会查找服务层. 查看 Section 16.2, “The DispatcherServlet”了解更多.

在决定方法的事务设置时, 最精确的配置优先. 在下面的例子中, DefaultFooService是一个在类级别使用只读 事务设置的类, 但是在同一个类的updateFoo(Foo)方法上的@Transactional注解优先于在类级别的事务设置.

@Transactional(readOnly = true)public class DefaultFooService implements FooService {    public Foo getFoo(String fooName) {        // do something    }    // 该方法的设置更优先    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)    public void updateFoo(Foo foo) {        // do something    }}

@Transactional 设置

@Transactional注解是一个用来定义一个接口、类或者方法必须具备事务化语义的元数据; 例如, "在调用 该方法时挂起所有已经存在的事务,开始一个新的只读事务". 下面是@Transactional注解的默认设置:

  • 传播设置是PROPAGATION_REQUIRED.
  • 隔离等级是ISOLATION_DEFAULT.
  • 事务是可读可写的.
  • 事务超时是使用底层事务系统的默认值, 或者在不支持时没有.
  • 任何的RuntimeException触发回滚, 并且所有的检查的Exception不触发.

这些默认设置都是可以修改的; @Transactional注解的各种属性都整理在下面的表格中了:

Table 11.3. @Transactional

属性类型描述

value

String

指定事务管理器使用的可选限定符.

propagation

enum: Propagation

指定传播属性设置.

isolation

enum: Isolation

指定隔离等级.

readOnly

boolean

设置事务是可读可写还是只读

timeout

int (用秒作为粒度)

事务超时.

rollbackFor

Class对象数组, 必须是继承自Throwable.

指定 必定会触发回滚的类的数组.

rollbackForClassName

类名的数组. 类必须是继承自Throwable.

指定 必定会触发回滚的异常类的数组.

noRollbackFor

Class对象的数组, 这些类必须是继承自Throwable.

指定 必定不会触发回滚的类的数组.

noRollbackForClassName

类的字符串数组, 这些类都必须继承自Throwable.

指定 必定不会触发回滚的异常类的数组.


当前你还不可能明确拥有通过名称对事务的控制力, 如果可以的话(例如, WebLogic的事务记录器), 这个名称 指的是在事务记录器中显示的事务名称, 也是在日志输出中. 对于声明式事务, 事物的名称总是类的完整限定名+ "."+事务化通知配置的方法名. 例如, 如果BusinessService类的handlePayment(..)方法打开了一个 事务, 那么名字就会是: com.foo.BusinessService.handlePayment.

@Transactional 使用多个事务管理器

大多数的Spring应用都只需要一个事务管理器, 但也存在你需要在一个单一应用中使用多个不同的事务管理器的情况. @Transactional注解的value属性可以用来指定要使用的不同的PlatformTransactionManager. 这可以是 bean的名称或者是事务管理器bean的修饰值. 例如, 要使用修饰符号, 下面的Java代码

public class TransactionalService {    @Transactional("order")    public void setSomething(String name) { ... }    @Transactional("account")    public void doSomething() { ... }}

可以和下面在应用上下文声明的事务管理器的bean进行绑定.

<tx:annotation-driven/>    <bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        ...        <qualifier value="order"/>    </bean>    <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        ...        <qualifier value="account"/>    </bean>

在这种情况下, TransactionalService中的两个方法将会分别运行在独立的事务管理器中, 通过"order"和 "account"的修饰符来区分. 默认的<tx:annotation-driven>的目标bean的名称transactionManager 仍然将会在指定的PlatformTransactionManager的bean的修饰符号没有被找到的时候使用.

自定义快捷注解

如果你发现你反复在许多不同的方法上使用@Transactional注解相同的属性, 那么 Spring的基础注解支持将允许你针对你的特定使用场景自定义快捷注解. 例如,定义如下的注解

@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Transactional("order")public @interface OrderTx {}@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Transactional("account")public @interface AccountTx {}

将允许我们将前面章节的例子改写为

public class TransactionalService {    @OrderTx    public void setSomething(String name) { ... }    @AccountTx    public void doSomething() { ... }}

这里我们使用了定义事务管理器的语法,但是我们完全可以包含进传播性行为、回滚规则、超时等等.

11.5.7 事务传播性

本部分讲解一些关于Spring事务传播性行为的语义. 请记住本部分并不是介绍事务的传播性本身; 尽管它比Spring 中的事务传播性更加详细.

在Spring的受管事务中, 存在物理逻辑事务的差别, 以及还有传播性的设置是怎样在这种差别上生效的.

需要 Required

Figure 11.2. 

tx prop required

PROPAGATION_REQUIRED

当传播属性设置为PROPAGATION_REQUIRED时, 将会为设置应用到的每一个方法创建一个逻辑上的事务 作用域. 这每一个单独的逻辑事务作用域可以单独的确定回滚状态, 在逻辑上独立于事务范围的外部事务范围. 当然, 考虑到标准的PROPAGATION_REQUIRED的行为, 所有的这些作用域都将会映射到相同的物理事务上. 因此, 在内部事务作用域中作的事务回滚标记确实会影响到外部事物实际上提交的可能性(这和你所期待的一样).

然而, 在内部事务作用域中标记了回滚, 外部事物决定它自己不回滚的情况下, 这样的回滚(由内部事务作用域 静默触发)就不是期待的了. 一个对应的UnexpectedRollbackException将会在在那里抛出. 这是一个异常 行为, 所以事务的调用者将不可能会在事务其实没有提交的时候被误导为假设提交了. 所以对于内部事务作用域 (在外部调用者没有发觉时)静默的标记了回滚的情况下, 外部调用者调用了提交. 那么外部调用者需要收到一个 UnexpectedRollbackException来清楚的知道需要用一个回滚来取而代之(提交).

需要新的 RequiresNew

Figure 11.3. 

tx prop requires new

PROPAGATION_REQUIRES_NEW

相比较于PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEW对每一个受影响的事务作用域都使用完全 独立的事务. 这样, 物理上的事务就不同了并且可以独立的提交或者回滚, 外部事物不会影响到内部事务的回滚 状态.

嵌套 Nested

PROPAGATION_NESTED对多个可以回滚到的保存点使用了一个单独的底层事务. 这种局部化的回滚允许一个 内部事务触发一个针对它的作用域的回滚, 尽管一些操作已经回滚了, 但外部事物还是可以继续物理上的事务. 这个设置通常都和JDBC的保存点对应, 所以只会在JDBC的资源的事务上有作用. 请查看Spring的DataSourceTransactionManager.

11.5.8 通知事务操作

假设你想要同时执行事务型的一些基本的分析通知. 你怎样在<tx:annotation-driven/>的上下文 中体现?

当你执行updateFoo(Foo)方法时, 你期望看到下面的动作:

  • 配置了分析通知的切面启动.
  • 事务通知执行.
  • 被添加了通知的对象的方法执行.
  • 提交事务.
  • 分析切面报告整个事务方法执行的准确时间.
[Note]

本章节不会详细阐述AOP(除了适用于事务). 查看Chapter 9, Aspect Oriented Programming with Spring了解下面有关AOP配置的详细信息以及AOP的其他信息.

这里是上面讨论的简单分析切面的代码. 通知的排序由Ordered接口控制. 完整的通知排序的信息请查看 the section called “Advice ordering”.

package x.y;import org.aspectj.lang.ProceedingJoinPoint;import org.springframework.util.StopWatch;import org.springframework.core.Ordered;public class SimpleProfiler implements Ordered {    private int order;    // 允许我们对通知排序    public int getOrder() {        return this.order;    }    public void setOrder(int order) {        this.order = order;    }    // 这个方法关于通知    public Object profile(ProceedingJoinPoint call) throws Throwable {        Object returnValue;        StopWatch clock = new StopWatch(getClass().getName());        try {            clock.start(call.toShortString());            returnValue = call.proceed();        } finally {            clock.stop();            System.out.println(clock.prettyPrint());        }        return returnValue;    }}
<?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.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <bean id="fooService" class="x.y.service.DefaultFooService"/>    <!-- 这是切面 -->    <bean id="profiler" class="x.y.SimpleProfiler">        <!-- 在事务通知之前执行(更低的排序) -->        <property name="order" __value="1"__/>    </bean>    <tx:annotation-driven transaction-manager="txManager" __order="200"__/>    <aop:config>            <!-- 这个通知将会在事务通知执行时执行 -->            <aop:aspect id="profilingAspect" ref="profiler">                <aop:pointcut id="serviceMethodWithReturnValue"                        expression="execution(!void x.y..*Service.*(..))"/>                <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>            </aop:aspect>    </aop:config>    <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>    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <property name="dataSource" ref="dataSource"/>    </bean></beans>

上面配置的结果就是对一个叫做fooService的bean进行分析并且也在合适的顺序应用了事务切面. 你也 可以使用相似的方法配置任何数字的补充切面.

下面的例子和上面的有一样的作用, 但是使用了纯粹的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.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <bean id="fooService" class="x.y.service.DefaultFooService"/>    <!-- 分析通知 -->    <bean id="profiler" class="x.y.SimpleProfiler">        <!-- 在事务通知之前执行(更低的排序数字) -->        __<property name="order" value="1__"/>    </bean>    <aop:config>        <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>        <!-- 将在分析的通知执行之后执行(注意order属性) -->        <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" __order="2__"/>        <!-- order的值比分析切面的高 -->        <aop:aspect id="profilingAspect" ref="profiler">            <aop:pointcut id="serviceMethodWithReturnValue"                    expression="execution(!void x.y..*Service.*(..))"/>            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>        </aop:aspect>    </aop:config>    <tx:advice id="txAdvice" transaction-manager="txManager">        <tx:attributes>            <tx:method name="get*" read-only="true"/>            <tx:method name="*"/>        </tx:attributes>    </tx:advice>    <!-- 其他的<bean/>的定义, 如DataSource和PlatformTransactionManager --></beans>

上面配置的结果就是分析一个叫做fooService的bean并且在他之上应用了指定的事务切面. 如果你想让 你的分析通知在进入的时候在事务通知之后执行, 并且在出去的时候在事务通知之前执行, 你只需要 简单的交换分析切面bean的order属性的值, 使得它比事务通知的排序值更高就行.

你配置其他切面也使用类似的方式.

11.5.9 与AspectJ一起使用@Transactional

使用Spring Framework的@Transactional来支持Spring容器外由AspectJ切面所定义的事务也是可以的. 要这样做, 你首先需要在你的类(或者针对可选的类的方法)上使用@Transactional注解进行注解, 然后使用 spring-aspects.jar文件里面定义的org.springframework.transaction.aspectj.AnnotationTransactionAspect对应用进行链接(编织). 指定的切面也必须使用事务管理器进行配置. 你当然可以使用Spring Framework的IoC容器来负责进行切面的 依赖注入.配置事务管理器切面的最简单方法是使用<tx:annotation-driven/>元素并且对属性mode使用 Section 11.5.6, “@Transactional 的使用”里面描述的aspectj. 因为我们这里关注的是程序运行在 Spring容器外部, 所以我们将向你展示编程式的方法.

[Note]

在开始之前, 你可能需要先去阅读Section 11.5.6, “@Transactional 的使用”和Chapter 9, Aspect Oriented Programming with Spring.

// 构造一个适当的事务管理器DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());// 配置AnnotationTransactionAspect去使用它; 这个操作必须在执行事务方法之前进行AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);
[Note]

当使用切面时, 你必须在实现类(以及/或者说这些类的方法)上进行注解, 不要在实现类的接口(如果有)上 注解. AspectJ遵循Java的规则在接口上的注解不会继承.

在类上的@Transactional注解定义了执行类中所有方法的默认事务行为.

在类中方法上的@Transactional注解覆盖了由类上的注解(存在的话)定义的默认事务行为. 任何方法均可以 被注解, 不论其访问可见性如何.

为了使用AnnotationTransactionAspect编织你的应用你必须连同AspectJ进行构建(查看 AspectJ开发指南)或者使用 加载时编织. 查看Section 9.8.4, “Load-time weaving with AspectJ in the Spring Framework”了解关于使用AspectJ进行加载时编织的讨论.

11.6 编程式事务管理

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

  • 使用TransactionTemplate.
  • 直接使用PlatformTransactionManager的一个实现.

Spring一般都推荐使用TransactionTemplate来进行编程式事务管理. 第二种方式有点类似于使用JTA的 UserTransaction接口, 尽管异常处理没有那么复杂化了.

11.6.1 使用TransactionTemplate

TransactionTemplate采用了像JdbcTemplate等其他Spring的templates的一样的方式. 它才用了 回调的方式来使得应用的代码脱离需要的样本采集和食物资源释放, 并且在代码中表现为意图驱动, 这样代码的 编写就可以完全只关注于开发人员想要做的部分.

[Note]

就像你将在下面的例子中看到的那样, 使用TransactionTemplate会把你和Spring的事务基础设施和API绑定 在一起. 用与不用编程式事务管理是不是符合你们开发的需求取决于你自己.

应用代码必须在事务上下文中执行, 并且必须显示地使用TransactionTemplate, 看起来就像下面这样. 你, 作为一名开发者, 编写一个TransactionCallback的实现类(通常表现为一个匿名内部类)来包含需要在事务 上下文中执行的代码. 然后你需要将你自定义的TransactionCallback的实例传递给在TransactionTemplate 中暴露出来的execute(..)方法.

public class SimpleService implements Service {    // 在这个实例而的所有方法中共享的TransactionTemplate单例    private final TransactionTemplate transactionTemplate;    // 使用构造器注入PlatformTransactionManager    public SimpleService(PlatformTransactionManager transactionManager) {        Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");        this.transactionTemplate = new TransactionTemplate(transactionManager);    }    public Object someServiceMethod() {        return transactionTemplate.execute(new TransactionCallback() {            // 本方法的代码在事务上下文中执行            public Object doInTransaction(TransactionStatus status) {                updateOperation1();                return resultOfUpdateOperation2();            }        });    }}

如果没有返回值的话, 使用方便的TransactionCallbackWithoutResult的匿名类就像下面这样:

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

在回调方法中可以通过调用TransactionStatus对象提供的setRollbackOnly()方法来回滚事务:

transactionTemplate.execute(new TransactionCallbackWithoutResult() {    protected void doInTransactionWithoutResult(TransactionStatus status) {        try {            updateOperation1();            updateOperation2();        } catch (SomeBusinessExeption ex) {            status.setRollbackOnly();        }    }});

指定事务设置

你可以指定事务设置, 如传播类型、隔离等级、超时, 都可以通过TransactionTemplate设置, 不管是编程式 还是配置文件. TransactionTemplate的实例默认拥有 默认事务配置. 下面的代码展示了对 TransactionTemplate的事务配置的定制方法:

public class SimpleService implements Service {    private final TransactionTemplate transactionTemplate;    public SimpleService(PlatformTransactionManager transactionManager) {        Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");        this.transactionTemplate = new TransactionTemplate(transactionManager);        // 如果需要, 可以将需要的配置明确的设置在这里        this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);        this.transactionTemplate.setTimeout(30); // 30 秒        // 还可以有更多...    }}

下面的代码使用Spring XML配置文件使用自定义的事务设置定义了一个TransactionTemplatesharedTransactionTemplate可以被注入到很多需要的服务里面.

<bean id="sharedTransactionTemplate"        class="org.springframework.transaction.support.TransactionTemplate">    <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>    <property name="timeout" value="30"/></bean>"

最后, TransactionTemplate类的实例时线程安全的, 在一个实例里面不会维护任何会话的状态. 但是 TransactionTemplate的实例却维护配置的状态, 所以尽管许多的类可以共享一个 TransactionTemplate的单例, 但如果某一个类需要TransactionTemplate拥有不一样的设置(例如, 不一样的隔离等级), 那么你需要创建两个TransactionTemplate的不同实例.

11.6.2 PlatformTransactionManager的使用

你也可以直接使用org.springframework.transaction.PlatformTransactionManager来管理你的事务. 只需简单的将你所使用的PlatformTransactionManager的实现类通过一个bean引用传递给你的bean. 然后, 你就可以使用TransactionDefinitionTransactionStatus对象来新建、回滚、提交事务了.

DefaultTransactionDefinition def = new DefaultTransactionDefinition();// 显示指定事务名称是只能通过编程式才能做到的def.setName("SomeTxName");def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);TransactionStatus status = txManager.getTransaction(def);try {    // 在这里执行你的业务逻辑}catch (MyException ex) {    txManager.rollback(status);    throw ex;}txManager.commit(status);

11.7 在编程式和声明式事务管理中选择

编程式事务管理只在你只有很小数量的事务操作的时候是好主意. 例如, 如果你有一个web应用只有确定的更新 操作需要事务, 你就有可能不想通过Spring或者其他技术来建立事务代理. 在这种情况下, 使用 TransactionTemplate可能是一个好的方法. 可以明确的设置事务的名字也是只有编程式事务管理才能 做的一些事情之一.

另一方面, 如果你有大量的事务操作, 那么声明式事务管通常是值得的. 它让事务管理和业务逻辑分开, 并且也 不难配置. 当使用Spring Framework时, 不想EJB CMT, 声明式事务管理的配置大大的减少了.

11.8 应用服务器的特异性集成

Spring的事务抽象通常都是和应用服务器无关的. 补充一下, Spring的JtaTransactionManager类, 能够 可选地执行一个JNDI发现来自动地定位由于应用服务器改变的JTA的UserTransactionTransactionManager 的最新对象. 通过访问JTA的TransactionManager允许操作事务语义, 以一种特殊的方式来支持事务悬停. 查看JtaTransactionManager的API来了解详情.

Spring的JtaTransactionManager是运行在Java EE应用服务器上面的标准选择, 并且也是已知的能够在 所有的通用服务器上很好工作的. 高级功能, 如事务悬浮, 能够在许多服务器上很好工作————包括GlassFish, JBoss以及Geronimo————不需要额外的特殊配置. 然而, 为了完整的支持事务悬浮和更多的高级特性的集成, Spring为WebLogic Server和WebSphere提供了特殊的适配器. 关于这些适配器将在下面的章节中讨论.

在标准场景下, 包括WebLogic Server和WebSphere, 考虑使用方便的<tx:jta-transaction-manager/> 配置元素. 当配置之后, 这个元素就会自动探测底层服务器并且选择针对平台可用的最佳的事务管理器. 这也 意味着你不需要单独明确配置针对服务器的适配器类(例如下面将要讨论的); 对应的, 它们是由标准的 JtaTransactionManager使用默认的值自动选择的.

11.8.1 IBM WebSphere

在WebSphere 6.1.0.9及更高版本中, 推荐使用的Spring JTA事务管理器是 WebSphereUowTransactionManager. 这个特殊的适配器使用了IBM的UOWManager API, 这个实在WebSphere 应用服务器的6.0.2.19及更高版本和6.1.0.9及更高版本中可用的. 通过这个适配器, Spring提供的事务悬浮 (如PROPAGATION_REQUIRES_NEW开启的挂起/恢复)就是由IBM官方支持的了!

11.8.2 Oracle WebLogic Server

在WebLogic Server 9.0或更高版本中, 你通常应该使用WebLogicJtaTransactionManager来代替备用的 JtaTransactionManager类. 这个特殊的有WebLogic声明的正常的JtaTransactionManager的子类支持 全部的在WebLogic管理的环境中的Spring的事务定义, 超越了标准的JTA语义: 特性包括事务名称, 每个事务的 隔离等级, 以及在所有场景中的事务的正常恢复.

11.9 通用问题的解决

11.9.1 特殊DataSource使用错误的事务管理器

使用正确的 PlatformTransactionManager实现取决于你对事务技术的选择和需求.使用合适了, Spring Framework仅仅是提供了简单又编写的抽象. 如果你使用全局的事务, 你必须在所有的事务操作中 使用org.springframework.transaction.jta.JtaTransactionManager类(或者是它 应用服务器定义的子类).否则事务基础设施会尝试在 如容器的DataSource等资源上执行本地事务. 这些本地事务不会有效果, 并且好的应用服务器会把他们当做 错误.

11.10 更多资源

关于Spring Framework的事务支持的更多信息:

  • Spring 的分布式事务, 使用和不使用XA是一个JavaWorld 的演示, 通过7个在Spring应用中的分布式事务, Spring的David Syer引导你, 3个使用了XA, 4个没有使用.
  • Java事务设计策略是一本来自 InfoQ的书籍, 使用了很好的节奏来介绍介绍了Java中的事务. 它也包含了怎样配置和使用Spring Framework和EJB3事务的对比例子.


文章摘自:http://spring.cndocs.tk/index.html

0 0
原创粉丝点击