jOOQ默认安全的前提是全程使用代码生成的Table/Field类(如BOOK.AUTHOR_ID),编译期校验表名字段名;若改用DSL.field("author_id")等字符串字面量,则丧失类型安全,可能引发运行时错误或SQL注入。

用 DSLContext.select() 配合生成的 Table/Field 类写查询
只要不手动拼接字符串,jOOQ 默认就安全。它所有表名、字段名都来自代码生成器生成的 BOOK、BOOK.AUTHOR_ID 这类类型安全类,编译期就能报错——比如把 BOOK.AUTHOR_ID 写成 BOOK.AUTHORID,直接编译失败,根本进不了运行时。
常见错误是绕过生成类,改用 DSL.field("author_id") 或 DSL.table("book")。这类字符串字面量不会被校验,一旦数据库改名或大小写变化,运行时报错且可能触发 SQL 注入(如果拼进动态上下文)。
- ✅ 正确:用
BOOK.AUTHOR_ID.eq(authorId) - ❌ 危险:用
DSL.condition("author_id = " + authorId)或DSL.field("author_id").eq(authorId)(后者虽不直接拼接,但字段名失守) - ⚠️ 注意:生成类需随 DDL 变更及时重生成,否则类型安全失效
动态 WHERE 条件必须用 and() / or() 链式调用,别用字符串拼接
很多人想根据参数有无来控制条件,结果写出这种代码:"where " + (name != null ? "name = '" + name + "'" : "") + (age > 0 ? " and age > " + age : "")——这等于把 PreparedStatement 的防护全扔了。
jOOQ 的 Condition 是一等公民,支持惰性构建和组合。它内部自动做参数绑定,且逻辑优先级由方法链决定,括号也由框架按需加。
- ✅ 正确:用
Condition cond = DSL.trueCondition(); if (name != null) cond = cond.and(BOOK.NAME.eq(name)); if (age > 0) cond = cond.and(BOOK.AGE.gt(age)); dsl.select().from(BOOK).where(cond) - ❌ 危险:任何含
+拼接 SQL 片段的操作,哪怕只拼字段名或操作符 - ? 小技巧:用
DSL.noCondition()代替空字符串占位,避免 null 判断干扰链式调用
必须用 param() 或 val() 处理运行时值,禁用 condition(String, Object...)
condition("name = ?", name) 看似用了问号,但 jOOQ 对这种 plain SQL 模板不做字段名/表名校验,且当它被嵌入 .and() 或 .or() 时,会强制外层加括号,容易破坏原有 SQL 结构(比如在 PostgreSQL 的 decode() 场景下导致语法错误)。
真正安全的做法是把运行时值封装为 Field,再参与表达式构建。
- ✅ 推荐:用
val(name)得到类型安全的字面量字段,再参与eq()、like()等操作 - ✅ 高级场景:用
field("decode({0}, {1})", String.class, val(addr), val(escape))构建函数调用,再用inline("address").eq(...)比较 - ❌ 避免:
condition("name = {0}", name)、condition("name = :name", name)、condition("name = ?", name)—— 这些都属于 plain SQL templating,绕过了类型检查
原生函数或特殊语法必须走 field() + inline() 组合
像 PostgreSQL 的 decode()、MySQL 的 IF()、Oracle 的 DECODE() 这类数据库特有函数,jOOQ 不提供内置 DSL 方法。此时不能退回到字符串拼接,而要用 field() 显式声明函数调用,并用 inline() 表示字面量列名。
关键点在于:整个表达式最终必须是一个 Field<?>,才能参与 eq()、isNotNull() 等类型安全操作;否则你就又回到了字符串拼接的老路。
- ✅ 安全写法:
Field<string> decoded = field("decode({0}, {1})", String.class, val("input"), val("default")); Condition c = inline("status").eq(decoded);</string> - ❌ 错误写法:
condition("status = decode('input', 'default')")—— 字符串中值不可控,且列名 status 未校验 - ⚠️ 注意:
field()第二个参数必须指定正确返回类型(如String.class),否则后续类型推导失败,编译不通过
field("xxx") 当成万能兜底——它其实只是把校验推迟到了运行时**。

















