接上次博客:JavaEE进阶(11)Spring AOP:AOP概述、Spring AOP快速入门、详解)、原理(代理模式:静态代理、动态代理:JDK动态代理、CGLIB动态代理)、源码剖析(了解)-CSDN博客
目录
事务回顾
什么是事务?
事务是数据库管理系统中的一个重要概念,它是一组操作的集合,被视为一个不可分割的单元。事务可以包括插入、更新、删除等数据库操作,这些操作共同构成了一个逻辑上的工作单元。事务的关键特性在于它们被看作是一个整体,要么全部成功地执行,要么全部失败地回滚。
当执行一个事务时,数据库系统将事务中的所有操作视为一个原子操作。这意味着,如果事务中的任何一部分操作失败,整个事务都将被回滚到最初的状态,以确保数据的一致性和完整性。因此,事务的目标之一是维护数据的完整性,即使在面临异常情况或系统故障时也要保证数据的稳定性。事务的另一个重要特性是隔离性。即使多个事务同时执行,它们之间也应该是相互独立的,不应该相互干扰。数据库系统通过隔离级别来定义事务之间的隔离程度,常见的隔离级别包括读未提交(Read Uncommitted)、读提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。此外,事务还具有持久性。一旦事务提交成功,其所做的更改应该永久保存在数据库中,并且不会丢失。即使系统发生故障或重启,已经提交的事务所做的更改也应该得到保留。
事务的特点如下:
-
原子性(Atomicity):事务是一个原子操作单元,即事务中的所有操作要么全部执行成功,要么全部失败。如果事务中的任何一部分操作失败,整个事务都将被回滚到原始状态,以确保数据的一致性。
-
一致性(Consistency):事务的执行过程应该使数据库从一个一致性状态转移到另一个一致性状态。在事务开始之前和事务结束之后,数据库都应该保持一致性。
-
隔离性(Isolation):事务的执行应该是相互隔离的,即一个事务的执行不应该受到其他事务的影响。事务之间应该是相互独立的,即使多个事务同时执行,它们之间也不应该相互干扰。
-
持久性(Durability):一旦事务提交成功,其所做的更改应该永久保存在数据库中,并且不会丢失。即使系统发生故障或重启,已经提交的事务所做的更改也应该得到保留。
综上所述,事务是数据库管理系统中确保数据一致性、完整性和持久性的关键机制。通过将一系列操作组织成一个事务单元,数据库系统能够保证这些操作要么全部成功执行,要么全部失败回滚,从而维护了数据库的稳定性和可靠性。
为什么需要事务?
事务的作用在于保证一组操作要么全部成功,要么全部失败,以确保数据的一致性和完整性。在程序开发中,有许多情况需要使用事务来处理,比如转账操作和秒杀系统等。
举例来说,转账操作中,如果没有事务,可能会出现一种情况,即第一步成功执行了,但第二步执行失败,导致资金的不一致。而使用事务可以保证一旦其中一个步骤失败,整个操作都会回滚,资金不会因为操作失败而丢失。
类似地,在秒杀系统中,下单成功后需要扣减库存,如果这两步操作不在同一个事务中,可能会出现下单成功但库存扣减失败的情况,导致库存数量和实际下单数量不一致。通过将这两步操作放在同一个事务中,可以保证下单成功的同时库存也会同步减少,保持数据的一致性。
然而,在实际的企业开发中,并不是简单地使用事务来解决问题。事务的管理涉及到事务的隔离级别、事务的传播行为、事务的异常处理等方面,需要根据具体业务需求来设计和实现。同时,我们还需要考虑事务的性能和并发控制等问题,以保证系统的高可用性和性能稳定性。因此,理解事务的概念只是第一步,实际的企业开发中还需要综合考虑各种因素来合理地使用和管理事务。
事务的操作
事务的操作主要包括三个步骤:
-
开启事务(Start Transaction/Begin):在进行一组操作之前,需要开启事务。开启事务意味着将这组操作放入一个事务中,以便在执行过程中能够保证操作的原子性和一致性。
-
提交事务(Commit):当一组操作全部成功执行完成后,需要提交事务。提交事务表示将这组操作所做的修改永久地保存到数据库中,并结束事务。一旦事务提交成功,数据库的状态就会发生改变,变化将会持久化到数据库中。
-
回滚事务(Rollback):如果在执行一组操作的过程中出现了异常或者某些操作失败,就需要回滚事务。回滚事务意味着撤销这组操作所做的修改,将数据库恢复到事务开始之前的状态,保持数据的一致性和完整性。回滚操作会取消事务中的所有修改,使得数据库不会受到异常操作的影响。
这三个操作是事务管理中非常重要的基本操作,确保了数据库操作的安全性和可靠性。通过合理地使用这些操作,可以有效地管理事务,保证数据的正确性和完整性。
Spring 中事务的实现
之前我们已经学习过MySQL的事务操作,Spring对事务也进行了实现。
在Spring中,事务操作分为两类:
-
编程式事务:编程式事务是通过手动编写代码来操作事务的方式。开发人员需要在代码中显式地使用事务管理器(Transaction Manager)来控制事务的开启、提交和回滚等操作。通常,编程式事务需要在每个需要事务支持的方法中添加事务管理代码,以确保在方法执行过程中能够正确地处理事务。虽然编程式事务提供了更细粒度的控制,但也增加了代码的复杂度和维护成本。
-
声明式事务:声明式事务是利用注解或XML配置等方式来自动管理事务的方式。开发人员可以通过在方法上添加事务注解(如@Transactional)来指示Spring框架自动对该方法进行事务管理。Spring会根据注解的配置自动地在方法执行前开启事务,在方法执行完成后提交事务,或者在出现异常时回滚事务。声明式事务将事务管理从业务代码中解耦出来,使得代码更加简洁清晰,同时提高了代码的可维护性和可读性。
总的来说,编程式事务需要开发人员手动管理事务的过程,而声明式事务通过注解或配置来实现自动事务管理,简化了开发流程。根据具体的业务需求和开发场景,开发人员可以选择使用适合的事务管理方式。
在学习事务之前,我们先准备好数据和数据的访问代码。
需求:用户注册,注册时在日志表中插入一条操作记录。
数据准备:
-- 创建数据库
DROP DATABASE IF EXISTS trans_test;
CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4;
USE trans_test;
-- 用户表
DROP TABLE IF EXISTS user_info;
CREATE TABLE user_info (
`id` INT NOT NULL AUTO_INCREMENT,
`user_name` VARCHAR(128) NOT NULL,
`password` VARCHAR(128) NOT NULL,
`create_time` DATETIME DEFAULT NOW(),
`update_time` DATETIME DEFAULT NOW() ON UPDATE NOW(),
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARACTER SET=utf8mb4 COMMENT='用户表';
-- 操作日志表
DROP TABLE IF EXISTS log_info;
CREATE TABLE log_info (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`user_name` VARCHAR(128) NOT NULL,
`op` VARCHAR(256) NOT NULL,
`create_time` DATETIME DEFAULT NOW(),
`update_time` DATETIME DEFAULT NOW() ON UPDATE NOW()
) DEFAULT CHARSET='utf8mb4';
代码准备:
1. 创建项目 spring-trans,引入Spring Web,Mybatis,mysql 等依赖
2. 配置文件
配置文件.yml:
server:
port:
8080
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/trans?characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration: # 配置打印 MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true # 配置驼峰自动转换
3. 实体类:
package com.example.translation.model;
import lombok.Data;
import java.util.Date;
@Data
public class UserInfo {
private Integer id;
private String userName;
private String password;
private Date createTime;
private Date updateTime;
}
4. Mapper:
package com.example.translation.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserInfoMapper {
@Insert("insert into user_info (user_name,password) values(#{userName}, #{password})")
Integer insertUser(String userName, String password);
}
5. Service:
package com.example.translation.service;
import com.example.translation.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
public Integer insertUser(String userName, String password) {
return userInfoMapper.insertUser(userName,password);
}
}
6. Contrller:
package com.example.translation.controller;
import com.example.translation.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
private TransactionDefinition transactionDefinition;
@Autowired
private UserService userService;
@RequestMapping("/registry")
public String registry(String userName, String password){
Integer result = userService.insertUser(userName,password);
log.info("数据插入成功, result:"+result);
return "注册成功";
}
}
我们现在先来运行一下看看有没有问题:
Spring 编程式事务(了解)
Spring 中的手动操作事务也有三个重要的步骤,与 MySQL 中的事务操作类似:
- 开启事务:在Spring中,可以使用TransactionStatus类来开始一个事务。通常情况下,我们会在方法上加上@Transactional注解,Spring会自动为我们管理事务,但如果我们选择手动操作事务,就需要在代码中显式地调用PlatformTransactionManager的getTransaction()方法来获得TransactionStatus对象,从而开启一个事务。
- 提交事务:在手动事务管理中,我们需要显式地调用TransactionStatus对象的commit()方法来提交事务。这会将所有已执行的操作永久性地保存到数据库中。
- 回滚事务:如果在事务执行过程中发生了错误,我们需要回滚事务以撤销之前已经执行的操作。在手动事务管理中,可以调用TransactionStatus对象的rollback()方法来回滚事务。
SpringBoot 内置了两个对象:
- DataSourceTransactionManager:这是Spring框架中的一个事务管理器,用于管理数据库事务。它实现了Spring的PlatformTransactionManager接口,能够提供事务的开启、提交和回滚等功能。通过DataSourceTransactionManager,我们可以轻松地与数据库交互,并确保事务的一致性和可靠性。
- TransactionDefinition:这是用于定义事务属性的对象。在Spring中,事务的行为由事务属性决定,例如事务的隔离级别、超时时间、是否只读等。TransactionDefinition接口定义了这些属性,我们可以根据需求创建一个TransactionDefinition对象,并将其传递给事务管理器,以便在获取事务时应用这些属性。
当使用Spring框架进行编程式事务管理时,经常会涉及到DataSourceTransactionManager中的一些重要方法:
-
beginTransaction(TransactionDefinition definition):
- 方法说明:开启一个新的事务。
- 参数:传入一个TransactionDefinition对象,该对象包含了事务的各种属性,如隔离级别、超时时间等。
- 返回值:该方法返回一个TransactionStatus对象,用于跟踪和管理当前事务的状态。
-
commit(TransactionStatus status):
- 方法说明:提交当前事务。
- 参数:传入一个TransactionStatus对象,该对象表示当前事务的状态。
- 返回值:无。
-
rollback(TransactionStatus status):
- 方法说明:回滚当前事务。
- 参数:传入一个TransactionStatus对象,该对象表示当前事务的状态。
- 返回值:无。
-
setTransactionSynchronization(int synchronization):
- 方法说明:设置事务同步行为。
- 参数:传入一个整数值,用于指定事务同步的行为,例如DataSourceTransactionManager.SYNCHRONIZATION_NEVER表示不同步,DataSourceTransactionManager.SYNCHRONIZATION_ON_ACTUAL_TRANSACTION表示在实际事务中同步,等等。
-
doGetTransaction():
- 方法说明:获取当前事务。
- 返回值:返回一个代表当前事务的对象。
-
isExistingTransaction(Object transaction):
- 方法说明:检查当前是否存在事务。
- 参数:传入一个事务对象。
- 返回值:返回一个布尔值,表示当前是否存在事务。
-
doCleanupAfterCompletion(Object transaction):
- 方法说明:在事务完成后进行清理工作。
- 参数:传入一个事务对象。
- 返回值:无。
-
setDataSource(DataSource dataSource):
- 方法说明:设置数据源。
- 参数:传入一个DataSource对象,用于指定当前事务管理器要操作的数据源。
- 返回值:无。
上述这些是DataSourceTransactionManager中常用的方法,它们用于开启、提交和回滚事务,以及管理事务的状态和行为。
还有TransactionDefinition:
// 定义了事务的属性和行为
public interface TransactionDefinition {
// 事务传播行为
int PROPAGATION_REQUIRED = 0; // 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
int PROPAGATION_SUPPORTS = 1; // 支持当前事务,如果当前没有事务,则以非事务方式执行。
int PROPAGATION_MANDATORY = 2; // 支持当前事务,如果当前没有事务,则抛出异常。
int PROPAGATION_REQUIRES_NEW = 3; // 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
int PROPAGATION_NOT_SUPPORTED = 4; // 以非事务方式执行操作,如果当前存在事务,则把当前事务挂起。
int PROPAGATION_NEVER = 5; // 以非事务方式执行,如果当前存在事务,则抛出异常。
int PROPAGATION_NESTED = 6; // 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
// 事务隔离级别
int ISOLATION_DEFAULT = -1; // 使用默认的隔离级别。
int ISOLATION_READ_UNCOMMITTED = 1; // 允许读取尚未提交的数据变更。
int ISOLATION_READ_COMMITTED = 2; // 允许读取已经提交的数据变更。
int ISOLATION_REPEATABLE_READ = 4; // 对相同字段的多次读取结果一致,除非数据被事务本身修改。
int ISOLATION_SERIALIZABLE = 8; // 最高的隔离级别,完全服从ACID的隔离规则,确保不同事务之间的数据完全隔离。
int TIMEOUT_DEFAULT = -1; // 使用默认的事务超时时间。
// 获取事务传播行为,默认返回0
default int getPropagationBehavior() {
return 0;
}
// 获取事务隔离级别,默认返回-1
default int getIsolationLevel() {
return -1;
}
// 获取事务超时时间,默认返回-1
default int getTimeout() {
return -1;
}
// 是否为只读事务,默认返回false
default boolean isReadOnly() {
return false;
}
// 获取事务名称,默认返回null
@Nullable
default String getName() {
return null;
}
// 返回具有默认属性的TransactionDefinition实例
static TransactionDefinition withDefaults() {
return StaticTransactionDefinition.INSTANCE;
}
}
我们还是根据代码的实现来学习:
TransactionStatus是Spring框架中用于跟踪和管理事务状态的接口。它提供了一组方法,用于管理事务的各种操作,例如提交、回滚、获取保存点等。通过TransactionStatus接口,我们可以检查当前事务的状态并执行相应的操作。
在Spring中,TransactionStatus对象通常由事务管理器(例如DataSourceTransactionManager)返回,用于表示当前事务的状态。通过调用事务管理器的方法,我们可以获取到TransactionStatus对象,并在需要的时候执行事务相关的操作,如提交、回滚等。
下面是一些TransactionStatus接口中常用的方法:
- isCompleted():检查当前事务是否已完成。
- isNewTransaction():检查当前事务是否是新事务。
- setRollbackOnly():标记当前事务为只读。
- hasSavepoint():检查当前事务是否有保存点。
- createSavepoint():创建一个新的保存点。
- rollbackToSavepoint(Object savepoint):将当前事务回滚到指定的保存点。
- releaseSavepoint(Object savepoint):释放指定的保存点。
通过这些方法,我们可以有效地管理事务的状态和操作,确保事务的正确执行和处理。
但是数据库没有变化:
虽然程序返回“注册成功”,但数据库并没有新增数据。这是怎么回事?
因为事务回滚了。
现在我们试试事务提交:
package com.example.translation.controller;
import com.example.translation.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private UserService userService;
@RequestMapping("/registry")
public String registry(String userName, String password){
//获取事务
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
Integer result = userService.insertUser(userName,password);
log.info("数据插入成功, result:"+result);
//回滚事务
//dataSourceTransactionManager.rollback(transaction);
dataSourceTransactionManager.commit(transaction);
return "注册成功";
}
}
思考:如果我们先提交,再回滚,程序会有什么反应?
虽然后台显示没有报错,但是数据有没有插入呢?
而且你会发现我们之前进行回滚操作时,虽然数据没有插入,但是id有在跟着变化。
在回滚事务之后我们观察到的自增 ID 的变化可能是由于数据库的实现机制造成的。一些数据库管理系统(DBMS)在执行插入操作时,即使在事务回滚之后也可能会增加自增 ID 的值。这是因为自增 ID 的分配通常是通过数据库的内部机制来处理的,而不受事务的影响。即使我们执行了回滚操作,数据库仍然会分配下一个可用的自增 ID。这种行为是为了确保数据库在高并发环境下的一致性和性能。如果回滚操作也会导致自增 ID 的值回退,可能会引起一系列的问题,特别是在高并发的情况下。因此,尽管事务回滚了,但数据库仍然会分配下一个自增 ID,这样做是为了保持数据库的一致性和可靠性。
commit和rollback的日志只相差一个:
Spring 声明式事务 @Transactional
声明式事务的实现非常简单,只需要在需要事务支持的方法上添加 @Transactional 注解即可。这样做的好处是,无需手动编写代码来开启、提交或回滚事务,框架会自动处理这些事务管理的细节。
当程序执行到带有 @Transactional 注解的方法时,事务会自动开启。在方法执行完成后,如果没有发生异常,事务会自动提交,确保所有的数据库操作都得到了正确的持久化。这样可以保证数据的一致性和完整性,同时简化了代码的编写和维护工作。
然而,如果在方法执行过程中发生了未经处理的异常,事务会自动回滚。这意味着所有对数据库的修改都会被撤销,数据库会回滚到事务开始之前的状态,从而保证了数据的安全性和可靠性。
总的来说,使用声明式事务能够极大地简化代码的编写,提高了开发效率,并且保证了数据操作的一致性和可靠性。
package com.example.translation.controller;
import com.example.translation.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@Slf4j
@RequestMapping("/trans")
@RestController
public class TransactionalController {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/r1")
public String registry(String userName, String password){
Integer result = userService.insertUser(userName,password);
log.info("数据插入成功, result:"+result);
return "注册成功";
}
}
当涉及到事务管理时,使用 @Transactional 注解是一种常见的方式,它可以轻松地管理事务的生命周期。下面我会详细解释每个知识点:
@Transactional 注解的可以修饰方法或类:
- 当修饰方法时:只有修饰 public 方法时才会生效(修饰其他类型的方法不会报错,但也不会生效)[推荐]。
- 当修饰类时:对于被 @Transactional 注解修饰的类中的所有 public 方法都会生效。
当方法或类被 @Transactional 注解修饰时,在目标方法执行开始之前,会自动开启事务,并执行目标方法。方法执行结束后,会自动提交事务。如果在方法执行过程中出现异常且异常未被捕获,就会执行事务回滚操作。如果异常被程序捕获,方法就会被认为是成功执行,依然会提交事务。
那么我们如果给程序加点异常呢?
执行回滚操作,直接关闭事务。
数据也没有插入成功:
修改上述代码, 对异常进行捕获:
package com.example.translation.controller;
import com.example.translation.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@Slf4j
@RequestMapping("/trans")
@RestController
public class TransactionalController {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/r1")
public String registry(String userName, String password){
Integer result = userService.insertUser(userName,password);
log.info("数据插入成功, result:"+result);
try {
//强制程序抛出异常
int a = 10/0;
}catch (Exception e){
e.printStackTrace();
}
return "注册成功";
}
}
运行程序,发现虽然程序出错了,但是由于异常被捕获了,所以事务依然得到了提交。
当发生了异常,但是你仍然需要让事务进行回滚操作时,通常可以采取以下两种方式:
-
重新抛出异常:
- 在方法中捕获到异常后,可以选择重新抛出该异常,从而触发事务回滚。
- 这种方式会使事务管理器检测到异常被重新抛出,然后自动触发事务回滚。
-
手动回滚事务:
- 在方法中捕获到异常后,可以通过编程方式显式地调用事务管理器的回滚方法来触发事务回滚。
- 这种方式会直接通知事务管理器回滚当前事务,而无需再次抛出异常。
- 操作:使用 TransactionAspectSupport.currentTransactionStatus() 得到当前的事务,并使用 setRollbackOnly 设置 setRollbackOnly
TransactionAspectSupport.currentTransactionStatus() 是 Spring 提供的一个静态方法,用于获取当前方法执行的事务状态。通过调用该方法可以获取当前方法所处的事务状态,包括事务是否已经激活、事务的隔离级别、是否为只读事务等信息。
setRollbackOnly() 方法是 TransactionStatus 接口的一个方法,用于将当前事务标记为回滚状态。一旦事务被标记为回滚状态,即使方法执行成功,事务提交时也会被强制回滚。
这两种方式的选择取决于具体的业务场景和需求。通常来说,如果希望在捕获到异常后进行其他处理,可以选择重新抛出异常;如果只需要触发事务回滚而不需要其他处理,可以选择手动回滚事务。
重新抛出异常:
@Transactional
@RequestMapping("/r6")
public String r6(String userName, String password) {
Integer result = userService.insertUser(userName,password);
log.info("数据插入成功, result:"+result);
try {
int a = 10/0;
}catch (Exception e){
log.error("发生了异常...");
throw e; //重新抛出异常, 事务回滚
}
return "注册成功";
}
手动回滚事务:
@Transactional
@RequestMapping("/r7")
public String r7(String userName, String password) {
Integer result = userService.insertUser(userName,password);
log.info("数据插入成功, result:"+result);
try {
int a = 10/0;
}catch (Exception e){
log.error("发生了异常...");
//手动设置回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return "注册成功";
}
虽然显示注册成功,但是没有真的提交数据:
@Transactional 详解
通过上⾯的代码, 我们学习了 @Transactional 的基本使用,接下来我们就要来学习 @Transactional 注解的使用细节。
@Transactional 用法
-
修饰方法或类:
- @Transactional 注解可以应用在单个方法上,也可以应用在整个类上。
- 当修饰方法时,只有被注解修饰的方法执行时才会启用事务。
- 当修饰类时,所有被 @Transactional 注解修饰的公共方法都会共享同一个事务配置。
-
方法的可见性:
- @Transactional 注解只对公共方法(public)有效。对于私有方法、受保护方法等非公共方法,该注解不会生效。
-
事务的生命周期:
- 在方法执行之前,事务会自动启动。
- 方法执行完成后,事务会自动提交。
- 如果方法执行过程中发生了未被捕获的异常,事务会自动回滚。
- 如果异常被捕获并处理,方法会被认为成功执行,事务依然会提交。
-
异常处理:
- 如果在被 @Transactional 注解修饰的方法中发生未被捕获的异常,事务会自动回滚,以确保数据的一致性和完整性。
- 异常被捕获并处理后,事务仍然会继续执行,除非显式地调用回滚操作。
-
事务边界:
- 使用 @Transactional 注解可以将多个数据库操作限定在同一个事务边界内。这样,无论这些操作是成功还是失败,它们都会被当做一个整体来处理,从而确保数据库的一致性。
总的来说,@Transactional 注解简化了事务管理的过程,使得开发人员可以更轻松地处理数据库事务,同时保证了数据的一致性和完整性。通过合适地使用该注解,可以使代码更加清晰、简洁,并且更容易维护。
在通常情况下,我们会选择在业务逻辑层来控制事务。这是因为在业务逻辑层中,一个业务功能可能涉及到多个数据访问操作,而将这些操作控制在同一个事务范围内可以确保数据的一致性和完整性。
通过在业务逻辑层使用声明式事务管理,我们可以轻松地将多个数据访问操作包装在一个事务中。这样,当业务逻辑执行过程中的任何一个数据访问操作失败或抛出异常时,整个事务都会被回滚,从而保证了数据的一致性。
我们的代码编写放在了Controller里面只是为了方便,并不是很规范。
三个常见的属性
@Transactional 注解有很多属性:
package org.springframework.transaction.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
// 定义了一个名为 Transactional 的注解
@Target({ElementType.TYPE, ElementType.METHOD}) // 指定注解可以应用的目标类型,这里包括类和方法
@Retention(RetentionPolicy.RUNTIME) // 指定注解在运行时保留,以便通过反射访问
@Inherited // 指定注解可以被子类继承
@Documented // 表明注解应该被 javadoc 工具记录
public @interface Transactional {
// 别名注解,用于设置 value 属性和 transactionManager 属性的别名关系
@AliasFor("transactionManager")
String value() default ""; // 定义了 value 属性,默认为空字符串
@AliasFor("value")
String transactionManager() default ""; // 定义了 transactionManager 属性,默认为空字符串,和 value 属性是互为别名的
String[] label() default {}; // 定义了 label 属性,默认为空数组
// 定义了 propagation 属性,默认为 Propagation.REQUIRED,表示事务的传播机制
Propagation propagation() default Propagation.REQUIRED;
// 定义了 isolation 属性,默认为 Isolation.DEFAULT,表示事务的隔离级别
Isolation isolation() default Isolation.DEFAULT;
// 定义了 timeout 属性,默认值为 -1,表示事务超时时间(以秒为单位)
int timeout() default -1;
// 定义了 timeoutString 属性,默认为空字符串,表示事务超时时间的字符串表示形式
String timeoutString() default "";
// 定义了 readOnly 属性,默认为 false,表示事务是否为只读
boolean readOnly() default false;
// 定义了 rollbackFor 属性,默认为空数组,表示导致事务回滚的异常类型
Class<? extends Throwable>[] rollbackFor() default {};
// 定义了 rollbackForClassName 属性,默认为空数组,表示导致事务回滚的异常类型的类名
String[] rollbackForClassName() default {};
// 定义了 noRollbackFor 属性,默认为空数组,表示不会导致事务回滚的异常类型
Class<? extends Throwable>[] noRollbackFor() default {};
// 定义了 noRollbackForClassName 属性,默认为空数组,表示不会导致事务回滚的异常类型的类名
String[] noRollbackForClassName() default {};
}
我们重点学习其中的三个。
@Transactional 注解中三个常见的属性:
- rollbackFor:异常回滚属性。用于指定能够触发事务回滚的异常类型。默认情况下,Spring 只会在遇到未检查异常(继承自 RuntimeException 的异常)时进行事务回滚,而对于已检查异常(继承自 Exception 的异常),则不会触发事务回滚。通过 rollbackFor 属性,可以指定在遇到特定的异常时触发事务回滚。可以指定多个异常类型作为回滚触发条件。
- isolation:事务的隔离级别。用于指定事务的隔离级别,即多个事务同时运行时,各自之间的影响程度。默认值为 Isolation.DEFAULT,表示使用数据库的默认隔离级别。
- propagation:事务的传播机制。用于指定在方法调用链中已存在事务时,当前方法如何处理事务。默认值为 Propagation.REQUIRED,表示当前方法必须运行在一个事务中,如果已存在事务,则加入该事务;如果不存在事务,则创建一个新的事务。
rollbackFor
当使用 @Transactional 注解时,默认情况下只有在方法执行过程中遇到运行时异常(RuntimeException 及其子类)或者 Error 类型的异常时才会触发事务回滚,而对于非运行时异常(如受检异常),默认情况下不会触发事务回滚,除非显式声明需要回滚的异常类型。
@Transactional
@RequestMapping("/r1")
public String registry(String userName, String password) {
Integer result = userService.insertUser(userName, password);
log.info("数据插入成功, result:" + result);
// try {
// //强制程序抛出异常
// int a = 10/0;
// }catch (Exception e){
// e.printStackTrace();
// }
int a = 10 / 0;
return "注册成功";
}
@Transactional
@RequestMapping("/r2")
public String r2(String userName, String password) throws IOException {
Integer result = userService.insertUser(userName,password);
log.info("数据插入成功, result:"+result);
if (true){
throw new IOException(); //事务提交
}
return "注册成功";
}
@Transactional
@RequestMapping("/r3")
public String r3(String userName, String password) {
Integer result = userService.insertUser(userName,password);
log.info("数据插入成功, result:"+result);
if (true){
throw new NullPointerException(); //事务回滚
}
return "注册成功";
}
可以看出来,虽然都报了错,但是r2成功提交数据了。
这个时候我们就可以使用rollback指定在遇到特定的异常时触发事务回滚,可以指定多个异常类型作为回滚触发条件:
@Transactional(rollbackFor = {Exception.class})
@RequestMapping("/r4")
public String r4(String userName, String password) throws IOException {
Integer result = userService.insertUser(userName,password);
log.info("数据插入成功, result:"+result);
if (true){
throw new IOException(); //事务回滚
}
return "注册成功";
}
事务进行回滚:
事务隔离级别
MySQL 事务隔离级别(回顾)
-
读未提交 (READ UNCOMMITTED):
- 读未提交,也称为未提交读,是最低级别的隔离级别。在这个级别下,一个事务可以看到其他事务中未提交的数据,即可以读取到其他事务尚未提交的修改。这可能导致脏数据的问题,即读取到其他事务正在修改但尚未提交的数据,而这些数据可能会在后续回滚,造成读取到的数据实际上是无效的。因此,读未提交级别可能会产生脏读的现象。
-
读提交 (READ COMMITTED):
- 读提交,也称为提交读,是MySQL默认的隔离级别。在这个级别下,一个事务只能读取到已经提交的数据,不会读取到其他事务中尚未提交的数据,从而避免了脏读的问题。然而,由于在事务的执行过程中,可以读取到其他事务提交的结果,因此在不同时间点进行相同的查询可能会得到不同的结果,这种现象称为不可重复读。
-
可重复读 (REPEATABLE READ):
- 可重复读是MySQL的另一种隔离级别。在这个级别下,一个事务不会读取到其他事务对已有数据的修改,即使其他事务已经提交。这样可以确保同一个事务多次查询的结果是一致的。但是,其他事务新插入的数据是可以感知到的,这可能引发幻读问题。幻读指的是在同一事务多次查询时,会看到不同数量的数据行,即使没有其他事务对数据进行修改。
-
串行化 (SERIALIZABLE):
- 串行化是最高级别的隔离级别,也是最严格的隔离级别。在这个级别下,事务会被强制排序,以避免发生冲突,从而解决了脏读、不可重复读和幻读等问题。但是,由于串行化会导致事务的执行效率低下,因此在实际应用中并不常见。
这个 SQL 查询会返回全局事务隔离级别和当前连接的事务隔离级别。具体查询如下:
SELECT @@global.tx_isolation, @@tx_isolation;
这条查询会返回两个值,第一个值表示全局事务隔离级别 (@@global.tx_isolation),第二个值表示当前连接的事务隔离级别 (@@tx_isolation)。
Spring 事务隔离级别
Spring中的事务隔离级别定义了事务在并发执行时的隔离程度,以防止不同事务之间产生的数据异常问题。Spring框架支持以下五种事务隔离级别:
-
DEFAULT:使用底层数据源默认的隔离级别。通常是数据库的默认隔离级别。
-
READ_UNCOMMITTED:允许事务读取未提交的数据更改。这是最低的隔离级别,它允许一个事务读取另一个事务尚未提交的数据修改,可能会导致脏读、不可重复读和幻读等问题。
-
READ_COMMITTED:确保一个事务读取的数据是另一个事务已经提交的数据。在这个级别下,可以避免脏读的问题,但仍可能出现不可重复读和幻读。
-
REPEATABLE_READ:确保在同一个事务中多次读取同一行数据时,返回的数据是一致的。在这个级别下,可以避免脏读和不可重复读,但仍可能出现幻读。
-
SERIALIZABLE:最高的隔离级别,确保事务之间的数据访问串行执行,从而避免了脏读、不可重复读和幻读等所有并发问题。它会对数据进行锁定,因此可能会影响系统的并发性能。
对比MySQL的隔离级别:
-
READ UNCOMMITTED:与Spring的READ_UNCOMMITTED相同,允许读取未提交的数据更改。
-
READ COMMITTED:与Spring的READ_COMMITTED相同,确保事务读取的数据是另一个事务已经提交的数据。
-
REPEATABLE READ:MySQL的REPEATABLE READ相当于Spring的REPEATABLE_READ,确保在同一个事务中多次读取同一行数据时返回的数据是一致的。
-
SERIALIZABLE:与Spring的SERIALIZABLE相同,最高的隔离级别,确保事务之间的数据访问串行执行,从而避免了脏读、不可重复读和幻读等所有并发问题。
Spring 事务传播机制
什么是事务传播机制
事务传播机制是指多个事务方法之间事务如何在调用过程中进行传播和交互的规则。当一个事务方法调用另一个事务方法时,事务传播机制定义了新事务的创建、加入已有事务、挂起当前事务等行为。通过事务传播机制,可以控制事务在方法调用链中的行为,确保数据的一致性和完整性。在Spring框架中,事务传播机制提供了一系列的传播行为选项,开发人员可以根据业务需求选择适当的传播行为来管理事务。
比如:
两个方法A和B都被@Transactional修饰,A方法调用B方法。当A方法运行时,会开启一个事务。当A调用B时,B方法本身也有事务。此时B方法运行时,是加入A的事务还是创建一个新的事务呢?
这个问题涉及到事务的传播机制。
事务隔离级别解决的是多个事务同时调用一个数据库的问题。换句话说就是,事务隔离级别是针对多个事务同时访问同一个数据库时可能出现的问题进行解决的。
而事务传播机制解决的是一个事务在多个节点(方法)中传递的问题。
事务的传播机制有哪些
@Transactional 注解支持事务传播机制的设置,通过 propagation 属性来指定传播行为。
Spring 事务传播机制有以下 7 种:
-
Propagation.REQUIRED: 默认的事务传播级别。如果当前存在事务,则加入该事务。如果当前没有事务,则创建一个新的事务。
-
Propagation.SUPPORTS: 如果当前存在事务,则加入该事务。如果当前没有事务,则以非事务的方式继续运行。
-
Propagation.MANDATORY: 强制性。如果当前存在事务,则加入该事务。如果当前没有事务,则抛出异常。
-
Propagation.REQUIRES_NEW: 创建一个新的事务。如果当前存在事务,则把当前事务挂起。也就是说,不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。
-
Propagation.NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
-
Propagation.NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
-
Propagation.NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行。如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。
事务传播机制用于管理事务在多个方法之间的传递,从而保证事务的一致性和完整性。通过合适的传播行为设置,可以控制事务的行为,确保在复杂的业务场景下事务能够正确地执行。
比如两个人要合作开一家小餐馆,考虑到合作中涉及到的房子问题:
-
Propagation.REQUIRED:需要有厨房。如果你有厨房,我们就一起开,如果你没有,我们就一起找个有厨房的地方开。 (如果当前存在事务,就加入该事务;如果当前没有事务,就创建一个新的事务)
-
Propagation.SUPPORTS:可以有厨房。如果你有厨房,那就一起用。如果没有,那就租一个有厨房的地方开。 (如果当前存在事务,就加入该事务;如果当前没有事务,就以非事务的方式继续运行)
-
Propagation.MANDATORY:必须有厨房。要求必须有厨房,如果没有,就不合作开店。(如果当前存在事务,就加入该事务;如果当前没有事务,就抛出异常)
-
Propagation.REQUIRES_NEW:必须买新厨房。不管你有没有厨房,我们必须一起买厨房。即使有厨房也不单独用。 (创建一个新的事务,如果当前存在事务,就将当前事务挂起)
-
Propagation.NOT_SUPPORTED:不需要厨房。不管你有没有厨房,我们都不使用,必须租一个有厨房的地方开店。(以非事务方式运行,如果当前存在事务,就将当前事务挂起)
-
Propagation.NEVER:不能有厨房。 (以非事务方式运行,如果当前存在事务,就抛出异常)
-
Propagation.NESTED:如果你没有厨房,就一起买厨房。如果你有厨房,我们就以厨房为基地,做一些小生意。(如果当前存在事务,就创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED)
Spring 事务传播机制使用和各种场景演示
对于以上事务传播机制,我们重点关注以下两个就可以了:
1. REQUIRED(默认值) 2. REQUIRES_NEW
我们前面建过一个表:
实体类:
import lombok.Data;
import java.util.Date;
@Data
public class LogInfo {
private Integer id;
private String userName;
private String op;
private Date createTime;
private Date updateTime;
}
Mapper:
package com.example.translation.mapper;
import com.example.translation.model.LogInfo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface LogInfoMapper {
@Insert("insert into log_info(user_name, op) values(#{userName},#{op})")
Integer insertLog(LogInfo logInfo);
}
Service:
package com.example.translation.service;
import com.example.translation.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional
public Integer insertUser(String userName, String password) {
return userInfoMapper.insertUser(userName,password);
}
}
package com.example.translation.service;
import com.example.translation.mapper.LogInfoMapper;
import com.example.translation.model.LogInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Service
public class LogInfoService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional
public Integer insertLog(LogInfo logInfo){
Integer res = logInfoMapper.insertLog(logInfo);
return res;
}
}
Controller:
package com.example.translation.controller;
import com.example.translation.model.LogInfo;
import com.example.translation.service.LogInfoService;
import com.example.translation.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/proga")
@RestController
public class ProgaController {
@Autowired
private LogInfoService logInfoService;
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/p1")
public String p1(String userName, String password){
userService.insertUser(userName,password);
LogInfo logInfo = new LogInfo();
logInfo.setUserName(userName);
logInfo.setOp("用户主动注册");
logInfoService.insertLog(logInfo);
return "用户注册成功";
}
}
REQUIRED(加入事务)(默认)
我们直接运行程序:
成功时,两个事务都成功。
现在我们修改一下其中一个代码,比如把LogService的@Autowired给注释掉:
报了一个空指针异常:
数据也没有插入。所以一个及一个以上事务出错,全军覆没。
其实它们可以看成是一个事务。
REQUIRES_NEW(新建事务)
package com.example.translation.service;
import com.example.translation.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Integer insertUser(String userName, String password) {
return userInfoMapper.insertUser(userName, password);
}
}
package com.example.translation.service;
import com.example.translation.mapper.LogInfoMapper;
import com.example.translation.model.LogInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Service
public class LogInfoService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Integer insertLog(LogInfo logInfo){
Integer res = logInfoMapper.insertLog(logInfo);
return res;
}
}
所以两个事务全部成功,事务提交。
package com.example.translation.service;
import com.example.translation.mapper.LogInfoMapper;
import com.example.translation.model.LogInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Service
public class LogInfoService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Integer insertLog(LogInfo logInfo){
Integer res = logInfoMapper.insertLog(logInfo);
int a = 10 / 0;
return res;
}
}
失败:
成功:
一个事务出错,另一个不受影响。
NEVER (不支持当前事务, 抛异常)
package com.example.translation.service;
import com.example.translation.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.NEVER)
public Integer insertUser(String userName, String password) {
return userInfoMapper.insertUser(userName, password);
}
}
NESTED(嵌套事务)
package com.example.translation.service;
import com.example.translation.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public Integer insertUser(String userName, String password) {
return userInfoMapper.insertUser(userName, password);
}
}
package com.example.translation.service;
import com.example.translation.mapper.LogInfoMapper;
import com.example.translation.model.LogInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Service
public class LogInfoService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Integer insertLog(LogInfo logInfo){
Integer res = logInfoMapper.insertLog(logInfo);
return res;
}
}
两个事务都成功,事务提交。
那么如果我们写一个异常,然后捕获异常:
package com.example.translation.service;
import com.example.translation.mapper.LogInfoMapper;
import com.example.translation.model.LogInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Service
public class LogInfoService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Integer insertLog(LogInfo logInfo){
Integer res = logInfoMapper.insertLog(logInfo);
try {
int a = 10/0;
}catch (Exception e){
e.printStackTrace();
//设置当前事务回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return res;
}
}
虽然报错但是都正常提交。
把捕获异常去掉,只剩着异常:
两边数据都没有增加。
所以其中一个失败,事务回滚???
NESTED 和 REQUIRED 有什么区别?
写一个异常,然后捕获异常,再把insertLog里面的 Propagation改成REQUIRED:
和NESTED结果一样。所以区别不在这里。
回想手动回滚:
package com.example.translation.service;
import com.example.translation.mapper.LogInfoMapper;
import com.example.translation.model.LogInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Service
public class LogInfoService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public Integer insertLog(LogInfo logInfo){
Integer res = logInfoMapper.insertLog(logInfo);
try {
int a = 10/0;
}catch (Exception e){
e.printStackTrace();
//设置当前事务回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return res;
}
}
成功:
失败:
所以我们添加了手动回滚代码,只回滚当前事务。
现在把NESTED改为REQUIRED,重新运行:
所以如果隔离级别是REQUIRED,事务全部回滚,因为它们属于一个事务。
LogService 中的事务已经回滚, 但是嵌套事务不会回滚嵌套之前的事务, 也就是说NESTED嵌套事务可以实 现部分事务回滚。
总结NESTED和REQUIRED区别
在Spring事务管理中,REQUIRED 和 NESTED 是两种不同的事务传播行为,它们之间有以下区别:
-
REQUIRED:
- REQUIRED 是默认的事务传播行为。
- 如果当前没有事务,则创建一个新的事务;如果已经存在事务,则加入到当前事务中。
- 整个方法都在同一个事务中执行。
- 如果发生回滚,则会回滚所有嵌套的事务。
-
NESTED:
- NESTED 是一种嵌套的事务传播行为。
- 嵌套事务相当于在当前事务中创建了一个保存点(savepoint),在嵌套事务内部执行的操作可以回滚到嵌套点,而不会影响外部事务。
- 如果发生回滚,则只会回滚到当前嵌套点,而不会影响外部事务的状态。
总结来说,REQUIRED 和 NESTED 的区别在于事务的范围和回滚的影响:
- REQUIRED 的事务范围是整个方法,回滚会影响整个事务;
- NESTED 的事务范围是在嵌套点之后的部分,回滚只会影响嵌套事务内部的操作,而不会影响外部事务的状态。
- 在 Spring 中,NESTED 可以理解为创建了一个子事务,并将其嵌套在原始的父事务中执行。这意味着在 NESTED 事务内部执行的操作可以看作是一个独立的子事务,它可以部分回滚,而父事务不受影响。如果在 NESTED 事务中发生了异常,父事务可以选择回滚整个事务,也可以只回滚到嵌套点,这样只会影响到嵌套事务内部的操作,而不会影响到外部事务的状态。这种机制使得 NESTED 可以实现更加细粒度的事务控制和回滚策略。
- REQUIRED 是加入到当前事务中,并没有创建事务的保存点,因此出现了回滚就是整个事务回滚,这就是嵌套事务和加入事务的区别。
- 可以参考:MySQL :: MySQL 5.7 Reference Manual :: 13.3.4 SAVEPOINT, ROLLBACK TO SAVEPOINT, and RELEASE SAVEPOINT Statements