Oracle无AUTO_INCREMENT,必须用序列+触发器或RETURNING子句;Statement.RETURN_GENERATED_KEYS在Oracle中无效,需用PreparedStatement配合RETURNING或手动查序列取值。

Oracle没有AUTO_INCREMENT,得用序列+触发器或RETURNING子句
Java里用Statement.RETURN_GENERATED_KEYS直接获取Oracle自增ID会失败——因为Oracle压根不支持MySQL那种原生自增列。常见错误是执行executeUpdate()后调用getGeneratedKeys()返回空结果集,甚至抛SQLException: ORA-01008: not all variables bound(尤其在用了命名参数但没正确绑定时)。
实际可行路径只有两条:一是用序列(SEQUENCE)配合触发器自动赋值,再通过RETURNING子句取回;二是跳过触发器,显式从序列取值再插入。前者更贴近“自增”语义,后者可控性更强。
- 触发器方案要求表上已建好
BEFORE INSERT触发器,且触发器必须用:NEW.id := seq_name.NEXTVAL赋值,不能用SELECT ... INTO否则RETURNING无效 -
RETURNING只支持单行插入,批量插入必须拆成循环或改用addBatch()+ 单独查序列 - JDBC驱动版本需≥12.1,低版本(如11g驱动)对
RETURNING支持不稳定,可能报ORA-00933: SQL command not properly ended
用PreparedStatement + RETURNING子句拿ID(推荐)
这是最接近JDBC标准做法的方式,无需额外查序列,一次SQL完成插入和取ID。关键在于SQL末尾加RETURNING id INTO ?,且PreparedStatement必须声明Statement.RETURN_GENERATED_KEYS参数。
示例代码片段:
String sql = "INSERT INTO users(name, email) VALUES(?, ?) RETURNING id INTO ?";
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, "Alice");
ps.setString(2, "alice@example.com");
ps.execute(); // 注意:这里必须用execute(),不是executeUpdate()
ResultSet rs = ps.getGeneratedKeys();
if (rs.next()) {
long newId = rs.getLong(1); // 取出的ID
}
- 必须用
execute()而非executeUpdate(),否则getGeneratedKeys()返回空 -
RETURNING后的占位符?只能有一个,且类型要匹配目标列(如id是NUMBER,就用getLong()或getBigDecimal()) - Oracle 12c及以上支持
IDENTITY列,但JDBC仍需走RETURNING,不能依赖getGeneratedKeys()自动识别
手动取序列值再插入(兼容性更好)
当无法修改表结构(比如没权限建触发器)或用老版本Oracle时,先查序列再插入是最稳的方案。但要注意序列值不是事务安全的——如果插入失败,序列值已消耗,ID会跳号。
典型写法:
String seqSql = "SELECT user_seq.NEXTVAL FROM DUAL"; PreparedStatement seqPs = conn.prepareStatement(seqSql); ResultSet seqRs = seqPs.executeQuery(); seqRs.next(); long newId = seqRs.getLong(1); String insertSql = "INSERT INTO users(id, name, email) VALUES(?, ?, ?)"; PreparedStatement insertPs = conn.prepareStatement(insertSql); insertPs.setLong(1, newId); insertPs.setString(2, "Bob"); insertPs.setString(3, "bob@example.com"); insertPs.executeUpdate();
- 序列名(如
user_seq)必须与表实际关联的序列一致,查USER_SEQUENCES确认 - 不要用
currval代替nextval,除非刚调过nextval且在同一会话中,否则报ORA-08002: sequence XXX.CURRVAL is not yet defined in this session - 高并发下序列值跳号是正常行为,业务逻辑不能假设ID连续
MyBatis等ORM框架怎么配?
MyBatis里用最直接:keyProperty="id"对应对象字段,order="BEFORE"表示先取序列再插,resultType="long"声明返回类型。
<insert id="insertUser" parametertype="User"><selectkey keyproperty="id" resulttype="long" order="BEFORE">
SELECT user_seq.NEXTVAL FROM DUAL
</selectkey>
INSERT INTO users(id, name, email) VALUES(#{id}, #{name}, #{email})
</insert>
- 若用
order="AFTER",MyBatis底层仍走RETURNING,需确保SQL语法正确且驱动支持 - Hibernate默认不支持Oracle
RETURNING,得配@GeneratedValue(strategy = GenerationType.SEQUENCE)+@SequenceGenerator,本质还是先查序列 - Spring JDBC的
KeyHolder对Oracle支持弱,update()方法传new GeneratedKeyHolder()后常取不到值,建议绕开
Oracle的“自增ID”本质是序列模拟,JDBC层没有银弹。RETURNING简洁但有版本和语法约束,手动取序列啰嗦却稳。别信“设个属性就自动回填”这种说法——驱动不会替你猜序列名,也不会帮你建触发器。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











