前言

在实际企业开发中,经常会遇到使用存储过程处理复杂业务逻辑的场景(比如多表联查、数据统计、事务性批量操作)。MyBatis 作为主流的 ORM 框架,完美支持存储过程调用,但很多新手对如何接收存储过程的 OUT/INOUT 输出参数感到困惑。

本文将从存储过程编写、MyBatis 配置、参数接收、实战案例全流程讲解,带你彻底掌握 MyBatis 接收存储过程 OUTPUT 参数的方法。

在 Java 企业开发中,MyBatis 调用数据库存储过程接收 OUT/INOUT 输出参数是高频场景,本文基于纯 Java + MyBatis环境,从零到一实现存储过程调用、输出参数接收,包含 MySQL 存储过程编写、Mapper 接口、XML 配置、Java 测试全流程,代码可直接复制运行。

一、核心要点(必看)

  1. MyBatis 调用存储过程必须配置:statementType="CALLABLE"
  2. 输出参数用 mode=OUT 声明,必须指定 jdbcType
  3. Java 接收方式:实体类 / Map 集合(推荐实体类,更规范)
  4. 存储过程执行后,输出参数会自动回写到入参对象中

二、环境准备

  1. MySQL 5.7/8.0
  2. MyBatis 3.x
  3. JDK 1.8+
  4. 测试表:

sql

CREATE TABLE `user` (
  `id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
  `username` VARCHAR(50) NOT NULL COMMENT '用户名',
  `age` INT COMMENT '年龄'
);

-- 插入测试数据
INSERT INTO `user`(username, age) VALUES ('张三', 22), ('李四', 25);

三、创建 MySQL 存储过程(带 IN/OUT 参数)

创建一个根据用户 ID 查询信息,并返回输出参数的存储过程:

  • 入参:p_id(用户 ID)
  • 出参:p_username(用户名)、p_age(年龄)、p_msg(执行提示)

sql

-- 创建存储过程
DELIMITER //
CREATE PROCEDURE proc_get_user(
    IN  p_id INT,           -- 入参:用户ID
    OUT p_username VARCHAR(50),  -- 出参:用户名
    OUT p_age INT,          -- 出参:年龄
    OUT p_msg VARCHAR(100)  -- 出参:执行结果
)
BEGIN
    -- 查询用户数据赋值给输出参数
    SELECT username, age INTO p_username, p_age FROM `user` WHERE id = p_id;
    
    -- 判断是否查询到数据
    IF p_username IS NOT NULL THEN
        SET p_msg = CONCAT('查询成功,用户ID:', p_id);
    ELSE
        SET p_msg = CONCAT('用户ID:', p_id, ' 不存在');
        SET p_age = 0;
    END IF;
END //
DELIMITER ;

四、Java 代码实现

1. 实体类(接收输入 / 输出参数)

用于封装入参出参,存储过程执行后,输出参数会自动赋值到该对象:

java

运行

/**
 * 用户参数实体(接收存储过程IN/OUT参数)
 */
public class UserParam {
    // 入参:用户ID
    private Integer id;
    // 出参:用户名
    private String username;
    // 出参:年龄
    private Integer age;
    // 出参:执行结果信息
    private String msg;

    // getter/setter 必须写,MyBatis需要反射赋值
    public Integer getId() {return id;}
    public void setId(Integer id) {this.id = id;}
    public String getUsername() {return username;}
    public void setUsername(String username) {this.username = username;}
    public Integer getAge() {return age;}
    public void setAge(Integer age) {this.age = age;}
    public String getMsg() {return msg;}
    public void setMsg(String msg) {this.msg = msg;}

    // 重写toString,方便打印结果
    @Override
    public String toString() {
        return "UserParam{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", age=" + age +
                ", msg='" + msg + '\'' +
                '}';
    }
}

2. Mapper 接口(定义调用方法)

java

运行

import org.apache.ibatis.annotations.Param;

public interface UserMapper {
    /**
     * 调用存储过程,接收OUT输出参数
     * @param userParam 入参+出参封装对象
     */
    void callUserProc(UserParam userParam);
}

3. MyBatis XML 映射文件(核心配置)

关键statementType="CALLABLE" + mode=OUT 声明输出参数

xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- 对应Mapper接口全路径 -->
<mapper namespace="com.mapper.UserMapper">

    <!-- 调用存储过程:接收OUT参数 -->
    <select id="callUserProc" statementType="CALLABLE" parameterType="com.entity.UserParam">
        {
            CALL proc_get_user(
                #{id, mode=IN, jdbcType=INTEGER},
                #{username, mode=OUT, jdbcType=VARCHAR},
                #{age, mode=OUT, jdbcType=INTEGER},
                #{msg, mode=OUT, jdbcType=VARCHAR}
            )
        }
    </select>

</mapper>

4. Java 测试代码(调用 + 接收结果)

直接运行,即可获取存储过程的OUT 输出参数

java

运行

import com.entity.UserParam;
import com.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;

public class ProcTest {
    public static void main(String[] args) throws IOException {
        // 1. 加载MyBatis配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 2. 获取SqlSession
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 3. 获取Mapper接口
            UserMapper userMapper = session.getMapper(UserMapper.class);

            // 4. 封装入参
            UserParam param = new UserParam();
            param.setId(1); // 查询ID=1的用户

            // 5. 调用存储过程(执行后,输出参数自动回写到param对象中)
            userMapper.callUserProc(param);

            // 6. 接收并打印OUT输出参数
            System.out.println("=====存储过程执行结果=====");
            System.out.println("用户名:" + param.getUsername());
            System.out.println("年龄:" + param.getAge());
            System.out.println("执行信息:" + param.getMsg());
            System.out.println("完整对象:" + param);
        }
    }
}

五、执行结果

plaintext

=====存储过程执行结果=====
用户名:张三
年龄:22
执行信息:查询成功,用户ID:1
完整对象:UserParam{id=1, username='张三', age=22, msg='查询成功,用户ID:1'}

六、进阶:Map 接收 OUT 参数(简化版)

如果不想创建实体类,可直接用Map接收,无需定义实体:

1. Mapper 接口

java

运行

void callUserProcMap(Map<String, Object> paramMap);

2. XML 配置

xml

<select id="callUserProcMap" statementType="CALLABLE" parameterType="map">
    {
        CALL proc_get_user(
            #{id, mode=IN, jdbcType=INTEGER},
            #{username, mode=OUT, jdbcType=VARCHAR},
            #{age, mode=OUT, jdbcType=INTEGER},
            #{msg, mode=OUT, jdbcType=VARCHAR}
        )
    }
</select>

3. Java 测试

java

运行

Map<String, Object> paramMap = new HashMap<>();
paramMap.put("id", 1);
userMapper.callUserProcMap(paramMap);

// 接收OUT参数
System.out.println("用户名:" + paramMap.get("username"));
System.out.println("执行信息:" + paramMap.get("msg"));

七、常见坑(避坑指南)

  1. 必须加 statementType="CALLABLE":否则 MyBatis 会把存储过程当普通 SQL 执行,报错
  2. OUT 参数必须指定 jdbcType:不指定会报类型不匹配错误
  3. 参数顺序必须和存储过程一致:入参、出参顺序不能乱
  4. 输出参数自动回写:无需接收返回值,直接从入参对象 / Map 中取
  5. Oracle 注意:输出参数游标需指定 jdbcType=CURSORjavaType=ResultSet

八、总结

  1. MyBatis 调用存储过程核心:statementType="CALLABLE"
  2. 接收 OUT 参数:mode=OUT + jdbcType
  3. Java 接收方案:实体类(规范) / Map(简洁)
  4. 执行后输出参数自动回写到入参对象,直接取值即可
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐