原生SQL查询必须使用参数占位符(?1或:param),禁用字符串拼接;IN子句需手动展开占位符;字段别名须与实体属性名严格一致(含大小写);@Modifying操作必须配@Transactional。

原生SQL查询必须用参数占位符,禁用任何字符串拼接;否则哪怕只加一个单引号,就等于把数据库大门敞开。
nativeQuery = true 时只能用 ?1 或 :param,不能拼字符串
启用 nativeQuery = true 后,JPA 不再解析 JPQL,而是把整个 SQL 字符串直接交给 JDBC 执行。此时你对 SQL 结构负全责——@Query(value = "SELECT * FROM user WHERE name = '" + name + "'", nativeQuery = true) 这种写法编译期就报错,运行时更绝对禁止。
- 正确写法(索引参数):
@Query(value = "SELECT * FROM user WHERE status = ?1 AND dept_id = ?2", nativeQuery = true) - 正确写法(具名参数):
@Query(value = "SELECT * FROM user WHERE status = :status AND dept_id = :deptId", nativeQuery = true),再配合@Param("status")和@Param("deptId") - 两种风格不能混用:同一个查询里不能同时出现
?1和:param
IN 子句必须手动展开占位符,不能直接传 List
原生 SQL 不支持 WHERE id IN :ids 这种写法,Hibernate 不会帮你把 List 拆成 ?1, ?2, ?3。直接这么写会抛 IllegalArgumentException 或查不到数据。
- 错误写法:
@Query(value = "SELECT * FROM user WHERE id IN :ids", nativeQuery = true) - 安全替代一(固定长度):
@Query(value = "SELECT * FROM user WHERE id IN (?1, ?2, ?3)", nativeQuery = true),然后传入三个参数 - 安全替代二(换工具):改用
JdbcTemplate+PreparedStatementCreator,手动构建动态占位符数组 - 安全替代三(优先用 Specification):如果只是查数据,用
root.get("id").in(ids),它天然支持自动展开
字段别名必须与实体属性名严格一致,否则映射为空
开启 nativeQuery = true 后,JPA 不再按实体属性名自动映射,而是严格按 SELECT 列名或下标匹配。数据库字段叫 user_name,但实体属性是 userName?不加别名就为 null。
- 危险写法:
SELECT id, user_name FROM user→ 返回的User对象中userName字段为null - 推荐写法:
SELECT id AS id, user_name AS userName FROM user—— 别名必须和实体属性名完全一致(含大小写) - 更稳妥做法:不返回完整实体,改用
@Query+ 自定义 DTO 类,显式声明构造器参数
@Modifying + @Transactional 必须成对出现,动态排序/表名不可参数化
@Modifying 操作(如 UPDATE、DELETE)若没配 @Transactional,事务不会生效,可能造成部分更新成功、部分失败却无回滚。
-
@Modifying方法必须标注@Transactional,且通常需指定rollbackFor = Exception.class - 动态表名、列名、排序方向(
ASC/DESC)无法参数化:ORDER BY :sortField会报错或查不到数据 - 真实业务中需要动态排序时,应白名单校验后硬编码字段名,例如:
if (!List.of("name", "created_at").contains(sortField)) throw new IllegalArgumentException();
最容易被忽略的是:IN 子句的手动占位符展开和字段别名大小写一致性——前者导致查询无声失败,后者导致字段值始终为 null,两者都难以通过日志或单元测试快速暴露。

















