事务的概念
事务就是一组逻辑操作的组合,它们执行的结果要么全部成功,要么全部失败。
事务有4个特性:
- 原子性:一个事务就是一个不可再分解的单位,事务中的操作要么全部做,要么全部不做(事务中的任务要么全部提交要么全部回滚,不可能出现一部分提 交一部分回滚。例如:用户 A 给用户 B 转账 100 元,A 账户减100,那么 B 账户一定加 100)
- 一致性:事务执行后,所有的数据都应该保持一致状态。(还是以用户 A 给用户 B 转账 100 元为例,转账后 A 和 B 账户的总额和转账前是一致的)
- 隔离性:多个数据库操作并发执行时,一个请求的事务操作不能被其它操作干扰,多个并发事务执行之间要相互隔离。隔离性强调的是并发的隔离,一个事 务所做的操作,在最终提交前,对其他事务是不可见的,保证事务与事务之间不会冲突
- 持久性:事务执行完成后,它对数据的影响是永久性的。持久性强调的是操作的结果,只要事务提交,数据就不会丢失,即使系统崩溃,事务也已经完成
数据库事务
针对数据库的并发操作,可能会出现一些事务的并发问题。事务并发操作中会出现三种问题:
- 丢失更新:两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,这是由于没有加锁造成的
- 脏读:一个事务A 看到了另一个事务B 没有提交的数据
- 不可重复读:一个事务读到了另一个事务已提交的修改的数据,对同一行数据查询两次,结果不一致
- 幻读:一个事务读到了另一个事务已提交新增的数据,对同一张表查询两次,出现新增的行,导致结果不一致,出现了幻觉
针对上述三个问题,由此引出了数据库事务的隔离级别来解决,也就是事务4个特性里的隔离性:
- read uncommitted 读未提交,所有事务都可以看到其他未提交事务的执行结果 —— 不解决任何问题
- read committed 读已提交,一个事务只能看见已经提交事务所做的改变 —— 解决丢失更新,脏读
- repeatable read 可重复读,从当前事务开始读取的数据到事务结束的这段时间,数据内容不受其他事务的影响 —— 解决丢失更新,脏读、不可重复读
- serializable 可串行化,它通过强制事务排序执行(加锁),使之不可能相互冲突 —— 解决丢失更新,脏读、不可重复读、幻读,但可能导致大量的超时 现象和锁竞争.
四种隔离级别,自上而下级别逐级增高,但并发性能逐级降低:
- MySQL 中默认的事务隔离级别是 repeatable read
- Oracle 、PostgresSQL 的默认事务隔离级别是 read committed
隔离级别越高,数据库事务并发执行性能越差,能处理的操作越少。因此在实际项目开发中为了考虑并发性能一般使用提交读隔离级别,它能避免丢失更新和脏读,尽管不可重复读和幻读不能避免,但可以在可能出现的场合使用应用层面的悲观锁或乐观锁来解决这些问题。
隔离级别 | 丢失更新 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|---|
未提交读(Read uncommitted) | 可能 | 可能 | 可能 | 可能 |
已提交读(Read committed) | 不可能 | 不可能 | 可能 | 可能 |
可重复读(Repeatable read) | 不可能 | 不可能 | 不可能 | 可能 |
可串行化(Serializable ) | 不可能 | 不可能 | 不可能 | 不可能 |
MySQL数据库是如何做到事务的原子性的呢
我们知道如果想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,在 MySQL 中,恢复机制是通过 回滚日志(undo log) 实现的,所有事务进行的修改都会先>先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中遇到异常的话,我们直接利用 回滚日志 中的信息将数据回滚到修改之前的样子即可!并且,回滚日志会先于数据持>久化到磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚将之前未完成的事务。
数据库层面的锁
锁用来对数据进行锁定,我们可以从锁定对象的粒度大小来对锁进行划分,分别为行锁、页锁和表锁。
- 行级锁是数据库中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。特点:开销大,加锁 慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
- 表级锁是数据库中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少。特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最 高,并发度最低。
- 页级锁是数据库中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。特点:开销和加 锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般,时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
不同数据库支持的锁力度不同,甚至同一种数据库不同的引擎支持的锁力度都不同,如下表所示
应用层面的乐观锁和悲观锁处理
无论是乐观锁和悲观锁,并非是数据库自身持有的锁类型(虽然悲观锁形式上很像独占锁),而是程序设计的一种思想,是一种类似数据库锁机制保护数据一致性的策略。
程序编写过程中,操作数据无论采用哪个类型的锁,都需要注意死锁的发生,一个死锁有可能对整个应用是致命的。死锁的本质是对资源竞争的一种失败表现,所以sql语句的编写过程中对于多表的操作最好采用一致的顺序来进行,或者尽量减少多表操作
乐观锁,其实没有加锁
认为一般情况下数据不会造成冲突,所以在数据进行提交更新时才会对数据的冲突与否进行检测。如果没有冲突那就OK;如果出现冲突了,则返回错误信息并让用户决定如何去做. 乐观锁是一种程序的设计思想,通过一个标识的对比来决定数据是否可以操作,现在普遍的做法是给数据加一个版本号或者时间戳的方式来实现乐观锁.
操作过程:在表中设计一个版本字段 version,第一次读的时候,会获取 version 字段的取值。然后对数据进行更新或删除操作时,会执行UPDATE ... SET version=version+1 WHERE version=version。此时如果已经有事务对这条数据进行了更改,修改就不会成功
乐观锁比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
悲观锁
每次获取数据的时候,都会担心数据被修改,所以每次获取数据的时候都会进行加锁,确保在自己使用的过程中数据不会被别人修改,使用完成后进行数据解锁。由于数据进行加锁,期间对该数据进行读写的其他线程都会进行等待
操作过程:在代码里对修改数据的代码进行加锁,synchronized
悲观锁比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
事务的类型
数据库事务类型有本地事务和分布式事务:
- 本地事务:就是普通事务,能保证单台数据库上的操作的ACID,被限定在一台数据库上
- 分布式事务:涉及两个或多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成的),分布式事务旨在保证这些本地事务的所有操作的ACID,使事务可以跨越多台数据库
Java事务类型有JDBC事务和JTA事务:
- JDBC事务:就是数据库事务类型中的本地事务,通过Connection对象的控制来管理事务
- JTA事务:JTA指Java事务API(Java Transaction API),是Java EE数据库事务规范, JTA只提供了事务管理接口,由应用程序服务器厂(WebSphere Application Server)提供实现,JTA事务比JDBC更强大,支持分布式事务
Java EE事务类型有本地事务和全局事务:
- 本地事务:使用JDBC编程实现事务
- 全局事务:由应用程序服务器提供,使用JTA事务
按是否通过编程实现事务有声明式事务和编程式事务:
- 声明式事务: 通过注解或XML配置文件指定事务信息
- 编程式事务:通过编写代码实现事务。
Spring 支持两种方式的事务管理
Spring框架最核心功能之一就是事务管理,而且提供一致的事务管理抽象,这能帮助我们: *提供一致的编程式事务管理API,不管使用Spring JDBC框架还是集成第三方框架使用该API进行事务编程; *无侵入式的声明式事务支持。
Spring支持声明式事务和编程式事务事务类型。 Spring 关于事务的接口定义在这个包里
<dependency>-->
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.22</version>
</dependency>
Spring 框架中,事务管理相关最重要的 3 个接口如下:
- PlatformTransactionManager:(平台)事务管理器,Spring 事务策略的核心。
public interface PlatformTransactionManager extends TransactionManager { //返回一个已经激活的事务或创建一个新的事务 TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; //提交 void commit(TransactionStatus status) throws TransactionException; //回滚 void rollback(TransactionStatus status) throws TransactionException; }
- TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
public interface TransactionDefinition { //获取事务的传播性 default int getPropagationBehavior() { return 0; } //获取事务的隔离级别 default int getIsolationLevel() { return -1; } //获取事务的超时时间 default int getTimeout() { return -1; } //获取事务是否只读 default boolean isReadOnly() { return false; } //获取事务的名称 @Nullable default String getName() { return null; } static TransactionDefinition withDefaults() { return StaticTransactionDefinition.INSTANCE; } }
- TransactionStatus: 事务运行状态
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable { //获取是否存在保存点 boolean hasSavepoint(); //用于刷新底层会话中的修改到数据库,一般用于刷新如Hibernate/JPA的会话, //可能对如JDBC类型的事务无任何影响; void flush(); } public interface TransactionExecution { //获取是否是新事务 boolean isNewTransaction(); //设置事务回滚 void setRollbackOnly(); //获取事务是否回滚 boolean isRollbackOnly(); //获取事务是否完成 boolean isCompleted(); }
我们可以把 PlatformTransactionManager 接口可以被看作是事务上层的管理者,而 TransactionDefinition 和 TransactionStatus 这两个接口可以看作是事务的描述。>PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus 接口则提供了 一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。
PlatformTransactionManager: 通过这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager) JtaTransactionManager(分布式事务管理的支持,并将事务管理委托给Java EE应用服务器事务管理器)等都提供了对应的事务管理器
DataSourceTransactionManager 或 JdbcTransactionManager 用于Spring JDBC抽象框架、iBATIS或MyBatis框架的事务管理 的实现在如下 jar 包里
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.23</version>
</dependency>
JdbcTransactionManager 类继承关系 TransactionManager 它是 SpringFramework 5.2 才新加的接口, 由于响应式 jdbc 在 SpringFramework 中引入,在 SpringFramework 5.2 之后引入了响应式事务,由此产生了新的根接口
classDiagram
I_TransactionManager <|-- I_PlatformTransactionManager
I_PlatformTransactionManager <|-- I_ResourceTransactionManager
I_PlatformTransactionManager <|.. C_AbstractPlatformTransactionManager
C_AbstractPlatformTransactionManager <|-- C_DataSourceTransactionManager
I_Serializable <|.. C_AbstractPlatformTransactionManager
I_InitializingBean <|.. C_ DataSourceTransactionManager
I_ResourceTransactionManager <|.. C_DataSourceTransactionManager
C_ DataSourceTransactionManager --|> C_JdbcTransactionManager
演示准备的例子 postgresql 数据库测试需要引入的包
<!-- spring 对 jdbc 的封装 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.23</version>
</dependency>
<!-- postgres 数据库驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.3.7</version>
<scope>runtime</scope>
</dependency>
<!-- spring-jdbc 需要的连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
<!-- org.slf4j 做日志记录 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
@Configuration
@Order(1)
public class ConfigerDB {
//spring jdbc
@Bean("jdbcTemplate")
public JdbcTemplate getJdbcTemplate() {
var jdbcTemplate= new JdbcTemplate();
jdbcTemplate.setDataSource(getDataSource());
return jdbcTemplate;
}
//配置数据源
@Bean("hikariDataSource")
public HikariDataSource getDataSource() {
var dataSource = DataSourceBuilder.create();
dataSource.url("jdbc:postgresql://localhost:5432/postgres");
dataSource.driverClassName("org.postgresql.Driver");
dataSource.username("postgres");
dataSource.password("admin@123");
//连接池,演示目的不设置参数了
var pool= new HikariDataSource( );
pool.setDataSource(dataSource.build());
return pool;
}
}
对事务执行的方法进行 AOP 日志错误记录
@Aspect
@Component
public class MyAnnotationAOPAspect {
@Around("execution(* com.hank.postgres.Service*.*(..))")
public void around(ProceedingJoinPoint pjp) {
try {
pjp.proceed();
} catch (Throwable e) {
System.out.println(pjp.getTarget().getClass().getName() + "." + pjp.getSignature().getName()
+ ",执行错误,错误信息是" + e.getMessage());
}
}
}
编程事务-即类似于JDBC编程实现事务管理
主要依赖的实现:
- JdbcTransactionManager 事务管理器
- TransactionTemplate 事务模板,使用它可以完成编程式事务
TransactionTemplate 事务模板
@Configuration
@Order(2)
public class ConfigerTransantion {
private HikariDataSource hikariDataSource;
public ConfigerTransantion(HikariDataSource hikariDataSource){
this.hikariDataSource=hikariDataSource;
}
@Bean("transactionTemplate")
public TransactionTemplate getTransactionTemplate() {
return new TransactionTemplate(getJdbcTransactionManager());
}
@Bean("jdbcTransactionManager")
public JdbcTransactionManager getJdbcTransactionManager() {
return new JdbcTransactionManager(hikariDataSource);
}
}
编程式事务,利用事务模板去完成 TransactionTemplate transactionTemplate
@Component
public class Service {
private final TransactionTemplate transactionTemplate;
private final JdbcTemplate jdbcTemplate;
//注入
public Service(TransactionTemplate transactionTemplate,JdbcTemplate jdbcTemplate){
this.transactionTemplate = transactionTemplate;
this.jdbcTemplate=jdbcTemplate;
}
public void insert() throws Exception {
final Exception[] exception = {new Exception()};
var result = transactionTemplate.execute(p->{
var i= jdbcTemplate.update("insert into \"order\" (id, name)values (?,?)",
new Object[]{200,"name1"});
var j = 0;
//回滚点
var savePoint= p.createSavepoint();
try {
j=jdbcTemplate.update("insert into \"order\" (id, name)values (?,?)",
new Object[]{201,"name2"});
var bar =1/0;
}catch (Exception e){
j=0;
//回滚代码到 savePoint 对象定义执行的代码,
p.rollbackToSavepoint(savePoint);
//rollbackToSavepoint 意思是无论有无错误,都只执行回调点前的代码
//不能 throw e ,这样回滚效果就会失效
exception[0] =e;
}finally {
p.releaseSavepoint(savePoint);
}
return i+j;
});
System.out.println("更新成功次数:"+result);
if(exception.length==1) throw exception[0];
}
}
//测试
@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy(exposeProxy = true)
public class Programmer {
public static void main(String[] args) throws Exception {
var context = new AnnotationConfigApplicationContext(Programmer.class);
var bean =context.getBean(Service.class);
bean.insert();
context.close();
}
}
声明式事务 注解或xml 配置的方式
主要依赖的实现:
- @Transactional("jdbcTransactionManager") 一句注解,注解的参数是事务管理器的 beanName
- @EnableTransactionManagement 使能 @EnableTransactionManagement
注解的实现
// 声明式事务 需要有事务管理器,事务模板内部代码自己生成
@Configuration
@Order(2)
public class ConfigerTransantion {
private HikariDataSource hikariDataSource;
public ConfigerTransantion(HikariDataSource hikariDataSource){
this.hikariDataSource=hikariDataSource;
}
@Bean("jdbcTransactionManager")
public JdbcTransactionManager getJdbcTransactionManager() {
return new JdbcTransactionManager(hikariDataSource);
}
}
@Component
public class Service1 {
private final JdbcTemplate jdbcTemplate;
//注入
public Service1(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
//注解声明式事务
@Transactional("jdbcTransactionManager")
public void insert() {
var i = jdbcTemplate.update(
"insert into \"order\" (id, name)values (?,?)", new Object[]{200, "name1"});
var j = 0;
var savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
try {
j = jdbcTemplate.update(
"insert into \"order\" (id, name)values (?,?)", new Object[]{201, "name2"});
//var bar =1/0;
} catch (Exception e) {
j = 0;
TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
//编程式事务抛出错误,回滚会失效,注解不会,因为此时的事务访问是方法,而不是编程式事务的回调内的事务范围
throw e;
} finally {
TransactionAspectSupport.currentTransactionStatus().releaseSavepoint(savePoint);
System.out.println("更新成功次数:" + (i + j));
}
}
}
@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy()
@EnableTransactionManagement
public class Programmer {
public static void main(String[] args) throws Exception {
var context = new AnnotationConfigApplicationContext(Programmer.class);
var bean =context.getBean(Service1.class);
bean.insert();
context.close();
}
}
事务属性
@Transactional配置详解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
//指定事务管理器名字,默认为transactionManager,当使用其他名字时需要明确指定
@AliasFor("value")
String transactionManager() default "";
String[] label() default {};
//指定事务传播行为,默认为Required,使用Propagation.REQUIRED指定
Propagation propagation() default Propagation.REQUIRED;
//指定事务隔离级别,默认为“DEFAULT”,使用Isolation.DEFAULT指定;
Isolation isolation() default Isolation.DEFAULT;
//指定事务超时时间,以秒为单位,默认-1表示事务超时将依赖于底层事务系统
int timeout() default -1;
String timeoutString() default "";
//指定事务是否只读,默认false表示事务非只读
boolean readOnly() default false;
//指定一组异常类,遇到该类异常将回滚事务
Class<? extends Throwable>[] rollbackFor() default {};
//和 rollbackFor()功能一样
String[] rollbackForClassName() default {};
//指定一组异常类,即使遇到该类异常也将提交事务,即不回滚事务
Class<? extends Throwable>[] noRollbackFor() default {};
//和 noRollbackFor()功能一样
String[] noRollbackForClassName() default {};
}
使用@Transactional注解事务管理需要特别注意以下几点:
- 在Spring代理机制下(不管是JDK动态代理还是CGLIB代理),“自我调用”同样不会应用相应的事务属性
- 默认只对RuntimeException异常回滚;
- 在使用Spring代理时,默认只有在public可见度的方法的@Transactional 注解才是有效的,其它可见度(protected、private、包可见)的方法上即使有@Transactional 注解也不会应用这些事务属性的,Spring也不会报错,如果你非要使用非公共方法注解事务管理的话,可考虑使用AspectJ。
与其他AOP通知协作
Spring声明式事务实现其实就是Spring AOP+线程绑定实现,利用AOP实现开启和关闭事务,利用线程绑定(ThreadLocal)实现跨越多个方法实现事务传播。
由于我们不可能只使用一个事务通知,可能还有其他类型事务通知,而且如果这些通知中需要事务支持怎么办?这就牵扯到通知执行顺序的问题上了,因此如果可能与其他AOP通知协作的话,而且这些通知中需要使用声明式事务管理支持,事务通知应该具有最高优先级。
事务隔离级别
用来解决并发事务时出现的问题,其使用TransactionDefinition中的静态变量来指定:
- ISOLATION_DEFAULT:默认隔离级别,即使用底层数据库默认的隔离级别;
- ISOLATION_READ_UNCOMMITTED:未提交读;
- ISOLATION_READ_COMMITTED:提交读,一般情况下我们使用这个;
- ISOLATION_REPEATABLE_READ:可重复读;
- ISOLATION_SERIALIZABLE:序列化
@Transactional(transactionManager="jdbcTransactionManager",isolation = Isolation.READ_COMMITTED)
事务传播行为
事务的传播指的的是如下代码 f1 方法支持事务,在f1方法内部调用另一个事务方法f2,此时是吧 f1 和 f2 这俩个方法的事务合并成一个事务。还是分开呢,如果分开:
- f2 执行失败 要不要影响到 f1
- 反过来 f1 失败了还要不要执行 f2
class Bar{
@Transactional("jdbcTransactionManager")
public void f1(){ f2()}
@Transactional("jdbcTransactionManager")
public void f2(){
}
}
事务传播行为的7种策略:
事务的默认名称和方法名一样
class OutService {
@Autowired
InnerService innerService;
// 外层事务 do
public void doo() {
//事务1 do1
innerService.do1(
//TransactionSynchronizationManager.getCurrentTransactionName() 可以获取当前的事务名称
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());
);
//事务2 do2
innerService.do2();
throw new Exception("");
}
}
-
REQUIRED: spring 事务的 默认值 ,支持当前事务,如果不存在,就创建一个 do1 ,do2 都是 REQUIRED,举例:
- 如果当前 doo 事务不存在,则会开启俩个新的事务 do1 和 do2 ,do1 和 do2 互相不影响,do1 或 do2 出现异常不会影响到 doo
- 如果当前 doo 事务存在,则方法会运行在当前事务 doo 中,其中任何一个有异常, doo, do1 ,do2 方法会一起回滚
-
REQUIRES_NEW :如果有事务存在,挂起当前事务,创建一个新的事务。 do1 ,do2 是 REQUIRES_NEW,举例:
- 如果当前 doo 事务不存在,开启俩个新的事务 do1 和 do2 ,do1 和 do2 互相不影响
- 如果当前 doo 事务存在,挂起当前的事务 doo,并开启俩个新的事务 do1 和 do2 ,doo, do1 和 do2 事务互相不影响,
-
SUPPORTS :如果当前有事务运行,则方法会运行在当前事务中;如果当前没有事务运行,则不会创建新的事务(即不运行在事务中)
-
NOT_SUPPORTED :不支持事务,如果当前有事务运行,则会将该事务挂起(暂停);如果当前没有事务运行,则它也不会运行在事务中
-
MANDATORY :如果没有事务,则直接抛出异常
-
NEVER :当前方法不允许运行在事务中,如果当前已经有事务运行,则抛出异常
-
NESTED :嵌套 这个 NESTED 是最特殊的,它就是基于保存点 SavePoint 的传播行为。它的定义是:如果当前没有事务运行,则开启一个新的事务;如果当前已经有事务运行,则会记录一个保存点,并继续运行在当前事务中。如果子事务运行中出现异常,则不会全部回滚,而是回滚到上一个保存点。可以发现,由于在 NESTED 的执行需要依赖关系型数据库的 SavePoint 机制,所以这种传播行为只适用于 DataSourceTransactionManager (即基于数据源的事务管理器)。而且 NESTED 通常都在同一个数据源中实现,对于多数据源,或者分布式数据库的话,NESTED 是搞不定的(假设两个 Service 依赖的 Dao 分别操作不同的数据库,那实际上已经形成分布式事务了,Spring 搞不定的)