“在同一事务中“ 的含义

一、"在同一事务中" 的核心含义

"在同一事务中" 指多个数据库操作共享同一个事务上下文,具有以下特点:

  1. 原子性保证:所有操作要么全部成功提交,要么全部失败回滚。
  2. 隔离性共享:操作使用相同的隔离级别(如 READ COMMITTED)。
  3. 资源共享:操作使用同一个数据库连接,且事务状态(如锁)保持一致。

二、代码中如何表示 "在同一事务中"

1. Spring 框架中的实现方式

在 Spring 中,主要通过 **@Transactional注解编程式事务 ** 来控制事务边界。

示例 1:使用@Transactional注解(声明式事务)

@Service
public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate PaymentService paymentService;// 该方法开启一个事务,内部所有操作都在同一事务中@Transactional(propagation = Propagation.REQUIRED) // 默认值可不写public void createOrder(Order order) {// 操作1:保存订单orderRepository.save(order);// 操作2:扣减库存(假设在同一事务中)inventoryService.reduceStock(order.getProductId(), order.getQuantity());// 操作3:调用支付服务(默认加入当前事务)paymentService.processPayment(order);// 若以上任一操作失败,整个事务回滚}
}

关键点

  • @Transactional注解标记的方法会被 Spring AOP 拦截,自动开启、提交或回滚事务。
  • 默认传播行为Propagation.REQUIRED表示:若当前无事务,则创建新事务;若已有事务,则加入该事务。

示例 2:跨方法调用保持同一事务

因为@Transactional(propagation = Propagation.REQUIRED)是默认有的

@Service
public class OrderService {@Autowiredprivate PaymentService paymentService;// 外层事务方法@Transactionalpublic void processOrder(Order order) {// 操作1:创建订单createOrder(order);// 操作2:调用支付服务(默认加入当前事务)paymentService.processPayment(order);// 若此处抛出异常,createOrder和processPayment都会回滚}// 内层方法(默认加入外层事务)public void createOrder(Order order) {// 订单创建逻辑}
}

关键点

  • 同一个类中的方法调用(如processOrder调用createOrder)默认共享事务,因为 Spring AOP 通过代理对象实现事务增强。
  • createOrder单独标记@Transactional,且调用者无事务,则createOrder会创建新事务。

2. 编程式事务(手动控制事务边界)

适用于需要更细粒度控制事务的场景。

示例 3:使用 TransactionTemplate(Spring 早期方式)

@Service
public class TransactionExample {@Autowiredprivate TransactionTemplate transactionTemplate;@Autowiredprivate UserRepository userRepository;public void transferMoney(Long fromUserId, Long toUserId, BigDecimal amount) {transactionTemplate.execute(status -> {try {// 操作1:扣减转出用户余额User fromUser = userRepository.findById(fromUserId).orElseThrow();fromUser.setBalance(fromUser.getBalance().subtract(amount));userRepository.save(fromUser);// 模拟异常if (amount.compareTo(new BigDecimal("1000")) > 0) {throw new RuntimeException("金额过大");}// 操作2:增加转入用户余额User toUser = userRepository.findById(toUserId).orElseThrow();toUser.setBalance(toUser.getBalance().add(amount));userRepository.save(toUser);return true;} catch (Exception e) {// 手动回滚(实际中通常自动回滚)status.setRollbackOnly();throw e;}});}
}

关键点

  • transactionTemplate.execute()包裹的所有操作在同一事务中。
  • 异常会触发事务回滚,成功则自动提交。

3. 使用 PlatformTransactionManager(更底层的方式)

关键点

  • 通过PlatformTransactionManager手动控制事务的开始、提交和回滚。
  • 适合需要动态调整事务属性的场景。

三、常见问题与注意事项

1.事务传播行为的影响

若子方法使用REQUIRES_NEW,则会创建新事务,与外层事务隔离。

示例:

@Transactional
public void parentMethod() {// 外层事务childService.childMethod(); // 若childMethod使用REQUIRES_NEW,则不在同一事务中
}

2.异常处理与事务回滚

Spring 默认只对RuntimeExceptionError回滚事务,检查异常(如IOException)不会触发回滚。

可通过@Transactional(rollbackFor = Exception.class)扩大回滚范围。

3.同一个类中的方法调用

Spring AOP 通过代理对象实现事务增强,若methodA调用methodB(同一类中),methodB@Transactional会失效。

解决方案:

@Service
public class SelfCallExample {@Autowiredprivate SelfCallExample self; // 注入自身代理@Transactionalpublic void methodA() {// 正确方式:通过代理调用self.methodB();}@Transactionalpublic void methodB() {// ...}
}

四、总结

"在同一事务中" 的核心是共享事务上下文,在代码中通过以下方式实现:

  1. 声明式事务:使用@Transactional注解标记方法,默认传播行为REQUIRED确保操作在同一事务中。
  2. 编程式事务:通过TransactionTemplatePlatformTransactionManager手动控制事务边界。
  3. 跨方法调用:确保方法间通过代理对象调用,且子方法不使用REQUIRES_NEW等隔离传播行为。

合理控制事务边界是保证数据一致性的关键,需根据业务场景选择合适的事务管理方式。





通俗易懂地理解 "同一事务" 与代码示例



一、"同一事务" 的通俗解释

比喻:想象你在银行柜台办理转账业务,整个流程包括:

  1. 验证转出账户余额
  2. 扣减转出账户金额
  3. 增加转入账户金额
  4. 记录交易日志

这四个步骤必须要么全部成功,要么全部失败(例如,若扣钱成功但加钱失败,银行会回滚整个操作)。这就是 "在同一事务中" 的含义 ——一组不可分割的操作,共享同一个 "原子性" 保障

二、代码示例:如何在 Spring 中实现 "同一事务"

1. 最常见场景:一个方法内的多个操作

@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Autowiredprivate LogRepository logRepository;// 整个方法在同一事务中@Transactionalpublic void transferMoney(Long fromId, Long toId, BigDecimal amount) {// 操作1:扣钱User fromUser = userRepository.findById(fromId).orElseThrow();fromUser.setBalance(fromUser.getBalance().subtract(amount));userRepository.save(fromUser);// 模拟网络延迟或其他异常// if (true) throw new RuntimeException("模拟异常");// 操作2:加钱User toUser = userRepository.findById(toId).orElseThrow();toUser.setBalance(toUser.getBalance().add(amount));userRepository.save(toUser);// 操作3:记录日志(与转账共享同一事务)Log log = new Log("转账", amount, fromId, toId);logRepository.save(log);}
}

关键点

  • @Transactional标记整个方法,内部的 3 个数据库操作共享同一事务。
  • 若中间抛出异常(如取消注释第 16 行),则所有操作都回滚,钱不会平白消失。

2. 跨方法调用保持同一事务

@Service
public class OrderService {@Autowiredprivate ProductService productService;@Autowiredprivate InventoryService inventoryService;// 主事务方法@Transactionalpublic void createOrder(Order order) {// 操作1:保存订单orderRepository.save(order);// 操作2:扣减库存(调用其他服务的方法)inventoryService.reduceStock(order.getProductId(), order.getQuantity());// 操作3:更新商品销量(调用其他服务的方法)productService.updateSales(order.getProductId(), order.getQuantity());// 若此处抛出异常,整个事务回滚// throw new RuntimeException("订单创建失败");}
}@Service
public class InventoryService {// 该方法默认加入调用者的事务public void reduceStock(Long productId, Integer quantity) {Inventory inventory = inventoryRepository.findByProductId(productId);inventory.setStock(inventory.getStock() - quantity);inventoryRepository.save(inventory);}
}

关键点

  • createOrder方法上的@Transactional使整个调用链在同一事务中。
  • reduceStockupdateSales虽然在不同类中,但默认加入外层事务,共享原子性。
  • 若订单保存成功,但扣库存失败,则整个操作回滚,不会出现 "有订单但没扣库存" 的情况。

3. 同一类中方法调用的陷阱与解决方案

@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Autowiredprivate UserService self; // 注入自身代理// 错误示例:同一类中方法调用,事务不生效@Transactionalpublic void wrongUpdate(User user) {// 保存用户基本信息userRepository.save(user);// 调用同一类中的方法(事务不会生效)updateLastLoginTime(user.getId());// 若此处抛出异常,updateLastLoginTime的操作不会回滚}@Transactionalpublic void updateLastLoginTime(Long userId) {User user = userRepository.findById(userId).orElseThrow();user.setLastLoginTime(new Date());userRepository.save(user);}// 正确示例:通过代理调用,事务生效@Transactionalpublic void correctUpdate(User user) {userRepository.save(user);// 通过代理调用,事务生效self.updateLastLoginTime(user.getId());}
}

关键点

  • Spring 通过代理对象实现事务增强,同一类中直接调用方法(如wrongUpdate)会导致内层方法的@Transactional失效。
  • 解决方案:通过@Autowired注入自身代理(self),或拆分到不同 Service 类中。

三、常见问题与避坑指南

1. 为什么要在同一事务中?

反例:若转账操作不在同一事务中:

  • 扣钱成功 → 系统崩溃 → 加钱失败
  • 结果:钱平白消失,用户炸锅!

正例:在同一事务中,要么都成功,要么都失败,钱不会消失。

2. 如何验证是否在同一事务中?

@Service
public class TestService {@Autowiredprivate DataSource dataSource;@Transactionalpublic void testTransaction() {try (Connection conn = dataSource.getConnection()) {// 输出false表示自动提交已关闭,在事务中System.out.println("是否自动提交: " + conn.getAutoCommit());} catch (SQLException e) {e.printStackTrace();}// 模拟异常throw new RuntimeException("回滚测试");}
}

关键点

  • @Transactional方法中,数据库连接的autoCommitfalse,表示事务已开启。
  • 若抛出异常,数据库会自动回滚。

3. 哪些情况会导致事务失效?

  • 非 public 方法@Transactional只对 public 方法生效。
  • 同一类中直接调用:如前面示例中的wrongUpdate
  • 异常被吞掉
@Transactional
public void wrongMethod() {try {// 数据库操作throw new RuntimeException("异常");} catch (Exception e) {// 错误:异常被捕获,事务不会回滚}
}

四、总结

"同一事务" 的本质

  • 一组操作共享原子性(要么都成功,要么都失败)。
  • 共享数据库连接事务状态

实现方式

  1. @Transactional标记方法,确保多个操作在同一方法内或跨方法调用。
  2. 注意同一类中方法调用的代理问题。
  3. 避免手动捕获异常导致事务不回滚。

记住:只要看到@Transactional,就像给代码上了 "保险",要么全成功,要么全失败!




通俗易懂理解:新方法的事务选择(加入现有事务还是新建事务)



一、用生活场景比喻事务传播行为

场景假设:你和朋友一起做饭(现有事务),这时来了一个新帮手(新方法)。

  • 情况 1:新帮手加入你们的小组(加入现有事务)
    你们共用一口锅、一套工具,任何一个人犯错(比如菜炒糊了),整个做饭过程都可能取消(回滚)。
    对应代码:新方法和现有方法在同一个事务中,共享成功或失败。

  • 情况 2:新帮手自己开小灶(新建事务)
    他自己用另一口锅做饭,即使你们的菜炒糊了,他的饭依然能单独做好端出来;反之,他把饭做糊了,不影响你们的菜。
    对应代码:新方法开启独立事务,与外层事务互不影响。

二、代码示例:两种事务传播行为的对比

1. 新方法加入现有事务(默认行为:PROPAGATION_REQUIRED)

@Service
public class OrderService {@Autowiredprivate PaymentService paymentService;// 外层事务(主业务:创建订单+支付)@Transactionalpublic void createOrderWithPayment(Order order) {// 操作1:保存订单(现有事务)orderRepository.save(order);// 操作2:调用支付方法(默认加入现有事务)paymentService.pay(order.getOrderId(), order.getAmount());// 若此处抛出异常,整个事务回滚(订单和支付都失败)// throw new RuntimeException("订单创建失败");}
}@Service
public class PaymentService {// 未指定传播行为,默认PROPAGATION_REQUIRED(加入现有事务)@Transactionalpublic void pay(Long orderId, BigDecimal amount) {// 支付操作Payment payment = new Payment(orderId, amount);paymentRepository.save(payment);// 若此处抛出异常,外层事务一起回滚// throw new RuntimeException("支付失败");}
}

关键点

  • 外层createOrderWithPayment开启事务,内层pay方法默认加入这个事务。
  • 异常连锁反应:内层抛异常 → 外层事务回滚;外层抛异常 → 内层操作也回滚。

2. 新方法创建新事务(PROPAGATION_REQUIRES_NEW)

@Service
public class OrderService {@Autowiredprivate PaymentService paymentService;// 外层事务(主业务:创建订单)@Transactionalpublic void createOrder(Order order) {// 操作1:保存订单orderRepository.save(order);try {// 操作2:调用支付方法(新建独立事务)paymentService.payWithNewTransaction(order.getOrderId(), order.getAmount());} catch (Exception e) {// 支付失败不影响订单保存log.error("支付失败,但订单已创建", e);}// 外层抛出异常,不影响内层已提交的支付// throw new RuntimeException("订单创建失败");}
}@Service
public class PaymentService {// 明确指定新建事务@Transactional(propagation = Propagation.REQUIRES_NEW)public void payWithNewTransaction(Long orderId, BigDecimal amount) {// 支付操作Payment payment = new Payment(orderId, amount);paymentRepository.save(payment);// 内层抛异常,仅回滚支付操作,不影响外层订单throw new RuntimeException("支付失败(独立回滚)");}
}

关键点

  • payWithNewTransactionREQUIRES_NEW开启新事务,与外层事务隔离。
  • 异常隔离
    • 内层抛异常 → 仅回滚支付操作,订单保存成功;
    • 外层抛异常 → 订单回滚,但已提交的支付操作不回滚(因为内层事务已独立提交)。

三、常见应用场景对比

场景选择加入现有事务(REQUIRED)选择新建事务(REQUIRES_NEW)
典型案例转账(扣钱 + 加钱必须同时成功 / 失败)订单创建时记录日志(即使订单失败,日志也要保存)
核心需求操作必须整体成功或失败操作需要独立于外层逻辑
资源消耗更省资源(共用数据库连接)消耗更多资源(新建连接 + 事务)
异常处理内层异常会导致外层回滚内层异常不影响外层,外层异常不影响内层

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/news/909252.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Create my OS】从零编写一个操作系统

前言: 相信每个自学操作系统的同学,大致学习路线都离不开 HIT-OS、MIT-6.S081、MIT-6.824、MIT-6.828等经典的公开课。但学习完这些经典公开课并完成相应的Lab,很多同学脑海中对于操作系统的知识其实都是零散的,让你从头开始编写一…

计算机视觉与深度学习 | 低照度图像增强算法综述(开源链接,原理,公式,代码)

低照度图像增强算法综述 1 算法分类与原理1.1 传统方法1.2 深度学习方法2 核心算法详解2.1 多尺度Retinex (MSRCR) 实现2.2 SCI自校准光照学习2.3 自适应伽马校正2.4 WaveletMamba架构3 开源资源与实现3.1 主流算法开源库3.2 关键代码实现4 评估与实验对比4.1 客观评价指标4.2 …

【工具教程】批量PDF识别提取区域的内容重命名,将PDF指定区域位置的内容提取出来改名的具体操作步骤

在企业运营过程中,时常会面临处理海量 PDF 文件的挑战。从 PDF 指定区域提取内容并用于重命名文件,能极大地优化企业内部的文件管理流程,提升工作效率。以下为您详细介绍其在企业中的应用场景、具体使用步骤及注意事项。​ 详细使用步骤​ 选…

【Java多线程从青铜到王者】定时器的原理和实现(十一)

定时器 定时器时我们日常开发中会用到的组件工具,类似于一个"闹钟",设定一个时间,等到了时间,定时器最自动的去执行某个逻辑,比如博客的定时发布,就是使用到了定时器 Java标准库里面也提供了定时…

深入剖析AI大模型:Prompt 优化的底层逻辑

记得看到一篇NLP的文章,就 Prompt 时序效应的论文揭示了一个有趣现象,文章中说:模型对指令的解析存在 "注意力衰减" 特性 —— 就像人类阅读时会更关注段落开头,模型对 Prompt 前 20% 的内容赋予的权重高达 60%。这个发…

【备忘】PHP web项目一般部署办法

【PHP项目一般部署办法】 操作步骤 代码: 把php项目代码clone到指定位置如www/下新建php站点,填写域名,把站点根目录设置为项目根目录项目入口设置,一般为public/项目权限改为766(特殊时候可设置为777),如果有特殊要求…

【60 Pandas+Pyecharts | 箱包订单数据分析可视化】

文章目录 🏳️‍🌈 1. 导入模块🏳️‍🌈 2. Pandas数据处理2.1 读取数据2.2 数据信息2.3 去除订单金额为空的数据2.5 提取季度和星期 🏳️‍🌈 3. Pyecharts数据可视化3.1 每月订单量和订单金额分布3.2 各季…

玩转Docker | 使用Docker部署vaultwarden密码管理器

玩转Docker | 使用Docker部署vaultwarden密码管理器 前言一、vaultwarden介绍Vaultwarden 简介主要特点二、系统要求环境要求环境检查Docker版本检查检查操作系统版本三、部署vaultwarden服务下载vaultwarden镜像编辑部署文件创建容器检查容器状态检查服务端口安全设置四、配置…

晶振的多面舞台:从日常电子到高精尖科技的应用探秘

在现代科技的宏大舞台上,晶振宛如一位低调却至关重要的幕后主角,以其稳定的频率输出,为各类电子设备赋予了精准的“脉搏”。从我们日常生活中须臾不离的电子设备,到引领时代前沿的高精尖科技领域,晶振都发挥着不可替代…

uni-app 小程序 Cannot read property ‘addEventListener‘ of undefined, mounted hook

在用 uni-app 开发微信小程序时,提示 Cannot read property addEventListener of undefined, mounted hook document.addEventListener("mousemove", this.touchmove) 在小程序开发里,addEventListener 并非通用的标准 API,不过与…

《专业小词开课啦》——幂等

在系统对接过程中,当出现接口调用异常的情况时,程序员可能会用一些专业术语来答疑......对于0基础同学,自然是需要自行百度一番,学习一下! 接下来,先学习【幂等】 PS: 小白参考1.1~1.4内容即…

渗透实战PortSwigger Labs指南:自定义标签XSS和SVG XSS利用

阻止除自定义标签之外的所有标签 先输入一些标签测试&#xff0c;说是全部标签都被禁了 除了自定义的 自定义<my-tag onmouseoveralert(xss)> <my-tag idx onfocusalert(document.cookie) tabindex1> onfocus 当元素获得焦点时&#xff08;如通过点击或键盘导航&…

利用pycharm搭建模型步骤

1 如何将别人论文的代码跑起来&#xff0c;以Pycharm为例&#xff0c;在下载代码的时候&#xff0c;要注意使用的python版本是多少&#xff0c;并且要注意使用的keras和tensorflow等文件夹的版本&#xff0c;我们可以直接使用pycharm中file文件中的settings&#xff0c;来添加相…

Qt 中directoryChanged监听某个目录的内容是否发生变化

Qt 中&#xff0c;directoryChanged 是 QFileSystemWatcher 类的一个信号&#xff0c;用于监听某个目录的内容是否发生变化&#xff08;如添加、删除文件或子目录&#xff09; ✅ 一、功能说明 QFileSystemWatcher::directoryChanged(const QString &path) 信号的作用是&…

JavaWeb(Servlet预习)

案例1&#xff1a;基于jspServlet实现用户登录验证 1.input.jsp <% page language"java" contentType"text/html; charsetUTF-8"pageEncoding"UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset"UTF-8&q…

Docker Compose 部署 Prometheus + Grafana

安装 docker-compose.yml version: 3.8services:# Prometheus 监控服务prometheus:image: prom/prometheus:latestcontainer_name: prometheusrestart: unless-stoppedvolumes:- ./conf/prometheus.yml:/etc/prometheus/prometheus.yml- ./prometheus_data:/prometheuscomman…

准确--使用 ThinBackup 插件执行备份和恢复

使用 ThinBackup 插件执行备份和恢复 导出&#xff08;备份&#xff09;步骤&#xff1a; 进入 Manage Jenkins > ThinBackup。设置 Backup schedule for full backups&#xff08;可选&#xff09;&#xff0c;并配置 Files to exclude&#xff08;可选&#xff09;。点击…

Qt Creator 从入门到项目实战

Qt Creator 简介 Qt Creator 是一个跨平台的集成开发环境&#xff08;IDE&#xff09;&#xff0c;专门用于开发 Qt 应用程序。它为开发者提供了一个强大的工具集&#xff0c;包括代码编辑器、调试器、UI 设计器以及性能分析工具等。 1.1 Qt Creator 的安装 Qt Creator 支持…

公司内网远程访问配置教程:本地服务器(和指定端口应用)实现外网连接使用

在数字化时代&#xff0c;企业的办公模式日益多元化&#xff0c;远程办公、跨地区协作等需求不断增加。这使得在公司内网中配置远程访问变得至关重要&#xff0c;它能让员工无论身处何地&#xff0c;只要有网络连接&#xff0c;就能便捷地访问公司内部的各类资源&#xff0c;如…

边缘计算如何重塑能源管理?从技术原理到应用场景全解析

在全球能源数字化转型的浪潮中&#xff0c;一个看似不起眼的设备正在悄悄改变工业能效管理的模式 —— 这就是边缘计算网关。以能源领域为例&#xff0c;传统的 "设备 - 云端" 二层架构正面临数据传输延迟、网络带宽压力大、断网失效等挑战&#xff0c;而边缘计算技术…