Spring 的事务控制
# 140.Spring 的事务控制
之前我们是自己实现了事务控制,其实 Spring 本身也有事务控制,我们可以直接拿来用
# 问题分析
在上一篇博客中,其实有一些代码是多余的:
- 释放连接的代码,我们可以在提交或回滚后直接释放链接
- 开启连接的代码,我们可以在获取 connection 的时候,直接开启事务
因此我们剩下了添加事务和回滚事务的代码,这两个写法也是固定的,等我们后续学了 Spring 的事务控制之后,这也可以省略
# 相关概念
JavaEE 体系进行分层开发,事务处理位于业务层,Spring 提供了分层设计业务层的事务处理解决方案。
Spring 框架为我们提供了一组事务控制的接口,具体在后面介绍,这组接口在 spring-tx-5.0.2.RELEASE.jar 中
Spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式实现。我们学习的重点是使用配置的方式实现。
我们可以在 pom.xml 中添加一个依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
2
3
4
5
# 相关 API
# PlatformTransactionManager
我们导入 jar 包后,可以在代码中定义一个 PlatformTransactionManager:
PlatformTransactionManager ptm;
我们可以看其源码:可以看到这是一个接口,有 commit 和 rollback 方法
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface PlatformTransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
2
3
4
5
6
7
该接口实现类有:
- org.springframework.jdbc.datasource.DataSourceTransactionManager 使用 Spring JDBC 或 iBatis 进行持久化数据时使用
- org.springframework.orm.hibernate5.HibernateTransactionManager 使用 Hibernate 版本进行持久化数据时使用
# TransactionDefinition
它是事务的定义信息对象,里面有如下方法:
String getName()
:获取事务对象名称int getlsolationLevel()
:获取事务隔离级别,默认使用的是数据库的级别int getPropagationBehavior()
:获取事务传播行为,例如什么时候使用事务,什么时候不使用int getTimeout()
:获取事务超时时间boolean isReadonly()
:获取事务是否只读
事务隔离级反映事务提交并发访问时的处理态度
- ISOLATION_DEFAULT:默认级别,归属下列某一种
- ISOLATION_READ_UNCOMMITTED:可以读取未提交数据
- ISOLATION_READ_COMMITTED:只能读取已提交数据,解决脏读问题(Oracle 默认级别)
- ISOLATION_REPEATABLE_READ:是否读取其他事务提交修改后的数据,解决不可重复读
问题(MySQL 默认级别) - ISOLATION:是否读取其他事务提交添加后的数据,解决幻影读问题
事务的传播行为
- REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。默认值
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
- MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
- REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
- NEVER:以非事务方式运行,如果当前存在事务,抛出异常
- NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作。
重点关注前两个,第一个是增删改时用的,第二个是查询时用的
超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
是否是只读事务:建议查询时设置为只读。
# TransactionStatus
TransactionStatus 接口描述了某个时间点上事务对象的状态信息,包含有 6 个具体的操作
void flush()
:刷新事务boolean hasSavepoint()
:获取是否是否存在存储点,事务是按步提交的,我们可以自己设置存储点,回滚时就会回滚到这个存储点,在一些大型的复杂的事务面前,需要用到这个boolean isCompleted()
:获取事务是否完成boolean isNewTransaction()
:获取事务是否为新的事务boolean isRollbackOnly()
:获取事务是否回滚void setRollbackOnly()
:设置事务回滚
# 环境准备
我们这次从项目的 demo14 分支上进行开发,并删减相关代码:
- 自己实现的 JdbcDaoSupport.java 类
- jdbctemplate 包
# 配置依赖
我们调整依赖关系如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.peterjxl</groupId>
<artifactId>LearnSpring</artifactId>
<version>1.0-SNAPSHOT</version>
<name>LearnSpring</name>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
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
# 新增 service 接口和实现类
package com.peterjxl;
import com.peterjxl.domain.Account;
public interface IAccountService {
Account findAccountById(Integer accountId);
void transfer(String sourceName, String targetName, Float money);
}
2
3
4
5
6
7
package com.peterjxl.service.impl;
import com.peterjxl.dao.IAccountDao;
import com.peterjxl.domain.Account;
import com.peterjxl.service.IAccountService;
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
Account source = accountDao.findAccountByName(sourceName);
Account target = accountDao.findAccountByName(targetName);
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
accountDao.updateAccount(source);
accountDao.updateAccount(target);
}
}
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
# 配置 bean
<bean id="accountService" class="com.peterjxl.service.impl.AccountServiceImpl">
<!-- 注入AccountDao -->
<property name="accountDao" ref="accountDao"/>
</bean>
2
3
4
# 新增测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
2
3
4
5
6
7
8
9
10
11
12
如果有异常,是不能控制住事务的,接下来的博客我们就演示如何配置。
# 源码
本项目已将源码上传到 GitHub (opens new window) 和 Gitee (opens new window) 上。并且创建了分支 demo17,读者可以通过切换分支来查看本文的示例代码