文章目录
-
- 1 Spring中七种事务传播行为
- 2 定义
- 3 定理
- 4 推理
- 5 证明
-
- 5.1 事务传播行为证明
- 5.2 推理证明
- 6 事务切面设置的几个核心状态
-
- 6.1 newTransaction
- 6.2 actualNewSynchronization
- 6.3 isSynchronizationActive/actualTransactionActive
- 6.4 connectionHolder
- 6.5 currentTransactionStatus
- 7 required/requires_new/nested 浅析
-
- 7.1 doBegin
- 7.2 prepareSynchronization
- 8 support/not_support/never 浅析
- 9 REQUIRED/SUPPORTS/ MANDATORY 浅析
- 10 NESTED 浅析
- 11 REQUIRES_NEW 浅析
- 12 NOT_SUPPORTED 浅析
- 13 数据库操作执行时用到和设置的几个状态
-
- 13.1 获取 session
- 13.2 获取 connection
- 14 connection auto-commit 属性介绍
- 15 善用 PROPAGATION_SUPPORTS
- 参考
- Spring 在 TransactionDefinition 接口中规定了 7 种类型的事务传播行为
- 事务传播行为是Spring框架独有的事务增强特性,不属于数据库行为
- 通过这 7 种 传播行为,可以使我们更灵活的控制哪些行为在同一个事务内、哪些行为不在同一事务内、各事务之间的关系,从而使得我们的业务逻辑更加灵活
1 Spring中七种事务传播行为
- 事务传播行为规定了在一个嵌套方法内部,哪些方法属于同一个事务,哪些方法不属于同一个事务,以及事务之间的关系
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果存在则加入事务。如果不存在,创建新事务 |
PROPAGATION_SUPPORTS | 如果存在则加入事务。如果不存在,以不可用事务执行 |
PROPAGATION_MANDATORY | 如果存在则加入事务。如果不存在,抛出异常 |
PROPAGATION_REQUIRES_NEW | 如果存在则挂起当前事务,并创建新事务。如果不存在则创建事务 |
PROPAGATION_NESTED | 如果存在则创建嵌套事务。如果不存在,创建新事务 |
PROPAGATION_NOT_SUPPORTED | 以不可用事务运行。如果存在事务,则先挂起当前事务 |
PROPAGATION_NEVER | 以不可用事务运行。如果存在事务,抛出异常 |
2 定义
- null:表示没有 @Transactional 注解的方法,且不在事务内
- NULL:表示没有 @Transactional 注解的方法,但在事务内(其外部有事务,所以加入外部事务)
- required:表示有 @Transactional(propagation = Propagation.REQUIRED) 的方法,但是外部没有事务
- REQUIRED:表示有 @Transactional(propagation = Propagation.REQUIRED) 的方法,但是外部有事务
- 同理其它 6 个传播行为
- 数据库错误:表示有 @Transactional 注解的方法,执行数据库操作时失败,但是异常最终被 catch 掉了
- 异常:方法抛出来的、没有被捕获的异常
- 方法被回滚:表示方法内部的数据库操作被回滚
- 事务切面方法:表示加入事务,且加入事务切面(被 @Transactional 注解注释的方法才会加入事务切面)
- 事务方法:表示加入事务的方法(包含事务切面方法)
- 外层事务:表示先加入同一事务的方法
- 真实事务:表示有 @Transactional 注解的方法
- 普通方法:没有 @Transactional 注解的方法
3 定理
- null 方法不受事务影响,只要执行成功就不会被回滚(例:插入了一行数据后,执行其它语句立马报错也不会被回滚,因为null 方法插入后会立马提交,其 auto-commit 为 true)
- 事务内发生 异常,加入这个事务的所有数据库操作都会被回滚
4 推理
- NULL 方法受事务影响,外层事务被回滚,NULL 方法也会被回滚(…方法证明)
- 如果 NULL 方法发生 数据库错误,不影响事务(即是 3)
- required 内部的 NULL 方法发生 数据库错误,required 方法不会被回滚(虽然在同一事务内,但是 NULL 方法的数据库异常不会传递到事务切面,因为 NULL 方法虽然加入了事务,但是没有加入事务切面)
- required 内部的 REQUIRED 方法发生 数据库错误,required 方法被回滚(通过事务切面将回滚状态传递到外层)
- required 内部的 SUPPORTS 方法发生 数据库错误,required 方法被回滚(同理)
- required 内部的 MANDATORY 方法发生 数据库错误,required 方法被回滚(同理)
- required 内部的 REQUIRES_NEW 方法发生 数据库错误,required 方法不会被回滚(不在同一个事务)
- required 内部的 NESTED 方法发生 数据库错误,required 方法不会被回滚(NESTED 发生数据库错误会回滚到保存点,保证了外部事务的有效性)
- required 内部的 NOT_SUPPORTED 方法发生 数据库错误,required 方法不会被回滚(NOT_SUPPORTED 方法 不在事务内)
- requires_new、nested 情况和 required 相同,因为这两个都只是开启新事务,意思和 required 一模一样
- supports、not_supported、never 都是以非事务运行和 null 效果一样(null之内的普通方法不能复用session)
- mandatory 直接抛异常
- NESTED:--------0------0---------,两个0 之间是嵌套事务,不管是内层嵌套事务还是外层事务,其实是一个事务,只是在这个事务线上创建了一个保存点,嵌套事务内部事情处理完了之后,整个事务状态会回到保存点时的状态,这样保证了即使嵌套内部发生数据库错误(这时仍然会设置事务为 rollback 状态),因为方法完了之后事务状态会回到保存点,所以 rollback 状态被覆盖了
- NESTED 和 NULL 的区别
- NULL 内部的 事务方法 属于 NULL 所在事务,而 NESTED 内部的 事务方法 属于 NESTED 开启的嵌套事务和外层事务没关系
- NULL 内部的 事务切面方法 发生 数据库错误 会导致外部事务回滚,而 NESTED 内部的事务切面方法 发生 数据库错误 只会导致 NESTED 开启的嵌套事务回滚,而外层事务不会
- NESTED 和 REQUIRES_NEW 的区别
- 外层事务发生 数据库错误 会导致 NESTED 方法回滚,而不会导致 REQUIRES_NEW 方法回滚
5 证明
5.1 事务传播行为证明
- 事务传播行为证明
5.2 推理证明
- 推理证明
6 事务切面设置的几个核心状态
6.1 newTransaction
- 是否是新开启的事务
- 此属性是直接赋值的,没有判断逻辑
- 所有的小写/REQUIRES_NEW 为 true
- 所有的大写(除了 REQUIRES_NEW) 为 false
- 事务关闭时如果 newTransaction= true 且 actualTransactionActive=true,才会做 connection 的 commit 操作
6.2 actualNewSynchronization
- 当前线程是否是新开启同步的
- 如果当前线程已经开启同步了,返回 false。否则,返回 true
AbstractPlatformTransactionManager#newTransactionStatus
actualNewSynchronization = !TransactionSynchronizationManager.isSynchronizationActive()
6.3 isSynchronizationActive/actualTransactionActive
- 当前线程是否同步/当前线程的事务是否可用
- 如果 actualNewSynchronization 为 false 的话
- 如果 status 中的 transaction 为 null,actualTransactionActive=false。否则为 true
- isSynchronizationActive = true
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
if (status.isNewSynchronization()) {
//关键 1:设置当前线程事务是否活跃
setActualTransactionActive(status.hasTransaction());
//关键 2: 设置事务是否已经同步
//synchronizations.set(new LinkedHashSet<>());
initSynchronization();
}
}
6.4 connectionHolder
- 此属性只有在创建新的、可用的事务时,才会设置
- transaction.newConnectionHolder = true
- conHolder.setSynchronizedWithTransaction(true)
- conHolder.setTransactionActive(true)
- connection.setAutoCommit(false)
org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
在 doBegin 方法中做了以下操作
1. 创建 connectionHolder
2. 将此 connectionHolder 设置到当前事务中,并设置 newConnectionHolder = true
3. 设置 ConnectionHolder().setSynchronizedWithTransaction(true) ----> 1
4. 将此 connectionHolder 中的 connection 设置 auto-commit = false -----> 2
5. 设置 事务的 read only 状态(如果指定了 read only 的话)
6. getConnectionHolder().setTransactionActive(true) -----> 3
6.5 currentTransactionStatus
- 设置当前线程的事务状态
TransactionAspectSupport#prepareTransactionInfo#bindToThread
//获取当前事务的状态:TransactionStatus[transaction,newTransaction,newSynchronization,suspendedResources,readOnly]
//可以看出 之前很多状态信息 都在 TransactionStatus 中保存了
//这个线程中保存了 旧的 TransactionStatus 和当前的 TransactionStatus,旧的用于事务挂起等功能
private void bindToThread() {
this.oldTransactionInfo = transactionInfoHolder.get();
transactionInfoHolder.set(this);
}
7 required/requires_new/nested 浅析
- 此三个状态为 创建新事务
- 事务切面后的核心状态信息
- newTransaction=true(是新事务)
- actualNewSynchronization=true(新开启的)
- isSynchronizationActive=true(新开启同步)
- actualTransactionActive = true(事务可用)
- 创建并设置 connectionHolder(设置特定的 connectionHolder)
//AbstractPlatformTransactionManager#getTransaction
suspendedResources = suspend(null);//内部不进行任何操作,返回 null
try {
boolean newSynchronization = 始终为 true;
//status 的关键属性, newTransaction=true,actualNewSynchronization=true
//说明是新事务,且新开启事务同步(后面加入事务的就是 false)
DefaultTransactionStatus status = newTransactionStatus();
//创建新的connection,并设置其状态为和事务同步且代表一个活动的事务,并加入到线程中
//设置事务 read only 标识,如果需要的话
doBegin(transaction, definition);
//设置当前线程的事务状态:hasTransaction=true, isSynchronizationActive()=true
//这样在创建 session 后,就会将 session 加入当前线程,重复使用(registerSessionHolder())
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException | Error ex) {
//这里的 suspendedResources == null,内部不会做任何操作
resume(null, suspendedResources);
throw ex;
}
7.1 doBegin
protected void doBegin(Object transaction, TransactionDefinition definition) {
try {
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
//因为事务中还没有 connection 所以进入此处获取 con(这里就可以动态切换数据源了)
Connection newCon = obtainDataSource().getConnection();
//将此 connection 设置到事务中,并标记为 新的连接
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
//标记此 connection 为和事务同步状态
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = 事务隔离级别;
//Switch to manual commit
//设置事务 read only 状态(如果事务标记了 read only,通过注解中的属性标记)
prepareTransactionalConnection(con, definition);
//标记此 connection 代表一个活动的事务
txObject.getConnectionHolder().setTransactionActive(true);
if (txObject.isNewConnectionHolder()) {
//可以走到这,将上面创建的 connection 设置到当前线程中,这样统一事务就复用 con 了
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
//发生异常则关闭连接
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
7.2 prepareSynchronization
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
if (status.isNewSynchronization()) {//肯定为 true
setActualTransactionActive(status.hasTransaction());//肯定为 true
setCurrentTransactionIsolationLevel(..);
setCurrentTransactionReadOnly(..);
setCurrentTransactionName(..);
//synchronizations.set(new LinkedHashSet<>());
initSynchronization();
}
}
8 support/not_support/never 浅析
- 此三个状态为以 不可用事务 执行状态
- 事务切面后的核心状态信息
- newTransaction=true(是新事务)
- actualNewSynchronization=true(新开启的)
- isSynchronizationActive=true(新开启同步)
- actualTransactionActive = false(事务不可用)
- 没有创建 conHolder
- 这 3 个方法表示以 非事务执行
- 这样虽然执行时创建了 session和connection 并加入了线程,当内部 事务方法 执行时也会重新创建事务,并覆盖 session/connection 信息
- 技巧: 当此3种方法内部有 普通方法 时,普通方法会复用 session/connection,减轻了创建 session/connection 的消耗,特别是在高并发的时候
- 并且没有执行 doBegin() 方法创建并设置 connection 的 auto-commit 为 false!,使得数据库操作一执行完就立刻提交了
@Override protected boolean isExistingTransaction(Object transaction) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; //这里返回 false,因为 isTransactionActive() = false //返回 false,就导致新来的 真实事务 方法会走没有事务存在的逻辑,这样就覆盖了非事务绑定的 session 和 connection 信息了 return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive()); }
- 和 6 的区别仅于
- 6 通过 doBegin() 方法,创建 connection 相关信息
- 7 中的事务状态 hasTransaction=false,actualTransactionActive=false
//AbstractPlatformTransactionManager#getTransaction
boolean newSynchronization = 这个目前始终为 true;
//设置当前线程的事务状态:hasTransaction=true, isSynchronizationActive()=true
//ActualTransactionActive = false
//这样在创建 session 后,就会将 session 加入当前线程,重复使用(registerSessionHolder())
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
protected final DefaultTransactionStatus prepareTransactionStatus(
TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {
//transaction 为 null,hasTransaction=false, isSynchronizationActive()=true
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);
prepareSynchronization(status, definition);
return status;
}
9 REQUIRED/SUPPORTS/ MANDATORY 浅析
- 此三个状态为 加入已存在事务
- 事务切面后的核心状态信息
- newTransaction=false(不是新事务)
- actualNewSynchronization=false(不是新同步的)
- 没有设置 isSynchronizationActive(沿用创建事务时的状态,一般 true)
- 没有设置 actualTransactionActive(沿用创建事务时的状态,一般 true)
- 没有创建 conHolder(沿用创建事务时创建 conHolder)
//AbstractPlatformTransactionManager#handleExistingTransaction
boolean newSynchronization = 始终是 true;
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
protected final DefaultTransactionStatus prepareTransactionStatus(
TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {
//hasTransaction=true,newTransaction=false,newSynchronization=false
//以上值说明,当前的事务不是新开启的事务,也不是新同步的事务
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);
//因为 newSynchronization=false,此方法内部不做任何处理
prepareSynchronization(status, definition);
return status;
}
10 NESTED 浅析
- 此状态为 创建嵌套事务。和 9 的唯一区别就是创建了一个 savepoint。在嵌套事务内发生的 数据库错误,会回滚到这个保存点,所以并不会影响外部事务。外部事务仍可影响内部事务
- 事务切面后的核心状态信息
- newTransaction=false(不是新事务)
- actualNewSynchronization=false(不是新同步的)
- 没有设置 isSynchronizationActive(沿用创建事务时的状态,一般 true)
- 没有设置 actualTransactionActive(沿用创建事务时的状态,一般 true)
- 没有创建 conHolder(沿用创建事务时创建 conHolder)
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
//和 9 的唯一区别就是场景 savepoint
status.createAndHoldSavepoint();
11 REQUIRES_NEW 浅析
- 挂起之前的事务,重新创建新事务。完全可以当作 7 来看,只是挂起了之前的事务
- 事务切面后的核心状态信息
- newTransaction=true(是新事务)
- actualNewSynchronization=true(新开启的)
- isSynchronizationActive=true(新开启同步)
- actualTransactionActive = true(事务可用)
- 创建并设置 connectionHolder(设置特定的 connectionHolder)
//挂起之前的事务(把之前的事务信息保存到当前事务,当前事务完成后,在设置回去)
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
boolean newSynchronization = true;
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException | Error beginEx) {
//发生异常也重置挂起的事务
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
12 NOT_SUPPORTED 浅析
- 挂起之前事务 (之前的 connection/session 信息没有了),以不可用事务执行。和 8 的唯一区别是保存了之前事务。该事务执行完之后会重新设置之前的事务
- 事务切面后的核心状态信息
- newTransaction=true(是新事务)
- actualNewSynchronization=true(新开启的)
- isSynchronizationActive=true(新开启同步)
- actualTransactionActive = false(事务不可用)
- 没有创建 conHolder
Object suspendedResources = suspend(transaction);
boolean newSynchronization = true;
return prepareTransactionStatus(
definition, null, false, newSynchronization, debugEnabled, suspendedResources);
13 数据库操作执行时用到和设置的几个状态
13.1 获取 session
- session 只要有任何事务,都会加入到线程中,不可用事务也是
//org.mybatis.spring.SqlSessionUtils#getSqlSession#sessionHolder
//如果当前线程中的 sessionHolder 不为 null 且 此 session 和 事务处于同步状态,就从事务中得到 session,否则返回 null
//目前只要有 holder 则 isSynchronizedWithTransaction 肯定为 true
if (holder != null && holder.isSynchronizedWithTransaction()) {}
//org.mybatis.spring.SqlSessionUtils#getSqlSession#registerSessionHolder
//如果当前线程事务处于同步状态,则将创建的 session 绑定到当前线程中
//只要方法或方法外层有 @Transactional 任意类型注解,此处都为 true
if (TransactionSynchronizationManager.isSynchronizationActive()) {}
13.2 获取 connection
- 在此处创建的 connection 都不具备手动 commit/rollback 能力
- 只有在 doBegin() 方法中创建并加入事务的才正真具备事务能力
//org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
//如果当前线程存在 conHolder 且 conHolder 中存在 connection 或者 conholder 已经和事务同步,则直接返回 conHolder 中的 connection
//只要 conHolder 不为 null,后面一般都为 true
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
}
//org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
//只要方法或方法外层有 @Transactional 任意类型注解,此处一般都为 true
if (TransactionSynchronizationManager.isSynchronizationActive()) {}
14 connection auto-commit 属性介绍
- 总的来说如果 auto-commit 为 true,则数据库操作全部完成时就自动提交
将此连接的自动提交模式设置为给定状态。
如果连接处于自动提交模式,则其所有SQL语句都将作为单个事务执行和提交。否则,只会在事务完成或失败后统一进行 commit 或 rollback。
默认情况下,新连接处于自动提交模式。自动提交发生在 statement 完成后。
statement 完成的时间取决于SQL语句的类型:
1. 对于DML语句,如Insert、Update或Delete和DDL语句,该语句在完成执行后即完成。
2. 对于Select语句,当关联的结果集关闭时,该语句即完成。
3. 对于CallableStatement对象或返回多个结果的语句,当所有关联的结果集都已关闭,并且已检索到所有更新计数和输出参数时,该语句即完成。
- 总的来说就是数据库操作全部完成时就自动提交
注意:如果在事务期间调用此方法,并且更改了自动提交模式,则将提交事务。如果调用setAutoCommit并且自动提交模式没有更改,则调用是no-op。
void setAutoCommit(boolean autoCommit) throws SQLException;
15 善用 PROPAGATION_SUPPORTS
- 如果存在则加入事务。如果不存在,以非事务执行
- 此 传播类型其实和没有 @Transactional 标记的作用基本一致
- 如果外部有事务,则 这两种情况都加入事务
- 如果外部没有事务,则这两种情况都会自动提交
- 区别: SUPPORTS 方法在外围没有事务时,也会创建 session/connection 并加入到线程,这样在此方法内的其它 普通方法 都会重用线程中的 session/connection,避免了频繁创建!
参考
spring-boot-starter:2.1.13.RELEASE
mybatis-spring-boot-starter:2.1.1