分布式事务系列第三篇,Spring 事务概述

本文是我们分布式事务系列的第三篇,这篇文章来和大家捋一捋 Spring 框架中的事务体系。前面两篇文章大家可以参考:

  1. 分布式事务开局第一篇,从数据库事务隔离级别说起
  2. 分布式事务系列第二篇,回顾 Jdbc 事务

Spring 作为 Java 开发中的基础设施,对于事务也提供了很好的支持,总体上来说,Spring 支持两种类型的事务,声明式事务和编程式事务。

编程式事务类似于 Jdbc 事务的写法,需要将事务的代码嵌入到业务逻辑中,这样代码的耦合度较高,而声明式事务通过 AOP 的思想能够有效的将事务和业务逻辑代码解耦,因此在实际开发中,声明式事务得到了广泛的应用,而编程式事务则较少使用,考虑到文章内容的完整,本文对两种事务方式都会介绍。

1.核心类

Spring 中对事务的处理,涉及到三个核心类:

  1. PlatformTransactionManager
  2. TransactionDefinition
  3. TransactionStatus

这三个核心类是Spring处理事务的核心类。

1.1 PlatformTransactionManager

PlatformTransactionManager 是事务处理的核心,它有诸多的实现类,如下:

3-1

PlatformTransactionManager 的定义如下:

1
2
3
4
5
public interface PlatformTransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition);
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}

可以看到 PlatformTransactionManager 中定义了基本的事务操作方法,这些事务操作方法都是平台无关的,具体的实现都是由不同的子类来实现的。PlatformTransactionManager 中主要有如下三个方法:

1.getTransaction()

getTransaction() 是根据传入的 TransactionDefinition 获取一个事务对象,TransactionDefinition 中定义了一些事务的基本规则,例如传播性、隔离级别等。

2.commit()

commit() 方法用来提交事务。

3.rollback()

rollback() 方法用来回滚事务。

PlatformTransactionManager 有多个实现类,这些实现类就是 Spring 内置的事务管理器,如下:

3-2

1.2 TransactionDefinition

TransactionDefinition 用来描述事务的具体规则,该类中的方法如下:

3-2

可以看到一共有五个方法:

  1. getIsolationLevel(),获取事务的隔离级别
  2. getName(),获取事务的名称
  3. getPropagationBehavio(),获取事务的传播性
  4. getTimeout(),获取事务的超时时间
  5. isReadOnly(),获取事务是否是只读事务

TransactionDefinition 也有诸多的实现类,如下:

3-3

如果开发者使用了编程式事务的话,直接使用 DefaultTransactionDefinition 即可。

1.2.1 隔离级别

这里事务的隔离级别,主要有如下几种:

1
2
3
4
5
6
7
8
9
10
public enum Isolation {
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
private final int value;
Isolation(int value) { this.value = value; }
public int value() { return this.value; }
}

其中,DEFAULT 表示使用数据库默认的事务隔离级别,其他的隔离级别则和数据库中的隔离级别一一对应。

1.2.2 传播性

关于事务的传播性,Spring 主要定义了如下几种:

1
2
3
4
5
6
7
8
9
10
11
12
public enum Propagation {
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
NEVER(TransactionDefinition.PROPAGATION_NEVER),
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) { this.value = value; }
public int value() { return this.value; }
}

具体含义如下:

传播性 描述
REQUIRED 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
SUPPORTS 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
MANDATORY 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
REQUIRES_NEW 创建一个新的事务,如果当前存在事务,则把当前事务挂起
NOT_SUPPORTED 以非事务方式运行,如果当前存在事务,则把当前事务挂起
NEVER 以非事务方式运行,如果当前存在事务,则抛出异常
NESTED 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED

其中,PROPAGATION_NESTED 和 PROPAGATION_REQUIRES_NEW 区别如下:

1. 开启事务的多少,PROPAGATION_REQUIRES_NEW 会开启一个新事务,外部事务挂起,里面的事务独立执行。PROPAGATION_NESTED 为父子事务,实际上是借助 jdbc 的 savepoint 实现的,属于同一个事物。 
2. PROPAGATION_NESTED 的回滚可以总结为,子事务回滚到 savepoint,父事务可选择性回滚或者不不滚;父事务回滚子事务一定回滚。PROPAGATION_REQUIRES_NEW 则是不同事物,嵌套事务之间没有必然联系是否回滚都由自己决定。

1.3 TransactionStatus

TransactionStatus 可以直接理解为事务本身,该接口源码如下:

1
2
3
4
5
6
7
8
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}
  1. isNewTransaction() 方法获取当前事务是否是一个新事务。
  2. hasSavepoint() 方法判断是否存在 savePoint()。
  3. setRollbackOnly() 方法设置事务必须回滚。
  4. isRollbackOnly() 方法获取事务只能回滚。
  5. flush() 方法将底层会话中的修改刷新到数据库,一般用于 Hibernate/JPA 的会话,对如 JDBC 类型的事务无任何影响。
  6. isCompleted() 方法用来获取是一个事务是否结束。

2.编程式事务

编程式事务并非主流用法,但是为了使我们的知识点完整,这里还是给读者介绍下编程式事务的用法。本系列所有案例都是基于 SpringBoot,因此涉及到 SpringBoot 相关的知识点,默认都认为读者都懂。如果大家对 Spring Boot 尚不熟悉的话,可以先看看松哥关于 Spring Boot 的视频教程:Spring Boot + Vue 视频教程

编程式事务整体上来说有两种实现方式,下面分别予以介绍。

2.1 基于 Spring 基本的 API

这里还是使用前文的转账数据库,依然模拟一个转账操作。

本案例使用的数据库操作库为 Spring 自带的 JdbcTemplate,当然,读者也可以使用 MyBatis 或者其他库。JdbcTemplate 配置过程这里就省略了(不懂配置,可以参考Spring Boot 整合 JdbcTemplate),我们来直接看转账的核心操作,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
public class TransferService {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
PlatformTransactionManager txManager;

public void transfer() {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
TransactionStatus status = txManager.getTransaction(definition);
try {
jdbcTemplate.update("update user set account=account+100 where username='zhangsan'");
int i = 1 / 0;
jdbcTemplate.update("update user set account=account-100 where username='lisi'");
txManager.commit(status);
} catch (DataAccessException e) {
e.printStackTrace();
txManager.rollback(status);
}
}
}

关于这段代码,我做如下几点解释:

  1. 首先注入 JdbcTemplate,这是由 SpringBoot 自动配置类提供的,这个很简单,这里不赘述。
  2. 接下来注入 PlatformTransactionManager,本案例中 PlatformTransactionManager 的实现类是 DataSourceTransactionManager,这是由于开发者没有提供 PlatformTransactionManager 的实例,并且当前项目 claspath 下存在 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
@Configuration
@ConditionalOnClass({ JdbcTemplate.class, PlatformTransactionManager.class })
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceTransactionManagerAutoConfiguration {

@Configuration
@ConditionalOnSingleCandidate(DataSource.class)
static class DataSourceTransactionManagerConfiguration {
...
...
@Bean
@ConditionalOnMissingBean(PlatformTransactionManager.class)
public DataSourceTransactionManager transactionManager(
DataSourceProperties properties) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(
this.dataSource);
if (this.transactionManagerCustomizers != null) {
this.transactionManagerCustomizers.customize(transactionManager);
}
return transactionManager;
}
}
}
  1. 在 transfer 方法中,首先定义一个 DefaultTransactionDefinition,然后定义事务的隔离级别,再通过 txManager 中的 getTransaction 方法获取一个 TransactionStatus 的实例,最后在 try..cache 中处理业务,也方法顺利执行时提交事务,否则回滚事务。

2.2 基于 TransactionTemplate

考虑到基于 Spring 基础事务 API 实现的事务管理是一个模板化的操作,Spring 提供了另外一个类 TransactionTemplate,来简化这个模板操作,在 SpringBoot 的 TransactionAutoConfiguration 类中,可以看到 SpringBoot 已经为开发者提供了 TransactionTemplate 实例,如下:

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
@Configuration
@ConditionalOnClass(PlatformTransactionManager.class)
@AutoConfigureAfter({ JtaAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
Neo4jDataAutoConfiguration.class })
@EnableConfigurationProperties(TransactionProperties.class)
public class TransactionAutoConfiguration {
...
...
@Configuration
@ConditionalOnSingleCandidate(PlatformTransactionManager.class)
public static class TransactionTemplateConfiguration {

private final PlatformTransactionManager transactionManager;
public TransactionTemplateConfiguration(
PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Bean
@ConditionalOnMissingBean
public TransactionTemplate transactionTemplate() {
return new TransactionTemplate(this.transactionManager);
}
}
...
...
}

由于 TransactionTemplate 的实例系统已经配置好了,因此开发者可以直接使用 TransactionTemplate,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
public class TransferService {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
TransactionTemplate tranTemplate;
public void transfer() {
tranTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
jdbcTemplate.update("update user set account=account+100 where username='zhangsan'");
int i = 1 / 0;
jdbcTemplate.update("update user set account=account-100 where username='lisi'");
} catch (DataAccessException e) {
status.setRollbackOnly();
e.printStackTrace();
}
}
});
}
}

直接注入 TransactionTemplate,然后在 execute 方法中添加回调写核心的业务即可,当抛出异常时,将当前事务标注为只能回滚即可。注意,execute 方法中,如果不需要获取事务执行的结果,则直接使用 TransactionCallbackWithoutResult 类即可,如果要获取事务执行结果,则使用 TransactionCallback 即可。

3.声明式事务

编程式事务会干扰到业务逻辑代码,而声明式事务不会,这是声明式事务的最大优势。

声明式事务的使用比较简单,在 SpringBoot 项目中,直接使用 @Transactional 注解即可实现,如下:

1
2
3
4
5
6
7
8
9
10
11
@Service
public class TransferService {
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void transfer() {
jdbcTemplate.update("update user set account=account+100 where username='zhangsan'");
int i = 1 / 0;
jdbcTemplate.update("update user set account=account-100 where username='lisi'");
}
}

开发者可以在 @Transactional 注解中配置事务的隔离级别,传播性等。

参考:

  1. https://blog.csdn.net/Big_Blogger/article/details/70184627

喜欢这篇文章吗?扫码关注公众号【江南一点雨】【江南一点雨】专注于 SPRING BOOT+微服务以及前后端分离技术,每天推送原创技术干货,关注后回复 JAVA,领取松哥为你精心准备的 JAVA 干货!

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×