MySql的存储过程以及JDBC实战

MySql的存储过程以及JDBC实战


文章目录


前言

本次课程主要是关于Mysql的存储过程实战以及使用JDBC对存储过程进行操作。


一、MySQL存储过程?

MySQL 存储过程是一组预编译的 SQL 语句集合,封装了特定的业务逻辑,可被重复调用。它类似于编程语言中的 "函数",能减少网络传输、提高执行效率(预编译一次,多次执行),并增强代码复用性。

1、存储过程基本语法

1.1 创建存储过程

sql 复制代码
-- 修改分隔符(避免与存储过程内的 ; 冲突)
delimiter //

-- 创建存储过程
create procedure 存储过程名(
    [参数类型] 参数名 数据类型,  -- 可选:参数(IN/OUT/INOUT)
    ...
)
begin
    -- 存储过程逻辑(SQL语句、控制结构等)
end //

-- 恢复默认分隔符
delimiter ; 

重点强调:一定要注意 第一行的 delimiter 与后面的 // 存在一个空格,最后一行的 delimiter 与; 存在空格
参数类型:

IN:输入参数(默认,调用时传入值,过程内可使用)。

OUT:输出参数(过程内赋值,调用后可获取结果)。

INOUT:既输入又输出(调用时传入值,过程内修改后返回)。

1.2 调用存储过程

sql 复制代码
call 存储过程名([参数值1, 参数值2, ...]);  -- 无参数时直接 call 存储过程名;

1.3 查看存储过程

sql 复制代码
-- 查看所有存储过程
show procedure status;

-- 查看指定存储过程的创建语句
show create procedure 存储过程名;

1.4 删除存储过程

sql 复制代码
drop procedure if exists 存储过程名;  -- if exists 避免删除不存在的过程报错

2、存储过程示例

示例 1:无参数的存储过程(查询数据)

创建一个查询 user 表所有数据的存储过程:

sql 复制代码
delimiter //
create procedure get_all_users()
begin
    select * from user;  -- 假设存在 user 表
end //
delimiter ;

-- 调用
call get_all_users();

示例 2:带 IN 参数的存储过程(条件查询)

根据用户 ID 查询用户信息:

sql 复制代码
delimiter //
create procedure get_user_by_id(in user_id int)  -- 输入参数 user_id
begin
    select * from user where id = user_id;
end //
delimiter ;

-- 调用(查询 ID=1 的用户)
call get_user_by_id(1);

示例 3:带 OUT 参数的存储过程(返回结果)

计算 user 表的总记录数,并通过输出参数返回:

sql 复制代码
delimiter //
create procedure get_user_count(out total int)  -- 输出参数 total
begin
    select count(*) into total from user;  -- 将结果存入 total
end //
delimiter ;

-- 调用(需先定义变量接收结果)
set @total_count = 0;  -- 定义用户变量
call get_user_count(@total_count);  -- 传入变量
select @total_count as user_total;  -- 查看结果

示例 4:带 INOUT 参数的存储过程(修改并返回值)

将输入的数字翻倍后返回:

sql 复制代码
delimiter //
create procedure double_number(inout num int)  -- 既输入又输出
begin
    set num = num * 2;  -- 修改参数值
end //
delimiter ;

-- 调用
set @n = 5;  -- 初始值 5
call double_number(@n);  -- 传入并修改
select @n;  -- 结果为 10

示例 5:包含控制结构的存储过程(条件 + 循环)

根据年龄范围批量插入用户(使用 if 条件和 while 循环):

sql 复制代码
delimiter //
create procedure batch_insert_users(
    in start_age int,  -- 起始年龄
    in end_age int     -- 结束年龄
)
begin
    declare current_age int;  -- 声明局部变量
    set current_age = start_age;  -- 初始化
    
    -- 循环插入
    while current_age <= end_age do
        -- 条件判断:年龄>30 则状态为 'adult',否则为 'young'
        if current_age > 30 then
            insert into user(name, age, status) values(concat('user_', current_age), current_age, 'adult');
        else
            insert into user(name, age, status) values(concat('user_', current_age), current_age, 'young');
        end if;
        
        set current_age = current_age + 1;  -- 自增
    end while;
end //
delimiter ;

-- 调用(插入年龄 20-22 的用户)
call batch_insert_users(20, 22);

3、注意事项

分隔符问题:创建存储过程时必须用 delimiter 修改分隔符(如 //),否则 MySQL 会将过程内的 ; 视为语句结束,导致语法错误。
变量命名:局部变量(declare 声明)仅在过程内有效,避免与表列名或参数名冲突。
权限:创建存储过程需要 CREATE ROUTINE 权限,执行需要 EXECUTE 权限。
调试:MySQL 存储过程调试相对复杂,可通过 select 输出中间结果辅助调试。
适用场景:适合复杂业务逻辑(如多表关联、批量操作),但过度使用可能导致维护困难(建议简单逻辑直接用 SQL 语句)。

通过以上示例,可快速掌握存储过程的创建、调用和常见用法。根据实际业务需求,可组合 SQL 语句和控制结构(if/case/while/repeat 等)实现复杂功能。

二、JDBC调用存储过程的接口:CallableStatement

在 JDBC 中,CallableStatement 是一个接口(继承自 PreparedStatement),专门用于调用数据库中的存储过程。它支持存储过程的输入参数(IN)、输出参数(OUT)和输入输出参数(INOUT),并能处理存储过程的返回结果。

1、核心作用

  • 执行数据库存储过程(如 MySQL、Oracle 等的 PROCEDURE)。
  • 支持存储过程的参数传递(IN/OUT/INOUT)。
  • 相比直接执行 SQL 语句,调用存储过程可减少网络交互,提高效率(尤其适合复杂逻辑)。

2、使用步骤

1.获取数据库连接(Connection)。

2.编写调用存储过程的 SQL 模板:使用 {call 存储过程名(参数列表)} 格式(参数用 ? 占位)。

3.创建 CallableStatement 对象:通过connection.prepareCall(sql) 生成。

4.处理参数:

  • 输入参数(IN):用 setXxx(索引, 值) 方法设置(如 setInt(1, 100))。
  • 输出参数(OUT):用 registerOutParameter(索引, SQL类型) 注册(如 registerOutParameter(2, Types.INTEGER))。
  • 输入输出参数(INOUT):先 setXxx 设输入值,再 registerOutParameter 注册输出类型。
    5.执行存储过程:调用 execute() 方法。
    6.获取结果:通过 getXxx(索引) 方法获取输出参数或返回结果。
    7.关闭资源:关闭 CallableStatement 和 Connection(建议用 try-with-resources 自动关闭)。

3、示例代码(结合 MySQL 存储过程)

假设已在 MySQL 中创建以下存储过程,分别演示 IN/OUT/INOUT 参数:

  1. 带 IN 参数的存储过程(查询用户信息)
sql 复制代码
-- 存储过程:根据用户ID查询用户名
DELIMITER //
CREATE PROCEDURE get_username_by_id(IN user_id INT)
BEGIN
  SELECT username FROM users WHERE id = user_id;
END //
DELIMITER ;

JDBC 调用代码:

java 复制代码
import java.sql.*;

public class CallableDemo1 {
    public static void main(String[] args) {
        // 数据库连接信息
        String url = "jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC";
        String user = "root";
        String password = "your_password";

        // 调用存储过程的SQL模板
        String sql = "{call get_username_by_id(?)}";  // ? 对应 IN 参数 user_id

        try (
            Connection conn = DriverManager.getConnection(url, user, password);
            CallableStatement cstmt = conn.prepareCall(sql)
        ) {
            // 设置 IN 参数(索引从1开始,值为1)
            cstmt.setInt(1, 1);

            // 执行存储过程
            ResultSet rs = cstmt.executeQuery();  // 有返回结果集时用 executeQuery()

            // 处理结果
            if (rs.next()) {
                String username = rs.getString("username");
                System.out.println("用户名:" + username);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
  1. 带 OUT 参数的存储过程(返回用户总数)
sql 复制代码
-- 存储过程:返回用户表总记录数(通过 OUT 参数输出)
DELIMITER //
CREATE PROCEDURE get_user_count(OUT total INT)
BEGIN
  SELECT COUNT(*) INTO total FROM users;
END //
DELIMITER ;

JDBC 调用代码:

java 复制代码
import java.sql.*;

public class CallableDemo2 {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC";
        String user = "root";
        String password = "your_password";
        String sql = "{call get_user_count(?)}";  // ? 对应 OUT 参数 total

        try (
            Connection conn = DriverManager.getConnection(url, user, password);
            CallableStatement cstmt = conn.prepareCall(sql)
        ) {
            // 注册 OUT 参数(索引1,类型为整数)
            cstmt.registerOutParameter(1, Types.INTEGER);

            // 执行存储过程(无结果集时用 execute())
            cstmt.execute();

            // 获取 OUT 参数的值
            int total = cstmt.getInt(1);
            System.out.println("用户总数:" + total);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
  1. 带 INOUT 参数的存储过程(修改并返回值)
sql 复制代码
-- 存储过程:将输入的数字翻倍后返回(INOUT 参数)
DELIMITER //
CREATE PROCEDURE double_number(INOUT num INT)
BEGIN
  SET num = num * 2;
END //
DELIMITER ;

JDBC 调用代码:

java 复制代码
import java.sql.*;

public class CallableDemo3 {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC";
        String user = "root";
        String password = "your_password";
        String sql = "{call double_number(?)}";  // ? 对应 INOUT 参数 num

        try (
            Connection conn = DriverManager.getConnection(url, user, password);
            CallableStatement cstmt = conn.prepareCall(sql)
        ) {
            // 设置 INOUT 参数的初始值(输入)
            cstmt.setInt(1, 5);  // 初始值 5
            // 注册 INOUT 参数的输出类型
            cstmt.registerOutParameter(1, Types.INTEGER);

            // 执行存储过程
            cstmt.execute();

            // 获取修改后的值(输出)
            int result = cstmt.getInt(1);
            System.out.println("翻倍后的值:" + result);  // 输出 10
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

4、注意事项

  • 参数索引:CallableStatement 的参数索引从 1 开始(而非 0)。
  • 输出参数注册:OUT 和 INOUT 参数必须先通过 registerOutParameter 注册 SQL 类型(如 Types.INTEGER、Types.VARCHAR),否则会抛出异常。
  • 执行方法选择:
    存储过程返回结果集时,用 executeQuery()。
    无结果集但有输出参数时,用 execute()。
  • SQL 类型对应:registerOutParameter 的第二个参数需与数据库字段类型对应(如 MySQL 的 INT 对应 Types.INTEGER,VARCHAR 对应 Types.VARCHAR)。
    资源关闭:始终确保 Connection、CallableStatement、ResultSet 被关闭(try-with-resources 是最佳实践)。
    通过 CallableStatement,JDBC 可以灵活调用各种存储过程,尤其适合需要复用数据库逻辑的场景。

总结

以上就是今天要讲的内容,本文仅仅简单介绍了MySQL的存储过程的使用,以及JDBC的相关接口。

相关推荐
倔强的石头_5 小时前
kingbase备份与恢复实战(二)—— sys_dump库级逻辑备份与恢复(Windows详细步骤)
数据库
阿巴斯甜11 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker11 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952712 小时前
Andorid Google 登录接入文档
android
黄林晴13 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android