Oracle 12c+ IDENTITY列在Spring Data JPA中不自动生效,因JDBC驱动对getGeneratedKeys()支持不全,需显式配置@SequenceGenerator并使allocationSize与数据库CACHE值一致,或改用TABLE策略。
spring data jpa 默认不识别 oracle 12c+ 的 identity 列自增行为,直接用 @generatedvalue(strategy = generationtype.identity) 会报错或生成 null —— 因为它仍按传统 jdbc 的 getgeneratedkeys() 逻辑走,而 oracle 的 identity 列在底层依赖序列(隐式),但不暴露为标准的“返回键”机制。
Oracle 12c+ Identity 列在 Spring Data JPA 中为何不自动生效
Spring Data JPA 的 GenerationType.IDENTITY 实际上要求数据库驱动在执行 INSERT 后能通过 JDBC 的 Statement.getGeneratedKeys() 拿到值。Oracle JDBC 驱动(尤其是较老版本)对 IDENTITY 列的该接口支持不完整,即使表定义了 GENERATED ALWAYS AS IDENTITY,getGeneratedKeys() 也可能返回空结果集,导致实体 ID 保持为 0 或 null。
常见现象包括:
-
id字段插入后仍为 0 或null,查库发现实际已生成值(如 1、2),但 JPA 没同步回来 - 抛出
org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save() - 启用
spring.jpa.show-sql=true后看到 INSERT 语句里没带id,但日志没显示 RETURNING 子句
必须显式配置 @GeneratedValue + @SequenceGenerator(即使没显式建序列)
Oracle 的 IDENTITY 列虽隐式绑定序列,但 Hibernate 不会自动发现它。你得手动告诉 Hibernate:“这个列的值来自一个叫 MY_TABLE_ID_SEQ 的序列”,哪怕这个序列是 Oracle 内部管理、不可见的。
实操建议:
- 在实体类中用
@SequenceGenerator指向一个“逻辑序列名”,名字可任意,但需与@GeneratedValue的generator属性匹配 - 设置
allocationSize = 1—— 因为 OracleIDENTITY默认不 cache(或 cache 很小),设大了会导致 ID 跳号 - 不要设
sequenceName为真实序列名(它不可查、不可访问),留空或用占位符即可;Hibernate 会绕过 JDBC 的getGeneratedKeys(),改用SELECT seq.nextval FROM dual模拟(仅当配置了@SequenceGenerator时触发)
示例:
@Entity
@Table(name = "user_info")
public class UserInfo {
@Id
@GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "user_info_id_gen"
)
@SequenceGenerator(
name = "user_info_id_gen",
sequenceName = "dummy", // 实际无效,仅占位
allocationSize = 1
)
@Column(name = "id", updatable = false, nullable = false)
private Long id;
// 其他字段...
}
更可靠:用 @GeneratedValue(strategy = GenerationType.TABLE) + 自定义表
如果项目已用多数据源或需强兼容性(比如未来可能切到非 Oracle 数据库),推荐放弃依赖 IDENTITY 的 JDBC 行为,改用 TABLE 策略。它不依赖数据库特性,通过一张专用计数器表(如 hibernate_sequences)维护 ID,完全可控。
关键点:
- 建一张简单表:
CREATE TABLE hibernate_sequences (sequence_name VARCHAR2(50), next_val NUMBER) - 初始化一行:
INSERT INTO hibernate_sequences VALUES ('user_info_id', 1) - 实体中配置:
@GeneratedValue(
strategy = GenerationType.TABLE,
generator = "user_info_id_gen"
)
@TableGenerator(
name = "user_info_id_gen",
table = "hibernate_sequences",
pkColumnName = "sequence_name",
valueColumnName = "next_val",
pkColumnValue = "user_info_id",
allocationSize = 1
)
这种方式规避了 Oracle 隐式序列的黑盒行为,也避免了 CACHE 参数引发的间隙问题(比如实例崩溃后丢失缓存值)。
生产环境最容易被忽略的点:CACHE 参数与 allocationSize 必须对齐
Oracle 创建 IDENTITY 列时若指定了 CACHE 50(例如 GENERATED BY DEFAULT AS IDENTITY (CACHE 50)),意味着每次取一批 50 个 ID 预加载进内存。但 Hibernate 的 allocationSize 若设为 1,就会造成严重不一致:Hibernate 认为每次只用 1 个,而 Oracle 已经预占了 50 个,中间重启或故障可能导致 ID 大幅跳变甚至重复(尤其在 RAC 环境)。
所以务必做到:
- 查清建表 DDL 中
IDENTITY的CACHE值(用SELECT IDENTITY_OPTIONS FROM USER_TAB_IDENTITY_COLS WHERE TABLE_NAME = 'USER_INFO') - 把
@SequenceGenerator.allocationSize设为相同值(如 50) - 如果 DDL 没写
CACHE,默认是CACHE 20(Oracle 12.2+),不是 1
不匹配的后果不是报错,而是 ID 生成逻辑脱节 —— 这类问题在线上压测或高并发场景下才暴露,排查成本极高。


















