针对 SPJ_MNG 数据库,创建并执行存储过程
第1关:创建一个无参数的存储过程—jsearch1
1 | -- 使用指定的数据库 |
第2关:创建带输入参数的存储过程—jsearch2
1 | -- 使用指定的数据库 |
第3关:创建带输入参数和输出参数的存储过程—jsearch3
1 | -- 使用指定的数据库 |
命令行中测试:
1 | -- 声明一个变量来接收输出参数 |
第4关:创建一个使用游标的存储过程—jsearch4
创建存储过程
1 | -- 使用指定的数据库 |
调用存储过程
在 MySQL 命令行工具中调用存储过程 jsearch4
并验证结果:
1 | -- 使用指定的数据库 |
解释
使用指定的数据库:
1
USE SPJ_MNG;
确保我们正在使用
SPJ_MNG
数据库。更改语句结束符:
1
DELIMITER //
将语句结束符从默认的分号
;
改为//
,以避免在存储过程中使用分号时引起的问题。创建存储过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44CREATE PROCEDURE jsearch4(IN job_no VARCHAR(5), OUT supplier_names VARCHAR(255))
BEGIN
-- 声明变量
DECLARE done INT DEFAULT FALSE;
DECLARE supplier_name VARCHAR(20);
DECLARE temp_names VARCHAR(255) DEFAULT '';
-- 声明游标
DECLARE cur CURSOR FOR
SELECT s.SNAME
FROM spj sp, s
WHERE sp.JNO = job_no AND sp.SNO = s.SNO;
-- 声明继续处理程序
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = TRUE;
-- 创建临时表来存储唯一的供应商名称
CREATE TEMPORARY TABLE IF NOT EXISTS temp_suppliers (
SNAME VARCHAR(20),
UNIQUE (SNAME)
);
-- 开启游标
OPEN cur;
-- 循环处理游标
read_loop: LOOP
FETCH cur INTO supplier_name;
IF done THEN
LEAVE read_loop;
END IF;
-- 插入供应商名称到临时表,自动去重
INSERT IGNORE INTO temp_suppliers (SNAME) VALUES (supplier_name);
END LOOP;
-- 关闭游标
CLOSE cur;
-- 从临时表中读取供应商名称并拼接
SELECT GROUP_CONCAT(SNAME SEPARATOR ', ') INTO supplier_names FROM temp_suppliers;
-- 删除临时表
DROP TEMPORARY TABLE temp_suppliers;
END //- 声明变量:声明必要的变量。
- 声明游标:声明游标
cur
,用于查询供应该工程号job_no
零件的所有供应商的名称。 - 声明继续处理程序:当游标到达末尾时,设置
done
为TRUE
。 - 创建临时表:创建一个临时表
temp_suppliers
,用于存储唯一的供应商名称。 - 开启游标:打开游标。
- 循环处理游标:使用
LOOP
循环从游标中获取数据,并将供应商名称插入到临时表中,自动去重。 - 关闭游标:关闭游标。
- 从临时表中读取供应商名称并拼接:使用
GROUP_CONCAT
函数将临时表中的供应商名称拼接成一个字符串,并用逗号和空格分隔。 - 删除临时表:删除临时表,释放资源。
调用存储过程:
1
2
3SET @supplier_names = '';
CALL jsearch4('J2', @supplier_names);
SELECT @supplier_names;调用存储过程并查看输出参数的值。
?我tm都调用出来了,你个b头哥过不了
第5关:查看存储过程的文本信息
记得 \G
这里重新创建应该能过
1 | -- 使用指定的数据库 |
…..改文件大法!修改step5的sh文件
1 | export MYSQL_PWD=123123 |
procedures.txt记得保存到上一级目录,myshiyun下
1 | *************************** 1. row *************************** |
第6关:查看存储过程的基本状态信息
1 | -- 使用指定的数据库 |
第7关:删除存储过程
1 | -- 使用指定的数据库 |
针对员工表,综合运用存储函数与存储过程
第1关:创建一个用户自定义函数 generateEID
1 | -- 使用指定的数据库 |
第2关:创建一个基于游标的工资调整存储过程
1 | #使用 Student 数据库 |
针对 Student 数据库,创建和执行触发器
第1关:针对 SC 表创建一个名为 insert_s 的触发器
1 | use Student; |
第2关:为 Student 表创建一个名为 dele_s1 的触发器
1 | use Student; |
第3关:为 Student 表创建一个名为 dele_s2 的触发器
1 | use Student; |
第4关:为 Student 表创建一个名为 update_s 的触发器
1 | use Student; |
第5关:删除 update_s 触发器
1 | use Student; |
第6关:给 Student 表设计before 和 after 触发器
1 | use Student; |
1 | ('before_update_Student', 'UPDATE', 'Student', "BEGIN\n \n IF NOT EXISTS (SELECT 1 FROM Department WHERE Dname = NEW.SDEP) THEN\n SIGNAL SQLSTATE '45000'\n SET MESSAGE_TEXT = '无效的部门名称';\n END IF;\nEND", 'BEFORE', 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION', 'root@%', 'utf8', 'utf8_general_ci', 'utf8_general_ci') |
线下额外验证
AI:
当然,我可以帮助你比较 BEFORE
和 AFTER
触发器的区别,并提供一些分析说明。
区别分析
执行时机:
- BEFORE 触发器:在实际的更新操作发生之前执行。这允许你在数据更改之前进行验证、修改或取消操作。
- AFTER 触发器:在实际的更新操作发生之后执行。这允许你在数据更改后进行后续操作,如记录日志、发送通知等。
访问的数据:
- BEFORE 触发器:可以访问
OLD
和NEW
行数据。OLD
表示更新前的数据,NEW
表示更新后的数据。你可以在触发器中修改NEW
行的数据,从而影响最终的更新结果。 - AFTER 触发器:只能访问
OLD
和NEW
行数据,但不能修改NEW
行的数据。因为更新操作已经完成,所以NEW
表示的是最终更新后的数据。
- BEFORE 触发器:可以访问
应用场景:
- BEFORE 触发器:适用于需要在数据更改前进行验证、数据清洗或取消操作的场景。例如,验证新的部门名称是否有效。
- AFTER 触发器:适用于需要在数据更改后进行记录日志、发送通知或其他后续操作的场景。例如,记录更新日志。
示例代码
1. 创建 Department
表和 UpdateLog
表
假设我们有以下表结构:
1 | CREATE TABLE Department ( |
2. 创建 BEFORE
触发器
1 | DELIMITER $$ |
3. 创建 AFTER
触发器
1 | DELIMITER $$ |
验证触发器
1. 插入测试数据
1 | INSERT INTO Department (Dname) VALUES ('Computer Science'); |
2. 测试 BEFORE
触发器
尝试更新一个学生的部门为一个不存在的部门:
1 | UPDATE Student SET SDEP = 'Physics' WHERE sno = '1001'; |
预期结果:触发器会抛出错误,更新操作失败。
3. 测试 AFTER
触发器
更新一个学生的部门为一个存在的部门:
1 | UPDATE Student SET SDEP = 'Mathematics' WHERE sno = '1001'; |
预期结果:更新成功,并且 UpdateLog
表中会有一条记录。
4. 查看 UpdateLog
表
1 | SELECT * FROM UpdateLog; |
分析说明
BEFORE 触发器:
- 在更新操作之前执行。
- 可以访问
OLD
和NEW
行数据。 - 可以修改
NEW
行的数据,从而影响最终的更新结果。 - 适用于需要在数据更改前进行验证、数据清洗或取消操作的场景。
AFTER 触发器:
- 在更新操作之后执行。
- 只能访问
OLD
和NEW
行数据,但不能修改NEW
行的数据。 - 适用于需要在数据更改后进行记录日志、发送通知或其他后续操作的场景。
通过这些分析和测试,你可以更好地理解 BEFORE
和 AFTER
触发器的区别及其适用场景。希望这些信息对你完成实验报告有所帮助!如果有任何进一步的问题,请随时提问。
第7关:创建一个新的课程成绩统计表
本关任务:创建一个新的课程成绩统计表 CAvgGrade ( Cno, Snum, examSNum, avgGrade ),分别表示课号,选该课程的学生人数,参加考试人数,该门课程的平均成绩。
学生需要自行创建CAvgGrade表
create table CAvgGrade(
Cno char(2),
Snum int,
examSNum int,
avgGrade int
)
利用触发器实现如下的功能:当 SC 表中插入、删除或者更新某个人的成绩时,自动更新表 CAvgGrade 。注意 SC 表中的 grade 为 NULL 时表明该学生还未参加考试,计算平均成绩时不需要计算该成绩,但是 grade 为0即考试成绩为0时,计算平均成绩需要计算该学生成绩。
提示:在 MySQL 中需要针对插入、更新、删除动作需分别创建3个触发器。可以先设计并实现一个公共的存储过程,然后在3个触发器中调用该存储过程。
相关知识
为了完成本关任务,你需要掌握:
1.如果需要借助其他临时表,可以自行创建,如:
create table tmp(
Cno char(2),
numnull int
)
2.如何创建一个新的课程成绩统计表。
编程要求
根据提示,在右侧代码文件中补充代码,完成本关任务。
使用数据库 use Student;
创建一个新的课程成绩统计表和相应的触发器。
1 | USE Student; |
JDBC 银行场景化综合应用实验
第1关:JDBC查询某个指定银行卡的基本信息
1 | import java.sql.Connection; |
第2关:JDBC插入银行发售的新理财产品数据
1 | import java.sql.Connection; |
第3关:JDBC调用存储过程 buy_fp购买理财产品
1 | import java.sql.*; |
银行场景化综合SQL应用
1. 注册新客户,办卡,插入客户记录
1 | #使用 finance 数据库 |
第2关:添加外键以及金额等约束
1 | -- 添加外键约束 |
第3关:模拟银行各种不同业务查询完成SQL
1 | #使用 finance 数据库 |
第4关:创建持有银行卡客户的视图
1 | #使用 finance 数据库 |
第5关:修改持有银行卡的客户视图
1 | #使用 finance 数据库 |
第6关:为了提高查询速度,创建复合索引
1 | -- 在基金购买表 c_fund 上创建复合索引 IX_ciq |
第7关:利用SQL或存储过程模拟理财产品购买过程
1 | -- 使用 finance 数据库 |
实验总结
1. 数据库连接问题
问题描述
- 无法连接到数据库。
- 抛出
SQLException
异常,提示“无法建立连接”。
解决方案
- 检查数据库服务是否启动:
- 确保 MySQL 服务正在运行。可以通过命令行工具或 MySQL Workbench 检查服务状态。
- 检查连接字符串:
- 确认 URL、用户名和密码正确无误。例如:
1
2
3String url = "jdbc:mysql://127.0.0.1:3306/finance?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF8&useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "123123";
- 确认 URL、用户名和密码正确无误。例如:
- 防火墙设置:
- 确保防火墙没有阻止数据库连接。如果需要,可以临时关闭防火墙进行测试。
- 依赖库:
- 确保项目中包含了 MySQL JDBC 驱动(
mysql-connector-java
)。可以通过 Maven 或 Gradle 添加依赖:1
2
3
4
5
6<!-- Maven -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>1
2// Gradle
implementation 'mysql:mysql-connector-java:8.0.23'
- 确保项目中包含了 MySQL JDBC 驱动(
2. 参数类型不匹配
问题描述
- 传递给存储过程的参数类型不匹配。
- 抛出
SQLException
异常,提示“数据截断”或“类型不匹配”。
解决方案
- 检查参数类型:
- 确保 Java 代码中设置的参数类型与存储过程中定义的参数类型一致。例如:
1
2
3
4
5cStmt.setInt(1, customerId); // 客户ID
cStmt.setInt(2, productId); // 产品ID
cStmt.setInt(3, quantity); // 购买数量
cStmt.setString(4, cardNumber); // 卡号
cStmt.setString(5, purchaseTimeStr); // 购买时间(字符串)
- 确保 Java 代码中设置的参数类型与存储过程中定义的参数类型一致。例如:
- 调试信息:
- 在设置参数前打印每个参数的值,确保它们正确无误:
1
2
3
4
5System.out.println("客户ID: " + customerId);
System.out.println("产品ID: " + productId);
System.out.println("购买数量: " + quantity);
System.out.println("卡号: " + cardNumber);
System.out.println("购买时间: " + purchaseTimeStr);
- 在设置参数前打印每个参数的值,确保它们正确无误:
3. 存储过程调用失败
问题描述
- 存储过程调用失败,抛出
SQLException
异常。 - 提示“存储过程不存在”或“存储过程调用失败”。
解决方案
- 检查存储过程是否存在:
- 确保存储过程
buy_fp
已经在数据库中创建。可以通过 MySQL Workbench 或命令行工具检查:1
SHOW PROCEDURE STATUS LIKE 'buy_fp';
- 确保存储过程
- 检查存储过程定义:
- 确保存储过程的定义与 Java 代码中的调用一致。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39DELIMITER //
CREATE PROCEDURE buy_fp(
IN p_customer_id INT,
IN p_product_id INT,
IN p_quantity INT,
IN p_card_number CHAR(30),
IN p_purchase_time_str VARCHAR(50)
)
BEGIN
DECLARE v_balance DECIMAL(10, 2);
DECLARE v_product_price DECIMAL(10, 2);
DECLARE v_purchase_time TIMESTAMP;
-- 将字符串转换为 TIMESTAMP
SET v_purchase_time = STR_TO_DATE(p_purchase_time_str, '%Y/%m/%d %H:%i:%s');
-- 获取产品价格
SELECT f_price INTO v_product_price FROM finances_product WHERE f_id = p_product_id;
-- 获取银行卡余额
SELECT b_balance INTO v_balance FROM bank_card WHERE b_number = p_card_number;
-- 检查余额是否足够
IF v_balance < (v_product_price * p_quantity) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '余额不足';
END IF;
-- 插入购买记录
INSERT INTO c_finances (c_id, f_id, f_quantity, f_card_number, f_purchase_time)
VALUES (p_customer_id, p_product_id, p_quantity, p_card_number, v_purchase_time);
-- 更新银行卡余额
UPDATE bank_card
SET b_balance = b_balance - (v_product_price * p_quantity)
WHERE b_number = p_card_number;
END //
DELIMITER ;
- 确保存储过程的定义与 Java 代码中的调用一致。例如:
4. 日期时间格式问题
问题描述
- 日期时间格式不正确,导致存储过程调用失败。
- 抛出
SQLException
异常,提示“数据截断”或“日期时间格式错误”。
解决方案
- 使用
STR_TO_DATE
函数:- 在存储过程中使用
STR_TO_DATE
函数将字符串转换为TIMESTAMP
类型。例如:1
SET v_purchase_time = STR_TO_DATE(p_purchase_time_str, '%Y/%m/%d %H:%i:%s');
- 在存储过程中使用
- 检查日期时间格式:
- 确保传递的日期时间字符串格式正确。例如:
1
String purchaseTimeStr = "2023/10/15 15:00:00";
- 确保传递的日期时间字符串格式正确。例如:
5. 异常处理
问题描述
- 未捕获到预期的异常,导致程序崩溃或输出不明确的错误信息。
解决方案
- 捕获特定的异常:
- 使用
if-else
语句捕获特定的异常并输出明确的错误信息。例如:1
2
3
4
5
6
7
8
9
10
11catch (SQLException ex) {
if (ex.getMessage().contains("余额不足")) {
System.out.println("购买失败:该笔交易支付的银行卡余额不足");
} else {
System.out.println("购买失败:" + ex.getMessage());
}
} catch (ParseException ex) {
System.out.println("购买失败:日期解析错误:" + ex.getMessage());
} catch (Exception ex) {
System.out.println("购买失败:" + ex.getMessage());
}
- 使用
总结
通过上述解决方案,可以有效地解决实验中可能出现的各种问题。确保每一步都仔细检查和调试,可以帮助你更顺利地完成实验任务。希望这些解决方案对你有所帮助!
好的,以下是对本次实验的总结,包括实验目的、遇到的问题及解决方案、实验成果和改进建议。
实验总结
实验目的
本次实验的目的是通过 Java 代码调用 MySQL 存储过程,实现理财产品的购买功能。具体包括以下几个方面:
- 连接 MySQL 数据库。
- 定义购买信息(客户ID、产品ID、购买数量、卡号、购买时间)。
- 调用存储过程
buy_fp
完成购买操作。 - 处理存储过程返回的结果,输出购买成功或失败的信息。
遇到的问题及解决方案
数据库连接问题
- 问题描述:无法连接到数据库,抛出
SQLException
异常,提示“无法建立连接”。 - 解决方案:
- 检查 MySQL 服务是否启动。
- 确认 URL、用户名和密码正确无误。
- 确保防火墙没有阻止数据库连接。
- 确保项目中包含了 MySQL JDBC 驱动。
- 问题描述:无法连接到数据库,抛出
参数类型不匹配
- 问题描述:传递给存储过程的参数类型不匹配,抛出
SQLException
异常,提示“数据截断”或“类型不匹配”。 - 解决方案:
- 确保 Java 代码中设置的参数类型与存储过程中定义的参数类型一致。
- 在设置参数前打印每个参数的值,确保它们正确无误。
- 问题描述:传递给存储过程的参数类型不匹配,抛出
存储过程调用失败
- 问题描述:存储过程调用失败,抛出
SQLException
异常,提示“存储过程不存在”或“存储过程调用失败”。 - 解决方案:
- 确保存储过程
buy_fp
已经在数据库中创建。 - 确存储过程的定义与 Java 代码中的调用一致。
- 确保存储过程
- 问题描述:存储过程调用失败,抛出
日期时间格式问题
- 问题描述:日期时间格式不正确,导致存储过程调用失败,抛出
SQLException
异常,提示“数据截断”或“日期时间格式错误”。 - 解决方案:
- 在存储过程中使用
STR_TO_DATE
函数将字符串转换为TIMESTAMP
类型。 - 确保传递的日期时间字符串格式正确。
- 在存储过程中使用
- 问题描述:日期时间格式不正确,导致存储过程调用失败,抛出
异常处理
- 问题描述:未捕获到预期的异常,导致程序崩溃或输出不明确的错误信息。
- 解决方案:
- 使用
if-else
语句捕获特定的异常并输出明确的错误信息。
- 使用
实验成果
- 成功连接到数据库:通过配置正确的连接字符串和依赖库,成功建立了与 MySQL 数据库的连接。
- 正确设置参数:确保 Java 代码中设置的参数类型与存储过程中定义的参数类型一致,避免了类型不匹配的问题。
- 成功调用存储过程:通过正确的参数设置和异常处理,成功调用了存储过程
buy_fp
,完成了购买操作。 - 处理结果:根据存储过程的返回值判断购买是否成功,并输出了相应的信息。
改进建议
- 增强异常处理:进一步细化异常处理逻辑,增加对更多潜在错误的捕获和处理。
- 优化代码结构:将数据库连接、参数设置、存储过程调用等步骤封装成独立的方法,提高代码的可读性和可维护性。
- 增加日志记录:在关键步骤中增加日志记录,方便后续调试和问题排查。
- 单元测试:编写单元测试用例,确保每个模块的功能正确无误。
结论
通过本次实验,我们成功实现了通过 Java 代码调用 MySQL 存储过程完成理财产品的购买功能。实验过程中遇到了一些常见的问题,通过仔细检查和调试,最终解决了这些问题,达到了预期的目标。希望这些总结和建议能够帮助你在未来的开发中更加顺利。