Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > MySQL存储过程和Java类比

MySQL存储过程全面解析以及和Java的类比教程

作者:牧羊人_myr

存储过程是一种在MySQL数据库中定义和执行的可重复使用的程序单元,它允许我们将一系列的 SQL 语句组织在一起,并通过一个简单的调用来执行它们,这篇文章主要介绍了MySQL存储过程全面解析以及和Java类比的相关资料,需要的朋友可以参考下

MySQL存储过程详解

什么是存储过程

存储过程是一组预编译好的SQL语句集合,它们被存储在数据库中,可被重复调用。存储过程可以视为数据库中的"函数",能够接受输入参数、执行复杂的数据库操作,并返回结果。

存储过程的特点

  1. 封装性:将复杂的SQL逻辑封装在一个单元中,隐藏实现细节
  2. 可复用性:一次创建,多次调用,减少代码冗余
  3. 增强安全性:可以控制用户对数据的访问方式
  4. 提高性能:预编译执行,减少网络传输开销
  5. 支持参数交互:可以接收输入参数,也可以返回输出结果

存储过程的基本语法

创建存储过程

创建存储过程时,由于存储过程内部会使用;作为语句分隔符,因此需要先使用DELIMITER命令修改语句结束符:

-- 将语句结束符修改为//
DELIMITER //

-- 创建存储过程
CREATE PROCEDURE procedure_name([parameters])
BEGIN
    -- SQL语句集合
    SELECT * FROM employees;
END //

-- 恢复语句结束符为;
DELIMITER ;

示例:创建一个简单的存储过程,查询所有员工信息

DELIMITER //
CREATE PROCEDURE GetAllEmployees()
BEGIN
    SELECT id, name, position, salary FROM employees;
END //
DELIMITER ;

调用存储过程

使用CALL语句调用存储过程:

CALL procedure_name([parameters]);

示例:调用上面创建的存储过程

CALL GetAllEmployees();

查看存储过程

查看存储过程的创建语句:

SHOW CREATE PROCEDURE procedure_name;

查看数据库中所有存储过程:

SHOW PROCEDURE STATUS;

删除存储过程

DROP PROCEDURE [IF EXISTS] procedure_name;

示例:

DROP PROCEDURE IF EXISTS GetAllEmployees;

变量

MySQL存储过程中使用的变量分为三类:系统变量、用户自定义变量和局部变量。

系统变量

系统变量由MySQL服务器提供,分为全局变量和会话变量。

查看系统变量:

-- 查看所有全局变量
SHOW GLOBAL VARIABLES;

-- 查看所有会话变量
SHOW SESSION VARIABLES;

-- 查看特定变量
SHOW GLOBAL VARIABLES LIKE 'max_connections';
SHOW SESSION VARIABLES LIKE 'autocommit';

设置系统变量:

-- 设置全局变量
SET GLOBAL max_connections = 1000;

-- 设置会话变量
SET SESSION autocommit = 0;
-- 或
SET @autocommit = 0;

常用系统变量:

用户自定义变量

用户自定义变量不需要提前声明,使用时直接以@变量名的形式定义和使用,作用域为当前连接。

赋值方式:

-- 使用SET赋值
SET @var1 = 10;
SET @var2 = 'Hello World';
SET @var3 = (SELECT COUNT(*) FROM employees);

-- 使用SELECT赋值
SELECT COUNT(*) INTO @emp_count FROM employees;
SELECT name INTO @first_emp FROM employees LIMIT 1;

使用变量:

SELECT @var1, @var2;
SELECT * FROM employees WHERE id = @var1;

局部变量

局部变量需要使用DECLARE声明,作用范围仅限于声明它的BEGIN...END块中,通常用于存储过程或函数内部。

声明和使用:

DELIMITER //
CREATE PROCEDURE ExampleProc()
BEGIN
    -- 声明局部变量
    DECLARE total_count INT;
    DECLARE avg_salary DECIMAL(10,2);
    DECLARE emp_name VARCHAR(50);
    
    -- 赋值
    SELECT COUNT(*) INTO total_count FROM employees;
    SET avg_salary = (SELECT AVG(salary) FROM employees);
    
    -- 使用
    SELECT total_count AS '员工总数', avg_salary AS '平均工资';
END //
DELIMITER ;

流程控制语句

IF语句

DELIMITER //
CREATE PROCEDURE CheckSalary(IN emp_id INT)
BEGIN
    DECLARE emp_salary DECIMAL(10,2);
    
    SELECT salary INTO emp_salary FROM employees WHERE id = emp_id;
    
    IF emp_salary > 10000 THEN
        SELECT '高工资' AS salary_level;
    ELSEIF emp_salary > 5000 THEN
        SELECT '中等工资' AS salary_level;
    ELSE
        SELECT '低工资' AS salary_level;
    END IF;
END //
DELIMITER ;

-- 调用
CALL CheckSalary(1001);

CASE语句

DELIMITER //
CREATE PROCEDURE GetDepartmentName(IN dept_id INT)
BEGIN
    DECLARE dept_name VARCHAR(50);
    
    CASE dept_id
        WHEN 1 THEN SET dept_name = '技术部';
        WHEN 2 THEN SET dept_name = '市场部';
        WHEN 3 THEN SET dept_name = '财务部';
        ELSE SET dept_name = '未知部门';
    END CASE;
    
    SELECT dept_name AS department;
END //
DELIMITER ;

-- 调用
CALL GetDepartmentName(2);

存储过程的参数

存储过程支持三种类型的参数:

DELIMITER //
CREATE PROCEDURE EmployeeBonus(
    IN emp_id INT,           -- 输入参数:员工ID
    IN bonus_percent DECIMAL(5,2),  -- 输入参数:奖金百分比
    OUT total_bonus DECIMAL(10,2)   -- 输出参数:总奖金
)
BEGIN
    DECLARE emp_salary DECIMAL(10,2);
    
    SELECT salary INTO emp_salary FROM employees WHERE id = emp_id;
    SET total_bonus = emp_salary * (bonus_percent / 100);
END //
DELIMITER ;

-- 调用带输出参数的存储过程
CALL EmployeeBonus(1001, 10, @bonus);
SELECT @bonus AS '员工奖金';

INOUT参数示例:

DELIMITER //
CREATE PROCEDURE IncrementValue(INOUT value INT, IN increment INT)
BEGIN
    SET value = value + increment;
END //
DELIMITER ;

-- 调用
SET @num = 10;
CALL IncrementValue(@num, 5);
SELECT @num;  -- 结果为15

循环语句

WHILE循环

DELIMITER //
CREATE PROCEDURE CountDown(IN start_num INT)
BEGIN
    DECLARE current_num INT;
    SET current_num = start_num;
    
    WHILE current_num > 0 DO
        SELECT current_num;
        SET current_num = current_num - 1;
    END WHILE;
END //
DELIMITER ;

-- 调用
CALL CountDown(5);

REPEAT循环

DELIMITER //
CREATE PROCEDURE FindEvenNumber(IN start_num INT, OUT result INT)
BEGIN
    SET result = start_num;
    
    REPEAT
        SET result = result + 1;
    UNTIL result % 2 = 0  -- 直到找到偶数
    END REPEAT;
END //
DELIMITER ;

-- 调用
CALL FindEvenNumber(7, @even_num);
SELECT @even_num;  -- 结果为8

LOOP循环

DELIMITER //
CREATE PROCEDURE SumNumbers(IN max_num INT, OUT total INT)
BEGIN
    DECLARE num INT;
    SET num = 1;
    SET total = 0;
    
    -- 定义循环标签
    sum_loop: LOOP
        SET total = total + num;
        SET num = num + 1;
        
        -- 退出循环条件
        IF num > max_num THEN
            LEAVE sum_loop;  -- 退出循环
        END IF;
    END LOOP sum_loop;
END //
DELIMITER ;

-- 调用
CALL SumNumbers(100, @sum_result);
SELECT @sum_result;  -- 结果为5050

使用ITERATE进入下一次循环:

DELIMITER //
CREATE PROCEDURE SumOddNumbers(IN max_num INT, OUT total INT)
BEGIN
    DECLARE num INT;
    SET num = 0;
    SET total = 0;
    
    odd_loop: LOOP
        SET num = num + 1;
        
        -- 如果是偶数则跳过
        IF num % 2 = 0 THEN
            ITERATE odd_loop;  -- 进入下一次循环
        END IF;
        
        SET total = total + num;
        
        IF num >= max_num THEN
            LEAVE odd_loop;
        END IF;
    END LOOP odd_loop;
END //
DELIMITER ;

-- 调用
CALL SumOddNumbers(10, @odd_sum);
SELECT @odd_sum;  -- 结果为25 (1+3+5+7+9)

游标(Cursor)

游标用于存储查询结果集,以便逐行访问。

游标使用步骤:

  1. 声明游标
  2. 打开游标
  3. 获取游标记录
  4. 关闭游标
DELIMITER //
CREATE PROCEDURE UpdateLowSalaries(IN min_salary DECIMAL(10,2))
BEGIN
    DECLARE emp_id INT;
    DECLARE emp_salary DECIMAL(10,2);
    DECLARE done INT DEFAULT 0;
    
    -- 声明游标
    DECLARE emp_cursor CURSOR FOR
        SELECT id, salary FROM employees WHERE salary < min_salary;
    
    -- 声明条件处理程序,当游标到达末尾时设置done为1
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    
    -- 打开游标
    OPEN emp_cursor;
    
    -- 循环获取游标数据
    emp_loop: LOOP
        -- 获取游标记录
        FETCH emp_cursor INTO emp_id, emp_salary;
        
        -- 检查是否到达末尾
        IF done THEN
            LEAVE emp_loop;
        END IF;
        
        -- 更新工资
        UPDATE employees SET salary = salary * 1.1 WHERE id = emp_id;
    END LOOP emp_loop;
    
    -- 关闭游标
    CLOSE emp_cursor;
    
    SELECT '工资调整完成' AS message;
END //
DELIMITER ;

-- 调用
CALL UpdateLowSalaries(5000);

条件处理语句(Handler)

条件处理程序用于定义在遇到特定条件时应执行的操作,分为CONTINUE(继续执行)和EXIT(退出当前块)两种类型。

DELIMITER //
CREATE PROCEDURE SafeDeleteEmployee(IN emp_id INT)
BEGIN
    -- 声明条件处理程序,当发生错误时继续执行并设置flag为1
    DECLARE flag INT DEFAULT 0;
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET flag = 1;
    
    DELETE FROM employees WHERE id = emp_id;
    
    IF flag = 1 THEN
        SELECT '删除失败,可能存在外键约束' AS result;
    ELSE
        SELECT '删除成功' AS result;
    END IF;
END //
DELIMITER ;

-- 调用
CALL SafeDeleteEmployee(1001);

存储函数

存储函数是有返回值的存储过程,与存储过程的主要区别是:

DELIMITER //
CREATE FUNCTION GetEmployeeCount(dept_id INT) 
RETURNS INT
DETERMINISTIC
BEGIN
    DECLARE count INT;
    SELECT COUNT(*) INTO count FROM employees WHERE department_id = dept_id;
    RETURN count;
END //
DELIMITER ;

-- 调用存储函数
SELECT GetEmployeeCount(1) AS '技术部员工数';

另一个计算年龄的存储函数示例:

DELIMITER //
CREATE FUNCTION CalculateAge(birth_date DATE)
RETURNS INT
DETERMINISTIC
BEGIN
    DECLARE age INT;
    SET age = TIMESTAMPDIFF(YEAR, birth_date, CURDATE());
    RETURN age;
END //
DELIMITER ;

-- 使用存储函数
SELECT name, birth_date, CalculateAge(birth_date) AS age FROM employees;

存储过程和存储函数是MySQL数据库编程的重要工具,合理使用可以显著提高数据库操作的效率和安全性,同时简化应用程序的开发。

用Java视角理解MySQL存储过程

如果你熟悉Java编程,我们可以通过Java中的概念来类比理解MySQL存储过程,帮助你更快掌握这一数据库特性。

存储过程 vs Java方法

最直观的类比是:MySQL存储过程相当于数据库中的"Java方法"

特性MySQL存储过程Java方法
定义使用CREATE PROCEDURE声明使用访问修饰符+返回类型声明
目的封装SQL逻辑封装Java代码逻辑
调用使用CALL语句使用方法名+参数列表
可重用性一次创建,多次调用一次定义,多次调用
参数支持IN/OUT/INOUT类型支持输入参数,通过返回值或对象引用返回结果
作用域数据库级别的对象类级别的成员(或静态)

示例对比:

Java方法:

public double calculateBonus(double salary, double rate) {
    return salary * rate;
}

MySQL存储过程:

DELIMITER //
CREATE PROCEDURE calculateBonus(
    IN salary DECIMAL(10,2),
    IN rate DECIMAL(5,2),
    OUT bonus DECIMAL(10,2)
)
BEGIN
    SET bonus = salary * rate;
END //
DELIMITER ;

变量体系对比

1. 系统变量 vs Java系统属性

MySQL系统变量 ≈ Java的System.getProperty()

MySQL系统变量:

-- 查看全局变量(类似JVM级别的配置)
SHOW GLOBAL VARIABLES LIKE 'max_connections';

-- 查看会话变量(类似线程级别的配置)
SHOW SESSION VARIABLES LIKE 'autocommit';

Java系统属性:

// 查看JVM级别的系统属性
System.getProperty("java.version");
System.getProperty("user.language");

2. 用户自定义变量 vs Java成员变量

MySQL用户自定义变量(@变量)≈ Java类的成员变量

MySQL用户变量:

-- 定义和使用用户变量(会话范围内有效)
SET @total = 0;
CALL calculateSum(100, @total);
SELECT @total;

Java成员变量:

public class Calculator {
    // 类成员变量(对象范围内有效)
    private int total;
    
    public void calculateSum(int num) {
        total += num;
    }
}

3. 局部变量 vs Java局部变量

MySQL存储过程中的局部变量 ≈ Java方法中的局部变量

MySQL局部变量:

DELIMITER //
CREATE PROCEDURE example()
BEGIN
    -- 声明局部变量(仅在BEGIN...END块中有效)
    DECLARE count INT;
    DECLARE name VARCHAR(50);
    
    SET count = 10;
END //
DELIMITER ;

Java局部变量:

public void exampleMethod() {
    // 声明局部变量(仅在方法中有效)
    int count;
    String name;
    
    count = 10;
}

流程控制对比

条件判断

MySQL的IF语句 ≈ Java的if-else语句

MySQL:

IF score >= 90 THEN
    SET grade = 'A';
ELSEIF score >= 80 THEN
    SET grade = 'B';
ELSE
    SET grade = 'C';
END IF;

Java:

if (score >= 90) {
    grade = "A";
} else if (score >= 80) {
    grade = "B";
} else {
    grade = "C";
}

MySQL的CASE语句 ≈ Java的switch语句

MySQL:

CASE department_id
    WHEN 1 THEN SET dept_name = '技术部';
    WHEN 2 THEN SET dept_name = '市场部';
    ELSE SET dept_name = '其他';
END CASE;

Java:

switch (departmentId) {
    case 1:
        deptName = "技术部";
        break;
    case 2:
        deptName = "市场部";
        break;
    default:
        deptName = "其他";
}

循环结构

MySQL的WHILE循环 ≈ Java的while循环

MySQL:

WHILE i <= 10 DO
    SET sum = sum + i;
    SET i = i + 1;
END WHILE;

Java:

while (i <= 10) {
    sum = sum + i;
    i++;
}

MySQL的REPEAT循环 ≈ Java的do-while循环

MySQL:

REPEAT
    SET sum = sum + i;
    SET i = i + 1;
UNTIL i > 10
END REPEAT;

Java:

do {
    sum = sum + i;
    i++;
} while (i <= 10);

MySQL的LOOP循环 ≈ Java的for(;;)无限循环

MySQL:

loop_label: LOOP
    SET i = i + 1;
    IF i > 10 THEN
        LEAVE loop_label; -- 类似break
    END IF;
    IF i % 2 = 0 THEN
        ITERATE loop_label; -- 类似continue
    END IF;
    SET sum = sum + i;
END LOOP loop_label;

Java:

loopLabel: for (;;) {
    i++;
    if (i > 10) {
        break loopLabel;
    }
    if (i % 2 == 0) {
        continue loopLabel;
    }
    sum = sum + i;
}

游标 vs Java集合迭代器

MySQL的游标(Cursor) ≈ Java的Iterator迭代器

MySQL游标使用:

DELIMITER //
CREATE PROCEDURE processEmployees()
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE emp_id INT;
    DECLARE emp_name VARCHAR(50);
    
    -- 声明游标(类似获取迭代器)
    DECLARE cur CURSOR FOR 
        SELECT id, name FROM employees;
    
    -- 处理结束条件
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    
    OPEN cur; -- 打开游标(类似iterator.hasNext())
    
    read_loop: LOOP
        FETCH cur INTO emp_id, emp_name; -- 获取下一个元素
        
        IF done THEN
            LEAVE read_loop;
        END IF;
        
        -- 处理数据
        UPDATE employees SET last_processed = NOW() 
        WHERE id = emp_id;
    END LOOP;
    
    CLOSE cur; -- 关闭游标
END //
DELIMITER ;

Java迭代器使用:

public void processEmployees(List<Employee> employees) {
    Iterator<Employee> iterator = employees.iterator(); // 获取迭代器
    
    while (iterator.hasNext()) { // 检查是否有下一个元素
        Employee emp = iterator.next(); // 获取下一个元素
        
        // 处理数据
        emp.setLastProcessed(new Date());
    }
}

存储函数 vs Java有返回值的方法

MySQL存储函数 ≈ Java中有返回值的方法

MySQL存储函数:

DELIMITER //
CREATE FUNCTION calculateAge(birth_date DATE)
RETURNS INT
BEGIN
    DECLARE age INT;
    SET age = TIMESTAMPDIFF(YEAR, birth_date, CURDATE());
    RETURN age; -- 返回结果
END //
DELIMITER ;

Java方法:

public int calculateAge(Date birthDate) {
    Calendar birth = Calendar.getInstance();
    birth.setTime(birthDate);
    Calendar now = Calendar.getInstance();
    
    int age = now.get(Calendar.YEAR) - birth.get(Calendar.YEAR);
    return age; // 返回结果
}

异常处理对比

MySQL的条件处理程序(Handler) ≈ Java的try-catch块

MySQL异常处理:

DELIMITER //
CREATE PROCEDURE safeUpdate()
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        -- 发生异常时执行
        SELECT '更新失败,回滚事务' AS message;
        ROLLBACK;
    END;
    
    START TRANSACTION;
    -- 可能出错的操作
    UPDATE accounts SET balance = balance - 100 WHERE id = 1;
    UPDATE accounts SET balance = balance + 100 WHERE id = 2;
    COMMIT;
END //
DELIMITER ;

Java异常处理:

public void safeUpdate() {
    Connection conn = null;
    try {
        conn = getConnection();
        conn.setAutoCommit(false);
        
        // 可能出错的操作
        updateAccount(conn, 1, -100);
        updateAccount(conn, 2, 100);
        
        conn.commit();
    } catch (SQLException e) {
        // 发生异常时执行
        System.out.println("更新失败,回滚事务");
        if (conn != null) {
            try { conn.rollback(); } 
            catch (SQLException ex) { /* 处理异常 */ }
        }
    }
}

通过这种类比,我们可以发现MySQL存储过程和Java方法在设计理念上有很多相似之处,都是为了实现代码的封装、复用和模块化。理解这种对应关系,可以帮助Java开发者更快掌握MySQL存储过程的使用。

总结

到此这篇关于MySQL存储过程及和Java的类比的文章就介绍到这了,更多相关MySQL存储过程和Java类比内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文