Skip to content

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 扩展:

xml
  <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>
xml
    <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 会直接复用:

properties
quarkus.datasource.jdbc.url=jdbc:postgresql://127.0.0.1:5432/demo
quarkus.datasource.username=postgres
quarkus.datasource.password=postgres

6. 强烈建议停止使用 Hibernate 的自动建表 ​

配置为:

properties
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

示例:

java
package nexus.io.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

混用示例 ​

java
package nexus.io.quarkus.service;

import java.util.Map;

import nexus.io.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 开销
  • 无序列化成本

并支持:

平滑、可控、低风险的数据访问层演进。

MIT Licensed