六、企业级开发技术

六、企业级开发技术

6.1 存储过程

关于存储过程我只能说请看下图,这是阿里巴巴发布的《阿里巴巴Java开发手册(终极版)v1.3版本》在 MySQL 第七条中强制指出禁止使用存储过程

所以对于存储过程不必深究,做到会写能看懂即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hUsQx6Tf-1680617792594)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230329084802.png)]

6.1.1 什么是存储过程

Stored Procedure

  • 是一组为了完成特定功能的 SQL 语句集合
  • 经编译后保存在数据库中
  • 通过指定存储过程的名字并给出参数的值
  • MySQL5.0 版本开始支持存储过程,使数据库引擎更加灵活和强大

6.1.2 存储过程可以包含

  • 可带参数,也可返回结果
  • 可包含数据操纵语句、变量、逻辑控制语句等

6.1.3 存储过程的优缺点

优点

  • 减少网络流量
  • 提升执行速度
  • 减少数据库连接次数
  • 安全性高
  • 复用性高

缺点

  • 可移植性差

SQL 最大的缺点还是 SQL 语言本身的局限性 SQL 本身是一种结构化查询语言,我们不应该用存储过程处理复杂的业务逻辑让 SQL 回归它结构化查询语言的功用。复杂的业务逻辑,还是交给代码去处理吧

6.1.4 创建存储过程

CREATE
    [DEFINER = { user | CURRENT_USER }]  
    # 定义DEFINER默认为当前用户
PROCEDURE 存储过程名
    [SQL SECURITY { DEFINER | INVOKER } | …]
    # 指定DEFINER或INVOKER权限
BEGIN
    …
END
特性 说明
LANGUAGE SQL 表示存储过程语言,默认SQL
{CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA} 表示存储过程要做的工作类别默认值为CONTAINS SQL
SQL SECURITY { DEFINER | INVOKER } 指定存储过程的执行权限默认值是DEFINERDEFINDER:使用创建者的权限INVOKER:用执行者的权限
COMMENT ‘string’ 存储过程的注释信息

如果省略 SQL SECURITY 特性,则使用 DEFINER 属性指定调用者,且调用者必须具有 EXECUTE 权限,必须在 mysql.user 表中如果将 SQL SECURITY 特性指定为 INVOKER,则 DEFINER 属性无效

6.1.5 定义存储过程的参数

IN:指输入参数

  • 该参数的值必须在调用存储过程时指定
  • 存储过程中可以使用该参数,但它不能被返回

OUT:指输出参数

  • 该参数可以在存储过程中发生改变,并可以返回

INOUT:指输入输出参数

  • 该参数的值在调用存储过程时指定
  • 在存储过程中可以被改变和返回

如果需要定义多个参数,需要使用,进行分隔

6.1.6 调用存储过程

CALL 存储过程名([参数1,参数2, …]);
# 根据存储过程的定义包含相应的参数

存储过程调用类似于Java中的方法调用

6.1.7 查看存储过程状态

SHOW PROCEDURE STATUS

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xI3lnwUE-1680617792596)(./assets/image-20230403133422488.png)]

6.1.8 查看存储创建代码

SHOW CREATE PROCEDURE 存储过程名

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bCB1Meir-1680617792597)(./assets/image-20230403133612810.png)]

6.1.9 修改存储过程

ALTER PROCEDURE 存储过程名[特性………]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HpV3zvNd-1680617792597)(./assets/image-20230403133809483.png)]

6.1.10 删除存储过程

DROP PROCEDURE 存储过程名

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kefN8vXn-1680617792597)(./assets/image-20230403133956314.png)]

6.1.11 存储过程中的变量

与Java语言类似,定义存储过程时可以使用变量

DECLARE 变量名[,变量名...] 数据类型 [DEFAULT 值];

给变量进行赋值

SET 变量名 = 表达式值[,变量名=表达式...] ;

定义存储过程时,所有局部变量的声明一定要放在存储过程体的开始;否则,会提示语法错误

系统变量

  • 指 MySQL 全局变量,以@@开头,形式为@@变量名

用户自定义变量

  • 局部变量
    • 一般用于SQL的语句块中,如:存储过程中的BEGIN和END语句块
    • 作用域仅限于定义该变量的语句块内
    • 生命周期也仅限于该存储过程的调用期间
    • 在存储过程执行到END时,局部变量就会被释放
  • 会话变量
    • 是服务器为每个客户端连接维护的变量,与MySQL客户端是绑定的
    • 也称作用户变量
    • 可以暂存值,并传递给同一连接中其他SQL语句进行使用
    • 当MySQL客户端连接退出时,用户变量就会被释放
    • 用户变量创建时,一般以@开头,形式为@变量名

演示案例

  • 根据病人名称和检查项目ID输出最后一次检查时间
CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_exam_GetLastExamDateByPatientNameAndDepID`(IN patient_name VARCHAR(50), IN dep_id INT,OUT last_exam_date DATETIME)
BEGIN
	#Routine body goes here...
  DECLARE patient_id INT;  #声明局部变量
  SELECT patientID INTO patient_id FROM patient WHERE patientName= patient_name;
  SELECT patient_id; #输出病人的ID
  SELECT MAX(examDate) INTO last_exam_date FROM prescription WHERE patientID = patient_id AND depID = dep_id;
END
  • 调用存储过程
SET  @patient_name='夏颖';
SET  @dep_id =1;
CALL proc_exam_GetLastExamDateByPatientNameAndDepID(@patient_name, @dep_id, @last);

SELECT @last;

6.1.12 存储过程控制语句

与Java语言的流程控制语句类似,MySQL提供的控制语句

  • 条件语句
    • IF-ELSEIF-ELSE 条件语句
    • CASE 条件语句
  • 循环语句
    • WHILE 循环
    • LOOP 循环
    • REPEAT循环
  • 迭代语句

6.1.13 IF-ELSEIF-ELSE 条件语句

IF 条件 THEN 语句列表
   [ELSEIF 条件 THEN 语句列表]
   [ELSE 语句列表]
END IF;

根据病人的家庭收入,返还补贴不同比例的医疗费用

  • 家庭年收入在5000元以下的返还当年总医疗费用的20%
  • 家庭年收入在10000以下的返还当年总医疗费用的15%
  • 家庭年收入在30000以下的返还总医疗费用的5%
  • 30000元以上或未登记的不享受医疗费用返还
  • 输入病人编号和年份,计算该患者当年的应返还的医疗费用
CREATE DEFINER=`root`@`localhost` PROCEDURE `Proc_CH05_4`(IN patient_ID INT ,IN in_year VARCHAR(50),OUT ou_subsidy FLOAT  )
BEGIN
 DECLARE tital_Cost FLOAT;
 DECLARE totial_income FLOAT;
 
SELECT incomeMoney INTO totial_income FROM income WHERE patientID =patient_ID;

SELECT sum(checkItemCost) INTO tital_Cost FROM prescription  
INNER JOIN checkitem ON prescription.checkItemID=checkitem.checkItemID 
WHERE patientID=patient_ID AND examDate >= CONCAT(in_year,'-01-01') 
AND examDate <= CONCAT(in_year,'-12-31');


IF totial_income>=0 AND totial_income<5000 THEN
	SET ou_subsidy =tital_Cost*0.2;
ELSEIF totial_income>=5000 AND totial_income<10000 THEN
	SET ou_subsidy =tital_Cost*0.15;
ELSEIF totial_income>=10000 AND totial_income<30000 THEN
	SET ou_subsidy =tital_Cost*0.05;
ELSE
	SET ou_subsidy =0;
END IF;
END

6.1.14 CASE 条件语句

CASE
   WHEN 条件 THEN 语句列表
   [WHEN 条件 THEN 语句列表]
   [ELSE 语句列表]
END CASE;
CASE 列名
   WHEN 条件值 THEN 语句列表
   [WHEN 条件值 THEN 语句列表]
   [ELSE 语句列表]
END CASE;

使用CASE语句实现返还补贴不同比例的医疗费用

CREATE DEFINER=`root`@`localhost` PROCEDURE `Proc_CH05_5`(IN patient_ID INT ,IN in_year VARCHAR(50),OUT ou_subsidy FLOAT  )
BEGIN
 DECLARE tital_Cost FLOAT;
 DECLARE totial_income FLOAT;
 
SELECT incomeMoney INTO totial_income FROM income WHERE patientID =patient_ID;


SELECT sum(checkItemCost) INTO tital_Cost FROM prescription  
INNER JOIN checkitem ON prescription.checkItemID=checkitem.checkItemID 
WHERE patientID=patient_ID AND examDate >= CONCAT(in_year,'-01-01') 
AND examDate <= CONCAT(in_year,'-12-31');


CASE 
	WHEN totial_income>=0 AND totial_income<5000 THEN
		SET ou_subsidy =tital_Cost*0.2;
	WHEN totial_income>=5000 AND totial_income<10000 THEN
		SET ou_subsidy =tital_Cost*0.15;
	WHEN totial_income>=10000 AND totial_income<30000 THEN
		SET ou_subsidy =tital_Cost*0.05;
	WHEN totial_income>=30000 AND totial_income<0 THEN
		SET ou_subsidy =0;
END CASE;

END

在某种情况下(例如,做等值判断),使用第二种写法更加简洁但是,因为CASE后面有列名,功能上会有一些限制


6.1.15 WHILE 循环语句

[label:] WHILE 条件 DO
   语句列表
END WHILE [label]
  • 首先判断条件是否成立。如果成立,则执行循环体
  • label为标号,用于区分不同的循环,可省略
  • 用在begin、repeat、while 或者loop 语句前

假设有测试表test,有Id字段、Val字段

  • 根据输入的行数要求,批量插入测试数据
DECLARE rand_val FLOAT;
WHILE rows > 0 DO
  SELECT RAND() INTO rand_val;
  INSERT INTO test VALUES(NULL, rand_val);
  SET rows = rows - 1;
END WHILE;

6.1.16 LOOP 循环语句

[label:] LOOP
   语句列表
END LOOP [label] ;

不需判断初始条件,直接执行循环体

LEAVE label ;

遇到 LEAVE 语句,退出循环

批量插3个新的检查项目,检查项目名称为胃镜、肠镜和支气管纤维镜,各项检查的价格均为70元

CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_checkitem_insert`( IN checkitems VARCHAR(100))
BEGIN
 	DECLARE comma_pos INT;
  DECLARE current_checkitem VARCHAR(20);
	loop_label: LOOP
		SET comma_pos = LOCATE(',', checkitems);
		SET current_checkitem = SUBSTR(checkitems, 1, comma_pos-1);
		IF current_checkitem <> '' THEN
			SET checkitems = SUBSTR(checkitems, comma_pos+1);
    ELSE
      SET current_checkitem = checkitems;
    END IF;
    INSERT INTO checkitem(checkItemName,checkItemCost) VALUES(current_checkitem,70);
		IF comma_pos=0 OR current_checkitem='' THEN
      LEAVE loop_label;
      # 退出loop_label标识的程序块
    END IF;
	END LOOP loop_label;
	# LOOP循环结束
END

6.1.17 REPEAT 循环语句

[label:] REPEAT
   语句列表
UNTIL 条件
END REPEAT [label]
  • 先执行循环操作再判断循环条件
  • 与 LOOP 循环语句相比较相同点
  • 不需要初始条件直接进入循环体
  • 不同点:REPEAT 语句可以设置退出条件

使用REPEAT循环语句编码实现,根据输入的行数要求,向测试表test中批量插入测试数据

CREATE DEFINER=`root`@`localhost` PROCEDURE `Proc_CH05_7`(IN rows INT )
BEGIN
  
	DECLARE rand FLOAT;
	
	REPEAT
	SELECT RAND() INTO rand;
		INSERT INTO test (val)VALUES(rand);
		SET rows = rows -1 ;
  UNTIL rows <= 0 END REPEAT;

END

6.1.18 迭代语句

ITERATE label;
  • 从当前代码处返回到程序块开始位置,重新执行
  • ITERATE关键字可以嵌入到LOOP、WHILE和REPEAT程序块中

输入需增加数据行数,随机产生的测试数据必须大于0.5

CREATE DEFINER=`root`@`localhost` PROCEDURE `Proc_CH05_8`(IN rows INT)
BEGIN 
	DECLARE rand FLOAT; 
	random_lbl : REPEAT
		SELECT RAND() INTO rand; 
			IF rand< 0.5 THEN 
				ITERATE random_lbl; 
			END IF;
		INSERT INTO test (val) VALUES (rand);
		SET rows=rows-1; 
	UNTIL rows<=0 END REPEAT; 
END

6.2 事务

6.2.1 什么是事务

TRANSACTION

  • 是将一系列数据操作捆绑成为一个整体进行统一管理机制
  • 多个操作作为一个整体向系统提交,要么都执行、要么都不执行
  • 是一个不可分割的工作逻辑单元

6.2.2 事务的特性

事务必须具备的属性,简称 ACID 属性

ACID 属性描述
原子性:Atomicity 事务是一个完整的操作,事务的各步操作是不可分的(原子的),要么都执行,要么都不执行
一致性:Consistency 当事务完成时,数据必须处于一致状态
隔离性:Isolation 并发事务之间彼此隔离、独立,不应以任何方式依赖于或影响其他事务
持久性:Durability 事务完成后,它对数据库的修改被永久保持

6.2.3 如何创建事务

MySQL中支持事务的存储引擎

  • InnoDB支持事务操作

    • 通过 UNDO 日志和 REDO 日志实现对事务的支持

    • UNDO 日志

      • 复制事务执行前的数据,用于在事务发生异常时回滚数据
    • REDO 日志

      • 记录在事务执行中,每条对数据进行更新的操作
      • 当事务提交时,该内容将被刷新到磁盘
  • MyISAM不支持事务操作

实现事务的方式

  • SQL语句
  • 设置自动提交关闭或开启

在执行命令SET autocommit=0,禁止当前会话的自动提交后,后面的SQL语句将作为事务中的语句一同提交

6.2.4 事务处理

开始事务

BEGIN ;
或
START TRANSACTION;

提交事务

COMMIT ;

回滚(撤销)事务

ROLLBACK ;

使用事务实现小王和小张之间的转账操作

USE paycorp;
BEGIN;
    UPDATE account SET balance=balance-2000 WHERE accountName='小王';
    UPDATE account SET balance=balance+2000 WHERE accountName='小张';
COMMIT;

小王和小张的总账户余额和转账前保持一致,数据库中数据从一个一致性状态更新到另一个一致性状态

6.2.5 自动关闭和开启事务

  • 默认情况下,每条单独的SQL语句视为一个事务

  • 关闭默认提交状态后,可手动开启、关闭事务

关闭/开启自动提交

 SET autocommit = 0|1;
  • 状态值为0:关闭自动提交
  • 值为1:开启自动提交

关闭自动提交后,从下一条SQL语句开始将会开启新事务,需使用COMMIT或ROLLBACK语句结束该事务

6.2.6 遵循原则

事务尽可能简短

  • 事务启动至结束后在数据库管理系统中保留大量资源,以保证事务的原子性、一致性、隔离性和持久性
  • 如果在多用户系统中,较大的事务将会占用系统的大量资源,使得系统不堪重负,会影响软件的运行性能,甚至导致系统崩溃

事务中访问的数据量尽量最少

  • 当并发执行事务处理时,事务操作的数据量越少,事务之间对操作数据的争夺就越少

查询数据时尽量不要使用事务

  • 对数据进行浏览查询操作并不会更新数据库的数据时,尽量不使用事务查询数据,避免占用过量的系统资源

在事务处理过程中尽量不要出现等待用户输入的操作

  • 处理事务的过程中,如果需要等待用户输入数据,那么事务会长时间占用资源,有可能造成系统阻塞

6.3 视图

6.3.1 为什么需要视图

不同的人员关注不同的数据

保证信息的安全性

6.3.2 什么是视图

视图是一张虚拟表

  • 表示一张表的部分数据或多张表的综合数据
  • 其结构和数据是建立在对表的查询基础上

视图中不存放数据

  • 数据存放在视图所引用的原始表中

一个原始表,根据不同用户的不同需求,可以创建不同的视图

  • 筛选表中的行
  • 防止未经许可的用户访问敏感数据
  • 降低数据库的复杂程度
  • 将多个物理数据表抽象为一个逻辑数据表

6.3.3 视图的好处

开发人员

  • 限制数据检索更容易
  • 维护应用程序更方便

最终用户

  • 结果更容易理解
  • 获得数据更容易

6.3.4 创建 / 查看视图

使用SQL语句创建视图

CREATE VIEW view_name  
   AS