事务:事务是一组操作的集合,是不可分割的基本单位,要么全部成功,要么全部失败
我们在MySQL中使用事务有三步操作:开启事务,提交事务,回滚事务
-- 开启事务
start transaction;
-- 执行具体业务
-- 提交事务
commit;
-- 回滚事务
rollback
事务的四大特性(ACID): 原子性、一致性、持久性、隔离性
原子性(Atomicity):一个事务的操作,要么全部完成,要么全部不完成
一致性(Consistency):事务开始之前和事务结束之后,数据库的完整性没有被破坏
持久性(Isolation):事务提交后,对数据的修改就是永久的
隔离性(Durability):数据库允许多个并发事务同时对其数据读写和修改的能力,隔离性可以防止多个事务并发执行时导致的数据不一致
隔离级别是可以设置的
事务的隔离级别: 在数据库中,多个事务并发执行时,各个事务之间是相互隔离的,每个事务之间都不能相互干扰。为了保证并发事务的正确性和一致性,数据库管理系统(DBMS)提供了四种事务隔离级别,分别为读未提交、读已提交、可重复读和串行化。
读未提交(Read Uncommitted)
在该隔离级别下,一个事务可以读取另一个事务尚未提交的数据,不仅如此,它可能还会读到一些脏数据(Dirty Read)。在该级别下,数据的一致性和完整性得不到保障,不推荐使用。
读已提交(Read Committed)
在该隔离级别下,一个事务只能读取另一个事务已经提交的数据,避免了脏读的问题,但是可能会出现不可重复读(Non-Repeatable Read)的问题。即同一事务中,多次读取同一数据可能会得到不同的结果,因为其他事务可能在两次读取之间提交了修改。
可重复读(Repeatable Read)
在该隔离级别下,一个事务在执行期间多次读取同一数据时,能够保证所读取的数据一定是事务开始时的状态,避免了不可重复读的问题,但是可能会出现幻读(Phantom Read)问题,即两次查询间新增数据的影响。
串行化(Serializable)
在该隔离级别下,所有事务串行执行,避免了脏读、不可重复读和幻读的问题,但是实际应用中可能会导致性能严重下降,因此该级别一般只在特殊情况下使用。
我们可以使用以下SQL查询全局事务隔离级别和当前连接的事务隔离级别:
select @@global.tx_isolation,@@tx_isolation;
Spring中的事务分为两类:
1.编程式事务(手动操作)
2.声明式事务(自动提交事务)
SpringBoot内置了两个对象,DataSourceTransactionManager用来获取事务(开启事务、提交事务、回滚事务)。TransactionDefinition是事务的属性,在获取事务时,需要将TransactionDefinition传递进去获取一个TransactionStatus
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private DataSourceTransactionManager transactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/del")
public int del(Integer id) {
if(id != null && id > 0) {
//开启事务
TransactionStatus transactionStatus =
transactionManager.getTransaction(transactionDefinition);
//删除用户业务操作
int result = userService.del(id);
System.out.println("删除了: " + result);
// 提交事务/回滚事务
// transactionManager.commit(transactionStatus); //提交事务
transactionManager.rollback(transactionStatus); //回滚事务
}
return 0;
}
}
数据库原始数据:
这里显示操作成功,我们来看看我们数据库数据发生变化了没
我们可以发现,事务成功的进行了回滚,但是这样的方式太繁琐了,我们来学习更简单的声明式事务
声明式事务我们只需要在方法上@Transactional注解就可以实现,无需手动进行开启事务和提交事务,进入方法自动开启,执行完毕自动提交,发生异常后会自动回滚事务
@RestController
@RequestMapping("/user2")
public class UserController2 {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/del")
public int del(Integer id) {
if(id == null || id <= 0) {
return 0;
}
return userService.del(id);
}
}
这种是没有异常的异常,我们会进行commit,我们目前数据库数据:
我们可以发现成功的删除了数据库的一条数据:
我们来在业务中加上一段异常代码:
@RestController
@RequestMapping("/user2")
public class UserController2 {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/del")
public int del(Integer id) {
if(id == null || id <= 0) {
return 0;
}
int result = userService.del(id);
int n = 1 / 0; //异常业务
return result;
}
}
我们可以看到我们的控制台信息,成功的进行了删除操作
同时报了一个算术异常
我们查看数据库信息,发现当发生异常时会自动进行回滚操作。
@Transactional的作用范围:@Transactional既可以用来修饰方法也可以用来修饰类
1.修饰方法:只能应用到public方法上,否则不生效,推荐用法
2.修饰类:表明该注解对所有的public方法都生效
我们可以通过设置@Transactional的一些参数来决定事务的一些具体的功能
Spring的事务隔离级别是有以下5种的
1.DEFAULT:使用底层数据库默认的隔离级别。
2.READ_UNCOMMITTED:允许读取还未提交的数据,会出现脏读、不可重复读和幻读等问题。
3.READ_COMMITTED:只能读取已经提交的数据,避免了脏读的问题,但是可能出现不可重复读和幻读的问题。
4.REPEATABLE_READ:保证同一事务中多次读取同一记录结果是一致的,避免了脏读和不可重复读的问题,但是仍然可能出现幻读的问题。
5.SERIALIZABLE:最高的隔离级别,保证事务串行执行,避免了脏读、不可重复读和幻读等问题,但是影响系统性能。
默认情况下,Spring 会使用底层数据库的默认隔离级别,通常是READ_COMMITTED 级别。可以通过事务管理器的 setDefaultTransactionIsolation() 方法或在事务注解中使用 isolation 属性来设置隔离级别,例如:
@Transactional(isolation = Isolation.DEFAULT)
@RequestMapping("/del")
public int del(Integer id) {
}
常见的事务失效场景有以下三种:
1.异常捕获处理: @Transactional 在异常被捕获的情况下,不会进行事务自动回滚
我们进行操作之前的数据库数据,我们进行try,catch处理
我们发现程序抛出了异常,但并没有进行回滚操作
原因:事务通知只有自己捕捉到了目标抛出的异常,才能进行后续的回滚操作,如果目标自己处理掉了异常,事务无法知悉
解决方案: 在catch块中添加throw new RuntimeException(e)抛出
我们将数据恢复
我们来进行操作时,进行了回滚操作
2.抛出检查异常:
我们的事务未进行回滚操作
原因:Spring默认值只会回滚非检查异常
解决方案: 配置rollbackFor属性
在进行操作时,事务成功的进行了回滚操作
3.非public方法导致的事务失效:
事务没有进行回滚操作
原因:Spring为方法创建代理,添加事务通知、前提条件都是该方法为public
解决方案: 改为Public
@Transactional 是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。
@Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到异常,会进行回滚业务
@Transactional 具体执行细节如下图所示:
Spring事务传播机制定义了多个包含事务的方法,相互调用时
为什么需要事务传播机制?事务隔离级别不够用吗?
事务隔离级别是保证多个并发事务执行是可控的,而事务传播机制是保证一个事务在多个调用方法间是可控的。
事务隔离级别是为了解决多个事务同时调用一个数据库的问题:
Spring 支持以下七种事务传播机制:
1.PROPAGATION_REQUIRED:默认值,如果当前存在一个事务,则加入该事务;否则新建一个事务。这是最常见的传播机制,也是大多数情况下使用的传播机制。如果外围方法已经启动了事务,那么内部方法就会在该事务中运行;如果外围方法尚未启动事务,则内部方法会启动一个新的事务。
2.PROPAGATION_REQUIRES_NEW:将当前事务挂起,开启一个新的事务进行执行。如果当前存在事务,则将当前事务挂起并启动一个新事务;如果当前没有事务,则开启一个新的独立事务。
3.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;否则以非事务方式继续运行。若外围方法有事务,则使用该事务;若外围方法没有事务,则直接执行。
4.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则将该事务挂起。若外围有事务,则将当前事务挂起,然后以非事务方式执行;若外围没有事务,则直接以非事务方式执行。
5.PROPAGATION_NEVER:表示当前方法不应该执行在事务上下文中。如果当前事务存在,则抛出异常。即表示当前方法不能够在事务环境中运行。
6.PROPAGATION_MANDATORY:当前方法必须在事务上下文中运行,否则抛出异常。要求当前环境中必须存在事务,否则会抛出异常。
7.PROPAGATION_NESTED:如果当前存在事务,则开启一个子事务进行执行;如果当前不存在事务,则新建一个事务进行执行。嵌套事务和普通事务的区别是,嵌套事务可以独立于外围事务进行提交或回滚,而不影响外围事务的状态。如果外围事务已经提交或回滚,则子事务也会被提交或回滚,但如果子事务发生异常而回滚,则外围事务可以选择回滚或不回滚。