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

“在同一事务中“ 的含义

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

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

  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.xdnf.cn/news/13987.html

相关文章:

  • 【工具教程】批量PDF识别提取区域的内容重命名,将PDF指定区域位置的内容提取出来改名的具体操作步骤
  • 蘑菇街商品详情接口技术解析
  • Tlias-web 管理系统项目知识点复盘总结
  • 东土科技参与国家重点研发计划 ,共同研发工业智控创新技术
  • Vue里面的映射方法
  • 弹性梁:绘图、分析与可视化-AI云计算数值分析和代码验证
  • linux命令-用户与用户组
  • 什么是redis
  • 【k8s】阿里云ACK服务中GPU实例部署问题
  • QMainWindow、QDialog 和 QWidget区别
  • ubuntu 无法访问位置 error mounting 解决办法 双系统
  • 腐烂之息-(Breath of Decay VR ) 硬核VR游戏
  • OpenBayes 一周速览丨对标GPT-4o! BAGEL统一处理多模态数据理解和生成任务; 专为软件工程任务设计, Devstral自主处理复杂工程问题
  • 印度客机坠毁致波音美股盘前直线下跌​
  • Linux内核网络协议注册与初始化:从proto_register到tcp_v4_init_sock的深度解析
  • 后端开发:计算机网络、数据库常识
  • 戴尔 17G 服务器 E610 OCP千兆网卡驱动安装
  • 【 新能源汽车OBD网关全解析:原理、方案、测试与趋势】
  • 【车机显示仪表】软硬件详细方案
  • docker compose部署kafka
  • Snap宣布2026年推出AR眼镜
  • 【Erdas实验教程】019:遥感图像空间增强( 纹理分析)
  • 开源组件hive调优
  • Android 12.0 第三方应用左右两侧未全屏有黑边问题解决
  • 手机IP地址更换的影响与方法
  • 分享| 低代码建模工具-大数据挖掘建模平台白皮书
  • 中国老年健康调查(CLHLS)数据挖掘教程(1)--CLHLS简介和数据下载
  • C++11可变参数模板从入门到精通
  • 【报错解决】Java 连接https报错「javax.net.ssl.SSLHandshakeException」怎么破?看这篇!
  • Kubernetes安全机制深度解析(一):从身份认证到资源鉴权