• 第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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    现在,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>
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    将共有的一些属性提取到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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    现在,我们只需要正常地向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>  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    <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>
    
    • 1
    • 2
    • 3
    • 4

    在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{};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    要为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;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    如果将@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;
      }
    }
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    不过,我们不用自己去写这些底层的逻辑了,通过在容器的配置文件中指定如下一行配置,这些搜寻注解、读取内容、构建事务等工作全都由Spring的IoC容器搞定:

    <tx:annotation-driven transaction-manager="transactionManager"/>
    
    • 1

    所以,使用注解元数据驱动的声明式事务管理,基本上就需要做两件事:

    • 使用@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的事务抽象框架中使用到一些日常开发过程中常用的理念和方法。接下来,我们将尝试挖掘一下这些宝贵的经验,以为我用。

  • 相关阅读:
    kong安装与配置
    SpringBoot+MyBatis Plus对Map中Date格式转换的处理
    力扣(104.101)补9.7
    深入URP之Shader篇6: SimpleLit Shader分析(2) Vertex Shader
    322. 零钱兑换
    puppeteer常规操作代码段
    命令源码文件
    Spring Boot 2.x系列【18】功能篇之多线程定时任务
    x264 参考帧管理原理:reference_build_list 函数
    外贸邮件推广怎么统计维度
  • 原文地址:https://blog.csdn.net/qq_34626094/article/details/125485220