1. 概述

在大规模高性能系统中构建**反应式应用已成为Java开发的核心需求。Hibernate Reactive和Quarkus是两大强力工具,能帮助开发者高效构建反应式应用。Hibernate Reactive是Hibernate ORM的反应式扩展,专为无缝对接非阻塞数据库驱动而设计**。

另一方面,Quarkus是Kubernetes原生Java框架,针对GraalVM和OpenJDK HotSpot优化,专门用于构建反应式应用。两者结合为构建高性能、可扩展的反应式Java应用提供了强大平台。

本教程将通过从零构建一个反应式银行存款系统,深入探讨Hibernate Reactive和Quarkus。同时,我们将集成测试以确保应用的正确性和可靠性。

2. Quarkus中的反应式编程

Quarkus作为知名反应式框架,从诞生之初就将反应性作为架构核心要素。该框架集成了丰富的反应式特性,并拥有强大的生态系统支撑。

值得注意的是,Quarkus通过Mutiny提供的UniMulti类型实现反应式概念,充分体现了对异步和事件驱动编程范式的坚定承诺。

3. Quarkus中的Mutiny

Mutiny是Quarkus中处理反应式特性的核心API。大多数扩展通过返回UniMulti的API支持Mutiny,它们通过非阻塞背压处理异步数据流。

我们的应用通过Quarkus提供的UniMulti类型利用反应式概念:

  • Multi:表示可异步发射多个项目的类型,类似java.util.stream.Stream但具备背压处理能力。当处理潜在无界数据流时使用Multi,例如实时流式传输多笔银行存款
  • Uni:表示最多发射一个项目或错误的类型,类似java.util.concurrent.CompletableFuture但拥有更强大的组合操作符。Uni用于预期单个结果或错误的场景,例如从数据库查询单笔银行存款

4. 理解PanacheEntity

当在Quarkus中使用Hibernate Reactive时,**PanacheEntity类提供了一种定义JPA实体精简方案,极大减少样板代码*。通过继承Hibernate的PanacheEntityBasePanacheEntity*获得反应式能力,使实体能以非阻塞方式管理。

这允许高效处理数据库操作而不阻塞应用执行,从而提升整体性能。

5. Maven依赖

首先在pom.xml中添加*quarkus-hibernate-reactive-panache*依赖:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-hibernate-reactive-panache</artifactId>
    <version>3.11.0</version>
</dependency>

配置好依赖后,我们就可以在示例实现中使用它了。

6. 真实世界示例代码

银行系统通常面临高并发需求。我们可以利用Quarkus、Hibernate和反应式编程等技术实现关键服务。

本示例重点实现两个核心服务:创建银行存款和流式传输所有存款记录。

6.1 创建银行存款实体

ORM(对象关系映射)实体是所有CRUD系统的基础。该实体实现数据库对象到软件对象模型的映射,便于数据操作。正确定义Deposit实体对系统稳定运行和数据管理至关重要:

@Entity
public class Deposit extends PanacheEntity {
    public String depositCod;
    public String currency;
    public String amount;  
    // 标准setter和getter方法
}

本例中,Deposit类继承PanacheEntity类,使其成为由Hibernate Reactive管理的反应式实体。

通过继承,Deposit类自动获得CRUD(创建、读取、更新、删除)操作方法和查询能力,显著减少手动编写SQL或JPQL的需求。这种方式简化了数据库操作管理,提升了系统整体效率。

6.2 实现Repository

虽然通常可直接使用存款实体进行CRUD操作,但本例中我们创建了专门的DepositRepository

@ApplicationScoped
public class DepositRepository {

    @Inject
    Mutiny.SessionFactory sessionFactory;

    @Inject
    JDBCPool client;

    public Uni<Deposit> save(Deposit deposit) {
        return sessionFactory.withTransaction((session, transaction) -> session.persist(deposit)
          .replaceWith(deposit));
    }

    public Multi<Deposit> streamAll() {
        return client.query("SELECT depositCode, currency,amount FROM Deposit ")
          .execute()
          .onItem()
          .transformToMulti(set -> Multi.createFrom()
            .iterable(set))
          .onItem()
          .transform(Deposit::from);
    }
}

该Repository实现了自定义*save()方法和streamAll()方法,允许我们以Multi*格式检索所有存款记录。

6.3 实现REST接口

现在通过REST接口暴露我们的反应式方法:

@Path("/deposits")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class DepositResource {

    @Inject
    DepositRepository repository;

    @GET
    public Uni<Response> getAllDeposits() {
        return Deposit.listAll()
          .map(deposits -> Response.ok(deposits)
            .build());
    }

    @POST
    public Uni<Response> createDeposit(Deposit deposit) {
        return deposit.persistAndFlush()
          .map(v -> Response.status(Response.Status.CREATED)
            .build());
    }

    @GET
    @Path("stream")
    public Multi<Deposit> streamDeposits() {
      return repository.streamAll();
    }
}

如代码所示,该REST服务包含三个反应式方法:

  • getAllDeposits():返回所有存款,类型为Uni
  • createDeposit():创建存款,类型为Uni
  • streamDeposits():流式传输存款,类型为Multi

7. 测试

为确保应用的准确性和可靠性,我们将使用JUnit和*@QuarkusTest*编写集成测试。通过验证软件各组件的功能性和性能,早期发现并修复问题,构建更健壮可靠的应用:

@QuarkusTest
public class DepositResourceIntegrationTest {

    @Inject
    DepositRepository repository;

    @Test
    public void givenAccountWithDeposits_whenGetDeposits_thenReturnAllDeposits() {
        given().when()
          .get("/deposits")
          .then()
          .statusCode(200);
   }
}

该测试仅验证REST接口连接和存款创建功能。但需注意,并非所有测试都如此简单。在*@QuarkusTest*中测试反应式Panache实体比测试普通Panache实体更复杂。

复杂性的根源在于API的异步特性,以及所有操作必须在Vert.x事件循环上执行的强制要求

首先,我们在pom.xml中添加测试范围的*quarkus-test-hibernate-reactive-panache*依赖:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-test-hibernate-reactive-panache</artifactId>
    <version>3.3.3</version>
    <scope>test</scope>
</dependency>

集成测试方法需标注*@RunOnVertxContext*,使其在Vert.x线程而非主线程上运行

此注解对测试必须在事件循环上执行的组件特别有用,能更真实地模拟实际运行环境。

TransactionalUniAsserter是单元测试方法的预期输入类型。它像拦截器一样工作,将每个断言方法包装在独立的反应式事务中。这能更精确地管理测试环境,确保每个断言方法在隔离上下文中运行:

@Test
@RunOnVertxContext
public void givenDeposit_whenSaveDepositCalled_ThenCheckCount(TransactionalUniAsserter asserter){
    asserter.execute(() -> repository.save(new Deposit("DEP20230201","10","USD")));
    asserter.assertEquals(() -> Deposit.count(), 2l);
}

接下来为返回*MultistreamDeposit()*方法编写测试:

@Test
public void givenDepositsInDatabase_whenStreamAllDeposits_thenDepositsAreStreamedWithDelay() {
    Deposit deposit1 = new Deposit("67890", "USD", "200.0");
    Deposit deposit2 = new Deposit("67891", "USD", "300.0");
    repository.save(deposit1)
      .await()
      .indefinitely();
    repository.save(deposit2)
      .await()
      .indefinitely();
    Response response = RestAssured.get("/deposits/stream")
      .then()
      .extract()
      .response();

    // Then: 响应包含带延迟的流式传输存款记录
    response.then()
      .statusCode(200);
    response.then()
      .body("$", hasSize(2));
    response.then()
      .body("[0].depositCode", equalTo("67890"));
    response.then()
      .body("[1].depositCode", equalTo("67891"));
}

该测试旨在验证通过*Multi*类型流式传输存款以获取所有账户的功能是否正常。

8. 结论

本文探讨了使用Hibernate Reactive和Quarkus进行反应式编程的核心概念。我们介绍了UniMulti的基础知识,并通过集成测试验证代码正确性

使用Hibernate Reactive和Quarkus进行反应式编程,可实现高效的非阻塞数据库操作,使应用响应更迅速、扩展性更强。借助这些工具,我们能构建满足现代高性能环境需求的云原生应用。

本教程的完整源代码可在GitHub获取。


原始标题:Hibernate Reactive and Quarkus | Baeldung