Spring Transactions: Declarative Transaction Management

1. Background

       In Spring Transactions: Programmatic Transaction Management , the programmatic transaction management in Spring is briefly introduced. This article introduces the declarative transaction management in Spring.

 

2. Declarative transaction management

1. Overview of declarative transaction management in Spring

       Declarative transaction management in Spring is based on AOP at the bottom. Its essence is to intercept the method before and after, and then create or join a transaction before the target method starts. After executing the target method, commit or roll back according to the execution situation.

       The biggest advantage of declarative transactions is that there is no need to programmatically manage transactions, so there is no need to add transaction management code to the business logic code, only relevant transaction rule declarations need to be made in the configuration file, or equivalent annotation-based In this way, transactions can be applied to business logic.

       Declarative transaction management mainly benefits from the support of Spring dependency injection container and Spring AOP. The dependency injection container provides the infrastructure for declarative transaction management. Yes, beans are manageable for the Spring framework; Spring AOP is a direct observer of declarative transaction management, which can be confirmed by the corresponding configuration file.

Compared with programmatic transaction management, the only disadvantage of declarative transaction management is that the granularity of declarative transaction management can only act at the method level, and cannot act at the code block level like programmatic transactions. However, this shortcoming, there are many workarounds, for example, the code block that needs transaction management can be defined as a method separately, and so on.

       From the experience of most senior developers, they are more inclined to use declarative transaction management, because declarative transaction management is not only simple, but also the business code is very pure, will not be polluted, and is convenient for later maintenance.

 

2. Declarative embassy management based on TransactionInterceptor

Spring provides the TransactionInterceptor class to implement declarative transaction management functionality. The configuration file is as follows:

<beans...>
......
	<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
		<property name="transactionManager" ref="transactionManager"/>
		<property name="transactionAttributes">
			<props>
			   <prop key="transfer">PROPAGATION_REQUIRED</prop>
			</props>
		</property>
	</bean>

    <bean id="bankServiceTarget" class="footmark.spring.core.tx.declare.origin.BankServiceImpl">
        <property name="bankDao" ref="bankDao"/>
    </bean>
	
    <bean id="bankService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="bankServiceTarget"/>
        <property name="interceptorNames">
           <list>
              <idref bean="transactionInterceptor"/>
           </list>
        </property>
    </bean>
......
</beans>

 

       First, we configure a TransactionInterceptor to define related transaction rules. It has two main properties: one is transactionManager, which is used to specify a transaction manager and delegate specific transaction-related operations to it; the other is Properties type The transactionAttributes attribute is mainly used to define transaction rules. In each key-value pair of this attribute, the key specifies the method name, the method name can use wildcards, and the value represents the applied transaction attribute of the corresponding method. There are more complex rules for specifying the value of transaction attributes, which can be regarded as a headache in Spring. The specific writing rules are as follows:

 

Propagation behavior [, isolation level] [, read-only property] [, timeout property] [exception that does not affect commit] [, exception that caused rollback]

 

       The propagation behavior is the only property that must be set, the others can be ignored, and Spring provides us with reasonable defaults. The value of the propagation behavior must start with "PROPAGATION_", and there are seven values, as shown in the following table:            

PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS

 

         The value of the isolation level must start with "ISOLATION_", and there are five types of values, as shown in the following table:

ISOLATION_DEFAULT
ISOLATION_READ_COMMITTED
ISOLATION_READ_UNCOMMITTED
ISOLATION_REPEATABLE_READ
ISOLATION_SERIALIZABLE

 

        如果事务是只读的,那么我们可以指定只读属性,使用“readOnly”指定。否则我们不需要设置该属性。超时属性的取值必须以“TIMEOUT_”开头,后面跟一个int类型的值,表示超时时间,单位是秒。不影响提交的异常是指,即使事务中抛出了这些类型的异常,事务任然正常提交。必须在每一个异常的名字前面加上“+”。异常的名字可以是类名的一部分。比如,

“+RuntimeException”、“+tion”等等。导致回滚的异常是指,当事务中抛出这些类型的异常时,事务将回滚。必须在每一个异常的名字前面加上“-”。异常的名字可以是类名的全部或者部分,比如“-RuntimeException”、“-tion”等等。下面给出两个实例:

 

<property name="*Service">
    PROPAGATION_REQUIRED,
	ISOLATION_READ_COMMITTED,
	TIMEOUT_20,
        +AbcException,
	+DefException,
	-HijException
</property>
        以上表达式表示,针对所有方法名以Service结尾的方法,使用PROPAGATION_REQUIRED事务传播行为,事务的隔离级别是ISOLATION_READ_COMMITTED,超时时间为20秒,当事务抛出AbcException或者DefException类型的异常,则仍然提交,当抛出HijException类型的异常时必须回滚事务。这里没有指定"readOnly",表示事务不是只读的。

 

<property name="test">
    PROPAGATION_REQUIRED,
    readOnly
</property>
       以上表达式表示,针对所有方法名为test的方法,使用PROPAGATION_REQUIRED事务传播行为,并且该事务是只读的。除此之外,其他的属性均使用默认值。比如,隔离级别和超时时间使用底层事务性资源的默认值,并且当发生未检查异常,则回滚事务,发生已检查异常则仍提交事务。

 

       配置好了TransactionInterceptor,还需要配置一个ProxyFactoryBean来组装target和advice。这是典型的Spring AOP的做法。通过ProxyFactoryBean生成的代理类就是织入了事务管理逻辑后的目标类。至此,声明式事务管理就算是实现了。我们没有对业务代码进行任何操作,所有设置均在配置文件中完成,这就是声明式事务的最大优点。

 

3、基于TransactionProxy...的声明式事务管理

       前面的声明式事务虽然好,但是却存在一个非常恼人的问题:配置文件太多。必须针对每一个目标对象配置一个ProxyFactoryBean;另外,虽然可以通过父子Bean的方式来复用TransactionInterceptor的配置,但是实际的复用几率也不高;这样,加上目标对象本身,每一个业务类可能需要对应三个 <bean/> 配置,随着业务类的增多,配置文件将会变得越来越庞大,管理配置文件又成了问题。

       为了缓解这个问题,Spring提供了TransactionProxyFactoryBean,用于将TransactionInterceptor 和ProxyFactoryBean的配置合二为一。如下面的代码所示:

<beans......>
	......
	<bean id="bankServiceTarget" class="footmark.spring.core.tx.declare.classic.BankServiceImpl">
	   <property name="bankDao" ref="bankDao"/>
	</bean>
	<bean id="bankService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
	   <property name="target" ref="bankServiceTarget"/>
	   <property name="transactionManager" ref="transactionManager"/>
	   <property name="transactionAttributes">
		   <props>
			 <prop key="transfer">PROPAGATION_REQUIRED</prop>
		   </props>
	   </property>
	</bean>
    ......
</beans>
       如此一来,配置文件与先前相比简化了很多,这种配置方式称为Spring经典的声明式事务管理。但是,显式为每一个业务类配置一个TransactionProxyFactoryBean的做法将使得代码显得过于刻板,为此可以使用自动创建代理的方式来将其简化,使用自动创建代理是纯 AOP 知识,请读者参考相关文档。

 

4、基于<tx>命名空间的声明式事务管理

      前面两种声明式事务配置方式奠定了Spring声明式事务管理的基石。在此基础上,Spring 2.x 引入了<tx>命名空间,结合使用<aop>命名空间,带给开发人员配置声明式事务的全新体验,配置变得更加简单和灵活。另外,得益于 <aop> 命名空间的切点表达式支持,声明式事务也变得更加强大。如下面的配置文件所示:

<beans......>
	......
	<bean id="bankService"  class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">
	   <property name="bankDao" ref="bankDao"/>
	</bean>
	<tx:advice id="bankAdvice" transaction-manager="transactionManager">
	   <tx:attributes>
		  <tx:method name="transfer" propagation="REQUIRED"/>
	   </tx:attributes>
	</tx:advice>

	<aop:config>
	   <aop:pointcut id="bankPointcut" expression="execution(* *.transfer(..))"/>
	   <aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/>
	</aop:config>
	......
</beans>
  如果默认的事务属性就能满足要求,那么代码简化为如下所示:
<beans......>
	......
	<bean id="bankService" class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">
	   <property name="bankDao" ref="bankDao"/>
	</bean>
	<tx:advice id="bankAdvice" transaction-manager="transactionManager">
	<aop:config>
	   <aop:pointcut id="bankPointcut" expression="execution(**.transfer(..))"/>
	   <aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/>
	</aop:config>
	......
</beans>
       由于使用了切点表达式,我们就不需要针对每一个业务类创建一个代理对象了。另外,如果配置的事务管理器Bean的名字取值为“transactionManager”,则可以省略 <tx:advice> 的transaction-manager属性,因为该属性的默认值即为“transactionManager”。

 

5、基于@Transactional的声明式事务管理

       除了基于命名空间的事务配置方式,Spring 2.x 还引入了基于Annotation的方式,具体主要涉及@Transactional标注。@Transactional可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有public方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如下面的代码所示:

 

@Transactional(propagation = Propagation.REQUIRED)
public boolean transfer(Long fromId, Long toId, double amount) {
    return bankDao.transfer(fromId, toId, amount);
} 
       Spring使用BeanPostProcessor来处理Bean中的标注,因此我们需要在配置文件中作如下声明来激活该后处理Bean,如下面代码所示:
<tx:annotation-driven transaction-manager="transactionManager"/>

 

       与前面相似,transaction-manager属性的默认值是transactionManager,如果事务管理器Bean的名字即为该值,则可以省略该属性。

       虽然@Transactional注解可以作用于接口、接口方法、类以及类方法上,但是Spring小组建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional注解应该只被应用到public方法上,这是由Spring AOP的本质决定的。如果你在protected、private或者默认可见性的方法上使用@Transactional 注解,这将被忽略,也不会抛出任何异常。

       基于<tx>命名空间和基于@Transactional的事务声明方式各有优缺点:

       基于<tx>的方式,其优点是与切点表达式结合,功能强大。利用切点表达式,一个配置可以匹配多个方法。

       基于@Transactional的方式必须在每一个需要使用事务的方法或者类上用@Transactional标注,尽管可能大多数事务的规则是一致的,但是对@Transactional而言,也无法重用,必须逐个指定。另一方面,基于@Transactional的方式使用起来非常简单明了,没有学习成本。开发人员可以根据需要,任选其中一种使用,甚至也可以根据需要混合使用这两种方式。

       如果不是对遗留代码进行维护,则不建议再使用基于TransactionInterceptor以及基于TransactionProxyFactoryBean的声明式事务管理方式,但是,学习这两种方式非常有利于对底层实现的理解。

       虽然上面共列举了四种声明式事务管理方式,但是这样的划分只是为了便于理解,其实后台的实现方式是一样的,只是用户使用的方式不同而已。

 

三、结束语

       三篇关于Spring事务的论述,只是小弟在工作中用到的总结,以及还有参考一些优秀参考资料:Spring官方参考文档Spring系列:Spring框架简介。

      希望喜欢阅读的小伙伴,多看看这些优秀的文献。

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326801319&siteId=291194637