Oracle不支持主键自增,MyBatis中useGeneratedKeys="true"无效,必须用selectKey标签配合序列NEXTVAL(order="BEFORE")显式获取ID,否则id字段为null;批量插入时selectKey只执行一次,需预生成ID列表或改用触发器/MyBatis-Plus序列生成器。
oracle 本身不支持主键自增,mybatis 中必须显式获取序列值;直接用 usegeneratedkeys="true" 会失效,且无法返回 id —— 这是绝大多数人踩坑的起点。
为什么不能用 useGeneratedKeys
Oracle 没有类似 MySQL 的 `LAST_INSERT_ID()` 或 SQL Server 的 `SCOPE_IDENTITY()` 机制,useGeneratedKeys 依赖数据库原生自增能力,对 Oracle 完全无效。哪怕你在 标签里硬加上 useGeneratedKeys="true" 和 keyProperty="id",执行后 id 字段仍为 null 或 0,不会自动填充。
- MyBatis 日志里会出现类似
Updates: 1,但无主键回写 - 若同时配置了
selectKey和useGeneratedKeys="true",后者会被忽略,但容易造成逻辑混淆 - MyBatis Plus 中也需禁用
IdType.AUTO,改用IdType.INPUT+ 序列名
推荐做法:selectKey + 序列 NEXTVAL(BEFORE)
这是最稳定、兼容性最好、无需额外数据库对象的方式,适用于单条插入和大多数批量场景(只要不是真·高并发批量插入)。
- 在
内部嵌入,order="BEFORE"确保先取值再插入 -
keyProperty必须与 Java 对象字段名完全一致(如userId),且类型要匹配(resultType="long"或"java.lang.Long") - SQL 必须写成
SELECT seq_user.NEXTVAL FROM DUAL,不能漏掉FROM DUAL,否则 Oracle 报ORA-00923: FROM keyword not found - 示例:
<insert id="insertUser" parametertype="User"><selectkey keyproperty="id" resulttype="long" order="BEFORE">
SELECT seq_user.NEXTVAL FROM DUAL
</selectkey>
INSERT INTO users (id, name) VALUES (#{id}, #{name})
</insert>
批量插入时的序列处理陷阱
MyBatis 的 批量插入中, 只执行一次 —— 这意味着所有记录会拿到同一个序列值,导致唯一约束冲突或数据覆盖。
- 错误写法:
放在外层,然后用插入多条,全部共用一个#{id} - 可行方案一:改用数据库端生成(如触发器),但失去应用层对 ID 的控制权
- 可行方案二:Java 层预生成 ID 列表,调用
seq_user.NEXTVALN 次(可用SELECT seq_user.NEXTVAL FROM DUAL CONNECT BY LEVEL 一次性查出 N 个值) - 可行方案三:用 MyBatis-Plus 的
OracleKeyGenerator配合@KeySequence注解,它内部会按需多次调用序列
序列创建与缓存注意事项
序列不是“开箱即用”的安全组件,缓存(CACHE)和事务行为直接影响 ID 连续性与可靠性。
- 默认
CACHE 20会预分配 20 个值到内存;数据库异常重启会导致跳号(最多丢 20 个) - 如果业务要求严格连续(如财务流水号),必须加
NOCACHE,但性能下降明显 - 避免用
START WITH 0或负数起始,某些 JDBC 驱动对负 ID 解析异常 - 序列名建议统一前缀,如
SEQ_USER,避免和表名、触发器名冲突
真正难的不是怎么写那几行 XML,而是想清楚:你的 ID 是否允许跳号?是否需要全局唯一而非单表唯一?是否要支持分布式部署?这些决定了你该用序列、UUID(sys_guid())、还是雪花算法 —— 而 Oracle 序列,只是其中一种确定性高、成本低、但中心化强的选择。











