资讯动态

Spring Data Couchbase 事务实战:用 @Transactional 在 couchbase/transactions 示例中实现跨文档 ACID 事务

发布时间:2026/10/15 3:34:35 来源:尧图企业网站定制
示例工程【免费下载链接】spring-data-examplesSpring Data Example Projects项目地址https://gitcode.com/gh_mirrors/sp/spring-data-examples点击查看免费下载本篇基于spring-data-examples仓库中的 couchbase/transactions 示例完整讲解如何在 Spring Data Couchbase 中启用并使用 Couchbase 原生的 ACID 事务从用 Docker 搭建本地 Couchbase 集群、配置TransactionsConfig到通过Transactional注解在命令式Template/Repository与响应式Reactive两种编程模型下完成“跨文档转账”操作并验证异常抛出后的自动回滚行为。读完本文你将掌握事务配置的关键参数、事务边界划分的写法以及事务失败时异常包装TransactionSystemUnambiguousException的识别方式。示例概述一个“航司闸门转移”的事务 Democouchbase/transactions是 Spring Data Couchbase 官方示例集中专门演示**事务Transactions**能力的子项目见 couchbase/pom.xml 中的moduletransactions/module声明。它的业务场景非常贴近数据库事务的经典案例——账户间转账初始化两条AirlineGates文档各持有 200 个gates在事务内将 50 个 gates 从航司 A 转移到航司 B成功提交再次在事务内转移 44 个 gates但中途抛出异常应整体回滚最后用响应式Reactive方式再转移 44 个 gates成功提交。整个流程由应用启动后自动执行并在每一步用Assert.isTrue校验文档中的gates数值是否符合预期等价于一组“自带断言”的集成测试。子项目的模块坐标定义在 couchbase/transactions/pom.xmlartifactIdspring-data-couchbase-transactions/artifactId nameSpring Data Couchbase - Transaction example/name descriptionTransactions Demo project for Spring Data Couchbase/description源码结构如下位于couchbase/transactions/src/main/java/com/example/demo/文件职责DemoApplication.javaSpring Boot 启动入口SpringBootApplicationConfig.javaCouchbase 连接与事务环境配置AirlineGates.javaDocument实体AirlineGatesRepository.javaRepository 接口AirlineGatesService.java事务边界所在的服务层CmdRunner.java启动后自动执行的演示驱动环境搭建用 Docker 运行 Couchbase按照 couchbase/transactions/README.md 的 “Running the Sample” 章节本地准备一个 Couchbase 服务需要三步第一步启动容器$ docker run -d --name db -p 8091-8097:8091-8097 -p 11210:11210 -p 11207:11207 -p 18091-18095:18091-18095 -p 18096:18096 -p 18097:18097 couchbase各端口段的含义8091-8097为 Web 管理界面与节点间通信端口11210是 XDCR 端口11207是 KV/HTTP 直连端口18091-18097是集群Cluster相关端口。由于示例连接串走的是127.0.0.1这些端口映射是跑通示例的前提。第二步创建集群浏览器访问http://localhost:8091/按向导创建一个新集群。第三步创建 bucket进入管理界面http://localhost:8091/ui/index.html#/buckets创建一个名为travel-sample的 bucket——这个名称必须与下文Config#getBucketName()的返回值一致。另外要注意一点示例 Config.java 中硬编码的用户名/密码为Administrator/password因此本地集群应使用该凭据初始化或按需修改配置类。连接与事务配置Config 类逐行解析事务示例的核心配置在 Config.javaConfiguration EnableCouchbaseRepositories({ com.example.demo }) EnableTransactionManagement public class Config extends AbstractCouchbaseConfiguration { Override public String getConnectionString() { return 127.0.0.1; } Override public String getUserName() { return Administrator; } Override public String getPassword() { return password; } Override public String getBucketName() { return travel-sample; } Override public void configureEnvironment(ClusterEnvironment.Builder builder) { builder.transactionsConfig(TransactionsConfig.durabilityLevel(DurabilityLevel.NONE)); } }这里有两个要点事务管理的开启。类上的EnableTransactionManagement是 Spring Data Couchbase 事务支持的前提——它启用Transactional注解驱动的代理机制使服务类方法中的持久化操作在 Couchbase 事务上下文中执行。TransactionsConfig与持久性级别。configureEnvironment钩子允许直接向 Couchbase Java SDK 的ClusterEnvironment.Builder传递客户端级配置。示例中通过TransactionsConfig.durabilityLevel(DurabilityLevel.NONE)将事务写入的持久性级别设为NONE即不要求事务的写入复制到任何副本节点即视为成功。这一设置能换取更高的写入吞吐适合演示场景但对数据持久性的保证弱于默认的MAJORITY多数副本持久化级别若从源码结构看生产环境的需求应把该参数调整为DurabilityLevel.MAJORITY。EnableCouchbaseRepositories({ com.example.demo })则声明了在com.example.demo包下扫描 Repository 接口。实体与 RepositoryDocument、Version 与 DynamicProxyable实体类 AirlineGates.java 展示了事务场景下典型的文档映射Document public class AirlineGates { Id String id; Version Long version; QueryIndexed String name; String iata; Long gates; public AirlineGates(String id, String name, String iata, Long gates) { this.id id; this.name name; this.iata iata; this.gates gates; } // toString() 省略 }Document将其注册为 Couchbase 文档实体Id指定文档 ID 来源字段Version映射 Couchbase 的文档版本号支撑乐观并发控制QueryIndexed表示该字段会被纳入 N1QL 查询所需的二级索引。Repository 接口见 AirlineGatesRepository.javapublic interface AirlineGatesRepository extends CouchbaseRepositoryAirlineGates, String, DynamicProxyableAirlineGatesRepository { }除了标准的CouchbaseRepositoryAirlineGates, String提供findById、save等 CRUD 能力它还继承了DynamicProxyableAirlineGatesRepository。从源码结构看该标记接口用于让 Spring Data Couchbase 为 Repository 生成可动态增强的代理使 Repository 方法也能纳入Transactional的事务边界管理——这正是示例中transferGatesRepo方法能通过 Repository 完成事务操作的基础。事务边界服务层的三种调用方式所有“转账”逻辑都集中在 AirlineGatesService.java。服务类同时注入了命令式CouchbaseTemplate、响应式ReactiveCouchbaseTemplate通过template.reactive()派生以及 RepositoryService public class AirlineGatesService { CouchbaseTemplate template; ReactiveCouchbaseTemplate reactiveTemplate; AirlineGatesRepository airlineGatesRepository; public AirlineGatesService(CouchbaseTemplate template, AirlineGatesRepository airlineGatesRepository) { this.template template; this.reactiveTemplate template.reactive(); this.airlineGatesRepository airlineGatesRepository; } // ... }方式一CouchbaseTemplate TransactionaltransferGates 方法演示了最经典的事务用法Transactional public void transferGates(String fromId, String toId, int gatesToTransfer, RuntimeException exceptionToThrow) { AirlineGates fromAirlineGates template.findById(AirlineGates.class).one(fromId); AirlineGates toAirlineGates template.findById(AirlineGates.class).one(toId); toAirlineGates.gates gatesToTransfer; fromAirlineGates.gates - gatesToTransfer; template.save(fromAirlineGates); if (exceptionToThrow ! null) { throw exceptionToThrow; } template.save(toAirlineGates); }方法上的Transactional使 Spring 的代理在执行方法体前开启 Couchbase 事务方法内的findById与save都会参与同一事务方法正常结束则提交抛出未捕获的RuntimeException则回滚。注意exceptionToThrow参数是人为注入的“故障模拟”——先保存了fromAirlineGates、后保存toAirlineGates的中间状态若中途抛异常事务保证已执行的写操作不会部分生效。方式二Repository TransactionaltransferGatesRepo 与上面的逻辑完全相同只是把template.save/findById换成了airlineGatesRepository.save/findById。它验证了事务边界不依赖于持久化访问方式——无论走 Template 还是 Repository只要在Transactional代理方法内执行就参与同一 Couchbase 事务。方式三响应式Reactive TransactionaltransferGatesReactive 展示了响应式编程模型下的事务写法// The Transactional annotation results in the method of the proxy for the service executing this in a transaction Transactional public MonoVoid transferGatesReactive(String fromId, String toId, int gatesToTransfer, RuntimeException exceptionToThrow) { return Mono.deferContextual(ctx - { AirlineGates fromAirlineGates template.findById(AirlineGates.class).one(fromId); AirlineGates toAirlineGates template.findById(AirlineGates.class).one(toId); toAirlineGates.gates gatesToTransfer; fromAirlineGates.gates - gatesToTransfer; template.save(fromAirlineGates); if (exceptionToThrow ! null) { throw exceptionToThrow; } return reactiveTemplate.save(toAirlineGates).then(); }); }源码注释点明了关键机制Transactional使代理执行该方法时就建立事务上下文方法体返回的MonoVoid内的持久化操作包括命令式template.save与响应式reactiveTemplate.save都在同一事务中进行。Mono.deferContextual用于延迟执行并在订阅时读取 Reactor 上下文——这与事务传播依赖当前线程/上下文绑定事务语义的需求相配合。非事务方法的对照服务类末尾还定义了save、findById、saveRepo、findByIdRepo四个不带Transactional的便捷方法见 AirlineGatesService.java源码逐一注释 “This does not have the Transactional annotation therefore is not executed in a transaction”。它们供CmdRunner在事务之外做数据初始化和结果读取与事务内操作形成清晰对照事务边界完全由注解声明而非由持久化操作本身决定。启动自动执行与回滚验证CmdRunner 演示脚本CmdRunner.java 是一个Component且实现CommandLineRunner的类应用启动完成后 Spring Boot 会自动调用其run方法源码注释说明“Components of the type CommandLineRunner are called right after the application start up”无需手动触发。其执行流程CmdRunner.java分为四个阶段阶段 1清理与初始化。先removeById删除 id 为1、2的旧文档忽略异常再通过非事务的service.save写入两条gates 200的AirlineGates并用非事务的service.findById回读打印初始状态。阶段 2成功的事务转账。调用transferGates(1, 2, 50, null)将 50 个 gates 从航司 1 转到航司 2。随后回读并用 Spring 的Assert.isTrue断言Assert.isTrue(found_b_1.getGates().equals(found_a_1.getGates() - gatesToTransfer), should have transferred); Assert.isTrue(found_b_2.getGates().equals(found_a_1.getGates() gatesToTransfer), should have transferred);阶段 3失败回滚的事务转账。再次调用transferGates(..., 44, new SimulateErrorException())——中途抛出内部类SimulateErrorExceptionCmdRunner.java。调用方捕获异常并做了如下甄别} catch (RuntimeException rte) { if (!(rte instanceof TransactionSystemUnambiguousException) rte ! null rte.getCause() instanceof SimulateErrorException) { throw rte; } System.err.println( got exception rte); }这里体现了 Spring Data Couchbase 事务异常处理的一个细节业务异常抛出后底层事务管理器会执行回滚并抛出org.springframework.data.couchbase.transaction.error.TransactionSystemUnambiguousException一种不可恢复的事务系统异常——捕获分支专门识别这一类型说明“事务回滚引发的系统级异常”与“业务原始异常”都会被统一处理。回滚后回读两份文档断言其gates与阶段 2 结束时完全一致Assert.isTrue(found_c_1.getGates().equals(found_b_1.getGates()), should be same as previous)从而验证了中途失败时没有任何部分写入被持久化。阶段 4响应式转账。以.block()方式触发transferGatesReactive(..., 44, null)再回读断言 44 个 gates 转移成功验证响应式路径下事务同样生效。整个脚本通过System.err打印每一阶段的分隔线与文档状态运行后即可在控制台完整观察到“提交—回滚—再提交”的全过程。测试与构建运行示例自带一个轻量集成测试 DemoApplicationTests.javaSpringBootTest(classes DemoApplication.class) EnabledOnCouchbaseAvailable class DemoApplicationTests { Test void contextLoads() {} }EnabledOnCouchbaseAvailable注解定义在示例仓库的公共工具模块 EnabledOnCouchbaseAvailable.javaexample.springdata.couchbase.util包它是一个 JUnit 5 条件注解仅当本机localhost:8091上的 Couchbase 可达时才启用测试——没有本地服务时测试会被自动跳过而不是失败。子项目 pom.xml 正是通过依赖spring-data-couchbase-example-utils获得该注解并使用spring-boot-maven-plugin打包。构建与运行方式父模块 couchbase/pom.xml 已继承spring-boot-starter-data-couchbase仓库根 pom.xml 基于 Spring Boot4.0.4、Java 25 编译# 在仓库根目录仅构建 Couchbase 示例模块及其依赖 ./mvnw -pl couchbase/transactions -am package # 或进入子目录直接运行需先完成 Docker 环境与 bucket 准备 cd couchbase/transactions mvn spring-boot:run运行后观察控制台输出应能看到 “initialized airlines”、两次transferGates的前后文档状态、失败一次的异常打印以及最终断言全部通过无AssertionError抛出。小结与参考这个示例用极小的代码面覆盖了 Spring Data Couchbase 事务支持的全部关键知识点配置面EnableTransactionManagementAbstractCouchbaseConfiguration.configureEnvironment中通过TransactionsConfig.durabilityLevel(...)调节事务持久性级别示例取DurabilityLevel.NONE生产慎用事务边界Transactional加在服务层代理方法上Template 与 Repository 两种访问方式均可参与事务响应式支持Transactional 返回Mono的方法体在代理执行时建立事务上下文回滚语义中途抛RuntimeException时整体回滚并以TransactionSystemUnambiguousException标识事务系统层面的不可恢复异常。原文档 couchbase/transactions/README.md 中列出的参考章节Maven 官方文档、Spring Boot Maven Plugin Reference Guide、OCI 镜像构建、Spring Data Couchbase 的 Spring Boot 特性说明指向的均为 Spring 官方文档站建议结合这些文档与本文所引用的源码文件进一步深入。赞分享示例工程【免费下载链接】spring-data-examplesSpring Data Example Projects项目地址https://gitcode.com/gh_mirrors/sp/spring-data-examples点击查看免费下载相关推荐Spring Data Couchbase终极指南从入门到精通文档数据库与事务处理Spring Data Couchbase终极指南从入门到精通文档数据库与事务处理 Spring Data Couchbase 是 Spring 生态系统中用示例工程Orleans 银行账户 ACID 事务示例实战从接口标注到跨 Grain 转账实现Orleans 银行账户 ACID 事务示例实战从接口标注到跨 Grain 转账实现 本篇文章基于 Orleans 官方仓库中的 samples/BankAc后端微服务从零做一只桌面宠物VPet 完整上手从零做一只桌面宠物VPet 完整上手 下班后屏幕右下角那只小家伙自己在蹲着玩鼠标凑到头上方它还会配合地让你摸饿了会讨食。这就是桌面宠物该有的样子。VPe桌面应用游戏开发上一篇dimensional-analysis 插件解析dimension-annotator 维度标注 Agent 的完整注释规范与实战指南下一篇群晖 Audio Station 歌词插件 qq_music_aumNAS 歌词同步创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑