第20章 使用Spring进行事务管理(三)

第20章 使用Spring进行事务管理

20.2 声明式事务管理

20.2.2 XML元数据驱动的声明式事务

2. 使用“一站式”的TransactionProxyFactoryBean

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中的自动代理机制,而下面正是针对如何使用自动代理对声明式事务进行管理的内容。

3. 使用BeanNameAutoProxyCreator

使用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的配置方式吧!

4. 使用Spring2.x的声明事务配置方式

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/>可用的属性的详细列表。

image-20220623223548747

通过<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以区分不同的事务需求等内容。

20.2.3 注解元数据驱动的声明式事务

随着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标注了相应的事务管理信息之后,剩下的就是对应容器配置的内容了,详情见下方代码清单。

image-20220624130354140

提示:Spring推荐将@Transactional标注于具体的业务实现类或者实现类的业务方法上。之所以如此,是因为SpringAOP可以采用两种方式来生成代理对象(动态代理或者CGLIB)。如果将@Transactional标注于业务接口的定义上,那么,当使用动态代理机制构建代理对象时,读取接口定义上的@Transactional信息是没有问题的。可是当使用CGLIB构建代理对象的时候,则无法读取接口上定义的@Transactional数据。

20.3 小结

本章主要阐述了如何使用Spring事务抽象框架进行事务管理的相关内容,分别就如何使用Spring进行编程式事务管理和声明式事务管理进行了详细的阐述。学习完本章,相信大家已经可以游刃于事务管理相关的日常开发工作了。Spring的事务抽象框架中使用到一些日常开发过程中常用的理念和方法。接下来,我们将尝试挖掘一下这些宝贵的经验,以为我用。

猜你喜欢

转载自blog.csdn.net/qq_34626094/article/details/125485220
今日推荐