TransactionProxyFactoryBean是专门面向事务管理的ProxyFactoryBean,它直接将TransactionInterceptor纳入自身进行管理。 使用TransactionProxyFactoryBean代替ProxyFactoryBean进行声明式事务管理,不需要单独声明TransactionInterceptor的bean定义,有关事务的元数据、事务管理器等信息,全都通过TransactionProxyFactoryBean的bean定义指定就可以。
这样,同样针对QuoteService的声明式事务管理,使用TransactionProxyFactoryBean后的样子如下方代码清单所示。
<beans>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql://1oca1host/dbName"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="..."/>
<property name="password" value="..."/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="quoteServiceTarget"class="...QuoteService">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<bean id="quoteService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="target" ref="quoteServiceTarget"/>
<property name="proxyInterfaces" value="...IQuoteService"/>
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<propkey="getQuate*">PROPAGATION_SUPPORTS,readOnly,timeout_10</prop>
<propkeys"saveQuote">PROPAGATION_REQUIRED</prop>
<propkey="updateQuote">PROPAGATION_REQUIRED</prop>
<propkey="deleteQuote">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="client" class="...QuoteSerivceClient">
<property name="quoteService" ref="quoteService"/>
</bean>
</beans>
现在,TransactionProxyFactoryBean集ProxyFactoryBean、TransactionInterceptor功能于一身,一心一意地为声明式事务管理做贡献了。
不过,我们也看到了,针对TransactionProxyFactoryBean的bean定义看起来不是那么苗条,如果每个需要声明式事务的业务对象都来这么一下子,那么配置量可着实不轻松。所以,通常情况下,我们会使用bean定义模板的方式,来简化使用TransactionProxyFactoryBean进行声明式事务的配置,如下方代码清单所示。
<bean id="txProxyFactoryBean" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true">
<property name="proxyInterfaces" value="...IQuoteService"/>
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<propkey="getQuate*">PROPAGATION_SUPPORTS,readOnly,timeout_10</prop>
<propkey"saveQuote">PROPAGATTON__REQUIRED</prop>
<propkey="updateQuote">PROPAGATION_REQUIRED</prop>
<propkey="deleteQuote">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="quoteService" parent="txProxyFactoryBean">
<property name="target" ref="quoteServiceTarget"/>
</bean>
<bean id="quoteService2" parent="txProxyFactoryBean">
<property name="target" ref="otherQuoteServiceTarget"/>
</bean>
...
将共有的一些属性提取到txProxyFactoryBean的bean定义模板中,就可以减少每次配置单独业务对象对应的bean定义的工作量。
相对于直接使用ProxyFactoryBean和TransactionInterceptor,使用TransactionProxyFactoryBean可以将声明式事务相关的关注点集中起来,一定程度上减少了配置的工作量。不过话又说回来了,如果应用程序中仅有少量的业务对象需要配置声明式事务,那么配置的工作量还算说的过去,一旦需要声明式事务的业务对象数量增加,采用这种近乎“手工作坊式”的配置方式就会"拖后腿”了。这时,我们自然会想到AOP中的自动代理机制,而下面正是针对如何使用自动代理对声明式事务进行管理的内容。
使用BeanNameAutoProxyCreator进行声明式事务管理进一步地简化了配置的工作。当所有的声明式事务相关装备一次到位之后,要为新的业务对象添加声明式事务支持,唯一要做的就是,在为该业务对象添加bean定义的时候,同时将它的beanName添加到BeanNameAutoProxyCreator管理的beanNames列表中。
使用BeanNameAutoProxyCreator为业务对象提供声明式事务支持,通常配置如下方代码清单所示。
<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" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSouiceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<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.de1eteQuote=PROPAGATION_REQUIRED
</value>
</property>
</bean>
<bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="interceptorNames">
<list>
<value>transactionInterceptor</va1ue>
</list>
</property>
<property name="beanNames">
<list>
<idref bean="quoteService"/>
...
</list>
</property>
</bean>
<bean id="quoteService" class="...QuoteService">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<bean id="client" class="...QuoteSerivceClient">
<property name="quoteService" ref="quoteService"/>
</bean>
</beans>
现在,我们只需要正常地向IoC容器的配置文件中增加相应的业务对象bean定义。BeanNameAutoProxyCreator将根据TransactionInterceptor提供的事务管理功能,为添加到它的beanNames列表的所有业务对象自动添加事务支持(当然,本质上是为其生成动态代理对象)。
无论应用中业务对象数量多少,使用BeanNameAutoProxyCreator都可以很便捷地处理这些业务对象的声明式事务需求。不过,可能在实际的开发过程中,我们依然会感觉使用BeanNaneAutoProxyCreator有其不够便捷之处。好消息就是,如果我们的应用程序可以,或者已经升级到Spring2.x,那么,使用基于XSD的配置方式吧!
Spring2.x后提供的基于XMLSchema的配置方式,专门为事务管理提供了一个单独的命名空间用于简化配置,结合新的tx命名空间,现在的声明式事务管理看起来要清晰许多(见下方代码清单)。
<beans xmlns:="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jee="http://www.springframework.org/schema/jee"
xm1ns:lang="http://www.springframework.org/schema/lang"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http:1/www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/Bchema/tx
http://www.springframework.org/echema/tx/spring-tx-2.0.xsd">
<aop:config>
<aop:pointcut id="txServices" expression="execution(*cn.spring21.unveilspring.IQuoteService.*(..))"/>
<aop:advisor pointcut-ref="txServices" advice-ref="txAdvice"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attrlbutes>
<tx:method name="getQuate*" propagation="SUPPORTS" read-only="true" timeout="20"/>
<tx:method name="saveQuote">
<tx:method name="updateQuote"/>
<tx:method name="deleteQuote"/>
</tx:attributes>
</tx:advice>
<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" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSouiceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="quoteService" class="...QuoteService">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<bean id="client" class="...QuoteSerivceClient">
<property name="quoteService" ref="quoteService"/>
</bean>
</beans>
<tx:advice>
是专门为声明事务Advice而设置的配置元素,底层当然还是我们的TransactionInterceptor,只是披一件新衣裳而已。
<tx:advice>
的transaction-manager指定了它要使用的事务管理器是哪一个。如果容器中事务管理器的beanName恰好就是transactionManager,那么可以不明确指定。
<tx:advice>
内部由<tx:attributes>
提供声明式事务所需要的元数据映射信息,每条映射信息对应一一个<tx:method/>
元素声明。<tx:method/>
只有name属性是必须指定的,其他的属性代表事务定义的其他内容,比如propagation用于指定传播行为,isolation用于指定隔离度,timeout用于指定事务的超时时间等。如果不明确指定的话,将采用DefaultTransactionDefinition的设置内容。
表20-1是<tx:method/>
可用的属性的详细列表。
通过<tx:advice>
指定的事务信息,需要有SpringAOP的支持才能织入到具体的业务对象,所以,剩下的工作实际上是AOP的配置了,如下所示:
<aop:config>
<aop:pointcut id="txServices" expression="execution(*cn.spring21.unveilspring.IQuoteService.*(..))"/>
<aop:advisor pointcut-ref="txServices" advice-ref="txAdvice"/>
</aop:config>
在SpringAOP部分已经说过,<aop:config>
底层也是依赖于自动代理机制。所以,我一直强调,新的基于XSD的配置方式,只是换了一身简洁明快的外衣,而我想让大家看到的,则是外衣里面的东西。
注意:更多使用tx:advice的内容可以参照Spring2.x之后的参考文档,其中有更多详细内容,比如配置多个tx:advice以区分不同的事务需求等内容。
随着Java5(Tiger)的发布,注解越来越受到开发人员的关注和喜爱,如果你的应用程序构建在Java5或者更高版本的虚拟机上的话,那么恭喜你,现在你也可以使用Spring提供的基于注解的声明式事务管理了。
注解元数据驱动的声明式事务管理的基本原理是,将对应业务方法的事务元数据,直接通过注解标注到业务方法或者业务方法所在的对象上,然后在业务方法执行期间,通过反射读取标注在该业务方法上的注解所包含的元数据信息,最终将根据读取的信息为业务方法构建事务管理的支持。
Spring定义了org.springframework.transaction.annotation.Transactional
用于标注业务方法所对应的事务元数据信息。通过Transactional,可以指定与<tx:method/>
几乎相同的信息。当然,现在不用指定方法名称了,因为Transactional直接标注到了业务方法或者业务方法所在的对象定义上。通过查看Transactional的定义(见下方代码清单),我们可以获取所有可以指定的事务定义内容。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default{};
String[] rollbackForClassName() default{};
Class<? extends Throwable>[] noRollbackFor() default{};
String[] noRollbackForClassName() default{};
}
要为QuoteService添加基于注解的声明式事务管理,需要为其添加Transactional以标注必要的事务管理信息,如下方代码清单所示。
@Transactional
public class QuoteService implements IQuoteService {
private JdbcTemplate jdbcTemplate;
@Transactional(propagation=Propagation.SUPPORTS,reaaOnly=true,timeout=20)
public Quote getQuate() {
return(Quote)getJdbcTemplate().queryForObject("SELECT * FROM fx_quote where quote_id=2", new RowMapper() {
public Object mapRow(ResultSet rs, int row) throws SQLException {
Quote quote = new Quote();
//...
return quote;
}});
}
@Transactional(propagation=Propagation,SUPPORTS,readOnly=true,timeout=20)
public Quote getQuateByDateTime(DateTime dateTime) {
throw new NotImplementedException();
}
public void saveQuote(Quote quote) {
throw new NotImp1ementedException();
}
public void updateQuote(Quote quote) {
throw new NotImplementedException();
}
public void deleteQuote(Quotequote) {
throw new NotImplementedException();
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
如果将@Transactional标注为对象级别的话,对象中的方法将“继承”该对象级别上的@Transactional的事务管理元数据信息。如果某个方法有特殊的事务管理需求,可以在方法级别添加更加详细的@Transactional设定,比如getQuote*()
方法。通过将相同的事务管理行为提取到对象级别的@Transactional,可以有效地减少标注的数量。如果不为@Transactional指定自定义的一些设定,它也会像<tx:method/>
那样采用与DefaultTransactionDefinition一样的事务定义内容。
只通过@Transactional标注业务对象以及对象中的业务方法,并不会为业务方法带来任何事务管理的支持。@Transactional只是一个标志而已,需要我们在执行业务方法的时候,通过反射读取这些信息,并根据这些信息构建事务,才能使这些声明的事务行为生效。 这就好像下方代码清单中的代码所演示的那样。
public Quote getQuate() {
try {
Method method = quoteService.getClass().getDeclaredMethod("getQuate", null);
boolean isTxAnnotationPresent = method.isAnnotationPresent(Transactional.class);
if(!isTxAnnotationPresent) {
return (Quote)quoteService.getQuate();
}
Transactional txInfo = method.getAnnotation(Transactional.class);
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
if(!txInfo.propagation().equals(Propagation.REQUIRED)) {
transactionTemplate.setPropagationBehavior(txInfo.propagation().value());
}
if(txInfo.readOnly()) {
transactionTemplate.setReadOnly(true);
}
// ...
return (Quote)transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus txStatus) {
return quoteService.getQuate();
}});
} catch(SecurityException e) {
e.printStackTrace(); // 不要这样做
return null;
} catch(NoSuchMethodException e) {
e.printStackTrace(); // 不要这样做
return nul1;
}
}
...
不过,我们不用自己去写这些底层的逻辑了,通过在容器的配置文件中指定如下一行配置,这些搜寻注解、读取内容、构建事务等工作全都由Spring的IoC容器搞定:
<tx:annotation-driven transaction-manager="transactionManager"/>
所以,使用注解元数据驱动的声明式事务管理,基本上就需要做两件事:
使用@Transactional标注相应的业务对象以及相关业务方法。这一步通常由业务对象的开发者统一负责,@Transactional的使用使得元数据以及业务逻辑的实现全部集中到了一处,有了IDE的支持,管理起来更是得心应手。
在容器的配置文件中设定事务基础设施。我们需要添加<tx:annotation-driven transaction-manager=”transactionManager"/>
以便有人使用我们所标注的@Transactional,并且,需要给出相应的事务管理器,要进行事务管理,没有它可不行。
对于QuoteService来说,在已经使用@Transactional标注了相应的事务管理信息之后,剩下的就是对应容器配置的内容了,详情见下方代码清单。
提示:Spring推荐将@Transactional标注于具体的业务实现类或者实现类的业务方法上。之所以如此,是因为SpringAOP可以采用两种方式来生成代理对象(动态代理或者CGLIB)。如果将@Transactional标注于业务接口的定义上,那么,当使用动态代理机制构建代理对象时,读取接口定义上的@Transactional信息是没有问题的。可是当使用CGLIB构建代理对象的时候,则无法读取接口上定义的@Transactional数据。
本章主要阐述了如何使用Spring事务抽象框架进行事务管理的相关内容,分别就如何使用Spring进行编程式事务管理和声明式事务管理进行了详细的阐述。学习完本章,相信大家已经可以游刃于事务管理相关的日常开发工作了。Spring的事务抽象框架中使用到一些日常开发过程中常用的理念和方法。接下来,我们将尝试挖掘一下这些宝贵的经验,以为我用。