Hibernate Reactive 和 Quarkus
1. 概述
為大規模、高效能係統創建響應式應用程式在 Java 開發中變得越來越重要。 Hibernate Reactive 和 Quarkus 是強大的工具,使開發人員能夠有效率地建立反應式應用程式。 Hibernate Reactive 是 Hibernate ORM 的反應式擴展,旨在與非阻塞資料庫驅動程式無縫協作。
另一方面,Quarkus 是一個針對 GraalVM 和 OpenJDK HotSpot 進行最佳化的 Kubernetes 原生 Java 框架,專為建立反應式應用程式而客製化。它們共同提供了一個強大的平台,用於創建高效能、可擴展和反應式 Java 應用程式。
在本教程中,我們將透過從頭開始建立反應式銀行存款應用程式來深入探索 Hibernate Reactive 和 Quarkus。此外,我們將納入整合測試以確保應用程式的正確性和可靠性。
2. Quarkus 中的響應式編程
Quarkus 作為反應式框架而聞名,從一開始就將反應性作為其架構的基本元素。該框架豐富了眾多反應性功能,並得到了強大的生態系統的支持。
值得注意的是,Quarkus 透過 Mutiny 提供的Uni和Multi類型利用反應式概念,展現了對非同步和事件驅動程式設計範例的堅定承諾。
3. 誇庫斯叛變
Mutiny 是 Quarkus 中用於處理反應式功能的主要 API。大多數擴充功能透過提供傳回Uni和Multi的 API 來支援 Mutiny,該 API 以非阻塞背壓處理非同步資料流。
我們的應用程式透過 Quarkus 提供的Uni和Multi類型利用反應式概念。 Multi表示可以非同步發出多個專案的類型,類似於java.util.stream.Stream但具有反壓處理。
我們在處理潛在的無限資料流時使用Multi ,例如即時傳輸多個銀行存款。
Uni表示最多發出一項或一個錯誤的類型,類似於java.util.concurrent.CompletableFuture但具有更強大的組合運算子。 Uni用於我們期望單一結果或錯誤的場景,例如從資料庫中取得單一銀行存款。
4. 理解PanacheEntity
當我們將 Quarkus 與 Hibernate Reactive 結合使用時, [PanacheEntity](https://quarkus.io/guides/hibernate-orm-panache)類別提供了一種簡化的方法,以最少的樣板程式碼定義 JPA 實體。透過從 Hibernate 的PanacheEntityBase擴展, PanacheEntity獲得了反應能力,從而能夠以非阻塞方式管理實體。
這樣可以有效地處理資料庫操作,而不會阻止應用程式的執行,從而提高整體效能。
5.Maven依賴
首先,我們將[quarkus-hibernate-reactive-panache](https://mvnrepository.com/artifact/io.quarkus/quarkus-hibernate-reactive-panache)相依性新增至pom.xml :
<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;
// standard setters and getters
}
在這個具體範例中,類別Deposit擴展了PanacheEntity類,有效地使其成為由 Hibernate Reactive 管理的響應式實體。
由於此擴展, Deposit類別繼承了 CRUD(創建、讀取、更新、刪除)操作的方法並獲得了查詢功能,從而顯著減少了應用程式中手動 SQL 或 JPQL 查詢的需要。這種方法簡化了資料庫操作管理並提高了系統的整體效率。
6.2.實施儲存庫
在大多數情況下,我們通常會利用存款實體來進行所有 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);
}
}
該儲存庫建立了一個自訂save()方法和一個streamAll()方法,允許我們以Multi<Deposit>格式擷取所有存款。
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<Response>類型傳回所有存款,我們也有方法createDeposit() ,用於建立存款。這兩個方法的回傳類型都是Uni<Response> ,而streamDeposits()傳回Multi<Deposit>.
7. 測試
為了確保應用程式的準確性和可靠性,我們將使用 JUnit 和@QuarkusTest合併整合測試。這種方法涉及創建測試來驗證軟體的各個程式碼或組件,以驗證其正確的功能和效能。這些測試幫助我們在開發早期識別並糾正任何問題,最終形成更強大、更可靠的應用程式:
@QuarkusTest
public class DepositResourceIntegrationTest {
@Inject
DepositRepository repository;
@Test
public void givenAccountWithDeposits_whenGetDeposits_thenReturnAllDeposits() {
given().when()
.get("/deposits")
.then()
.statusCode(200);
}
}
我們討論的測試僅專注於驗證與 REST 端點的成功連接並建立存款。然而,必須注意的是,並非所有測試都那麼簡單。與測試常規 Panache 實體相比,在@QuarkusTest中測試反應式 Panache 實體會增加複雜度。
這種複雜性源自於 API 的非同步特性以及所有操作都必須在 Vert.x 事件循環上執行的強制要求。
首先,我們將[quarkus-test-hibernate-reactive-panache](https://mvnrepository.com/artifact/io.quarkus/quarkus-test-hibernate-reactive-panache/)依賴項和test範圍加入pom.xml中:
<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);
}
現在,我們需要為streamDeposit () 編寫一個測試,它會傳回Multi<Deposit> :
@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: the response contains the streamed books with a delay
response.then()
.statusCode(200);
response.then()
.body("$", hasSize(2));
response.then()
.body("[0].depositCode", equalTo("67890"));
response.then()
.body("[1].depositCode", equalTo("67891"));
}
此測試的目的是驗證串流存款的功能,以回應方式使用Multi<Deposit>類型擷取所有帳戶。
八、結論
在本文中,我們探討了使用 Hibernate Reactive 和 Quarkus 的反應式程式設計的概念。我們討論了Uni和Multi的基礎知識,並包括一個整合測試來驗證我們程式碼的正確性。
使用 Hibernate Reactive 和 Quarkus 進行反應式編程可實現高效、非阻塞的資料庫操作,使應用程式更具回應性和可擴展性。透過利用這些工具,我們可以建立滿足當今高效能環境需求的現代雲端原生應用程式。
與往常一樣,本教學的源代碼可在 GitHub 上取得。