robincakeellis/sqlrx: 使用Spring Boot的反应式MySQL


这是一个使用 Spring Boot 和 R2DBC 从 MySQL 8 数据库响应式返回数据的工作 Java 项目。可配置应用程序属性和构建时测试。

Github 项目中有一个包含测试数据的简单 SQL 脚本。最后是完整静态数据的各种风格的链接。
技术栈

  • Java:我正在使用 OpenJDK 17。我还使用 OpenJDK 8 构建和运行,一切都很好。
  • Spring Boot:我使用的是 2.7.x。我还用 2.5.0 构建和运行,没问题。
  • MySQL 8:我将它与 MySQL Workbench 一起安装。我没有使用 MySQL 5 进行测试,也没有使用 Docker 容器。
  • R2DBC:SQL 数据库的反应式网关。它包含在 Spring Boot 中。
  • Junit:我使用的是 v5,但 v4 应该可以正常工作,可能带有温和的注释编辑。
  • Apache Maven:我用 Maven 组装。我没有用 Gradle 进行测试。

POM文件说明:
对程序集 POM 的回顾:其中有什么,没有什么。该列表适用于 Gradle。

  • Java 版本在此处指定。请记住将其从 17 编辑为适合您的任何版本。同样,将 Spring Boot 版本指定为父版本。
  • spring-boot-starter-webflux将给出 Mono、Flux 和通常的控制器注释。
  • spring-boot-starter-data-r2dbc将在对数据库的响应式调用前面。
  • dev.miku:r2dbc-mysql是位于 R2DBC 和 MySQL 之间的驱动程序。MySQL 的另一个选择是com.github.jasync-sql:jasync-r2dbc-msqyl,对于 H2 等其他数据库,您可以查看最后的链接。
  • spring-boot-starter-test用于检测。
  • io.projectreactor:reactor:test用于测试反应结果。

我的可选依赖项是 Lombok 和 Spring Boot Devtools,它们不是必需的,但我认为它们很好。

POM中没有的内容:

  • spring-boot-starter-web: 不必要。Webflux 提供了所需的东西。
  • 任何与 JPA 或 JDBC 相关的内容:不。我不会启用 JPA 存储库,也不需要 JDBC。
  • mysql-connector-java: 不。这对于反应性工作是不需要的。它甚至不是传递依赖。松手。
  • 任何东西 HikariCP:没有。R2DBC 有自己的连接池。
  • 任何与 Tomcat、Jetty 或 Undertow 相关的东西:不。Netty 支持响应式工作。可能可以使用替代方法,但这超出了本文的范围。

基本项目将具有 POM、带有空 application.properties(或 .yaml)的典型文件夹结构和两个类:

  • 主源代码中的第一个类是基本的 Spring Boot 应用程序启动器。它将有一个静态的 main 方法,以及@SpringBootApplication类上的单个注释
  • 测试源中的第二个类是应用程序启动测试。它将有一个方法 - 可能称为 contextLoads() - 带有注释,@Test并且类本身也带有注释@SpringBootTest

数据访问 - Bean
Hibernate强大的用户可能已经经历过表名被重命名以适应平台的情况。例如,使用MySQL时,agtAgents会被Hibernate重命名为agt_types。这种情况在R2DBC中不会发生--你在@Table中指定的内容就是你在查询中得到的内容。

尽管如此(!),如果你没有指定名称,那么就会应用一个策略。例如,Bean被称为AgentRow,所以--如果没有特定的表名--R2DBC会尝试使用agent_row作为表名。

Bean中的属性将被映射到表中的匹配列。这种情况对你的SQL提供者来说可能很重要,也可能不重要,所以你可以指定一个确切的列名来使用每个属性。例如,我有一个名为agentTypeId的bean属性,它被映射到列agentTypeID,我有一个名为locator的bean布尔值,它被映射到列isLocator。

主键是使用@Id来记录的。我没有使用R2DBC的复合主键的经验。

数据访问 - 属性
我需要指定我的数据所在的位置,所以是时候更新application.properties(或.yaml)了。关键属性是spring.r2dbc.url和spring.r2dbc.username和spring.r2dbc.password。

标准的spring.datasource.*属性与R2DBC无关。使用spring.r2dbc.* - 全套已知的属性可能会出现在你的IDE的自动完成中,或者你可以在大量的Spring Boot属性页面上找到它们(在最后链接)。

一个重要的变化是,你必须从jdbc: 前缀的URLs转移到r2dbc。

池化是默认启用的,但我相信你需要在连接URL中指定:pool,以实际使用连接池。我愿意接受纠正!

一个R2DBC的URL:spring.r2dbc.url=r2dbc:pool:mysql://localhost:3306/SQL_RX_TEST?zeroDateTimeBehavior=convertToNull&useSSL=false&useServerPrepareStatement=true

请记住,Hibernate相关的属性或HikariCP配置不需要在你的属性文件上。

服务
我需要一个服务来隐藏所有未使用的 repo 功能。服务方法可以进行一些转换或装饰,但对于这个项目,服务是一个代理。任何来自repo的Flux或Mono都会按原样返回给服务调用者。

我可以为服务写测试,但由于它是一个代理,我跳过了这些测试。令人震惊。

控制器
控制器端点有典型的映射相关注解。控制器调用服务,并以服务返回的任何内容回应调用者--一直是反应式的。

@GetMapping("/corp/{id}")
public Flux<AgentRow> getForCorp(@PathVariable int id) {
    return service.getForCorp(id);
}

如果你用@Controller来注解你的控制器,可能会出现一个问题:调用一个反应式端点可能会导致一个异常,如视图解析中不支持多值反应式类型。这个问题可以通过在方法上标注@ResponseBody来解决。或者,用@RestController来注解控制器。

我想测试一下这个控制器。非反应式的方法是用@WebMvcTest来注解测试类,在MockMvc实例中布线,模拟底层实例(如服务),调用控制器,用jsonPath()检查响应,包括内容。

我们不知道数据何时会到达ReactiveLand,所以我们遵循不同的路线。

我使用@WebFluxText(而不是WebMvcTest)并指定我正在测试的控制器类。我连接了一个WebTestClient(而不是MockMvc)。我仍然需要向模拟的服务提供数据,以便端点能够找到一些东西。我可以使用Web测试客户端调用端点并测试响应。

我学到的一个很酷的东西是,你可以检查响应的状态和头信息,然后将主体传递给StepVerifier,以进行彻底的反应式测试。

final ResponseSpec response = webTestClient
   .get().uri("http://localhost:8080/agents/ids")
   .exchange();
final Flux<Integer> flux = response
   .expectStatus().isOk()
   .returnResult(Integer.class)
   .getResponseBody();
StepVerifier.create(flux.collectList())
   .expectNextMatches(list -> list.size() == 2)
   .verifyComplete();