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>

相关推荐