杰瑞科技汇

Java如何调用MySQL存储过程?

  1. MySQL 端:创建存储过程
  2. Java 端:编写 JDBC 代码调用存储过程
  3. 处理不同类型的存储过程(无参数、有参数、有返回结果集)
  4. 最佳实践和注意事项

第1步:在 MySQL 中创建存储过程

为了演示,我们创建几个不同类型的存储过程。

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

简单无参存储过程 这个存储过程只是简单地返回 "Hello, World!" 字符串。

-- 删除已存在的存储过程(避免重复创建时出错)
DROP PROCEDURE IF EXISTS hello_world;
-- 创建存储过程
DELIMITER //
CREATE PROCEDURE hello_world()
BEGIN
    SELECT 'Hello, World from MySQL Stored Procedure!' AS message;
END //
DELIMITER ;

带输入参数的存储过程 这个存储过程根据员工 ID 查询员工信息。

-- 假设我们有一个 employees 表
CREATE TABLE IF NOT EXISTS employees (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    department VARCHAR(100),
    salary DECIMAL(10, 2)
);
INSERT INTO employees (id, name, department, salary) VALUES
(1, 'Alice', 'Engineering', 9000.00),
(2, 'Bob', 'Marketing', 7500.50),
(3, 'Charlie', 'Engineering', 11000.00);
-- 删除已存在的存储过程
DROP PROCEDURE IF EXISTS get_employee_by_id;
-- 创建存储过程
DELIMITER //
CREATE PROCEDURE get_employee_by_id(IN p_id INT)
BEGIN
    SELECT id, name, department, salary
    FROM employees
    WHERE id = p_id;
END //
DELIMITER ;

带输出参数的存储过程 这个存储过程根据部门名称,返回该部门的平均薪资。

-- 删除已存在的存储过程
DROP PROCEDURE IF EXISTS get_avg_salary_by_dept;
-- 创建存储过程
DELIMITER //
CREATE PROCEDURE get_avg_salary_by_dept(IN p_dept_name VARCHAR(100), OUT p_avg_salary DECIMAL(10, 2))
BEGIN
    SELECT AVG(salary) INTO p_avg_salary
    FROM employees
    WHERE department = p_dept_name;
END //
DELIMITER ;

返回结果集的存储过程 这个存储过程返回指定部门的所有员工,注意,MySQL 中直接返回结果集的存储过程不需要特殊的 OUT 参数,直接 SELECT 即可。

Java如何调用MySQL存储过程?-图2
(图片来源网络,侵删)
-- 删除已存在的存储过程
DROP PROCEDURE IF EXISTS get_employees_by_dept;
-- 创建存储过程
DELIMITER //
CREATE PROCEDURE get_employees_by_dept(IN p_dept_name VARCHAR(100))
BEGIN
    SELECT id, name, department, salary
    FROM employees
    WHERE department = p_dept_name;
END //
DELIMITER ;

第2步:在 Java 中调用存储过程

在 Java 中,我们使用 JDBC (Java Database Connectivity) API 来调用存储过程,核心是 CallableStatement 接口。

准备工作

确保你的项目中已经添加了 MySQL JDBC 驱动的依赖。

Maven (pom.xml):

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.0.33</version> <!-- 使用合适的版本 -->
</dependency>

Gradle (build.gradle):

implementation 'com.mysql:mysql-connector-j:8.0.33' // 使用合适的版本

第3步:Java 代码示例

下面我们分别调用上面创建的四种存储过程。

示例 1:调用无参、返回结果集的存储过程 (hello_world)

import java.sql.*;
public class StoredProcedureExample {
    // 数据库连接信息,建议使用配置文件
    private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC";
    private static final String USER = "your_username";
    private static final String PASS = "your_password";
    public static void main(String[] args) {
        // 使用 try-with-resources 语句,确保连接、语句和结果集自动关闭
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS)) {
            if (conn != null) {
                System.out.println("Connected to the database successfully!");
                // 1. 调用无参、返回结果集的存储过程
                callSimpleStoredProcedure(conn);
                // 2. 调用带输入参数的存储过程
                callStoredProcedureWithInputParam(conn, 1);
                // 3. 调用带输出参数的存储过程
                callStoredProcedureWithOutputParam(conn, "Engineering");
                // 4. 调用返回结果集的存储过程
                callStoredProcedureWithResultSet(conn, "Marketing");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    /**
     * 调用无参、返回结果集的存储过程
     */
    private static void callSimpleStoredProcedure(Connection conn) throws SQLException {
        String sql = "{call hello_world()}";
        try (CallableStatement cstmt = conn.prepareCall(sql)) {
            // executeQuery() 用于执行返回结果集的存储过程
            try (ResultSet rs = cstmt.executeQuery()) {
                System.out.println("\n--- 调用 hello_world ---");
                while (rs.next()) {
                    // 列名是在 SELECT 语句中定义的 'message'
                    String message = rs.getString("message");
                    System.out.println("Message: " + message);
                }
            }
        }
    }
}

示例 2:调用带输入参数的存储过程 (get_employee_by_id)

// ... (接上面的 StoredProcedureExample 类)
    /**
     * 调用带输入参数的存储过程
     */
    private static void callStoredProcedureWithInputParam(Connection conn, int employeeId) throws SQLException {
        // ? 是一个占位符,用于设置 IN 参数
        String sql = "{call get_employee_by_id(?)}";
        try (CallableStatement cstmt = conn.prepareCall(sql)) {
            // 设置 IN 参数的索引 (从 1 开始) 和值
            cstmt.setInt(1, employeeId);
            // 执行并获取结果集
            try (ResultSet rs = cstmt.executeQuery()) {
                System.out.println("\n--- 调用 get_employee_by_id (ID=" + employeeId + ") ---");
                while (rs.next()) {
                    System.out.printf("ID: %d, Name: %s, Dept: %s, Salary: %.2f%n",
                            rs.getInt("id"),
                            rs.getString("name"),
                            rs.getString("department"),
                            rs.getBigDecimal("salary"));
                }
            }
        }
    }

示例 3:调用带输出参数的存储过程 (get_avg_salary_by_dept)

这是最复杂的一种,需要使用 registerOutParameter 来注册输出参数。

// ... (接上面的 StoredProcedureExample 类)
    /**
     * 调用带输出参数的存储过程
     */
    private static void callStoredProcedureWithOutputParam(Connection conn, String department) throws SQLException {
        // 第一个 ? 是 IN 参数,第二个 ? 是 OUT 参数
        String sql = "{call get_avg_salary_by_dept(?, ?)}";
        try (CallableStatement cstmt = conn.prepareCall(sql)) {
            // 1. 设置 IN 参数
            cstmt.setString(1, department);
            // 2. 注册 OUT 参数
            // 参数索引 (从 1 开始)
            // 参数类型 (java.sql.Types 中定义的类型)
            cstmt.registerOutParameter(2, java.sql.Types.DECIMAL);
            // 3. 执行存储过程
            // 对于有 OUT 参数的存储过程,使用 execute() 或 executeUpdate()
            cstmt.execute();
            // 4. 从 OUT 参数中获取值
            BigDecimal avgSalary = cstmt.getBigDecimal(2);
            System.out.println("\n--- 调用 get_avg_salary_by_dept (Dept=" + department + ") ---");
            System.out.println("Average Salary: " + avgSalary);
        }
    }

示例 4:调用返回结果集的存储过程 (get_employees_by_dept)

调用方式与示例 1 类似,只是多了一个 IN 参数。

// ... (接上面的 StoredProcedureExample 类)
    /**
     * 调用返回结果集的存储过程 (带 IN 参数)
     */
    private static void callStoredProcedureWithResultSet(Connection conn, String department) throws SQLException {
        String sql = "{call get_employees_by_dept(?)}";
        try (CallableStatement cstmt = conn.prepareCall(sql)) {
            // 设置 IN 参数
            cstmt.setString(1, department);
            // 执行并获取结果集
            try (ResultSet rs = cstmt.executeQuery()) {
                System.out.println("\n--- 调用 get_employees_by_dept (Dept=" + department + ") ---");
                while (rs.next()) {
                    System.out.printf("ID: %d, Name: %s, Dept: %s, Salary: %.2f%n",
                            rs.getInt("id"),
                            rs.getString("name"),
                            rs.getString("department"),
                            rs.getBigDecimal("salary"));
                }
            }
        }
    }
}

第4步:最佳实践和注意事项

  1. 使用 try-with-resources

    • 必须始终在 try 块中创建 Connection, Statement, PreparedStatement, CallableStatement, 和 ResultSet
    • 这样可以确保它们在使用完毕后被自动关闭,防止资源泄漏。
  2. 使用连接池

    • 在生产环境中,绝对不要在每次请求时都创建和销毁数据库连接,这非常消耗资源。
    • 应该使用连接池,如 HikariCP (目前性能最好的)、Apache DBCP 或 C3P0,它们可以复用连接,大大提高性能。
  3. 防止 SQL 注入

    • 当调用带参数的存储过程时,使用 cstmt.setXxx(index, value) 的方式来设置参数。
    • 永远不要使用字符串拼接的方式来构建 SQL 调用语句,"{call get_employee_by_id(" + employeeId + ")}",这会引入 SQL 注入风险。
  4. 处理多个结果集

    • 某些存储过程可能会返回多个结果集(先返回一个状态码,再返回一个数据集)。
    • 你可以使用 boolean hasResultSet = cstmt.execute(); 来循环处理所有结果。
    • cstmt.getMoreResults() 方法可以移动到下一个结果。
  5. 清晰的代码和注释

    • 为存储过程和 Java 调用代码添加清晰的注释,说明其功能、参数和返回值。
    • 使用有意义的变量名。
  6. 错误处理

    • 使用 try-catch 块妥善处理 SQLException,并向用户或日志记录友好的错误信息。

通过以上步骤和示例,你应该能够掌握在 Java 中调用各种类型 MySQL 存储过程的方法。

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