MyBatis调用MySQL存储过程OUT参数为null,根本原因是MySQL不支持JDBC显式OUT绑定,MyBatis的mode=OUT配置被忽略;正确做法是改用CALL后紧跟SELECT @var方式获取值,并确保同一连接中执行。

MyBatis 调用 MySQL 存储过程时输出参数映射为 null,绝大多数情况不是 MySQL 本身的问题,而是 MyBatis 绑定机制与 MySQL JDBC 驱动行为不匹配导致的——MySQL 不像 Oracle 那样强制要求显式注册 OUT 参数,但 MyBatis 仍按 Oracle 模式解析 mode 和 jdbcType,结果参数被跳过或类型误判。
MySQL 存储过程 OUT 参数不走 registerOutParameter 流程
MySQL Connector/J(8.0+)对存储过程 OUT 参数的处理是“隐式返回”,即通过 SELECT @out_var 或直接在 CALL 后查用户变量。MyBatis 默认不会触发 JDBC 的 registerOutParameter(),所以即使你在 XML 里写了 mode=OUT,驱动也根本不识别这个绑定意图。
- MySQL 存储过程声明
OUT p_msg VARCHAR(255),实际执行后需靠SELECT @p_msg获取值,而不是 JDBC 的 out parameter 机制 - MyBatis 的
mode=OUT仅对 Oracle/SQL Server 等显式支持 OUT 绑定的数据库有效;对 MySQL,该配置被忽略,params.get("p_msg")必然为null - 验证方式:开启 DEBUG 日志,搜索
Binding to parameter,若p_msg完全没出现,说明 MyBatis 根本没尝试绑定它
正确做法:改用 SELECT 查询用户变量
MySQL 下必须绕过 MyBatis 的 OUT 绑定逻辑,改用标准 SQL 查询方式读取用户变量值。这需要两步配合:
- 在
CALL语句后立即执行SELECT @p_msg AS p_msg,并确保在同一连接、同一事务中(否则变量丢失) - Mapper 中用
<select>标签封装整个流程,不要用<procedure>或statementType="CALLABLE" - 示例写法:
<select id="callProcWithOut" resultType="map"> CALL proc_test(@in_param, @out_msg); SELECT @out_msg AS outMsg; </select>
- Java 调用后从返回的
Map取outMsg字段,而非依赖mode=OUT注入
为什么 jdbcType=VARCHAR 写了也无效
因为 MyBatis 对 MySQL 的 mode=OUT 解析根本不会走到 JdbcType.valueOf() 这一步——它先判断数据库厂商类型,发现是 MySQL 就直接跳过 OUT 参数注册逻辑。所以即使你写成 #{p_msg, mode=OUT, jdbcType=VARCHAR},也不会报错,但也不会生效。
- 错误现象:
params.get("p_msg")返回null,且日志里完全看不到该参数的 binding 记录 - 对比 Oracle:Oracle 驱动会严格校验
mode大小写和jdbcType枚举值,错一个就静默跳过;MySQL 是直接不进这个分支 - 别试图加
javaType或resultMap补救——只要没真正执行SELECT @xxx,变量值就不存在于结果集中
INOUT 参数要特别注意变量生命周期
MySQL 用户变量是会话级的,INOUT 场景下容易因并发或连接复用导致值污染。比如两个线程共用一个连接池连接,先后调用带 @var 的存储过程,后者可能读到前者留下的旧值。
- 务必在每次
CALL前用SET @var = NULL初始化 - 避免跨方法复用同一个变量名,例如统一加前缀:
@proc_user_id_out - 如果业务允许,优先改用函数(
FUNCTION)替代存储过程,直接返回标量值,无需用户变量中转
MySQL 的 OUT 参数本质是用户变量模拟,MyBatis 不提供原生支持,硬套 Oracle 配置只会让问题更隐蔽。最稳的方式就是放弃 mode=OUT,老老实实写 CALL + SELECT @xxx 两步查询,并控制好变量作用域。


















