杰瑞科技汇

Java如何调用MySQL存储过程?

目录

  1. 准备工作
  2. 调用存储过程的几种主要场景
    • 调用无参数、无返回值的存储过程
    • 调用带 IN 参数的存储过程
    • 调用带 OUT 参数的存储过程
    • 调用带 INOUT 参数的存储过程
    • 调用返回结果集的存储过程
  3. 最佳实践与注意事项
  4. 完整示例代码

准备工作

在开始之前,请确保你已经完成了以下配置:

Java如何调用MySQL存储过程?-图1
(图片来源网络,侵删)

a. MySQL 存储过程示例

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

-- 创建一个测试数据库(如果不存在)
CREATE DATABASE IF NOT EXISTS `test_db` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `test_db`;
-- 创建一个用于演示的表
CREATE TABLE `users` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `name` VARCHAR(50) NOT NULL,
  `email` VARCHAR(100) NOT NULL
);
-- 插入一些测试数据
INSERT INTO `users` (`name`, `email`) VALUES 
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Charlie', 'charlie@example.com');

创建我们需要的存储过程:

-- 场景一:无参数、无返回值,仅执行操作
DELIMITER //
CREATE PROCEDURE `add_user`(IN p_name VARCHAR(50), IN p_email VARCHAR(100))
BEGIN
    INSERT INTO users (name, email) VALUES (p_name, p_email);
END //
DELIMITER ;
-- 场景二:带OUT参数,返回一个值
DELIMITER //
CREATE PROCEDURE `get_user_count`(OUT p_count INT)
BEGIN
    SELECT COUNT(*) INTO p_count FROM users;
END //
DELIMITER ;
-- 场景三:带INOUT参数,修改传入的值
DELIMITER //
CREATE PROCEDURE `format_name`(INOUT p_name VARCHAR(100))
BEGIN
    SET p_name = CONCAT('User: ', UPPER(p_name));
END //
DELIMITER ;
-- 场景四:返回一个结果集
DELIMITER //
CREATE PROCEDURE `get_all_users`()
BEGIN
    SELECT id, name, email FROM users;
END //
DELIMITER ;

b. Java 项目配置

你需要一个 MySQL 的 JDBC 驱动,如果你使用 Maven,在 pom.xml 中添加依赖:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version> <!-- 建议使用较新版本 -->
</dependency>

c. 数据库连接信息

准备好你的数据库连接信息:

Java如何调用MySQL存储过程?-图2
(图片来源网络,侵删)
  • URL: jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC
  • User: your_username
  • Password: your_password

调用存储过程的几种主要场景

在 Java 中,我们使用 java.sql.CallableStatement 来调用存储过程,它类似于 PreparedStatement,但专门用于处理 SQL 存储过程和函数。

调用无参数、无返回值的存储过程

例如调用我们上面创建的 add_user 存储过程。

Java 代码:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.CallableStatement;
public class CallSimpleProcedure {
    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 = "password"; // 你的密码
    public static void main(String[] args) {
        Connection conn = null;
        CallableStatement cstmt = null;
        try {
            // 1. 加载驱动 (对于新版本驱动,可以省略此步)
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 2. 获取数据库连接
            conn = DriverManager.getConnection(DB_URL, USER, PASS);
            // 3. 创建 CallableStatement 对象
            // 语法: {call procedure_name()}
            String sql = "{call add_user(?, ?)}";
            cstmt = conn.prepareCall(sql);
            // 4. 设置 IN 参数
            cstmt.setString(1, "David");
            cstmt.setString(2, "david@example.com");
            // 5. 执行存储过程
            cstmt.execute();
            System.out.println("成功调用 add_user 存储过程,新用户已添加。");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭资源
            try {
                if (cstmt != null) cstmt.close();
                if (conn != null) conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

调用带 IN 参数的存储过程

这与场景一类似,add_user 本身就是带 IN 参数的,上面的代码已经展示了如何设置 IN 参数。

调用带 OUT 参数的存储过程

例如调用 get_user_count 存储过程来获取用户总数。

关键点:

  • 在 SQL 字符串中,用 标记 OUT 参数。
  • 在执行前,必须使用 registerOutParameter(index, sqlType) 注册 OUT 参数。
  • 执行后,通过 getXXX(index) 方法获取 OUT 参数的值。

Java 代码:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.CallableStatement;
public class CallProcedureWithOutParam {
    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 = "password";
    public static void main(String[] args) {
        Connection conn = null;
        CallableStatement cstmt = null;
        try {
            conn = DriverManager.getConnection(DB_URL, USER, PASS);
            // 语法: {call procedure_name(?)},? 代表 OUT 参数
            String sql = "{call get_user_count(?)}";
            cstmt = conn.prepareCall(sql);
            // 注册 OUT 参数 (索引从 1 开始),类型为 INTEGER
            cstmt.registerOutParameter(1, java.sql.Types.INTEGER);
            // 执行
            cstmt.execute();
            // 获取 OUT 参数的值
            int userCount = cstmt.getInt(1);
            System.out.println("当前用户总数为: " + userCount);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭资源...
            try { if (cstmt != null) cstmt.close(); } catch (Exception e) {}
            try { if (conn != null) conn.close(); } catch (Exception e) {}
        }
    }
}

调用带 INOUT 参数的存储过程

例如调用 format_name 存储过程。

关键点:

  • INOUT 参数也需要注册。
  • 必须先设置 IN 值,再注册 INOUT 参数,或者按顺序执行。

Java 代码:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.CallableStatement;
public class CallProcedureWithInOutParam {
    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 = "password";
    public static void main(String[] args) {
        Connection conn = null;
        CallableStatement cstmt = null;
        try {
            conn = DriverManager.getConnection(DB_URL, USER, PASS);
            // 语法: {call procedure_name(?)},? 代表 INOUT 参数
            String sql = "{call format_name(?)}";
            cstmt = conn.prepareCall(sql);
            // 1. 设置 IN 值
            String originalName = "Eve";
            cstmt.setString(1, originalName);
            // 2. 注册 INOUT 参数 (类型为 VARCHAR)
            cstmt.registerOutParameter(1, java.sql.Types.VARCHAR);
            // 3. 执行
            cstmt.execute();
            // 4. 获取返回的 OUT 值
            String formattedName = cstmt.getString(1);
            System.out.println("原始名称: " + originalName);
            System.out.println("格式化后名称: " + formattedName);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭资源...
            try { if (cstmt != null) cstmt.close(); } catch (Exception e) {}
            try { if (conn != null) conn.close(); } catch (Exception e) {}
        }
    }
}

调用返回结果集的存储过程

例如调用 get_all_users 存储过程。

关键点:

  • 使用 cstmt.execute() 执行。
  • 使用 cstmt.getResultSet() 来获取返回的结果集。
  • getResultSet() 只能获取第一个结果集,如果存储过程返回多个结果集,需要循环使用 cstmt.getMoreResults() 并调用 cstmt.getResultSet()

Java 代码:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class CallProcedureWithResultSet {
    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 = "password";
    public static void main(String[] args) {
        Connection conn = null;
        CallableStatement cstmt = null;
        ResultSet rs = null;
        try {
            conn = DriverManager.getConnection(DB_URL, USER, PASS);
            // 语法: {call procedure_name()}
            String sql = "{call get_all_users()}";
            cstmt = conn.prepareCall(sql);
            // 执行
            cstmt.execute();
            // 获取结果集
            rs = cstmt.getResultSet();
            System.out.println("所有用户信息:");
            System.out.println("ID | Name      | Email");
            System.out.println("---------------------------");
            // 遍历结果集
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                String email = rs.getString("email");
                System.out.printf("%d | %-10s | %s\n", id, name, email);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭资源 (顺序很重要: ResultSet -> Statement -> Connection)
            try { if (rs != null) rs.close(); } catch (SQLException e) {}
            try { if (cstmt != null) cstmt.close(); } catch (SQLException e) {}
            try { if (conn != null) conn.close(); } catch (SQLException e) {}
        }
    }
}

最佳实践与注意事项

  1. 关闭资源:数据库连接 (Connection)、语句 (Statement/PreparedStatement/CallableStatement) 和结果集 (ResultSet) 都是有限资源。必须在 finally 块中关闭它们,以防止资源泄漏,关闭顺序是:ResultSet -> Statement -> Connection

  2. 使用 try-with-resources (Java 7+):这是更推荐的方式,它可以自动关闭实现了 AutoCloseable 接口(如 Connection, Statement, ResultSet)的资源,代码更简洁、安全。

    try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
         CallableStatement cstmt = conn.prepareCall(sql)) {
        // ... 执行逻辑 ...
    } catch (SQLException e) {
        e.printStackTrace();
    }
    // conn 和 cstmt 会在这里自动关闭
  3. 处理异常:始终妥善处理 SQLException,不要简单地 printStackTrace(),在生产环境中应该记录日志或向用户展示友好的错误信息。

  4. 防止 SQL 注入:虽然存储过程本身可以减少 SQL 注入的风险,但当你动态拼接存储过程的名称时,仍然存在风险,如果需要动态调用,请确保对存储过程名称进行严格的校验。

  5. 参数索引CallableStatement 中的参数索引从 1 开始,而不是 0,这一点很容易出错。

  6. 明确参数类型:在注册 OUTINOUT 参数时,必须明确指定其 SQL 类型(如 java.sql.Types.INTEGER, java.sql.Types.VARCHAR),以确保数据类型转换的正确性。


完整示例代码(整合版)

下面是一个整合了 try-with-resources 和多种调用方式的完整示例。

import java.sql.*;
import java.util.Scanner;
public class ComprehensiveJdbcExample {
    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 = "password";
    public static void main(String[] args) {
        // 场景一和二:添加用户 (带IN参数)
        addUser("Frank", "frank@example.com");
        // 场景三:获取用户总数 (带OUT参数)
        getUserCount();
        // 场景四:格式化名称 (带INOUT参数)
        formatName("Grace");
        // 场景五:获取所有用户 (返回结果集)
        getAllUsers();
    }
    public static void addUser(String name, String email) {
        // SQL 中的 ? 占位符会自动按顺序匹配 IN 参数
        String sql = "{call add_user(?, ?)}";
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             CallableStatement cstmt = conn.prepareCall(sql)) {
            cstmt.setString(1, name);
            cstmt.setString(2, email);
            cstmt.execute();
            System.out.println("成功添加用户: " + name);
        } catch (SQLException e) {
            System.err.println("添加用户失败: " + e.getMessage());
        }
    }
    public static void getUserCount() {
        String sql = "{call get_user_count(?)}";
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             CallableStatement cstmt = conn.prepareCall(sql)) {
            cstmt.registerOutParameter(1, Types.INTEGER);
            cstmt.execute();
            int count = cstmt.getInt(1);
            System.out.println("当前用户总数: " + count);
        } catch (SQLException e) {
            System.err.println("获取用户总数失败: " + e.getMessage());
        }
    }
    public static void formatName(String name) {
        String sql = "{call format_name(?)}";
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             CallableStatement cstmt = conn.prepareCall(sql)) {
            cstmt.setString(1, name); // 设置 IN 值
            cstmt.registerOutParameter(1, Types.VARCHAR); // 注册 INOUT 参数
            cstmt.execute();
            String formattedName = cstmt.getString(1);
            System.out.println("格式化名称 '" + name + "' -> '" + formattedName + "'");
        } catch (SQLException e) {
            System.err.println("格式化名称失败: " + e.getMessage());
        }
    }
    public static void getAllUsers() {
        String sql = "{call get_all_users()}";
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             CallableStatement cstmt = conn.prepareCall(sql);
             ResultSet rs = cstmt.executeQuery()) { // executeQuery() 也可以用于只返回结果集的存储过程
            System.out.println("\n--- 所有用户列表 ---");
            while (rs.next()) {
                System.out.printf("ID: %d, Name: %s, Email: %s\n",
                        rs.getInt("id"),
                        rs.getString("name"),
                        rs.getString("email"));
            }
            System.out.println("--------------------\n");
        } catch (SQLException e) {
            System.err.println("获取所有用户失败: " + e.getMessage());
        }
    }
}

希望这份详细的指南能帮助你完全掌握在 Java 中调用 MySQL 存储过程!

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