Overview
Spring Boot 是一个开源Java框架,可简化生产就绪应用程序的开发。当与Spring Data MongoDB结合使用时,该框架提供了一种与MongoDB数据库交互的有效方法。
本教程演示如何构建一个响应式 Spring Boot应用程序,该应用程序使用 Spring Data MongoDB对MongoDB 数据库执行操作。您可以使用此示例现金余额应用程序调用执行以下操作的 REST API:
创建或获取帐户
在一个账户或两个账户之间执行事务
Spring Data MongoDB
Spring Data MongoDB为MongoDB提供存储库支持,提供两种与数据库交互的主节点 (primary node in the replica set)方式:
ReactiveMongoRepository:更高级别的抽象,提供对常见数据库操作的访问权限
ReactiveMongoTemplate:较低级别的抽象,可更好地控制MongoDB操作和查询构造
多文档 ACID 事务
MongoDB支持多文档ACID事务,允许您执行多个满足原子性、一致性、隔离性和持久性ACID 一致性保证的操作。Spring Data MongoDB通过 @Transactional 注解或 TransactionalOperator 类提供对事务的内置支持。本教程中的示例应用程序配置ACID 事务支持以确保数据库操作的原子性。
要学习;了解有关MongoDB事务的更多信息,请参阅MongoDB Server手册中的事务。
Tutorial
本教程介绍如何下载实施简化现金余额系统的示例应用程序。该应用程序允许您创建、检索和更新银行账户余额。要查看完整的应用程序,请参阅 mdb-spring-boot-reactive GitHub存储库。
完成本教程,学习;了解如何执行以下操作:
下载示例Spring Boot应用程序
配置应用程序以连接到MongoDB
了解应用程序结构和关键组件
运行应用程序。
发送 POST 请求以创建银行帐户并执行事务
下载并配置连接
验证先决条件
在开始本教程之前,请确保您已准备好以下组件:
已配置集群的MongoDB Atlas帐户。要学习;了解如何创建集群,请参阅MongoDB入门指南。
Java21 或更高版本。
Maven.3 5或更高版本。
了解应用程序结构
查看帐户管理终结点
AccountController.java文件包含用于创建和获取帐户的REST API终结点。该文件定义了一个将文档插入 txn-demo.accounts集合的POST 方法终结点和一个根据其 accountNum 值查找文档的GET 方法终结点。以下代码展示了这些方法:
public class AccountController { //... public Mono<Account> createAccount( Account account) { return accountRepository.save(account); } public Mono<Account> getAccount( String accountNum) { return accountRepository.findByAccountNum(accountNum) .switchIfEmpty(Mono.error(new AccountNotFoundException())); } //... }
每个终结点从 AccountRepository 返回一个 Mono<Account> 类型, 是一个 ReactiveMongoRepository 接口,根本的Java Reactive Streams驱动程序的抽象。
查看余额事务终结点
AccountController 类还包括以下事务终结点:
添加帐户余额的
.../debit终结点从帐户余额中减去的
.../credit终结点执行从一个帐户到另一个帐户的转账的
.../transfer终结点
这些终结点具有以下定义:
public class AccountController { //... public Mono<Txn> debitAccount( String accountNum, Map<String, Object> requestBody) { //... txn.addEntry(new TxnEntry(accountNum, amount)); return txnService.saveTransaction(txn).flatMap(txnService::executeTxn); } public Mono<Txn> creditAccount( String accountNum, Map<String, Object> requestBody) { //... txn.addEntry(new TxnEntry(accountNum, -amount)); return txnService.saveTransaction(txn).flatMap(txnService::executeTxn); } public Mono<Txn> transfer( String from, TransferRequest transferRequest) { //... txn.addEntry(new TxnEntry(from, -amount)); txn.addEntry(new TxnEntry(to, amount)); return txnService.saveTransaction(txn).flatMap(txnService::executeTxn); } //... }
一个 TxnEntry对象代表对单个帐户的更改,而 Txn 可以由一个或多个 TxnEntry 对象组成。在示例代码中,借方和信用终结点创建一个新的 TxnEntry对象,转账终结点创建两个 TxnEntry 对象。
查看自定义查询方法
AccountRepository 接口扩展了 ReactiveMongoRepository 并定义了以下查询方法:
public interface AccountRepository extends ReactiveMongoRepository<Account, String> { Mono<Account> findByAccountNum(String accountNum); Mono<Long> findAndIncrementBalanceByAccountNum(String accountNum, double increment); }
这些代码使用以下注解,允许您在查询MongoDB时使用占位符,并动态替换为方法参数:
@Query:注释findByAccountNum()方法并指定查询条件以查找文档。?0占位符将替换为accountNum参数值。@Update:注解findAndIncrementBalanceByAccountNum()方法并指定要对匹配文档执行的更新操作。将?1占位符替换为increment参数值,以使用 MongoDB 的$inc操作符增加余额。
注意
Although the POST endpoint described in a previous step uses the accountRepository.save() method, you do not need to define this method. The save() method and many other base methods are already declared by interfaces in the inheritance chain of ReactiveMongoRepository.
了解ACID 事务服务
TxnService 类处理ACID 事务执行。该类包括以下代码:
public class TxnService { //... public Mono<Txn> saveTransaction(Txn txn) { return txnTemplate.save(txn); } public Mono<Txn> executeTxn(Txn txn) { return updateBalances(txn) .onErrorResume(DataIntegrityViolationException.class /*lambda expression to handle error*/) .onErrorResume(AccountNotFoundException.class /*lambda expression to handle error*/) .then(txnTemplate.findAndUpdateStatusById(txn.getId(), TxnStatus.SUCCESS)); } public Flux<Long> updateBalances(Txn txn) { Flux<Long> updatedCounts = Flux.fromIterable(txn.getEntries()).concatMap( entry -> accountRepository.findAndIncrementBalanceByAccountNum( entry.getAccountNum(), entry.getAmount()) ); return updatedCounts.handle(/*...*/); } }
TxnService 类包括以下方法:
saveTransaction():将Txn文档保存到transactions集合中executeTxn():调用updateBalances()方法,然后更新Txn文档中的ACID 事务状态updateBalances():遍历每个TxnEntry并对每个account文档进行相应更新
查看ACID 事务模板
上一步中描述的 saveTransaction() 和 executeTxn() 方法均使用 TxnTemplate 类中定义的方法。这些方法具有以下定义:
public class TxnTemplate { //... public Mono<Txn> save(Txn txn) { return template.save(txn); } public Mono<Txn> findAndUpdateStatusById(String id, TxnStatus status) { Query query = query(where("_id").is(id)); Update update = update("status", status); FindAndModifyOptions options = FindAndModifyOptions.options().returnNew(true); return template.findAndModify(query, update, options, Txn.class); } //... }
这些方法使用 ReactiveMongoTemplate 与MongoDB交互。
配置多文档事务
在帐户之间转账时,可以使用多文档事务。跨两个帐户的更新必须是原子性的,事务可确保数据原子性。
为了访问权限Spring 的ACID 事务支持,示例应用程序将 ReactiveMongoTransactionManager bean 添加到 ReactiveMongoConfig.java文件:
public class ReactiveMongoConfig extends AbstractReactiveMongoConfiguration { //... ReactiveMongoTransactionManager transactionManager(ReactiveMongoDatabaseFactory dbFactory) { return new ReactiveMongoTransactionManager(dbFactory); } }
您可以使用 TransactionalOperator对象或 @Transactional 注解来定义ACID 事务范围。TxnService 类显示了这两种方法。
测试应用程序
在账户之间转账
要将资金从一个帐户转账到另一个帐户,请创建一个帐号为 67890 的新帐户:
curl --location 'localhost:8080/account' \ --header 'Content-Type: application/json' \ --data '{ "accountNum": "67890" }'
然后,通过运行以下命令,将 500 美元转入此新帐户:
curl --location 'localhost:8080/account/12345/transfer' \ --header 'Content-Type: application/json' \ --data '{ "to": "67890", "amount": 500 }'
此命令向 .../transfer 终结点发送请求以完成余额转移。
其他资源
要学习;了解有关 Spring Data MongoDB 的更多信息,请参阅 Spring Data MongoDB参考文档。
要查看完整的示例应用程序,请参阅mdb-spring-boot-reactive GitHub存储库。