最稳妥方式是用在insert前查序列,order="before"、resulttype指定为long、sql为select seq.nextval from dual、keyproperty与实体字段名严格一致。

MyBatis里用selectKey获取Oracle序列值最稳妥
Oracle没有自增主键,必须显式取序列值再插入。MyBatis不支持Oracle的RETURNING语法(如INSERT ... RETURNING id INTO ?),所以不能像PostgreSQL或MySQL那样靠驱动自动回填。最通用、兼容性最好的方式是用<selectkey></selectkey>在INSERT前查序列。
常见错误是把selectKey放在INSERT后——Oracle中序列值必须先拿到才能作为主键插入,否则会报ORA-01400: cannot insert NULL into ("TABLE"."ID");另一个坑是用了ORDER="AFTER"还设KEYPROPERTY,结果主键字段仍是null。
-
ORDER="BEFORE",确保在INSERT语句执行前完成序列查询 -
RESULTTYPE="java.lang.Long"(或Integer),避免类型不匹配导致赋值失败 - SQL写
SELECT seq_user_id.NEXTVAL FROM DUAL,别漏FROM DUAL,否则Oracle报ORA-00923: FROM keyword not found -
KEYPROPERTY必须和实体类字段名严格一致(比如userId对应private Long userId;)
用@SelectKey注解时注意方法签名和执行时机
纯注解方式容易忽略执行顺序和返回值绑定。如果在@Insert方法上加@SelectKey,必须同时指定statement、keyProperty、before = true,且该方法的返回类型不能是void——MyBatis需要从方法调用中提取生成的ID。
- 方法需声明返回类型为
Long(或对应包装类型),即使你没用返回值 -
keyProperty填的是参数对象里的字段名,不是数据库列名,例如keyProperty = "id"对应new User().setId(...) - Oracle下
statement数组第一项必须是"SELECT seq_user.NEXTVAL FROM DUAL",不能写成"VALUES seq_user.NEXTVAL" - 别在同一个方法上既用
@SelectKey又用@Options(useGeneratedKeys = true),后者对Oracle无效,还会干扰前者
批量插入时别用selectKey逐条取序列
如果一次插1000条记录,每条都走一遍SELECT seq.NEXTVAL FROM DUAL,性能会断崖式下跌——不是因为SQL慢,而是网络往返和事务开销叠加。Oracle序列本身支持缓存(CACHE 20),但MyBatis无法复用单次查询结果给多条记录。
- 批量场景优先改用
SELECT seq.NEXTVAL FROM DUAL CONNECT BY LEVEL 一次性查出N个值,再在Java层分配 - 或者改用
INSERT ALL配合序列:用SELECT seq.NEXTVAL FROM DUAL查一次,在循环拼INTO table VALUES (seq_val, ...),但要注意SQL长度限制 - 更现实的做法是:业务允许的话,用UUID或雪花算法生成ID,彻底绕开序列瓶颈
MyBatis-Plus用户注意@TableId(type = IdType.AUTO)对Oracle无效
这个配置只对MySQL/PostgreSQL的AUTO_INCREMENT/SERIAL生效。Oracle下设了也白设,插入后id仍是null,而且不会抛异常,容易埋雷到运行时才发现。
- Oracle项目必须显式配
type = IdType.INPUT,并在插入前手动赋值entity.setId(sequenceService.nextVal()) - 如果坚持用MP的自动填充,得自己实现
IdentifierGenerator,重写nextId()方法调用JDBC查NEXTVAL - 别信文档里“支持Oracle”的模糊描述——MP底层还是走
useGeneratedKeys,而Oracle JDBC驱动根本不支持该特性
序列值获取看着简单,但Oracle的事务隔离、序列缓存、MyBatis执行阶段耦合度高,稍不注意就会出现ID重复、空指针或性能毛刺。重点盯住ORDER属性、KEYPROPERTY绑定和批量逻辑这三处,比调优SQL本身更关键。











