Criteria API 的卖点是“类型化”和“程序化构造”。这篇用真实的 SQL 与计时量出它到底带来什么:SQL 与 JPQL 一样、运行时与编译期的错误反馈、构建开销与查询计划缓存、动态条件对 SQL 文本数量的影响,以及它没有拦住的类型错误。
JPQL:select p from Person p where p.age >= :min and p.city = :city order by p.name
SQL> select p1_0.id,p1_0.active,p1_0.age,p1_0.city,p1_0.name from Person p1_0 where p1_0.age>=? and p1_0.city=? order by p1_0.name fetch first ? rows only
Criteria:cb.ge(age, 30)、cb.equal(city, "Beijing"),orderBy name:
SQL> select p1_0.id,p1_0.active,p1_0.age,p1_0.city,p1_0.name from Person p1_0 where p1_0.age>=? and p1_0.city=? order by p1_0.name fetch first ? rows only
Criteria:cb.equal(city, cb.literal("Beijing"))(显式字面量):
SQL> select p1_0.id,p1_0.active,p1_0.age,p1_0.city,p1_0.name from Person p1_0 where p1_0.city='Beijing' fetch first ? rows only
cb.literal 带单引号的用户输入 x' or '1'='1:
SQL> select p1_0.id,p1_0.active,p1_0.age,p1_0.city,p1_0.name from Person p1_0 where p1_0.city='x'' or ''1''=''1'
返回 0 行(单引号被转义成 '',没有变成 SQL 注入)
JPQL 里直接写字面量 p.city = 'Beijing':
SQL> select p1_0.id,p1_0.active,p1_0.age,p1_0.city,p1_0.name from Person p1_0 where p1_0.city='Beijing' fetch first ? rows only r.get("agee"):PathElementException:Could not resolve attribute 'agee' of 'Person'
cb.equal(r.get("age"), "not-a-number"):CoercionException:Error coercing value
r.get(Person_.age)(静态元模型)的属性类型是 int,正常执行返回 40 行JPQL 字符串 + setParameter 每次 61.5 µs;查询计划缓存 命中 10000 / 未命中 0 每次重新构建 CriteriaQuery 每次 77.0 µs;查询计划缓存 命中 0 / 未命中 0 CriteriaQuery 构建一次、复用(ParameterExpression) 每次 67.3 µs;查询计划缓存 命中 0 / 未命中 0 命名查询(@NamedQuery 等价:createNamedQuery) 每次 59.8 µs;查询计划缓存 命中 10000 / 未命中 0
8 种条件组合各执行 3 次(共 24 次)Criteria 动态查询:发给数据库的不同 SQL 文本共 8 种
例 1:select …from Person p1_0
例 2:select …from Person p1_0 where p1_0.name like ? escape ''
例 3:select …from Person p1_0 where p1_0.age>=?
同样 8 种组合 × 3 次,用一条“可选参数”JPQL(:p is null or …):发给数据库的不同 SQL 文本共 1 种(都是同一条带 ? 的 SQL,数据库的预编译语句可以复用;代价是执行计划是通用的,(? is null or col = ?) 往往用不上索引)CriteriaUpdate:把 city = Beijing 的 active 设为 false,影响 500 行 CriteriaDelete:删除 age < 20 的行,影响 80 行
javac 退出码 1:
<jtmp>/j279a/Bad.java:1: error: cannot find symbol
import jakarta.persistence.criteria.*; public class Bad { static void f(CriteriaBuilder cb, Root<Person> r) { Predicate p = cb.equal(r.get(Person_.agee), 1); Predicate q = cb.equal(r.get(Person_.age),
^
symbol: variable agee