tio-boot + Quarkus + Hibernate ORM Panache + jOOQ 整合方案
1. 架构目标升级
在既有设计中:
tio-boot 唯一对外 HTTP / 网络入口,负责端口、协议、路由与 handler。
Quarkus 仅作为后台运行时容器,提供:
- CDI(Arc)
- 事务(JTA /
@Transactional) - Hibernate ORM(Panache)
- 数据源与连接池
业务代码 完全运行在 Quarkus Bean 内,不感知 tio-boot。
现在新增一个现实需求:
从 ORM(Panache)逐步迁移到 jOOQ,而不是一次性重写数据层。
因此架构必须支持:
- ORM 与 SQL DSL 长期共存
- 同一事务内混用
- 不引入 RPC 或微服务复杂度
- Quarkus 不启动 HTTP
2. 为什么要同时使用 Panache 和 jOOQ
这不是技术叠加,而是两种数据访问哲学的组合。
Panache(ORM)
优势:
- CRUD 开发效率极高
- 与 CDI 和事务天然集成
- 模型驱动,代码简洁
- 非常适合中等复杂度业务
不足:
- SQL 可控性较弱
- 复杂查询可预测性差
- 性能调优空间有限
jOOQ(类型安全 SQL DSL)
优势:
- SQL 完全可控
- 编译期字段检查
- JOIN、窗口函数、复杂子查询能力极强
- 执行计划更稳定
适用于:
- 核心链路
- 报表系统
- 高并发查询
- 数据密集型业务
推荐策略
不要将其理解为“ORM vs jOOQ”。
更合理的模式是:
简单模型使用 ORM,复杂查询使用 jOOQ。
大量成熟系统都经历过类似演进路径。
3. 是否可以共存
可以,并且建议共用以下基础设施:
- 同一个 DataSource
- 同一个连接池
- 同一个 JTA 事务管理器
这样可确保:
Panache 与 jOOQ 在同一事务中提交或回滚。
这是整合成功的关键前提。
4. Maven 依赖
在已有依赖基础上新增 jOOQ 扩展:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq-bom</artifactId>
<version>3.20.10</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq</artifactId>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen</artifactId>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq-meta</artifactId>
</dependency>
注意事项:
- 该扩展属于 Quarkiverse(社区扩展)
- 升级 Quarkus 时建议同步检查兼容版本
5. 数据源配置
无需新增 datasource。
jOOQ 会直接复用:
quarkus.datasource.jdbc.url=jdbc:postgresql://127.0.0.1:5432/demo
quarkus.datasource.username=postgres
quarkus.datasource.password=postgres
6. 强烈建议停止使用 Hibernate 的自动建表
配置为:
quarkus.hibernate-orm.database.generation=none
7. jOOQ Code Generation(强烈推荐)
不要手写表字段。
通过 codegen 自动生成:
- Tables
- Records
- 字段常量
示例效果:
APP_DEMO_USER.ID
APP_DEMO_USER.USERNAME
优势:
写错字段名会在编译期失败,而不是运行期。
这在大型系统中价值极高。
8. Repository 设计原则
不要让 Service 直接操作 DSLContext。
正确方式是建立独立仓储:
UserJooqRepo
示例:
package com.litongjava.quarkus.dao;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.Table;
import org.jooq.impl.DSL;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ApplicationScoped
public class UserJooqRepo {
@Inject
DSLContext dsl;
public Long insertUser(String username, String password, Long createdAt) {
Table<?> userTable = DSL.table(DSL.name("app_demo_user"));
Field<Long> id = DSL.field(DSL.name("id"), Long.class);
Field<String> usernameField = DSL.field(DSL.name("username"), String.class);
Field<String> passwordField = DSL.field(DSL.name("password"), String.class);
Field<Long> createdAtField = DSL.field(DSL.name("createdat"), Long.class);
var record = dsl.insertInto(userTable)
//
.set(usernameField, username).set(passwordField, password).set(createdAtField, createdAt)
//
.returning(id).fetchOne();
if (record == null) {
throw new IllegalStateException("Insert failed");
}
return record.get(id);
}
}
好处:
- 避免 DSL 泄漏到业务层
- 保持数据访问风格一致
- 降低未来替换成本
9. 事务共享(整合的核心)
始终遵守一条规则:
事务边界必须放在 Service 层。
不要放在:
- handler
- repository
混用示例
package com.litongjava.quarkus.service;
import java.util.Map;
import com.litongjava.quarkus.dao.UserJooqRepo;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
public class JooqUserService {
@Inject
private UserJooqRepo userJooqRepo;
@Transactional
public Map<String, Object> createUser(String username, String password) {
Long id = userJooqRepo.insertUser(username, password, System.currentTimeMillis());
return Map.of("ok", true, "id", id);
}
}
执行时:
- 使用同一数据库连接
- 属于同一事务
- 一次 commit
无需额外配置。
10. tio-boot 架构下的关键规则
由于系统没有使用 Quarkus HTTP,请求线程来自 tio worker thread。
因此:
CDI Request Context 不会自动激活。
结论:
每一个涉及数据库的方法都必须使用:
@Transactional
包括:
- 查询
- 分页
- JOIN
否则极易触发:
ContextNotActiveException
事务在这里不仅用于写入,更是连接与 Session 生命周期的边界。
11. 推荐迁移路径
不建议一次性替换 ORM。
风险极高。
更合理的方式是分阶段演进。
阶段一:并存
- 老模块继续使用 Panache
- 新模块优先采用 jOOQ
无需修改既有代码。
阶段二:优先迁移复杂查询
ORM 最大的问题通常出现在复杂 SQL。
先替换这些部分,收益最高。
阶段三:Repository 内部替换
例如:
UserRepo
逐步改为 jOOQ 实现。
但保持:
Service 接口不变。
这是成熟系统常用策略:
稳定业务层,替换数据层。
阶段四:移除 Hibernate(可选)
当 ORM 使用率接近零时,可以考虑删除:
- Hibernate
- Panache
收益:
- 更快启动速度
- 更低内存占用
- 更少运行时代理
但这不是必须步骤。
12. 一个常见错误
不要长期在同一张表上混合两套写入方式,例如:
- ORM 写入
- jOOQ 更新
可能引发:
- 一级缓存冲突
- flush 时机不一致
- 乐观锁问题
- 脏检查异常
迁移建议:
以“表”为单位完成切换,而不是以“方法”为单位。
13. 最终架构逻辑
HTTP
↓
tio-boot handler
↓
Service (@Transactional)
↓
├── Panache Repository(逐步减少)
└── jOOQ Repository(逐步主导)
↓
DataSource / JTA
↓
PostgreSQL
14. 最终结论
该整合方案的本质是:
让 Quarkus 专注于运行时基础设施,而不是 Web 框架。
从而同时获得:
- ORM 的开发效率
- jOOQ 的 SQL 控制力
- 单 JVM 调用性能
- 无 RPC 开销
- 无序列化成本
并支持:
平滑、可控、低风险的数据访问层演进。
