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

Spring Boot 中如何启用 MongoDB 事务

在 Spring Boot 中启用和使用 MongoDB 事务主要依赖于以下几个方面:

  1. MongoDB 服务器和部署模式

    • MongoDB 版本 4.0 或更高版本才支持副本集 (Replica Set) 上的多文档 ACID 事务。
    • MongoDB 版本 4.2 或更高版本才支持分片集群 (Sharded Cluster) 上的多文档 ACID 事务。
    • Standalone (单节点) 模式不支持多文档事务。 MongoDB 实例必须是副本集或分片集群的一部分。
  2. Spring Boot 和 Spring Data MongoDB 版本

    • 确保使用的 Spring Boot 版本(以及它所管理的 Spring Data MongoDB 版本)支持 MongoDB 事务。较新的 Spring Boot 版本(2.1.x 及以后)都提供了良好的支持。
    • spring-boot-starter-data-mongodb 依赖是必需的。
  3. 配置 MongoTransactionManager

    • Spring Boot 会在检测到合适的条件时(例如,连接 URI 指向一个副本集)自动配置 MongoTransactionManager
  4. 使用 @Transactional 注解

    • 这是在 Spring 中管理事务的标准方式。

下面是如何在 Spring Boot 中启用和使用 MongoDB 事务:

步骤 1: 确保 MongoDB 环境支持事务

  • 确认MongoDB 服务器版本(4.0+ for replica sets, 4.2+ for sharded clusters)。
  • 确认MongoDB 是以副本集或分片集群模式运行。

步骤 2: 添加依赖

在你的 pom.xml (Maven) 或 build.gradle (Gradle) 文件中,确保有 Spring Data MongoDB 的 starter:

Maven (pom.xml):

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

步骤 3: 配置数据库连接 URI

application.propertiesapplication.yml 中配置 MongoDB 连接 URI。关键是要包含 replicaSet 参数(如果你的 MongoDB 是副本集)。

application.properties:

spring.data.mongodb.uri=mongodb://localhost:27017,localhost:27018,localhost:27019/mydatabase?replicaSet=rs0
# 或者对于分片集群,连接到 mongos 实例
# spring.data.mongodb.uri=mongodb://mongos1:27017,mongos2:27017/mydatabase
  • localhost:27017,localhost:27018,localhost:27019:副本集成员地址。
  • mydatabase:数据库名称。
  • replicaSet=rs0:副本集名称。这个参数对于 Spring Boot 自动配置 MongoTransactionManager 非常重要。

步骤 4: 启用事务管理器

a) 自动配置 (推荐)

如果 spring.data.mongodb.uri 正确配置了 replicaSet 参数(或者连接的是分片集群的 mongos),Spring Boot 通常会自动为你配置一个 MongoTransactionManager bean。不需要额外做配置。

b) 手动配置 (如果自动配置不生效或需要自定义)

如果需要手动配置,可以在配置类中创建一个 MongoTransactionManager bean:

import com.mongodb.client.MongoClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.MongoTransactionManager;
import org.springframework.data.mongodb.core.MongoTemplate; // 仅为示例,非必需@Configuration
public class MongoConfig {// Spring Boot 会自动配置 MongoDatabaseFactory// 你只需要注入它来创建 MongoTransactionManager@BeanMongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) {return new MongoTransactionManager(dbFactory);}// 可选: 如果你也想配置一个 MongoTemplate bean// @Bean// public MongoTemplate mongoTemplate(MongoDatabaseFactory dbFactory, MongoClient mongoClient) {//     return new MongoTemplate(mongoClient, dbFactory.getMongoDatabase().getName());// }
}

注意: 通常情况下,如果你的 URI 配置正确,Spring Boot 的自动配置就足够了,你不需要手动创建这个 bean。

步骤 5: 在 Service 层使用 @Transactional

现在可以在Service 方法上使用 Spring 的 @Transactional 注解来声明事务。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; // Spring的事务注解
import com.mongodb.MongoException; // 更通用的MongoDB异常
import com.mongodb.MongoTransactionException; // 事务特定异常@Service
public class AccountService {@Autowiredprivate AccountRepository accountRepository; // 假设你有一个 AccountRepository@Autowiredprivate AuditLogRepository auditLogRepository; // 假设你有一个 AuditLogRepository/*** 示例:在一个事务中转账并记录日志* 如果任何一步失败,整个操作将回滚*/@Transactional // 关键注解public void transferMoney(String fromAccountId, String toAccountId, double amount) {try {Account fromAccount = accountRepository.findById(fromAccountId).orElseThrow(() -> new RuntimeException("Source account not found"));Account toAccount = accountRepository.findById(toAccountId).orElseThrow(() -> new RuntimeException("Destination account not found"));if (fromAccount.getBalance() < amount) {throw new RuntimeException("Insufficient funds");}fromAccount.setBalance(fromAccount.getBalance() - amount);toAccount.setBalance(toAccount.getBalance() + amount);accountRepository.save(fromAccount);// 模拟一个可能发生的错误,以测试回滚// if (true) {//     throw new RuntimeException("Simulated error during transfer!");// }accountRepository.save(toAccount);// 记录审计日志AuditLog log = new AuditLog("Transfer of " + amount + " from " + fromAccountId + " to " + toAccountId);auditLogRepository.save(log);System.out.println("Transfer successful and logged!");} catch (RuntimeException e) {// @Transactional 会在 RuntimeException 抛出时自动回滚// 你可以在这里记录错误,但不需要手动回滚System.err.println("Transfer failed: " + e.getMessage());throw e; // 重新抛出,以便 Spring 能够捕获并回滚事务}}/*** 示例:只读事务* 对于只需要读取数据的操作,可以标记为只读,这可能有一些性能优化。*/@Transactional(readOnly = true)public Account getAccountDetails(String accountId) {return accountRepository.findById(accountId).orElse(null);}// 假设的实体和仓库// Account.java, AuditLog.java// AccountRepository.java extends MongoRepository<Account, String>// AuditLogRepository.java extends MongoRepository<AuditLog, String>
}

工作原理:

  1. 当调用被 @Transactional 注解的方法时,Spring 会启动一个 MongoDB 事务(通过 MongoTransactionManager)。
  2. 方法内的所有数据库操作(例如 repository.save())都会在这个事务的上下文中执行。
  3. 如果方法成功完成(没有抛出未被捕获的 RuntimeExceptionError),事务将被提交。
  4. 如果方法抛出任何未被捕获的 RuntimeExceptionError(默认行为),事务将被回滚。受检异常(Checked Exceptions)默认不会触发回滚,除非通过 @Transactional(rollbackFor = SpecificCheckedException.class) 特别指定。

步骤 6: 异常处理和重试 (重要!)

MongoDB 事务可能会因为写冲突 (write conflicts) 而中止。当两个并发事务尝试修改同一个文档时,就会发生这种情况。MongoDB 会中止其中一个事务,并抛出 MongoTransactionException (通常是其子类,如 MongoWriteConflictException)。

在应用程序中必须捕获这些异常并重试整个事务。

// 在调用方或者一个更高层次的Service
@Service
public class TransactionalCallerService {@Autowiredprivate AccountService accountService;private static final int MAX_RETRIES = 3;public void performTransferWithRetry(String from, String to, double amount) {int retries = 0;boolean success = false;while (retries < MAX_RETRIES && !success) {try {accountService.transferMoney(from, to, amount);success = true; // 如果没有异常,标记成功} catch (MongoTransactionException e) { // 捕获事务相关的异常,特别是写冲突retries++;System.err.println("Transaction failed due to conflict, retrying (" + retries + "/" + MAX_RETRIES + "): " + e.getMessage());if (retries >= MAX_RETRIES) {System.err.println("Max retries reached. Transfer aborted.");throw e; // 或者抛出一个自定义的业务异常}// 可以考虑在此处添加短暂的延迟try {Thread.sleep(100 * retries); // 简单的指数退避} catch (InterruptedException ie) {Thread.currentThread().interrupt();throw new RuntimeException("Retry interrupted", ie);}} catch (RuntimeException e) { // 捕获其他业务逻辑异常System.err.println("Transfer failed due to business error: " + e.getMessage());throw e; // 这些通常不需要重试}}}
}

总结关键点:

  • MongoDB 环境: 确保是副本集 (4.0+) 或分片集群 (4.2+)。
  • URI 配置: 在 spring.data.mongodb.uri 中正确设置 replicaSet 参数。
  • MongoTransactionManager: 通常由 Spring Boot 自动配置。
  • @Transactional: 在 Service 方法上使用。
  • 异常处理: 必须处理 MongoTransactionException 并实现重试逻辑以应对写冲突。
  • 事务范围: 尽量保持事务简短,避免长时间运行的事务。
  • 非事务操作: 某些 MongoDB 操作(如创建集合、创建索引)不在事务内执行或在事务内有特定行为。

通过这些步骤,我们就可以在 Spring Boot 应用程序中有效的使用 MongoDB 的多文档 ACID 事务了。

http://www.xdnf.cn/news/372385.html

相关文章:

  • 教育系统源码如何支持白板直播与刷题功能?功能开发与优化探索
  • 如何通过ABAP获取SAP生产订单的目标成本
  • 《AI大模型应知应会100篇》第53篇:Hugging Face生态系统入门
  • 【Web前端开发】HTML基础
  • Go语言——goflow工作流使用
  • WPF之集合绑定深入
  • 计算机网络:什么是Mesh组网以及都有哪些设备支持Mesh组网?
  • drf 使用jwt
  • cv_connection (像halcon一样对区域进行打散)
  • .Net Mqtt协议-MQTTNet(一)简介
  • 养生:为健康生活筑牢根基
  • 路由重发布
  • 软件测试——用例篇(3)
  • 嵌入式与物联网:C 语言在边缘计算时代的破局之道
  • OSPF不规则区域划分
  • Win10无法上网:Windows 无法访问指定设备、路径或文件。你可能没有适当的权限访问该项目找不到域 TEST 的域控制器DNS 解析存在问题
  • 大节点是选择自建机房还是托管机房
  • 数据结构与算法分析实验12 实现二叉查找树
  • 深入理解 TCP:重传机制、滑动窗口、流量控制与拥塞控制
  • 考研408《计算机组成原理》复习笔记,第三章数值数据的表示和运算(定点数篇)
  • Ping 不通外网,Ping 得通主机问题解决小记
  • BUUCTF——Cookie is so stable
  • 《C++探幽:模板从初阶到进阶》
  • Docker Desktop安装在其他盘
  • [面试]SoC验证工程师面试常见问题(七)低速接口篇
  • rust-candle学习笔记13-实现多头注意力
  • Skyvern:用 AI+视觉驱动浏览器自动化
  • FreeTex v0.2.0:功能升级/支持Mac
  • Ubuntu 22.04(WSL2)使用 Docker 安装 Zipkin 和 Skywalking
  • 【含文档+PPT+源码】基于微信小程序的社区便民防诈宣传系统设计与实现