ORA-12704 根本原因是存储过程内部SQL中混用不同字符集(如VARCHAR2与NVARCHAR2),典型场景包括CASE、UNION ALL、字符串拼接未统一加N前缀或类型转换;需通过DBMS_OUTPUT分段调试定位,优先统一为NCHAR修复。
ORA-12704 出现在存储过程中,根本不是参数传错
oracle 存储过程里报 ora-12704: 字符集不匹配,90% 不是 java 传参或 mybatis 配置问题,而是存储过程内部 sql(比如 select ... union all、case、decode 或字符串拼接)混用了不同字符集的列或字面量。jdbc 层还没开始绑定参数,sql 就在数据库端编译失败了。
典型诱因:
-
VARCHAR2列和NVARCHAR2字面量直接比较(如CASE WHEN col = N'中文' THEN ...) - 两个表的同名字段字符集不一致,又在存储过程中做
UNION ALL - 动态拼接 SQL 时,硬编码字符串没加
N前缀,而目标列是NVARCHAR2
查清哪一行 SQL 触发 ORA-12704 的最快方法
别急着改代码,先定位具体语句。在 PL/SQL Developer / SQL*Plus 中执行存储过程前,加这三行:
ALTER SESSION SET NLS_LANG=AMERICAN_AMERICA.AL32UTF8; SET SERVEROUTPUT ON; EXEC DBMS_OUTPUT.ENABLE(1000000);
然后在存储过程里每段关键 SQL 前加 DBMS_OUTPUT.PUT_LINE('debug: step X');。运行后看错误前最后输出哪一行,基本就是罪魁祸首。
更准的办法:用 SELECT * FROM USER_ERRORS WHERE NAME = 'YOUR_PROC_NAME'; 查编译错误位置,但注意——它只报语法/类型错误,不报运行时字符集冲突;所以必须结合手动分段调试。
修复 CASE / UNION ALL 类语句的字符集对齐
核心原则:参与比较、拼接、合并的所有字符串值,字符集必须统一。优先转成 NCHAR(即 Unicode),而不是降级为 VARCHAR2。
常见写法对比:
- 错:
CASE WHEN status = 'ACTIVE' THEN '启用' ELSE '禁用' END(如果status是NVARCHAR2,字面量默认是VARCHAR2) - 对:
CASE WHEN status = N'ACTIVE' THEN N'启用' ELSE N'禁用' END(全部加N前缀) - 错:
SELECT name FROM t1 UNION ALL SELECT name FROM t2(t1.name是VARCHAR2(100),t2.name是NVARCHAR2(100)) - 对:
SELECT TO_NCHAR(name) FROM t1 UNION ALL SELECT name FROM t2(统一转NCHAR)
Java 调用侧也要防一手:避免 JDBC 自动降级
即使存储过程内部 SQL 修复了,Java 端传入的字符串如果被 JDBC 驱动误判为 VARCHAR2,仍可能在绑定阶段触发隐式转换失败。尤其当参数是 NVARCHAR2 类型时:
- MyBatis 中必须显式写
#{param,jdbcType=NVARCHAR},不能只写#{param} - 原生 JDBC 使用
CallableStatement.setString()前,先调用setFormOfUse(1, OracleResultSetMetaData.FORM_OF_USE_NCHAR)(需 cast 到OracleCallableStatement) - Spring JDBC 的
SimpleJdbcCall需注册SqlParameter时指定sqlType = Types.NVARCHAR
真正容易被忽略的是:存储过程里的 SQL 报 ORA-12704,和 Java 参数类型无关,但排查时所有人都会先翻 MyBatis 配置——结果浪费两小时才发现是 CASE 里少了个 N。


















