oracle动态sql需严守语法洁癖:空where须用trim+1=1兜底;字符串拼接禁用concat多参,改用||;set末尾逗号需trim suffixoverrides处理;分页必须rownum嵌套,不可用limit或rowbounds。

Oracle 语法限制多,MyBatis 动态 SQL 拼接稍有不慎就会报 ORA-00936: missing expression 或 ORA-00907: missing right parenthesis —— 根本原因不是逻辑写错,而是 Oracle 对空条件、函数用法、字符串拼接比 MySQL 更敏感。
Oracle 中 <where></where> 的边界行为必须手动兜底
Oracle 不允许出现孤立的 WHERE 子句(比如所有 <if></if> 都不满足时,<where></where> 会生成空 WHERE,直接报错)。MyBatis 的 <where></where> 在 Oracle 下不会自动跳过,它仍会插入 WHERE 关键字。
- 错误写法:
<where><if test="id != null">id = #{id}</if></where>→ 若id为null,实际执行SELECT * FROM t WHERE,Oracle 立即报错 - 正确做法:改用
<trim prefix="WHERE" prefixoverrides="AND |OR "></trim>,并确保至少有一个条件“保底” - 更稳妥的写法是加一个恒真条件:
WHERE 1 = 1 <trim prefix="" suffixoverrides="AND">...</trim>,但注意1 = 1在 Oracle 中无性能问题,且能彻底规避空WHERE
CONCAT() 和字符串拼接在 Oracle 中不能乱用
MySQL 的 CONCAT('%', #{name}, '%') 在 Oracle 里不支持多参数,会报 ORA-00909: invalid number of arguments;必须改用双竖线 || 或 CONCAT(CONCAT('%', #{name}), '%')(嵌套调用)。
- 推荐统一用
#{name} || '%'或'%' || #{name} || '%',Oracle 原生支持,语义清晰 - 避免混用:不要在同一个
<if></if>里一边用||一边用CONCAT,容易漏括号 - 如果字段是
CHAR类型(定长),记得用RTRIM(#{name}),否则模糊查询可能因尾部空格失效
<set></set> 更新时 Oracle 对末尾逗号更严格
Oracle 的 UPDATE 语句中,SET 后若生成了形如 SET name = ?, age = ? ,(末尾多逗号),会直接报 ORA-00936。而 MyBatis 的 <set></set> 虽能删前缀,但对结尾逗号无感知。
- 务必确认每个
<if></if>内部不带结尾逗号,例如写成name = #{name},而不是name = #{name}, - 更新语句建议显式用
<trim prefix="SET" suffixoverrides=","></trim>替代<set></set>,更可控 - 如果更新字段含序列(如
SEQ.NEXTVAL),别放进<if></if>,应单独处理或用触发器,否则动态逻辑会干扰序列调用
Oracle 分页必须用 ROWNUM 套壳,不能依赖 LIMIT
MyBatis 自带的 RowBounds 在 Oracle 上会退化为全表扫描后内存分页,数据量一过万就卡死。真实项目必须手写 ROWNUM 嵌套子查询。
- 错误示范:
SELECT * FROM user WHERE status = 1 LIMIT 10 OFFSET 20→ Oracle 不识别LIMIT - 正确结构:
SELECT * FROM (SELECT ROWNUM rn, t.* FROM (原查询) t WHERE ROWNUM #{startRow} - 注意:外层
ROWNUM别名(如rn)必须显式声明,否则 Oracle 报ORA-00904: "RN": invalid identifier - 如果你用的是 MyBatis Plus,
Page<t></t>默认走RowBounds,需配置OracleDialect或自定义分页插件
Oracle 动态 SQL 最容易翻车的地方,不在逻辑分支,而在语法洁癖——它不宽容任何“看起来差不多”的写法。空 WHERE、漏括号、多逗号、ROWNUM 位置错一层,全挂。写完务必用日志打出最终 SQL,在 SQL*Plus 里粘贴执行一遍,才是最省时间的验证方式。











