代码生成(可选)与类型安全升级
在前两篇中,我们已经完成了:
- tio-boot + jOOQ 纯配置类整合(DSLContext 注入)
- 事务管理
- 批量操作与性能优化
但你目前的 SQL 仍然是这种写法:
DSL.table("system_admin")
DSL.field("login_name")
这类字符串方式虽然可用,但它的缺点也很明显:
- 字段名写错只能运行时报错
- 重构改字段/表名时无法自动修复
- IDE 无法提供真正可靠的自动补全
- 大项目里容易“潜伏 bug”
jOOQ 的“完全体”体验,是通过 代码生成(Codegen) 得到:
Tables.SYSTEM_ADMINSYSTEM_ADMIN.LOGIN_NAMESystemAdminRecord
让你的 SQL 进入真正的“编译期可校验”状态。
本文将讲清楚如何在 tio-boot 项目中加入 jOOQ Codegen,并完成类型安全升级。
一、代码生成能带来什么
以查询为例,对比非常直观。
1)生成前(字符串)
dsl.select(DSL.field("id"), DSL.field("login_name"))
.from(DSL.table("system_admin"))
.where(DSL.field("login_name").eq(loginName))
.fetchOne();
问题:表名/字段名完全是字符串,IDE 很难保证正确性。
2)生成后(类型安全)
import static demo.jooq.gen.Tables.SYSTEM_ADMIN;
dsl.select(SYSTEM_ADMIN.ID, SYSTEM_ADMIN.LOGIN_NAME)
.from(SYSTEM_ADMIN)
.where(SYSTEM_ADMIN.LOGIN_NAME.eq(loginName))
.fetchOne();
收益:
- 字段不存在会直接编译失败
- 重构字段名,引用会跟着改
- 自动补全非常强
- Record/POJO 映射更自然
二、jOOQ Codegen 的基本思路
jOOQ 代码生成的本质是:
- 连接数据库
- 读取 schema 元数据(表、字段、类型、约束、枚举等)
- 生成 Java 类到指定目录与包名
因此它不是运行时做的事,而是构建时做的事。
常用工作流:
- 本地开发:执行
mvn -DskipTests generate-sources - CI:生成并编译
- 生成代码通常加入 Git(也可以不加入,取决于团队习惯)
三、在 pom.xml 添加 jOOQ 代码生成插件
你当前使用:
- Java 21
- jOOQ 3.18.6
- PostgreSQL
建议在 pom.xml 增加这些属性(可选但推荐统一版本):
<properties>
<jooq.version>3.18.6</jooq.version>
</properties>
然后在 <build><plugins> 里加入 jooq-codegen-maven 插件。
1)插件配置(PostgreSQL)
<build>
<plugins>
<plugin>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<version>${jooq.version}</version>
<executions>
<execution>
<id>jooq-codegen</id>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- JDBC 连接信息:用于读取数据库元数据 -->
<jdbc>
<driver>org.postgresql.Driver</driver>
<url>jdbc:postgresql://192.168.3.151/defaultdb</url>
<user>postgres</user>
<password>1234566</password>
</jdbc>
<generator>
<database>
<name>org.jooq.meta.postgres.PostgresDatabase</name>
<!-- 生成哪个 schema 的表 -->
<inputSchema>public</inputSchema>
<!-- 可选:只生成匹配的表(正则) -->
<includes>.*</includes>
<!-- 可选:排除表 -->
<!-- <excludes>flyway_schema_history</excludes> -->
</database>
<generate>
<!-- 生成 Record / POJO / DAO 看团队习惯 -->
<records>true</records>
<pojos>true</pojos>
<daos>false</daos>
<!-- 让 POJO 上生成 setter/getter 更符合常用习惯 -->
<fluentSetters>false</fluentSetters>
</generate>
<target>
<!-- 生成代码包名(建议独立 gen 包) -->
<packageName>demo.jooq.gen</packageName>
<!-- 生成目录(Maven 标准目录) -->
<directory>${project.build.directory}/generated-sources/jooq</directory>
</target>
</generator>
</configuration>
<!-- 插件运行依赖:必须带上 jdbc 驱动 -->
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.24</version>
</dependency>
</dependencies>
</plugin>
<!-- 让 Maven 自动把 generated-sources 加入编译源(多数 IDE 会识别,但建议显式) -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>add-generated-sources</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/jooq</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
说明要点:
phase=generate-sources:执行 Maven 生成源码阶段target/directory:统一放到target/generated-sources/jooqpackageName:建议固定生成包,避免污染业务包build-helper-maven-plugin:确保编译时能找到生成代码
四、执行生成
在项目根目录执行:
mvn -DskipTests generate-sources
成功后你会看到类似结构:
target/generated-sources/jooq/demo/jooq/gen/Tables.java
target/generated-sources/jooq/demo/jooq/gen/tables/SystemAdmin.java
target/generated-sources/jooq/demo/jooq/gen/tables/records/SystemAdminRecord.java
target/generated-sources/jooq/demo/jooq/gen/tables/pojos/SystemAdmin.java
建议 IDE 操作:
- 把
target/generated-sources/jooq标记为 Generated Sources Root - 避免生成代码被误格式化/误改
五、类型安全升级:DAO 改造示例
你之前的 DAO(字符串方式):
dsl.select(DSL.field("id"), DSL.field("login_name"))
.from(DSL.table("system_admin"))
.where(DSL.field("login_name").eq(loginName))
.fetchOneInto(SystemAdmin.class);
升级为生成代码后(强类型):
1)使用生成的 Tables
package demo.jooq.dao;
import org.jooq.DSLContext;
import com.litongjava.annotation.Inject;
import demo.jooq.tx.TransactionContext;
import static demo.jooq.gen.Tables.SYSTEM_ADMIN;
// 这里建议返回你自己的 DTO 或生成的 POJO,都可以
import demo.jooq.model.SystemAdmin;
public class SystemAdminDao {
@Inject
private DSLContext dsl;
private DSLContext useDsl() {
DSLContext txDsl = TransactionContext.get();
return txDsl != null ? txDsl : dsl;
}
public SystemAdmin findByLoginName(String loginName) {
return useDsl()
.select(SYSTEM_ADMIN.ID, SYSTEM_ADMIN.LOGIN_NAME, SYSTEM_ADMIN.PASSWORD)
.from(SYSTEM_ADMIN)
.where(SYSTEM_ADMIN.LOGIN_NAME.eq(loginName))
.fetchOneInto(SystemAdmin.class);
}
public int updatePassword(String loginName, String newPassword) {
return useDsl()
.update(SYSTEM_ADMIN)
.set(SYSTEM_ADMIN.PASSWORD, newPassword)
.where(SYSTEM_ADMIN.LOGIN_NAME.eq(loginName))
.execute();
}
}
这里的关键变化:
SYSTEM_ADMIN是生成的表对象,不会写错SYSTEM_ADMIN.LOGIN_NAME是生成字段对象,有类型信息eq(loginName)也能获得更严格的类型推导
六、选择:使用生成的 POJO 还是业务 DTO
生成后你会拿到:
demo.jooq.gen.tables.pojos.SystemAdmin(生成 POJO)demo.jooq.gen.tables.records.SystemAdminRecord(Record)
你有两种主流选择:
方案 A:业务 DTO 继续用你自己的(更推荐)
优点:
- 业务层结构稳定
- 不被生成包结构绑定
- 未来切换生成策略影响小
缺点:
- 多一层映射(但 jOOQ 的
fetchInto通常足够)
方案 B:直接使用生成 POJO(开发更快)
优点:
- 少写一个 DTO
- 映射最直接
缺点:
- 业务层依赖生成代码包结构
- 生成策略变化会影响业务代码
通常建议:
- DAO 层可以用生成 POJO / Record
- Controller 返回仍然用你自己的 DTO(更可控)
七、进阶:强制类型映射(forcedTypes)
很多团队会遇到这种需求:
jsonb映射为String或Maptimestamp映射为LocalDateTime- 自定义 enum 类型映射为 Java enum
这就需要 forcedTypes。
示例(把 jsonb 映射为 String):
<database>
<name>org.jooq.meta.postgres.PostgresDatabase</name>
<inputSchema>public</inputSchema>
<forcedTypes>
<forcedType>
<name>VARCHAR</name>
<includeTypes>jsonb</includeTypes>
</forcedType>
</forcedTypes>
</database>
后续你也可以进一步使用 Converter,把 jsonb 转成 Map<String,Object>(通常需要自定义 converter 类)。
八、生成策略建议
1)生成范围控制
大库建议不要全生成:
- 只生成特定表:
<includes>system_admin|audit_log</includes> - 排除历史表/临时表:
<excludes>tmp_.*|bak_.*</excludes>
2)生成代码是否提交 Git
常见两种流派:
- 提交:构建更稳定,避免“本地没生成导致编译失败”
- 不提交:仓库更干净,但 CI 必须保证生成
如果团队没有严格 CI,建议先提交生成代码,后面再演进。
九、总结
加入 jOOQ Codegen 后,你的 tio-boot + jOOQ 体系会从“可用”提升为“强类型工程化”:
- 表/字段编译期校验
- IDE 补全与重构能力极强
- 大幅减少运行期 SQL 拼写错误
- 事务、批量、性能优化都能继续复用原有设计
