Spring架构事务配置
注:引用文章,本文曾发表于it168
在Spring中进行事务配置除了定义对象自身的bean外,还需要定义一个进行事务代理的bean.如果你有n个类需要引入事务,那么你就必须定义2n个bean。维护这些bean的代价是十分昂贵的,所以必须要对事务配置进行减化。如果你是基于Spring进行架构设计,那么作为一个好的架构设计师,应该把一些公共的方面进行简化,让项目的开发人员只关心项目的业务逻辑,而不要花费太多的精力去关心业务逻辑之外的太多东西。所以作为一个好的架构就应该把事务管理进行简化,让程序员花在编程之外的工作最小化。
1.Spring声明式事务配置的几种方法
在Spring中进行事务控制首先要选择适当的事务管理器,其次为程序选择划分事务的策略。如果只有单个事务性资源,可以从“单一资源”的PlatformTransactionManger实现当中选择一个,这些实现有:DataSourceTransactionManager,HibernateTransactionManager,JdoTransactionManager,PersistenceBrokerTransactionManager和JmsTransactionManager。
根据你所采用的数据库持久化技术选择。如果你的项目运行于支持JTA的服务器,那么将选择JtaTransactionManger,将会支持多资源事务。
下表将为你选择适当的事务管理器提供参考。
技术事务管理器内建的事务支持
JDBCDataSurceTransactionManagerJtaTransactionManagerJdbcTemplate和org.springframework.jdbc.object包中的所有类
IBATISDataSourceTransactionManagerJtaTransactionManagerSqlMapClientTemplate和SqlClientTemplate
HibernateHibernateTransactionManagerJtaTransactionManagerHibernateTemplate和HibernateInterceptor
JDOJdoTransactionManagerJtaTransactionManagerJdoTemplate和JdoInterceptor
ApacheOJBPersistenceBrokerTransactionManagerJtaTransactionManagerPersistenceBrokerTemplate
JMSJmsTransactionManagerJmsTemplate
在划分事务时,我们需要进行事务定义,也就是配置事务的属性。事务的属性有传播行业,隔离级别,超时值及只读标志。TransactionAttribute接口指定哪些异常将导致一个回滚,哪些应该一次性提交。
(1)使用ProxyFactoryBean和TransactionInterceptor
<!--定义本地数据源-->
<beanid="dataSource"name="dataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">
<propertyname="driverClassName"value="${jdbc.driverClassName}"/>
<propertyname="url"value="${jdbc.url}"/>
<propertyname="username"value="${jdbc.username}"/>
<propertyname="password"value="${jdbc.password}"/>
</bean>
<!--!定义单个jdbc数据源的事务管理器-->
<beanid="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<propertyname="dataSource"ref="dataSource"/>
</bean>
<!—定义拦截器-->
<beanid="transactionInterceptor"class="org.springframework.transaction.interceptor.TransactionInterceptor">
<propertyname="transactionManager">
<refbean="transactionManager"/>
</property>
<propertyname="transactionAttributes">
<props>
<propkey="insert*">PROPAGATION_REQUIRED</prop>
<propkey="update*">PROPAGATION_REQUIRED</prop>
<propkey="save*">PROPAGATION_REQUIRED</prop>
<propkey="find*">PROPAGATION_SUPPORTS,readOnly</prop>
<propkey="get*">PROPAGATION_SUPPORTS,readOnly</prop>
<propkey="*">PROPAGATION_SUPPORTS,readOnly</prop>
</props>
</property>
</bean>
<!—定义业务对象-->
<beanid="sampleService"class="com.prs.application.ehld.sample.biz.service.impl.SampleServiceImpl">
<propertyname="userInfoDAO"ref="com.prs.application.ehld.sample.integration.dao.userInfoDAOImpl">
</property>
</bean>
<!—定义业务对象的事务代理对象-->
<beanid="proxyFacgtoryBean"class="org.springframeword.aop.framework.ProxyFacgtoryBean">
<propertyname="target"ref="sampleService">
</property>
<propertyname="interceptorNames">
<value>transactionInterceptor</value>
</property>
</bean>
通过ProxyFacgtoryBean和TransactionInterceptor组合使用,可以对事务进行更多的控制。所有需要事务控制的对象可以共享一个transactionInterceptor的事务属性。
(2)使用TransactionProxyFactoryBean
<!—定义业务对象-->
<beanid="sampleService"
class="com.prs.application.ehld.sample.biz.service.impl.SampleServiceImpl">
<propertyname="userInfoDAO"ref="com.prs.application.ehld.sample.integration.dao.userInfoDAOImpl">
</property>
</bean>
<!—定义业务对象的事务代理对象-->
<beanid="transactionProxyFactoryBean"class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"abstract="true">
<propertyname="transactionManager">
<refbean="transactionManager"/>
</property>
<propertyname="target"ref="sampleService"/>
<propertyname="transactionAttributes">
<props>
<propkey="insert*">PROPAGATION_REQUIRED</prop>
<propkey="update*">PROPAGATION_REQUIRED</prop>
<propkey="save*">PROPAGATION_REQUIRED</prop>
<propkey="find*">PROPAGATION_SUPPORTS,readOnly</prop>
<propkey="get*">PROPAGATION_SUPPORTS,readOnly</prop>
<propkey="*">PROPAGATION_SUPPORTS,readOnly</prop>
</props>
</property>
</bean>
使用TransactionProxyFactoryBean需要为每一个代理对象都去定义自己的事务属性。
(3)使用TransactionProxyFactoryBean及abstract属性来简化配置
这种方工也是目前使用得最多的一种声明式事务配置方法
<!--事务控制代理抽象定义-->
<beanid="baseTransactionProxy"class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"abstract="true">
<propertyname="transactionManager">
<refbean="transactionManager"/>
</property>
<propertyname="transactionAttributes">
<props>
<propkey="insert*">PROPAGATION_REQUIRED</prop>
<propkey="update*">PROPAGATION_REQUIRED</prop>
<propkey="save*">PROPAGATION_REQUIRED</prop>
<propkey="find*">PROPAGATION_SUPPORTS,readOnly</prop>
<propkey="get*">PROPAGATION_SUPPORTS,readOnly</prop>
<propkey="*">PROPAGATION_SUPPORTS,readOnly</prop>
</props>
</property>
</bean>
<!—定义业务对象-->
<beanid="sampleService"class="com.prs.application.ehld.sample.biz.service.impl.SampleServiceImpl">
<propertyname="userInfoDAO"ref="com.prs.application.ehld.sample.integration.dao.userInfoDAOImpl">
</property>
</bean>
<!—定义业务对象的事务代理对象-->
<beanid="sampleServiceProxy"parent="baseTransactionProxy">
<propertyname="target"
ref="com.prs.sampleService">
</property>
</bean>
使用abstract属性,可以让代理对象可以共享一个定义好的事务属性,使配置简化。
(4)使用BeanNameAutoProxyCreator
<!—定义拦截器-->
<beanid="transactionInterceptor"class="org.springframework.transaction.interceptor.TransactionInterceptor">
<propertyname="transactionManager">
<refbean="transactionManager"/>
</property>
<propertyname="transactionAttributes">
<props>
<propkey="insert*">PROPAGATION_REQUIRED</prop>
<propkey="update*">PROPAGATION_REQUIRED</prop>
<propkey="save*">PROPAGATION_REQUIRED</prop>
<propkey="find*">PROPAGATION_SUPPORTS,readOnly</prop>
<propkey="get*">PROPAGATION_SUPPORTS,readOnly</prop>
<propkey="*">PROPAGATION_SUPPORTS,readOnly</prop>
</props>
</property>
</bean>
<!—定义bean别名自动代理创建器-->
<beanid="autoProxyCreator"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<propertyname="interceptorNames">
<value>transactionInterceptor</value>
</property>
<propertyname="beanNames">
<list>
<idreflocal="sampleService"/>
</list>
</property>
</bean>
<!—定义业务对象-->
<beanid="sampleService"
class="com.prs.application.ehld.sample.biz.service.impl.SampleServiceImpl">
<propertyname="userInfoDAO"ref="com.prs.application.ehld.sample.integration.dao.userInfoDAOImpl">
</property>
</bean>
使用BeanNameAutoProxyCreator可以由框架来提供适当的代理,由一个transactionInterceptor统一定义事务属性,只需要把需要事务控制的bean加到beannames的列表。
对于需要大量声明式事务的bean,BeanNameAutoProxyCreator是十分方便的。减少了代理bean的定义,还可以灵活的决定一个bean是否进行事务控制。
上面四种方法是在Spring中常见的声明式事务配置方法,其中使用TransactionProxyFactoryBean及abstract属性进行配置是最常见的简化方法。
我们暂且把需要进行事务控制的bean叫事务bean.把依赖和调用它的bean叫做业务bean。对事务bean进行代理叫做事务代理bean.
1.使用ProxyFactoryBean和TransactionInterceptor,可以由一个TransactionInterceptor统一定义事务属性,对于每一个事务bean都需要再定义一个事务代理bean。如果有n个事务bean,那么就需要定义和维护2n个bean。并且注入到业务bean的不是事务bean本身,而是要求用事务代理bean注入。这增加了理解的难度。
2.使用TransactionProxyFactoryBean需要为每一个事务代理bean都定义自己的事务属性,除了需要维护2n个bean外,还需要为每一个事务代理bean定义事务属性。可以说是最麻烦的。同样需要把事务代理bean注入到业务bean,增加了理解的难度和项目的复杂度。
3.使用TransactionProxyFactoryBean及abstract属性是对使用TransactionProxyFactoryBean的一种简单化配置,可以让所有的事务bean共享一致的事务属性定义。需要维护2n个bean,需要把事务代理bean注入到业务bean。
4.使用BeanNameAutoProxyCreator最适合在框架中使用,只需要维护n个bean。也无需要事务代理bean。直接把事务bean注入业务bean中。但是它必须把需要事务控制的bean加到beanNames列表中。
2.类型自动代理创建器BeanClassTypeAutoProxyCreator
得于BeanNameAutoProxyCreator的启示,BeanNameAutoProxyCreator可以实现框架来实现自动代理。它只是把需要代理的bean加入beanNames属性列表。大大的简化了代理的配置,减少了代理bean的定义,使用事务bean注入业务对象,而不是代理bean注入,更合乎事务逻辑。BeanNameAutoProxyCreator仍然需要开发人员除了定义业务bean外,还需要关心事务的定义,当然已经简单了很多。如果能实现一个BeanClassTypeAutoProxyCreator,为它指定一个可以代理的ClassType列表,那么在context中所有属于ClassType和其子类的bean都自动获得代理。
实现思路:
1.BeanNameAutoProxyCreator继承了AbstractAutoProxyCreator,去实现方法:
protectedabstractObject[]getAdvicesAndAdvisorsForBean(
ClassbeanClass,StringbeanName,TargetSourcecustomTargetSource)
在BeanNameAutoProxyCreator中的实现是判断beanName是存在于beanNames列表,如果能找到则Object[]不对空。否则返回null。
所以BeanClassTypeAutoProxyCreator也应该继承AbstractAutoProxyCreator。
getAdvicesAndAdvisorsForBean方法的实现可以参照BeanNameAutoProxyCreator方法的实现
2.BeanClassTypeAutoProxyCreator需要有一个进行代理的ClassType列表,在bean进行初始化后就在context中查找类型为ClassType列表中类型的所有beanName.从而获得一个beanNames列表。
获得beanNames列表后就可以像BeanNameAutoProxyCreator一样实现自动代理了。
3.要想获得当前context,我们可以实现ApplicationContextAware接口。让BeanClassTypeAutoProxyCreator的bean可以获得当前context.
4.要在bean进行初始化动作,可以实现InitializingBean接口,实现afterPropertiesSet,在这个方法中在context中根据classType查找获得相关的beanName的列表。
5.写一个空接口,里面没有任何方法。需要事务代理的类实现这个空接口。
这样,只需要把这个空接口的全类名作为BeanClassTypeAutoProxyCreator的classTypes参数值,然后所有需要代理的类都去实现这个接口就可以自动获得代理了。无再需要任何配置。这样就可以让程序员专心于业务逻辑的开发,而无需要去关心事务控制方法,就像是没有使用事务一样。
完整的实现类如下:
BeanClassTypeAutoProxyCreator.java
/**
*根据类型自动代理Creator
*
*/
publicclassBeanClassTypeAutoProxyCreatorextendsAbstractAutoProxyCreator
implementsApplicationContextAware,InitializingBean{
/**Loggerthatisavailabletosubclasses*/
protectedfinalLoglogger=LogFactory.getLog(getClass());
/**ApplicationContextthisobjectrunsin*/
privateApplicationContextapplicationContext;
/**MessageSourceAccessorforeasymessageaccess*/
privateMessageSourceAccessormessageSourceAccessor;
/**被代理的bean别名列表**/
privateListbeanNames;
/**被代理的classType列表**/
privateListclassTypes;
//---------------------------------------------------------
//实现AbstractAutoProxyCreator的抽像方法
//---------------------------------------------------------
/**
*@seeorg.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean(java.lang.Class,
*java.lang.String,org.springframework.aop.TargetSource)
*/
protectedObject[]getAdvicesAndAdvisorsForBean(ClassbeanClass,
StringbeanName,TargetSourcetargetSource)throwsBeansException{
if(this.beanNames!=null){
if(this.beanNames.contains(beanName)){
returnPROXY_WITHOUT_ADDITIONAL_INTERCEPTORS;
}
}
returnDO_NOT_PROXY;
}
//-------------------------------------------------------
//实现ApplicationContextAware接口方法
//-------------------------------------------------------
/**
*@seeorg.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
publicvoidsetApplicationContext(ApplicationContextcontext)
throwsBeansException{
if(context==null&&!isContextRequired()){
//Resetinternalcontextstate.
this.applicationContext=null;
this.messageSourceAccessor=null;
}elseif(this.applicationContext==null){
//Initializewithpassed-incontext.
if(!requiredContextClass().isInstance(context)){
thrownewApplicationContextException(
"Invalidapplicationcontext:needstobeoftype["
+requiredContextClass().getName()+"]");
}
this.applicationContext=context;
this.messageSourceAccessor=newMessageSourceAccessor(context);
initApplicationContext();
}else{
//Ignorereinitializationifsamecontextpassedin.
if(this.applicationContext!=context){
thrownewApplicationContextException(
"Cannotreinitializewithdifferentapplicationcontext:currentoneis["
+this.applicationContext
+"],passed-inoneis["+context+"]");
}
}
}
/**
*Determinewhetherthisapplicationobjectneedstoruninan
*ApplicationContext.
*<p>
*Defaultis"false".Canbeoverriddentoenforcerunninginacontext
*(i.e.tothrowIllegalStateExceptiononaccessorsifoutsideacontext).
*
*@see#getApplicationContext
*@see#getMessageSourceAccessor
*/
protectedbooleanisContextRequired(){
returntrue;
}
/**
*Determinethecontextclassthatanycontextpassedto
*<code>setApplicationContext</code>mustbeaninstanceof.Canbe
*overriddeninsubclasses.
*
*@see#setApplicationContext
*/
protectedClassrequiredContextClass(){
returnApplicationContext.class;
}
/**
*ReturntheApplicationContextinstanceusedbythisobject.
*/
publicfinalApplicationContextgetApplicationContext()
throwsIllegalStateException{
if(this.applicationContext==null&&isContextRequired()){
thrownewIllegalStateException(
"ApplicationObjectSupportinstance["+this
+"]doesnotruninanApplicationContext");
}
returnapplicationContext;
}
/**
*ReturnaMessageSourceAccessorfortheapplicationcontextusedbythis
*object,foreasymessageaccess.
*
*@throwsIllegalStateException
*ifnotrunninginanApplicationContext
*/
protectedfinalMessageSourceAccessorgetMessageSourceAccessor()
throwsIllegalStateException{
if(this.messageSourceAccessor==null&&isContextRequired()){
thrownewIllegalStateException(
"ApplicationObjectSupportinstance["+this
+"]doesnotruninanApplicationContext");
}
returnthis.messageSourceAccessor;
}
publicvoidsetClassTypes(String[]classTypes){
this.classTypes=Arrays.asList(classTypes);
}
/**
*Subclassescanoverridethisforcustominitializationbehavior.Gets
*calledby<code>setApplicationContext</code>aftersettingthecontext
*instance.
*<p>
*Note:Does</i>not</i>getcalledonreinitializationofthecontextbut
*ratherjustonfirstinitializationofthisobject'scontextreference.
*
*@throwsApplicationContextException
*incaseofinitializationerrors
*@throwsBeansException
*ifthrownbyApplicationContextmethods
*@see#setApplicationContext
*/
protectedvoidinitApplicationContext()throwsBeansException{
}
//-----------------------------------
//实现InitializingBean接口方法
//-----------------------------------
/**
*查找指定classType的beanName列表
*/
privateListgetBeanNames(StringclassType){
ListbeanNameList=null;
try{
String[]beanName=this.getApplicationContext()
.getBeanNamesForType(Class.forName(classType),true,false);
if(beanName!=null){
beanNameList=Arrays.asList(beanName);
}
}catch(ClassNotFoundExceptionex){
thrownewIllegalArgumentException("Classnotfound:"
+ex.getMessage());
}
returnbeanNameList;
}
/**
*
*@seeorg.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
publicvoidafterPropertiesSet()throwsException{
if(classTypes!=null&&!classTypes.isEmpty()){
beanNames=newArrayList();
for(inti=0;i<classTypes.size();i++){
StringclassType=(String)classTypes.get(i);
ListaList=getBeanNames(classType);
beanNames.addAll(aList);
}
}
if(logger.isDebugEnabled()){
for(inti=0;i<beanNames.size();i++){
logger.debug("printBean:"+(String)beanNames.get(i));
}
}
}
}
3.使用BeanClassTypeAutoProxyCreator
3.1为了使用BeanClassTypeAutoProxyCreator,将为所有需要进行代理的类定一个接口。
packagecom.prs.application.ehld.biz.service;
publicinterfaceBaseService{
}
3.2让需要代理的类实现或继承这个公共接口
packagecom.prs.application.ehld.sample.biz.service;
publicinterfaceSampleServiceextendsBaseService{
publicvoidsetUserInfoDAO(UserInfoDAOuserInfoDAO);
publicvoidinsertUserInfo(UserInfoDTOuserInfo)throwsBusinessServiceException;
}
3.3配置事务代理
<!—定义拦截器-->
<beanid="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<propertyname="transactionManager">
<refbean="transactionManager"/>
</property>
<propertyname="transactionAttributes">
<props>
<propkey="insert*">PROPAGATION_REQUIRED</prop>
<propkey="update*">PROPAGATION_REQUIRED</prop>
<propkey="save*">PROPAGATION_REQUIRED</prop>
<propkey="find*">PROPAGATION_SUPPORTS,readOnly</prop>
<propkey="get*">PROPAGATION_SUPPORTS,readOnly</prop>
<propkey="*">PROPAGATION_SUPPORTS,readOnly</prop>
</props>
</property>
</bean>
<!—定义类型自动代理创建器-->
<beanid="autoClassTypeProxyCreator"
class="com.prs.application.ehld.common.aotoproxy.BeanClassTypeAutoProxyCreator">
<propertyname="interceptorNames">
<value>transactionInterceptor</value>
</property>
<propertyname="classTypes">
<list>
<value>com.prs.application.ehld.biz.service.BaseService</value>
</list>
</property>
</bean>
<!—定义事务bean-->
<beanid="sampleService"class="com.prs.application.ehld.sample.biz.service.impl.SampleServiceImpl">
<propertyname="userInfoDAO"ref="com.prs.application.ehld.sample.integration.dao.userInfoDAOImpl">
</property>
</bean>
效果:只需要定义BeanClassTypeAutoProxyCreator,把需要代理的类型BaseService作为classTypes的值。这样任何实现了BaseService接口的类都自动获得代理。使得程序员就像配置普通bean一样去配置一个需要事务代理的bean。使得程序员只需要去关心业务逻辑。而无需要去关注事务这些框架应该支持的事情。特别是当开发团队成员水平不一,或团队人员流动性大时,BeanClassTypeAutoProxyCreator就发挥了它的作用。一个好的架构设计应该对事务控制,异常处理,日志记录这些方面进行统一的规划和处理,才能保证系统的健壮性。
采用Spring框架进行项目开发,我们在获得它的IOC等好处,同时给我们增加了维护太多配置文件的负担。应该尽量减少bean的定义,更多采用嵌套bean定义。否则将加大项目后期的维护成本。作为一个架构设计者更是应该把通用性比较强的方面进行统一规划。
基于Spring2.*使用tx标签配置的拦截器配置:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.5.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:annotation-config/>
<context:component-scanbase-package="com.bluesky"/>
<beanid="sessionFactory"class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<propertyname="configLocation"value="classpath:hibernate.cfg.xml"/>
<propertyname="configurationClass"value="org.hibernate.cfg.AnnotationConfiguration"/>
</bean>
<!--定义事务管理器(声明式的事务)-->
<beanid="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<propertyname="sessionFactory"ref="sessionFactory"/>
</bean>
<tx:adviceid="txAdvice"transaction-manager="transactionManager">
<tx:attributes>
<tx:methodname="*"propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcutid="interceptorPointCuts"
expression="execution(*com.bluesky.spring.dao.*.*(..))"/>
<aop:advisoradvice-ref="txAdvice"pointcut-ref="interceptorPointCuts"/>
</aop:config>
</beans>
Spring3.*推荐的全注解
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.5.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:annotation-config/>
<context:component-scanbase-package="com.bluesky"/>
<tx:annotation-driventransaction-manager="transactionManager"/>
<beanid="sessionFactory"class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<propertyname="configLocation"value="classpath:hibernate.cfg.xml"/>
<propertyname="configurationClass"value="org.hibernate.cfg.AnnotationConfiguration"/>
</bean>
<!--定义事务管理器(声明式的事务)-->
<beanid="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<propertyname="sessionFactory"ref="sessionFactory"/>
</bean>
</beans>