目录
1.什么是事务?
2.事务的特性ACID了解吗?
3.谈谈Spring对事务的支持
4.Spring支持两种方式的事务管理
4.1 编程式事务管理
4.2 声明式事务管理
5.Spring事务管理接口介绍
6.事务隔离级别
7.Spring事务传播机制及实例
7.1 Spring事务传播机制有哪些呢?
事务定义 :将一组操作封装成一个执行单元(封装到一起),要么全部成功,要么全部失败。
为什么要使用到事务?
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作就是:
- 将小明的余额减少 1000 元。
- 将小红的余额增加 1000 元。
万一在这两个操作之间突然出现错误比如银行系统崩溃或者网络故障,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
public class OrdersService {
private AccountDao accountDao;
public void setOrdersDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT, readOnly = false, timeout = -1)
public void accountMoney() {
//小红账户多1000
accountDao.addMoney(1000,xiaohong);
//模拟突然出现的异常,比如银行中可能为突然停电等等
//如果没有配置事务管理的话会造成,小红账户多了1000而小明账户没有少钱
int i = 10 / 0;
//小明账户少1000
accountDao.reduceMoney(1000,xiaoming);
}
}
数据库事务的 ACID 四大特性是事务的基础,下面简单来了解一下。
Atomicity
):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;Consistency
):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;Isolation
):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;Durability
):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。这里要额外补充一点:只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是手段,C 是目的!
⚠️注意:你的程序是否支持事务首先取决于数据库 ,比如使用 MySQL 的话,如果你选择的是 innodb 引擎,那么恭喜你,是可以支持事务的。但是,如果你的 MySQL 数据库使用的是 myisam 引擎的话,那不好意思,从根上就是不支持事务的。
这里存在一个比较重要的问题:MySQL 怎么保证原子性的?
答:我们知道如果想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,在 MySQL 中,恢复机制是通过 回滚日志(undo log) 实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中遇到异常的话,我们直接利用 回滚日志 中的信息将数据回滚到修改之前的样子即可!并且,回滚日志会先于数据持久化到磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚之前未完成的事务。
通过 TransactionTemplate
或者TransactionManager
手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。
使用TransactionTemplate
进行编程式事务管理的示例代码如下:
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
// .... 业务代码
} catch (Exception e){
//回滚
transactionStatus.setRollbackOnly();
}
}
});
}
使用 TransactionManager
进行编程式事务管理的示例代码如下:
@Autowired
private PlatformTransactionManager transactionManager;
public void testTransaction() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// .... 业务代码
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
}
推荐使用(代码侵入性最小),实际是通过 AOP 实现(基于@Transactional
的全注解方式使用最多)。
使用 @Transactional
注解进行事务管理的示例代码如下:
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
B b = new B();
C c = new C();
b.bMethod();
c.cMethod();
}
Spring 框架中,事务管理相关最重要的 3 个接口如下:
PlatformTransactionManager
:(平台)事务管理器,Spring 事务策略的核心。TransactionDefinition
:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。TransactionStatus
:事务运行状态。我们可以把 PlatformTransactionManager
接口可以被看作是事务上层的管理者,而 TransactionDefinition
和 TransactionStatus
这两个接口可以看作是事务的描述。
PlatformTransactionManager
会根据 TransactionDefinition
的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus
接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。
PlatformTransactionManager:事务管理接口
Spring 并不直接管理事务,而是提供了多种事务管理器 。Spring 事务管理器的接口是:PlatformTransactionManager
。
通过这个接口,Spring 为各个平台如:JDBC(DataSourceTransactionManager
)、Hibernate(HibernateTransactionManager
)、JPA(JpaTransactionManager
)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
PlatformTransactionManager
接口的具体实现如下:
PlatformTransactionManager
接口中定义了三个方法:
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface PlatformTransactionManager {
//获得事务
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//提交事务
void commit(TransactionStatus var1) throws TransactionException;
//回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
事务隔离级别是对事务 4 大特性中隔离性的具体体现,使用事务隔离级别可以控制并发事务在同时执行时的某种行为。
我们可以先来回顾一下MYSQL的4种事务隔离级别
1. READ UNCOMMITTED:读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数 据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们 把该级别读取到的数据称之为脏数据,把这个问题称之为脏读。
2. READ COMMITTED:读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此 它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读。
3. REPEATABLE READ:可重复读,是 MySQL 的默认事务隔离级别,它能确保同一事务多次查询的结果 一致。但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为 它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束 的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读 (Phantom Read)。
4. SERIALIZABLE:序列化,事务最高隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏 读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。
- 脏读:一个事务读取到了另一个事务修改的数据之后,后一个事务又进行了回滚操作,从而导致第一个事务读取的数据是错误的。
- 不可重复读:一个事务两次查询得到的结果不同,因为在两次查询中间,有另一个事务把数据修改了。
- 幻读:一个事务两次查询中得到的结果集不同,因为在两次查询中另一个事务有新增了一部分数据。
相比于 MySQL 的事务隔离级别,Spring 中多了一种 DEFAULT 的事务隔离级别。
接下来我们来看看Spring的5种事务隔离级别
1. Isolation.DEFAULT:Spring 中默认的事务隔离级别,以连接的数据库的事务隔离级别为准;
2. Isolation.READ_UNCOMMITTED:读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读;
3. Isolation.READ_COMMITTED:读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读;
4. Isolation.REPEATABLE_READ:可重复读,它能确保同一事务多次查询的结果一致。但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读 (Phantom Read);
5. Isolation.SERIALIZABLE:串行化,最高的事务隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。
Spring 中事务隔离级别只需要设置 @Transactional 里的 isolation 属性即可,具体实现代码如下:
@RequestMapping("/save")
@Transactional(isolation = Isolation.SERIALIZABLE)
public Object save(User user) {
// 业务实现
}
具体操作如下图所示:
事务隔离级别是保证多个并发事务执行的可控性的(稳定性的)
而事务传播机制是保证一个事务在多个调 用方法间的可控性的(稳定性的)。或者说包含多个事务的方法在相互调用时,事务是如何在这些方法间传播的。
从上述的介绍中可以看出,事务隔离级别描述的是多个事务同时执行时的某种行为,它们的调用流程如下:
而事务传播机制是描述,多个包含了事务的方法在相互调用时事务的传播行为,它们的调用流程如下:
所以事务隔离级别描述的是纵向事务并发调用时的行为模式,而事务传播机制描述的是横向事务传递时的行为模式。
Spring 事务传播机制的级别包含以下 7 种:
以上 7 种传播机制,可分为以下 3 类:
看到这里,你可能会说:说了这么多,我也看不懂啊,即使看懂了,我也记不住啊?这要怎么办?
没关系,接下来我们用一个例子,来说明这 3 类事务传播机制的区别。
以情侣之间是否要买房为例,我们将以上 3 类事务传播机制可以看作是恋爱中的 3 类女生类型:
这三类女生如下图所示:
相信你看到了这里对于事务与Spring事务传播机制有了一个较为深入的理解