当前位置: 首页 > article >正文

聊一聊Spring中的@Transactional注解【中】【事务传播特性】

前言


在开发过程中,我们常常会遇到这样的问题:当你在处理复杂的业务逻辑时,如何确保每一个操作都能顺利完成,而不会因为一个小小的错误而引发一场“数据灾难”?这就像在一场盛大的舞会上,你需要确保每位舞者都能完美配合,才能让整个表演毫无瑕疵。

这时,事务传播特性便像一位神秘的指挥家,默默地在幕后调控着这一切。它们帮助我们管理事务的生命周期,无论是让某个方法加入现有事务,还是在需要时优雅地挂起当前事务,甚至是独立开创一段新的舞蹈。不同的传播特性就像是各种舞步,灵活多变、各具特色。


一、样例

package com.lazy.snail.service;

import com.lazy.snail.dao.UserDao;
import com.lazy.snail.domain.UserInfo;
import com.lazy.snail.domain.UserLoginInfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.Timestamp;

/**
 * @author lazysnail
 */
@Service
public class UserService {
    private final UserDao userDao;

    private  final UserLoginService userLoginService;

    public UserService(UserDao userDao, UserLoginService userLoginService) {
        this.userDao = userDao;
        this.userLoginService = userLoginService;
    }



    @Transactional(rollbackFor = Exception.class)
    public void createUser(UserInfo user) throws UnknownHostException {
        userDao.save(user);

        UserLoginInfo userLoginInfo = new UserLoginInfo();
        userLoginInfo.setId(user.getUserId());
        userLoginInfo.setUserId(user.getUserId());
        userLoginInfo.setIp(InetAddress.getLocalHost().getHostAddress());
        userLoginInfo.setMac("");
        userLoginInfo.setLoginTime(new Timestamp(System.currentTimeMillis()));

        userLoginService.saveUserLoginInfo(userLoginInfo);
    }
}
package com.lazy.snail.service;

import com.lazy.snail.dao.UserLoginDao;
import com.lazy.snail.domain.UserLoginInfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @ClassName UserLoginService
 * @Description TODO
 * @Author lazysnail
 * @Date 2024/11/1 10:43
 * @Version 1.0
 */
@Service
public class UserLoginService {
    private final UserLoginDao userLoginDao;

    public UserLoginService(UserLoginDao userLoginDao) {
        this.userLoginDao = userLoginDao;
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveUserLoginInfo(UserLoginInfo userLoginInfo) {
        userLoginDao.save(userLoginInfo);
    }
}

二、事务传播特性

2.1PROPAGATION_REQUIRED

支持当前事务;如果不存在则创建事务

  • createUser和saveUserLoginInfo都有@Transactional注解
    1. createUser创建新事务tx1
    2. 由于存在tx1吗,saveUserLoginInfo不会创建新的事务,使用tx1
    3. saveUserLoginInfo出现异常,因在同一个事务,均回滚
/**
 * saveUserLoginInfo拦截
 */
// AbstractPlatformTransactionManager
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException {

    // 省略部分代码...

    if (isExistingTransaction(transaction)) {
        // 已存在事务,handleExistingTransaction中没有对PROPAGATION_REQUIRED进行特殊处理
        return handleExistingTransaction(def, transaction, debugEnabled);
    }

    // 省略部分代码...
}
  • createUser无@Transactional注解;saveUserLoginInfo有@Transactional注解
    1. createUser中的DML运行完就提交了
    2. saveUserLoginInfo会创建事务tx
    3. saveUserLoginInfo出现异常,不会回滚createUser中的DML
/**
 * saveUserLoginInfo拦截
 */
// AbstractPlatformTransactionManager
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException {

    // 省略部分代码...
	
    // PROPAGATION_REQUIRED的处理
    if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
				def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
				def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        SuspendedResourcesHolder suspendedResources = suspend(null);
        if (debugEnabled) {
            logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
        }
        try {
            // 创建新事务tx
            return startTransaction(def, transaction, debugEnabled, suspendedResources);
        }
        catch (RuntimeException | Error ex) {
            resume(null, suspendedResources);
            throw ex;
        }
    }

    // 省略部分代码...
}
  • createUser有@Transactional注解;saveUserLoginInfo无@Transactional注解
    1. createUser拦截后创建新事务tx
    2. saveUserLoginInfo中DML以非事务形式执行
    3. saveUserLoginInfo对于createUser只是一个普通方法
// MethodProxy
public Object invoke(Object obj, Object[] args) throws Throwable {
    try {
        init();
        FastClassInfo fci = fastClassInfo;
        // 目标方法调用 createUser
        // 由于saveUserLoginInfo方法不会被拦截,createUser方法后直接运行了saveUserLoginInfo,没有任何关于事务的操作
        return fci.f1.invoke(fci.i1, obj, args);
    } catch (InvocationTargetException ex) {
        throw ex.getTargetException();
    } catch (IllegalArgumentException ex) {
        if (fastClassInfo.i1 < 0)
            throw new IllegalArgumentException("Protected method: " + sig1);
        throw ex;
    }
}

2.2PROPAGATION_SUPPORTS

支持当前事务;如果不存在则按照非事务执行

package com.lazy.snail.service;

import com.lazy.snail.dao.UserLoginDao;
import com.lazy.snail.domain.UserLoginInfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * @ClassName UserLoginService
 * @Description TODO
 * @Author lazysnail
 * @Date 2024/11/1 10:43
 * @Version 1.0
 */
@Service
public class UserLoginService {
    private final UserLoginDao userLoginDao;

    public UserLoginService(UserLoginDao userLoginDao) {
        this.userLoginDao = userLoginDao;
    }
	
    // 传播特性指定为SUPPORTS
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
    public void saveUserLoginInfo(UserLoginInfo userLoginInfo) {
        userLoginDao.save(userLoginInfo);
    }
}
  • createUser(Propagation.REQUIRED) && saveUserLoginInfo(Propagation.SUPPORTS)
    1. createUser创建了新事务tx1
    2. saveUserLoginInfo使用tx1
/**
 * saveUserLoginInfo拦截
 */
// AbstractPlatformTransactionManager
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException {

    // 省略部分代码...

    if (isExistingTransaction(transaction)) {
        // 已存在事务,handleExistingTransaction中没有对PROPAGATION_SUPPORTS进行特殊处理
        return handleExistingTransaction(def, transaction, debugEnabled);
    }

    // 省略部分代码...
}
  • createUser没有@Transactional && saveUserLoginInfo(Propagation.SUPPORTS)
    1. createUser中的DML以非事务形式运行
    2. saveUserLoginInfo被拦截
    3. saveUserLoginInfo中运行两次userLoginDao.save(userLoginInfo),第二次主键冲突异常失败,第一次被提交至数据库
    4. saveUserLoginInfo中以非事务形式运行
/**
 * saveUserLoginInfo拦截
 */
// AbstractPlatformTransactionManager
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException {

    // 省略部分代码...
	
    // PROPAGATION_SUPPORTS满足以下条件,不会创建新事务
    if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
				def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
				def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        SuspendedResourcesHolder suspendedResources = suspend(null);
        if (debugEnabled) {
            logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
        }
        try {
            // 创建开启新事务tx
            return startTransaction(def, transaction, debugEnabled, suspendedResources);
        }
        catch (RuntimeException | Error ex) {
            resume(null, suspendedResources);
            throw ex;
        }
    }

    // 省略部分代码...
}

2.3PROPAGATION_MANDATORY

支持当前事务;如果不存在则抛出异常

  • createUser没有@Transactional && saveUserLoginInfo(Propagation.MANDATORY)
    1. saveUserLoginInfo被拦截后发现没有事务,直接抛出异常
// AbstractPlatformTransactionManager
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException {
    if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
        throw new IllegalTransactionStateException(
                "No existing transaction found for transaction marked with propagation 'mandatory'");
    }
}

2.4PROPAGATION_REQUIRES_NEW

创建新事务,如果存在事务则挂起

  • createUser有@Transactional && saveUserLoginInfo(Propagation.REQUIRES_NEW)
    1. saveUserLoginInfo被拦截后发现有事务,挂起之前的事务
      • 获取当前事务对象,包含事务的相关信息
      • 修改事务的激活状态
      • 保存当前事务的状态,如事务的传播特性、隔离级别等,便于后续能恢复到当前状态
      • 从当前线程的上下文移除事务相关的信息
    2. 创建新的事务
    3. 新事务完成后,恢复挂起事务
      • 从之前保存的状态中恢复事务的状态信息
      • 更新事务的激活状态
      • 恢复之前的上下文状态
// AbstractPlatformTransactionManager
private TransactionStatus handleExistingTransaction(
			TransactionDefinition definition, Object transaction, boolean debugEnabled)
			throws TransactionException {
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
        // 挂起事务
        SuspendedResourcesHolder suspendedResources = suspend(transaction);
        try {
            // 开启新的事务
            return startTransaction(definition, transaction, debugEnabled, suspendedResources);
        } catch (RuntimeException | Error beginEx) {
            resumeAfterBeginException(transaction, suspendedResources, beginEx);
            throw beginEx;
        }
    }
}

/**
 * 新事物正常结束提交时,恢复之前的事务
 */
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
    status.setCompleted();
    if (status.isNewSynchronization()) {
        TransactionSynchronizationManager.clear();
    }
    if (status.isNewTransaction()) {
        doCleanupAfterCompletion(status.getTransaction());
    }
    if (status.getSuspendedResources() != null) {
        if (status.isDebug()) {
            logger.debug("Resuming suspended transaction after completion of inner transaction");
        }
        Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
        // 恢复事务
        resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
    }
}

2.5PROPAGATION_NOT_SUPPORTED

不支持当前事务;始终以非事务形式执行

  • createUser有@Transactional && saveUserLoginInfo(Propagation.NOT_SUPPORTED)
    1. saveUserLoginInfo被拦截后,挂起createUser的事务
    2. saveUserLoginInfo中的DML以非事务的形式执行
    3. 恢复createUser的事务
  • createUser没有@Transactional && saveUserLoginInfo(Propagation.NOT_SUPPORTED)
    1. saveUserLoginInfo中的DML以非事务的形式执行
// AbstractPlatformTransactionManager
private TransactionStatus handleExistingTransaction(
			TransactionDefinition definition, Object transaction, boolean debugEnabled)
			throws TransactionException {
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
        if (debugEnabled) {
            logger.debug("Suspending current transaction");
        }
        // 挂起事务,并没有开启新的事务
        Object suspendedResources = suspend(transaction);
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        return prepareTransactionStatus(
                definition, null, false, newSynchronization, debugEnabled, suspendedResources);
    }
}

2.6PROPAGATION_NEVER

不支持当前事务;如果存在事务抛出异常

  • createUser有@Transactional && saveUserLoginInfo(Propagation.NEVER)
    1. saveUserLoginInfo被拦截后,发现有活动的事务,直接抛出异常
  • createUser没有@Transactional && saveUserLoginInfo(Propagation.NEVER)
    1. saveUserLoginInfo被拦截后,发现没有活动的事务,以非事务形式执行DML
// AbstractPlatformTransactionManager
private TransactionStatus handleExistingTransaction(
			TransactionDefinition definition, Object transaction, boolean debugEnabled)
			throws TransactionException {
	
    // 抛出异常
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
        throw new IllegalTransactionStateException(
                "Existing transaction found for transaction marked with propagation 'never'");
    }
}

2.7PROPAGATION_NESTED

如果存在事务,则以嵌套形式执行,否则像PROPAGATION_REQUIRED一样

  • createUser有@Transactional && saveUserLoginInfo(Propagation.NESTED)
    1. saveUserLoginInfo被拦截后,发现已存在活动事务,创建一个新的嵌套事务(创建一个保存点)
    2. 共享外部事务的数据库连接
    3. 嵌套事务的回滚提交独立于外部事务
  • createUser没有@Transactional && saveUserLoginInfo(Propagation.NESTED)
    1. saveUserLoginInfo被拦截后,发现没有活动事务
    2. 创建一个新事务,与PROPAGATION_REQUIRED相同
// AbstractPlatformTransactionManager
// 存在活动事务时
private TransactionStatus handleExistingTransaction(
			TransactionDefinition definition, Object transaction, boolean debugEnabled)
			throws TransactionException {
 	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        if (!isNestedTransactionAllowed()) {
            throw new NestedTransactionNotSupportedException(
                    "Transaction manager does not allow nested transactions by default - " +
                    "specify 'nestedTransactionAllowed' property with value 'true'");
        }
        if (debugEnabled) {
            logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
        }
        if (useSavepointForNestedTransaction()) {
            // 创建保存点
            DefaultTransactionStatus status =
                    prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
            status.createAndHoldSavepoint();
            return status;
        } else {
            return startTransaction(definition, transaction, debugEnabled, null);
        }
    }   
}

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException {
    // 没有活动事务时,PROPAGATION_NESTED与PROPAGATION_REQUIRED一样
    if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
				def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
				def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        SuspendedResourcesHolder suspendedResources = suspend(null);
        if (debugEnabled) {
            logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
        }
        try {
            return startTransaction(def, transaction, debugEnabled, suspendedResources);
        }
        catch (RuntimeException | Error ex) {
            resume(null, suspendedResources);
            throw ex;
        }
    }
}

三、总结

3.1七大特性总结

传播特性描述适用场景
PROPAGATION_REQUIRED加入现有事务或新建事务最常用的特性
PROPAGATION_REQUIRES_NEW总是新建事务,挂起当前事务独立执行某些操作
PROPAGATION_NESTED创建嵌套事务,依赖于保存点复杂的业务逻辑,需要部分回滚
PROPAGATION_NOT_SUPPORTED非事务方式执行,挂起当前事务确保在没有事务上下文中执行
PROPAGATION_NEVER非事务方式执行,若有事务则抛出异常明确要求不使用事务
PROPAGATION_SUPPORTS加入事务或非事务方式执行灵活的环境,支持可选事务
PROPAGATION_MANDATORY加入现有事务,若无事务则抛出异常强制在事务上下文中运行

3.2事务挂起与嵌套事务的区别

特性事务挂起嵌套事务
目的暂时中断当前事务的执行在一个事务上下文中创建一个独立的事务
状态管理保持挂起事务的状态,稍后恢复使用保存点管理嵌套事务的状态
事务提交/回滚挂起后,当前事务继续执行,之后可以恢复嵌套事务可以独立提交或回滚,外部事务不受影响
传播特性适用于 PROPAGATION_NOT_SUPPORTEDPROPAGATION_REQUIRES_NEW使用 PROPAGATION_NESTED

http://www.kler.cn/a/380727.html

相关文章:

  • C++ Learning string类的使用
  • kubeadm搭建k8s集群
  • I.MX6U 启动方式详解
  • 如何评估一个股票API接口
  • typora数学符号
  • SQL server学习09-数据库编程(上)
  • 【保姆级教程】使用 oh-my-posh 和 clink 打造个性化 PowerShell 和 CMD
  • vue 使用docx-preview 预览替换文档内的特定变量
  • k8s Service四层负载:服务端口暴露
  • 【OJ题解】在字符串中查找第一个不重复字符的索引
  • WPF-实现多语言的静态(需重启)与动态切换(不用重启)
  • 这款Chrome 插件,帮助任意内容即可生成二维码
  • C语言---文件操作万字详细分析(6)
  • Charles抓包安装
  • 一个最简单的网络编程
  • 【车辆车型识别】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+算法模型
  • git使用的一般流程
  • 一周内从0到1开发一款 AR眼镜 相机应用?
  • 浅谈——深度学习和马尔可夫决策过程
  • bert-base-chinese模型使用教程
  • Linux系统-日志轮询(logrotate)
  • 【Java语言】继承和多态(一)
  • FPGA实现图像处理算法的创新点
  • Handler源码和流程分析
  • 算法: 链表题目练习
  • 前端用docker部署