杰瑞科技汇

java调用mysql存储过程

目录

  1. 准备工作
  2. 存储过程示例 (MySQL)
  3. Java 调用步骤
  4. 完整代码示例
    • 示例1:调用无参存储过程
    • 示例2:调用带 IN 参数的存储过程
    • 示例3:调用带 OUT 参数的存储过程
    • 示例4:调用带 INOUT 参数的存储过程
  5. 最佳实践与注意事项

准备工作

在开始之前,请确保你已经:

java调用mysql存储过程-图1
(图片来源网络,侵删)
  • Java 环境: JDK 已安装并配置好。
  • MySQL 数据库: MySQL 服务正在运行。
  • MySQL JDBC 驱动: 下载了对应你 MySQL 版本的 JDBC 驱动 (JAR 文件),并将其添加到你的 Java 项目的 classpath 中,如果你使用 Maven,可以直接在 pom.xml 中添加依赖。
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version> <!-- 请使用最新稳定版 -->
    </dependency>

存储过程示例 (MySQL)

为了演示,我们在 MySQL 数据库中创建几个不同类型的存储过程。

假设我们有一个数据库 test_db 和一张表 users:

CREATE DATABASE IF NOT EXISTS test_db;
USE test_db;
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50),
    email VARCHAR(50),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入一些测试数据
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');

我们创建几个存储过程:

-- 示例1: 无参,查询所有用户并返回结果集
DELIMITER //
CREATE PROCEDURE get_all_users()
BEGIN
    SELECT id, name, email FROM users;
END //
DELIMITER ;
-- 示例2: 带 IN 参数,根据ID查询用户
DELIMITER //
CREATE PROCEDURE get_user_by_id(IN p_id INT)
BEGIN
    SELECT id, name, email FROM users WHERE id = p_id;
END //
DELIMITER ;
-- 示例3: 带 OUT 参数,根据ID获取用户名
DELIMITER //
CREATE PROCEDURE get_user_name_by_id(IN p_id INT, OUT p_name VARCHAR(50))
BEGIN
    SELECT name INTO p_name FROM users WHERE id = p_id;
END //
DELIMITER ;
-- 示例4: 带 INOUT 参数,增加用户年龄(这里我们用name_length代替)
DELIMITER //
CREATE PROCEDURE process_name_length(INOUT p_name_length INT)
BEGIN
    -- 这里我们只是简单地将输入的长度乘以2作为返回
    SET p_name_length = p_name_length * 2;
END //
DELIMITER ;

Java 调用步骤

在 Java 中调用存储过程的核心是 java.sql.CallableStatement,基本步骤如下:

java调用mysql存储过程-图2
(图片来源网络,侵删)
  1. 加载并注册 JDBC 驱动 (对于较新版本的驱动,这一步通常是可选的)。
  2. 建立数据库连接 (Connection)。
  3. 创建 CallableStatement 对象:使用 Connection.prepareCall() 方法,并传入调用存储过程的 SQL 字符串。
    • 调用语法:{call procedure_name(?, ?, ...)}
    • 代表参数占位符。
    • 对于 OUT 和 INOUT 参数,需要在 前面加上 OUTINOUT 关键字,{call procedure_name(?, OUT ?)}
  4. 设置参数:根据参数类型,使用 CallableStatement 的相应方法设置参数值。
    • IN 参数: setXxx(index, value)
    • OUT 参数: registerOutParameter(index, sqlType)
    • INOUT 参数: 先 setXxx()registerOutParameter()
  5. 执行存储过程:使用 execute() 方法。
  6. 处理结果
    • 如果存储过程返回结果集 (ResultSet),使用 statement.getResultSet() 获取。
    • 如果存储过程返回 OUT 参数,使用 getXxx(index) 获取。
  7. 关闭资源:按照 ResultSet -> CallableStatement -> Connection 的顺序关闭。

完整代码示例

这里我们使用 try-with-resources 语句来自动管理 Connection, CallableStatement, 和 ResultSet,这是一种推荐的最佳实践。

示例1:调用无参存储过程 (get_all_users)

这个存储过程返回一个结果集。

import java.sql.*;
public class CallStoredProcedureExample {
    // 数据库连接信息 (请根据你的实际情况修改)
    private static final String DB_URL = "jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC";
    private static final String USER = "root";
    private static final String PASS = "your_password";
    public static void main(String[] args) {
        // try-with-resources 确保 Connection, CallableStatement, ResultSet 自动关闭
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             CallableStatement cstmt = conn.prepareCall("{call get_all_users()}")) {
            System.out.println("正在调用 get_all_users 存储过程...");
            // 执行存储过程
            cstmt.execute();
            // 获取结果集
            ResultSet rs = cstmt.getResultSet();
            // 处理结果集
            System.out.println("ID\tName\tEmail");
            System.out.println("---------------------------");
            while (rs.next()) {
                // 通过列名获取数据,更健壮
                int id = rs.getInt("id");
                String name = rs.getString("name");
                String email = rs.getString("email");
                System.out.println(id + "\t" + name + "\t" + email);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

示例2:调用带 IN 参数的存储过程 (get_user_by_id)

这个存储过程接收一个输入,并返回一个结果集。

// ... (DB_URL, USER, PASS 同上)
public static void callProcedureWithInParam(int userId) {
    String sql = "{call get_user_by_id(?)}"; // ? 是 IN 参数占位符
    try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
         CallableStatement cstmt = conn.prepareCall(sql)) {
        // 设置 IN 参数 (参数索引从1开始)
        cstmt.setInt(1, userId);
        System.out.println("正在调用 get_user_by_id 存储过程,ID: " + userId);
        // 执行并获取结果
        ResultSet rs = cstmt.executeQuery(); // executeQuery() 适用于只返回结果集的情况
        System.out.println("ID\tName\tEmail");
        System.out.println("---------------------------");
        if (rs.next()) {
            System.out.println(rs.getInt("id") + "\t" + rs.getString("name") + "\t" + rs.getString("email"));
        } else {
            System.out.println("未找到ID为 " + userId + " 的用户。");
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

示例3:调用带 OUT 参数的存储过程 (get_user_name_by_id)

这个存储过程接收一个 IN 参数,并通过一个 OUT 参数返回值。

// ... (DB_URL, USER, PASS 同上)
public static void callProcedureWithOutParam(int userId) {
    String sql = "{call get_user_name_by_id(?, ?)}"; // 第一个 ? 是 IN,第二个 ? 是 OUT
    try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
         CallableStatement cstmt = conn.prepareCall(sql)) {
        // 设置 IN 参数
        cstmt.setInt(1, userId);
        // 注册 OUT 参数 (参数索引从1开始,类型为 VARCHAR)
        cstmt.registerOutParameter(2, Types.VARCHAR);
        System.out.println("正在调用 get_user_name_by_id 存储过程,ID: " + userId);
        // 执行存储过程
        cstmt.execute();
        // 获取 OUT 参数的值
        String userName = cstmt.getString(2); // 通过索引获取
        System.out.println("用户ID " + userId + " 的名字是: " + (userName != null ? userName : "未找到"));
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

示例4:调用带 INOUT 参数的存储过程 (process_name_length)

这个存储过程接收一个值,处理它,然后返回一个新值。

// ... (DB_URL, USER, PASS 同上)
public static void callProcedureWithInOutParam() {
    String initialName = "Charlie";
    int initialLength = initialName.length();
    String sql = "{call process_name_length(?)}"; // 这个 ? 是 INOUT 参数
    try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
         CallableStatement cstmt = conn.prepareCall(sql)) {
        // 设置 INOUT 参数的初始值
        cstmt.setInt(1, initialLength);
        // 注册 INOUT 参数 (类型为 INTEGER)
        cstmt.registerOutParameter(1, Types.INTEGER);
        System.out.println("正在调用 process_name_length 存储过程,初始长度: " + initialLength);
        // 执行存储过程
        cstmt.execute();
        // 获取 INOUT 参数处理后的值
        int processedLength = cstmt.getInt(1);
        System.out.println("处理后的长度: " + processedLength);
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

最佳实践与注意事项

  1. 使用 try-with-resources:始终确保数据库资源(Connection, Statement, ResultSet)被正确关闭,以避免内存泄漏和连接耗尽。try-with-resources 是最简单、最安全的方式。

  2. 防止 SQL 注入CallableStatementPreparedStatement 一样,使用参数化查询( 占位符)来传递参数,这可以有效地防止 SQL 注入攻击。永远不要使用字符串拼接来构建 SQL 调用语句

  3. 区分 execute()executeQuery()

    • execute():可以执行任何 SQL 语句,如果第一个结果是 ResultSet 对象,则返回 true;如果第一个结果是更新计数或没有结果,则返回 false,适用于存储过程可能返回多种结果的情况。
    • executeQuery():只能执行返回单个 ResultSet 对象的 SQL 语句(如 SELECT 或某些存储过程),如果用它执行不返回结果集的 SQL,会抛出异常,对于只返回结果集的存储过程,使用 executeQuery() 更简洁。
  4. 处理多个结果集:有些存储过程可能返回多个结果集,可以通过 boolean hasResultSet = cstmt.execute(); 循环调用 cstmt.getResultSet()cstmt.getUpdateCount() 来处理所有结果。

  5. 明确指定 OUT 参数类型:在注册 OUT 参数时(registerOutParameter),必须明确指定 SQL 类型(如 Types.INTEGER, Types.VARCHAR),如果类型不匹配,可能会导致运行时错误或数据转换问题。

  6. 异常处理:妥善处理 SQLException,记录详细的错误信息,以便于调试。

  7. 连接管理:在实际项目中,不要在每次请求时都创建和销毁数据库连接,应该使用数据库连接池(如 HikariCP, Druid)来管理连接,以提高性能和资源利用率。

分享:
扫描分享到社交APP
上一篇
下一篇