web处理事务

ssh中处理事务的几种方式

在spring中使用声明型事务
spring使用aop机制管理jdbc的连接和事务。它使用TransactionInterceptor类,Spring事务支持中的核心接口是
org.springframework.transaction.PlatformTransactionManager。为了实际执行事务,Spring所有的事务划分功能都通过传递适当的TransactionDefinition实例,委托给 PlatformTransactionManager。
尽管PlatformTransactionManager接口可以直接使用,应用程序通常配置具体的事务管理器并使用声明性事务来划分事务。
Spring具有多种PlatformTransactionManager实现,它们分为两类:
局部事务策略即针对单个资源执行事务(主要是针对单个的数据库)。实现有 org.springframework.jdbc.datasource.DataSourceTransactionManager。它用于jdbc数据源的配置,调用TransactionInterceptor开是一个事务,
从DataSource得到一个connection并确保auto-commit设为disabled。他用JdbcTemplate在一个线程内绑定一个JDBC connection,TransactionInterceptor负责提交事务,
DataSourceTransactionManager 调用Connection.commit()关闭connection,并解除绑定(potentially allowing for one thread connection per data source)。
例如
<bean id="DataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>oracle.jdbc.driver.OracleDriver</value>
</property>
<property name="url">
<value>jdbc:oracle:thin:@localhost:1521:hua2</value>
</property>
<property name="username">
<value>user</value></property>
<property name="password">
<value>gotpassword</value>
</property>
</bean>
</beans>
<bean id="DataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="DataSource" />
</bean>
<bean id="tatanTransactionScriptsProxyCreator"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<idref bean="tatanTransactionScripts" />
</list>
</property>
<property name="interceptorNames">
<list>
<idref bean="DataSourceTransactionInterceptor" />
</list>
</property>
</bean>
<bean id="DataSourceTransactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager"
ref="DataSourceTransactionManager" />
<property name="transactionAttributeSource">
<value>
com.tatan.tatanTransactionScriptsImpl.*=PROPAGATION_REQUIRED
</value>
</property>
</bean>
 transactionAttributesSource 属性指定每个方法的transaction attribute,PROPAGATION_REQUIRED说明在一个事务内这个方法被执行。
 和EJB一样,默认的情况下,spring只有当unchecked exception被抛出时,才rollback事务,也可以自己加入checked exception。
 tatanTransactionScripts被TransactionInterceptor封装,在一个事物内执行类的每一个方法。
更为简单的配置
 
    <bean id="UserManagerTran"
            class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
            <property name="transactionManager">
                <ref bean="transactionManager"/></property>
            <property name="target"><ref bean="UserManager"/></property>
            <property name="transactionAttributes">
                <props>
                    <prop key="insert*">
                        PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED</prop>
                    <prop key="tran*">
                        PROPAGATION_REQUIRED, ISOLATION_SERIALIZABLE</prop>
                    <prop key="deposit*">
                        PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED</prop>
                </props>
            </property>
        </bean>
         <bean id="transactionManager"
           class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
           <property name="dataSource"><ref bean="dataSource"/></property>
       </bean>

(The TransactionProxyFactoryBean is a ProxyFactoryBean where every bean is adviced with a TransactionInterceptor. And the TransactionInterceptor is a piece of advice.
So you can use a seperate TransactionInterceptor and ProxyFactoryBean. But if you are lazy/smart, you can use the TransactionProxyFactoryBean that does the same thing (only less configuration needed))
对于特定的方法或方法命名模式,代理的具体事务行为由事务属性驱动,如下面的例子所示:
<prop key="insert*">
 ROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED
 </prop>
key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用:
 PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
 PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
 PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
 PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
 PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
 PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
 PROPAGATION_NESTED--如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
 前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。
 它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)。
事务属性中的readOnly标志表示对应的事务应该被最优化为只读事务。这是一个最优化提示。在一些情况下,一些事务策略能够起到显著的最优化效果,例如在使用Object/Relational映射工具(如:Hibernate或TopLink)时避免dirty checking(试图“刷新”)。
在事务属性中还有定义“timeout”值的选项,指定事务超时为几秒。在JTA中,这将被简单地传递到J2EE服务器的事务协调程序,并据此得到相应的解释。

   全局事务管理即执行有可能跨越多个资源的全局事务。主要对应的Spring类是org.springframework.transaction.jta.JtaTransactionManager,它委托给遵循JTA规范的J2EE服务器,也有例外。
spring支持JTA,只需要一个标准的JtaTransactionManager定义,数据库必须支持XA protocol,或者J2EE服务器提供支持XA规范的DataSource。
默认的Spring JtaTransactionManager设置将从标准的JNDI位置获取JTA的 javax.transaction.UserTransaction对象,该JNDI位置由J2EE指定:java:comp/UserTransaction。对于大多数标准J2EE环境下的用例来说,它工作良好。
但是,默认的 JtaTransactionManager不能执行事务挂起操作(即它不支持PROPAGATION_REQUIRES_NEW和 PROPAGATION_NOT_SUPPORTED)。原因是标准的JTA UserTransaction接口不支持挂起或恢复事务的操作;它只支持开始和完成新事务的操作。
为执行事务挂起操作,还需要提供javax.transaction.TransactionManager实例,按照JTA的规定,它提供标准的挂起和恢复方法。遗憾的是,J2EE没有为JTA TransactionManager定义标准的JNDI位置!
因此,必须使用特定于供应商的(vendor-specific)查寻机制。J2EE没有考虑把JTA TransactionManager接口作为它的公开API的一部分。JTA规范规定的TransactionManager接口原本是打算用于容器集成的。
但是为JTA TransactionManager定义标准的JNDI位置还是有重大意义的,尤其是对于轻量级容器(如Spring);然后,便可以以同样的方式来定位任意的J2EE服务器的JTA TransactionManager。
结合jboss JTA的Spring事务划分
oracle-ds.xml
<?xml version="1.0" encoding="UTF-8"?>

<datasources>
 <xa-datasource>
  <jndi-name>XASpringDS</jndi-name>
  <track-connection-by-tx/>
  <isSameRM-override-value>false</isSameRM-override-value>
  <xa-datasource-class>oracle.jdbc.xa.client.OracleXADataSource</xa-datasource-class>
  <xa-datasource-property name="URL">jdbc:oracle:oci8:@orcl</xa-datasource-property>
  <xa-datasource-property name="User">SCOTT</xa-datasource-property>
  <xa-datasource-property name="Password">tiger</xa-datasource-property>
  <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter</exception-sorter-class-name>
  <no-tx-separate-pools/>
 </xa-datasource>
 <mbean
  code="org.jboss.resource.adapter.jdbc.xa.oracle.OracleXAExceptionFormatter"
  name="jboss.jca:service=OracleXAExceptionFormatter">
  <depends optional-attribute-name="TransactionManagerService">
   jboss:service=TransactionManager</depends>
 </mbean>
</datasources>
spring配置
<!-- Data source bean -->
 
 <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
  <property name="jndiName"><value>java:/XASpringDS</value></property>
 </bean>
 
 <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

Spring有效地将DAO实现从实际的运行时环境中分离出来,允许在J2EE容器之外轻松地测试或重用用。
Spring提供了多种事务策略,比如JtaTransactionManager和JDBC DataSourceTransactionManager,
前者委托给J2EE服务器的事务协调程序,后者则针对单个JDBC DataSource(即单个的目标数据库)执行事务。
通过对后端配置进行简单的更改,就能够轻松地调整事务策略适应另一个环境。

 

Spring事务传播机制解惑

概述

当我们调用一个基于Spring的Service接口方法(如UserService#addUser())时,它将运行于Spring管理的事务环境中,Service接口方法可能会在内部调用其它的Service接口方法以共同完成一个完整的业务操作,因此就会产生服务接口方法嵌套调用的情况,Spring通过事务传播行为控制当前的事务如何传播到被嵌套调用的目标服务接口方法中。

事务传播是Spring进行事务管理的重要概念,其重要性怎么强调都不为过。但是事务传播行为也是被误解最多的地方,在本文里,我们将详细分析不同事务传播行为的表现形式,掌握它们之间的区别。

事务传播行为种类

Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播:

表 1事务传播行为类型

事务传播行为类型

说明

PROPAGATION_REQUIRED

如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。

PROPAGATION_SUPPORTS

支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY

使用当前的事务,如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW

新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_NOT_SUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER

以非事务方式执行,如果当前存在事务,则抛出异常。

PROPAGATION_NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

当使用PROPAGATION_NESTED时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制。

几种容易引起误解的组合事务传播行为

当服务接口方法分别使用表1中不同的事务传播行为,且这些接口方法又发生相互调用的情况下,大部分组合都是一目了然,容易理解的。但是,也存在一些容易引起误解的组合事务传播方式。

下面,我们通过两个具体的服务接口的组合调用行为来破解这一难点。这两个服务接口分别是UserService和 ForumService,UserSerice有一个addCredits()方法,ForumSerivce#addTopic()方法调用了 UserSerice#addCredits()方法,发生关联性服务方法的调用:

public class ForumService {

private UserService userService;

public void addTopic() {①调用其它服务接口的方法

//add Topic…

userService.addCredits() ;②被关联调用的业务方法

}

}

嵌套调用的事务方法

对Spring事务传播行为最常见的一个误解是:当服务接口方法发生嵌套调用时,被调用的服务方法只能声明为 PROPAGATION_NESTED。这种观点犯了望文生义的错误,误认为PROPAGATION_NESTED是专为方法嵌套准备的。这种误解遗害不浅,执有这种误解的开发者错误地认为:应尽量不让Service类的业务方法发生相互的调用,Service类只能调用DAO层的DAO类,以避免产生嵌套事务。

其实,这种顾虑是完全没有必要的,PROPAGATION_REQUIRED已经清楚地告诉我们:事务的方法会足够“聪明”地判断上下文是否已经存在一个事务中,如果已经存在,就加入到这个事务中,否则创建一个新的事务。

依照上面的例子,假设我们将ForumService#addTopic()和UserSerice#addCredits()方法的事务传播行为都设置为PROPAGATION_REQUIRED,这两个方法将运行于同一个事务中。

为了清楚地说明这点,可以将Log4J的日志设置为DEBUG级别,以观察Spring事务管理器内部的运行情况。下面将两个业务方法都设置为PROPAGATION_REQUIRED,Spring所输出的日志信息如下:

Using transaction object

[org.springframework.jdbc.datasource.DataSourceTransactionManager$DataSourceTransactionObject@e3849c]

①为 ForumService#addTopic() 新建一个事务

Creating new transaction with name [com.baobaotao.service.ForumService.addTopic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT

Acquired Connection [org.apache.commons.dbcp.PoolableConnection@dc41c5] for JDBC transaction

Switching JDBC Connection [org.apache.commons.dbcp.PoolableConnection@dc41c5] to manual commit

Bound value [org.springframework.jdbc.datasource.ConnectionHolder@ee1ede] for key [org.apache.commons.dbcp.BasicDataSource@4204] to thread [main]

Initializing transaction synchronization

Getting transaction for [com.baobaotao.service.ForumService.addTopic]

Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@ee1ede] for key [org.apache.commons.dbcp.BasicDataSource@4204] bound to thread [main]

Using transaction object [org.springframework.jdbc.datasource.DataSourceTransactionManager$DataSourceTransactionObject@8b8a47]

② UserService#addCredits() 简单地加入到已存在的事务中(即①处创建的事务)

Participating in existing transaction

Getting transaction for [com.baobaotao.service.UserService.addCredits]

Completing transaction for [com.baobaotao.service.UserService.addCredits]

Completing transaction for [com.baobaotao.service.ForumService.addTopic]

Triggering beforeCommit synchronization

Triggering beforeCompletion synchronization

Initiating transaction commit

③调用底层 Connection#commit() 方法提交事务

Committing JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@dc41c5]

Triggering afterCommit synchronization

Triggering afterCompletion synchronization

Clearing transaction synchronization

嵌套事务

将ForumService#addTopic()设置为PROPAGATION_REQUIRED 时,UserSerice#addCredits()设置为PROPAGATION_REQUIRED、PROPAGATION_SUPPORTS、 PROPAGATION_MANDATORY时,运行的效果都是一致的(当然,如果单独调用addCredits()就另当别论了)。

当addTopic()运行在一个事务下(如设置为PROPAGATION_REQUIRED),而addCredits()设置为 PROPAGATION_NESTED时,如果底层数据源支持保存点,Spring将为内部的addCredits()方法产生的一个内嵌的事务。如果 addCredits()对应的内嵌事务执行失败,事务将回滚到addCredits()方法执行前的点,并不会将整个事务回滚。内嵌事务是内层事务的一部分,所以只有外层事务提交时,嵌套事务才能一并提交。

嵌套事务不能够提交,它必须通过外层事务来完成提交的动作,外层事务的回滚也会造成内部事务的回滚。

嵌套事务和新事务

PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED也是容易混淆的两个传播行为。PROPAGATION_REQUIRES_NEW 启动一个新的、和外层事务无关的“内部”事务。该事务拥有自己的独立隔离级别和锁,不依赖于外部事务,独立地提交和回滚。当内部事务开始执行时,外部事务将被挂起,内务事务结束时,外部事务才继续执行。

由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于:PROPAGATION_REQUIRES_NEW 将创建一个全新的事务,它和外层事务没有任何关系,而 PROPAGATION_NESTED 将创建一个依赖于外层事务的子事务,当外层事务提交或回滚时,子事务也会连带提交和回滚。

其它需要注意问题

以下几个问题值得注意:

1) 当业务方法被设置为PROPAGATION_MANDATORY时,它就不能被非事务的业务方法调用。如将 ForumService#addTopic()设置为PROPAGATION_MANDATORY,如果展现层的Action直接调用 addTopic()方法,将引发一个异常。正确的情况是:addTopic()方法必须被另一个带事务的业务方法调用(如 ForumService#otherMethod())。所以PROPAGATION_MANDATORY的方法一般都是被其它业务方法间接调用的。

2) 当业务方法被设置为PROPAGATION_NEVER时,它将不能被拥有事务的其它业务方法调用。假设 UserService#addCredits()设置为PROPAGATION_NEVER,当ForumService# addTopic()拥有一个事务时,addCredits()方法将抛出异常。所以PROPAGATION_NEVER方法一般是被直接调用的。

3)当方法被设置为PROPAGATION_NOT_SUPPORTED时,外层业务方法的事务会被挂起,当内部方法运行完成后,外层方法的事务重新运行。如果外层方法没有事务,直接运行,不需要做任何其它的事。

小结

在Spring声明式事务管理的配置中,事务传播行为是最容易被误解的配置项,原因在于事务传播行为名称(如 PROPAGATION_NESTED:嵌套式事务)和代码结构的类似性上(业务类方法嵌套调用另一个业务类方法)。这种误解在很多Spring开发者中广泛存在,本文深入讲解了Spring事务传播行为对业务方法嵌套调用的真实影响,希望能帮助读者化解对事务传播行为的困惑。

Spring 声明式事务管理源码解读之事务提交

简介: 上次说到spring 声明式事务管理的事务开始部分,按流程来讲,下面应该提交事务了, spring 的声明式事务管理其实是比较复杂的,事实上这种复杂性正是由于事务本身的复杂性导致的,如果能用两三句话就把这部分内容说清楚是不现实的,也是不成熟的,而我对这部分的理解也可能是不全面的,还是那句话,希望大家和我一起把本贴的质量提交起来。
在下面的文章中,我讲会多次提到第一篇文章,第一篇文章的地址是:http://www.javaeye.com/topic/87426
如果要理解事务提交的话,理解事务开始是一个前提条件,所以请先看第一篇文章,再来看这篇
如果你仔细看下去,我想肯定是有很多收获,因为我们确实能从spring 的代码和思想中学到很多东西。

正文:

其实俺的感觉就是事务提交要比事务开始复杂,看事务是否提交我们还是要回到TransactionInterceptor类的invoke方法

代码
  1. public Object invoke(MethodInvocation invocation) throws Throwable {  
  2. // Work out the target class: may be <code> null </code> .  
  3. // The TransactionAttributeSource should be passed the target class  
  4. // as well as the method, which may be from an interface  
  5. Class targetClass = (invocation.getThis() != null) ? invocation.getThis().getClass() : null;  
  6. // Create transaction if necessary.  
  7. TransactionInfo txInfo =  createTransactionIfNecessary(invocation.getMethod(), targetClass);  
  8. Object retVal =  null;  
  9. try {  
  10. // This is an around advice.  
  11. // Invoke the next interceptor in the chain.  
  12. // This will normally result in a target object being invoked.  
  13. retVal =  invocation.proceed();  
  14. }  
  15. catch (Throwable ex) {  
  16. // target invocation exception  
  17. doCloseTransactionAfterThrowing(txInfo, ex);  
  18. throw ex;  
  19. }  
  20. finally {  
  21. doFinally(txInfo);//业务方法出栈后必须先执行的一个方法  
  22. }  
  23. doCommitTransactionAfterReturning(txInfo);  
  24. return retVal;  
  25. }  

    其中的doFinally(txInfo)那一行很重要,也就是说不管如何,这个doFinally方法都是要被调用的,为什么它这么重要呢,举个例子:
    我们还是以propregation_ required 来举例子吧,假设情况是这样的,AService中有一个方法调用了BService中的,这两个方法都处在事务体之中,他们的传播途径都是required 。那么调用开始了,AService的方法首先入方法栈,并创建了TransactionInfo的实例,接着BService的方法入栈,又创建了一个 TransactionInfo的实例,而重点要说明的是TransactionInfo是一个自身关联的内部类,第二个方法入栈时,会给新创建的 TransactionInfo的实例设置一个属性,就是TransactionInfo对象中的private TransactionInfo oldTransactionInfo;属性,这个属性表明BService方法的创建的TransactionInfo对象是有一个old的 transactionInfo对象的,这个oldTransactionInfo对象就是AService方法入栈时创建的 TransactionInfo对象,我们还记得在createTransactionIfNecessary方法里有这样一个方法吧:

    代码
    1. protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) {  
    2. // We always bind the TransactionInfo to the thread, even if we didn't create  
    3. // a new transaction here. This guarantees that the TransactionInfo stack  
    4. // will be managed correctly even if no transaction was created by this aspect.  
    5. txInfo.bindToThread();  
    6. return txInfo;  
    7. }  
    8. 就是这个bindToThread()方法在作怪:  
    9. private  void bindToThread() {  
    10. // Expose current TransactionStatus, preserving any existing transactionStatus for  
    11. // restoration after this transaction is complete.  
    12. oldTransactionInfo = (TransactionInfo) currentTransactionInfo.get();  
    13. currentTransactionInfo.set(this);  
    14. }  

     如果当前线程中已经有了一个TransactionInfo,则拿出来放到新建的transactionInfo对象的oldTransactionInfo属性中,然后再把新建的TransactionInfo设置到当前线程中。

     

    这里有一个概念要搞清楚,就是TransactionInfo对象并不是表明事务状态的对象,表明事务状态的对象是TransactionStatus对象,这个对象同样是TransactionInfo的一个属性(这一点,我在前面一篇文章中并没有讲清楚)。

    接下来BService中的那个方法返回,那么该它退栈了,它退栈后要做的就是doFinally方法,即把它的 oldTransactionInfo设置到当前线程中(这个TransactionInfo对象显然就是AService方法入栈时创建的,怎么现在又要设置到线程中去呢,原因就是BService的方法出栈时并不提交事务,因为BService的传播途径是required ,所以要把栈顶的方法所创建transactioninfo给设置到当前线程中),即调用AService的方法时所创建的TransactionInfo 对象。那么在AServie的方法出栈时同样会设置TransactionInfo对象的oldTransactionInfo到当前线程,这时候显然 oldTransactionInfo是空的,但AService中的方法会提交事务,所以它的oldTransactionInfo也应该是空了。

    在这个小插曲之后,么接下来就应该是到提交事务了,之前在AService的方法出栈时,我们拿到了它入栈时创建的TransactionInfo 对象,这个对象中包含了AService的方法事务状态。即TransactionStatus对象,很显然,太显然了,事务提交中的任何属性都和事务开始时的创建的对象息息相关,这个TransactionStatus对象哪里来的,我们再回头看看createTransactionIfNessary 方法吧:

    代码
    1. protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) {  
    2. txInfo.newTransactionStatus(this.transactionManager.getTransaction(txAttr));  
    3. }  

    再看看transactionManager.getTransaction(txAttr)方法吧:

    代码
    1. public  final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {  
    2. else  if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||  
    3. definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||  
    4. definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {  
    5. if (debugEnabled) {  
    6. logger.debug("Creating new transaction with name [" + definition.getName() +  "]");  
    7. }  
    8. doBegin(transaction, definition);  
    9. boolean newSynchronization = ( this.transactionSynchronization != SYNCHRONIZATION_NEVER);  
    10. return newTransactionStatus(definition, transaction,  true , newSynchronization, debugEnabled, null); //注意这里的返回值,返回的就是一个TransactionStatus对象,这个对象表明了一个事务的状态,比如说是否是一个新的事务,事务是否已经结束,等等,这个对象是非常重要的,在事务提交的时候还是会用到它的。        }  
    11. }  
    12. }  

    还有一点需要说明的是,AService的方法在执行之前创建的transactionstatus确实是通过这个方法创建的,但是,BService的方法在执行之前创建transactionstatus的方法就与这个不一样了,下面会有详解。

     

    回顾了事务开始时所调用的方法之后,是不是觉得现在对spring 如何处理事务越来越清晰了呢。由于这么几个方法的调用,每个方法入栈之前它的事务状态就已经被设置好了。这个事务状态就是为了在方法出栈时被调用而准备的。

    让我们再次回到BService中的方法出栈的那个时间段,看看spring 都做了些什么,我们知道,后入栈的肯定是先出栈,BService中的方法后入栈,拿它肯定要先出栈了,它出栈的时候是要判断是否要提交事务,释放资源的,让我们来看看TransactionInterceptor的invoke的最后那个方法 doCommitTransactionAfterReturning:

     

    代码
    1. protected  void doCommitTransactionAfterReturning(TransactionInfo txInfo) {  
    2. if (txInfo !=  null && txInfo.hasTransaction()) {  
    3. if (logger.isDebugEnabled()) {  
    4. logger.debug("Invoking commit for transaction on " + txInfo.joinpointIdentification());  
    5. }  
    6. this.transactionManager.commit(txInfo.getTransactionStatus());  
    7. //瞧:提交事务时用到了表明事务状态的那个TransactionStatus对象了。  
    8. }  
    9. }  

    看这个方法的名字就知道spring 是要在业务方法出栈时提交事务,貌似很简单,但是事实是这样的吗? 我们接着往下看。

    代码
    1. public  final  void commit(TransactionStatus status)  throws TransactionException {  
    2. DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;  
    3. if (defStatus.isCompleted()) {  
    4. throw  new IllegalTransactionStateException(  
    5. "Transaction is already completed - do not call commit or rollback more than once per transaction");  
    6. }  
    7. if (defStatus.isLocalRollbackOnly()) {  
    8. if (defStatus.isDebug()) {  
    9. logger.debug("Transactional code has requested rollback");  
    10. }  
    11. processRollback(defStatus);  
    12. return;  
    13. }  
    14. if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {  
    15. if (defStatus.isDebug()) {  
    16. logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");  
    17. }  
    18. processRollback(defStatus);  
    19. throw  new UnexpectedRollbackException(  
    20. "Transaction has been rolled back because it has been marked as rollback-only");  
    21. }  
    22. processCommit(defStatus);  
    23. }  

    上面这段代码就是transactionmanager中的commit,但是看上去,它又把自己的职责分配给别人了,从代码里我们看到,如果事务已经结束了就抛异常,如果事务是rollbackonly的,那么就rollback吧,但是按照正常流程,我们还是想来看一下,事务的提交,就是processCommit(status)这个方法吧。

    代码
    1. private  void processCommit(DefaultTransactionStatus status)  throws  TransactionException {  
    2. try {  
    3. boolean beforeCompletionInvoked =  false;  
    4. try {  
    5. triggerBeforeCommit(status);  
    6. triggerBeforeCompletion(status);  
    7. beforeCompletionInvoked = true;  
    8. if (status.hasSavepoint()) {  
    9. if (status.isDebug()) {  
    10. logger.debug("Releasing transaction savepoint");  
    11. }  
    12. status.releaseHeldSavepoint();  
    13. }  
    14. else  if (status.isNewTransaction()) { //这个判断非常重要,下面会详细讲解这个判断的作用  
    15. if (status.isDebug()) {  
    16. logger.debug("Initiating transaction commit");  
    17. }  
    18. boolean globalRollbackOnly = status.isGlobalRollbackOnly();  
    19. doCommit(status);  
    20. // Throw UnexpectedRollbackException if we have a global rollback-only  
    21. // marker but still didn't get a corresponding exception from commit.  
    22. `````````````````````  
    23. }  

    我们看到这个方法其实很明了,就是什么样的传播途径就创建什么样的transactionstatus,这个方法是在事务开始时被调用的,拿到我们之前举的例子中来看下,我们就恍然大悟了,原来,如果之前已经创建过事务,那个这个新建的transactionstauts就不应该是属于一个 newTransaction了,所以第3个参数就是false了。

     

    也就是说,在BService的方法出栈要要执行processcommit,但是由于BService的那个TransactionStatus不是一个newTransaction,所以它根本不会触发这个动作:

    代码
    1. else  if (status.isNewTransaction()) { //这个判断非常重要,下面会详细讲解这个判断的作用  
    2. if (status.isDebug()) {  
    3. logger.debug("Initiating transaction commit");  
    4. }  
    5. boolean globalRollbackOnly = status.isGlobalRollbackOnly();  
    6. doCommit(status);  
    7. }  

    也就是说在BService的方法出栈后,事务是不会提交的。这完全符合propragation_required 的模型。
    而在AService的方法出栈后,AService的方法所对应的那个TransactionStatus对象的newTransaction属性是为 true的,即它会触发上面这段代码,进行真正的事务提交。让我们回想一下AService方法入栈之前创建TransactionStatus对象的情形吧:
    newTransactionStatus(definition, transaction , true, newSynchronization, debugEnabled, null);看到第3个参数为true没有。

     

    那么事务该提交了吧,事务的提交我想使用过hibernate的人都知道怎么提交了:
    txObject.getSessionHolder().getTransaction().commit();
    从当前线程中拿到SessionHolder,再拿到开始事务的那个Transaction 对象,然后再commit事务。在没有用spring 之前,我们经常这么做。呵呵。

    好吧,我已经说到了spring 声明式事务管理的70%到80%的内容了,这70%到80%的内容看上去还是非常容易理解的,如果把这两篇文章认真看过,我相信会有所收获的,剩下的内容需要靠大家自己去挖掘了,因为另剩下的内容可是需要花费很多时间的,因为牵扯的东西实在是太多了,呵呵。最后祝大家阅读愉快,因为我的文笔实在是让大家的眼睛受罪了。 

    用 Spring 框架指定自定义隔离级别
    在 Java EE 应用程序的分布式事务中使用自定义隔离级别
    如果您正在构建一个应用程序,该应用程序要求在执行用例时在全局事务中具有自定义隔离级别,您可能已经发现这是一件困难的事,因为 Java™ Transaction API 并不提供对自定义隔离级别的支持。幸运地是,Spring 框架允许您设计在全局事务中使用自定义隔离级别的 Web 和企业应用程序,但这却不是一件容易的事。在本文中,Ricardo Olivieri 用 7 个详细的步骤演示了这一过程。
    许多 Java Enterprise Edition(EE)应用程序在执行用户请求时都会访问多处资源。例如,应用程序也许需要将一条消息放到一个面向消息的中间件队列中,并在相同的事务上下文中更新数据库行。可以通过使用应用服务器提供的 Java Transaction API(JTA)事务管理器和兼容 XA 的驱动程序连接到数据资源来实现这一任务。但应用程序的需求也许会在执行一个用例时调用全局事务中的自定义隔离级别(custom isolation level) —— JTA 事务管理器并不支持自定义隔离级别。如果正在使用 Spring 框架,出这个原因,如果为 Spring 配置文件中的全局事务指定一个自定义隔离级别,将会抛出一个异常。

    本文展示了一种能够 使用 Spring 来指定全局事务中的自定义隔离级别的方法。如果您部署应用程序的应用服务器,允许在定义数据源的位置指定作为数据库访问的隔离级别值,那么该方法都是有效的。为从本文中获益,您应该熟悉 Spring 框架并理解如何在 Spring 配置文件中定义事务代理及面向方面的 advice。在对应用服务器熟悉的前提下,也假设您熟悉 Java EE 设计模式和全局/分布式事务的概念。

    问题

    软件应用程序的需求也许做了这样的规定(这里的许多技术超出了本文讨论范围),即在执行一个给定用例的过程中,必须将相同的隔离级别使用到所有的数据访问中。需求也许还这样规定,在一个用例实现中只要访问了两项或超过两项的外部资源,该应用程序就应该使用全局事务。例如,作为用例实现的一部分,应用程序也许会查询两个不同的数据库表并将一条消息放到消息队列中。针对这个用例的设计也许需要使用 “已提交读” 隔离级别来执行两个数据库READ 操作。但也需要在执行不同的 用例时,应用程序会使用不同的隔离级别(如 “可重复读”)来执行这两个相同数据库的READ 操作。在这两个用例的执行中,应用程序执行相同的数据库操作和部分相同的代码段,但却必须使用不同的隔离级别。

    您可以分别为两个 READ 操作定义方法,并以要使用的隔离级别作为参数。这些方法的调用者会依据执行中的用例来指定相应的隔离级别。但即使这种方法会起作用,将这种逻辑包含在 Java 代码中并不是最佳方法,且维护代码会很困难。表面上看,利用 Spring 框架的功能似乎是更好的方法。Spring 是一个强大的框架,这在很大程度上是由于其为应用程序定义事务的强大功能。Spring 让您用一种清晰的方式指定事务属性,如隔离级别、传播行为和异常处理行为(例如,当抛出特定的异常时,事务是否应该自动回滚)。但缺乏对指定自定义隔离级别的支持是 JTA 是一块软肋,如下列场景所说明的那样。

     

    使用 JTA 事务管理器的新手或只对它了解一点的开发人员也许想要为服务对象(如 OrderService )(参见 什么是服务对象? )的实现定义(在 Spring 配置文件中)一个事务代理,什么是服务对象?
    在本文的上下文中,可以把服务对象 想象成负责隐藏业务组件并集中工作流的门面(facade)。它们的方法为应用程序的使用场景定义了实现。服务对象为客户机(Web UI、远程服务客户机,等等)提供粗糙的界面。Jave EE Session Facade 设计模式(参见 参考资料 )很好地适应了服务对象。(在 EJB 世界中,会话的门面是由企业会话 bean 来实现的。)如清单 1 所示:

     

    <bean id="transactionManager"
      class="org.springframework.transaction.jta.JtaTransactionManager">
        <constructor-arg>
            <ref local="jtaTransactionManager" />
        </constructor-arg>
    </bean>

    <bean id="orderService"
      class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
            <ref local="transactionManager" />
        </property>
        <property name="proxyInterfaces">
            <list>
                <value>sample.services.OrderService</value>
            </list>
        </property>
        <property name="target">
            <ref local="orderServiceTarget" />
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="save*">PROPAGATION_REQUIRED, ISOLATION_SERIALIZABLE</prop>
                <prop key="delete*">PROPAGATION_REQUIRED, ISOLATION_READ_UNCOMMITTED</prop>
                <prop key="find*">PROPAGATION_REQUIRED, ISOLATION_READ_UNCOMMITTED,
                readOnly</prop>
            </props>
        </property>
    </bean>
     
    清单 1 中定义了两个 bean。第一个 bean 的定义指定了应用程序将使用的事务管理器。正如您能看到的那样,这个 bean 依赖于另一个叫做 jtaTransactionManager 的 bean,而这个 bean 的定义依赖于您正在使用的应用服务器。例如,对于 IBM WebSphere Application Server 来说,这个 bean 的定义是这样的:
    <bean id="jtaTransactionManager"     class="org.springframework.transaction.jta.WebSphereTransactionManagerFactoryBean"
        singleton="true" /> 
     

    清单 1 中第二个 bean(称为 orderService )包含一个服务对象的事务代理定义,该服务对象实现了一个名为 OrderService 的接口。这个代理为三个方法声明了三个事务性定义:save() 、delete() 和 find() 。由于 “序列化” 和 “未提交读” 被指定为这些方法的隔离级别,那么期望这些就是在运行时获得的隔离级别是符合逻辑的。然而,请注意该代理定义包含了对 JTA 事务管理器的引用。如果用这个配置运行应用程序,您也许会十分惊诧。只要执行了 OrderService 实现的 save() 、delete() 或 find() 方法,就会出现这样一个异常:

    为什么需要一个 JTA 事务管理器?
    可能需要 JTA 事务管理器是因为当应用程序执行一个用例实现时要访问多处资源。例如,在数据库中保存一条记录时,代码也会将一条输出消息放到消息队列中。要在这种情况下保证数据的集成性和原子性,需要一个支持分布式事务的事务管理器。

    org.springframework.transaction.InvalidIsolationLevelException:
      JtaTransactionManager does not support custom isolation levels
      at org.springframework.transaction.jta.JtaTransactionManager.applyIsolationLevel(
      JtaTransactionManager.java:617)
      at org.springframework.transaction.jta.JtaTransactionManager.doJtaBegin(
      JtaTransactionManager.java:595)
      at org.springframework.transaction.jta.JtaTransactionManager.doBegin(
      JtaTransactionManager.java:559)
      at org.springframework.transaction.support.AbstractPlatformTransactionManager.
      getTransaction(AbstractPlatformTransactionManager.java:234)
      ...
     
    出现这个错误是因为 JTA 事务管理器不支持自定义隔离级别。当使用 JTA 事务管理器时,事务代理的 bean 定义会和清单 2 中的类似:

    清单 2. 使用 JTA 事务管理器的事务代理的正确定义
    <bean id="orderService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
            <ref local="transactionManager" />
        </property>
        <property name="proxyInterfaces">
            <list>
                <value>sample.services.OrderService</value>
            </list>
        </property>
        <property name="target">
            <ref local="orderServiceTarget" />
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="save*">PROPAGATION_REQUIRED, ISOLATION_DEFAULT</prop>
                <prop key="delete*">PROPAGATION_REQUIRED, ISOLATION_DEFAULT</prop>
                <prop key="find*">PROPAGATION_REQUIRED, ISOLATION_DEFAULT,readOnly
                </prop>
            </props>
        </property>
    </bean>
     
    请注意,和 清单 1 惟一的区别是,现在所有的隔离级别都被设置为 ISOLATION_DEFAULT 。如果要用 清单 2 中的事务配置执行一个应用程序,该代码会顺利运行。然而,您很可能想知道当执行 save() 、delete() 或 find() 方法时,使用哪个隔离级别。这个问题的答案取决于 “其依赖项”。隔离级别依赖于用于与数据库通信的数据源。

    图 1 中的序列图说明了在执行 save() 方法时,OrderService 实现对象和两个数据访问对象(DAO)的交互。(正如从您的经验中得出的那样,DAO 主要用于将业务逻辑从存储访问/持久性代码中分离出来。)

    图 1. OrderService 实现的 save() 方法的序列图

    Save 命令用例序列图

    从 这里 看全图。

    在执行 OrderService 实现的 save() 方法时使用的隔离级别由在 OrderDAO 和 CustomerDAO 数据访问对象中引用的数据源所声明。例如,如果 OrderDAO 被配置为从定义为具有 “未提交读” 隔离级别的数据源中获取连接,而 CustomerDAO 被配置为使用定义为具有 “序列化” 隔离级别的数据源,然后在通过 OrderDAO 对象访问数据时, save() 方法会使用 “未提交读” 隔离级别,而在通过 CustomerDAO 访问数据时,使用 “序列化” 隔离级别。但如果再回过头来看 清单 1 ,就会发现这并不是预期的目的。相反,在一个用例执行中,单个的隔离级别将被用于所有的数据访问(如 save() 、delete() 或 find() 方法),即使不同的用例执行相同的数据库操作,并且对数据访问对象执行相同的调用集。继续读下去,看看如何实现这一目标。

    解决方案
      步骤 1
    该解决方案是一个由 7 个步骤组成的过程,在此过程中利用了名为 JdbcOperations 的 Spring 接口,该接口可以在 org.springframework.jdbc.core 包中找到。正如 Spring 文档中所描述的那样,该接口能被轻易地模拟或保存。第一步是要创建一个名为 JdbcOperationsImpl 的类,该类实现 JdbcOperations 接口。该类也实现 ApplicationContextAware 接口。

    JdbcOperations 接口需要许多数据库访问操作的实现。当然,您不应该(也不应该想要)编写如此低层的代码。相反,此类的目的仅仅是作为一个代理,该代理将所有的数据访问调用转发至一个 org.springframework.jdbc.core.JdbcTemplate 实例。
    您也许会回想起之前用 Spring 编写数据访问代码的经历,可以轻易地通过将一个 javax.sql.DataSource 实例传给 JdbcTemplate 的构造函数将其实例化。请记住,本文假设您正在使用一个应用服务器,该服务器将数据源定义作为隔离级别值的占位符。为在执行用例时使用相同的隔离级别,必须在执行该用例时,使用相同的 JdbcTemplate 实例来跨越所有的数据访问对象。换言之,依赖于执行中的用例,数据访问对象需要获得对 JdbcTemplate 实例的引用,该实例与(通过其 DataSource 对象)相应的隔离级别值相关联。

    ApplicationContextAware 接口需要 setApplicationContext() 方法的一个实现,该方法将实现类的访问提供给 Spring 应用程序的上下文。正如稍后将会看到的那样,访问 Spring 的上下文是必需的,因为 JdbcOperationsImpl 使用它来获取 bean(通过其 ID)。JdbcOperationsImpl 类的 bean 定义如清单 3 所示:

    清单 3. JdbcOperationsImpl 实例的定义     
    <bean id="jdbcOperations"
      class="application.storage.JdbcOperationsImpl" singleton="true">
        <constructor-arg index="0">
            <!-- Reference to a JdbcTemplate instance with a
              "read committed" isolation level -->
            <ref local="rcJdbcTemplate" />
        </constructor-arg>
    </bean>

      步骤 2
    第二步是要确保所有的数据访问对象使用 JdbcOperationsImpl 类的一个实例来与数据库进行通信,而不是 JdbcTemplate 实例。这是很明显的,因为 JdbcTemplate 类实现 JdbcOperations 接口。不需要改变数据访问对象中一行代码;只需要改变 Spring 配置文件中每个数据访问对象的配置。例如,最初的 OrderDAO 数据访问对象的定义是这样的:

    <bean id="orderDAO"
      class="sample.dao.OrderDAOImpl" singleton="true">
        <property name="jdbcOperations">
            <ref local="jdbcTemplate" />
        </property>
    </bean>
     
    请将 OrderDAO 数据访问对象的定义改成这样:
    <bean id="orderDAO"
      class="sample.dao.OrderDAOImpl" singleton="true">
        <property name="jdbcOperations">
           <ref local="jdbcOperations" />
        </property>
    </bean>
     
    现在,JdbcOperationsImpl 类中的所有访问存储资源(如 batchUpdate() 或 execute() 方法)的方法都调用一个名为 getJdbcTemplate() 的方法,如清单 4 所示:
    清单 4. JdbcOperationsImpl 类中 getJdbcTemplate() 方法的实现
        
    private JdbcTemplate getJdbcTemplate() {
        try {
            return (JdbcTemplate) applicationContext.getBean("jdbcTemplate");
        } catch (ClassCastException e) {
            logger.warn(
              "Using default JdbcTemplate instance.", e);

            return defaultJdbcTemplate;
        }
    }
    在这段代码中,getJdbcTemplate() 方法查询 Spring 应用程序的上下文以获取相应的 JdbcTemplate 实例。请注意,使用了 jdbcTemplate 的 bean id 来查询上下文。同样,请注意如果在 getJdbcTemplate() 获取 JdbcTemplate 对象时发生错误,将返回对默认 JdbcTemplate 对象的引用。defaultJdbcTemplate 对象是使用 “已提交读” 隔离级别的 JdbcOperationsImpl 类的 JdbcTemplate 实例变量。JdbcOperationsImpl 类使用这个实例变量作为后备解决方案,以防相应的 JdbcTemplate 实例不能从应用程序的上下文中获取。(当发生这种情况时,会在日记中记一个警告。)此类的构造函数期望将默认的 JdbcTemplate 实例作为一个参数,如清单 5 所示:

    清单 5. JdbcOperationsImpl 类的构造函数    
    public JdbcOperationsImpl(JdbcTemplate defaultJdbcTemplate) {
        super();
        this.defaultJdbcTemplate = defaultJdbcTemplate;
    }
     
    从清单 6 中可见,只要要求应用程序的上下文返回标识为 jdbcTemplate 的对象,就会调用 IsolationLevelUtil 类的 getJdbcTemplate() 方法:

    清单 6. jdbcTemplate bean 的定义     
    <bean id="jdbcTemplate"  class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="targetClass">
            <value>application.services.IsolationLevelUtil</value>
        </property>
        <property name="targetMethod">
            <value>getJdbcTemplate</value>
        </property>
        <property name="singleton">
            <value>false</value>
        </property>
    </bean>

      步骤 3
    第三步是用 清单 6 显示的定义更新 Spring 配置文件,并定义 IsolationLevelUtil 类的实现,如清单 7 所示:
    清单 7. IsolationLevelUtil 类的实现
        
    public class IsolationLevelUtil {

        private static final ThreadLocal threadJdbcTemplate = new ThreadLocal();

        private IsolationLevelUtil() {
            super();
        }

        public static JdbcTemplate getJdbcTemplate() {
            JdbcTemplate jdbcTemplate = (JdbcTemplate) threadJdbcTemplate.get();
            return jdbcTemplate;
        }

        public static void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
            threadJdbcTemplate.set(jdbcTemplate);
        }
    }
    IsolationLevelUtil 类的 getJdbcTemplate() 方法返回和当前执行线程关联在一起的 JdbcTemplate 实例。名为 threadJdbcTemplate 的本地线程变量被用于保持线程和 JdbcTemplate 实例间的关联。您也许想知道为什么 JdbcOperationsImpl 类的 getJdbcTemplate() 方法没有显式地调用 IsolationLevelUtil 的 getJdbcTemplate() 方法。尽管这个方法会起作用,但更好的设计是让这两个类保持解耦。例如,如果想要实现一种不同的机制来获取和执行中的用例相应的 JdbcTemplate 实例,只需要改变 Spring 配置文件,而不是 JdbcOperationsImpl 类。

      步骤 4
    如果您正在思考哪个组件将相应的 JdbcTemplate 实例设置为 IsolationLevelUtil 类上的本地线程变量,您的思路是正确的。为此,这个值必须在线程执行的前期已经设置好了。否则,将返回 NULL 值。所以,第四步是编写一个负责设置名为 threadJdbcTemplate 的本地线程变量的组件。请将这个组件实现为一个名为 IsolationLevelAdvice 的面向方面的 advice,如清单 8 所示。这个 advice 在用例开始执行前即被应用。

    清单 8. IsolationLevelAdvice 类的实现
        
    public class IsolationLevelAdvice implements MethodInterceptor {

        private Map methodJdbcTemplateMap;

        private JdbcTemplate defaultJdbcTemplate;

        public IsolationLevelAdvice(Map methodJdbcTemplateMap,
          JdbcTemplate defaultJdbcTemplate) {

            super();
            this.defaultJdbcTemplate = defaultJdbcTemplate;
            this.methodJdbcTemplateMap = methodJdbcTemplateMap;
        }

        public Object invoke(MethodInvocation invocation) throws Exception {
            boolean set = false;
            try {
                Method method = invocation.getMethod();
                set = setThreadJdbcTemplate(method);
                Object rval = invocation.proceed();
                return rval;
            } finally {
                if (set) {
                    unsetThreadJdbcTemplate();
                }
            }
        }

        public boolean setThreadJdbcTemplate(Method method) {
            boolean set = false;
            if (IsolationLevelUtil.getJdbcTemplate() == null) {
                JdbcTemplate jdbcTemplate = null;
                String methodName = method.getName();
                Iterator methodPatterns = methodJdbcTemplateMap.keySet().iterator();
                while (methodPatterns.hasNext()) {
                    String methodPattern = (String) methodPatterns.next();
                    if (Pattern.matches(methodPattern, methodName)) {
                        jdbcTemplate = (JdbcTemplate)
                          methodJdbcTemplateMap.get(methodPattern);
                        break;
                    }
                }
                if (jdbcTemplate == null) {
                    jdbcTemplate = defaultJdbcTemplate;
                }

                IsolationLevelUtil.setJdbcTemplate(jdbcTemplate);
                set = true;
            }
            return set;
        }

        public void unsetThreadJdbcTemplate() {
            IsolationLevelUtil.setJdbcTemplate(null);
        }
    }
    在该应用程序中,每个服务对象实现都需要此类的实例。

      步骤 5
    第五步是要在 Spring 配置文件中定义这个类的一个 bean 定义,该 bean 将和 OrderService 实现类关联起来,如清单 9 所示:

    清单 9.针对 OrderService 实现的隔离 advice bean 的定义
    <bean id="orderServiceIsolationAdvice"
      class="application.services.IsolationLevelAdvice" singleton="true">
        <constructor-arg index="0">
            <map>
                <entry key="save.*">
                    <ref local="rrJdbcTemplate" />
                 </entry>
                 <entry key="delete.*">
                     <ref local="rcJdbcTemplate" />
                 </entry>
                 <entry key="find.*">
                     <ref local="rcJdbcTemplate" />
                 </entry>
            </map>
        </constructor-arg>
        <constructor-arg index="1">
            <ref local="rcJdbcTemplate" />
         </constructor-arg>
    </bean>
     
    清单 9 中 bean 的定义显示了 IsolationLevelAdvice 类的实例的构造函数将一个对象映射表作为第一个参数。这个映射表使用字符串匹配模式作为定义在 OrderService 接口中方法的名称的键。这些模式中的每一个都被映射到一个 JdbcTemplate 实例中,该实例具有必须用于用例执行的隔离级别。构造函数的第二个参数指定 JdbcTemplate 实例,使用该实例是为了防止没有 JdbcTemplate 对象被映射到已经调用的方法中。如果在 清单 8 中仔细观察这个类的实现,会看到 IsolationLevelAdvice 实例将在运行时使用反射来确定要在 OrderService 实现对象上调用哪个方法。在确定了将执行的方法的名称后,该 advice 实例查询 methodJdbcTemplateMap 实例变量(methodJdbcTemplateMap 对象是对这个类的构造函数中第一个参数的引用)来确定在执行该用例时要使用哪个 JdbcTemplate 。
      步骤 6
    第六步是要指定 IsolationLevelAdvice bean(被标识为 orderServiceIsolationAdvice )和 OrderService 实现对象间的关联。清单 10 中显示的 bean 定义通过让 Spring 容器(被 IsolationLevelAdvice 实例标识为 orderServiceIsolationAdvice )充当 OrderService 类实现的 advice 正好完成这项任务:

    清单 10. 针对 OrderService 实现的 AOP 代理 bean 的定义     
    <bean id="orderServiceTarget" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>application.services.OrderService</value>
        </property>
        <property name="interceptorNames">
            <value>orderServiceIsolationAdvice</value>
        </property>
        <property name="target">
            <ref bean="orderServiceImpl" />
        </property>
    </bean>
      步骤 7
    第七步也是最后的一步是要定义应用程序所需的 JdbcTemplate 实例。清单 11 显示了每个实例的定义。每个 JdbcTemplate 定义都有一个对不同数据源对象的引用。由于有四个隔离级别,所以需要四个数据源定义和四个 JdbcTemplate 定义。清单 11 也显示了这些数据源定义:
    清单 11. JdbcTemplate 和数据源对象的定义
    <!-- "Serializable" isolation level - JdbcTemplate -->
    <bean id="sJdbcTemplate"
      class="org.springframework.jdbc.core.JdbcTemplate" singleton="true">
        <property name="dataSource">
            <ref local="sDataSource" />
        </property>
    </bean>

    <!-- "Read repeatable" isolation level - JdbcTemplate -->
    <bean id="rrJdbcTemplate"
      class="org.springframework.jdbc.core.JdbcTemplate" singleton="true">
        <property name="dataSource">
            <ref local="rrDataSource" />
        </property>
    </bean>

    <!-- "Read committed" isolation level - JdbcTemplate -->
    <bean id="rcJdbcTemplate"
      class="org.springframework.jdbc.core.JdbcTemplate" singleton="true">
        <property name="dataSource">
            <ref local="rcDataSource" />
        </property>
    </bean>

    <!-- "Read uncommitted" isolation level - JdbcTemplate -->
    <bean id="ruJdbcTemplate"
      class="org.springframework.jdbc.core.JdbcTemplate" singleton="true">
        <property name="dataSource">
            <ref local="ruDataSource" />
        </property>
    </bean>

    <!-- "Serializable" isolation level - data source -->
    <bean id="sDataSource"
      class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:comp/env/jdbc/s_ds</value>
        </property>
    </bean>

    <!-- "Repeatable read" isolation level - data source -->
    <bean id="rrDataSource"
      class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:comp/env/jdbc/rr_ds</value>
        </property>
    </bean>

    <!-- "Read committed" isolation level - data source -->
    <bean id="rcDataSource"
      class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:comp/env/jdbc/rc_ds</value>
        </property>
    </bean>

    <!-- "Read uncommitted" isolation level - data source -->
    <bean id="ruDataSource"
      class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:comp/env/jdbc/ru_ds</value>
        </property>
    </bean>
    图 2 中的类图撷取了这些类中存在的关系,定义这些类是为了实现我所描述过的解决方案:
    图 2. 本文解决方案的类图

    类图

        文档选项
        将此页作为电子邮件发送
     
     


     
    级别: 中级

    Ricardo Olivieri (roliv@us.ibm.com ), 软件工程师, IBM

    2006 年 11 月 20 日

    如果您正在构建一个应用程序,该应用程序要求在执行用例时在全局事务中具有自定义隔离级别,您可能已经发现这是一件困难的事,因为 Java™ Transaction API 并不提供对自定义隔离级别的支持。幸运地是,Spring 框架允许您设计在全局事务中使用自定义隔离级别的 Web 和企业应用程序,但这却不是一件容易的事。在本文中,Ricardo Olivieri 用 7 个详细的步骤演示了这一过程。
    许多 Java Enterprise Edition(EE)应用程序在执行用户请求时都会访问多处资源。例如,应用程序也许需要将一条消息放到一个面向消息的中间件队列中,并在相同的事务上下文中更新数据库行。可以通过使用应用服务器提供的 Java Transaction API(JTA)事务管理器和兼容 XA 的驱动程序连接到数据资源来实现这一任务。但应用程序的需求也许会在执行一个用例时调用全局事务中的自定义隔离级别(custom isolation level) —— JTA 事务管理器并不支持自定义隔离级别。如果正在使用 Spring 框架,出这个原因,如果为 Spring 配置文件中的全局事务指定一个自定义隔离级别,将会抛出一个异常。

    本文展示了一种能够 使用 Spring 来指定全局事务中的自定义隔离级别的方法。如果您部署应用程序的应用服务器,允许在定义数据源的位置指定作为数据库访问的隔离级别值,那么该方法都是有效的。为从本文中获益,您应该熟悉 Spring 框架并理解如何在 Spring 配置文件中定义事务代理及面向方面的 advice。在对应用服务器熟悉的前提下,也假设您熟悉 Java EE 设计模式和全局/分布式事务的概念。

    问题

    软件应用程序的需求也许做了这样的规定(这里的许多技术超出了本文讨论范围),即在执行一个给定用例的过程中,必须将相同的隔离级别使用到所有的数据访问中。需求也许还这样规定,在一个用例实现中只要访问了两项或超过两项的外部资源,该应用程序就应该使用全局事务。例如,作为用例实现的一部分,应用程序也许会查询两个不同的数据库表并将一条消息放到消息队列中。针对这个用例的设计也许需要使用 “已提交读” 隔离级别来执行两个数据库 READ 操作。但也需要在执行不同的 用例时,应用程序会使用不同的隔离级别(如 “可重复读”)来执行这两个相同数据库的 READ 操作。在这两个用例的执行中,应用程序执行相同的数据库操作和部分相同的代码段,但却必须使用不同的隔离级别。

    您可以分别为两个 READ 操作定义方法,并以要使用的隔离级别作为参数。这些方法的调用者会依据执行中的用例来指定相应的隔离级别。但即使这种方法会起作用,将这种逻辑包含在 Java 代码中并不是最佳方法,且维护代码会很困难。表面上看,利用 Spring 框架的功能似乎是更好的方法。Spring 是一个强大的框架,这在很大程度上是由于其为应用程序定义事务的强大功能。Spring 让您用一种清晰的方式指定事务属性,如隔离级别、传播行为和异常处理行为(例如,当抛出特定的异常时,事务是否应该自动回滚)。但缺乏对指定自定义隔离级别的支持是 JTA 是一块软肋,如下列场景所说明的那样。

      什么是服务对象?
    在本文的上下文中,可以把服务对象 想象成负责隐藏业务组件并集中工作流的门面(facade)。它们的方法为应用程序的使用场景定义了实现。服务对象为客户机(Web UI、远程服务客户机,等等)提供粗糙的界面。Jave EE Session Facade 设计模式(参见 参考资料 )很好地适应了服务对象。(在 EJB 世界中,会话的门面是由企业会话 bean 来实现的。) 
     
    Spring 场景

    使用 JTA 事务管理器的新手或只对它了解一点的开发人员也许想要为服务对象(如 OrderService )(参见 什么是服务对象? )的实现定义(在 Spring 配置文件中)一个事务代理,如清单 1 所示:


    清单 1. 使用 JTA 事务管理器的事务代理的错误定义
        

    <bean id="transactionManager"
      class="org.springframework.transaction.jta.JtaTransactionManager">
        <constructor-arg>
            <ref local="jtaTransactionManager" />
        </constructor-arg>
    </bean>

    <bean id="orderService"
      class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
            <ref local="transactionManager" />
        </property>
        <property name="proxyInterfaces">
            <list>
                <value>sample.services.OrderService</value>
            </list>
        </property>
        <property name="target">
            <ref local="orderServiceTarget" />
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="save*">PROPAGATION_REQUIRED, ISOLATION_SERIALIZABLE</prop>
                <prop key="delete*">PROPAGATION_REQUIRED, ISOLATION_READ_UNCOMMITTED</prop>
                <prop key="find*">PROPAGATION_REQUIRED, ISOLATION_READ_UNCOMMITTED,
                readOnly</prop>
            </props>
        </property>
    </bean>
     


    清单 1 中定义了两个 bean。第一个 bean 的定义指定了应用程序将使用的事务管理器。正如您能看到的那样,这个 bean 依赖于另一个叫做 jtaTransactionManager 的 bean,而这个 bean 的定义依赖于您正在使用的应用服务器。例如,对于 IBM WebSphere Application Server 来说,这个 bean 的定义是这样的:

     
    <bean id="jtaTransactionManager"
        class="org.springframework.transaction.jta.WebSphereTransactionManagerFactoryBean"
        singleton="true" />
     

      为什么需要一个 JTA 事务管理器?
    可能需要 JTA 事务管理器是因为当应用程序执行一个用例实现时要访问多处资源。例如,在数据库中保存一条记录时,代码也会将一条输出消息放到消息队列中。要在这种情况下保证数据的集成性和原子性,需要一个支持分布式事务的事务管理器。 
     
    清单 1 中第二个 bean(称为 orderService )包含一个服务对象的事务代理定义,该服务对象实现了一个名为 OrderService 的接口。这个代理为三个方法声明了三个事务性定义:save() 、delete() 和 find() 。由于 “序列化” 和 “未提交读” 被指定为这些方法的隔离级别,那么期望这些就是在运行时获得的隔离级别是符合逻辑的。然而,请注意该代理定义包含了对 JTA 事务管理器的引用。如果用这个配置运行应用程序,您也许会十分惊诧。只要执行了 OrderService 实现的 save() 、delete() 或 find() 方法,就会出现这样一个异常:

     
    org.springframework.transaction.InvalidIsolationLevelException:
      JtaTransactionManager does not support custom isolation levels
      at org.springframework.transaction.jta.JtaTransactionManager.applyIsolationLevel(
      JtaTransactionManager.java:617)
      at org.springframework.transaction.jta.JtaTransactionManager.doJtaBegin(
      JtaTransactionManager.java:595)
      at org.springframework.transaction.jta.JtaTransactionManager.doBegin(
      JtaTransactionManager.java:559)
      at org.springframework.transaction.support.AbstractPlatformTransactionManager.
      getTransaction(AbstractPlatformTransactionManager.java:234)
      ...
     


    出现这个错误是因为 JTA 事务管理器不支持自定义隔离级别。当使用 JTA 事务管理器时,事务代理的 bean 定义会和清单 2 中的类似:


    清单 2. 使用 JTA 事务管理器的事务代理的正确定义
        
    <bean id="orderService"
      class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
            <ref local="transactionManager" />
        </property>
        <property name="proxyInterfaces">
            <list>
                <value>sample.services.OrderService</value>
            </list>
        </property>
        <property name="target">
            <ref local="orderServiceTarget" />
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="save*">PROPAGATION_REQUIRED, ISOLATION_DEFAULT</prop>
                <prop key="delete*">PROPAGATION_REQUIRED, ISOLATION_DEFAULT</prop>
                <prop key="find*">PROPAGATION_REQUIRED, ISOLATION_DEFAULT,readOnly
                </prop>
            </props>
        </property>
    </bean>
     


    请注意,和 清单 1 惟一的区别是,现在所有的隔离级别都被设置为 ISOLATION_DEFAULT 。如果要用 清单 2 中的事务配置执行一个应用程序,该代码会顺利运行。然而,您很可能想知道当执行 save() 、delete() 或 find() 方法时,使用哪个隔离级别。这个问题的答案取决于 “其依赖项”。隔离级别依赖于用于与数据库通信的数据源。

    图 1 中的序列图说明了在执行 save() 方法时,OrderService 实现对象和两个数据访问对象(DAO)的交互。(正如从您的经验中得出的那样,DAO 主要用于将业务逻辑从存储访问/持久性代码中分离出来。)


    图 1. OrderService 实现的 save() 方法的序列图
     

    从 这里 看全图。

    在执行 OrderService 实现的 save() 方法时使用的隔离级别由在 OrderDAO 和 CustomerDAO 数据访问对象中引用的数据源所声明。例如,如果 OrderDAO 被配置为从定义为具有 “未提交读” 隔离级别的数据源中获取连接,而 CustomerDAO 被配置为使用定义为具有 “序列化” 隔离级别的数据源,然后在通过 OrderDAO 对象访问数据时, save() 方法会使用 “未提交读” 隔离级别,而在通过 CustomerDAO 访问数据时,使用 “序列化” 隔离级别。但如果再回过头来看 清单 1 ,就会发现这并不是预期的目的。相反,在一个用例执行中,单个的隔离级别将被用于所有的数据访问(如 save() 、delete() 或 find() 方法),即使不同的用例执行相同的数据库操作,并且对数据访问对象执行相同的调用集。继续读下去,看看如何实现这一目标。


     
     
     
     
     回页首 
     

     

    解决方案

      步骤 1
     
     
    该解决方案是一个由 7 个步骤组成的过程,在此过程中利用了名为 JdbcOperations 的 Spring 接口,该接口可以在 org.springframework.jdbc.core 包中找到。正如 Spring 文档中所描述的那样,该接口能被轻易地模拟或保存。第一步是要创建一个名为 JdbcOperationsImpl 的类,该类实现 JdbcOperations 接口。该类也实现 ApplicationContextAware 接口。

    JdbcOperations 接口需要许多数据库访问操作的实现。当然,您不应该(也不应该想要)编写如此低层的代码。相反,此类的目的仅仅是作为一个代理,该代理将所有的数据访问调用转发至一个 org.springframework.jdbc.core.JdbcTemplate 实例。

    您也许会回想起之前用 Spring 编写数据访问代码的经历,可以轻易地通过将一个 javax.sql.DataSource 实例传给 JdbcTemplate 的构造函数将其实例化。请记住,本文假设您正在使用一个应用服务器,该服务器将数据源定义作为隔离级别值的占位符。为在执行用例时使用相同的隔离级别,必须在执行该用例时,使用相同的 JdbcTemplate 实例来跨越所有的数据访问对象。换言之,依赖于执行中的用例,数据访问对象需要获得对 JdbcTemplate 实例的引用,该实例与(通过其 DataSource 对象)相应的隔离级别值相关联。

    ApplicationContextAware 接口需要 setApplicationContext() 方法的一个实现,该方法将实现类的访问提供给 Spring 应用程序的上下文。正如稍后将会看到的那样,访问 Spring 的上下文是必需的,因为 JdbcOperationsImpl 使用它来获取 bean(通过其 ID)。JdbcOperationsImpl 类的 bean 定义如清单 3 所示:


    清单 3. JdbcOperationsImpl 实例的定义
        
    <bean id="jdbcOperations"
      class="application.storage.JdbcOperationsImpl" singleton="true">
        <constructor-arg index="0">
            <!-- Reference to a JdbcTemplate instance with a
              "read committed" isolation level -->
            <ref local="rcJdbcTemplate" />
        </constructor-arg>
    </bean>
     

      步骤 2
     
     
    第二步是要确保所有的数据访问对象使用 JdbcOperationsImpl 类的一个实例来与数据库进行通信,而不是 JdbcTemplate 实例。这是很明显的,因为 JdbcTemplate 类实现 JdbcOperations 接口。不需要改变数据访问对象中一行代码;只需要改变 Spring 配置文件中每个数据访问对象的配置。例如,最初的 OrderDAO 数据访问对象的定义是这样的:

    <bean id="orderDAO"
      class="sample.dao.OrderDAOImpl" singleton="true">
        <property name="jdbcOperations">
            <ref local="jdbcTemplate" />
        </property>
    </bean>
     


    请将 OrderDAO 数据访问对象的定义改成这样:

    <bean id="orderDAO"
      class="sample.dao.OrderDAOImpl" singleton="true">
        <property name="jdbcOperations">
           <ref local="jdbcOperations" />
        </property>
    </bean>
     


    现在,JdbcOperationsImpl 类中的所有访问存储资源(如 batchUpdate() 或 execute() 方法)的方法都调用一个名为 getJdbcTemplate() 的方法,如清单 4 所示:


    清单 4. JdbcOperationsImpl 类中 getJdbcTemplate() 方法的实现
        
    private JdbcTemplate getJdbcTemplate() {
        try {
            return (JdbcTemplate) applicationContext.getBean("jdbcTemplate");
        } catch (ClassCastException e) {
            logger.warn(
              "Using default JdbcTemplate instance.", e);

            return defaultJdbcTemplate;
        }
    }
     


    在这段代码中,getJdbcTemplate() 方法查询 Spring 应用程序的上下文以获取相应的 JdbcTemplate 实例。请注意,使用了 jdbcTemplate 的 bean id 来查询上下文。同样,请注意如果在 getJdbcTemplate() 获取 JdbcTemplate 对象时发生错误,将返回对默认 JdbcTemplate 对象的引用。defaultJdbcTemplate 对象是使用 “已提交读” 隔离级别的 JdbcOperationsImpl 类的 JdbcTemplate 实例变量。JdbcOperationsImpl 类使用这个实例变量作为后备解决方案,以防相应的 JdbcTemplate 实例不能从应用程序的上下文中获取。(当发生这种情况时,会在日记中记一个警告。)此类的构造函数期望将默认的 JdbcTemplate 实例作为一个参数,如清单 5 所示:


    清单 5. JdbcOperationsImpl 类的构造函数
        
    public JdbcOperationsImpl(JdbcTemplate defaultJdbcTemplate) {
        super();
        this.defaultJdbcTemplate = defaultJdbcTemplate;
    }
     


    从清单 6 中可见,只要要求应用程序的上下文返回标识为 jdbcTemplate 的对象,就会调用 IsolationLevelUtil 类的 getJdbcTemplate() 方法:


    清单 6. jdbcTemplate bean 的定义
        
    <bean id="jdbcTemplate"
      class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="targetClass">
            <value>application.services.IsolationLevelUtil</value>
        </property>
        <property name="targetMethod">
            <value>getJdbcTemplate</value>
        </property>
        <property name="singleton">
            <value>false</value>
        </property>
    </bean>
     

      步骤 3
     
     
    第三步是用 清单 6 显示的定义更新 Spring 配置文件,并定义 IsolationLevelUtil 类的实现,如清单 7 所示:


    清单 7. IsolationLevelUtil 类的实现
        
    public class IsolationLevelUtil {

        private static final ThreadLocal threadJdbcTemplate = new ThreadLocal();

        private IsolationLevelUtil() {
            super();
        }

        public static JdbcTemplate getJdbcTemplate() {
            JdbcTemplate jdbcTemplate = (JdbcTemplate) threadJdbcTemplate.get();
            return jdbcTemplate;
        }

        public static void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
            threadJdbcTemplate.set(jdbcTemplate);
        }
    }
     


    IsolationLevelUtil 类的 getJdbcTemplate() 方法返回和当前执行线程关联在一起的 JdbcTemplate 实例。名为 threadJdbcTemplate 的本地线程变量被用于保持线程和 JdbcTemplate 实例间的关联。您也许想知道为什么 JdbcOperationsImpl 类的 getJdbcTemplate() 方法没有显式地调用 IsolationLevelUtil 的 getJdbcTemplate() 方法。尽管这个方法会起作用,但更好的设计是让这两个类保持解耦。例如,如果想要实现一种不同的机制来获取和执行中的用例相应的 JdbcTemplate 实例,只需要改变 Spring 配置文件,而不是 JdbcOperationsImpl 类。

      步骤 4
     
     
    如果您正在思考哪个组件将相应的 JdbcTemplate 实例设置为 IsolationLevelUtil 类上的本地线程变量,您的思路是正确的。为此,这个值必须在线程执行的前期已经设置好了。否则,将返回 NULL 值。所以,第四步是编写一个负责设置名为 threadJdbcTemplate 的本地线程变量的组件。请将这个组件实现为一个名为 IsolationLevelAdvice 的面向方面的 advice,如清单 8 所示。这个 advice 在用例开始执行前即被应用。


    清单 8. IsolationLevelAdvice 类的实现
        
    public class IsolationLevelAdvice implements MethodInterceptor {

        private Map methodJdbcTemplateMap;

        private JdbcTemplate defaultJdbcTemplate;

        public IsolationLevelAdvice(Map methodJdbcTemplateMap,
          JdbcTemplate defaultJdbcTemplate) {

            super();
            this.defaultJdbcTemplate = defaultJdbcTemplate;
            this.methodJdbcTemplateMap = methodJdbcTemplateMap;
        }

        public Object invoke(MethodInvocation invocation) throws Exception {
            boolean set = false;
            try {
                Method method = invocation.getMethod();
                set = setThreadJdbcTemplate(method);
                Object rval = invocation.proceed();
                return rval;
            } finally {
                if (set) {
                    unsetThreadJdbcTemplate();
                }
            }
        }

        public boolean setThreadJdbcTemplate(Method method) {

            boolean set = false;
            if (IsolationLevelUtil.getJdbcTemplate() == null) {
                JdbcTemplate jdbcTemplate = null;
                String methodName = method.getName();
                Iterator methodPatterns = methodJdbcTemplateMap.keySet().iterator();
                while (methodPatterns.hasNext()) {
                    String methodPattern = (String) methodPatterns.next();
                    if (Pattern.matches(methodPattern, methodName)) {
                        jdbcTemplate = (JdbcTemplate)
                          methodJdbcTemplateMap.get(methodPattern);
                        break;
                    }
                }
                if (jdbcTemplate == null) {
                    jdbcTemplate = defaultJdbcTemplate;
                }

                IsolationLevelUtil.setJdbcTemplate(jdbcTemplate);
                set = true;
            }
            return set;
        }

        public void unsetThreadJdbcTemplate() {
            IsolationLevelUtil.setJdbcTemplate(null);
        }
    }
     


    在该应用程序中,每个服务对象实现都需要此类的实例。

      步骤 5
     
     
    第五步是要在 Spring 配置文件中定义这个类的一个 bean 定义,该 bean 将和 OrderService 实现类关联起来,如清单 9 所示:


    清单 9.针对 OrderService 实现的隔离 advice bean 的定义
        
    <bean id="orderServiceIsolationAdvice"
      class="application.services.IsolationLevelAdvice" singleton="true">
        <constructor-arg index="0">
            <map>
                <entry key="save.*">
                    <ref local="rrJdbcTemplate" />
                 </entry>
                 <entry key="delete.*">
                     <ref local="rcJdbcTemplate" />
                 </entry>
                 <entry key="find.*">
                     <ref local="rcJdbcTemplate" />
                 </entry>
            </map>
        </constructor-arg>
        <constructor-arg index="1">
            <ref local="rcJdbcTemplate" />
         </constructor-arg>
    </bean>
     


    清单 9 中 bean 的定义显示了 IsolationLevelAdvice 类的实例的构造函数将一个对象映射表作为第一个参数。这个映射表使用字符串匹配模式作为定义在 OrderService 接口中方法的名称的键。这些模式中的每一个都被映射到一个 JdbcTemplate 实例中,该实例具有必须用于用例执行的隔离级别。构造函数的第二个参数指定 JdbcTemplate 实例,使用该实例是为了防止没有 JdbcTemplate 对象被映射到已经调用的方法中。如果在 清单 8 中仔细观察这个类的实现,会看到 IsolationLevelAdvice 实例将在运行时使用反射来确定要在 OrderService 实现对象上调用哪个方法。在确定了将执行的方法的名称后,该 advice 实例查询 methodJdbcTemplateMap 实例变量(methodJdbcTemplateMap 对象是对这个类的构造函数中第一个参数的引用)来确定在执行该用例时要使用哪个 JdbcTemplate 。

      步骤 6
     
     
    第六步是要指定 IsolationLevelAdvice bean(被标识为 orderServiceIsolationAdvice )和 OrderService 实现对象间的关联。清单 10 中显示的 bean 定义通过让 Spring 容器(被 IsolationLevelAdvice 实例标识为 orderServiceIsolationAdvice )充当 OrderService 类实现的 advice 正好完成这项任务:


    清单 10. 针对 OrderService 实现的 AOP 代理 bean 的定义
        
    <bean id="orderServiceTarget" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>application.services.OrderService</value>
        </property>
        <property name="interceptorNames">
            <value>orderServiceIsolationAdvice</value>
        </property>
        <property name="target">
            <ref bean="orderServiceImpl" />
        </property>
    </bean>
     

      步骤 7
     
     
    第七步也是最后的一步是要定义应用程序所需的 JdbcTemplate 实例。清单 11 显示了每个实例的定义。每个 JdbcTemplate 定义都有一个对不同数据源对象的引用。由于有四个隔离级别,所以需要四个数据源定义和四个 JdbcTemplate 定义。清单 11 也显示了这些数据源定义:


    清单 11. JdbcTemplate 和数据源对象的定义
        
    <!-- "Serializable" isolation level - JdbcTemplate -->
    <bean id="sJdbcTemplate"
      class="org.springframework.jdbc.core.JdbcTemplate" singleton="true">
        <property name="dataSource">
            <ref local="sDataSource" />
        </property>
    </bean>

    <!-- "Read repeatable" isolation level - JdbcTemplate -->
    <bean id="rrJdbcTemplate"
      class="org.springframework.jdbc.core.JdbcTemplate" singleton="true">
        <property name="dataSource">
            <ref local="rrDataSource" />
        </property>
    </bean>

    <!-- "Read committed" isolation level - JdbcTemplate -->
    <bean id="rcJdbcTemplate"
      class="org.springframework.jdbc.core.JdbcTemplate" singleton="true">
        <property name="dataSource">
            <ref local="rcDataSource" />
        </property>
    </bean>

    <!-- "Read uncommitted" isolation level - JdbcTemplate -->
    <bean id="ruJdbcTemplate"
      class="org.springframework.jdbc.core.JdbcTemplate" singleton="true">
        <property name="dataSource">
            <ref local="ruDataSource" />
        </property>
    </bean>

    <!-- "Serializable" isolation level - data source -->
    <bean id="sDataSource"
      class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:comp/env/jdbc/s_ds</value>
        </property>
    </bean>

    <!-- "Repeatable read" isolation level - data source -->
    <bean id="rrDataSource"
      class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:comp/env/jdbc/rr_ds</value>
        </property>
    </bean>

    <!-- "Read committed" isolation level - data source -->
    <bean id="rcDataSource"
      class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:comp/env/jdbc/rc_ds</value>
        </property>
    </bean>

    <!-- "Read uncommitted" isolation level - data source -->
    <bean id="ruDataSource"
      class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:comp/env/jdbc/ru_ds</value>
        </property>
    </bean>
     


    图 2 中的类图撷取了这些类中存在的关系,定义这些类是为了实现我所描述过的解决方案:


    图 2. 本文解决方案的类图
     

    在 这里 查看全图。

    在这个类图中显示的大多数关系并没有定义在 Java 源代码中,而是在 Spring 配置文件中。(这对 Spring 用户来说并不奇怪。)同样,如果将我探讨过的 Spring bean 的定义和该类图中的实体作比较,很容易看出,在 图 2 中被标识为 orderServiceIsolationAdvice 、rrTemplate 和 rcTemplate 的类在本质上并不是 Java 类。这三个类中的每个类都有一个 Spring bean 的定义(而不是 Java 类文件)。为在类图中传达这个信息,我使用了在 IsolationLevelAdvice 类和 orderServiceIsolationAdvice 间以及在 JdbcTemplate 类和 rrTemplate 及 rcTemplate 间的 “绑定关系”。orderServiceIsolationAdvice 、rrTemplate 和 rcTemplate 实体只不过是通过将其模板类的参数和实际值绑定起来从而实例化其相应的 “模板类” 的具体对象。

    下载 这些类的完整的源代码,您需要这些类来实现我在本文中演示的解决方案。
     

    结束语

    如果在技术需求中声明了在执行使用分布式事务的用例过程中应该使用相同的隔离级别,尽管 JTA 事务管理器不支持自定义隔离级别,但您的应用程序能够满足此项需求。本文提供了实现此目标的一种方法,即使用 Spring 的依赖项-注入功能来保持类的解耦。第一眼看去,该实现似乎有点复杂,但您会意识到它很直白且相当简单。在执行用例时的任何时刻访问数据库,它让您在一种可配置的方式下使用相同的隔离级别。处理业务逻辑和持久性逻辑的 Java 代码并未改变。相反,使用在 Spring 配置文件中的设置,所有 “神奇的事情” 都在运行时发生。这就是该解决方案的设计中的主要优点之一:使实现应用程序业务逻辑和持久性逻辑的类从确保在执行用例过程中使用的相同隔离级别的类和组件中解耦出来。

     

     

     ssh中处理事务的几种方式

    事务,就是保证一系列业务逻辑全部执行或者全部不执行,在开发中,事务是怎么控制的呢?

           方案一、使用hibernate的OpenSession()。这种方式需要在业务边界创建session,并将session作为参数传递到Dao层,以此来保证多个业务逻辑之间使用的是同一个session。

             添加用户的同时要完成addLog()和addUser()两个操作:

               1. LogManagerImpl类中的添加日志

              public void addLog(Log log,Session session){

                      session.save(log,session);

               }

               2. UserManagerImpl类中完成所有业务逻辑

    [java] view plaincopyprint?//openSession()创建session    
    SessionFactory factory= new Configuration().configure(); 
     
    Session session=factory.openSession();           
    //开启事务           
    session.beginTransaction();          
    //执行业务逻辑1. 保存user      
    session.save(user);               
    //Log和LogManagerImpl的创建由IoC控制                        
    log.setTime(new Date());     
    log.setType("操作日志");               
    //执行业务逻辑2. 保存log,同时传递session           
    logManager.addLog(log,session);                  
    session.getTransaction().commit();          
    //使用openSession,当最后一个业务逻辑完成后必须关闭session           
    session.close(); 

        //openSession()创建session 
        SessionFactory factory= new Configuration().configure();
     
        Session session=factory.openSession();         
        //开启事务        
        session.beginTransaction();        
        //执行业务逻辑1. 保存user   
        session.save(user);             
        //Log和LogManagerImpl的创建由IoC控制                     
        log.setTime(new Date());   
        log.setType("操作日志");             
        //执行业务逻辑2. 保存log,同时传递session        
        logManager.addLog(log,session);                
        session.getTransaction().commit();        
        //使用openSession,当最后一个业务逻辑完成后必须关闭session        
        session.close();

      方法二、 使用Hibernate的getCurrentSession(),currentSession和openSession的区别在于,使用currentSession使用完毕后不用手动关闭session。currentSession相当于将session放到一个ThreadLocal中。

             1.  LogManagerImpl类

              pubic  void addLog(Log log){

                        //可以通过getCurrentSession()创建Session,不必使用传递的session

                       Session session= factory.getCurrentSession()

                       session.save(log);

                }

               2. UserManagerImpl类中完成所有业务逻辑     

    [java] view plaincopyprint? //openSession()创建session        
    SessionFactory factory= new Configuration().configure();       
    Session session=factory.getCurrentSession();               
    //开启事务               
    session.beginTransaction();              
    //执行业务逻辑1. 保存user          
    session.save(user);                   
    //Log和LogManagerImpl的创建由IoC控制                            
    log.setTime(new Date());         
    log.setType("操作日志");                   
    //执行业务逻辑2. 保存log              
    logManager.addLog(log);                      
    session.getTransaction().commit();              
    //使用currentSession,当最后一个业务逻辑完成后不用关闭session              

           //openSession()创建session     
          SessionFactory factory= new Configuration().configure();     
          Session session=factory.getCurrentSession();             
          //开启事务            
          session.beginTransaction();            
          //执行业务逻辑1. 保存user       
          session.save(user);                 
          //Log和LogManagerImpl的创建由IoC控制                         
          log.setTime(new Date());       
          log.setType("操作日志");                 
          //执行业务逻辑2. 保存log           
          logManager.addLog(log);                    
          session.getTransaction().commit();            
          //使用currentSession,当最后一个业务逻辑完成后不用关闭session           
                 3. 使用currentSession,需要在hibernate.cfg.xml配置文件中开启事务

          <property name="hibernate.current_session_context_class">thread</property>

     

    方案三、将hibernate和spring集成,使用spring框架的声明式事务。
           使用spring的声明式事务,不再需要自动创建sessionFactory和Session,不再需要手动控制事务的开启和关闭。

           使用spring声明式事务的几个步骤:

           1. applicationContext.xml中进行配置

    [html] view plaincopyprint?<!-- 配置事务管理器 -->     
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">         
         <property name="sessionFactory">             
              <ref bean="sessionFactory"/>  <!-- transactionManager的SetSessionFactory()方法的参数为SessionFactory -->                     </property>     
    </bean>         
     
    <!-- 那些类那些方法使用事务 -->     
    <aop:config>         
        <aop:pointcut id="allManagerMethod" expression="execution(* com.bjpowernode.usermgr.manager.*.*(..))"/>         
        <aop:advisor pointcut-ref="allManagerMethod" advice-ref="txAdvice"/>     
    </aop:config>          
        
     
    <!-- 事务的传播特性 -->       
    <tx:advice id="txAdvice" transaction-manager="transactionManager">         
        <tx:attributes>             
            <tx:method name="add*" propagation="REQUIRED"/>             
            <tx:method name="del*" propagation="REQUIRED"/>             
            <tx:method name="modify*" propagation="REQUIRED"/>            
            <tx:method name="*" propagation="REQUIRED" read-only="true"/>         
        </tx:attributes>     
    </tx:advice> 

         <!-- 配置事务管理器 -->   
         <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">       
              <property name="sessionFactory">           
                   <ref bean="sessionFactory"/>  <!-- transactionManager的SetSessionFactory()方法的参数为SessionFactory -->                     </property>   
         </bean>       

         <!-- 那些类那些方法使用事务 -->   
         <aop:config>       
             <aop:pointcut id="allManagerMethod" expression="execution(* com.bjpowernode.usermgr.manager.*.*(..))"/>       
             <aop:advisor pointcut-ref="allManagerMethod" advice-ref="txAdvice"/>   
         </aop:config>        
           

         <!-- 事务的传播特性 -->     
         <tx:advice id="txAdvice" transaction-manager="transactionManager">       
             <tx:attributes>           
                 <tx:method name="add*" propagation="REQUIRED"/>           
                 <tx:method name="del*" propagation="REQUIRED"/>           
                 <tx:method name="modify*" propagation="REQUIRED"/>          
                 <tx:method name="*" propagation="REQUIRED" read-only="true"/>       
             </tx:attributes>   
         </tx:advice>[html] view plaincopyprint? 2.UserManagerImpl类继承HibernateDaoSupport 

     2.UserManagerImpl类继承HibernateDaoSupport[java] view plaincopyprint?public class UserManagerImpl extends HibernateDaoSupport{    
         public void addUser(User user)      
         throws Exception {         
         this.getHibernateTemplate().save(user);         
         log.setType("操作日志");         
         log.setTime(new Date());         
         log.setDetail("XXX");                  
         logManager.addLog(log);                  
         throw new Exception();     

        public class UserManagerImpl extends HibernateDaoSupport{  
             public void addUser(User user)    
             throws Exception {       
             this.getHibernateTemplate().save(user);       
             log.setType("操作日志");       
             log.setTime(new Date());       
             log.setDetail("XXX");                
             logManager.addLog(log);                
             throw new Exception();   
        }

     


     

     

     

    • 0
      点赞
    • 1
      收藏
      觉得还不错? 一键收藏
    • 0
      评论
    随着信息产业的不断壮大,对软件的需求也越来越多,推动了软件开发行业的迅猛发展,团体开发软件已经是非常重要的软件开发手段,为了加强团体协作的高效性和对软件开发的同步和准确性,实现对软件开发过程的有效的管理和对开发事务的有效及时的处理,就需要一款适合软件开发团队协作使用的事务跟踪和管理系统。 系统主要基于.NET Framework的体系结构,应用ASP.NET开发技术以及C#语言和ADO.NET,实现了基于ASP.NET的事务跟踪系统,首先分析了团体开发软件时的协作中碰到的同步性、时效性等问题,分析并获取了本事务跟踪系统的原始需求,在设计系统时采用了表现层USL、业务逻辑层BLL、数据访问层DAL以及数据访问层接口IDAL的三层模型,另外采用了Log4net为系统的管理并记录日志。实现了对事务的及时跟踪,使得对信息的管理更加及时、高效,提高了工作效率,该系统的主要功能的功能有人员注册、密码修改及找回,项目的创建、人员配置、人员锁定解锁,任务的创建、状态修改、任务的搜索、关注,邮件提醒,以及后台线程对数据库的操作实现历史任务信息的自动存档功能。 系统布署上线以后运行良好,经过对使用者的使用调查,加入了任务留言时邮件提醒、新增了对任务类别的管理及搜索,使得系统有更好的用户体验。 关键词:ASP.NET,ADO.NET,C#,Log4net,IDAL,事务跟踪
    Spring Web Services 是 Spring 框架中的一个模块,它提供了基于 SOAP 和 WSDL 的 Web 服务开发框架。使用 Spring Web Services,开发人员可以轻松地创建和发布 Web 服务,同时也可以使用现有的 WSDL 和 XSD 文件来生成 Web 服务点和客户代码。 Spring Web Services 的主要特性包括: 1. 基于 Spring 框架和 Spring MVC 的 Web 服务开发框架。 2. 支持 SOAP 和 WSDL 规范,可以通过 WSDL 和 XSD 文件来生成 Web 服务点和客户代码。 3. 支持多种 Web 服务协议,包括 SOAP 1.1、SOAP 1.2、XML-RPC 和 HTTP GET/POST。 4. 支持多种消息格式,包括 XML、JSON 和二进制。 5. 支持 Web 服务安全,包括基于 SOAP 的安全性和 HTTPS 等协议级别的安全性。 使用 Spring Web Services 进行 Web 服务开发的过程如下: 1. 定义 Web 服务的接口和数据模型,可以使用 JAXB、XMLBeans 或 Codehaus XFire 等工具来生成 Java 类。 2. 定义 Web 服务的 WSDL 和 XSD 文件,可以使用 Eclipse WTP、Apache Axis 或 Codehaus XFire 等工具来生成。 3. 创建 Spring Web Services 应用程序,配置 Web 服务点、数据源、事务管理器等组件。 4. 在 Web 服务点中实现 Web 服务接口,包括请求处理和响应处理等逻辑。 5. 部署和发布 Web 服务,可以使用 Tomcat、Jetty 或 JBoss 等 Web 容器。 需要注意的是,在使用 Spring Web Services 进行 Web 服务开发时,需要考虑 Web 服务的安全性、性能和可扩展性等问题。同时,也需要遵循 SOAP 和 WSDL 规范,保证 Web 服务的互操作性和兼容性。

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值