☰
Spring Data Couchbase 事务实战:用 @Transactional 在 couchbase/transactions 示例中实现跨文档 ACID 事务
2026/10/15 3:34:33 网站建设 项目流程
  • 示例工程

【免费下载链接】spring-data-examples

Spring 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)的识别方式。

示例概述:一个“航司闸门转移”的事务 Demo

couchbase/transactions是 Spring Data Couchbase 官方示例集中专门演示**事务(Transactions)**能力的子项目(见 couchbase/pom.xml 中的<module>transactions</module>声明)。它的业务场景非常贴近数据库事务的经典案例——账户间转账:

  • 初始化两条AirlineGates文档,各持有 200 个gates;
  • 在事务内将 50 个 gates 从航司 A 转移到航司 B(成功提交);
  • 再次在事务内转移 44 个 gates,但中途抛出异常(应整体回滚);
  • 最后用响应式(Reactive)方式再转移 44 个 gates(成功提交)。

整个流程由应用启动后自动执行,并在每一步用Assert.isTrue校验文档中的gates数值是否符合预期,等价于一组“自带断言”的集成测试。子项目的模块坐标定义在 couchbase/transactions/pom.xml:

<artifactId>spring-data-couchbase-transactions</artifactId> <name>Spring Data Couchbase - Transaction example</name> <description>Transactions Demo project for Spring Data Couchbase</description>

源码结构如下(位于couchbase/transactions/src/main/java/com/example/demo/):

文件职责
DemoApplication.javaSpring Boot 启动入口(@SpringBootApplication)
Config.javaCouchbase 连接与事务环境配置
AirlineGates.java@Document实体
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.java:

@Configuration @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)); } }

这里有两个要点:

  1. 事务管理的开启。类上的@EnableTransactionManagement是 Spring Data Couchbase 事务支持的前提——它启用@Transactional注解驱动的代理机制,使服务类方法中的持久化操作在 Couchbase 事务上下文中执行。
  2. TransactionsConfig与持久性级别。configureEnvironment钩子允许直接向 Couchbase Java SDK 的ClusterEnvironment.Builder传递客户端级配置。示例中通过TransactionsConfig.durabilityLevel(DurabilityLevel.NONE)将事务写入的持久性级别设为NONE,即不要求事务的写入复制到任何副本节点即视为成功。这一设置能换取更高的写入吞吐(适合演示场景),但对数据持久性的保证弱于默认的MAJORITY(多数副本持久化)级别;若从源码结构看生产环境的需求,应把该参数调整为DurabilityLevel.MAJORITY。

@EnableCouchbaseRepositories({ "com.example.demo" })则声明了在com.example.demo包下扫描 Repository 接口。

实体与 Repository:@Document、@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.java:

public interface AirlineGatesRepository extends CouchbaseRepository<AirlineGates, String>, DynamicProxyable<AirlineGatesRepository> { }

除了标准的CouchbaseRepository<AirlineGates, String>(提供findById、save等 CRUD 能力),它还继承了DynamicProxyable<AirlineGatesRepository>。从源码结构看,该标记接口用于让 Spring Data Couchbase 为 Repository 生成可动态增强的代理,使 Repository 方法也能纳入@Transactional的事务边界管理——这正是示例中transferGatesRepo方法能通过 Repository 完成事务操作的基础。

事务边界:服务层的三种调用方式

所有“转账”逻辑都集中在 AirlineGatesService.java。服务类同时注入了命令式CouchbaseTemplate、响应式ReactiveCouchbaseTemplate(通过template.reactive()派生)以及 Repository:

@Service 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 + @Transactional

transferGates 方法演示了最经典的事务用法:

@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 + @Transactional

transferGatesRepo 与上面的逻辑完全相同,只是把template.save/findById换成了airlineGatesRepository.save/findById。它验证了:事务边界不依赖于持久化访问方式——无论走 Template 还是 Repository,只要在@Transactional代理方法内执行,就参与同一 Couchbase 事务。

方式三:响应式(Reactive)+ @Transactional

transferGatesReactive 展示了响应式编程模型下的事务写法:

// The @Transactional annotation results in the method of the proxy for the service executing this in a transaction @Transactional public Mono<Void> 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使代理执行该方法时就建立事务上下文,方法体返回的Mono<Void>内的持久化操作(包括命令式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())——中途抛出内部类SimulateErrorException(CmdRunner.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.java:

@SpringBootTest(classes = DemoApplication.class) @EnabledOnCouchbaseAvailable class DemoApplicationTests { @Test void contextLoads() {} }

@EnabledOnCouchbaseAvailable注解定义在示例仓库的公共工具模块 EnabledOnCouchbaseAvailable.java(example.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 事务支持的全部关键知识点:

  • 配置面:@EnableTransactionManagement+AbstractCouchbaseConfiguration.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-examples

Spring Data Example Projects

项目地址:https://gitcode.com/gh_mirrors/sp/spring-data-examples
点击查看免费下载
上一篇:dimensional-analysis 插件解析:dimension-annotator 维度标注 Agent 的完整注释规范与实战指南
下一篇:群晖 Audio Station 歌词插件 qq_music_aum:NAS 歌词同步

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询