<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.vertx.howtos</groupId>
<artifactId>hibernate-reactive-howto</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.verticle>io.vertx.howtos.hr.MainVerticle</main.verticle>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stack-depchain</artifactId>
<version>4.3.7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>vertx-mutiny-clients-bom</artifactId>
<version>3.2.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>smallrye-mutiny-vertx-core</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>smallrye-mutiny-vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>smallrye-mutiny-vertx-pg-client</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.reactive</groupId>
<artifactId>hibernate-reactive-core</artifactId>
<version>1.1.9.Final</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.6</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.17.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<release>11</release>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<mainClass>${main.verticle}</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
在 Vert.x 中使用 Hibernate Reactive
本指南展示如何结合 Hibernate Reactive 构建高性能响应式 Vert.x 服务。
你将构建什么
-
你将构建一个支持列出、查询与录入商品数据的 HTTP REST 微服务;
-
数据库访问全程由 Hibernate Reactive API 以及底层驱动 Vert.x Reactive PostgreSQL Client 执行;
-
为了简化测试,数据库将通过 Testcontainers 作为 Docker PostgreSQL 容器自动拉起。
前置要求
-
文本编辑器或 IDE
-
Java 11 或更高版本
-
Apache Maven
-
Docker 环境
-
用于发起命令行测试请求的 HTTPie 或 curl
创建工程项目
这是你应该使用的 pom.xml 文件配置:
pom.xml该项目引入了 Vert.x Mutiny 绑定,因此你可以使用优雅的 Mutiny 响应式编程模型来组合异步操作。
服务端实现
核心服务仅由两个核心类构成:
-
Product:标准 JPA 实体映射类; -
MainVerticle:配置 HTTP 路由与 Hibernate Reactive 会话工厂的核心 Verticle。
实体类定义
打开 src/main/java/io/vertx/howtos/hr/Product.java 查看 JPA 实体映射:
package io.vertx.howtos.hr;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.math.BigDecimal;
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
@Column(unique = true)
private String name;
@Column(nullable = false)
private BigDecimal price;
public Product() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
}
核心 Verticle 实现
打开 src/main/java/io/vertx/howtos/hr/MainVerticle.java:
package io.vertx.howtos.hr;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.vertx.core.AbstractVerticle;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.mutiny.core.Vertx;
import io.vertx.mutiny.core.http.HttpServer;
import io.vertx.mutiny.ext.web.Router;
import io.vertx.mutiny.ext.web.RoutingContext;
import io.vertx.mutiny.ext.web.handler.BodyHandler;
import org.hibernate.reactive.mutiny.Mutiny;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.PostgreSQLContainer;
import javax.persistence.Persistence;
import java.util.List;
import java.util.Map;
// tag::preamble[]
public class MainVerticle extends AbstractVerticle {
private static final Logger logger = LoggerFactory.getLogger(MainVerticle.class);
private Mutiny.SessionFactory emf; (1)
@Override
public Uni<Void> asyncStart() {
// end::preamble[]
// tag::hr-start[]
Uni<Void> startHibernate = Uni.createFrom().deferred(() -> {
var pgPort = config().getInteger("pgPort", 5432);
var props = Map.of("javax.persistence.jdbc.url", "jdbc:postgresql://localhost:" + pgPort + "/postgres"); (1)
emf = Persistence
.createEntityManagerFactory("pg-demo", props)
.unwrap(Mutiny.SessionFactory.class);
return Uni.createFrom().voidItem();
});
startHibernate = vertx.executeBlocking(startHibernate) (2)
.onItem().invoke(() -> logger.info("✅ Hibernate Reactive is ready"));
// end::hr-start[]
// tag::routing[]
Router router = Router.router(vertx);
BodyHandler bodyHandler = BodyHandler.create();
router.post().handler(bodyHandler::handle);
router.get("/products").respond(this::listProducts);
router.get("/products/:id").respond(this::getProduct);
router.post("/products").respond(this::createProduct);
// end::routing[]
// tag::async-start[]
Uni<HttpServer> startHttpServer = vertx.createHttpServer()
.requestHandler(router)
.listen(8080)
.onItem().invoke(() -> logger.info("✅ HTTP server listening on port 8080"));
return Uni.combine().all().unis(startHibernate, startHttpServer).discardItems(); (1)
// end::async-start[]
}
// tag::crud-methods[]
private Uni<List<Product>> listProducts(RoutingContext ctx) {
return emf.withSession(session -> session
.createQuery("from Product", Product.class)
.getResultList());
}
private Uni<Product> getProduct(RoutingContext ctx) {
long id = Long.parseLong(ctx.pathParam("id"));
return emf.withSession(session -> session
.find(Product.class, id))
.onItem().ifNull().continueWith(Product::new);
}
private Uni<Product> createProduct(RoutingContext ctx) {
Product product = ctx.body().asPojo(Product.class);
return emf.withSession(session -> session.
persist(product)
.call(session::flush)
.replaceWith(product));
}
// end::crud-methods[]
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
logger.info("🚀 Starting a PostgreSQL container");
// tag::tc-start[]
PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>("postgres:11-alpine")
.withDatabaseName("postgres")
.withUsername("postgres")
.withPassword("vertx-in-action");
postgreSQLContainer.start();
// end::tc-start[]
long tcTime = System.currentTimeMillis();
logger.info("🚀 Starting Vert.x");
// tag::vertx-start[]
Vertx vertx = Vertx.vertx();
DeploymentOptions options = new DeploymentOptions().setConfig(new JsonObject()
.put("pgPort", postgreSQLContainer.getMappedPort(5432))); (1)
vertx.deployVerticle(MainVerticle::new, options).subscribe().with( (2)
ok -> {
long vertxTime = System.currentTimeMillis();
logger.info("✅ Deployment success");
logger.info("💡 PostgreSQL container started in {}ms", (tcTime - startTime));
logger.info("💡 Vert.x app started in {}ms", (vertxTime - tcTime));
},
err -> logger.error("🔥 Deployment failure", err));
// end::vertx-start[]
}
}
运行与测试
使用 Maven 启动程序:
mvn compile exec:java
Testcontainers 将自动拉起一个临时 PostgreSQL 容器并完成表结构初始化。
使用 HTTPie 进行 CRUD 测试:
-
创建新商品:
http POST :8080/products name="Vert.x in Action"
-
查询所有商品:
http GET :8080/products
总结
本文档展示了:
-
如何在 Vert.x 中使用 Hibernate Reactive 取代传统阻塞式 Hibernate ORM;
-
如何配合 Mutiny 响应式流处理异步持久化会话;
-
如何基于 Testcontainers 实现零配置本地集成测试。