如何合理使用Spring的事务方式

2025-05-19 15:50

本文主要是介绍如何合理使用Spring的事务方式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《如何合理使用Spring的事务方式》:本文主要介绍如何合理使用Spring的事务方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教...

Spring Framework 提供了全面的事务管理功能,以确保在应用程序中操作的原子性、一致性、隔离性和持久性(即 ACID 特性)。

事务管理是重要的,尤其是在涉及到数据库操作时,以确保数据的完整性和一致性。

1、介绍

1.1、底层构造

Spring事务管理高层抽象主要包括3个接口,Spring的事务主要是由他们共同完成的:

  • PlatformTransactionManager:事务管理器—主要用于平台相关事务的管理
  • TransactionDefinition:事务定义信息(隔离、传播、超时、只读)—通过配置如何进行事务管理。
  • TransactionStatus:事务具体运行状态—事务管理过程中,每个时间点事务的状态信息。

1.1.事务管理器

如何合理使用Spring的事务方式

PlatformTransactionManager该接口提供三个方法:

  • commit:提交事务
  • rollback:回滚事务
  • getTransaction:获取事务状态

1.2.事务定义信息

如何合理使用Spring的事务方式

TransactionDefinition该接口主要提供的方法:

  • getIsolationLevel:隔离级别获取
  • getPropagationBehavior:传播行为获取
  • getTimeout:获取超时时间
  • isReadOnly 是否只读(保存、更新、删除—对数据进行操作-变成可读写的,查询-设置这个属性为true,只能读不能写)

1.3.事务状态

TransactionStatus代表获取事务的状态。

	try {
		操作
	} catch (){
		rollback
	} finally {
		commit 
}

1.4.联系

用户管理事务,先配置事务管理方案TransactionDefinition、管理事务通过TransactionManager完成,TransactionManager根据 TransactionDefinition进行事务管理,

在事务运行过程中,每个时间点都可以通过获取TransactionStatus了解事务运行状态!

1.2、特点

定义

  • 事务是一个操作序列,这些操作要么全部成功,要么全部失败。无法将部分操作视为成功,其他部分则视为失败。因此,事务是一种确保数据一致性和完整性的重要手段。

ACID 特性

  • 原子性(Atomicity): 事务内的所有操作要么全部成功,要么全部失败。
  • 一致性(Consistency): 事务必须使数据库从一个一致的状态转变到另一个一致的状态。
  • 隔离性(Isolation): 一个事务的执行不能被其他事务干扰,确保并发执行下的每个事务都是在其自己的上下文中执行。
  • 持久性(Durability): 事务一旦提交,其结果便永久保存,即使系统故障也不会丢失。

1.3、原理

常见面试题:事务的原理

Spring 事务的底层实现主要使用的技术:AOP(动态代理) + ThreadLocal + try/catch。

1.动态代理AOP:

基本所有要进行逻辑增强的地方都会用到动态代理,AOP 底层也是通过动态代理实现。

2.事务同步管理器(TransactionSynchronizationManager)

Spring使用线程局部变量(ThreadLocal)来管理事务资源:

  • ThreadLocal:主要用于线程间的资源隔离,以此实现不同线程可以使用不同的数据源、隔离级别等等。
public abstract class TransactionSynchronizationManager {
    private static final ThreadLocal<Map<Object, Object>> resources =
        new NamedThreadLocal<>("Transactional resources");
    
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
        new NamedThreadLocal<>("Transaction synchronizations");
    
    private static final ThreadLocal<String> currentTransactionName =
        new NamedThreadLocal<>("Current transaction name");
    
    private static final ThreadLocal<Boolean> currentTransactionReadOnly =
        new NamedThreadLocal<>("Current transaction read-only status");
    
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
        new NamedThreadLocal<>("Current transaction isolation level");
    
    private static final ThreadLocal<Boolean> 编程actualTransactionActive =
        new NamedThreadLocal<>("Actual transaction active");
}

3.try/catch:

最终是执行 commit 还是 rollback,是根据业务逻辑处理是否抛出异常来决定。

Spring 事务的核心逻辑伪代码如下:

public void invokeWithinTransaction() {
    // 1.事务资源准备
    try {
        // 2.业务逻辑处理,也就是调用被代理的方法
    } catch (Exception e) {
        // 3.出现异常,进行回滚并将异常抛出
    } finally {
        // 现场还原:还原旧的事务信息
    }
    // 4.正常执行,进行事务的提交
    // 返回业务逻辑处理结果
}

4.流程

@Transactional方法调用
   │
   ↓
TransactionInterceptor拦截
   │
   ↓
获取事务属性(@Transactional配置)
   │
   ↓
根据传播行为决定是否创建新事务
   │
   ↓
TransactionSynchronizationManager绑定资China编程源到当前线程
   │
   ↓
执行业务方法
   │
   ┌─────────┴──────────┐
   ↓                    ↓
方法成功             方法抛出异常
   │                    │
   ↓                    ↓
提交事务              判断是否需要回滚
   │                    │
   ↓                    ↓
清理线程资源           回滚事务
   │                    │
   ↓                    ↓
返回结果              抛出异常

2. Spring 的事务管理

Spring 提供了强大的事务管理功能,包括编程式和声明式事务管理。

1、声明式事务管理

XML和注解形式)

特点

  • 易于使用:只需在方法上添加注解或在配置文件中定义事务规则即可启用事务管理。
  • 非侵入性:事务管理逻辑与业务逻辑分离,不会干扰业务逻辑代码。
  • 可配置性:可以灵活地配置事务的传播行为、隔离级别、超时和回滚规则等。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    @Transactional // 默认传播行为为 REQUIRED
    public void createUser(String username) {
        System.out.println("User " + username + " created.");
        // 调用另一个方法
        addUser(username);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW) // 新事务
    public void addUser(String username) {
        System.out.println("User " + username + " added in a new transaction.");
    }
    
    @Transactional(propagation = Propagation.NESTED) // 嵌套事务
    public void updateUser(String username) {
        System.out.println("Updating user: " + username);
    }
}

输出:

在调用 createUser 方法之后,如果内部调用 addUser,因为 addUser 不会使用aop代理,

这也意味着,addUser 的成功或失败不会影响到 createUser 所在的原有事务。

2、编程式事务

通过TransactionTempandroidlate手动管理事务。这里不过多讲解,实际使用较少。

在代码中显式地管理事务的开始、提交和回滚。这种方式更加灵活,但同时也增加了代码的复杂性。

有兴趣的可以参考:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PlatformTransactionManager transactionManager;

    public void createUser(User user) {
        TransactionDefinition def = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(def);

        try {
            userRepository.save(user);
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}

和JDBC相比:

  • 不需要开始或是关闭connection(可能还有try-finally的语法),相对应的,使用的是Transaction callbacks。
  • 也不需要手动的catch SQLExceptions,因为Spring会把这个异常当作runtime exceptions来处理。
  • 当然想要使用TransactionTemplate,需要先设置TransactionManager,并需要传入dataSource。这些都可以配置成Spring的bean,放到context中。

3、事务传播行为

Spring 提供了多种事务传播行为来定义一个事务方法应如何参与现有事务。常用的传播行为包括:

对事务比较积极的三个传播行为:

  • REQUIRED: 使用现有的事务,如果没有,则创建一个新的事务(默认值)。
  • REQUIRES_NEW: 总是创建一个新的事务,并挂起当前事务。
  • MANDATORY: 必须存在一个当前事务,若没有则抛出异常。

观望者:

  • NESTED: 在当前事务中执行一个嵌套事务(支持保存点)。

比较佛系的三个传播行为

  • SUPPORTS: 存在则加入,不存在,则非事务运行。
  • PROPAGATION_NOT_SUPPORTED:不能参与。
  • PROPAGATION_NEVER:不参与,参与事务,则抛出异常。

3、事务的隔离级别

1、并发事务的问题

如何合理使用Spring的事务方式

2、隔离级别

如何合理使用Spring的事务方式

如何合理使用Spring的事务方式

3、如何设置

(1) 声明式事务(通过 @Transactional 注解)

@Transactional(isolation = Isolation.READ_COMMITTED)
public void transferMoney() {
    // 业务逻辑
}

(2) 编程式事务(通过 TransactionTemplate

@Autowired
private TransactionTemplate transactionTemplate;

public void executeInTransaction() {
    transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
    transactionTemplate.execute(status -> {
        // 业务逻辑
        return null;
    });
}

4、选择隔离级别

优先使用默认值(ISOLATION_DEFAULT)

  • 除非有明确需求,否则依赖数据库默认配置,避免框架与数据库行为不一致。

平衡性能与一致性

  • 高并发场景 → READ_COMMITTED(减少锁竞争)。
  • 强一致性场景 → REPEATABLE_READSERIALIZABLE(需测试性能影响)。

5、查看隔离级别

  • 数据库日志:执行 SHOW VARIABLES LIKE 'transaction_isolation';mysql)或等效命令。
  • 代码调试:通过 TransactionSynchronizationManager.getCurrentTransactionIsolationLevel() 获取。

4、事务失效

1. 直接调用同一类中的事务方法

在同一类中直接调用被事务注解的方法会导致事务失效。因为 Spring 的 AOP 代理只在通过 Spring 管理的 Bean 进行调用时才会生效。

代码示例:

@Service
public class UserServiceTxImpl implements UserServiceTx {

   
    @Transactional(rollbackFor = RuntimeException.class) // 默认传播行为为 REQUIRED
    public void createUser(String username) {
        System.out.println("User " + username + " created.");
        // 调用另一个方法
        addUser(username);
        System.out.println("User " + username + " createUsed ");
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = RuntimeException.class) // 新事务
    public void addUser(String username) {
        System.out.println("User " + username + " added in a new transaction.");
        throw new RuntimeException("Exception in innerMethod");
    }


}

解决方案

1、可以在本bean里面直接使用spring注入的方式:不过会导致循环依赖。

2、 AopContext.currentProxy();

@Service
public class UserServiceTxImpl implements UserServiceTx {

    @Autowired
    private UserServiceTxImpl userService;

    @Transactional(rollbackFor = RuntimeException.class) // 默认传播行为为 REQUIRED
    public void createUser(String username) {
        System.out.println("User " + username + " created.");
        // 调用另一个方法
        userService.addUser(username);
        System.out.println("User " + username + " createUsed ");
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = RuntimeException.class) // 新事务
    public void addUser(String username) {
        System.out.println("User " + username + " added in a new transaction.");
        throw new RuntimeException("Exception in innerMethod");
    }


}
UserServiceTx o = (UserServiceTx) AopContext.currentProxy();

2. 直接使用 SQL 操作

如果你在事务管理的方法中执行了原生的 SQL 操作(例如通过 JDBC),而没有通过 Spring 的事务管理进行处理,事务可能会失效。

@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void createUser(String username) {
        System.out.println("In createUser method");

        // 直接使用 JDBC 操作,不通过 Spring 数据库访问
        jdbcTemplate.execute("INSERT INTO users (username) VALUES ('" + username + "')");

        // 模拟异常
        throw new RuntimeException("Intentional exception"); // 模拟抛出异常
    }
}

这些操作不通过 Spring 的数据访问层,将会导致事务管理操作失效。

3. 使用不正常的异常处理

Spring 只对运行时异常(RuntimeException)自动回滚,而对检查性异常(Exception)默认不回滚。

如果需要回滚需要rollbackFor指定回滚异常。

  @Transactional(rollbackFor = RuntimeException.class) // 默认传播行为为 REQUIRED
    public void createUser(String username) {
        System.out.println("User " + username + " created.")js;
        // 调用另一个方法
        userService.addUser(username);
        System.out.println("User " + username + " createUsed ");
    }

4、非public方法,目前事务 只支持pub

如何合理使用Spring的事务方式

5、mysql的存储引擎说mysaim不支持事务。

6、对异常进行try catch捕获,导致无法生效。

7、事务处理的响应超时。

总结

Spring 的事务管理功能为开发者提供了一种高效且灵活的方式来处理复杂的数据库操作,确保数据的一致性和完整性,通过注解或 XML 配置,可以很容易地查看哪些方法是事务性的,逻辑结构清晰。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(www.chinasem.cn)。

这篇关于如何合理使用Spring的事务方式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1154674

相关文章

使用Vue-ECharts实现数据可视化图表功能

《使用Vue-ECharts实现数据可视化图表功能》在前端开发中,经常会遇到需要展示数据可视化的需求,比如柱状图、折线图、饼图等,这类需求不仅要求我们准确地将数据呈现出来,还需要兼顾美观与交互体验,所... 目录前言为什么选择 vue-ECharts?1. 基于 ECharts,功能强大2. 更符合 Vue

Vue中插槽slot的使用示例详解

《Vue中插槽slot的使用示例详解》:本文主要介绍Vue中插槽slot的使用示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、插槽是什么二、插槽分类2.1 匿名插槽2.2 具名插槽2.3 作用域插槽三、插槽的基本使用3.1 匿名插槽

springboot+vue项目怎么解决跨域问题详解

《springboot+vue项目怎么解决跨域问题详解》:本文主要介绍springboot+vue项目怎么解决跨域问题的相关资料,包括前端代理、后端全局配置CORS、注解配置和Nginx反向代理,... 目录1. 前端代理(开发环境推荐)2. 后端全局配置 CORS(生产环境推荐)3. 后端注解配置(按接口

使用WPF实现窗口抖动动画效果

《使用WPF实现窗口抖动动画效果》在用户界面设计中,适当的动画反馈可以提升用户体验,尤其是在错误提示、操作失败等场景下,窗口抖动作为一种常见且直观的视觉反馈方式,常用于提醒用户注意当前状态,本文将详细... 目录前言实现思路概述核心代码实现1、 获取目标窗口2、初始化基础位置值3、创建抖动动画4、动画完成后

PyQt5 QDate类的具体使用

《PyQt5QDate类的具体使用》QDate是PyQt5中处理日期的核心类,本文主要介绍了PyQt5QDate类的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录核心功能常用方法及代码示例​1. 创建日期对象​2. 获取日期信息​3. 日期计算与比较​4. 日

Java如何根据word模板导出数据

《Java如何根据word模板导出数据》这篇文章主要为大家详细介绍了Java如何实现根据word模板导出数据,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... pom.XML文件导入依赖 <dependency> <groupId>cn.afterturn</groupId>

Java应用如何防止恶意文件上传

《Java应用如何防止恶意文件上传》恶意文件上传可能导致服务器被入侵,数据泄露甚至服务瘫痪,因此我们必须采取全面且有效的防范措施来保护Java应用的安全,下面我们就来看看具体的实现方法吧... 目录恶意文件上传的潜在风险常见的恶意文件上传手段防范恶意文件上传的关键策略严格验证文件类型检查文件内容控制文件存储

Go语言使用slices包轻松实现排序功能

《Go语言使用slices包轻松实现排序功能》在Go语言开发中,对数据进行排序是常见的需求,Go1.18版本引入的slices包提供了简洁高效的排序解决方案,支持内置类型和用户自定义类型的排序操作,本... 目录一、内置类型排序:字符串与整数的应用1. 字符串切片排序2. 整数切片排序二、检查切片排序状态:

浅析Java如何保护敏感数据

《浅析Java如何保护敏感数据》在当今数字化时代,数据安全成为了软件开发中至关重要的课题,本文将深入探讨Java安全领域,聚焦于敏感数据保护的策略与实践,感兴趣的小伙伴可以了解下... 目录一、Java 安全的重要性二、敏感数据加密技术(一)对称加密(二)非对称加密三、敏感数据的访问控制(一)基于角色的访问

Java计算经纬度距离的示例代码

《Java计算经纬度距离的示例代码》在Java中计算两个经纬度之间的距离,可以使用多种方法(代码示例均返回米为单位),文中整理了常用的5种方法,感兴趣的小伙伴可以了解一下... 目录1. Haversine公式(中等精度,推荐通用场景)2. 球面余弦定理(简单但精度较低)3. Vincenty公式(高精度,