直接使用编程式事务管理的不足就是,事务管理代码与业务逻辑代码相互混杂。而声明式事务管理则可以避免这种不同系统关注点之间的纠缠,使得事务管理代码不用再去影响具体业务逻辑的实现。
声明式事务实际上并没有想象中那么神秘,在我们将事务管理这一横切关注点从原来硬编码事务管理逻辑的系统中剥离出来后,就会发现,声明式事务已经在“幸福终点站”那里等着我们了。
先不管Spring是如何提供声明式事务管理的。如果要我们从原来硬编码事务管理的系统中,将这些事务管理相关的代码从业务对象中剥离出来,我们会怎么做?最土的一个办法是,为每个Service都提供一个TransactionFacade,将事务管理逻辑集中到TransactionFacade中,而Service实现类可以对事务管理一无所知,只要保证针对所有Service的调用必须走TransactionFacade即可。
整个情形看起来就是图20-1所示的样子。
虽然这种方法可以实现事务管理代码与业务逻辑代码之间的分离,但是如果不做一些变通的话,在实际的开发中也不会给我们带来更多的好处。难道你想为每个Serivce对象都实现一个对应的TransactionFacade对象吗?
对,经过SpringAOP的洗礼,你应该已经想到了,动态代理不就是为这个设计的吗?呵呵,不过,原理归原理,要真的实现这种功能,直接使用Spring AOP才是正道啊。事务管理本身就是一种横切关注点,与其他的横切关注点本质上没有任何区别,所以,我们完全可以为其提供相应的Advice实现,然后织入到系统中需要该横切逻辑的Joinpoint处。这样不就达到了将事务管理逻辑从业务逻辑实现中剥离出来的目的了吗?现在,我们要做的就是提供一个拦截器,在业务方法执行开始之前开启一个事务,当方法执行完成或者异常退出的时候就提交事务或者回滚事务。
有了Spring的编程式事务管理API的支持,实现这样的一个拦截器对于我们来说应该就很容易了,下方代码清单给出了这样一个拦截器原型的代码实现。
public class PrototypeTransactionInterceptor imp1ements MethodInterceptor {
private PlatformTransactionManager transactionManager;
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
TransactionDefinition definition = getTransactionDefinitionByMethod(method);
TransactionStatus txStatus = transactionManager.getTransaction(definition);
Object result = null;
try {
result = invocation.proceed();
}
catch(Throwable t) {
if(needRollbackOn(t)) {
transactionManager.rollback(txStatus);
}
else {
transactionManager.commit(txStatus);
}
throw t;
}
transactionManager.commit(txStatus);
return result;
}
protected boolean needRollbackOn(Throwablet) {
//TODO ...更多实现细节
return false;.
}
protected TransactionDefinition getTransactionDefinitionByMethod(Methodmethod) {
//TODO ...更多实现细节
return null;
}
public PlatformTransactionManager getTransactionManager() {
return transactionManager;
}
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
在实现这样的一个拦截器的过程中,我们会发现有些逻辑不好确定,如下所述。
针对每个对象业务方法的拦截,需要知道该方法是否需要事务支持。如果需要,针对该事务的TransactionDefinition相关信息又从哪里获得?
如果调用方法过程中抛出异常,如何对这些异常进行处理,哪些异常抛出的情况下需要回滚事务,哪些异常抛出的情况下又不需要?
我们需要提供某种方式来记载业务方法与对应的事务信息之间的映射关系,拦截器只要查询这种映射关系,就可以知道要不要为当前业务方法创建事务。如果要创建事务,则以业务方法作为标志,到映射关系中查找创建事务所需要的信息,然后创建事务。我们当然可以将这种映射信息写死到拦截器实现类中。不过,将它们放在外部配置文件(例如properties或者XML文件)或者Java源代码的注解中才是当下比较流行的方式。这种映射信息正规来讲,叫做驱动事务的元数据(Metadata)。有了类似于PrototypeTransactionInterceptor这样的AOP装备的支持,系统的事务管理就变成了只需要在元数据中声明相应的事务控制信息。
Spring提供了用于声明式事务管理的一切设施(使用org.springframework.transaction.interceptor.TransactionInterceptor
实现替代PrototypeTransactionInterceptor),对于我们来说,所要做的只是决定要使用XML元数据驱动,还是使用注解元数据驱动的声明式事务管理。
提示:什么?我还没有公布第二个问题是如何解决的?还记得在TransactionDefinition部分提到的用于声明式事务的TransactionAttribute定义吗?其实仅需要将“在什么异常类型抛出的情况下需要回滚事务”的信息补充到默认的TransactionDefintion定义中,用于事务处理的拦截器根据获得的TransactionDefinition提供的该信息,就能够决定出现异常的情况下应该如何结束事务。而TransactionAttribute就是增加了异常回滚信息的TransactionDefinition定义。用TransactionAttribute实现类代替默认的TransactionDefintion实现类创建事务不就结了?
Spring允许我们在IoC容器的配置文件中直接指定事务相关的元数据,从1.x版本发展到2.x版本,不管配置形式如何变化,所要达到的目的以及底层的机制却是不变的,配置形式的演化只是为了能够更加简洁方便。从Spring1.x到2.x,大体上来说,我们可以使用以下4种配置方式在IoC容器的配置文件中指定事务需要的元数据。
使用ProxyFactory(ProxyFactoryBean)+TransactionInterceptor。
使用“一站式”的TransactionProxyFactoryBean。
使用BeanNameAutoProxyCreator。
使用Spring2.x的声明事务配置方式。
我们将对这4种配置方式给出详细的实例和相关内容的讲解。不过在此之前,我们需要先声明一个事务加之于上的模型。总不能将所有的配置都构建成“空中楼阁”不是?
总是FooService或者BarService,可能大家都看烦了,所以,我们这回打算换个口味。假设我们要搭建一个QuoteService,先暂且不管它是应用于证券系统还是外汇系统,总之,通过它,我们能够查询报价信息,并且必要的话,也可以更新底层数据内容。现在的QuoteService不是一个远程服务,它的目的也很简单,基本上就是实现一个Quote信息的基本管理功能(第33章将介绍如何通过Spring将QuoteService以远程服务的方式公开出来)。
首先定义的是对应QuoteService的服务接口,面向接口编程为管理问题域提供了很好的抽象方式,接口定义如下所示:
public interface IQuoteService {
Quote getQuate();
Quote getQuateByDateTime(DateTime dateTime);
void saveQuote(Quote quote);
void updateQuote(Quote quote);
void deleteQuote(Quote quote);
}
QuoteService实现了IQuoteService接口,其定义见下方代码清单。
public class QuoteService implements IQuoteService {
private JdbcTemplate jdbcTemplate;
public Quote getQuate() {
return(Quote)getJdbcTemplate().queryForObject("",new RowMapper() {
public Object mapRow(ResultSet rs, int row) throws SQLException {
Quote quote = new Quote();
// ...
return quote;
}});
}
public Quote getQuateByDateTime(DateTime dateTime) {
throw new NotImplementedException();
}
public void saveQuote(Quote quote) {
throw new NotImplementedException();
}
public void updateQuote(Quote quote) {
throw new NotImplementedException();
}
public void deleteQuote(Quote quote) {
throw new NotImplementedException();
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(jdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
这里只是为了说明针对QuoteService的事务管理,所以,暂且直接使用JdbcTemplate进行数据访问。如果系统需要进一步的抽象,可以将数据访问逻辑提取到DAO层,屏蔽数据访问技术造成的差异。我们仅给出一个方法的实现,其余的先抛出NotImplementedException。当系统运行时,这可以告诉我们还有工作没有完成。
在有了QuoteService这个简单的模型之后,我们开始进入正题。
XML元数据驱动的声明式事务,本质上来说,是为TransactionInterceptor提供需要的TransactionDefiniton(具体来说,是TransactionAttribute)信息。而至于说将TransactionInterceptor给出的事务管理相关的横切逻辑加到相应的业务对象上的过程,则纯粹就是一个AOP的配置过程。既然如此,最基本的方式当然就是,直接通过ProxyFactoryBean(或者ProxyFactory)完成事务管理这一横切关注点到系统的织入工作。
所以,使用ProxyFactoryBean和TransactionInterceptor为我们的QuoteService添加声明式事务,看起来就是下方代码清单所示的样子。
<beans>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost/databaseName"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="..."/>
<property name="password" value="..."/>
</bean>
<bean id="jdbcTemplate" c1ass="org.springframework.jdbc.core.JdbcTemplate">
<propertyname="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<propkey="getQuate*">PROPAGATION_SUPPORTS,readOnly,timeout_20</prop>
<propkey="saveQuote">PROPAGATION_REQUIRED</prop>
<propkey="updateQuote">PROPAGATION_REQUIRED</prop>
<propkey="deleteQuote">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="quoteServiceTarget" class="...QuoteService">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<bean id="quoteService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="quoteServiceTarget"/>
<property name="proxyInterfaces" value="...IQuoteService"/>
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
<bean id="client" class="...QuoteSerivceClient">
<property name="quoteService" ref="quoteService"/>
</bean>
</beans>
一般意义上,QuoteSerivceClient是使用Quoteservice的应用类,当我们通过它调用QuoteService的业务方法的时候,除了getQuote()方法之外,其他方法,比如getQuoteService().saveQuote(quote),因为抛出了unchecked的NotImplementedException,从而导致事务自动回滚。这说明我们的声明式事务生效了,不是吗?如下所示:
现在让我们回头探究一下,在使用ProxyFactoryBean和TransactionInterceptor进行声明式事务的过程中,到底有哪些奥秘和需要我们注意的地方。要使声明式事务起作用,需要几个方面协同工作,这包括事务要管理的具体数据资源类型、采用的数据访问技术、特定的事务管理器实现,以及TransactionInterceptor提供的业务方法拦截功能。
具体到我们的QuoteService来说,我们要对数据库提供的数据资源进行事务操作,所以,配置中需要dataSource的定义,这是事务操作的基础,也就是如下所示配置内容:
<bean id="dataSource" class="org.apache.cormons.dbcp.BasicDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost/databaseName"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/
<property name="username" value="..."/>
<propertyname="password" value="..."/>
</bean>
要对数据资源进行访问,QuoteService采用的是JDBC的方式,即直接便用Spring提供的JdbcTemplate,那么,我们需要提供JdbcTemplate以及特定于JDBC的事务管理器DataSourceTransactionManager,如下定义:
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="đataSource" ref="dataSource"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
数据访问技术与事务管理器的类型是一一对应的,否则就有“驴唇不对马嘴”之嫌,让A公司的老板来管理B公司的事务,B公司的员工当然可以不理他。所以,如果你的QuoteService要用Hibemate进行数据访问,那么请提供HibernateTransactionManager作为事务管理器。
**TransactionInterceptor是整个声明式事务的主体。**要让它发挥事务管理的职能,需要为它提供一个事务管理器。要让它知道该为哪个方法加诸什么事务,是否要加诸事务,需要为它提供必要的映射信息,如下方代码清单所示。
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="getQuate*">PROPAGATION_SUPPORTS,readOnly,timeout_20</prop>
<prop key="saveQuote">PROPAGATION_REQUIRED</prop>
<prop key="updateQuote">PROPAGATION_REQUIRED</prop>
<prop key="deleteQuote">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
TransactionInterceptor所需要的业务对象上的每个业务方法的事务管理信息,通过org.springframework.transaction.interceptor.TransactionAttributeSource
接口来获取,该接口定义如下:
public interface TransactionAttributeSource {
TransactionAttribute getTransactionAttribute(Method method, Class targetClass);
}
通常,TransactionAttributeSource的具体实现类以不同的形式存储了从不同位置获取的事务管理元数据信息,比如NameMatchTransactionAttributeSource将以方法名作为映射信息的Key,对相应的元数据进行存储;MethodMapTransactionAttributeSource则直接以对应每个方法的Method实例作为Key,来存储元数据对应的映射信息;而AnnotationTransactionAttributeSource则是直接从源代码中的注解中获取对应每个业务方法的事务管理信息。
我们通常通过如下两个属性为TransactionInterceptor设置需要的TransactionAttributeSource。
org.springframework.transaction.interceptor.TransactionAttributeSourceEditor
对String形式的值进行一个转换,再设置给TransactionInterceptor,转换后的具体TransactionAttributeSource实现类为MethodMapTransactionAttributeSource。同样的元数据,通过transactionAttributeSource设置如下:<property name="transactionAttributeSource">
<value>
org.spring21.package.IQuoteService.getQuate*=PROPAGATION_SUPPORTS,readOnly,timeout_20
org.spring21.package.IQuoteService.saveQuote=PROPAGATION_REQUIRED
org.spring21.package.IQuoteService.updateQuote=PROPAGATION_REQUIRED
org.spring21.package.IQuoteService.deleteQuote=PROPAGATION_REQUIRED
</value>
</property>
唯一需要注意的就是,现在需要指定全限定的类名和方法名。
注意也可以通过transactionAttributeSources属性指定元数据信息,它是transactionAttributeSource的复数形式。
在TransactionInterceptor准备就绪之后,剩下的就是通过SpringAOP将业务对象与其绑定,见下方代码清单。
<bean id="quoteServiceTarget" class="...QuoteService">
<property name="jdbcTemplate" ref="jabcTemplate"/>
</bean>
<bean id="quoteService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="quoteServiceTarget"/>
<property name="proxyInterfaces" value="...IQuoteService"/>
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
<bean id="c1ient" class="...QuoteSerivceClient">
<property name="quoteService" ref="quoteService"/>
</bean>
只不过,一定不要把quoteServiceTarget当作quoteService而注入给需要的对象,否则,事务管理不生效那就怨不得别人了。
元数据中事务属性指定规则:
以String形式给出的事务属性是有一定规则的,这个规则由org.springframework.transaction.interceptor.TransactionAttributeEditor
类的逻辑来界定,TransactionAttributeEditor负责将String形式给出的事务属性转换为具体的TransactionAttribute实例(RuleBasedTransactionAttribute)。
String形式的事务属性规则如下:
PROPAGATIONNAME,[ISOLATION_NAME],[readOnly],[timeout_NNNN],[+Exception1],[-Exception2]
除了PROPAGATION_NAME是必须的之外,其他规则可以根据需要决定是否需要指定,各个区段之间以逗号分隔。
serviceMethod=PROPAGATIONREQUIRED,-QuoteException
因为“unchecked exception”默认情况下会自动回滚,所以,通过自定义异常回滚规则主要是指定“checked exception”类型的应用异常。
使用ProxyFactoryBean和TransactionInterceptor实现声明式事务,可以从最低层次上理解Spring提供的声明式事务是如何运作的。不过,为了减少配置量,进一步提高开发效率,我们会探索更加便捷的方式,TransactionProxyFactoryBean就向这一目标更迈进了一步。