MySQL存储过程全面解析以及和Java的类比教程
作者:牧羊人_myr
MySQL存储过程详解
什么是存储过程
存储过程是一组预编译好的SQL语句集合,它们被存储在数据库中,可被重复调用。存储过程可以视为数据库中的"函数",能够接受输入参数、执行复杂的数据库操作,并返回结果。
存储过程的特点
- 封装性:将复杂的SQL逻辑封装在一个单元中,隐藏实现细节
- 可复用性:一次创建,多次调用,减少代码冗余
- 增强安全性:可以控制用户对数据的访问方式
- 提高性能:预编译执行,减少网络传输开销
- 支持参数交互:可以接收输入参数,也可以返回输出结果
存储过程的基本语法
创建存储过程
创建存储过程时,由于存储过程内部会使用;作为语句分隔符,因此需要先使用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服务器提供,分为全局变量和会话变量。
- 全局变量:影响服务器整体操作(需要
SUPER权限) - 会话变量:仅影响当前会话
查看系统变量:
-- 查看所有全局变量 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;
常用系统变量:
max_connections:最大连接数autocommit:自动提交模式character_set_client:客户端字符集character_set_results:结果集字符集sql_mode:SQL模式设置
用户自定义变量
用户自定义变量不需要提前声明,使用时直接以@变量名的形式定义和使用,作用域为当前连接。
赋值方式:
-- 使用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);
存储过程的参数
存储过程支持三种类型的参数:
IN:输入参数(默认),只能传入值,不能返回值OUT:输出参数,用于返回值INOUT:既可以输入也可以输出
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)
游标用于存储查询结果集,以便逐行访问。
游标使用步骤:
- 声明游标
- 打开游标
- 获取游标记录
- 关闭游标
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);
存储函数
存储函数是有返回值的存储过程,与存储过程的主要区别是:
- 存储函数必须有返回值
- 存储函数的参数只能是
IN类型
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类比内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
