Comprehensive analysis of Spring's programmatic transaction management and declarative transaction management

before the start

About this tutorial

This tutorial provides an in-depth look at Spring's simple and powerful transaction management capabilities, both programmatic and declarative. Through the study of this tutorial, you will be able to understand the essence of Spring transaction management and use it flexibly.

prerequisites

This tutorial assumes that you already have Java basics and some familiarity with Spring. You also need to have basic transaction management knowledge, such as: the definition of transactions, the concept of isolation levels, and so on. This article will use these concepts directly without explaining them in detail. Also, it's a good idea to have basic knowledge of databases, although this is not required.

system requirement

To experiment with the tools and examples in this tutorial, the hardware requirements are: a system with at least 512MB of RAM (1GB recommended). The following software needs to be installed:

  • Sun JDK 5.0 or later or IBM Developer Kit for the Java 5 platform version.
  • Spring framework 2.5. The sample code that accompanies this tutorial has been tested on Spring 2.5.6.
  • MySQL 5.0 or later.

Spring transaction attribute analysis

Transaction management is critical for enterprise applications. It ensures that every operation of the user is reliable, and even if there is an abnormal access situation, the integrity of the background data will not be damaged. Just like a bank's self-service teller machine, it can usually serve customers normally, but it is inevitable that the machine suddenly fails during the operation. At this time, the transaction must ensure that the operation of the account before the failure does not take effect, just like The user has not used the ATM at all just now, so as to ensure that the interests of the user and the bank are not lost.

In Spring, transactions are defined through the TransactionDefinition interface. This interface contains methods related to transaction properties. Specifically as shown in Listing 1:

Listing 1. Main methods defined in the TransactionDefinition interface

1

2

3

4

5

6

public interface TransactionDefinition{

int getIsolationLevel();

int getPropagationBehavior();

int getTimeout();

boolean isReadOnly();

}

You may be wondering why the interface only provides methods to get properties, but not related methods to set properties. In fact, the reason is very simple. The setting of transaction attributes is completely controlled by the programmer, so the programmer can customize any method of setting the attribute, and there is no requirement for the field to save the attribute. The only requirement is that when Spring performs transaction operations, it must be able to return transaction-related property values ​​by calling the methods provided by the above interfaces.

Transaction isolation level

Isolation level refers to the degree of isolation between several concurrent transactions. Five constants representing isolation levels are defined in the TransactionDefinition interface:

  • TransactionDefinition.ISOLATION_DEFAULT: This is the default value, which means to use the default isolation level of the underlying database. For most databases, this is usually TransactionDefinition.ISOLATION_READ_COMMITTED.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: This isolation level indicates that a transaction can read data modified by another transaction but not yet committed. This level does not prevent dirty reads and non-repeatable reads, so this isolation level is rarely used.
  • TransactionDefinition.ISOLATION_READ_COMMITTED: This isolation level means that a transaction can only read data that has been committed by another transaction. This level prevents dirty reads and is the recommended value in most cases.
  • TransactionDefinition.ISOLATION_REPEATABLE_READ: This isolation level indicates that a transaction can repeat a query multiple times during the entire process, and the records returned each time are the same. Even if there is new data between multiple queries to satisfy the query, these new records will be ignored. This level prevents dirty reads and non-repeatable reads.
  • TransactionDefinition.ISOLATION_SERIALIZABLE: All transactions are executed one by one, so that there is absolutely no possibility of interference between transactions, that is, this level can prevent dirty reads, non-repeatable reads, and phantom reads. But this will seriously affect the performance of the program. Normally this level is not used either.

Transaction propagation behavior

The so-called transaction propagation behavior means that if a transaction context already exists before starting the current transaction, there are several options to specify the execution behavior of a transactional method. The following constants representing propagation behavior are included in the TransactionDefinition definition:

  • TransactionDefinition.PROPAGATION_REQUIRED: If a transaction currently exists, join the transaction; if there is no current transaction, create a new transaction.
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW: Create a new transaction, if there is a current transaction, suspend the current transaction.
  • TransactionDefinition.PROPAGATION_SUPPORTS: If there is a current transaction, join the transaction; if there is no current transaction, continue to run in a non-transactional manner.
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: run in non-transactional mode, if there is a current transaction, suspend the current transaction.
  • TransactionDefinition.PROPAGATION_NEVER: Runs in non-transactional mode and throws an exception if a transaction currently exists.
  • TransactionDefinition.PROPAGATION_MANDATORY: If there is currently a transaction, join the transaction; if there is no current transaction, throw an exception.
  • TransactionDefinition.PROPAGATION_NESTED: If a transaction currently exists, create a transaction to run as a nested transaction of the current transaction; if there is no current transaction, the value is equivalent to TransactionDefinition.PROPAGATION_REQUIRED.

It should be pointed out here that the previous six transaction propagation behaviors were introduced by Spring from EJB, and they share the same concept. And PROPAGATION_NESTED is Spring-specific. The transaction started with PROPAGATION_NESTED is embedded in the external transaction (if there is an external transaction), at this time, the embedded transaction is not an independent transaction, it depends on the existence of the external transaction, only through the external transaction commit, can the internal transaction be caused Commitment of transactions, nested subtransactions cannot be committed individually. If you are familiar with the concept of savepoints in JDBC, nested transactions are easy to understand. In fact, nested sub-transactions are an application of savepoints. A transaction can include multiple savepoints, and each nested sub-transaction is an application of savepoints. sub-transaction. In addition, the rollback of the outer transaction will also cause the rollback of the nested subtransactions.

transaction timeout

The so-called transaction timeout refers to the maximum time allowed for a transaction to execute. If the time limit is exceeded but the transaction has not been completed, the transaction will be automatically rolled back. In the TransactionDefinition, the timeout period is represented by an int value, and its unit is seconds.

read-only properties of transactions

The read-only attribute of a transaction refers to a read-only operation or a read-write operation on a transactional resource. The so-called transactional resources refer to those resources that are managed by transactions, such as data sources, JMS resources, and custom transactional resources. If it is determined that only read-only operations are performed on transactional resources, then we can mark the transaction as read-only to improve the performance of transaction processing. In the TransactionDefinition, the boolean type indicates whether the transaction is read-only.

Rollback rules for transactions

Normally, if an unchecked exception (an exception inherited from RuntimeException) is thrown within a transaction, the transaction will be rolled back by default. If no exception is thrown, or a checked exception is thrown, the transaction is still committed. This is usually the way most developers want to handle it, and it's the default way of doing things in EJBs. However, we can manually control the transaction to commit the transaction when some unchecked exceptions are thrown, or roll back the transaction when some checked exceptions are thrown.

Spring Transaction Management API Analysis

In the Spring framework, there are about 100 APIs related to transaction management, three of which are the most important: TransactionDefinition, PlatformTransactionManager, and TransactionStatus. The so-called transaction management is actually "performing the commit or rollback operation according to the given transaction rules". "Given transaction rule" is represented by TransactionDefinition, "perform commit or rollback operation according to..." is represented by PlatformTransactionManager, and TransactionStatus is used to represent the status of a running transaction. To make an inappropriate analogy, the relationship between TransactionDefinition and TransactionStatus is like the relationship between program and process.

TransactionDef...

This interface has been introduced earlier, it is used to define a transaction. It contains the static properties of the transaction, such as: transaction propagation behavior, timeout, etc. Spring provides us with a default implementation class: DefaultTransactionDefinition, which is suitable for most situations. If this class cannot meet the requirements, you can implement your own transaction definition by implementing the TransactionDefinition interface.

PlatformTrans ...

PlatformTransactionManager is used to perform specific transaction operations. The interface definition is shown in Listing 2:

Listing 2. Main methods defined in the PlatformTransactionManager interface

1

2

3

4

5

6

Public interface PlatformTransactionManager{

  TransactionStatus getTransaction(TransactionDefinition definition)

   throws TransactionException;

   void commit(TransactionStatus status)throws TransactionException;

   void rollback(TransactionStatus status)throws TransactionException;

}

According to the different persistence APIs or frameworks used at the bottom, the main implementation classes of PlatformTransactionManager are roughly as follows:

  • DataSourceTransactionManager: Suitable for data persistence operations using JDBC and iBatis.
  • HibernateTransactionManager: It is suitable for data persistence operations using Hibernate.
  • JpaTransactionManager: Suitable for data persistence operations using JPA.
  • There are also JtaTransactionManager, JdoTransactionManager, JmsTransactionManager and so on.

If we use JTA for transaction management, we can get a container-managed DataSource through JNDI and Spring's JtaTransactionManager. The JtaTransactionManager does not need to know about the DataSource and other specific resources because it will use the global transaction management provided by the container. For other transaction managers, such as DataSourceTransactionManager, it is necessary to provide the underlying data source as its property when defining, that is, DataSource. Corresponding to HibernateTransactionManager is SessionFactory, corresponding to JpaTransactionManager is EntityManagerFactory and so on.

TransactionStatus

The PlatformTransactionManager.getTransaction(…) method returns a TransactionStatus object. The returned TransactionStatus object may represent a new or existing transaction (if there is an eligible transaction in the current call stack). The TransactionStatus interface provides a simple way to control transaction execution and query transaction status. The interface definition is shown in Listing 3:

Listing 3. Main methods defined in the TransactionStatus interface

1

2

3

4

5

public  interface TransactionStatus{

   boolean isNewTransaction();

   void setRollbackOnly();

   boolean isRollbackOnly();

}

Programmatic Transaction Management

An overview of Spring's programmatic transaction management

Before Spring, programmatic transaction management was the only option for POJO-based applications. Anyone who has used Hibernate knows that we need to explicitly call beginTransaction(), commit(), rollback() and other transaction management related methods in the code, which is programmatic transaction management. Through the transaction management API provided by Spring, we can flexibly control the execution of transactions in the code. Under the hood, Spring still delegates transaction operations to the underlying persistence framework.

Programmatic transaction management based on low-level API

According to the three core interfaces of PlatformTransactionManager, TransactionDefinition and TransactionStatus, we can conduct transaction management programmatically. Example code is shown in Listing 4:

Listing 4. Example code for transaction management based on the underlying API

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

public class BankServiceImpl implements BankService {

private BankDao bankDao;

private TransactionDefinition txDefinition;

private PlatformTransactionManager txManager;

......

public boolean transfer(Long fromId, Long toId, double amount) {

TransactionStatus txStatus = txManager.getTransaction(txDefinition);

boolean result = false;

try {

result = bankDao.transfer(fromId, toId, amount);

txManager.commit(txStatus);

} catch (Exception e) {

result = false;

txManager.rollback(txStatus);

System.out.println("Transfer Error!");

}

return result;

}

}

The corresponding configuration file is shown in Listing 5:

Listing 5. Example configuration file for transaction management based on the underlying API

1

2

3

4

5

6

7

8

9

<bean id="bankService" class="footmark.spring.core.tx.programmatic.origin.BankServiceImpl">

<property name="bankDao" ref="bankDao"/>

<property name="txManager" ref="transactionManager"/>

<property name="txDefinition">

<bean class="org.springframework.transaction.support.DefaultTransactionDefinition">

<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>

</bean>

</property>

</bean>

As shown above, we have added two properties to the class: one is a property of type TransactionDefinition, which is used to define a transaction; the other is a property of type PlatformTransactionManager, which is used to perform transaction management operations.

If the method needs to implement transaction management, we first need to start a transaction before the method starts executing, and call the PlatformTransactionManager.getTransaction(...) method to start a transaction. After the transaction is created and started, you can start writing business logic code, and then perform the commit or rollback of the transaction where appropriate.

Programmatic Transaction Management Based on TransactionTemplate

通过前面的示例可以发现,这种事务管理方式很容易理解,但令人头疼的是,事务管理的代码散落在业务逻辑代码中,破坏了原有代码的条理性,并且每一个业务方法都包含了类似的启动事务、提交/回滚事务的样板代码。幸好,Spring 也意识到了这些,并提供了简化的方法,这就是 Spring 在数据访问层非常常见的模板回调模式。如清单6所示:

清单6. 基于 TransactionTemplate 的事务管理示例代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

public class BankServiceImpl implements BankService {

private BankDao bankDao;

private TransactionTemplate transactionTemplate;

......

public boolean transfer(final Long fromId, final Long toId, final double amount) {

return (Boolean) transactionTemplate.execute(new TransactionCallback(){

public Object doInTransaction(TransactionStatus status) {

Object result;

try {

result = bankDao.transfer(fromId, toId, amount);

} catch (Exception e) {

status.setRollbackOnly();

result = false;

System.out.println("Transfer Error!");

}

return result;

}

});

}

}

相应的XML配置如下:

清单 7. 基于 TransactionTemplate 的事务管理示例配置文件

1

2

3

4

5

<bean id="bankService"

class="footmark.spring.core.tx.programmatic.template.BankServiceImpl">

<property name="bankDao" ref="bankDao"/>

<property name="transactionTemplate" ref="transactionTemplate"/>

</bean>

TransactionTemplate 的 execute() 方法有一个 TransactionCallback 类型的参数,该接口中定义了一个 doInTransaction() 方法,通常我们以匿名内部类的方式实现 TransactionCallback 接口,并在其 doInTransaction() 方法中书写业务逻辑代码。这里可以使用默认的事务提交和回滚规则,这样在业务代码中就不需要显式调用任何事务管理的 API。doInTransaction() 方法有一个TransactionStatus 类型的参数,我们可以在方法的任何位置调用该参数的 setRollbackOnly() 方法将事务标识为回滚的,以执行事务回滚。

根据默认规则,如果在执行回调方法的过程中抛出了未检查异常,或者显式调用了TransacationStatus.setRollbackOnly() 方法,则回滚事务;如果事务执行完成或者抛出了 checked 类型的异常,则提交事务。

TransactionCallback 接口有一个子接口 TransactionCallbackWithoutResult,该接口中定义了一个 doInTransactionWithoutResult() 方法,TransactionCallbackWithoutResult 接口主要用于事务过程中不需要返回值的情况。当然,对于不需要返回值的情况,我们仍然可以使用 TransactionCallback 接口,并在方法中返回任意值即可。

声明式事务管理

Spring 的声明式事务管理概述

Spring 的声明式事务管理在底层是建立在 AOP 的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。因为事务管理本身就是一个典型的横切逻辑,正是 AOP 的用武之地。Spring 开发团队也意识到了这一点,为声明式事务提供了简单而强大的支持。

声明式事务管理曾经是 EJB 引以为傲的一个亮点,如今 Spring 让 POJO 在事务管理方面也拥有了和 EJB 一样的待遇,让开发人员在 EJB 容器之外也用上了强大的声明式事务管理功能,这主要得益于 Spring 依赖注入容器和 Spring AOP 的支持。依赖注入容器为声明式事务管理提供了基础设施,使得 Bean 对于 Spring 框架而言是可管理的;而 Spring AOP 则是声明式事务管理的直接实现者,这一点通过清单8可以看出来。

通常情况下,笔者强烈建议在开发中使用声明式事务,不仅因为其简单,更主要是因为这样使得纯业务代码不被污染,极大方便后期的代码维护。

和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

下面就来看看 Spring 为我们提供的声明式事务管理功能。

基于 TransactionInter... 的声明式事务管理

最初,Spring 提供了 TransactionInterceptor 类来实施声明式事务管理功能。先看清单8的配置文件:

清单 8. 基于 TransactionInterceptor 的事务管理示例配置文件

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

<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>

首先,我们配置了一个 TransactionInterceptor 来定义相关的事务规则,他有两个主要的属性:一个是 transactionManager,用来指定一个事务管理器,并将具体事务相关的操作委托给它;另一个是 Properties 类型的 transactionAttributes 属性,它主要用来定义事务规则,该属性的每一个键值对中,键指定的是方法名,方法名可以使用通配符,而值就表示相应方法的所应用的事务属性。

指定事务属性的取值有较复杂的规则,这在 Spring 中算得上是一件让人头疼的事。具体的书写规则如下:

1

传播行为 [,隔离级别] [,只读属性] [,超时属性] [不影响提交的异常] [,导致回滚的异常]

  • 传播行为是唯一必须设置的属性,其他都可以忽略,Spring为我们提供了合理的默认值。
  • 传播行为的取值必须以“PROPAGATION_”开头,具体包括:PROPAGATION_MANDATORY、PROPAGATION_NESTED、PROPAGATION_NEVER、PROPAGATION_NOT_SUPPORTED、PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_SUPPORTS,共七种取值。
  • 隔离级别的取值必须以“ISOLATION_”开头,具体包括:ISOLATION_DEFAULT、ISOLATION_READ_COMMITTED、ISOLATION_READ_UNCOMMITTED、ISOLATION_REPEATABLE_READ、ISOLATION_SERIALIZABLE,共五种取值。
  • 如果事务是只读的,那么我们可以指定只读属性,使用“readOnly”指定。否则我们不需要设置该属性。
  • 超时属性的取值必须以“TIMEOUT_”开头,后面跟一个int类型的值,表示超时时间,单位是秒。
  • 不影响提交的异常是指,即使事务中抛出了这些类型的异常,事务任然正常提交。必须在每一个异常的名字前面加上“+”。异常的名字可以是类名的一部分。比如“+RuntimeException”、“+tion”等等。
  • 导致回滚的异常是指,当事务中抛出这些类型的异常时,事务将回滚。必须在每一个异常的名字前面加上“-”。异常的名字可以是类名的全部或者部分,比如“-RuntimeException”、“-tion”等等。

以下是两个示例:

1

2

3

4

<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",表示事务不是只读的。

1

<property name="test">PROPAGATION_REQUIRED,readOnly</property>

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

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

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

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

为了缓解这个问题,Spring 为我们提供了 TransactionProxyFactoryBean,用于将TransactionInterceptor 和 ProxyFactoryBean 的配置合二为一。如清单9所示:

清单9. 基于 TransactionProxyFactoryBean 的事务管理示例配置文件

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

<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 经典的声明式事务管理。相信在早期使用 Spring 的开发人员对这种配置声明式事务的方式一定非常熟悉。

但是,显式为每一个业务类配置一个 TransactionProxyFactoryBean 的做法将使得代码显得过于刻板,为此我们可以使用自动创建代理的方式来将其简化,使用自动创建代理是纯 AOP 知识,请读者参考相关文档,不在此赘述。

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

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

如清单10所示:

清单10. 基于 <tx> 的事务管理示例配置文件

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

<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>

如果默认的事务属性就能满足要求,那么代码简化为如清单 11 所示:

清单 11. 简化后的基于 <tx> 的事务管理示例配置文件

1

2

3

4

5

6

7

8

9

10

11

12

13

<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”。

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

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

清单12. 基于 @Transactional 的事务管理示例配置文件

1

2

3

4

@Transactional(propagation = Propagation.REQUIRED)

public boolean transfer(Long fromId, Long toId, double amount) {

return bankDao.transfer(fromId, toId, amount);

}

Spring 使用 BeanPostProcessor 来处理 Bean 中的标注,因此我们需要在配置文件中作如下声明来激活该后处理 Bean,如清单13所示:

清单13. 启用后处理Bean的配置

1

<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 的声明式事务管理方式,但是,学习这两种方式非常有利于对底层实现的理解。

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

结束语

本教程的知识点大致总结如下:

  • 基于 TransactionDefinition、PlatformTransactionManager、TransactionStatus 编程式事务管理是 Spring 提供的最原始的方式,通常我们不会这么写,但是了解这种方式对理解 Spring 事务管理的本质有很大作用。
  • 基于 TransactionTemplate 的编程式事务管理是对上一种方式的封装,使得编码更简单、清晰。
  • 基于 TransactionInterceptor 的声明式事务是 Spring 声明式事务的基础,通常也不建议使用这种方式,但是与前面一样,了解这种方式对理解 Spring 声明式事务有很大作用。
  • 基于 TransactionProxyFactoryBean 的声明式事务是上中方式的改进版本,简化的配置文件的书写,这是 Spring 早期推荐的声明式事务管理方式,但是在 Spring 2.0 中已经不推荐了。
  • 基于 <tx> 和 <aop> 命名空间的声明式事务管理是目前推荐的方式,其最大特点是与 Spring AOP 结合紧密,可以充分利用切点表达式的强大支持,使得管理事务更加灵活。
  • 基于 @Transactional 的方式将声明式事务管理简化到了极致。开发人员只需在配置文件中加上一行启用相关后处理 Bean 的配置,然后在需要实施事务管理的方法或者类上使用 @Transactional 指定事务规则即可实现事务管理,而且功能也不必其他方式逊色。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325520943&siteId=291194637