Specification通过CriteriaBuilder构建类型安全谓词树,用户输入仅作参数绑定,不参与SQL拼接;动态条件以Predicate对象链式组合,字段名需白名单校验,杜绝SQL注入。

Specification底层始终走PreparedStatement绑定
Specification不是“拼SQL”,而是通过CriteriaBuilder构造类型安全的谓词树,最终由Hibernate翻译成预编译语句。所有用户输入值(如name、status)只作为参数传入setParameter()调用,不参与SQL字符串生成。哪怕你传入"admin' --",它也只会被当做一个普通字符串值塞进?占位符,不会改变SQL语法结构。
动态条件组合不依赖字符串拼接
常见翻车点是用StringBuilder拼JPQL再喂给@Query——这种写法在编译期就固定了SQL模板,运行时“动态”只能靠字符串插值,等于主动绕过JPA的安全机制。Specification则完全不同:
- 每个条件封装为独立
Predicate,比如cb.like(root.get("name"), "%" + name + "%"),其中%是硬编码,name是纯参数值 -
Specifications.where().and().or()做逻辑组合,全程在Java对象层面操作,不生成中间SQL字符串 - 嵌套
AND/OR、多表join、空值跳过等,都靠API方法链完成,无字符串介入
IN子句、模糊匹配、空条件自动处理都安全
Specification对集合和边界场景有内建支持,无需手动展开或拼接:
-
root.get("id").in(ids)会自动映射为IN (?, ?, ?),占位符数量与ids.size()严格一致 -
cb.like(root.get("email"), email + "%")中,%写死在代码里,email变量仅作参数值传入 - 条件为空时(如
if (StringUtils.hasText(name)) { predicates.add(cb.like(...)); }),直接跳过添加,不会产生WHERE 1=1之类补丁式SQL
白名单控制字段名比校验字符串更可靠
真正容易被忽略的是字段名本身——比如排序字段orderBy来自前端请求。Specification不支持root.get(orderBy)这种动态字段引用(会抛IllegalArgumentException),逼你显式写出root.get("username")或root.get("createdAt")。这意味着你必须用白名单枚举或switch分支来路由字段,而不是放任用户传任意字符串去反射取属性。这个限制反而是安全优势:它把“动态列名”问题从SQL层前置到了Java编译/运行时校验层。

















