摘要
spring框架是一个流行的基于轻量级控制反转容器的java/j2ee应用框架,尤其在数据访问和事务管理方面的能力是众所周知的。spring的声明性事务分离可以应用到任何pojo目标对象,并且包含所有ejb基于容器管理事务中的已声明事务。后台的事务管理器支持简单的基于jdbc的事务和全功能的基于jta的j2ee事务。
这篇文章详细的讨论了spring的事务管理特性。重点是如何在使用jta作为后台事务策略的基础上让pojo利用spring的声明性事务,这也显示了spring的事务服务可以无缝地与j2ee服务器(如bea weblogic server的事务协调器)的事务协调器进行交互,作为ejb cmt传统事务分离方式的一个替代者。
pojo的声明性事务
作为spring声明性事务分离方式的样例,让我们来看一下spring的样例应用petclinic的中心服务外观中的配置:
清单1:
<bean id="datasource"
class="org.springframework.jndi.jndiobjectfactorybean">
<property name="jndiname">
<value>java:comp/env/jdbc/petclinic</value>
</property>
</bean>
<bean id="transactionmanager"
class="org.springframework.transaction.jta.jtatransactionmanager"/>
<bean id="clinictarget"
class="org.springframework.samples.petclinic.jdbc.jdbcclinic">
<property name="datasource"><ref bean="datasource"/></property>
</bean>
<bean id="clinic"
class="org.springframework.transaction.interceptor.transactionproxyfactorybean">
<property name="transactionmanager"><ref bean="transactionmanager"/></property>
<property name="target"><ref bean="clinictarget"/></property>
<property name="transactionattributes">
<props>
<prop key="load*">propagation_required,readonly</prop>
<prop key="store*">propagation_required</prop>
</props>
</property>
</bean>
他遵循spring的标准xmlbean定义格式。定义了:
1、一个datasource引用,指向一个jndi位置―在j2ee服务器管理下这将从jndi环境中获取特定的datasource。
2、一个应用服务实现―这是一个pojo,封装了业务和数据访问逻辑。在这里实现了应用中的clinic服务接口。
3、一个应用服务的事务代理―这个代理为目标服务定义了事务属性,匹配特定的方法名模式并为之创建相应的事务。在实际的事务管理中,代理指向一个platformtransactionmanager实现。
注意:除了显式的代理定义,spring还支持自动代理机制和通过commons attributes或j2se 5.0注解实现源程序级的元数据使用。这些可选方法的讨论超过了本文的范围。可以参考spring的文档来了解相关细节。
业务接口和业务实现是特定于应用的并且不需要关心spring或者spring的事务管理。普通java对象可以作为服务的目标对象,而且任何普通java接口可以作为服务的接口。下面是一个clinic接口的示例:
清单2:
public interface clinic {
pet loadpet(int id);
void storepet(pet pet);
...
}
这个接口的实现如下显示,假设他使用jdbc来执行必要的数据访问。他通过bean属性的设置方法来获取jdbc的datasource;这与上面的配置中的datasource属性定义相对应。
清单3:
public class jdbcclinic implements clinic {
private datasource datasource;
public void setdatasource(datasource datasource) {
this.datasource = datasource;
}
public pet loadpet(int id) {
try {
connection con = this.datasource.getconnection();
...
}
catch (sqlexception ex) {
...
}
}
public void storepet(pet pet) {
try {
connection con = this.datasource.getconnection();
...
}
catch (sqlexception ex) {
...
}
}
...
}
如你所见,代码相当直接。我们使用一个简单的java对象,而事务管理由事务代理来处理,这个我们会在下面讨论。
注意在petclinic示例应用中实际的基于jdbc的clinic实现利用了spring的jdbc支持类来避免直接使用jdbc的api。虽然spring的事务管理也可以与普通的基于jdbc实现一起工作,就向上面的示例。
定义事务代理
除了jdbcclinic实例以外,配置中也定义了一个事务代理。如果愿意这个代理所暴露的实际接口也可以显式定义。默认情况下,所有由目标对象实现的接口都暴露出来,在这个例子中就是应用的clinic服务接口。
从客户端的观点来看,"clinic" bean只是这个应用的clinic接口的实现。客户端不需要知道这会被一个事务代理所处理。这就是接口的能力:一个直接的目标对象的引用可以容易的被一个实现相同接口的代理所代替―在这儿就是一个隐式创建事务的代理。
代理的具体事务行为会由为根据特定的方法或方法命名模式而定义的事务属性来驱动,就像下面的例子所示:
清单3:
<prop key="load*">propagation_required,readonly</prop>
<prop key="store*">propagation_required</prop>
key属性决定代理将为方法提供什么样的事务行为。这个属性的最重要部分就是事务传播行为。下面是一些可选的属性值:
1、propagation_required --支持当前的事务,如果不存在就创建一个新的。这是最常用的选择。
2、propagation_supports --支持当前的事务,如果不存在就不使用事务。
3、propagation_mandatory --支持当前的事务,如果不存在就抛出异常。
4、propagation_requires_new --创建一个新的事务,并暂停当前的事务(如果存在)。
5、propagation_not_supported --不使用事务,并暂停当前的事务(如果存在)。
6、propagation_never --不使用事务,如果当前存在事务就抛出异常。
7、propagation_nested --如果当前存在事务就作为嵌入事务执行,否则与propagation_required类似。
前6个事务策略与ejb的cmt类似,而且使用相同的常量名,因此对ejb开发人员来说是很亲切的。第7个策略propagation_nested是spring提供的一个变体:他需要事务管理器(如datasourcetransactionmanager)提供类似jdbc3.0那样的保存点api来嵌套事务行为或者通过
jta支持嵌套事务。
事务属性中的readonly标识指示相应的事务应该作为一个只读事务来优化。这是一个优化提示:一些事务策略在这种情况下可以得到很好的性能优化,如使用orm工具如hibernate或toplink时避免脏数据检查(“flush”尝试)。
在事务属性中还有一个“timeout”选项来定义事务的超时秒数。在jta中,这个属性会简单地传递给j2ee服务器的事务协调器并被正确地解释。
使用事务代理
在运行时,客户端会取得一个“clinic”引用并转换为clinic接口,然后调用如loadpet或storepet方法。这就隐式地使用了spring的事务代理,通过“事务解释器”在目标对象中注册;这样一个新的事务就创建了,然后具体的工作就会代理给jdbcclinic的目标方法。
图1示例了一个使用“建议链”并到达最后目标的aop代理的潜在概念。在这个示例中,唯一的建议是一个事务解释器用来包装目标方法的事务行为。这是一种用来在声明性事务功能下使用的基于代理的aop。

figure 1. an aop proxy with an advisor chain and a target at the end
例如,一个petclinic应用的web层组件可以执行servletcontext定位来获取spring webapplicationcontext的引用并且获取受管理的“clinic”bean:
清单4:
webapplicationcontext ctx =
webapplicationcontexutils.getwebapplicationcontext(servletcontext);
clinic clinic = (clinic) ctx.getbean("clinic);
pet pet = new pet();
pet.setname("my new cat");
clinic.storepet(pet);
在调用storepet()之前,spring的事务代理隐式地创建一个事务。当storepet()调用返回时,事务将提交或回滚。缺省情况下任何runtimeexception或error将导致回滚。实际的提交或回滚可以是可以定义的:spring的事务属性支持“回滚规则”的概念。
例如,我们可以可以引入一个强制的petclinicexception并且告诉事务代理在抛出异常时回滚:
清单5:
<prop key="load*">propagation_required,readonly,-petclinicexception</prop>
<prop key="store*">propagation_required,-petclinicexception</prop>
这儿也有一个类似的“提交规则”语法,指示特定的异常将触发一次提交。
注意上面示例的显式定位引用的方法只是一种访问受spring管理bean的方法的变化,可以用在任何web资源如servlet或filter。在构建基于spring自身的mvc框架时,bean可以直接被注射到web控制器中。当然也支持在如struts, webwork, jsf, and tapestry框架中访问spring管理bean。详情可以参考spring的文档。
platformtransactionmanager策略
spring事务支持的核心接口是org.springframework.transaction.platformtransactionmanager。所有spring的事务分离功能都会委托给platformtransactionmanager(传给相应的transactiondefinition实例)来做实际的事务执行。虽然platformtransactionmanager接口可以直接调用,但通常应用只需要配置一个具体的事务管理器并且通过声明性事务来分离事务。
spring提供几种不同的platformtransactionmanager实现,分为如下两个类别:
1、本地事务策略―支持单一资源的事务(通常是单个数据库),其包括org.springframework.jdbc.datasource.datasourcetransactionmanager和 org.springframework.orm.hibernate.hibernatetransactionmanager。
2、全局事务管理―支持可能跨越多个资源的全局事务。其相应的类为org.springframework.transaction.jta.jtatransactionmanager,将事务委托给遵循jta规范的事务协调器(通常为j2ee服务器,但不是强制的)。
platformtransactionmanager抽象的主要价值在于应用不再被绑定在特定的事务管理环境。相反,事务策略可以很容易地切换―通过选择不同的platformtransactionmanager实现类。这就使得应用代码与声明事务分离保持一致,而不需要考虑应用组件所使用的环境了。
例如,应用的初始版本可能布署在tomcat上,与单个oracle数据库交互。这可以方便地利用spring的事务分离特性,只要选择基于jdbc的datasourcetransactionmanager作为使用的事务策略。spring会分离事务,而jdbc驱动会执行相应的原始jdbc事务。
相同应用的另一个版本可能会布署在weblogic服务器上,使用两个oracle数据库。应用代码和事务分离不需要改变。唯一不同的是选择作为jtatransactionmanager事务策略,让spring来分离事务而weblogic服务器的事务协调器来执行事务。
jta usertransaction与jta transactionmanager比较
让我们来看一下spring对jta支持的细节。虽然并非经常需要考虑这个细节但了解相关的细节还有必要的。对简单的用例如前面章节的示例,标准的jtatransactionmanager定义已经足够了,
缺省的spring jtatransactionmanager设置会从标准jndi位置(j2ee规范所定义的java:comp/usertransaction)获取jta的javax.transaction.usertransaction对象。这对大部分标准j2ee环境来说已经足够了。
闽公网安备 35060202000074号