Spring 事务处理

Spring 支持编程式事务处理和声明式事务处理。

编程式事务处理

基于底层 API

在 Spring 事务管理中提供了非常完善的 API,最核心的三个接口主要是 PlatformTransactionManagerTransactionDefinitionTransactionStatus

关键 API

TransactionDefinition 用于描述控制事务具体行为的事务属性,包括隔离级别、超时时间、是否为只读事务和事务传播规则等,可以通过 XML 配置或注解描述提供;PlatformTransactionManager 根据 TransactionDefinition 提供的事务属性配置信息创建事务;并用 TransactionStatus 描述这个激活事务的状态。

TransactionDefinition

TransactionDefinition 定义了 Spring 兼容的事务属性,以此来对事务管理控制的若干方面进行配置。如

  1. 事务隔离级别

    1. ISOLATION_DEFAULT (使用数据库默认)
    2. ISOLATION_READ_UNCOMMITTED
    3. ISOLATION_READ_COMMITTED
    4. ISOLATION_REPEATABLE_READ
    5. ISOLATION_SERIALIZABLE
  2. 超时时间:TIMEOUT_DEFAULT(默认30秒)

  3. 事务传播行为

    1. PROPAGATION_REQUIRED(默认):加入当前事务,如果当前没有事务,就新建一个事务;
    2. PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行;
    3. PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
    4. PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起;
    5. PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起;
    6. PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常;
    7. PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,就新建一个事务。
  4. 只读状态

TransactionStatus

TransactionStatus 代表了一个事务的具体运行状态。该接口继承自 SavepointManager,提供了如下几个方法:

  1. boolean hasSavepoint():判断当前事务是否在内部创建了一个保存点;
  2. boolean isNewTransaction():判断当前事务是否是一个新的事务;
  3. boolean isCompleted:判断当前事务是否已经结束;
  4. boolean isRollbackOnly():判断当前事务是否已被标识为 rollback-only;
  5. void setRollbackOnly():将当前事务设置为 rollback-only。

PlatformTransactionManager

开始事务后,事务只能提交或回滚,这些操作被归类到 PlatformTransactionManager 接口,该接口只定义三个方法:

  1. TransactionStatus getTransactionManager(TransactionDefinition definition):返回一个已存在的事务或创建一个新的事务;
  2. commit(TransactionStatus status):提交事务,如果事务被标记为 rollback-only,则执行一个事务回滚操作;
  3. rollback(TransactionStatus status):事务回滚。

事务管理实现类

Spring 为不同的持久化框架提供了事务管理实现类,如 DataSourceTransactionManagerJpaTransactionManagerHibernateTransactionManager 等,根据实际需要进行选择。

实例准备

在 maven 中引入相关依赖:

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
52
53
54
55
56
57
58
<properties>
<spring.version>4.3.15.RELEASE</spring.version>
</properties>

<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
</dependencies>

对于项目持久层,使用 Spring JDBC Template,所以在配置文件 spring-dao.xml 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/shiwu?useUnicode=true&amp;characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<context:component-scan base-package="com.imtt.dao"/>
</beans>

说明:实体类 Order 和 Product 分别表示订单和商品,持久层(com.imtt.dao 包下)里的接口及其实现包含了对订单数据表和商品数据表的各种操作。

事务模拟

首先使用事务管理器的实例 transactionManager 调用 getTransaction() 方法开启一个事务,方法的参数是一个事务定义的实例 transactionDefinition,且返回一个事务状态的对象 transactionStatus。接着就可以进行持久化操作了。

事务结束后就调用事务管理器的 commit() 方法,且参数前面拿到的事务状态的对象;如果出现了异常就调用事务管理器的 rollback() 方法,参数仍为事务状态的对象。

代码如下:

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
52
53
package com.imtt.service.impl1;

//持久化相关导入包
import com.imtt.dao.OrderDao;
import com.imtt.dao.ProductDao;
import com.imtt.entity.Order;
import com.imtt.entity.Product;
import com.imtt.service.OrderService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;

import java.util.Date;

@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDao orderDao;

@Autowired
private ProductDao productDao;

@Autowired
private PlatformTransactionManager transactionManager;

@Autowired
private TransactionDefinition transactionDefinition;

public void addOrder(Order order) {
order.setCreateTime(new Date());
order.setStatus("待付款");

//开启事务
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);

try {
//持久化相关操作
orderDao.insert(order);
Product product = productDao.select(order.getProductsId());
product.setStock(product.getStock() - order.getNumber());
productDao.update(product);

//事务提交
transactionManager.commit(transactionStatus);
} catch (Exception e) {
//事务回滚
transactionManager.rollback(transactionStatus);
}
}
}

Spring 配置文件 spring-service1.xml,主要包括事务事务管理器及状态的注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<import resource="spring-dao.xml"/>
<context:component-scan base-package="com.imtt.service.impl1"/>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionDefinition" class="org.springframework.transaction.support.DefaultTransactionDefinition">
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
</bean>
</beans>

基于 TransactionTemplate

使用基于底层 API 的方式每次都想调用事务管理器开启一个事务就显得太麻烦,这时就可以使用 TransactionTemplate 来简化操作。

TransactionTemplate 继承了 DefaultTransactionDefinition,里面有一个 execute 方法(public <T> T execute(TransactionCallback<T> action) throws TransactionException),其参数是一个表示回调函数的接口。在这个方法中包含了开启事务和提交事务的代码。

新的处理方法示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
@Autowired
private TransactionTemplate transactionTemplate;

public void addOrder(final Order order) {
order.setCreateTime(new Date());
order.setStatus("待付款");

transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
try {
//持久化相关操作
orderDao.insert(order);
Product product = productDao.select(order.getProductsId());
product.setStock(product.getStock() - order.getNumber());
productDao.update(product);
} catch (Exception e) {
status.setRollbackOnly();
}
return null;
}
});
}
...

生成一个 TransactionTemplate 的实例,调用 execute 方法,方法里不需要显示的进行开启事务和提交事务的操作。方法的参数使用一个 TransactionCallback 的匿名内部类,其 doInTransaction(TransactionStatus status) 方法参数为一个事务状态的对象,在方法里进行我们自己的持久化操作,如果出现异常,通过事务状态的 setRollbackOnly() 方法将事务设置只能回滚状态。

当然在 Spring 配置中进行配置:

1
2
3
4
5
6
7
8
...
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
...

声明式事务处理

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

建议在开发中使用声明式事务,是因为这样可以使得业务代码纯粹干净,方便后期的代码维护。

且以基于 <tx> 命名空间 和 基于 @Transactional 这两种方式最常用。

基于 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
27
28
29
30
31
32
33
package com.imtt.service.impl3;

import com.imtt.dao.OrderDao;
import com.imtt.dao.ProductDao;
import com.imtt.entity.Order;
import com.imtt.entity.Product;
import com.imtt.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.Date;

@Service
public class OrderServiceImp3 implements OrderService {
@Autowired
private OrderDao orderDao;

@Autowired
private ProductDao productDao;

public void addOrder(final Order order) {
order.setCreateTime(new Date());
order.setStatus("待付款");

orderDao.insert(order);
Product product = productDao.select(order.getProductsId());
product.setStock(product.getStock() - order.getNumber());
productDao.update(product);
}
}

只需要在配置文件中配置拦截器:

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
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<import resource="spring-dao.xml"/>
<context:component-scan base-package="com.imtt.service.impl3"/>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="orderServiceTarget" class="com.imtt.service.impl3.OrderServiceImp3"/>
<!--拦截器设置-->
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED, readOnly</prop>
</props>
</property>
</bean>
<bean id="orderService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="orderServiceTarget"/>
<property name="interceptorNames">
<list>
<idref bean="transactionInterceptor"/>
</list>
</property>
</bean>
</beans>

在拦截器配置(id="transactionInterceptor")中配置了事务处理管理器及事务属性(name="transactionAttributes"),如上面代码中配置了 get 开头的方法名(<prop key="get*">PROPAGATION_REQUIRED, readOnly</prop>)以PROPAGATION_REQUIRED、只读(readOnly)的方法。

基于 TransactionProxyFactoryBean

上面基于 TransactionInterceptor 方式的配置文件内容实在是太长了,Spring 可以通过 TransactionProxyFactoryBean 进行简化:把拦截器配置和增强以后的配置(id="orderService")结合起来。

配置如下:

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
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<import resource="spring-dao.xml"/>

<context:component-scan base-package="com.imtt.service.impl3"/>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<bean id="orderServiceTarget" class="com.imtt.service.impl3.OrderServiceImp3"/>

<!--拦截器设置-->
<bean id="orderService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED, readOnly</prop>
<prop key="fin*">PROPAGATION_REQUIRED, readOnly</prop>
<prop key="set*">PROPAGATION_REQUIRED</prop>
</props>
</property>
<property name="target" ref="orderServiceTarget"/>
</bean>
</beans>

基于 <tx> 命名空间

和前面一样所有方式都需要配置事务管理器(id="transactionManager"),接着配置通知:

  • <tx:advice>:配置通知的内容是什么;
  • <aop:config>:配置什么地方需要通知(<aop:pointcut>),并且如通知内容关联起来(<aop:advisor>)。
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
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<import resource="spring-dao.xml"/>
<context:component-scan base-package="com.imtt.service.impl3"/>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!--配置通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" propagation="REQUIRED" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.imtt.service.impl3.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
</beans>

基于 @Transactional 注解

配置文件中需要 <tx:annotation-driven 来注解事务并制定事务管理器。

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
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<import resource="spring-dao.xml"/>

<context:component-scan base-package="com.imtt.service.impl6"/>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

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

在业务代码中的方法添加 @Transactional 注解,也可以在注解中添加其它信息如事务传播行为(@Transactional(propagation = Propagation.REQUIRED))。

坚持原创技术分享,您的支持将鼓励我继续创作!