已完结全文五万字_玄子Share-BCSP助学手册之数据库开发

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

玄子Share-BCSP助学手册之数据库开发

前言:

此文为玄子,学习 BCSP 一二期后整理的文章,文中对知识点的解释仅为个人理解,源码均可直接复制运行

配套PPT,站点源码,等学习资料请加文章封面联系方式

针对课程的一些调整:

  1. 如果你当前正在学习BCSP S1课程的C sharp语言和SQL Server数据库技术并不影响阅读浏览这篇文章,SQL ServerMySQL的绝大部分SQL语法都是相通的,关于SQL语法我均写了详细的介绍
  2. 一期 C sharp语言和SQL Server数据库技术与二期JAVA开发之JAVA数据库编程这两本书的相似度较高,所以针对数据库编程,只写了二期的JAVA开发之JAVA数据库编程
  3. 这篇文章与之前的玄子Share-BCSP助学手册之JAVA开发以及玄子Share-BCSP助学手册之前端开发属于同一系列Java开发之Java数据库编程这本书所学,几乎就是以后工作的日常操作,所以这篇文章,会加入更多关于我对数据库及JDBC的理解,相对前两篇文章,会比较啰嗦,请谅解
  4. 文章演示的数据库为BCSP配套数据库hospital文章第八章附件有源文件,可直接复制运行
  5. 文章的部分知识相对课程有增改,以课本为准
  6. 课程中部分软件涉及版权无法展示安装教程,可加封面联系方式获取软件和安装教程

目录

文章目录

一、课程软件的安装与卸载

1.1 MySQL 版本选择与安装方式

Mysql

1.1.1 MySQL 版本选择

按照教材上的要求,统一使用MySQL版本号为5.7.40的版本

1.1.2 MySQL 安装方式

MySQL 的安装方式有两种

  1. 安装器直接安装
  2. 压缩包配置安装

安装器安装,快速便捷,安装过程均为图像操作界面,但缺点是卸载,极其麻烦,不仅要删除MySQL文件,还要删除注册表,等相关信息。如果卸载不干净,影响以后的安装配置等。

压缩包安装,相对比较麻烦,需要解压后,自行配置MySQL服务等,但优点是,卸载非常简单,只需要把解压文件删除即可。

我推荐使用,压缩包解压配置的方式安装MySQL,虽然麻烦了一点,但后期使用MySQL出现问题,需要重新安装MySQL就会很舒服。

两种安装方式我都写有教程,不用纠结使用哪种安装方式,能安装上就行了。


1.2 MySQL 压缩包安装教程

1.2.1 下载链接

官方 https://downloads.mysql.com/archives/community/

度盘 https://pan.baidu.com/s/1fuGdLIDXx2-2b5mEdqEh3g?pwd=xzsr

1.2.2 安装 MySQL

注意选择版本号为5.7.40

下载完后我们会得到一个以 MySQL 开头命名的压缩包,我们需要把这个压缩包解压到本地

这里可以把 MySQL 文件名后面的-winx64删除掉(度盘下载的压缩包我已经删除过了)

推荐将文件解压到 C 盘的 Program Files 文件下,即解压路径为C:\Program Files\mysql-5.7.40

1.2.3 添加配置文件

C:\Program Files\mysql-5.7.40文件夹内新建一个文本文档,【强制】命名为:my.ini

注意开启显示文件拓展名设置

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

如果在度盘下载的压缩包,我在压缩包中已经写入过my.ini文件,只需要注意自己的安装路径是否和配置文件内路径一致即可

使用记事本方法打开my.ini文件,将下面的配置文件粘贴进去

[mysqld]
#设置3306端口
port = 3306 
# 设置mysql的安装目录
basedir=C:\Program Files\mysql-5.7.40
# 设置mysql数据库的数据的存放目录
datadir=C:\Program Files\mysql-5.7.40\data
# 允许最大连接数
max_connections=200
# 服务端使用的字符集默认为8比特编码的latin1字符集
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
#跳过密码
skip-grant-tables

basedir:就是mysql的安装路径

datadir:只需要把\data前面的路径替换即可

文本里的代码前面是没有数字的

1.2.4 配置环境变量

  1. 右键此电脑
  2. 属性
  3. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zYNgEaGg-1680618008610)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330224010.png)]
  4. 高级系统设置
  5. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PlkxL24f-1680618008610)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330224026.png)]
  6. 环境变量
  7. 下方系统变量,找到Path双击进入
  8. 新建系统变量,变量值为 mysql 安装bin目录(实际路径以自己安装时设置为准)
  9. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lsuiIp8g-1680618008611)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330224206.png)]
C:\Program Files\mysql-5.7.40\bin

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

  1. 配置好后,保存退出
  2. 按下Win + R然后通过管理员身份打开cmd
  3. 输入mysql --version查看环境是否配置成功

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

1.2.5 安装 MySQL 服务

  1. 按下键盘Win + R打开运行输入cmd以管理员身份运行
  2. cd 到 mysql 文件的 bin 目录中
cd /d C:\Program Files\mysql-5.7.40\bin
  1. 输入mysqld -install命令安装 MySQL 服务
  2. 继续输入mysqld --initialize-insecure初始化 data 目录
  3. 输入net start mysql启动 MySQL 服务
  4. 成功启动后,输入 mysql -uroot登录 MySQL
  5. 成功登录之后,输入下面代码
update mysql.user set authentication_string=password('root') where user='root' and Host = 'localhost';

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

  1. 这里的root就是登录密码,账户名也是root或者你可以把密码改成自己喜欢的
  2. 再输入flush privileges;刷新一下权限
  3. 最后输入exit退出 MySQL 即可

1.2.6 删除配置文件代码

退出后回到my.ini配置文件中,用#注释掉或者直接删除掉最后一行代码skip-grant-tables然后保存退出

到这里就已经成功下载并配置好了 MySQL

1.2.7 验证 MySQL 配置

  1. 命令行先输入net stop mysql关闭 MySQL 服务

  2. 在输入net start mysql开启 MySQL 服务

  3. 这里报错的请自己到服务中手动打开 MySQL 服务后重新尝试

  4. 登录数据库

mysql -uroot -proot

mysql -u账户名 -p密码

注意密码前后不能有空格

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

最后可以输入一个查询版本信息的SQl语句玩一下:select version(),user(); 代码后面有分号

1.2.8 完成下课

MySQL 压缩包安装教程


1.3 MySQL 安装器安装教程

1.3.1 下载链接

官方 https://downloads.mysql.com/archives/installer/

度盘 https://pan.baidu.com/s/1fuGdLIDXx2-2b5mEdqEh3g?pwd=xzsr

1.3.2 安装 MySQL

注意选择版本号为5.7.40

下载完后我们会得到一个以 MySQL 开头命名的msi安装器,直接双击运行安装

  1. 提示是否更新,点击NO即可
  2. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vQQpVNbM-1680618008613)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330223027.png)]
  3. 勾选下方Custom自定义安装
  4. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lK74EniP-1680618008613)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330223154.png)]
  5. 左边找到MySQL Server 5.7.40 - X64点击箭头添加到右边
  6. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sU5JjuVV-1680618008613)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330223349.png)]
  7. 点击Execute 执行安装即可
  8. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uoBYAK3O-1680618008614)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330223415.png)]
  9. 这个界面保持默认状态
  10. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ilKW14vg-1680618008614)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330223532.png)]
  11. 设置MySQL登录密码(默认设置为 root)
  12. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oOARyqya-1680618008615)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330225947.png)]
  13. 这里的MySQL57就是服务器名称,点击Next下一步
  14. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-60GkTQBK-1680618008615)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330223658.png)]
  15. 安装完毕
  16. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KRzR7tVL-1680618008615)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330223754.png)]

1.3.3 配置环境变量

  1. 右键此电脑
  2. 属性
  3. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KyTxI90M-1680618008615)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330224010.png)]
  4. 高级系统设置
  5. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VVRMOf14-1680618008616)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330224026.png)]
  6. 环境变量
  7. 下方系统变量,找到Path双击进入
  8. 新建系统变量,变量值为 mysql 安装bin目录(实际路径以自己安装时设置为准)
  9. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0fspwxDq-1680618008616)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330224206.png)]
C:\Program Files\MySQL\MySQL Server 5.7\bin
  1. 配置好后,保存退出
  2. 按下Win + R然后通过管理员身份打开cmd
  3. 输入mysql --version查看环境是否配置成功

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

1.3.4 验证 MySQL 配置

  1. 再次通过按下Win + R打开cmd窗口
  2. 输入账号密码mysql -uroot -proot(-u后的root为管理员账号,-p后的root为之前设置的MySQL登录密码,没有空格直接跟密码)
  3. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lVtUoPTc-1680618008617)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330224416.png)]

最后可以输入一个查询版本信息的SQl语句玩一下:select version(),user(); 代码后面有分号

1.3.5 完成下课

MySQL 安装器安装教程


1.4 MySQL 卸载教程

1.4.1 压缩包卸载

压缩包安装方式安装的MySQL,卸载就直接删除掉解压后的MySQL文件夹即可,然后删除配置的 MySQL 环境变量

1.4.2 安装器卸载

  1. 按下Win + R打开cmd窗口输入services.msc打开服务
  2. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AW2C5Dna-1680618008617)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330230901.png)]
  3. 找到MySQL停止服务
  4. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I8pGlJuQ-1680618008617)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330230926.png)]
  5. 打开控制面板
  6. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SSE6cN9h-1680618008618)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330230617-1680189633074-23.png)]
  7. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rcktxR7D-1680618008618)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330230629.png)]
  8. 这里的两个软件都要卸载掉
  9. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m2w8LiTT-1680618008618)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330230648.png)]

到这里只是卸载了MySQL软件,MySQL还有许多文件并没有删掉,如果不删除干净,会影响后续安装导致安装失败。

  1. 删除MySQL在电脑硬盘上物理位置上的所有文件
  2. C:\Program Files (x86)\MySQL内所有文件
  3. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MqIb59cv-1680618008619)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330231218.png)]
  4. C:\ProgramData\MySQL内所有文件,该文件是默认隐藏的,设置显示后即可见
  5. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SM5PUKPq-1680618008619)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330231312.png)]
  6. C:\Documents and Settings\All Users\Application Data\MySQL内所有文件
  7. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9UBEKZH0-1680618008619)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330231346.png)]

到这里也只是删掉了MySQL所有残余文件,还需要删除MySQL的注册表信息

  1. 按下Win + R打开cmd窗口输入regedit打开注册表
  2. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D7FMHaeM-1680618008620)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330231549.png)]
  3. 删除以下三项内所有信息(可直接粘贴至地址栏)
  4. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xe9Iiiqn-1680618008620)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230330231609.png)]
  5. HKEY_LOCAL_MACHINE/SYSTEM/ControlSet001/Services/Eventlog/Application/MySQL
  6. HKEY_LOCAL_MACHINE/SYSTEM/ControlSet002/Services/Eventlog/Application/MySQL
  7. HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Eventlog/Application/MySQL
  8. 到此MySQL算是彻底卸载完毕

1.5 MySQL管理工具安装

1.5.1 下载链接

Navicate官网 https://www.navicat.com/en/products

SqlYog官网 https://sqlyog.en.softonic.com/

度盘 https://pan.baidu.com/s/1fuGdLIDXx2-2b5mEdqEh3g?pwd=xzsr

1.5.2 Navicate 安装

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

Navicate 涉及版权无法分享,加封面联系方式获取软件

1.5.3 SqlYog 安装

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

SqlYog 涉及版权无法分享,加封面联系方式获取软件

1.5.4 PowerDesigner 安装

t01814cadb480e4610f

PowerDesigner 涉及版权无法分享,加封面联系方式获取软件


二、MySQL 数据库设计

2.1 为什么需要设计数据库

良好的数据库设计

  • 降低应用程序的开发难度
  • 具备高效的查询效率
  • 具备良好的扩展性

糟糕的数据库设计

  • 出现数据操作异常、修改复杂、数据冗余等问题
  • 影响程序的性能,甚至会导致程序崩溃

数据库设计是对数据库中实体以及这些实体之间的关系进行规划和结构化的过程,当数据库比较复杂时,需要设计数据库

2.1.1 软件项目开发周期中数据库设计

  • 需求分析阶段:分析客户的业务和数据处理需求
  • 概要设计阶段:设计数据库的E-R模型图,确认需求信息的正确和完整
  • 详细设计阶段:应用三大范式审核数据库结构
  • 代码实现阶段:物理实现数据库,编码实现应用
  • 软件测试阶段:……
  • 上线部署:……

2.1.2 设计数据库的步骤

收集信息

  • 通过对业务人员的访谈等方法,充分了解用户需求,理解数据库需要存储的业务信息(数据)及需要提供的操作

标识实体 (Entity)

  • 标识数据库要管理的关键对象或实体,实体一般是名词

标识每个实体的属性(Attribute)

标识实体之间的关系(Relationship)

数据库设计中,不能重复出现含义相同的实体


2.2 为什么使用 E-R 图

2.2.1 E-R 图概念

E-R图也称实体-联系图(Entity Relationship Diagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型

符号含义
实体,一般是名词
属性,一般是名词
关系,一般是动词

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

  • E-R图表示映射关系时,也可以通过添加箭头区分一对多和多对一
  • 箭头指向映射关系为一的实体
  • 在业务人员与开发人员的沟通中,E-R图能够极大的提高彼此之间的沟通效率

2.3 数据库设计模型转换

2.3.1 关系型数据库设计分类

  • 概念数据模型设计

  • 逻辑数据模型设计

  • 物理数据模型设计

2.3.2 各模型之间的元素对应关系

概念数据模型逻辑数据模型物理数据模型
实体(Entity)实体(Entity)表(Table)
属性(Attribute)属性(Attribute)列(Column)
标识符(Identifier)标识符(Primary Identifier/ Foreign Identifier)键(Primary key/ Foreign key)
关系(Relationship)关系(Relationship)参照完整性约束(Reference)

2.3.3 数据模型转换方案

基本转换原理

  • 将E-R图中每一个实体转换成一个表,实体的属性转换为表的列,实体的标识符转换为表的主键

  • 将实体关系转化为表之间的引用完整性约束

    • 根据关系的不同类型,通过外键引用主键的方式有所不同

2.4 实体关系约束转换

2.4.1 一对多关系的两个实体

  • 一般会各自转换为一张表,且后者对应的表引用前者对应的表

  • 一个表中的主键对应另一个表中可重复的字段

    • 主键的值是不能重复的,关联的字段是可以重复的

    • 存在一个值对应一个值(一对一)或者一个值对应多个值(一对多)

2.4.2 一对一关系的两个实体

  • 一般是一个主键对应一个不可重复的字段

2.4.3 多对多关系的两个实体

  • 除了将多对多关系中的两个实体各自转换为表外,一般还会创建第3个表,称为连接表
    • 将多对多关系划分为两个一对多关系,将这两个表主键都插入到第3个表中

2.5 PowerDesigner 软件

t01814cadb480e4610f
  • Sybase 公司开发的一款强大的数据库设计软件
  • 开发人员经常使用的数据库建模工具
  • 能够使用设计好的数据库模型直接生成SQL建表语句
  • 极大的提升数据库的开发效率

推荐使用的版本:PowerDesigner 16.5

PowerDesigner 涉及版权无法分享,加封面联系方式获取软件

2.5.1 绘制数据库模型图

  • 创建物理数据模型
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3RDCabyC-1680618008622)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230331235814.png)]
  • 绘制模型图
  • 创建表实体
  • 添加属性
  • 添加实体之间的映射关系
  • 两个实体间连线,箭头指向主表
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L0aMngHd-1680618008622)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230401000646.png)]

2.5.2 生成 SQL 代码

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

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

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

生成的 SQL文件可直接拿到 Navicate 运行

PowerDesigner中开发数据库模型图时,需要指定该模型图是为数据库管理系统开发的,因为PowerDesigner支持用数据库模型图生成数据库创建脚本的功能


2.6 数据库设计范式

为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则

2.6.1 三大数据库设计范式

  1. 第一范式

    • 第一范式的目标是确保每列保持原子性
    • 如果每列都是不可再分的最小数据单元(也称为最小的原子单元),则满足第一范式(1NF)
  2. 第二范式

    • 第二范式的目标是确保表中的每列都和主键相关
    • 要求每个表只描述一件事情
  3. 第三范式

    • 第三范式的目标是确保每列都和主键列直接相关,而不是间接相关
    • 如果一个关系满足第二范式,并且除了主键以外的其他列都不传递依赖于主键列,则满足第三范式(3NF)

2.7 规范化和性能的关系

为满足某种商业目标,数据库性能比规范化数据库更重要

  • 通过在给定的表中添加额外的字段,以大量减少需要从中搜索信息所需的时间

  • 通过在给定的表中插入计算列(如成绩总分),以方便查询

  • 在数据规范化同时,要综合考虑数据库的性能

2.7.1 演示案例

名称商品型号单价数量金额
电视机29英寸250040100000

金额 = 单价 * 数量,是冗余的列

为了提高查询统计速度,以空间换取时间

不要轻易违反数据库设计的规范化原则,如果处理不好,可能会适得其反,使应用程序运行速度更慢


2.8 用三大范式设计表结构

2.8.1 实现思路

  1. 向各表中插入数据,查看表中的每个属性列是否存在重复、插入异常、更新异常和删除异常

  2. 对照三大范式的定义,解决表中的异常问题

  3. 第一范式的目标是确保每列都是不可再分的最小数据单元,查看每列是否都满足第一范式

  4. 第二范式的每列与主键相关,不相关的放入别的表中,即要求一个表只描述一件事情

  5. 第三范式要求表中各列必须和主键直接相关,不能间接相关,查看各表,满足第三范式,对于不满足三大范式的表要进行拆分


三、MySQL 基础

3.1 MySQL 简介

Mysql

MySQL 是一款流行的开源数据库,也是一个关系型数据库管理系统

在 WEB 应用方面 MySQL 是最好的 RDBMS(Relational Database Management System:关系数据库管理系统)应用软件之一

3.1.1 MySQL 发展历史

时间里程碑
1996 年MySQL 1.0 发布。它的历史可以追溯到 1979 年,作者 Monty 用 BASIC 设计的一个报表工具
1996 年10 月 3.11.1 发布。MySQL 没有 2.x 版本
2000 年ISAM 升级成 MyISAM 引擎。MySQL 开源
2003 年MySQL 4.0 发布,集成 InnoDB 存储引擎
2005 年MySQL 5.0 版本发布,提供了视图、存储过程等功能
2008 年MySQL AB 公司被 Sun 公司收购,进入 Sun MySQL 时代
2009 年Oracle 收购 Sun 公司,进入 Oracle MySQL 时代
2010 年MySQL 5.5 发布,InnoDB 成为默认的存储引擎
2016 年MySQL 发布 8.0.0 版本

为什么没有 MySQL6、MySQL7?

MySQL5.6 可以当成 MySQL6.x

MySQL5.7 可以当成 MySQL7.x

3.1.2 MySQL 的优势

  • 运行速度快

  • 使用成本低

    • 价格:MySQL对多数个人来说是免费的
  • 容易使用

    • 与其他大型数据库的设置和管理相比,其复杂程度较低,容易学习
  • 可移植性强

    • 能够工作在众多不同的系统平台上,例如:Windows 、Linux、Unix、MacOS等
  • 适用用户广

  • 丰富的接口

    • 提供了用于C 、C++、Eiffel、Java、Perl、PHP、Python、Rudy和TCL 等语言的API
  • 支持查询语言

    • MySQL可以利用标准SQL语法和支持ODBC(开放式数据库连接)的应用程序
  • 安全性和连接性

    • 安全性和连接性; 十分灵活和安全的权限和密码系统,允许主机验证。连接到服务器时,所有的密码均采用加密形式,从而保证了密码安全

    • 并且由于MySQL是网络化的,因此可以在因特网网上的任何地方访问,提高数据共享效率

3.1.3 MySQL 安装教程

Mysql

官网 https://downloads.mysql.com/archives/community/

CSDN安装教程 https://blog.csdn.net/qq_62283694/article/details/129869634

安装教程见第一章、课程软件的安装与卸载


3.2 命令行连接 MySQL

  1. 首先检查 MySQL 服务是否启动

  2. 电脑按下Win + R 输入 cmd打开dos窗口

3.2.1 启动服务命令

net start mysql

3.2.2 停止服务命令

net stop mysql

3.2.3 登录数据库命令

mysql –h服务器主机地址 –u用户名 –p密码

参数说明:

-h 指定客户端所要登录的 MySQL 主机名, 登录本机(localhost 或 127.0.0.1)该参数可以省略

-u 登录的用户名

-p 告诉服务器将会使用一个密码来登录, 如果所要登录的用户名密码为空, 可以忽略此选项

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

3.2.4 查看当前版本和用户信息

SELECT VERSION(),USER();

分号为结束符,不可缺

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

3.3 数据库管理指令

3.3.1 创建数据库

CREATE DATABASE 数据库名;

Query OK:SQL语句执行成功

1 row affected:操作影响的行数

(0.00 sec):操作执行时间

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

3.3.2 查看数据库列表

SHOW databases;

student:用户数据库

其他的是系统数据库

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

3.3.3 选择数据库

USE 数据库名;

使用数据表前,必须先选择该数据库!

ERROR 1049 (42000): Unknown database 'a'

数据库不存在,报错

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

3.3.4 删除数据库

DROP DATABASE 数据库名;

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


3.4 Navicat 数据库管理工具

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

Navicat Premium是一款功能强大的、可支持多连接的数据库管理工具

允许在单一程序中同时连接多达7种数据库

  1. MySQL

  2. MariaDB

  3. MongoDB

  4. SQL Server

  5. SQLite

  6. Oracle

  7. PostgreSQL

3.4.1 安装教程

官网 https://www.navicat.com/en/products

推荐使用:Navicat Premium 16 最新版

Navicate 涉及版权无法分享,加封面联系方式获取软件

3.4.2 Navicat 连接 MySQL

搜狗截图20230205154753

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

3.4.3 使用 Navicat 创建数据库

  1. 通过操作向导创建数据库
搜狗截图20230205160037

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

  • 字符集要选择utf8点击 SQL 预览可以查看建表语句

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

  1. 通过SQL语句创建数据库
搜狗截图20230205155234

3.5 结构化查询语言

3.5.1 DML (数据操作语言)

操作数据库中所包含的数据

  • INSERT UPDATE DELETE

3.5.2 DDL (数据定义语言)

创建和删除数据库对象等操作

  • CREATE DROP ALTER

3.5.3 DQL (数据查询语言)

对数据库中的数据进行查询

  • SELECT

3.5.4 DCL (数据控制语言)

控制数据库组件的存取许可、存取权限等

  • GRANT COMMIT ROLLBACK

3.6 MySQL 数据类型

MySQL 中定义数据字段的类型对你数据库的优化是非常重要的

MySQL 支持多种类型,大致可以分为三类:数值、日期/时间和字符串(字符)类型

3.6.1 数值类型

类型说明取值范围存储需求
TINYINT非常小的数据有符值:-27~27-1
无符号值:0~28-1
1字节
SMALLINT较小的数据有符值:-215~215-1
无符号值:0~216-1
2字节
MEDIUMINT中等大小的数据有符值:-223~223-1
无符号值:0~224-1
3字节
INT标准整数有符值:-231~231-1
无符号值:0~232-1
4字节
BIGINT较大的整数有符值:-263~263-1
无符号值:0~264-1
8字节
FLOAT单精度浮点数±1.1754351e-384字节
DOUBLE双精度浮点数±2.2250738585072014e-3088字节
DECIMAL字符串形式的浮点数Decimal(M,D)M+2个字节

**UNSIGNED 属性:**标识为无符号数,非负数

**ZEROFILL 属性:**宽度(位数)不足以0填充

搜狗截图20230205163743

若某数值字段指定了 ZEROFILL 属性,将自动添加 UNSIGNED 属性

搜狗截图20230205163822

数值位数小于宽度,以0填充

数值位数大于宽度,显示实际位数

3.6.2 日期类型

日期类型格式取值范围用途
DATEYYYY-MM-DD1000-01-01 / 9999-12-31日期值
TIMEHH:MM:SS-835:59:59 / 838:59:59时间值或持续时间
YEARYYYY1901~2155年份值
DATETIMEYYYY-MM-DD hh:mm:ss1000-01-01 00:00:00 到 9999-12-31 23:59:59混合日期和时间值
TIMESTAMPYYYY-MM-DD hh:mm:ss‘1970-01-01 00:00:01’ UTC 到 ‘2038-01-19 03:14:07’ UTC
结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038年1月19日 凌晨 03:14:07
混合日期和时间值,时间戳

若某日期字段默认值为系统当前日期,可指定为TIMESTAMP类型

3.6.3 字符串

字符串类型大小用途
CHAR[(M)]0-255 bytes定长字符串
VARCHAR[(M)]0-65535 bytes变长字符串
TINYBLOB0-255 bytes不超过 255 个字符的二进制字符串
TINYTEXT0-255 bytes短文本字符串
BLOB0-65 535 bytes二进制形式的长文本数据
TEXT0-65 535 bytes长文本数据
MEDIUMBLOB0-16 777 215 bytes二进制形式的中等长度文本数据
MEDIUMTEXT0-16 777 215 bytes中等长度文本数据
LONGBLOB0-4 294 967 295 bytes二进制形式的极大文本数据
LONGTEXT0-4 294 967 295 bytes极大文本数据

char(m) 和 varchar(m) 中括号中 m 代表字符的个数,并不代表字节个数

比如 CHAR(30) 就可以存储 30 个字符

  • CHAR 和 VARCHAR 类型类似,但它们保存和检索的方式不同。它们的最大长度和是否尾部空格被保留等方面也不同。在存储或检索过程中不进行大小写转换

  • BINARY 和 VARBINARY 类似于 CHAR 和 VARCHAR,不同的是它们包含二进制字符串而不要非二进制字符串。也就是说,它们包含字节字符串而不是字符字符串。这说明它们没有字符集,并且排序和比较基于列值字节的数值值

  • BLOB 是一个二进制大对象,可以容纳可变数量的数据。有 4 种 BLOB 类型:TINYBLOB、BLOB、MEDIUMBLOB 和 LONGBLOB。它们区别在于可容纳存储范围不同

  • 有 4 种 TEXT 类型:TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT。对应的这 4 种 BLOB 类型,可存储的最大长度不同,可根据实际情况选择


3.7 DDL (数据定义语言)

3.7.1 创建表

CREATE TABLE table_name (column_name column_type);
CREATE TABLE [IF NOT EXISTS] 表名 (
    字段 1 数据类型 [ 字段属性 | 约束 ][ 索引 ][ 字段备注 ],
    字段 2 数据类型 [ 字段属性 | 约束 ][ 索引 ][ 字段备注 ],
    字段 n 数据类型 [ 字段属性 | 约束 ][ 索引 ][ 字段备注 ]
)[ 表类型 ][ 表字符集 ][ 表备注 ];

IF NOT EXISTS 判断表是否存在,存在就不重复建表了

多字段使用逗号分隔,保留字用撇号括起来

演示案例

CREATE TABLE IF NOT EXISTS xuanzi(
   id INT UNSIGNED AUTO_INCREMENT,
   name VARCHAR(100) NOT NULL,
   phone VARCHAR(40) NOT NULL,
   date DATE,
   PRIMARY KEY ( id )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • 如果你不想字段为NULL可以设置字段的属性为NOT NULL,在操作数据库时如果输入该字段的数据为NULL,就会报错
  • AUTO_INCREMENT 定义列为自增的属性,一般用于主键,数值会自动加1
  • PRIMARY KEY 关键字用于定义列为主键。 您可以使用多列来定义主键,列间以逗号分隔
  • ENGINE 设置存储引擎,CHARSET 设置编码

3.7.2 修改表

  • 修改表名
ALTER TABLE <旧表名> RENAME [TO] <新表名>;
# 修改表名称
ALTER TABLE XuanZi RENAME TO patient;
  • 添加字段
ALTER TABLE 表名 ADD 字段名  数据类型  [属性];
# 添加字段
ALTER TABLE patient add XuanZi VARCHAR(20) NOT NULL;
  • 修改字段
ALTER TABLE 表名 CHANGE 原字段名 新字段名 数据类型 [属性];
#修改字段
ALTER TABLE patient CHANGE XuanZiemail email  VARCHAR(20);
  • 删除表中的字段
ALTER TABLE 表名 DROP 字段名;
# 删除字段
ALTER TABLE patient DROP XuanZi;
  • 添加主键约束
ALTER TABLE 表名 ADD CONSTRAINT 约束名称 PRIMARY KEY 表名(主键字段);
# 添加主键约束     添加约束       约束名称  约束类型     表(字段)
ALTER TABLE patient ADD CONSTRAINT pk_patientID PRIMARY KEY patient(patientID);
  • 添加外键约束
ALTER TABLE 表名 ADD CONSTRAINT 约束名称 FOREIGN KEY(外键字段)REFERENCES 关联表名(关联字段);
# 添加外键约束      添加约束      约束名称  约束类型 (添加外键的字段) 关联     外键(字段)
ALTER TABLE prescription ADD CONSTRAINT fk_patientID_patient FOREIGN KEY (patientID) REFERENCES patient(patientID);

MySQL中,MyISAM存储类型的表不支持外键。因此,可以通过建立逻辑关联的方式保证数据的完整性和一致性

3.7.3 删除表

DROP TABLE [IF  EXISTS] 表名;

在删除表之前,先使用IF EXISTS语句验证表是否存在,执行 DROP TABLE 的表是空表,则可以直接删除

如果表中已存储了业务数据,则需和数据库管理员联系,先对数据库数据进行备份并确认,再执行删除表及数据的操作


3.8 MySQL 注释

3.8.1 单行注释

#  单行注释
-- 单行注释

3.8.2 多行注释

/** 
	多行注释
*/

3.9 MySQL 字段的约束及属性

名称关键字说明
主键约束PRIMARY KEY(PK)设置该字段为表的主键 可唯一标识该表记录
外键约束FOREIGN KEY(FK)用于在两表之间建立关系, 需要指定引用主表的哪一字段
自动增长AUTO_INCREMENT设置该列为自增字段 默认每条自增1 通常用于设置主键
非空约束NOT NULL字段不允许为空
默认约束DEFAULT赋予某字段默认值
唯一约束UNIQUE KEY(UK)设置字段的值是唯一的 允许为空,但只能有一个空值

3.9.1 主键约束

主键:使用数据库表中某一字段或某几个字段唯一标识所有记录

  • 单字段主键
CREATE TABLE [IF NOT EXISTS] 表名 (
    字段 1 数据类型 PRIMARY KEY,
    #……
);
-- 定义字段的同时指定主键
CREATE TABLE [IF NOT EXISTS] 表名 (
    字段 1 数据类型 ,
    #……
    [CONSTRAINT< 约束名 >] PRIMARY KEY[ 列名 ]
);
-- 定义所有字段之后指定主键
  • 多字段联合主键
CREATE TABLE [IF NOT EXISTS] 表名 (
    #……
    PRIMARY KEY [字段1,字段2,…… ]
);

3.9.2 外键约束

ALTER TABLE 表名 ADD CONSTRAINT 约束名称 FOREIGN KEY(外键字段)REFERENCES 关联表名(关联字段);
# 添加外键约束      添加约束      约束名称  约束类型 (添加外键的字段) 关联     外键(字段)

其他约束的设置方式同理

3.9.3 主键与外键的区别

定义作用个数
主键唯一标识一条记录,不能出现重复且不能出现空值保证数据完整性主键只能有一个
外键“从表”中对应于“主表”的列,在从表中称为外键或者引用键“主表”中的列应为主键或者唯一键和其他表建立关联,保持数据的一致性一个表可以有多个外键

3.9.4 主外键建立后注意事项

  1. 当主表中没有对应的记录时,不能将记录添加到子表
  2. 不能更改主表中的值而导致子表中的记录孤立
  3. 子表存在与主表对应的记录,不能从主表中删除该行
  4. 删除主表前,先删子表

3.10 MySQL 注释与字符集编码

3.10.1 注释

  • COMMENT关键字

  • 在表结构中可以看到,与代码注释不同、

CREATE TABLE test (
    id int(11) UNSIGNED COMMENT '编号'
)COMMENT='测试表';

3.10.2 设置字符集编码

  • 默认情况下,MySQL所有数据库、表、字段等使用 MySQL 默认字符集

  • 为了存储独特的数据,可在创建表时指定字符集

CREATE TABLE [IF NOT EXISTS] 表名(
     #……
)CHARSET = 字符集名;

3.10.3 演示案例

CREATE TABLE `checkitem`  (
  `checkItemID` int(4) NOT NULL AUTO_INCREMENT COMMENT '检查项目编号',
  `checkItemName` varchar(50) NOT NULL COMMENT '检查项目名称',
  `checkItemCost` float NOT NULL COMMENT '检查项目价格',
  PRIMARY KEY (`checkItemID`) USING BTREE
) ENGINE = InnoDB  CHARACTER SET = utf8  COMMENT = '检查项目表';

3.11 MySQL 查看表

3.11.1 查看数据库中的表

SHOW TABLES;

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

3.11.2 描述表结构

DESCRIBE 表名;
DESC 表名;

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

为了避免DOS窗口出现中文乱码,可执行SET NAMES gbk;


3.12 MySQL 的存储引擎

存储引擎,是存储数据的核心组件,指定了表的存储类型和存储形式

3.12.1 存储引擎的类型

  • InnoDB
  • MyISAM
  • Memory
  • CSV
  • 等9种

3.12.2 查看系统所支持的存储引擎类型

SHOW ENGINES;

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

3.12.3 常用的 MySQL 存储引擎

InnoDB 存储引擎

  • 支持具有提交、回滚和崩溃恢复能力的事务控制

MyISAM 存储引擎

  • 不支持事务,也不支持外键约束,访问速度比较快

3.12.4 InnoDB 和 MyISAM 存储引擎比较

功能InnoDBMyISAM
事务处理支持不支持
外键约束支持不支持
表空间大小较大较小
数据行锁定支持不支持

3.12.5 适用场合

  • 使用 MyISAM:不需事务,空间小,以查询访问为主
  • 使用InnoDB:多删除、更新操作,安全性高,事务处理及并发控制

3.12.6 查看当前存储引擎

MySQL 5.7 版本默认的存储引擎是InnoDB

  • 查看当前默认存储引擎
SHOW VARIABLES LIKE 'default_storage_engine%';
-- default_storage_engine% 默认存储引擎

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

2.12.7 修改存储引擎

  • 修改my.ini配置文件
default-storage-engine=MyISAM
-- MyISAM 改为其他类型的存储引擎

修改配置文件后,需要重启MySQL服务设置才会生效

3.12.8 设置表的存储引擎

数据表默认使用当前 MySQL 默认的存储引擎

创建表时,可以设置表的存储引擎类型

CREATE TABLE 表名 (
# ……
) ENGINE= 存储引擎 ;

演示案例

CREATE TABLE `checkitem`  (
  `checkItemID` int(4) NOT NULL AUTO_INCREMENT COMMENT '检查项目编号',
  `checkItemName` varchar(50) NOT NULL COMMENT '检查项目名称',
  `checkItemCost` float NOT NULL COMMENT '检查项目价格',
  PRIMARY KEY (`checkItemID`) USING BTREE
) ENGINE = MyISAM;

3.12.9 数据表的存储文件

MyISAM 类型表文件

  • *.frm表结构定义文件

  • *.MYD数据文件

  • *.MYI索引文件

InnoDB 类型表文件

  • *.frm表结构定义文件

  • ibdata1文件

3.12.10 存储位置

因操作系统而异,可查my.ini

datadir="C:\ProgramData\MySQL\MySQL Server 5.7\Data"
innodb_data_home_dir="D:/MySQL Datafiles/"

3.13 MySQL 系统帮助

 HELP 查询内容;

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

3.13.1 详细帮助

HELP contents;
#查看帮助文档目录列表
HELP Data Definition;
#查看数据定义
HELP CREATE DATABASE;
#查看CREATE DATABASE的帮助信息

四、MySQL 高级(DML 增删改)

4.1 DML 数据操纵语言

DML(Data Manipulation Language)DML对数据库中表记录的执行操作

  • 插入(INSERT)

    • 插入单行数据

    • 插入多行数据

    • 将查询结果插入到新表

  • 更新(UPDATE)

  • 删除(DELETE)

软件开发人员日常使用最频繁的操作,必备技能!!!


4.2 插入数据

4.2.1 一次向表中插入一条记录

INSERT INTO 表名 [(字段名列表)]  VALUES (值列表);
# 增
INSERT into gradeinfo (grade) VALUES ('ACCP');
  • 字段名是可选的,如省略,则依次插入所有字段

  • 多个列表和多个值之间使用逗号分隔

  • 值列表和字段名列表一 一对应

  • 如果插入表中的部分字段数据,则字段名列表必填

  • 每个数据值的数据类型、精度和小数位数必须与相应的列匹配

  • 不能为标识列指定值(主键)

  • 如果在设计表的时候就指定了某列不允许为空,则必须插入数据

  • 如果字段具有默认值,可以使用DEFAULT关键字来代替插入的数值

4.2.2 一次向表中插入多行数据

INSERT INTO 表名 [(字段名列表)] VALUES (值列表1), (值列表2), (值列表n);
# 增
INSERT INTO gradeinfo (grade) VALUES ('ACCP'),('BCSP');

为避免表结构发生变化引发的错误,建议插入数据时写明具体字段名!

4.2.3 将查询结果插入到新表

INSERT INTO 表名(字段1,字段2 , ……) SELECT 字段1,字段2 , …… FROM 原表;
# 事先创建且与插入数据字段相符
CREATE TABLE patient_address (SELECT  patientName, address  FROM patient ) ;
# 无须事先创建

如果新表已存在,将会报错!


4.3 更新数据

4.3.1 更新数据记录

UPDATE 表名 SET 字段1=值1, 字段2=值2, …, 字段n=值n [WHERE 条件];
# 更新表数据
UPDATE gradeinfo SET gradeinfo.grade='BCSP' WHERE gradeinfo.id=2;

4.4 删除数据

4.4.1 删除数据记录

DELETE FROM 表名 [WHERE条件];
# 删除表信息
DELETE FROM patient WHERE patientID =1

DELETE条件删除语句中,如果未指定WHERE条件语句,则将删除表中的所有数据

DELETE FROM gradeinfo WHERE gradeinfo.id=1;

4.4.2 格式化表记录

TRUNCATE TABLE 表名;
# 格式化表
TRUNCATE TABLE gradeinfo;

使用 TRUNCATE 语句删除表中记录后,将重置自增列,表结构及其字段、约束、索引保持不变,执行速度比 DELETE 语句快


4.5 比较 DROP、DELETE 与 TRUNCATE 语句

DROPDELETETRUNCATE
类型DDL 会隐式提交,不能回滚DML 每次从表中删除一行数据的同时将改行的删除操作记录在redo和undo表空间中,以便进行回滚和重做操作 需要手动提交操作才能生效,可通过ROLLBACK撤销操作DDL 会隐式提交,不会记录日志,不能回滚
功能删除表结构及所有数据,并将标所占用的空间全部释放可根据条件删除表中满足条件的数据,如果不指定WHERE子句,则删除表中所有记录删除表中所有记录,并将重建表结构

4.5.1 执行速度

一般来说,DROP>TRUNCATE>DELETE

4.5.2 使用选择

  • 使用 DROP、TRUNCATE 语句时,要慎重
  • 如果使用 DELETE 语句删除表中部分数据要带上 WHERE 子句,且要注意表空间要足够大
  • 如果要删除表,使用 DROP 语句
  • 如果要保留表但删除表中所有数据,如果与事务无关可以使用 TRUNCATE 语句
  • 如果与事务有关,则使用 DELETE 语句

五、MySQL 高级(DQL 查)

5.1 DQL 数据查询语言

Data Query Language,用于查询数据库的表中数据,是数据库中最为核心的语言,使用频率最高


5.2 基础查询

5.2.1 查询全部行和列

SELECT * FROM 表名称
SELECT * FROM patient
# 查询 patient 病人表中的所有字段 * 表示所有字段

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


5.2.2 查询部分列

SELECT 字段1,字段2,字段n FROM 表名称
SELECT patientID,patientName,gender FROM patient
# 查询 patient 病人表中的部分字段(多个字段之间使用,分隔)

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

有的时候我们不想让用户看到所有的信息,比如用户密码这类的隐私信息,我们就可以通过写具体的字段,就只查询部分信息

在开发中,推荐使用以查询部分列的方式代替SELECT *查询全部列,可提高执行效率并养成良好编码习惯

5.2.3 AS 别名

  • 使用 AS 关键字,可以为表、字段、查询结果指定别名
SELECT 字段列表 FROM 表名 AS 表的别名

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

SELECT 字段1 AS 别名,字段2 FROM 表名

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

  • 使用空格,简便的方法
SELECT 字段列表 FROM 表名  表的别名;
# 中间是空格

为某个表命名了别名后,在 SELECT 语句中出现该表的字段需要指定表名时,就必须统一使用该表的别名;否则将产生语法错误


5.3 运算符

运算符是一种符号,用来进行列间或变量之间的比较和数学运算

MySQL 常的运算符

  • 算术运算符
  • 比较运算符
  • 逻辑运算符
  • 位运算符

5.3.1 算术运算符

运算符作用
+加法
-减法
*乘法
/ 或 DIV除法
% 或 MOD取余

在除法运算和模运算中,如果除数为0,将是非法除数,返回结果为 NULL

5.3.2 比较运算符

符号描述备注
=等于
<>, !=不等于
>大于
<小于
<=小于等于
>=大于等于
BETWEEN在两值之间
NOT BETWEEN不在两值之间
IN在集合中
NOT IN不在集合中
<=>严格比较两个NULL值是否相等两个操作码均为NULL时,其所得值为1;而当一个操作码为NULL时,其所得值为0
LIKE模糊匹配
REGEXP 或 RLIKE正则式匹配
IS NULL为空
IS NOT NULL不为空

5.3.3 逻辑运算符

运算符号作用
NOT 或 !逻辑非
AND逻辑与
OR逻辑或
XOR逻辑异或

逻辑运算符用来判断表达式的真假。如果表达式是真,结果返回 1。如果表达式是假,结果返回 0

5.3.4 位运算符

运算符号作用
&按位与
|按位或
^按位异或
!取反
<<左移
>>右移

位运算符是在二进制数上进行计算的运算符。位运算会先将操作数变成二进制数,进行位运算。然后再将计算结果从二进制数变回十进制数

5.3.5 运算符优先级

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


5.4 条件查

如果我们查询时,并不想显示所有的用户信息,只要查出满足指定条件的部分用户,或者精确到某一个用户的信息,就需要使用到 WHERE 关键字对 sql 语句增加查询条件

5.4.1 精确查询

SELECT * FROM patient WHERE patient.patientID = 1
# 通过患者 ID 精确查询

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

这里我们就把患者 ID 作为查询条件,查询出患者 ID 为 1 的患者信息

5.4.2 单条件查询

SELECT * FROM patient WHERE patient.patientID < 10
# 查询病人 ID 小于 10 的患者信息

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

SELECT * FROM patient WHERE patientName ='玄子'
# 查询所有病人姓名为玄子的患者信息

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

5.4.3 多条件查询

  • 如果查询条件中包含的条件不止一个,则条件根据逻辑关系的不同可以分为条件和条件两种

  • 条件表示要求同时满足两个以上的条件,使用 AND 关键字可以构造条件。

  • 条件表示几个条件中只需满足其中一个的条件,使用 OR 关键字可以构造条件

  • AND

SELECT * FROM patient WHERE patientName ='玄子' AND address='长春市'
# 多条件查询 姓名为 玄子 且住址为 长春市

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

  • OR
SELECT * FROM patient WHERE patientName ='玄子' OR address='长春市'
# 多条件查询 姓名为 玄子 '或者' 住址为 长春市

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

姓名 和 住址 这两个条件满足一个即可


5.5 排序查

5.5.1 升序排序

SELECT * FROM patient ORDER BY birthDate
# 通过病人的出生年月进行排序

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

这里我们使用 ORDER BY 关键字,对病人的出生年月进行排序,默认是从小到大,也就是升序排列

5.5.2 降序排序

SELECT * FROM patient ORDER BY birthDate DESC
# 通过病人的出生年月进行降序排序

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

使用降序排列只需要 在最后加上一个DESC关键字即可

升序排序后面其实也有一个关键字ASC不过ORDER BY默认就是升序排列,所以ASC加不加都可以

5.5.3 多字段排序

SELECT * FROM patient ORDER BY birthDate ASC,patientID DESC
# 通过病人的出生年月和ID进行多字段排序

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

如果想要对多个字段进行排序,可直接使用,分隔。然后写第二个需要排序的字段和排序方式即可


5.6 常用函数

MySQL 中将一些常用的数据处理操作封装起来,作为函数提供给程序员使用,可以提高程序员开发效率

MySQ L支持的常用函数

  • 字符串函数

  • 时间日期函数

  • 聚合函数

  • 数学函数

5.6.1 字符串函数

函数名作用举例
CONCAT(str1,str2,strn)连接字符串str1、str2、strn为一个完整字符串SELECT CONCAT( ‘MySQL’,’ is powerful.'); 返回:MySQL is powerful.
LOWER(str)将字符串str中所有字符变为小写SELECT LOWER( ‘MySQL is powerful.’); 返回:mysql is powerful.
UPPER(str)将字符串str中所有字符变为大写SELECT UPPER( ‘MySQL is powerful.’); 返回:MYSQL IS POWERFUL.
SUBSTRING(str,num,len)返回字符串str的第num个位置开始长度为len的子字符串SELECT SUBSTRING( ‘MySQL is powerful.’,10,8); 返回:powerful
INSERT(str,pos,len,newstr)将字符串str从pos位置开始,len个字符长的子串替换为字符串newstrSELECT INSERT( ‘MySQL is powerful.’,10,0,'very '); 返回:MySQL is very powerful.

5.6.2 时间日期函数

函数名作用举例(部分结果与当前日期有关)
CURDATE()获取当前日期SELECT CURDATE(); 返回:2020-08-03
CURTIME()获取当前时间SELECT CURTIME(); 返回:16:54:40
NOW()获取当前日期和时间SELECT NOW(); 返回:2020-08-03 16:55:00
WEEK(date)返回日期date为一年中的第几周SELECT WEEK(NOW()); 返回:31
YEAR(date)返回日期date的年份SELECT YEAR(NOW()); 返回:2020
HOUR(time)返回时间time的小时值SELECT HOUR(NOW()); 返回:16
MINUTE(time)返回时间time的分钟值SELECT MINUTE(NOW()); 返回:56
DATEDIFF(date1,date2)返回日期参数date1和date2之间相隔的天数SELECT DATEDIFF(NOW(), ‘2019-8-8’); 返回:361
ADDDATE(date,n)计算日期参数date加上n天后的日期SELECT ADDDATE(NOW(), 5); 返回:2020-08-07 16:57:28
UNIX_TIMESTAMP(date)将日期转换成时间戳SELECT UNIX_TIMESTAMP(‘2020-9-1’); 返回:1598889600
DATE_ADD(d,INTERVAL expr type)计算起始日期 d 加上一个时间段后的日期SELECT DATE_ADD(‘2017-06-15’, INTERVAL 10 DAY); 返回:2017-06-25

DATE_ADD(d,INTERVAL expr type) type 参数可以是下列值:MICROSECOND,SECOND,MINUTE,HOUR,DAY,WEEK,MONTH,QUARTER,YEAR,SECOND_MICROSECOND,MINUTE_MICROSECOND,MINUTE_SECOND,HOUR_MICROSECOND,HOUR_SECOND,HOUR_MINUTE,DAY_MICROSECOND,DAY_SECOND,DAY_MINUTE,DAY_HOUR,YEAR_MONTH

5.6.3 聚合函数

函数名作用
COUNT()返回某字段的行数
MAX()返回某字段的最大值
MIN()返回某字段的最小值
SUM()返回某字段的和
AVG()返回某字段的平均值

4.6.4 数学函数

函数名作用举例
CEIL(x)返回大于或等于数值x的最小整数SELECT CEIL(-2.1); 返回:32
FLOOR(x)返回小于或等于数值x的最大整数SELECT FLOOR(-2.1); 返回:-3
RAND()返回0~1间的随机数SELECT RAND(); 返回:0.15013303621684485

MySQL 中还有许多专业领域或不常用的函数,想具体了解见文章附件:第八章 8.4 MySQL 函数


5.7 分组查

5.7.1 GROUP BY

如果我们想要对,查询结果分组,比如按照,考试的科目ID 进行分组。就要使用GROUP BY 关键字对 subjectNo进行分组,首先我们可以看到成绩表中有四个字段分别是studentNo 学生学号subjectNo 课程编号examDate 考试日期studentResult 考试成绩。其中subjectNo有三个编号我们就可以对其分组查询查出这三个编号

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

  • 分组考试成绩表中的科目编号
SELECT subjectNo FROM result GROUP BY subjectNo
# 分组考试成绩表中的科目编号

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

这样分组就只能看到成绩表中的科目 ID,并不能查询其他字段,所以我们通常搭配聚合函数使用

  • 分组科目编号,查询对应科目考试成绩的平均分
SELECT subjectNo,AVG(studentResult) FROM result GROUP BY subjectNo
# 分组科目编号,查询对应科目考试成绩的平均分

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

5.7.2 HAVING

我们在使用 GROUP BY 分组后还想要进一步筛选查询结果,就需要使用 HAVING 关键字进一步筛选,这里并不能使用 WHERE

  • 分组考试成绩表中的科目编号,后再次筛选
SELECT subjectNo,AVG(studentResult) AS avg FROM result GROUP BY subjectNo HAVING avg >60
# 分组科目编号,查询对应科目考试成绩的平均分 后继续筛选 成绩在 60 分以上的 科目编号

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


5.8 分页查

SELECT  <字段名列表>
FROM  <表名或视图>
[WHERE  <查询条件>]
[GROUP BY <分组的字段名>] [HAVING <条件>]
[ORDER BY <排序的字段名> [ASC 或 DESC]]
[LIMIT [位置偏移量,]行数];

5.8.1 下标 5 开始查询 5 条

SELECT * FROM patient LIMIT 5,5
# 位置偏移量:第1条记录的位置偏移量是0,第2条记录的位置偏移量是1……
# 行数:显示记录的条数

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

使用LIMIT子句时,第1条记录的位置是0!

LIMIT 子句经常和 ORDER BY 子句一起使用,即先对查询结果进行排序,再根据 LIMIT 子句的参数返回指定的数据

5.8.2 每页 m 个数据查第 n 页

SELECT * FROM patient LIMIT (n-1)*m,m
# 每页 m 个第 n 页

5.9 模糊查

5.9.1 通配符

模糊查询一般使用关键字 LIKE 主要用于匹配列中的数据

通配符解释示例
_一个字符A LIKE ‘V_’,则符合条件的A如“VR”、“VC”等
%任意长度的字符串B LIKE ‘SQL%’,则符合条件的A如“SQL Server”、“SQL Server高级编程”等
[]括号中所指定范围内的一个字符C LIKE ‘900[1-2]’,则符合条件的C如“9001”或“9002”
[^]不在括号中所指定范围内的任意一个字符D LIKE ‘900[^1-2]’,则符合条件的D如“9003”或“9007”等

5.9.2 LIKE

select * from Students where StudentName like '张%'
# 名字以张开头的人
select * from Students where StudentName like '%张'
# 名字以张结尾的人
select * from Students where StudentName like '%张%'
# 名字中包含张的人
select * from Students where StudentName like '张_'
# 名字为张姓的单名人
select * from Students where StudentName like '张[1-4]'
# 查询结果为张姓的张1到张4
select * from Students where StudentName like '张[^1-4]'
# 查询结果为张姓的除了张1到张4的所有张姓单名

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

  • 匹配的字符串必须加单引号或者双引号

  • 默认情况下,LIKE 关键字匹配字符串时候不区分大小写,可以在 LIKE 关键字后添加 BINARY 关键字来区分大小写。

  • 如果查询内容中有通配符字符,就需要加转义字符 \

5.9.3 BETWEEN

SELECT 字段 FROM 表名 WHERE 列名 [NOT] BETWEEN 起始值 AND 最终值
SELECT * FROM patient WHERE patient.patientID BETWEEN 5 AND 15
# 查询 ID 在 5 到 15 的患者信息

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

NOT 可选参数,表示取反

查询指定范围内所有值,包括起始值和最终值

5.9.4 IS NULL

IS NULL关键字判断该列的值是否为空值,空值不是空字符串

SELECT 字段 FROM 表名 WHERE 列名 IS [NOT] NULL
SELECT * FROM patient WHERE address IS NOT NULL

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

NOT 可选参数,表示取反


5.10 子查询

子查询是一个嵌套在 SELECT、INSERT、UPDATE 或 DELETE 语句或其他子查询中的查询

SELECT …… FROM 表1 WHERE 字段1 比较运算符 (子查询);
SELECT * FROM patient WHERE patientID IN(1,2,3,4,5,9)

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

SELECT * FROM patient WHERE patientID IN(SELECT patientID FROM patient WHERE patient.patientID BETWEEN 5 AND 15)

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

先执行子查询,返回所有来自子查询的结果

再执行外围的父查询,返回查询的最终结果

将子查询和比较运算符联合使用

必须保证子查询返回的值不能多于一个

5.10.1 IN 子查询

  • 如果子查询的结果为多个值,就会导致代码报错
  • 解决方案就是使用 IN 关键字,将 = 替换成 IN
SELECT …… FROM 表名 WHERE 字段名  IN (子查询);

5.10.2 NOT IN 子查询

  • in 一样,查询结果有多条使用

  • 获取的值是不包含在查询结果里面的值

SELECT …… FROM 表名 WHERE 字段名 NOT IN (子查询);

5.10.3 EXISTS

DROP TABLE IF EXISTS temp;
CREATE TABLE log (
    … … #省略建表语句
) ;

5.10.4 EXISTS 子查询

  • 子查询有返回结果: EXISTS 子查询结果为 TRUE
  • 子查询无返回结果: EXISTS 子查询结果为 FALSE,外层查询不执行
SELECT …… FROM 表名 WHERE  EXISTS (子查询);

5.10.5 NOT EXIST 子查询

  • 与 EXISTS 子查询相反
  • 子查询有返回结果: NOT EXIST子查询结果为 FALSE
  • 子查询无返回结果: NOT EXIST子查询结果为 TRUE,外层查询执行

5.10.6 子查询小结

当一个查询是另一个查询的条件时,称之为子查询

  • 任何允许使用表达式的地方都可以使用子查询

  • 嵌套在父查询SELECT语句的子查询,可包括

    • SELECT 子句

    • SELECT (子查询) [AS 列别名] FROM 表名;
      
    • FROM 子句

    • SELECT * FROM (子查询)  AS 表别名;
      
    • WHERE 子句

    • GROUP BY 子句

    • HAVING 子句

5.10.7 子查询注意事项

通常,将子查询放在比较条件的右边以增加可读性

子查询可以返回单行或多行数据,此时要选择合适的关键字

  • 子查询的返回是单行数据时,比较条件中可以使用比较运算符

  • 子查询的返回是多行数据时,比较条件中需要使用IN或 NOT IN 关键字

  • 如果判断子查询是否有数据返回时,需要使用 EXISTS 或 NOT EXISTS 关键字

只出现在子查询中、而没有出现在父查询中的列不能包含在输出列中


5.11 连接查

5.11.1 JOIN

JOIN 按照功能大致分为如下三类

  • INNER JOIN(内连接,或等值连接):获取两个表中字段匹配关系的记录
  • **LEFT JOIN(左连接):**获取左表所有记录,即使右表没有对应匹配的记录
  • RIGHT JOIN(右连接): 与 LEFT JOIN 相反,用于获取右表所有记录,即使左表没有对应匹配的记录

5.11.2 内连接

最典型、最常用的连接查询,根据两张表中共同的列进行匹配,两个表存在主外键关系时,通常会使用内连接查询

SELECT 	……  FROM 表1 INNER JOIN 	表2 ON 	……

INNER JOIN用来连接两个表

INNER可以省略

ON用来设置两个表之间的关联条件

SELECT * FROM department_checkitem 
INNER JOIN department ON department_checkitem.depID=department.depID
INNER JOIN checkitem ON department_checkitem.checkItemID=checkitem.checkItemID

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

5.11.3 外连接

特点

  • 外连接可以分为主表和从表
  • 主表的数据会被完全显示出来
  • 从表中只显示满足连接条件的数据
  • 外连接结果一般会比主表和从表中数据量最小的表中的数据多

  • 左外连
SELECT * FROM prescription
LEFT JOIN patient ON prescription.patientID=patient.patientID

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

  • 右外连
SELECT * FROM prescription
RIGHT JOIN patient ON prescription.patientID=patient.patientID

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

5.12 执行顺序

5.12.3书写顺序

select->distinct->from->join->on->where->group by->having->order by->limit

5.12.4 执行顺序

from->on->join->where->group by(开始使用select中的别名,后面的语句中都可以使用别名)->sum、count、max、avg->having->select->distinct->order by->limit


六、企业级开发技术

6.1 存储过程

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fuFR4d6Z-1680618008637)(./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-NFJ5lJ7B-1680618008637)(./assets/image-20230403133422488.png)]

6.1.8 查看存储创建代码

SHOW CREATE PROCEDURE 存储过程名

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

6.1.9 修改存储过程

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

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

6.1.10 删除存储过程

DROP PROCEDURE 存储过程名

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RvTj3iv0-1680618008638)(./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
    <SELECT 语句>;
-- 一般以view_xxx或v_xxx格式命名

使用SQL语句删除视图

DROP VIEW  [IF EXISTS]   view_name;
-- 删除前先判断 视图是否存在

使用SQL语句查看视图

SELECT 字段1, 字段2, …… FROM view_name;

6.3.5 使用视图注意事项

  • 视图中可以使用多个表

  • 一个视图可以嵌套另一个视图,但最好不要超过3层

  • 对视图数据进行添加、更新和删除操作会直接影响所引用表中的数据

  • 当视图数据来自多个表时,不允许添加和删除数据

查看所有视图

USE information_schema;
SELECT * FROM views;

使用视图修改数据会有许多限制,一般在实际开发中视图仅用作查询

6.3.6 演示案例

CREATE VIEW VIEW_1
AS
SELECT prescription.*,patient.patientName FROM prescription INNER JOIN patient ON prescription.patientID=patient.patientID;

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

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


6.4 索引

6.4.1 什么是索引

是对数据库表中一列或多列值进行排列的一种结构

  • 作用

    • 大大提高数据库的检索速度

    • 改善数据库性能

  • MySQL索引按存储类型分类

    • B-树索引(BTREE)

      • InnoDB、MyISAM均支持
    • 哈希索引(HASH)

  • 数据存储在数据表中,而索引是创建在数据库表对象上

  • 由表中的一个字段或多个字段生成的键组成

    • 通过索引,可以极大的提升数据查询效率,改善数据库的性能

Indexes Use Key Values to Locate Data

(根据索引键查找定位数据行)

6.4.2 常用索引类型

普通索引

  • 基本索引类型
  • 允许在定义索引的列中插入重复值和空值

唯一索引

  • 索引列数据不重复
  • 允许有空值

主键索引

  • 主键列中的每个值是非空、唯一的
  • 一个主键将自动创建主键索引

复合索引

  • 将多个列组合作为索引

全文索引

  • 支持值的全文查找
  • 允许重复值和空值

6.4.3 创建/删除索引

创建索引

CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name ON table_name (column_name [length] …);
-- 唯一索引、全文索引或空间索引,可选

如果创建索引是未指定创建索引的类型,则创建的索引为普通索引通过CREATE INDEX语句无法创建主键索引,主键索引的创建语句

删除索引

ALTER TABLE tablename ADD PRIMARY KEY(column)

删除表时,该表的所有索引同时会被删除

6.4.4 创建索引的指导原则

按照下列标准选择建立索引的列

  • 频繁搜索的列
  • 经常用作查询选择的列
  • 经常排序、分组的列经常用作连接的列(主键/外键)

不要使用下面的列创建索引

  • 仅包含几个不同值的列
  • 表中仅包含几行

6.4.5 使用索引时注意事项

  • 查询时减少使用*返回表的全部列,不要返回不需要的列

  • 索引应该尽量小,在字节数小的列上建立索引

  • WHERE 子句中有多个条件表达式时,包含索引列的表达式应置于其他条件表达式之前

  • 避免在 ORDER BY 子句中使用表达式

  • 根据业务数据发生频率,定期重新生成或重新组织索引,进行碎片整理

6.4.6 查看索引 / 删除索引

查看已创建的索引信息

SHOW INDEX FROM table_name;

删除索引

DROP INDEX index_name ON table_name;

删除表时,该表的所有索引将同时被删

除删除表中的列时,如果要删除的列为索引的组成部分,则该列也会从索引中删除

如果组成索引的所有列都被删除,则整个索引将被删除


6.5 数据库的备份与恢复

6.5.1 为什么进行数据库备份

可能导致数据丢失的意外状况

  • 数据库故障
  • 突然断电
  • 病毒入侵
  • 人为误操作
  • 程序错误
  • 运算错误
  • 磁盘故障
  • 灾难(如火灾、地震)和盗窃

6.5.2 数据的备份与恢复

数据备份

  • 是容灾的基础
  • 指为防止系统出现操作失误或系统故障导致数据丢失,而将全部或部分数据集合从应用主机的硬盘或阵列复制到其他的存储介质的过程
  • MySQL数据备份的常用方法
    • mysqldump 备份数据库
    • Navicat 备份数据库

数据恢复

  • 是指通过技术手段,将保存在硬盘等存储介质上的丢失的数据进行抢救和恢复的技术

6.5.3 mysqldump 备份数据库

mysqldump 命令——MySQL的客户端常用逻辑备份工具

  • 将 CREATE 和 INSERT INTO 语句保存到文本文件
  • 属于 DOS 命令
mysqldump [options] database [table1,[table2]…] > [path]/filename.sql
  • [options] 的选项参数
    • –u username: 表示用户名
    • –h host: 表示登录的主机名称,如本机为主机可省略
    • –p password: 表示登录密码
  • database
    • 需备份的数据库名
  • [table1,[table2]…]
    • 需备份的表名若省略,备份所有表
  • [path]/filename.sql
    • 备份文件名称

mysqldump是DOS系统下的命令

在使用时,无须进入mysql命令行;否则,将无法执行

示例

以root账户登录到MySQL服务器,使用mysqldump命令备份hospital数据库,将SQL脚本保存到e:\hospital.sql中

mysqldump -uroot -proot hospital > e:\hospital.sql

导出的SQL脚本中两种注释

  • --开头:关于SQL语句的注释信息
  • /*!开头, */结尾:是关于MySQL服务器相关的注释

为保证账户密码安全,命令中可不写密码

但是,参数-p必须有,回车后根据提示写密码

常用参数选项

参数描述
-add-drop-table在每个CREATE TABLE语句前添加DROP TABLE语句,默认是打开的,可以用-skip-add-drop-table取消
–add-locks该选项会在INSERT 语句中捆绑一个LOCK TABLE 和UNLOCK TABLE 语句
好处:防止记录被再次导入时,其他用户对表进行的操作
-t或-no-create-info只导出数据,而不添加CREATE TABLE语句
-c或–complete-insert在每个INSERT语句的列上加上列名,在数据导入另一个数据库时有用
-d或–no-data不写表的任何行信息,只转储表的结构

备份文件包含的主要信息

  • 备份后文件包含信息MySQL服务器及mysqldump工具的版本号
  • 备份账户的名称
  • 主机信息
  • 备份的数据库名称
  • SQL语句注释和服务器相关注释
  • CREATE和INSERT语句

6.5.4 Navicat 备份数据库

Navicat也可以用于导出数据库的备份脚本

  • 右键点选数据库->转储SQL文件->结构和数据…
  • 选择导出文件的保存路径和文件名后,便可导出数据库脚本

6.5.5 恢复数据库

在需要恢复数据库数据时,对导出的SQL备份脚本执行导入操作

  • 使用mysql命令
  • 使用source命令
  • 使用Navicat导入数据

6.5.6 mysql 恢复数据库

mysql为DOS命令

mysql –u username –p [dbname] < filename.sql
-- –u 用户名
-- –p 数据库名
-- filename.sql 备份文件名

在执行该语句之前,必须在MySQL服务器中创建新数据库

因为导出的备份文件中只包含表的备份,而不包含创建的库的语句

因此执行导入操作时必须指定数据库名,且该数据库必须存在

6.5.7 source 恢复数据库

除了在命令行中导入数据以外,还可以在数据库已连接状态下导入数据

source filename;

登录MySQL服务后使用

执行该命令前,先创建并选择恢复后的目标数据库

CREATE DATABASE hospitalDB;  #创建数据库
USE hospitalDB;              #选择要导入数据库的数据库
source e:\hospital.sql       #导入数据
# SQL脚本文件后面不要加字符 ;

6.5.8 Navicat 恢复数据库

Navicat中导入数据的操作步骤

  1. 右键单击要导入数据的数据库
  2. 右键单击运行SQL文件…快捷菜单项
  3. 在导入窗口,选择要运行的SQL文件
  4. 点击开始按钮开始导入数据

七、JDBC 与 DAO 模式

7.1 JDBC 介绍

7.1.1 什么是 JDBC

JDBC(Java Database Conectivity)

Java数据库连接技术的简称,提供连接各种常用数据库的能力

7.1.2 JDBC 的工作原理

JDBC API

内容:供程序员调用的接口与类,集成在java.sql和javax.sql包中,如

  • DriverManager类
  • Connection接口
  • Statement接口
  • ResultSet接口

DriverManager

  • 作用:管理各种不同的JDBC驱动

JDBC 驱动

  • 提供者:数据库厂商
  • 作用:负责连接各种不同的数据库

7.1.3 JDBC API

JDBC API 主要功能

  • 与数据库建立连接、执行SQL 语句、处理结果

  • DriverManager :依据数据库的不同,管理JDBC驱动

  • Connection :负责连接数据库并担任传送数据的任务

  • Statement :由 Connection 产生、负责执行SQL语句

  • esultSet:负责保存Statement执行后的查询结果


7.2 使用 JDBC 连接数据库

7.2.1 导入 JDBC 驱动 JAR 包

数据库版本:MySQL5.7

MySQL官网下载对应的JDBC驱动JAR包

  • mysql-connector-java-8.0.19.jar

驱动类

  • com.mysql.cj.jdbc.Driver

7.2.2 纯 Java 驱动方式

使用纯Java方式连接数据库

  • 由JDBC驱动直接访问数据库

  • 优点:完全Java代码,快速、跨平台

  • 缺点:访问不同的数据库需要下载专用的JDBC驱动

  • JDBC驱动由数据库厂商提供

7.2.3 JDBC编程模板

try {
      Class.forName(JDBC驱动类);
      # 1.加载JDBC驱动 
} catch (ClassNotFoundException e) {
    //异常输出代码
} //… …
try {
      Connection con=DriverManager.getConnection(数据连接字符串,数据库用户名,密码);
      // 2.与数据库建立连接 
      
      Statement stmt = con.createStatement();
      ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM table1;");
	// 3.发送SQL语句,并得到返回结果 
      while (rs.next()) {
             int x = rs.getInt("a");
             String s = rs.getString("b");
             float f = rs.getFloat("c");
      }
      // 4.处理返回结果 
      rs.close();
      stmt.close();   
      con.close();
      // 5.释放资源
} //… …

7.2.4 数据库连接字符串

jdbc:数据库://ip:端口/数据库名称[连接参数=参数值]
  • 数据库:表示JDBC连接的目标数据库
  • ip: 表示JDBC所连接的目标数据库地址,如果是本地数据库,可为localhost,即本地主机名
  • 端口:连接数据库的端口号
  • 数据库名称:是目标数据库的名称
  • 连接参数:连接数据库时的参数配置

连接本地MySQL中hospital数据库

jdbc:mysql://localhost:3306/hospital?serverTimezone=GMT-8
// 我国处于东八区,时区设置为GMT-8

7.2.5 Connection 接口

Connection是数据库连接对象的类型

方法作用
Statement createStatement()创建一个Statement对象将SQL语句发送到数据库
PreparedStatement prepareStatement(String sql)创建一个PreparedStatement对象,将参数化的SQL语句发送到数据库
boolean isClosed()查询此Connection对象是否已经被关闭。如果已关闭,则返回true;否则返回false
void close()立即释放此Connection对象的数据库和JDBC资源

7.2.6 连接本地 hospital 数据库

package XaunZiShare;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.sql.*;

public class HospitalConn {
    private static Logger logger = LogManager.getLogger(HospitalConn.class.getName());

    public static void main(String[] args) {
        Connection conn = null;
        // 1、加载驱动
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            logger.error(e);
        }
        try {
            // 2、建立连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hospital?serverTimezone=UTC","root", "root");
            System.out.println("数据库连接成功");
        } catch (SQLException e) {
            logger.error(e);
        } finally {
            // 3、关闭数据库连接
            try {
                if (null != conn) {
                    conn.close();
                    System.out.println("数据库连接断开");
                }
            } catch (SQLException e) {
                logger.error(e);
            }
        }
    }
}

7.2.7 常见异常

使用JDBC连接数据库时,经常出现的错误

  • JDBC驱动类的名称书写错误,导致ClassNotFoundException异常
  • 数据连接字符串、数据库用户名、密码错误,导致SQLException异常
  • 数据库操作结束后,没有关闭数据库连接,导致仍旧占有系统资源
  • 关闭数据库连接语句没有放到finally语句块中,导致语句可能没有被执行

7.3 Statement 操作数据库

Java执行数据库操作的一个重要接口,在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句

  • Statement对象:执行不带参数的简单SQL语句
  • PreparedStatement对象:执行带或不带In参数的预编译SQL语句
方法作用
ResultSet executeQuery(String sql)可以执行SQL查询并获取ResultSet对象
int executeUpdate(String sql)可以执行插入、删除、更新的操作,返回值是执行该操作所影响的行数
boolean execute(String sql)可以执行任意SQL语句。如果结果为 ResultSet 对象,则返回 true;如果其为更新计数或者不存在任何结果,则返回false

使用 executeQuery() 和 executeUpdate() 方法都需要啊传入 SQL 语句,因此,需要在 Java 中通过字符串拼接获得 SQL 字符串

7.3.1 Java 的字符串操作

String类

  • 字符串常量一旦声明则不可改变
  • String类对象可以改变,但改变的是其内存地址的指向
  • 使用“+”作为数据的连接操作
  • 不适用频繁修改的字符串操作

StringBuffer类

  • StringBuffer类对象能够被多次修改,且不产生新的未使用对象
  • 使用append()方法进行数据连接
  • 适用于字符串修改操作
  • 是线程安全的,支持并发操作,适合多线程

如果使用StringBuffer 生成了 String 类型字符串,可以通过 toString( ) 方法将其转换为一个 String 对象

  • 需要拼接的字符串
String patientName="李明";
String gender="男";
String birthDate="2010-09-03";
  • 使用+拼接字符串
//使用+拼接字符串
String sql = "insert into patient (patientName,gender,birthDate) values('"+
	patientName+"','"+
	gender+"','"+
	birthDate+"');";
System.out.println(sql);
  • 使用StringBuffer拼接字符串
//使用StringBuffer拼接字符串
StringBuffer sbSql = new StringBuffer("insert into patient (patientName,gender,birthDate)" +
      "  values('");sbSql.append(patientName+"','");
sbSql.append(gender+"','");
sbSql.append(birthDate+"');");
sql = sbSql.toString();
System.out.println(sql);
  • SQL语句中,字符"'是等效的
  • 但在Java代码中拼接字符串时使用字符'会使代码更加清晰
  • 也不容易出错引号、逗号或括号等符号必须成对出现
  • 可在控制台输出拼接后的字符串,检查SQL语句是否正确

7.3.1 Statement 插入数据

使用Statement接口执行插入数据的操作的方法

  • executeUpdate()方法
  • execute()方法

如果希望得到插入成功的数据行数,可以使用executeUpdate()方法;否则,使用execute()方法

实现步骤

  • 声明Statement变量
  • 创建Statement对象
  • 构造SQL语句
  • 执行数据插入操作
  • 关闭Statement对象
  • 关闭顺序是后创建的对象要先关闭释放资源

演示案例

使用JDBC,向hospital数据库病人表中添加一个新的病人记录关键代码

package XaunZiShare;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class HospitalInsert {
    private static Logger logger = LogManager.getLogger(HospitalInsert.class.getName());

    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        String name = "张菲";
        // 姓名
        String gender = "女";
        // 性别
        String birthDate = "1995-02-12";
        // 出生日期
        String phoneNum = "13887676500";
        // 联系电话
        String email = "fei.zhang@qq.com";
        //邮箱
        String password = "909000";
        //密码
        String identityNum = "610000199502126100";
        //身份证号
        String address = "北京市";
        //地址
        // 1、加载驱动
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            logger.error(e);
        }
        try {
            // 2、建立连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hospital?serverTimezone=UTC", "root", "root");
            // 创建Statement对象
            stmt = conn.createStatement();
            //构造SQL
            StringBuffer sbSql = new StringBuffer("insert into patient (patientName,gender,birthDate,phoneNum,email,password,identityNum,address) values ( '");
            sbSql.append(name + "','");
            sbSql.append(gender + "','");
            sbSql.append(birthDate + "','");
            sbSql.append(phoneNum + "','");
            sbSql.append(email + "','");
            sbSql.append(password + "','");
            sbSql.append(identityNum + "','");
            sbSql.append(address + "');");
            System.out.println(sbSql.toString());
            //3、执行插入操作
            stmt.execute(sbSql.toString());
        } catch (SQLException e) {
            logger.error(e);
        } finally {
            // 4、关闭数据库连接
            try {
                if (null != stmt) {
                    stmt.close();
                }
                if (null != conn) {
                    conn.close();
                    System.out.println("数据库连接断开");
                }
            } catch (SQLException e) {
                logger.error(e);
            }
        }
    }
}

为了避免可能出现的乱码问题,可将指定数据库连接的编码集为UTF8,多个参数间使用字符&进行分隔

jdbc:mysql://localhost:3306/hospital?serverTimezone=GMT-8&useUnicode=true&characterEncoding=utf-8

7.3.2 Statement 更新数据

  • 使用executeUpdate()方法或execute()方法实现更新数据的操作
  • 使用Statement接口更新数据库中的数据的步骤与插入数据类似

实现步骤

  • 声明Statement变量
  • 创建Statement对象
  • 构造SQL语句
  • 执行数据更新操作
  • 关闭Statement对象

需关注拼接的SQL字符串,以避免出错

演示案例

使用JDBC,将hospital数据库中patientID为13的病人电话更新为13627395833

package XaunZiShare;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class HospitalUpdate {
    private static Logger logger = LogManager.getLogger(HospitalUpdate.class.getName());

    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        int patientID = 13;
        // 病人编号
        String phoneNum = "13627395833";
        // 联系电话
        // 1、加载驱动
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            logger.error(e);
        }
        try {
            // 2、建立连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hospital?serverTimezone=UTC", "root", "root");
            // 创建Statement对象
            stmt = conn.createStatement();
            //构造SQL
            StringBuffer sbSql = new StringBuffer("update patient ");
            sbSql.append("set phoneNum='" + phoneNum + "' ");
            sbSql.append("where patientID=" + patientID + ";");
            System.out.println(sbSql.toString());
            //3、执行插入更新操作
            int effectRowNum = stmt.executeUpdate(sbSql.toString());
            System.out.println("更新数据的行数:" + effectRowNum);
        } catch (SQLException e) {
            logger.error(e);
        } finally {
            // 4、关闭数据库连接
            try {
                if (null != stmt) {
                    stmt.close();
                }
                if (null != conn) {
                    conn.close();
                    System.out.println("数据库连接断开");
                }
            } catch (SQLException e) {
                logger.error(e);
            }
        }
    }
}

7.3.3 ResultSet 接口

保存和处理Statement执行后所产生的查询结果

  • 由查询结果组成的一个二维表
  • 每行代表一条记录
  • 每列代表一个字段
方法说明
boolean next()将游标从当前位置向下移动一行
void close()关闭ResultSet 对象
int getInt(int colIndex)以int形式获取结果集当前行指定列号值
int getInt(String colLabel)以int形式获取结果集当前行指定列名值
float getFloat(int colIndex)以float形式获取结果集当前行指定列号值
float getFloat(String colLabel)以float形式获取结果集当前行指定列名值
String getString(int colIndex)以String形式获取结果集当前行指定列号值
String getString(String colLabel)以String形式获取结果集当前行指定列名值
  • 要从中获取数据的列号或列名可作为方法的参数
  • 根据值的类型选择对应的方法

ResultSet 接口 getXxx() 方法

  • 获取当前行中某列的值
  • 要从中获取数据的列号或列名可作为方法的参数
  • 根据值的类型选择对应的方法
int类型       ->   getInt()
float类型    ->   getFloat()
String类型  ->   getString()

假设结果集的第一列为patientID,存储类型为int类型,能够获得该列值的两种方法

//使用列号提取数据
int id = rs.getInt(1);
//使用列名提取数据
int id = rs.getInt("patientID");
  • 列号从1开始计数,与数组下标从0开始计数不同
  • 采用列名来标识列可读性强,且不容易出错

7.3.4 Statement 和 ResultSet 查询数据

使用 JDBC 从 hospital 数据库中查询前3个病人的编号、姓名、性别、住址信息并输出到控制台上

package XaunZiShare;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.sql.*;

public class HospitalQuery {
    private static Logger logger = LogManager.getLogger(HospitalQuery.class.getName());

    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        int patientID = 13;
        // 病人编号
        String phoneNum = "13627395833";
        // 联系电话
        // 1、加载驱动
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            logger.error(e);
        }
        try {
            // 2、建立连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hospital?serverTimezone=UTC", "root", "root");
            System.out.println("建立连接成功 !");
            // 创建Statement对象
            stmt = conn.createStatement();
            //构造SQL
            String sql = "select patientID,patientName,gender,address from patient limit 3;";
            //3、执行查询更新操作
            rs = stmt.executeQuery(sql);
            //4、移动指针遍历结果集并输出查询结果
            while (rs.next()) {
                System.out.println(rs.getInt("patientID") + "\t" +
                        rs.getString("patientName") + "\t" +
                        rs.getString("gender") + "\t" +
                        rs.getString("address"));
            }

        } catch (SQLException e) {
            logger.error(e);
        } finally {
            // 5、关闭数据库连接
            try {
                if (null != rs) {
                    rs.close();
                }
                if (null != stmt) {
                    stmt.close();
                }
                if (null != conn) {
                    conn.close();
                    System.out.println("数据库连接断开");
                }
            } catch (SQLException e) {
                logger.error(e);
            }
        }
    }
}

7.4 PreparedStatement 操作数据库

7.4.1 SQL 注入攻击

通过提交一段SQL代码,执行超出用户访问权限的数据操作称为SQL注入(SQL Injection),SQL注入攻击是应用安全领域的一种常见攻击方式,会造成的数据库安全风险包括:刷库、拖库和撞库等,主要是没有对用户输入数据的合法性进行判断,导致应用程序存在安全隐患

使用JDBC实现医院管理系统用户登录验证功能

package XaunZiShare;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.sql.*;
import java.util.Scanner;

public class HospitalLogin {
    private static Logger logger = LogManager.getLogger(HospitalLogin.class.getName());

    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        //根据控制台提示输入用户身份证号和密码
        Scanner input = new Scanner(System.in);
        System.out.println("用户登录");
        System.out.print("请输入身份证号:");
        String identityNum = input.next();
        System.out.print("请输入密码:");
        String password = input.next();

        // 1、加载驱动
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            logger.error(e);
        }
        try {
            // 2、建立连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hospital?serverTimezone=UTC", "root", "123456");
            // 创建Statement对象
            stmt = conn.createStatement();
            //构造SQL
            StringBuffer sbSql = new StringBuffer("SELECT patientName FROM patient WHERE ");
            sbSql.append("password='" + password + "'");
            sbSql.append(" and identityNum='" + identityNum + "';");
            //3、执行查询更新操作
            rs = stmt.executeQuery(sbSql.toString());
            System.out.println(sbSql.toString());
            //4、验证用户名和密码
            if (rs.next()) {
                System.out.println("欢迎" + rs.getString("patientName") + "登录系统!");
            } else {
                System.out.println("密码错误!");
            }

        } catch (SQLException e) {
            logger.error(e);
        } finally {
            // 5、关闭数据库连接
            try {
                if (null != rs) {
                    rs.close();
                }
                if (null != stmt) {
                    stmt.close();
                }
                if (null != conn) {
                    conn.close();
                }
            } catch (SQLException e) {
                logger.error(e);
            }
        }
    }
}

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

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

修改查询结构

7.4.2 PreparedStatement 接口

使用PreparedStatement 接口

  • 继承自 Statement接口
  • 与Statement对象相比,使用更加灵活,更有效率

PreparedStatement接口 (预编译的 SQL 语句)

  • 提高代码可读性和可维护性

  • 提高安全性

  • 提高SQL语句执行的性能

方 法作 用
boolean execute()执行SQL语句,可以是任何SQL语句。如果结果是Result对象,则返回true。如果结果是更新计数或没有结果,则返回false
ResultSet executeQuery()执行SQL查询,返回该查询生成的ResultSet对象
int executeUpdate()执行SQL语句,该语句必须是一个DML语句,比如:INSERT、UPDATE或DELETE语句;或者是无返回内容的SQL语句,比如DDL语句。返回值是执行该操作所影响的行数
void setXxx(int index,xxx x)方法名Xxx和第二个参数的xxx均表示(如int,float,double等)基本数据类型,且两个类型需一致,参数列表中的x表示方法的形式参数。把指定数据类型(xxx)的值x设置给index位置的参数。根据参数类型的不同,常见方法有:setInt(int index,int x) 、setFloat(int index,float x)、setDouble(int index,double x)等
void setObject(int index,Object x)除基本数据类型外,参数类型也可以是Object,可以将Object对象x设置给index位置的参数

7.4.3 PreparedStatement 操作数据

创建PreparedStatement对象

  • 使用Connection接口prepareStatement(String sql)方法创建PreparedStatement对象
  • 需要提前设置该对象将要执行的SQL语句
  • SQL语句可具有一个或多个输入参数

设置输入参数的值

  • 调用setXxx()方法完成参数赋值

执行SQL语句

  • 调用PreparedStatement接口
  • executeQuery()
  • executeUpdate()
  • execute()
  • 方法执行SQL语句

验证用户输入的身份证号和密码

  • 如果通过验证,则输出“欢迎[姓名]登录系统!”的信息;
  • 否则输出“密码错误!”
package XaunZiShare;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.sql.*;
import java.util.Scanner;

public class HospitalLogin {
    private static Logger logger = LogManager.getLogger(HospitalLogin.class.getName());

    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        //根据控制台提示输入用户身份证号和密码
        Scanner input = new Scanner(System.in);
        System.out.println("用户登录");
        System.out.print("请输入身份证号:");
        String identityNum = input.next();
        System.out.print("请输入密码:");
        String password = input.next();

        // 1、加载驱动
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            logger.error(e);
        }
        try {
            // 2、建立连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hospital?serverTimezone=UTC", "root", "123456");

            //3、构造PreparedStatement对象
            pstmt = conn.prepareStatement("SELECT patientName FROM patient WHERE identityNum=? and password=?");
            pstmt.setString(1, identityNum);
            pstmt.setString(2, password);
            rs = pstmt.executeQuery();
            //4、验证用户名和密码
            if (rs.next()) {
                System.out.println("欢迎" + rs.getString("patientName") + "登录系统!");
            } else {
                System.out.println("密码错误!");
            }

        } catch (SQLException e) {
            logger.error(e);
        } finally {
            // 5、关闭数据库连接
            try {
                if (null != rs) {
                    rs.close();
                }
                if (null != pstmt) {
                    pstmt.close();
                }
                if (null != conn) {
                    conn.close();
                }
            } catch (SQLException e) {
                logger.error(e);
            }
        }
    }
}

7.4.4 PreparedStatement 的优势

实际开发中,推荐使用PreparedStatement接口执行数据库操作

  • PreparedStatement与Statement接口相比,具有的优势
  • 可读性和可维护性高
  • SQL语句执行性能高
  • 安全性更高

7.5 Properties 配置文件

7.5.1 为什么使用 Properties 类

使用JDBC技术访问数据库数据的关键代码

private String driver = "com.mysql.jdbc.Driver";
private String url = "jdbc:mysql://localhost:3306/hospital?serverTimezone=GMT-8";
private  String user = “root"; 	
private  String password=123456"; 
// 修改后需重新编译
Connection conn = null;
public Connection getConnection() {
    if(conn==null) {
        try {
            Class.forName(driver);
            conn = DriverManager.getConnection(url, user, password);
        } catch (Exception e) {//省略代码……}
    }	
    return conn;// 返回连接对象
}

让用户脱离程序本身修改相关的变量设置——使用配置文件

7.5.2 properties配置文件

Java的配置文件常为properties文件

  • 后缀为.properties
  • 以“键=值”格式储存数据
  • 使用“#”添加注释

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

通常,为数据库访问添加的配置文件是database.properties

7.5.3 读取配置文件信息

使用java.util包下的Properties类读取配置文件

方法描述
String getProperty(String key)用指定的键在此属性列表中搜索属性,通过参数key得到其所对应的值
Object setProperty(String key, String value)通过调用基类Hashtable的put()方法设置键-值对
void load(InputStream inStream)从输入流中读取属性列表 (键和元素对),通过对指定文件进行装载获取该文件中所有键-值对
void clear()清除所装载的键-值对,该方法由基类Hashtable提供

使用Properties配置文件的方式改造医院管理系统

package XaunZiShare;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;

public class HospitalSystem {
    private static Logger logger = LogManager.getLogger(HospitalSystem.class.getName());

    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        String patientID = null;
        boolean isExist = false;
        //根据控制台提示输入用户身份证号和密码
        Scanner input = new Scanner(System.in);
        System.out.println("用户登录");
        System.out.print("请输入身份证号:");
        String identityNum = input.next();
        System.out.print("请输入密码:");
        String password = input.next();


        Properties params = new Properties();
        String configFile = "database.properties";
        //配置文件路径
        String url = null;
        String username = null;
        String pwd = null;
        //加载配置文件到输入流中
        try {
            InputStream is = HospitalSystem.class.getClassLoader().getResourceAsStream(configFile);
            params.load(is);
            //根据指定的获取对应的值
            String driver = params.getProperty("driver");
            url = params.getProperty("url");
            username = params.getProperty("username");
            pwd = params.getProperty("password");

            // 1、加载驱动
            Class.forName(driver);
        } catch (IOException e) {
            logger.error(e);
        } catch (ClassNotFoundException e) {
            logger.error(e);
        }

        try {
            // 2、建立连接
            conn = DriverManager.getConnection(url, username, pwd);

            //3、构造PreparedStatement对象
            pstmt = conn.prepareStatement("SELECT patientID, patientName FROM patient WHERE identityNum=? and password=?");
            pstmt.setString(1, identityNum);
            pstmt.setString(2, password);
            rs = pstmt.executeQuery();
            //4、验证用户名和密码
            if (rs.next()) {
                patientID = rs.getString("patientID");
                System.out.println("欢迎" + rs.getString("patientName") + "登录系统!");
                while (!isExist) {
                    System.out.println("1.查询检查记录\t 0.退出");
                    System.out.print("请输入要执行的操作:");
                    String action = input.next();
                    if (action.equals("1")) {
                        pstmt = conn.prepareStatement("SELECT depName, checkItemName, checkResult, checkItemCost, examDate FROM prescription p  INNER JOIN department d ON p.depID = d.depID INNER JOIN checkitem c ON p.checkItemID = c.checkItemID WHERE p.patientID=?;");
                        pstmt.setString(1, patientID);
                        rs = pstmt.executeQuery();
                        System.out.println("检查科室\t检查项目\t检查结果\t检查费用\t检查时间");
                        while (rs.next()) {
                            System.out.println(rs.getString("depName") + "\t" + rs.getString("checkItemName") + "\t" + rs.getString("checkResult") + "\t" + rs.getString("checkItemCost") + "\t" + rs.getString("examDate") + "\t");
                        }
                    } else if (action.equals("0")) {
                        isExist = true;
                        System.out.println("再见");
                    } else {
                        System.out.println("输入错误,请重新输入");
                    }
                }
            } else {
                System.out.println("密码错误!");
            }

        } catch (SQLException e) {
            logger.error(e);
        } finally {
            // 5、关闭数据库连接
            try {
                if (null != rs) {
                    rs.close();
                }
                if (null != pstmt) {
                    pstmt.close();
                }
                if (null != conn) {
                    conn.close();
                }
            } catch (SQLException e) {
                logger.error(e);
            }
        }
    }
}

7.6 DAO 模式

7.6.1 什么是 DAO

非常流行的数据访问模式——DAO模式

  • Data Access Object(数据存取对象)
  • 位于业务逻辑和持久化数据之间
  • 实现对持久化数据的访问

DAO起着转换器的作用,把实体类转换为数据库中的记录

7.6.2 DAO 模式的组成

组成部分

  • DAO接口
  • DAO实现类
  • 实体类
  • 数据库连接和关闭工具类

优势

  • 隔离了数据访问代码和业务逻辑代码
  • 隔离了不同数据库实现

7.6.3 使用实体类传递数据

数据访问代码和业务逻辑代码之间通过实体类来传输数据

业务逻辑代码
数据访问代码

实体类特征

  • 属性一般使用private修饰
  • 提供public修饰的getter/setter方法
  • 实体类提供无参构造方法,根据业务提供有参构造
  • 实现java.io.Serializable接口,支持序列化机制

7.6.4 实体类

实体类(Entity)是Java应用程序中与数据库表对应的类

  • 用于存储数据,并提供对这些数据的访问
  • 通常,实现类是持久的,需要存储于文件或数据库中
  • 访问操作数据库时,以实体类的方式组织数据库中的实体及关系
  • 通常,在Java工程中创建一个名为entity的Package,用于集中保存实体类
  • 一个数据库表对应一个实体类

7.6.5 定义实体类

package XaunZiShare;

import java.io.Serializable;

public class Patient implements Serializable {
    private static final long serialVersionUID = -8762235641468472877L;
    private String patientID;  //病人编号
    private String password; //登录密码
    private String birthDate; //出生日期
    private String gender; //性别
    private String patientName; //姓名
    private String phoneNum; //联系电话
    private String email; //邮箱
    private String identityNum; //身份证号
    private String address; //地址

    /**
     * 无参构造方法
     */
    public Patient() {

    }

    /**
     * 有参构造方法,根据需要提供
     *
     * @param identityNum 身份证号
     * @param name        姓名
     */
    public Patient(String identityNum, String name) {
        this.identityNum = identityNum;
        this.patientName = name;
    }

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public String getPatientID() {
        return patientID;
    }

    public void setPatientID(String patientID) {
        this.patientID = patientID;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(String birthDate) {
        this.birthDate = birthDate;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getPatientName() {
        return patientName;
    }

    public void setPatientName(String patientName) {
        this.patientName = patientName;
    }

    public String getPhoneNum() {
        return phoneNum;
    }

    public void setPhoneNum(String phoneNum) {
        this.phoneNum = phoneNum;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getIdentityNum() {
        return identityNum;
    }

    public void setIdentityNum(String identityNum) {
        this.identityNum = identityNum;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

7.6.6 使用实体类传递数据

package XaunZiShare;

import com.javamysql.entity.Patient;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;

public class HospitalSystem {
    private static Logger logger = LogManager.getLogger(HospitalSystem.class.getName());

    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        Patient patient = new Patient();
        boolean isExist = false;
        //根据控制台提示输入用户身份证号和密码
        Scanner input = new Scanner(System.in);
        System.out.println("用户登录");
        System.out.print("请输入身份证号:");
        patient.setIdentityNum(input.next());
        System.out.print("请输入密码:");
        patient.setPassword(input.next());

        Properties params = new Properties();
        String configFile = "database.properties";
        //配置文件路径
        String url = null;
        String username = null;
        String pwd = null;
        //加载配置文件到输入流中
        try {
            InputStream is = HospitalSystem.class.getClassLoader().getResourceAsStream(configFile);
            params.load(is);
            //根据指定的获取对应的值
            String driver = params.getProperty("driver");
            url = params.getProperty("url");
            username = params.getProperty("username");
            pwd = params.getProperty("password");

            // 1、加载驱动
            Class.forName(driver);
        } catch (IOException e) {
            logger.error(e);
        } catch (ClassNotFoundException e) {
            logger.error(e);
        }

        try {
            // 2、建立连接
            conn = DriverManager.getConnection(url, username, pwd);

            //3、构造PreparedStatement对象
            pstmt = conn.prepareStatement("SELECT * FROM patient WHERE identityNum=? and password=?");
            pstmt.setString(1, patient.getIdentityNum());
            pstmt.setString(2, patient.getPassword());
            rs = pstmt.executeQuery();
            //4、验证用户名和密码
            if (rs.next()) {
                //从MySQL读取用户信息,并加载到patient对象中
                patient.setPatientID(rs.getString("patientID"));
                patient.setAddress(rs.getString("address"));
                patient.setBirthDate(rs.getString("birthDate"));
                patient.setEmail(rs.getString("email"));
                patient.setGender(rs.getString("gender"));
                patient.setPatientID(rs.getString("patientName"));
                patient.setPhoneNum(rs.getString("phoneNum"));

                System.out.println("欢迎" + patient.getPatientName() + "登录系统!");
                while (!isExist) {
                    System.out.println("1.查询检查记录\t2.查询病人信息\t 0.退出");
                    System.out.print("请输入要执行的操作:");
                    String action = input.next();
                    if (action.equals("1")) {
                        pstmt = conn.prepareStatement("SELECT depName, checkItemName, checkResult, checkItemCost, examDate FROM prescription p  INNER JOIN department d ON p.depID = d.depID INNER JOIN checkitem c ON p.checkItemID = c.checkItemID WHERE p.patientID=?;");
                        pstmt.setString(1, patient.getPatientID());
                        rs = pstmt.executeQuery();
                        System.out.println("检查科室\t检查项目\t检查结果\t检查费用\t检查时间");
                        while (rs.next()) {
                            System.out.println(rs.getString("depName") + "\t" + rs.getString("checkItemName") + "\t" + rs.getString("checkResult") + "\t" + rs.getString("checkItemCost") + "\t" + rs.getString("examDate") + "\t");
                        }
                    } else if (action.equals("2")) {
                        System.out.println(patient.getPatientID() + "\t" + patient.getPatientName() + "\t" + patient.getGender() + "\t" + patient.getBirthDate() + "\t" + patient.getIdentityNum() + "\t" + patient.getPhoneNum() + "\t" + patient.getEmail() + "\t" + patient.getAddress());
                    } else if (action.equals("0")) {
                        isExist = true;
                        System.out.println("再见");
                    } else {
                        System.out.println("输入错误,请重新输入");
                    }
                }
            } else {
                System.out.println("密码错误!");
            }

        } catch (SQLException e) {
            logger.error(e);
        } finally {
            // 5、关闭数据库连接
            try {
                if (null != rs) {
                    rs.close();
                }
                if (null != pstmt) {
                    pstmt.close();
                }
                if (null != conn) {
                    conn.close();
                }
            } catch (SQLException e) {
                logger.error(e);
            }
        }
    }
}

7.6.7 实体类的特征

实体类特征

  • 属性一般使用private修饰
  • 提供public修饰的getter/setter方法
  • 实体类提供无参构造方法,根据业务提供有参构造
  • 实现java.io.Serializable接口,支持序列化机制,可以将该对象转换成字节序列而保存在磁盘上或在网络上传输

如果实体类实现了java.io.Serializable接口,应该定义属性serialVersionUID,解决不同版本之间的序列化问题

为serialVersionUID赋值的方法

  • 手动

  • 使用IDEA生成

  • private static final long serialVersionUID = -8762235641468472877L;
    

一旦为一个实体类的serialVersionUID赋值,就不要再修改;否则,在反序列化之前版本的数据时,会报java.io.InvalidClassException异常


7.7 实现 JDBC 封装

7.7.1 JDBC

将程序中数据在瞬时状态和持久状态间转换的机制为数据持久化

JDBC

  • 读取
  • 删除
  • 查找
  • 修改
  • 保存

7.7.2 持久化的实现方式

数据库

普通文件

XML文件

7.7.3 为什么进行 JDBC 封装

Scanner input = new Scanner(System.in);
System.out.print("请输入登录名:");
String name=input.next();
System.out.print("请输入登录密码:");
String password=input.next();
// 业务相关代码
// ……省略加载驱动
try {
    conn = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/hospital?serverTimezone=GMT-8",
                "root", "123456");
    // … …省略代码 … …
    if(rs.next())
        System.out.println("登录成功,欢迎您!");
    else
	System.out.println("登录失败,请重新输入!");	
    // ……省略代码
} catch (SQLException e) {			
    // ……省略代码
} finally {}
// 数据访问代码

业务代码和数据访问代码耦合

  • 可读性差
  • 不利于后期修改和维护
  • 不利于代码复用

采用面向接口编程,可以降低代码间的耦合性

采用面向接口编程,可以降低代码间的耦合性

业务逻辑代码
数据访问代码
MySQL
SQLServer
Oracle

业务逻辑代码调用数据访问接口

7.7.4 使用 DAO 模式改造 Hospital

将HospitalSystem中对病人的所有数据库操作抽象成接口

对病人的数据库操作包括修改病人信息、通过身份证号和密码验证登录

设计接口时,尽量以对象为单位,给调用者提供面向对象的接口

  • 使用实体类作为接口的参数和返回值,可以让接口更加清晰简洁
  • 如果以Patient类的各个属性为形参进行传递,不仅会导致参数个数很多,还会增加接口和实现类中方法的数量等

接口的命名,应以简明为主

  • “实体类名+Dao”格式如
  • PatientDao
  • 作为工程中相对独立的模块
  • 所有DAO接口文件都放在dao包中

接口由不同数据库的实现类分别实现

PatientDao 接口

package XaunZiShare;

import com.javamysql.entity.Patient;

public interface PatientDao {
    /**
     * 更新病人信息
     *
     * @param patient 病人
     */
    int update(Patient patient);

    /**
     * 根据身份证号和登录密码返回病人信息
     *
     * @param identityNum 身份证号
     * @param pwd         登录密码
     * @return 病人
     */
    Patient getPatientByIdNumAndPwd(String identityNum, String pwd);
}

PatientDao实现类的方法:update()方法

package XaunZiShare;

import com.javamysql.HospitalSystem;
import com.javamysql.dao.PatientDao;
import com.javamysql.entity.Patient;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class PatientDaoMySQLImpl implements PatientDao {
    private static Logger logger = LogManager.getLogger(HospitalSystem.class.getName());

    @Override
    public int update(Patient patient) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        int result = 0;

        Properties params = new Properties();
        String configFile = "database.properties";//配置文件路径
        String url = null;
        String username = null;
        String password = null;
        //加载配置文件到输入流中
        try {
            InputStream is = HospitalSystem.class.getClassLoader().getResourceAsStream(configFile);
            params.load(is);
            //根据指定的获取对应的值
            String driver = params.getProperty("driver");
            url = params.getProperty("url");
            username = params.getProperty("username");
            password = params.getProperty("password");

            // 1、加载驱动
            Class.forName(driver);
        } catch (IOException e) {
            logger.error(e);
        } catch (ClassNotFoundException e) {
            logger.error(e);
        }

        try {
            // 2、建立连接
            conn = DriverManager.getConnection(url, username, password);

            //3、构造PreparedStatement对象
            pstmt = conn.prepareStatement("UPDATE patient SET address=?, birthDate=?, email=?, gender=?, patientName=?, phoneNum=?, identityNum=?,password=? WHERE patientID=?");
            pstmt.setString(1, patient.getAddress());
            pstmt.setString(2, patient.getBirthDate());
            pstmt.setString(3, patient.getEmail());
            pstmt.setString(4, patient.getGender());
            pstmt.setString(5, patient.getPatientName());
            pstmt.setString(6, patient.getPhoneNum());
            pstmt.setString(7, patient.getIdentityNum());
            pstmt.setString(8, patient.getPassword());
            pstmt.setString(9, patient.getPatientID());
            result = pstmt.executeUpdate();
        } catch (SQLException e) {
            logger.error(e);
        } finally {
            // 5、关闭数据库连接
            try {
                if (null != pstmt) {
                    pstmt.close();
                }
                if (null != conn) {
                    conn.close();
                }
            } catch (SQLException e) {
                logger.error(e);
            }
        }
        return result;
    }

    @Override
    public Patient getPatientByIdNumAndPwd(String identityNum, String pwd) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        Patient patient = null;
        Properties params = new Properties();
        String configFile = "database.properties";
        //配置文件路径
        String url = null;
        String username = null;
        String password = null;
        //加载配置文件到输入流中
        try {
            InputStream is = HospitalSystem.class.getClassLoader().getResourceAsStream(configFile);
            params.load(is);
            //根据指定的获取对应的值
            String driver = params.getProperty("driver");
            url = params.getProperty("url");
            username = params.getProperty("username");
            password = params.getProperty("password");

            // 1、加载驱动
            Class.forName(driver);
        } catch (IOException e) {
            logger.error(e);
        } catch (ClassNotFoundException e) {
            logger.error(e);
        }

        try {
            // 2、建立连接
            conn = DriverManager.getConnection(url, username, password);

            //3、构造PreparedStatement对象
            pstmt = conn.prepareStatement("SELECT * FROM patient WHERE identityNum=? and password=?");
            pstmt.setString(1, identityNum);
            pstmt.setString(2, pwd);
            rs = pstmt.executeQuery();
            //4、验证用户名和密码
            if (rs.next()) {
                //从MySQL读取用户信息,并加载到patient对象中
                patient = new Patient();
                patient.setPatientID(rs.getString("patientID"));
                patient.setAddress(rs.getString("address"));
                patient.setBirthDate(rs.getString("birthDate"));
                patient.setEmail(rs.getString("email"));
                patient.setGender(rs.getString("gender"));
                patient.setPatientName(rs.getString("patientName"));
                patient.setPhoneNum(rs.getString("phoneNum"));
                patient.setIdentityNum(rs.getString("identityNum"));
                patient.setPassword(rs.getString("password"));
            }

        } catch (SQLException e) {
            logger.error(e);
        } finally {
            // 5、关闭数据库连接
            try {
                if (null != rs) {
                    rs.close();
                }
                if (null != pstmt) {
                    pstmt.close();
                }
                if (null != conn) {
                    conn.close();
                }
            } catch (SQLException e) {
                logger.error(e);
            }
        }
        return patient;
    }
}

通用的操作是否能够进一步简化?


7.8 BaseDao基类

7.8.1 将通用的操作(打开、关闭连接等)封装到基类

package XaunZiShare;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class BaseDao {
    private static String driver;
    // 数据库驱动字符串
    private static String url;
    // 连接URL字符串
    private static String user;
    // 数据库用户名
    private static String password;
    // 用户密码

    // 数据连接对象
    static {//静态代码块,在类加载的时候执行
        init();
    }

    Connection conn = null;

    /**
     * 初始化连接参数,从配置文件里获得
     */
    public static void init() {
        Properties params = new Properties();
        String configFile = "database.properties";
        //配置文件路径
        //加载配置文件到输入流中
        InputStream is = BaseDao.class.getClassLoader().getResourceAsStream(configFile);
        try {
            //从输入流中读取属性列表
            params.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //根据指定的获取对应的值
        driver = params.getProperty("driver");
        url = params.getProperty("url");
        user = params.getProperty("username");
        password = params.getProperty("password");
    }

    /**
     * 获取数据库连接对象
     */
    public Connection getConnection() {
        try {
            if (conn == null || conn.isClosed()) {
                // 获取连接并捕获异常
                try {
                    Class.forName(driver);
                    conn = DriverManager.getConnection(url, user, password);
                } catch (Exception e) {
                    e.printStackTrace();
                    // 异常处理
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
        // 返回连接对象
    }

    /**
     * 关闭数据库连接
     *
     * @param conn 数据库连接
     * @param stmt Statement对象
     * @param rs   结果集
     */
    public void closeAll(Connection conn, Statement stmt, ResultSet rs) {
        // 若结果集对象不为空,则关闭
        if (rs != null) {
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 若Statement对象不为空,则关闭
        if (stmt != null) {
            try {
                stmt.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 若数据库连接对象不为空,则关闭
        if (conn != null) {
            try {
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

7.8.2 基类 BaseDao:增、删、改的通用方法

    /**
     * 增、删、改的操作
     *
     * @param preparedSql 预编译的 SQL 语句
     * @param param       参数的字符串数组
     * @return 影响的行数
     */
    public int exceuteUpdate(String preparedSql, Object[] param) {
        PreparedStatement pstmt = null;
        int num = 0;
        conn = getConnection();
        try {
            pstmt = conn.prepareStatement(preparedSql);
            if (param != null) {
                for (int i = 0; i < param.length; i++) {
                    //为预编译sql设置参数
                    pstmt.setObject(i + 1, param[i]);
                }
            }
            num = pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            closeAll(conn, pstmt, null);
        }
        return num;
    }

7.8.3 实现类实现接口并继承 BaseDao 基类

package XaunZiShare;

import com.javamysql.HospitalSystem;
import com.javamysql.dao.BaseDao;
import com.javamysql.dao.PatientDao;
import com.javamysql.entity.Patient;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class PatientDaoMySQLImpl extends BaseDao implements PatientDao {
    private static Logger logger = LogManager.getLogger(HospitalSystem.class.getName());

    @Override
    public int update(Patient patient) {
        //构造SQL语句
        String preparedSQL = "UPDATE patient SET address=?, birthDate=?, email=?, gender=?, patientName=?, phoneNum=?, identityNum=?,password=? WHERE patientID=?";
        //构造SQL执行参数数组
        List<String> params = new ArrayList<String>();
        params.add(patient.getAddress());
        params.add(patient.getBirthDate());
        params.add(patient.getEmail());
        params.add(patient.getGender());
        params.add(patient.getPatientName());
        params.add(patient.getPhoneNum());
        params.add(patient.getIdentityNum());
        params.add(patient.getPassword());
        params.add(patient.getPatientID());
        //调用BaseDao中的更新
        return exceuteUpdate(preparedSQL, params.toArray());
    }

    @Override
    public Patient getPatientByIdNumAndPwd(String identityNum, String pwd) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        Patient patient = null;

        conn = getConnection();

        try {
            //构造PreparedStatement对象
            pstmt = conn.prepareStatement("SELECT * FROM patient WHERE identityNum=? and password=?");
            pstmt.setString(1, identityNum);
            pstmt.setString(2, pwd);
            rs = pstmt.executeQuery();
            //验证用户名和密码
            if (rs.next()) {
                //从MySQL读取用户信息,并加载到patient对象中
                patient = new Patient();
                patient.setPatientID(rs.getString("patientID"));
                patient.setAddress(rs.getString("address"));
                patient.setBirthDate(rs.getString("birthDate"));
                patient.setEmail(rs.getString("email"));
                patient.setGender(rs.getString("gender"));
                patient.setPatientName(rs.getString("patientName"));
                patient.setPhoneNum(rs.getString("phoneNum"));
                patient.setIdentityNum(rs.getString("identityNum"));
                patient.setPassword(rs.getString("password"));
            }

        } catch (SQLException e) {
            logger.error(e);
        } finally {
            //关闭数据库连接
            closeAll(conn, pstmt, rs);
        }
        return patient;
    }
}

此种封装JDBC的模式称为DAO模式


7.9 项目结构图

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

7.9.1 项目名

项目名其实就是反过来的网站网址

7.9.2 enrity(实体类)

这个包里面放的是都是,数据表的实体类,(POJO)。即具有,基础属性,属性封装(getter/setter),构造函数,和Tostring()的普通类

7.9.3 dao(接口类)

dao 包里面还有一层包叫``impl即实现类,dao 包下的接口类只定义方法,具体实现由impl`包下的实现类实现

7.9.4 service (业务类)

service 为业务层,将基础实现类,整合为复杂业务,与dao 包一样包内部还有一层包impl service 包下的接口类只定义方法,具体实现由impl包下的实现类实现

7.9.5 Main (运行类)

代码运行类

7.9.6 lib(依赖包)

项目所需的 JDBC 驱动 放置在此包内

7.9.7 database (连接数据库配置文件)

项目连接数据库所需要的配置文件


7.10 命名规范

直接参考阿里巴巴发布的《阿里巴巴Java开发手册(终极版)v1.3版本》

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


八、综合项目实训

以下项目都是我练习使用,部分代码可能无法正常运行,当做参考即可

8.1 理发店办卡项目

8.1.1 项目结构图

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

8.1.2 创建数据库sql

/*
 Navicat Premium Data Transfer

 Source Server         : XuanZi
 Source Server Type    : MySQL
 Source Server Version : 50740 (5.7.40)
 Source Host           : localhost:3306
 Source Schema         : block

 Target Server Type    : MySQL
 Target Server Version : 50740 (5.7.40)
 File Encoding         : 65001

 Date: 25/03/2023 01:03:21
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for blocktype
-- ----------------------------
DROP TABLE IF EXISTS `blocktype`;
CREATE TABLE `blocktype`  (
  `blockId` int(11) NOT NULL AUTO_INCREMENT COMMENT '卡类型id',
  `blockType` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '卡类型',
  `discount` double(2, 2) NOT NULL COMMENT '享受折扣',
  `minRange` double(8, 2) NOT NULL COMMENT '最小范围',
  `maxRange` double(8, 2) NOT NULL COMMENT '最大范围',
  PRIMARY KEY (`blockId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of blocktype
-- ----------------------------
INSERT INTO `blocktype` VALUES (1, '白银卡', 0.95, 30.00, 99.00);
INSERT INTO `blocktype` VALUES (2, '黄金卡', 0.90, 100.00, 199.00);
INSERT INTO `blocktype` VALUES (4, '钻石卡', 0.80, 200.00, 499.00);
INSERT INTO `blocktype` VALUES (5, '尊享终生VIP卡', 0.50, 500.00, 1000.00);

-- ----------------------------
-- Table structure for consumption
-- ----------------------------
DROP TABLE IF EXISTS `consumption`;
CREATE TABLE `consumption`  (
  `consumptionId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `userInfoId` int(11) NOT NULL COMMENT '用户id',
  `staffTypeId` int(11) NOT NULL COMMENT '员工类型ID',
  `projectTypeId` int(11) NOT NULL COMMENT '项目类型编号',
  `consumptionAmount` double(8, 2) NOT NULL COMMENT '消费金额,格局消费的项目和员工类型来决定',
  `actualAmount` double(8, 2) NOT NULL COMMENT '实际消费金额,根据用户卡类型打折',
  `recordTime` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '消费时间',
  PRIMARY KEY (`consumptionId`) USING BTREE,
  INDEX `FK_projectTypeId`(`staffTypeId`) USING BTREE,
  INDEX `FK_staffId_consumption`(`projectTypeId`) USING BTREE,
  INDEX `FK_userInfoId`(`userInfoId`) USING BTREE,
  CONSTRAINT `FK_projectTypeId` FOREIGN KEY (`projectTypeId`) REFERENCES `projecttype` (`projectTypeId`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `FK_staffTypeId_1` FOREIGN KEY (`staffTypeId`) REFERENCES `stafftype` (`staffTypeId`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `FK_userInfoId` FOREIGN KEY (`userInfoId`) REFERENCES `userinfo` (`userInfoId`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of consumption
-- ----------------------------
INSERT INTO `consumption` VALUES (1, 1, 3, 4, 30.00, 28.50, '2023-03-24 11:44:06');
INSERT INTO `consumption` VALUES (2, 2, 3, 4, 40.00, 36.00, '2023-03-24 11:57:41');
INSERT INTO `consumption` VALUES (3, 1, 3, 4, 30.00, 28.80, '2023-03-24 16:12:26');
INSERT INTO `consumption` VALUES (5, 1, 1, 1, 50.00, 47.50, '2023-03-24 18:38:47');

-- ----------------------------
-- Table structure for projecttype
-- ----------------------------
DROP TABLE IF EXISTS `projecttype`;
CREATE TABLE `projecttype`  (
  `projectTypeId` int(11) NOT NULL AUTO_INCREMENT COMMENT '项目类型id',
  `projectTypeName` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '项目名称',
  `projectTypePrice` double(8, 2) NOT NULL COMMENT '项目价格',
  `staffTypeId` int(11) NOT NULL COMMENT '员工类型id',
  PRIMARY KEY (`projectTypeId`) USING BTREE,
  INDEX `FK_staffTypeId2`(`staffTypeId`) USING BTREE,
  CONSTRAINT `FK_staffTypeId2` FOREIGN KEY (`staffTypeId`) REFERENCES `stafftype` (`staffTypeId`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of projecttype
-- ----------------------------
INSERT INTO `projecttype` VALUES (1, '剪头', 50.00, 2);
INSERT INTO `projecttype` VALUES (2, '剪头', 40.00, 3);
INSERT INTO `projecttype` VALUES (3, '剪头', 30.00, 4);
INSERT INTO `projecttype` VALUES (4, '洗剪吹', 80.00, 2);
INSERT INTO `projecttype` VALUES (5, '洗剪吹', 70.00, 3);
INSERT INTO `projecttype` VALUES (6, '洗剪吹', 60.00, 4);
INSERT INTO `projecttype` VALUES (7, '烫头', 110.00, 2);
INSERT INTO `projecttype` VALUES (8, '烫头', 100.00, 3);
INSERT INTO `projecttype` VALUES (9, '烫头', 90.00, 4);

-- ----------------------------
-- Table structure for staff
-- ----------------------------
DROP TABLE IF EXISTS `staff`;
CREATE TABLE `staff`  (
  `staffId` int(11) NOT NULL AUTO_INCREMENT COMMENT '员工id',
  `staffName` varchar(10) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '员工姓名',
  `staffTypeId` int(11) NOT NULL COMMENT '员工类型',
  PRIMARY KEY (`staffId`) USING BTREE,
  INDEX `FK_staffTypeId`(`staffTypeId`) USING BTREE,
  CONSTRAINT `FK_staffTypeId` FOREIGN KEY (`staffTypeId`) REFERENCES `stafftype` (`staffTypeId`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of staff
-- ----------------------------
INSERT INTO `staff` VALUES (1, '于昂', 1);
INSERT INTO `staff` VALUES (2, '韩松', 2);
INSERT INTO `staff` VALUES (3, '吕永成', 3);
INSERT INTO `staff` VALUES (4, '敬添', 3);
INSERT INTO `staff` VALUES (5, '方艺科', 4);
INSERT INTO `staff` VALUES (6, '赵梦涛', 4);
INSERT INTO `staff` VALUES (7, '梁瑞康', 4);

-- ----------------------------
-- Table structure for stafftype
-- ----------------------------
DROP TABLE IF EXISTS `stafftype`;
CREATE TABLE `stafftype`  (
  `staffTypeId` int(11) NOT NULL AUTO_INCREMENT COMMENT '员工类型',
  `staffTypeName` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '员工类型名称',
  PRIMARY KEY (`staffTypeId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of stafftype
-- ----------------------------
INSERT INTO `stafftype` VALUES (1, '店长');
INSERT INTO `stafftype` VALUES (2, '首席');
INSERT INTO `stafftype` VALUES (3, '资深师傅');
INSERT INTO `stafftype` VALUES (4, '普通员工');

-- ----------------------------
-- Table structure for userinfo
-- ----------------------------
DROP TABLE IF EXISTS `userinfo`;
CREATE TABLE `userinfo`  (
  `userInfoId` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户编号 ',
  `userInfoName` varchar(10) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '用户姓名',
  `phone` varchar(11) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '电话号码',
  `balance` double(8, 2) NOT NULL DEFAULT 0.00 COMMENT '余额',
  `staffId` int(11) NOT NULL COMMENT '员工id',
  `openBlock` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '开卡时间',
  `rechargeable` double(8, 2) NOT NULL COMMENT '第一次充值金额',
  `blockId` int(11) NOT NULL COMMENT '卡类型id',
  PRIMARY KEY (`userInfoId`) USING BTREE,
  INDEX `FK_staffid`(`staffId`) USING BTREE,
  INDEX `FK_calorie`(`blockId`) USING BTREE,
  CONSTRAINT `FK_blockId` FOREIGN KEY (`blockId`) REFERENCES `blocktype` (`blockId`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `FK_staffid` FOREIGN KEY (`staffId`) REFERENCES `staff` (`staffId`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of userinfo
-- ----------------------------
INSERT INTO `userinfo` VALUES (1, '玄子', '111346518', 20.00, 2, '2023-03-24 23:48:18', 60.00, 1);
INSERT INTO `userinfo` VALUES (2, '玉玉诏', '142141411', 30.00, 2, '2023-03-24 11:21:02', 100.00, 2);
INSERT INTO `userinfo` VALUES (3, '玄子Share', '132131231', 30.00, 2, '2023-03-24 11:33:07', 222.00, 2);
INSERT INTO `userinfo` VALUES (4, '刘景宇', '223213132', 2.00, 2, '2023-03-24 11:33:09', 22.00, 2);

-- ----------------------------
-- Procedure structure for Proc_1
-- ----------------------------
DROP PROCEDURE IF EXISTS `Proc_1`;
delimiter ;;
CREATE PROCEDURE `Proc_1`()
BEGIN


END
;;
delimiter ;

SET FOREIGN_KEY_CHECKS = 1;

8.1.3 database.properties

driver=com.mysql.jdbc.Driver
url=jdbc\:mysql\://localhost\:3306/block?useUnicode\=true&characterEncoding\=UTF-8
user=root
password=root

8.1.4 dao

BaseDao

package xuanzishare.dao;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class BaseDao {
    public static String driver;
    // 数据库驱动

    public static String url;
    // url

    public static String user;
    // 数据库用户名

    public static String password;
    // 数据库密码

    static {
        //静态代码块,在类加载的时候执行
        init();
    }

    public static void init() {
        String configFile = "database.properties";
        //配置文件路径
        //加载配置文件到输入流中
        InputStream is = BaseDao.class.getClassLoader().getResourceAsStream(configFile);
        Properties params = null;
        try {

            params = new Properties();
            //从输入流中读取属性列表
            params.load(is);

        } catch (IOException e) {
            e.printStackTrace();
        }
        //根据指定的获取对应的值
        driver = params.getProperty("driver");
        url = params.getProperty("url");
        user = params.getProperty("user");
        password = params.getProperty("password");
    }

    /**
     * 得到数据库连接
     *
     * @return 数据库连接
     */

    public static Connection getConn() {

        Connection conn = null;
        try {
            Class.forName(driver);
            // 注册驱动
            conn = DriverManager.getConnection(url, user, password);
            // 获得数据库连接
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return conn;
        // 返回连接
    }

    public static void closeAll(Connection conn, PreparedStatement ps, ResultSet rs) {

        /* 如果rs不空,关闭rs */
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        /* 如果pstmt不空,关闭pstmt */
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        /* 如果conn不空,关闭conn */
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }

    public int updateBuffer(String sql, Object[] objects) {
        Connection conn = null;
        PreparedStatement ps = null;
        int i = 0;
        try {
            conn = getConn();
            ps = conn.prepareStatement(sql);
            if (objects != null) {
                for (int j = 0; j < objects.length; j++) {
                    ps.setObject(j + 1, objects[j]);
                }
            }
            System.out.println("\u001B[34mupdateBuffer:" + ps + "\u001B[0m");
            i = ps.executeUpdate();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        closeAll(conn, ps, null);
        return i;
    }

    public double selectAllId(String sql, Object[] objects) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        double allId = 0;
        try {
            conn = getConn();
            ps = conn.prepareStatement(sql);
            if (objects != null) {
                for (int i = 0; i < objects.length; i++) {
                    ps.setObject(i + 1, objects[i]);
                }
            }
            System.out.println("\u001B[34mselectAllId:" + ps + "\u001B[0m");
            rs = ps.executeQuery();
            allId = 0;
            while (rs.next()) {
                allId = rs.getDouble(1);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        closeAll(conn, ps, rs);
        return allId;
    }
//    只查询数字类型 把值返还回去
}

BlocktypeDao

package com.block.dao;

import com.block.entity.Blocktype;

import java.util.ArrayList;

public interface BlocktypeDao {

    public  int updateBlocktypeInfoByBlockId(int blockId, Blocktype blocktype) throws Exception;

    //    修改 Blocktype 表字段信息 By BlockId
    public ArrayList<Blocktype> selectBlocktypeInfo() throws Exception;

    //    查询 Blocktype 表所有字段信息
    public int insertBlocktypeInfo(Blocktype blocktype)throws Exception;

    //    增加 Blocktype 表所有字段信息
    public int deleteBlocktypeInfoByBlockId(int blockId) throws Exception;
    //    删除 Blocktype 表所有字段信息 By BlockId

    public int slelectBlockIdByUserInfoOfrechargeable(double rechargeable) throws Exception;
    // 根据用户缴费数量,决定卡类型ID
}

ConsumptionDao

package com.block.dao;

import com.block.entity.Consumption;

import java.util.ArrayList;

public interface ConsumptionDao {
    public ArrayList<Consumption> selectConsumptionInfo() throws Exception;

    //    查询 Consumption 表所有字段信息
    public int updateConsumptionInfoByConsumptionId(int consumptionId, Consumption consumption) throws Exception;

    //    修改 Consumption 表字段信息 By consumptionId
    public int insertConsumptionInfo(Consumption consumption) throws Exception;

    //    增加 Consumption 表所有字段信息

    public int deleteConsumptionInfoByUserInfoId(int userInfoId) throws Exception;
    //    删除 Consumption 表所有字段信息 By userInfoId
}

ProjecttypeDao

package com.block.dao;

import com.block.entity.Projecttype;

import java.util.ArrayList;

public interface ProjecttypeDao {
    public ArrayList<Projecttype> selectProjecttypeInfo() throws Exception;
// 查询 Projecttype 表所有信息

    public double selectProjectTypePriceByProjectTypeNameAndstaffTypeId(String projectTypeName, int staffTypeId) throws Exception;

    //        通过项目类型和服务员工类型ID计算价格
    public int selectProjectTypeIdByprojectTypeName(String projectTypeName) throws Exception;
    //        通过项目名称获取项目ID


}

StaffDao

package com.block.dao;

import com.block.entity.Staff;

import java.util.ArrayList;

public interface StaffDao {

    public ArrayList<Staff> selectStaffInfo() throws Exception;

    // 查询 Staff 表所有信息
    public int selectStafftypeByStaffName(String staffName) throws Exception;

    //        通过员工名称查询出员工类型
    public int deleteStaffByStaffId(int staffId) throws Exception;

    //    删除员工, 先把这个员工 办的卡 算到店长头上,再删除员工信息
    public int insertStaffInfo(Staff staff) throws Exception;
//    增加员工信息

}

StafftypeDao

package com.block.dao;

import com.block.entity.Stafftype;

import java.util.ArrayList;

public interface StafftypeDao {

    public ArrayList<Stafftype> selectStafftypeInfo() throws Exception;
// 查询 Stafftype 表所有信息
}

UserinfoDao

package com.block.dao;

import com.block.entity.Userinfo;

import java.util.ArrayList;

public interface UserinfoDao {

    public ArrayList<Userinfo> selectUserinfoInfo() throws Exception;

    // 查询 Userinfo 表所有字段信息
    public int insertUserinfoInfo(Userinfo userinfo) throws Exception;

    //    增加 Userinfo 表所有字段信息
    public int deleteUserinfoInfoByUserInfoId(int userInfoId) throws Exception;
    //    删除 Userinfo 表所有字段信息 By userInfoId

    public int deleteUserinfoInfoByBlocktypeId(int blocktypeId) throws Exception;
//    删除 Userinfo  通过  BlocktypeId

    public int selectUserIdByUserInfoNameAndPhoneLastFour(String userInfoName, String phoneLastFour) throws Exception;
// 通过客户名称和手机号后四位查询用户ID

    public double selectDiscountByUserInfoId(int userInfoId) throws Exception;

    //    通过用户ID查询卡类型计算折扣
    public int updateBalanceByActualAmountAndUserInfoId(double actualAmount, int userInfoId) throws Exception;

    //    用户余额减少
    public int updateStaffIdByStaffId(int staffId) throws Exception;
//    修改员工 办的卡到店长手下
}

8.1.5 dao.impl

BlocktypeDaoImpl.java

package com.block.dao.impl;

import com.block.dao.BaseDao;
import com.block.dao.BlocktypeDao;
import com.block.entity.Blocktype;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;

public class BlocktypeDaoImpl extends BaseDao implements BlocktypeDao {
    @Override
    public int updateBlocktypeInfoByBlockId(int blockId, Blocktype blocktype) throws Exception {
        String sql = "update blocktype set blockType=? ,discount=? ,minRange=? ,maxRange=? where blockId=?;";
        Object[] objects = {blocktype.getBlockType(), blocktype.getDiscount(), blocktype.getMinRange(), blocktype.getMaxRange(), blockId};
        return updateBuffer(sql, objects);
    }

    @Override
    public ArrayList<Blocktype> selectBlocktypeInfo() throws Exception {
        Connection conn = getConn();
        ArrayList<Blocktype> blocktypeList = new ArrayList<>();
        String sql = "select * from blocktype";
        PreparedStatement ps = conn.prepareStatement(sql);
        System.err.println("selectBlocktypeInfo:" + ps);
        ResultSet rs = ps.executeQuery(sql);
        while (rs.next()) {
            blocktypeList.add(new Blocktype(
                    rs.getInt(1),
                    rs.getString(2),
                    rs.getDouble(3),
                    rs.getDouble(4),
                    rs.getDouble(5)
            ));
        }
        closeAll(conn, ps, rs);
        return blocktypeList;
    }

    @Override
    public int insertBlocktypeInfo(Blocktype blocktype) throws Exception {
        String sql = "insert into blocktype (blockType, discount, minRange, maxRange)\n" +
                "values (?,?,?,?)";
        Object[] objects = {blocktype.getBlockType(), blocktype.getDiscount(), blocktype.getMinRange(), blocktype.getMaxRange()};
        return updateBuffer(sql, objects);
    }

    @Override
    public int deleteBlocktypeInfoByBlockId(int blockId) throws Exception {
        String sql = "delete from blocktype where blockId = ?";
        Object[] objects = {blockId};
        return updateBuffer(sql, objects);
    }

    @Override
    public int slelectBlockIdByUserInfoOfrechargeable(double rechargeable) throws Exception {
        Connection conn = getConn();
        String sql = "SELECT blockId FROM blocktype WHERE ? Between minRange AND maxRange ";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setDouble(1, rechargeable);
        System.err.println("slelectBlockIdByUserInfoOfrechargeable:" + ps);
        ResultSet rs = ps.executeQuery();
        int blockId = 0;
        while (rs.next()) {
            blockId = rs.getInt(1);
        }
        closeAll(conn, ps, rs);
        return blockId;
    }
}

ConsumptionDaoImpl

package com.block.dao.impl;

import com.block.dao.BaseDao;
import com.block.dao.ConsumptionDao;
import com.block.entity.Consumption;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;

public class ConsumptionDaoImpl extends BaseDao implements ConsumptionDao {
    @Override
    public ArrayList<Consumption> selectConsumptionInfo() throws Exception {
        Connection conn = getConn();
        ArrayList<Consumption> consumptionList = new ArrayList<>();
        String sql = "select * from consumption";
        PreparedStatement ps = conn.prepareStatement(sql);
        System.err.println("selectConsumptionInfo:" + ps);
        ResultSet rs = ps.executeQuery(sql);
        while (rs.next()) {
            consumptionList.add(new Consumption(
                    rs.getInt(1),
                    rs.getInt(2),
                    rs.getInt(3),
                    rs.getInt(4),
                    rs.getDouble(5),
                    rs.getDouble(6),
                    rs.getString(7)
            ));
        }
        closeAll(conn, ps, rs);
        return consumptionList;
    }

    @Override
    public int updateConsumptionInfoByConsumptionId(int consumptionId, Consumption consumption) throws Exception {
        return 0;
    }

    @Override
    public int insertConsumptionInfo(Consumption consumption) throws Exception {
        String sql = "insert into consumption (userInfoId, staffTypeId, projectTypeId, consumptionAmount, actualAmount)\n" +
                "values (?, ?, ?, ?, ?)";
        Object[] objects = {consumption.getUserInfoId(), consumption.getProjectTypeId(), consumption.getProjectTypeId(), consumption.getConsumptionAmount(), consumption.getActualAmount()};
        return updateBuffer(sql, objects);
    }


    @Override
    public int deleteConsumptionInfoByUserInfoId(int userInfoId) throws Exception {
        String sql = "delete from  consumption where userInfoId = ?";
        Object[] objects = {userInfoId};
        return updateBuffer(sql, objects);
    }
}

ProjecttypeDaoImpl

package com.block.dao.impl;

import com.block.dao.BaseDao;
import com.block.dao.ProjecttypeDao;
import com.block.entity.Projecttype;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;

public class ProjecttypeDaoImpl extends BaseDao implements ProjecttypeDao {
    @Override
    public ArrayList<Projecttype> selectProjecttypeInfo() throws Exception {
        Connection conn = getConn();
        ArrayList<Projecttype> projecttypeList = new ArrayList<>();
        String sql = "select * from projecttype";
        PreparedStatement ps = conn.prepareStatement(sql);
        System.err.println("selectProjecttypeInfo:" + ps);
        ResultSet rs = ps.executeQuery(sql);
        while (rs.next()) {
            projecttypeList.add(new Projecttype(
                    rs.getInt(1),
                    rs.getString(2),
                    rs.getDouble(3),
                    rs.getInt(4)
            ));
        }
        closeAll(conn, ps, rs);
        return projecttypeList;
    }

    @Override
    public double selectProjectTypePriceByProjectTypeNameAndstaffTypeId(String projectTypeName, int staffTypeId) throws Exception {
        Connection conn = getConn();
        String sql = "SELECT projectTypePrice FROM projecttype WHERE staffTypeId=? AND projectTypeName=?";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setInt(1, staffTypeId);
        ps.setString(2, projectTypeName);
        System.err.println("selectProjectTypePriceByprojectTypeNameAndstaffTypeId:" + ps);
        ResultSet rs = ps.executeQuery();
        double projectTypePrice = 0;
        while (rs.next()) {
            projectTypePrice = rs.getDouble(1);
        }
        closeAll(conn, ps, rs);
        return projectTypePrice;
    }

    @Override
    public int selectProjectTypeIdByprojectTypeName(String projectTypeName) throws Exception {
        Connection conn = getConn();
        String sql = "SELECT MIN(projectTypeId) FROM projecttype  WHERE  projectTypeName=?";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1, projectTypeName);
        System.err.println("selectProjectTypeIdByprojectTypeName:" + ps);
        ResultSet rs = ps.executeQuery();
        int projectTypeId = 0;
        while (rs.next()) {
            projectTypeId = rs.getInt(1);
        }
        closeAll(conn, ps, rs);
        return projectTypeId;
    }


}

StaffDaoImpl

package com.block.dao.impl;

import com.block.dao.BaseDao;
import com.block.dao.StaffDao;
import com.block.entity.Staff;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;

public class StaffDaoImpl extends BaseDao implements StaffDao {
    @Override
    public ArrayList<Staff> selectStaffInfo() throws Exception {
        Connection conn = getConn();
        ArrayList<Staff> staffList = new ArrayList<>();
        String sql = "select * from staff";
        PreparedStatement ps = conn.prepareStatement(sql);
        System.err.println("selectStaffInfo:" + ps);
        ResultSet rs = ps.executeQuery(sql);
        while (rs.next()) {
            staffList.add(new Staff(
                    rs.getInt(1),
                    rs.getString(2),
                    rs.getInt(3)
            ));
        }
        closeAll(conn, ps, rs);
        return staffList;
    }

    @Override
    public int selectStafftypeByStaffName(String staffName) throws Exception {
        Connection conn = getConn();
        String sql = "SELECT staffTypeId FROM staff WHERE staffName=?";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1, staffName);
        System.err.println("selectStafftypeByStaffName:" + ps);
        ResultSet rs = ps.executeQuery();
        int Stafftype = 0;
        while (rs.next()) {
            Stafftype = rs.getInt(1);
        }
        closeAll(conn, ps, rs);
        return Stafftype;
    }

    @Override
    public int deleteStaffByStaffId(int staffId) throws Exception {
        String sql = "DELETE FROM staff WHERE staffId=?";
        Object[] objects = {staffId};
        return updateBuffer(sql, objects);
    }

    @Override
    public int insertStaffInfo(Staff staff) throws Exception {
        String sql = "insert into staff (staffName, staffTypeId)\n" +
                "values (?,?)";
        Object[] objects = {staff.getStaffName(), staff.getStaffTypeId()};
        return updateBuffer(sql, objects);
    }
}

StafftypeDaoImpl

package com.block.dao.impl;

import com.block.dao.BaseDao;
import com.block.dao.StafftypeDao;
import com.block.entity.Stafftype;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;

public class StafftypeDaoImpl extends BaseDao implements StafftypeDao {
    @Override
    public ArrayList<Stafftype> selectStafftypeInfo() throws Exception {
        Connection conn = getConn();
        ArrayList<Stafftype> stafftypeList = new ArrayList<>();
        String sql = "select * from stafftype";
        PreparedStatement ps = conn.prepareStatement(sql);
        System.err.println("selectStafftypeInfo:" + ps);
        ResultSet rs = ps.executeQuery(sql);
        while (rs.next()) {
            stafftypeList.add(new Stafftype(
                    rs.getInt(1),
                    rs.getString(2)
            ));
        }
        closeAll(conn, ps, rs);
        return stafftypeList;
    }
}

UserinfoDaoImpl

package com.block.dao.impl;

import com.block.dao.BaseDao;
import com.block.dao.UserinfoDao;
import com.block.entity.Userinfo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;

public class UserinfoDaoImpl extends BaseDao implements UserinfoDao {
    @Override
    public ArrayList<Userinfo> selectUserinfoInfo() throws Exception {
        Connection conn = getConn();
        ArrayList<Userinfo> userinfoList = new ArrayList<>();
        String sql = "select * from userinfo";
        PreparedStatement ps = conn.prepareStatement(sql);
        System.err.println("selectUserinfoInfo:" + ps);
        ResultSet rs = ps.executeQuery(sql);
        while (rs.next()) {
            userinfoList.add(new Userinfo(
                    rs.getInt(1),
                    rs.getString(2),
                    rs.getString(3),
                    rs.getDouble(4),
                    rs.getInt(5),
                    rs.getString(6),
                    rs.getDouble(7),
                    rs.getInt(8)
            ));
        }
        closeAll(conn, ps, rs);
        return userinfoList;
    }

    @Override
    public int insertUserinfoInfo(Userinfo userinfo) throws Exception {
        String sql = "insert into userinfo (userInfoName, phone, balance, staffId, rechargeable, blockId)\n" +
                "values (?, ?, ?, ?, ?, ?)";
        Object[] objects = {userinfo.getUserInfoName(), userinfo.getPhone(), userinfo.getBalance(), userinfo.getStaffId(), userinfo.getRechargeable(), userinfo.getBlockId()};
        return updateBuffer(sql, objects);
    }

    @Override
    public int deleteUserinfoInfoByUserInfoId(int userInfoId) throws Exception {
        String sql = "delete from  userinfo where userInfoId = ?";
        Object[] objects = {userInfoId};
        return updateBuffer(sql, objects);
    }

    @Override
    public int deleteUserinfoInfoByBlocktypeId(int blocktypeId) throws Exception {
        String sql = "delete from userinfo where blockId = ?";
        Object[] objects = {blocktypeId};
        return updateBuffer(sql, objects);
    }

    @Override
    public int selectUserIdByUserInfoNameAndPhoneLastFour(String userInfoName, String PhoneLastFour) throws Exception {
        Connection conn = getConn();
        String sql = "SELECT userInfoId FROM userinfo WHERE userInfoName=? AND phone LIKE '%" + PhoneLastFour + "'";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1, userInfoName);
        System.err.println("selectUserinfoByUserInfoNameAndPhoneLastFour:" + ps);
        ResultSet rs = ps.executeQuery();
        int userInfoId = 0;
        while (rs.next()) {
            userInfoId = rs.getInt(1);
        }
        closeAll(conn, ps, rs);
        return userInfoId;
    }

    @Override
    public double selectDiscountByUserInfoId(int userInfoId) throws Exception {
        Connection conn = getConn();
        String sql = "SELECT blocktype.discount FROM userinfo INNER JOIN blocktype ON userinfo.blockId=blocktype.blockId WHERE userInfoId =?";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setInt(1, userInfoId);
        System.err.println("selectDiscountByUserInfoId:" + ps);
        ResultSet rs = ps.executeQuery();
        double discount = 0;
        while (rs.next()) {
            discount = rs.getDouble(1);
        }
        closeAll(conn, ps, rs);
        return discount;
    }

    @Override
    public int updateBalanceByActualAmountAndUserInfoId(double actualAmount, int userInfoId) throws Exception {
        String sql = "UPDATE userinfo SET balance=balance-? WHERE userInfoId=?";
        Object[] objects = {actualAmount, userInfoId};
        return updateBuffer(sql, objects);
    }

    @Override
    public int updateStaffIdByStaffId(int staffId) throws Exception {
        String sql = "UPDATE userinfo SET staffId=1 WHERE staffId=?";
        Object[] objects = {staffId};
        return updateBuffer(sql, objects);
    }


}

8.1.6 entity

Blocktype

package com.block.entity;


public class Blocktype {

    private int blockId;
    private String blockType;
    private double discount;
    private double minRange;
    private double maxRange;

    public Blocktype(int blockId, String blockType, double discount, double minRange, double maxRange) {
        this.blockId = blockId;
        this.blockType = blockType;
        this.discount = discount;
        this.minRange = minRange;
        this.maxRange = maxRange;
    }

    public Blocktype(String blockType, double discount, double minRange, double maxRange) {
        this.blockType = blockType;
        this.discount = discount;
        this.minRange = minRange;
        this.maxRange = maxRange;
    }

    public Blocktype() {
    }

    public int getBlockId() {
        return blockId;
    }

    public void setBlockId(int blockId) {
        this.blockId = blockId;
    }

    public String getBlockType() {
        return blockType;
    }

    public void setBlockType(String blockType) {
        this.blockType = blockType;
    }

    public double getDiscount() {
        return discount;
    }

    public void setDiscount(double discount) {
        this.discount = discount;
    }

    public double getMinRange() {
        return minRange;
    }

    public void setMinRange(double minRange) {
        this.minRange = minRange;
    }

    public double getMaxRange() {
        return maxRange;
    }

    public void setMaxRange(double maxRange) {
        this.maxRange = maxRange;
    }

    @Override
    public String toString() {
        return "Blocktype{" +
                "blockId=" + blockId +
                ", blockType='" + blockType + '\'' +
                ", discount=" + discount +
                ", minRange=" + minRange +
                ", maxRange=" + maxRange +
                '}';
    }
}
//CREATE TABLE `block`.`blocktype`  (
//  `blockId` int(11) NOT NULL AUTO_INCREMENT COMMENT '卡类型id',
//  `blockType` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '卡类型',
//  `discount` double(2, 2) NOT NULL COMMENT '享受折扣',
//  `minRange` double(8, 2) NOT NULL COMMENT '最小范围',
//  `maxRange` double(8, 2) NOT NULL COMMENT '最大范围',
//  PRIMARY KEY (`blockId`) USING BTREE
//) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = DYNAMIC;

Consumption

package com.block.entity;


public class Consumption {

    private int consumptionId;
    private int userInfoId;
    private int staffTypeId;
    private int projectTypeId;
    private double consumptionAmount;
    private double actualAmount;
    private String recordTime;

    public Consumption(int consumptionId, int userInfoId, int staffTypeId, int projectTypeId, double consumptionAmount, double actualAmount, String recordTime) {
        this.consumptionId = consumptionId;
        this.userInfoId = userInfoId;
        this.staffTypeId = staffTypeId;
        this.projectTypeId = projectTypeId;
        this.consumptionAmount = consumptionAmount;
        this.actualAmount = actualAmount;
        this.recordTime = recordTime;
    }

    public Consumption(int userInfoId, int staffTypeId, int projectTypeId, double consumptionAmount, double actualAmount) {
        this.userInfoId = userInfoId;
        this.staffTypeId = staffTypeId;
        this.projectTypeId = projectTypeId;
        this.consumptionAmount = consumptionAmount;
        this.actualAmount = actualAmount;
    }

    public Consumption() {
    }

    public int getConsumptionId() {
        return consumptionId;
    }

    public void setConsumptionId(int consumptionId) {
        this.consumptionId = consumptionId;
    }

    public int getUserInfoId() {
        return userInfoId;
    }

    public void setUserInfoId(int userInfoId) {
        this.userInfoId = userInfoId;
    }

    public int getStaffTypeId() {
        return staffTypeId;
    }

    public void setStaffTypeId(int staffTypeId) {
        this.staffTypeId = staffTypeId;
    }

    public int getProjectTypeId() {
        return projectTypeId;
    }

    public void setProjectTypeId(int projectTypeId) {
        this.projectTypeId = projectTypeId;
    }

    public double getConsumptionAmount() {
        return consumptionAmount;
    }

    public void setConsumptionAmount(double consumptionAmount) {
        this.consumptionAmount = consumptionAmount;
    }

    public double getActualAmount() {
        return actualAmount;
    }

    public void setActualAmount(double actualAmount) {
        this.actualAmount = actualAmount;
    }

    public String getRecordTime() {
        return recordTime;
    }

    public void setRecordTime(String recordTime) {
        this.recordTime = recordTime;
    }

    @Override
    public String toString() {
        return "Consumption{" +
                "consumptionId=" + consumptionId +
                ", userInfoId=" + userInfoId +
                ", staffTypeId=" + staffTypeId +
                ", projectTypeId=" + projectTypeId +
                ", consumptionAmount=" + consumptionAmount +
                ", actualAmount=" + actualAmount +
                ", recordTime='" + recordTime + '\'' +
                '}';
    }

}
// CREATE TABLE `block`.`consumption`  (
//  `consumptionId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
//  `userInfoId` int(11) NOT NULL COMMENT '用户id',
//  `staffTypeId` int(11) NOT NULL COMMENT '员工类型ID',
//  `projectTypeId` int(11) NOT NULL COMMENT '项目类型编号',
//  `consumptionAmount` double(8, 2) NOT NULL COMMENT '消费金额,格局消费的项目和员工类型来决定',
//  `actualAmount` double(8, 2) NOT NULL COMMENT '实际消费金额,根据用户卡类型打折',
//  `recordTime` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '消费时间',
//  PRIMARY KEY (`consumptionId`) USING BTREE,
//  INDEX `FK_projectTypeId`(`staffTypeId`) USING BTREE,
//  INDEX `FK_staffId_consumption`(`projectTypeId`) USING BTREE,
//  INDEX `FK_userInfoId`(`userInfoId`) USING BTREE,
//  CONSTRAINT `FK_projectTypeId` FOREIGN KEY (`projectTypeId`) REFERENCES `block`.`projecttype` (`projectTypeId`) ON DELETE RESTRICT ON UPDATE RESTRICT,
//  CONSTRAINT `FK_staffTypeId_1` FOREIGN KEY (`staffTypeId`) REFERENCES `block`.`stafftype` (`staffTypeId`) ON DELETE RESTRICT ON UPDATE RESTRICT,
//  CONSTRAINT `FK_userInfoId` FOREIGN KEY (`userInfoId`) REFERENCES `block`.`userinfo` (`userInfoId`) ON DELETE RESTRICT ON UPDATE RESTRICT
//) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = DYNAMIC;

Projecttype

package com.block.entity;


public class Projecttype {

    private int projectTypeId;
    private String projectTypeName;
    private double projectTypePrice;
    private int staffTypeId;


    public Projecttype(int projectTypeId, String projectTypeName, double projectTypePrice, int staffTypeId) {
        this.projectTypeId = projectTypeId;
        this.projectTypeName = projectTypeName;
        this.projectTypePrice = projectTypePrice;
        this.staffTypeId = staffTypeId;
    }

    public int getProjectTypeId() {
        return projectTypeId;
    }

    public void setProjectTypeId(int projectTypeId) {
        this.projectTypeId = projectTypeId;
    }

    public String getProjectTypeName() {
        return projectTypeName;
    }

    public void setProjectTypeName(String projectTypeName) {
        this.projectTypeName = projectTypeName;
    }

    public double getProjectTypePrice() {
        return projectTypePrice;
    }

    public void setProjectTypePrice(double projectTypePrice) {
        this.projectTypePrice = projectTypePrice;
    }

    public int getStaffTypeId() {
        return staffTypeId;
    }

    public void setStaffTypeId(int staffTypeId) {
        this.staffTypeId = staffTypeId;
    }

    @Override
    public String toString() {
        return "Projecttype{" +
                "projectTypeId=" + projectTypeId +
                ", projectTypeName='" + projectTypeName + '\'' +
                ", projectTypePrice=" + projectTypePrice +
                ", staffTypeId=" + staffTypeId +
                '}';
    }
}
// CREATE TABLE `block`.`projecttype`  (
//  `projectTypeId` int(11) NOT NULL AUTO_INCREMENT COMMENT '项目类型id',
//  `projectTypeName` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '项目名称',
//  `projectTypePrice` double(8, 2) NOT NULL COMMENT '项目价格',
//  `staffTypeId` int(11) NOT NULL COMMENT '员工类型id',
//  PRIMARY KEY (`projectTypeId`) USING BTREE,
//  INDEX `FK_staffTypeId2`(`staffTypeId`) USING BTREE,
//  CONSTRAINT `FK_staffTypeId2` FOREIGN KEY (`staffTypeId`) REFERENCES `block`.`stafftype` (`staffTypeId`) ON DELETE RESTRICT ON UPDATE RESTRICT
//) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = DYNAMIC;

Staff

package com.block.entity;


public class Staff {

    private int staffId;
    private String staffName;
    private int staffTypeId;


    public Staff(int staffId, String staffName, int staffTypeId) {
        this.staffId = staffId;
        this.staffName = staffName;
        this.staffTypeId = staffTypeId;
    }
    public Staff(String staffName, int staffTypeId) {
        this.staffName = staffName;
        this.staffTypeId = staffTypeId;
    }

    public Staff() {
    }



    public int getStaffId() {
        return staffId;
    }

    public void setStaffId(int staffId) {
        this.staffId = staffId;
    }

    public String getStaffName() {
        return staffName;
    }

    public void setStaffName(String staffName) {
        this.staffName = staffName;
    }

    public int getStaffTypeId() {
        return staffTypeId;
    }

    public void setStaffTypeId(int staffTypeId) {
        this.staffTypeId = staffTypeId;
    }

    @Override
    public String toString() {
        return "Staff{" +
                "staffId=" + staffId +
                ", staffName='" + staffName + '\'' +
                ", staffTypeId=" + staffTypeId +
                '}';
    }
}
// CREATE TABLE `block`.`staff`  (
//  `staffId` int(11) NOT NULL AUTO_INCREMENT COMMENT '员工id',
//  `staffName` varchar(10) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '员工姓名',
//  `staffTypeId` int(11) NOT NULL COMMENT '员工类型',
//  PRIMARY KEY (`staffId`) USING BTREE,
//  INDEX `FK_staffTypeId`(`staffTypeId`) USING BTREE,
//  CONSTRAINT `FK_staffTypeId` FOREIGN KEY (`staffTypeId`) REFERENCES `block`.`stafftype` (`staffTypeId`) ON DELETE RESTRICT ON UPDATE RESTRICT
//) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = DYNAMIC;

Stafftype

package com.block.entity;

public class Stafftype {

    private int staffTypeId;
    private String staffTypeName;

    public Stafftype(int staffTypeId, String staffTypeName) {
        this.staffTypeId = staffTypeId;
        this.staffTypeName = staffTypeName;
    }

    public Stafftype() {
    }

    public int getStaffTypeId() {
        return staffTypeId;
    }

    public void setStaffTypeId(int staffTypeId) {
        this.staffTypeId = staffTypeId;
    }

    public String getStaffTypeName() {
        return staffTypeName;
    }

    public void setStaffTypeName(String staffTypeName) {
        this.staffTypeName = staffTypeName;
    }

    @Override
    public String toString() {
        return "Stafftype{" +
                "staffTypeId=" + staffTypeId +
                ", staffTypeName='" + staffTypeName + '\'' +
                '}';
    }
}
// CREATE TABLE `block`.`staff`  (
//  `staffId` int(11) NOT NULL AUTO_INCREMENT COMMENT '员工id',
//  `staffName` varchar(10) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '员工姓名',
//  `staffTypeId` int(11) NOT NULL COMMENT '员工类型',
//  PRIMARY KEY (`staffId`) USING BTREE,
//  INDEX `FK_staffTypeId`(`staffTypeId`) USING BTREE,
//  CONSTRAINT `FK_staffTypeId` FOREIGN KEY (`staffTypeId`) REFERENCES `block`.`stafftype` (`staffTypeId`) ON DELETE RESTRICT ON UPDATE RESTRICT
//) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = DYNAMIC;

Userinfo

package com.block.entity;


public class Userinfo {

    private int userInfoId;
    private String userInfoName;
    private String phone;
    private double balance;
    private int staffId;
    private String openBlock;
    private double rechargeable;
    private int blockId;

    public Userinfo(int userInfoId, String userInfoName, String phone, double balance, int staffId, String openBlock, double rechargeable, int blockId) {
        this.userInfoId = userInfoId;
        this.userInfoName = userInfoName;
        this.phone = phone;
        this.balance = balance;
        this.staffId = staffId;
        this.openBlock = openBlock;
        this.rechargeable = rechargeable;
        this.blockId = blockId;
    }


    public Userinfo(String userInfoName, String phone, double balance, int staffId, double rechargeable) {
        this.userInfoName = userInfoName;
        this.phone = phone;
        this.balance = balance;
        this.staffId = staffId;
        this.rechargeable = rechargeable;
    }

    public Userinfo(String userInfoName, String phone, double balance, int staffId, double rechargeable, int blockId) {
        this.userInfoName = userInfoName;
        this.phone = phone;
        this.balance = balance;
        this.staffId = staffId;
        this.rechargeable = rechargeable;
        this.blockId = blockId;
    }

    public int getUserInfoId() {
        return userInfoId;
    }

    public void setUserInfoId(int userInfoId) {
        this.userInfoId = userInfoId;
    }

    public String getUserInfoName() {
        return userInfoName;
    }

    public void setUserInfoName(String userInfoName) {
        this.userInfoName = userInfoName;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public int getStaffId() {
        return staffId;
    }

    public void setStaffId(int staffId) {
        this.staffId = staffId;
    }

    public String getOpenBlock() {
        return openBlock;
    }

    public void setOpenBlock(String openBlock) {
        this.openBlock = openBlock;
    }

    public double getRechargeable() {
        return rechargeable;
    }

    public void setRechargeable(double rechargeable) {
        this.rechargeable = rechargeable;
    }

    public int getBlockId() {
        return blockId;
    }

    public void setBlockId(int blockId) {
        this.blockId = blockId;
    }

    @Override
    public String toString() {
        return "Userinfo{" +
                "userInfoId=" + userInfoId +
                ", userInfoName='" + userInfoName + '\'' +
                ", phone=" + phone +
                ", balance=" + balance +
                ", staffId=" + staffId +
                ", openBlock='" + openBlock + '\'' +
                ", rechargeable=" + rechargeable +
                ", blockId=" + blockId +
                '}';
    }
}
// CREATE TABLE `block`.`userinfo`  (
//  `userInfoId` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户编号 ',
//  `userInfoName` varchar(10) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '用户姓名',
//  `phone` int(11) NOT NULL COMMENT '电话号码',
//  `balance` double(8, 2) NOT NULL DEFAULT 0.00 COMMENT '余额',
//  `staffId` int(11) NOT NULL COMMENT '员工id',
//  `openBlock` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '开卡时间',
//  `rechargeable` double(8, 2) NOT NULL COMMENT '第一次充值金额',
//  `blockId` int(11) NOT NULL COMMENT '卡类型id',
//  PRIMARY KEY (`userInfoId`) USING BTREE,
//  INDEX `FK_staffid`(`staffId`) USING BTREE,
//  INDEX `FK_calorie`(`blockId`) USING BTREE,
//  CONSTRAINT `FK_blockId` FOREIGN KEY (`blockId`) REFERENCES `block`.`blocktype` (`blockId`) ON DELETE RESTRICT ON UPDATE RESTRICT,
//  CONSTRAINT `FK_staffid` FOREIGN KEY (`staffId`) REFERENCES `block`.`staff` (`staffId`) ON DELETE RESTRICT ON UPDATE RESTRICT
//) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = DYNAMIC;
//

8.1.7 service

BlocktypeService

package com.block.service;

public interface BlocktypeService {

    public void deleteBlocktypeInfoByBlockId(int blockId) throws Exception;
//    删除 卡 表的外键 然后 删除字段
}

ConsumptionService

package com.block.service;

public interface ConsumptionService {
    public void insertConsumptionInfo() throws Exception;

    //    增加 Consumption 表所有字段信息
}

StaffService

package com.block.service;

public interface StaffService {

    public void deleteStaffByStaffId(int staffId) throws Exception;
//    删除员工信息 先把员工办的卡转移到 店长下 在删除信息
}

UserinfoService

package com.block.service;

import com.block.dao.*;
import com.block.dao.impl.*;

public interface UserinfoService {


    public void insertUserinfoInfo() throws Exception;

    //    增加 UserinfoDao 表所有字段信息
    public void deleteUserinfoInfoByUserInfoId(int userInfoId) throws Exception;
    //    删除 UserinfoDao 表所有字段信息 by userInfoId
}

8.1.8 service.impl

BlocktypeServiceImpl

package com.block.service.impl;

import com.block.dao.*;
import com.block.dao.impl.*;
import com.block.service.BlocktypeService;

import java.util.Scanner;

public class BlocktypeServiceImpl implements BlocktypeService {
    Scanner input = new Scanner(System.in);
    BlocktypeDao blocktypeDao = new BlocktypeDaoImpl();
    ConsumptionDao consumptionDao = new ConsumptionDaoImpl();
    ProjecttypeDao projecttypeDao = new ProjecttypeDaoImpl();
    StaffDao staffDao = new StaffDaoImpl();
    StafftypeDao stafftypeDao = new StafftypeDaoImpl();
    UserinfoDao userinfoDao = new UserinfoDaoImpl();

    @Override
    public void deleteBlocktypeInfoByBlockId(int blockId) throws Exception {
        userinfoDao.deleteUserinfoInfoByBlocktypeId(blockId);
        int i = blocktypeDao.deleteBlocktypeInfoByBlockId(blockId);
        System.out.println("deleteBlocktypeInfoByBlockId:" + i + "行受影响");
    }
}

ConsumptionServiceImpl

package com.block.service.impl;

import com.block.dao.*;
import com.block.dao.impl.*;
import com.block.entity.Consumption;
import com.block.service.ConsumptionService;

import java.util.Scanner;

public class ConsumptionServiceImpl implements ConsumptionService {
    Scanner input = new Scanner(System.in);
    BlocktypeDao blocktypeDao = new BlocktypeDaoImpl();
    ConsumptionDao consumptionDao = new ConsumptionDaoImpl();
    ProjecttypeDao projecttypeDao = new ProjecttypeDaoImpl();
    StaffDao staffDao = new StaffDaoImpl();
    StafftypeDao stafftypeDao = new StafftypeDaoImpl();
    UserinfoDao userinfoDao = new UserinfoDaoImpl();

    @Override
    public void insertConsumptionInfo() throws Exception {
        System.out.print("请输入用户姓名:");
        String userName = input.next();
        System.out.print("请输入用户手机号后四位:");
        String phoneLastfour = input.next();
//        SELECT userInfoId FROM userinfo WHERE userInfoName='玄子' AND phone LIKE '%6518'
        int userId = userinfoDao.selectUserIdByUserInfoNameAndPhoneLastFour(userName, phoneLastfour);
        System.out.print("请输入消费项目名称:");
        String projectTypeName = input.next();
        int projectTypeId = projecttypeDao.selectProjectTypeIdByprojectTypeName(projectTypeName);
//        SELECT MIN(projectTypeId) FROM projecttype  WHERE  projectTypeName='剪头'
//        通过项目名称获取项目ID
        System.out.print("请输入服务员工名称:");
        String staffName = input.next();
        int stafftypeId = staffDao.selectStafftypeByStaffName(staffName);
//        SELECT staffTypeId FROM staff WHERE staffName='于昂'
//        通过员工名称查询出员工类型
        double projectTypePrice = projecttypeDao.selectProjectTypePriceByProjectTypeNameAndstaffTypeId(projectTypeName, stafftypeId);
//        SELECT projectTypePrice FROM projecttype WHERE staffTypeId=2 AND projectTypeName='剪头'
//        通过项目类型和服务员工类型计算价格
        double discount = userinfoDao.selectDiscountByUserInfoId(userId);
//        SELECT blocktype.discount FROM userinfo INNER JOIN blocktype ON userinfo.blockId=blocktype.blockId WHERE userInfoId =1;
//        通过用户ID查询卡类型计算折扣
        double actualAmount = projectTypePrice * discount;
        userinfoDao.updateBalanceByActualAmountAndUserInfoId(actualAmount, userId);
//        用户余额减少
        int i = consumptionDao.insertConsumptionInfo(new Consumption(userId, stafftypeId, projectTypeId, projectTypePrice, actualAmount));
        System.out.println("insertConsumptionInfo:" + i + "行受影响");
    }
}

StaffServiceImpl

package com.block.service.impl;

import com.block.dao.*;
import com.block.dao.impl.*;
import com.block.service.StaffService;

import java.util.Scanner;

public class StaffServiceImpl implements StaffService {
    Scanner input = new Scanner(System.in);
    BlocktypeDao blocktypeDao = new BlocktypeDaoImpl();
    ConsumptionDao consumptionDao = new ConsumptionDaoImpl();
    ProjecttypeDao projecttypeDao = new ProjecttypeDaoImpl();
    StaffDao staffDao = new StaffDaoImpl();
    StafftypeDao stafftypeDao = new StafftypeDaoImpl();
    UserinfoDao userinfoDao = new UserinfoDaoImpl();

    @Override
    public void deleteStaffByStaffId(int staffId) throws Exception {
        userinfoDao.updateStaffIdByStaffId(staffId);
        int i = staffDao.deleteStaffByStaffId(staffId);
        System.out.println("deleteStaffByStaffId:" + i + "行受影响");
    }
}

UserinfoServiceImpl

package com.block.service.impl;

import com.block.dao.*;
import com.block.dao.impl.*;
import com.block.entity.Userinfo;
import com.block.service.UserinfoService;

import java.util.Scanner;

public class UserinfoServiceImpl implements UserinfoService {
    Scanner input = new Scanner(System.in);
    BlocktypeDao blocktypeDao = new BlocktypeDaoImpl();
    ConsumptionDao consumptionDao = new ConsumptionDaoImpl();
    ProjecttypeDao projecttypeDao = new ProjecttypeDaoImpl();
    StaffDao staffDao = new StaffDaoImpl();
    StafftypeDao stafftypeDao = new StafftypeDaoImpl();
    UserinfoDao userinfoDao = new UserinfoDaoImpl();

    @Override
    public void insertUserinfoInfo() throws Exception {
        System.out.print("请输入用户名称:");
        String userName = input.next();
        System.out.print("请输入用户电话号码:");
        String userPhone = input.next();
        System.out.print("请输入办卡员工ID:");
        int staffId = input.nextInt();
        System.out.print("请输入用户第一次缴费金额:");
        double rechargeabie = input.nextDouble();
        int i = userinfoDao.insertUserinfoInfo(new Userinfo(userName, userPhone, rechargeabie, staffId, rechargeabie, blocktypeDao.slelectBlockIdByUserInfoOfrechargeable(rechargeabie)));
        System.out.println("insertUserinfoInfo:" + i + "行受影响");
    }
    @Override
    public void deleteUserinfoInfoByUserInfoId(int userInfoId) throws Exception {
        consumptionDao.deleteConsumptionInfoByUserInfoId(userInfoId);
        int i = userinfoDao.deleteUserinfoInfoByUserInfoId(userInfoId);
        System.out.println("insertUserinfoInfo:" + i + "行受影响");
    }
}

写到这里 文章是 四万多字 复制代码 Typora 都是卡的。后面的项目就只提供sql文件了

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

8.2 学生管理项目

/*
 Navicat Premium Data Transfer

 Source Server         : XuanZi
 Source Server Type    : MySQL
 Source Server Version : 50740 (5.7.40)
 Source Host           : localhost:3306
 Source Schema         : myschool

 Target Server Type    : MySQL
 Target Server Version : 50740 (5.7.40)
 File Encoding         : 65001

 Date: 31/03/2023 11:54:12
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for grade
-- ----------------------------
DROP TABLE IF EXISTS `grade`;
CREATE TABLE `grade`  (
  `gradeID` int(4) NOT NULL AUTO_INCREMENT COMMENT '年级编号',
  `gradeName` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '年级名称',
  PRIMARY KEY (`gradeID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '年级表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of grade
-- ----------------------------
INSERT INTO `grade` VALUES (1, 'Y1');
INSERT INTO `grade` VALUES (2, 'Y2');
INSERT INTO `grade` VALUES (3, 'Y3');

-- ----------------------------
-- Table structure for result
-- ----------------------------
DROP TABLE IF EXISTS `result`;
CREATE TABLE `result`  (
  `studentNo` int(4) NOT NULL COMMENT '学号',
  `subjectNo` int(4) NOT NULL COMMENT '课程编号',
  `examDate` datetime NULL DEFAULT NULL COMMENT '考试日期',
  `studentResult` int(4) NOT NULL COMMENT '考试成绩'
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '成绩表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of result
-- ----------------------------
INSERT INTO `result` VALUES (10000, 1, '2019-02-17 00:00:00', 71);
INSERT INTO `result` VALUES (10002, 1, '2019-02-17 00:00:00', 83);
INSERT INTO `result` VALUES (10003, 1, '2019-02-17 00:00:00', 60);
INSERT INTO `result` VALUES (10000, 3, '2019-02-17 00:00:00', 60);
INSERT INTO `result` VALUES (10000, 1, '2019-02-17 00:00:00', 71);
INSERT INTO `result` VALUES (10002, 1, '2019-02-17 00:00:00', 83);
INSERT INTO `result` VALUES (10003, 1, '2019-02-17 00:00:00', 60);
INSERT INTO `result` VALUES (10004, 3, '2019-02-17 00:00:00', 60);

-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student`  (
  `studentNo` int(4) NOT NULL COMMENT '学号',
  `loginPwd` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
  `studentName` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '学生姓名',
  `sex` char(2) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '男' COMMENT '性别',
  `gradeId` int(4) UNSIGNED NULL DEFAULT NULL COMMENT '年级编号',
  `phone` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '联系电话',
  `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '地址不详' COMMENT '地址',
  `bornDate` datetime NULL DEFAULT NULL COMMENT '出生时间',
  `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮件账号',
  `identityCard` varchar(18) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '身份证号码',
  PRIMARY KEY (`studentNo`) USING BTREE,
  UNIQUE INDEX `identityCard`(`identityCard`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '学生表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES (10000, '012345', '郭靖', '男', 1, '13645667783', '天津市河西区', '1990-09-08 00:00:00', NULL, NULL);
INSERT INTO `student` VALUES (10002, '234567', '李斯文', '男', 1, '13645556793', '河南洛阳', '1993-07-23 00:00:00', NULL, NULL);
INSERT INTO `student` VALUES (10003, '345678', '张萍', '女', 1, '13642345112', '地址不详', '1995-06-10 00:00:00', NULL, NULL);
INSERT INTO `student` VALUES (10004, '456789', '韩秋洁', '女', 1, '13812344566', '北京市海淀区', '1995-07-15 00:00:00', NULL, NULL);
INSERT INTO `student` VALUES (10008, '890123', '何睛睛', '女', 1, '13053445221', '广州市天河区', '1997-07-23 00:00:00', NULL, NULL);

-- ----------------------------
-- Table structure for subject
-- ----------------------------
DROP TABLE IF EXISTS `subject`;
CREATE TABLE `subject`  (
  `subjectNo` int(4) NOT NULL AUTO_INCREMENT COMMENT '课程编号',
  `subjectName` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '课程名称',
  `classHour` int(4) NULL DEFAULT NULL COMMENT '学时',
  `gradeID` int(4) NULL DEFAULT NULL COMMENT '年级编号',
  PRIMARY KEY (`subjectNo`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '课程表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of subject
-- ----------------------------
INSERT INTO `subject` VALUES (1, 'Logic Java', 220, 1);
INSERT INTO `subject` VALUES (2, 'HTML', 160, 1);
INSERT INTO `subject` VALUES (3, 'Java OOP', 230, 2);

-- ----------------------------
-- Procedure structure for Proc_select_student
-- ----------------------------
DROP PROCEDURE IF EXISTS `Proc_select_student`;
delimiter ;;
CREATE PROCEDURE `Proc_select_student`(OUT count int)
BEGIN
# 查询所有学生信息
 SELECT Count(*) INTO count FROM student;
END
;;
delimiter ;

-- ----------------------------
-- Procedure structure for Proc_studentName
-- ----------------------------
DROP PROCEDURE IF EXISTS `Proc_studentName`;
delimiter ;;
CREATE PROCEDURE `Proc_studentName`(IN student_Name VARCHAR(50) ,OUT last_Result DATETIME)
BEGIN

DECLARE student_No INT;
SELECT studentNo INTO student_No  FROM student  WHERE studentName=student_Name;
SELECT MAX(examDate) INTO last_Result FROM result  WHERE studentNo=student_No ;

END
;;
delimiter ;

SET FOREIGN_KEY_CHECKS = 1;

8.3 淘宝购物车项目

/*
 Navicat Premium Data Transfer

 Source Server         : XuanZi
 Source Server Type    : MySQL
 Source Server Version : 50740 (5.7.40)
 Source Host           : localhost:3306
 Source Schema         : taobao

 Target Server Type    : MySQL
 Target Server Version : 50740 (5.7.40)
 File Encoding         : 65001

 Date: 28/03/2023 11:33:58
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for product
-- ----------------------------
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product`  (
  `productId` int(4) NOT NULL AUTO_INCREMENT,
  `productName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `productPrice` double(10, 2) NULL DEFAULT NULL,
  PRIMARY KEY (`productId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of product
-- ----------------------------
INSERT INTO `product` VALUES (1, '农夫山泉', 2.00);
INSERT INTO `product` VALUES (2, '可口可乐', 3.00);
INSERT INTO `product` VALUES (3, '百事可乐', 3.00);
INSERT INTO `product` VALUES (4, '辣条', 2.00);

-- ----------------------------
-- Table structure for shoppingcar
-- ----------------------------
DROP TABLE IF EXISTS `shoppingcar`;
CREATE TABLE `shoppingcar`  (
  `shoppingCarId` int(4) NOT NULL AUTO_INCREMENT,
  `userId` int(4) NOT NULL,
  `productId` int(4) NOT NULL,
  `productCount` int(4) NOT NULL,
  `productTotalCost` double(10, 2) NOT NULL,
  `addTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`shoppingCarId`) USING BTREE,
  INDEX `userId`(`userId`) USING BTREE,
  INDEX `productId`(`productId`) USING BTREE,
  CONSTRAINT `shoppingcar_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `userinfo` (`userId`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `shoppingcar_ibfk_2` FOREIGN KEY (`productId`) REFERENCES `product` (`productId`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of shoppingcar
-- ----------------------------
INSERT INTO `shoppingcar` VALUES (1, 1, 1, 1, 20.00, '2023-03-28 10:40:10');
INSERT INTO `shoppingcar` VALUES (2, 1, 2, 2, 30.00, '2023-03-28 10:40:18');
INSERT INTO `shoppingcar` VALUES (3, 2, 3, 2, 22.00, '2023-03-28 10:40:24');

-- ----------------------------
-- Table structure for userinfo
-- ----------------------------
DROP TABLE IF EXISTS `userinfo`;
CREATE TABLE `userinfo`  (
  `userId` int(4) NOT NULL AUTO_INCREMENT,
  `userName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `userPwd` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`userId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of userinfo
-- ----------------------------
INSERT INTO `userinfo` VALUES (1, '玄子', '123456');
INSERT INTO `userinfo` VALUES (2, '玉玉诏', '123456');

SET FOREIGN_KEY_CHECKS = 1;

8.4 医院挂号项目

/*
 Navicat Premium Data Transfer

 Source Server         : XuanZi
 Source Server Type    : MySQL
 Source Server Version : 50740 (5.7.40)
 Source Host           : localhost:3306
 Source Schema         : registration

 Target Server Type    : MySQL
 Target Server Version : 50740 (5.7.40)
 File Encoding         : 65001

 Date: 27/03/2023 15:38:03
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for doctor
-- ----------------------------
DROP TABLE IF EXISTS `doctor`;
CREATE TABLE `doctor`  (
  `doctorId` int(4) NOT NULL AUTO_INCREMENT COMMENT '医生编号',
  `doctorName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '医生名称',
  `technicalOfficesId` int(4) NOT NULL COMMENT '科室编号',
  `doctorHouseId` int(4) NOT NULL COMMENT '诊室编号',
  `doctorLevelId` int(4) NOT NULL COMMENT '医生等级ID',
  `maxVisits` int(4) NOT NULL COMMENT '最大接诊人数',
  PRIMARY KEY (`doctorId`) USING BTREE,
  INDEX `PK_01`(`technicalOfficesId`) USING BTREE,
  INDEX `PK_02`(`doctorHouseId`) USING BTREE,
  INDEX `PK_05`(`doctorLevelId`) USING BTREE,
  CONSTRAINT `PK_01` FOREIGN KEY (`technicalOfficesId`) REFERENCES `technicaloffices` (`technicalOfficesId`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `PK_02` FOREIGN KEY (`doctorHouseId`) REFERENCES `doctorhouse` (`doctorHouseId`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `PK_05` FOREIGN KEY (`doctorLevelId`) REFERENCES `doctorlevel` (`doctorLevelId`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of doctor
-- ----------------------------
INSERT INTO `doctor` VALUES (1, '吕永成', 1, 1, 1, 3);
INSERT INTO `doctor` VALUES (2, '韩松', 2, 2, 2, 7);
INSERT INTO `doctor` VALUES (3, '梁瑞康', 3, 3, 3, 9);

-- ----------------------------
-- Table structure for doctorhouse
-- ----------------------------
DROP TABLE IF EXISTS `doctorhouse`;
CREATE TABLE `doctorhouse`  (
  `doctorHouseId` int(4) NOT NULL AUTO_INCREMENT COMMENT '诊室编号',
  `doctorHouseName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '诊室名称',
  `technicalOfficesId` int(4) NOT NULL COMMENT '所属科室编号',
  PRIMARY KEY (`doctorHouseId`) USING BTREE,
  INDEX `PK_03`(`technicalOfficesId`) USING BTREE,
  CONSTRAINT `PK_03` FOREIGN KEY (`technicalOfficesId`) REFERENCES `technicaloffices` (`technicalOfficesId`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of doctorhouse
-- ----------------------------
INSERT INTO `doctorhouse` VALUES (1, '血常规', 1);
INSERT INTO `doctorhouse` VALUES (2, '尿常规', 1);
INSERT INTO `doctorhouse` VALUES (3, '血脂、血糖检查', 2);
INSERT INTO `doctorhouse` VALUES (4, '凝血五项', 2);
INSERT INTO `doctorhouse` VALUES (5, '肺炎支、衣原体(快速)', 3);
INSERT INTO `doctorhouse` VALUES (6, 'CT', 3);
INSERT INTO `doctorhouse` VALUES (7, '胃镜', 3);
INSERT INTO `doctorhouse` VALUES (8, '支气管纤维镜', 3);

-- ----------------------------
-- Table structure for doctorlevel
-- ----------------------------
DROP TABLE IF EXISTS `doctorlevel`;
CREATE TABLE `doctorlevel`  (
  `doctorLevelId` int(4) NOT NULL AUTO_INCREMENT,
  `doctorLevelName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`doctorLevelId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of doctorlevel
-- ----------------------------
INSERT INTO `doctorlevel` VALUES (1, '专家');
INSERT INTO `doctorlevel` VALUES (2, '医生');
INSERT INTO `doctorlevel` VALUES (3, '教授');
INSERT INTO `doctorlevel` VALUES (4, '主任');

-- ----------------------------
-- Table structure for patient
-- ----------------------------
DROP TABLE IF EXISTS `patient`;
CREATE TABLE `patient`  (
  `patientId` int(4) NOT NULL AUTO_INCREMENT COMMENT '患者ID',
  `doctorId` int(4) NOT NULL COMMENT '医生ID',
  `patientName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '患者名称',
  `patientState` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '待' COMMENT '患者状态',
  `registrationTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '挂号时间',
  `diagnosticTime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '就诊时间',
  PRIMARY KEY (`patientId`) USING BTREE,
  INDEX `PK_04`(`doctorId`) USING BTREE,
  CONSTRAINT `PK_04` FOREIGN KEY (`doctorId`) REFERENCES `doctor` (`doctorId`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of patient
-- ----------------------------
INSERT INTO `patient` VALUES (1, 1, '玄子', '待', '2023-03-25 10:16:28', '2023-03-25 21:03:49');
INSERT INTO `patient` VALUES (2, 1, '玉玉诏', '待', '2023-03-25 10:16:39', '2023-03-25 21:09:29');
INSERT INTO `patient` VALUES (3, 2, '清玄子', '待', '2023-03-25 10:17:19', '2023-03-25 10:17:19');
INSERT INTO `patient` VALUES (4, 2, '22', '待', '2023-03-25 12:19:00', '2023-03-25 21:16:37');
INSERT INTO `patient` VALUES (8, 1, '方艺科', '待', '2023-03-25 20:50:43', '2023-03-25 21:12:31');
INSERT INTO `patient` VALUES (9, 1, '312', '待', '2023-03-25 20:52:00', '2023-03-25 21:56:53');
INSERT INTO `patient` VALUES (10, 1, '方艺科', '待', '2023-03-25 21:13:14', '2023-03-26 14:26:29');
INSERT INTO `patient` VALUES (11, 2, '312', '待', '2023-03-25 21:14:38', '2023-03-25 21:16:57');
INSERT INTO `patient` VALUES (12, 2, '312', '待', '2023-03-25 21:15:42', '2023-03-25 21:48:10');
INSERT INTO `patient` VALUES (13, 2, '韩松', '待', '2023-03-25 21:15:57', '2023-03-25 21:49:25');
INSERT INTO `patient` VALUES (14, 1, '玄子', '待', '2023-03-26 14:25:59', '2023-03-26 14:26:46');
INSERT INTO `patient` VALUES (15, 2, '玄子', '待', '2023-03-26 14:27:33', '2023-03-26 14:27:45');

-- ----------------------------
-- Table structure for technicaloffices
-- ----------------------------
DROP TABLE IF EXISTS `technicaloffices`;
CREATE TABLE `technicaloffices`  (
  `technicalOfficesId` int(4) NOT NULL AUTO_INCREMENT COMMENT '科室编号',
  `technicalOfficesFloor` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '科室楼层',
  `technicalOfficesName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '科室名称',
  PRIMARY KEY (`technicalOfficesId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of technicaloffices
-- ----------------------------
INSERT INTO `technicaloffices` VALUES (1, '一楼', '急诊科');
INSERT INTO `technicaloffices` VALUES (2, '二楼', '呼吸科');
INSERT INTO `technicaloffices` VALUES (3, '三楼', '内科');

SET FOREIGN_KEY_CHECKS = 1;

8.5 求职招聘项目

/*
 Navicat Premium Data Transfer

 Source Server         : XuanZi
 Source Server Type    : MySQL
 Source Server Version : 50740 (5.7.40)
 Source Host           : localhost:3306
 Source Schema         : wantedjob

 Target Server Type    : MySQL
 Target Server Version : 50740 (5.7.40)
 File Encoding         : 65001

 Date: 30/03/2023 19:05:27
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for collect
-- ----------------------------
DROP TABLE IF EXISTS `collect`;
CREATE TABLE `collect`  (
  `collectId` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id',
  `initiativeCollect` int(11) NOT NULL COMMENT '收藏者',
  `passiveCollect` int(11) NOT NULL COMMENT '被收藏者',
  `CollectType` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '收藏者关系  u-c   c-u',
  PRIMARY KEY (`collectId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of collect
-- ----------------------------
INSERT INTO `collect` VALUES (1, 1, 1, 'u-c');
INSERT INTO `collect` VALUES (2, 1, 2, 'u-c');
INSERT INTO `collect` VALUES (3, 2, 2, 'c-u');
INSERT INTO `collect` VALUES (4, 1, 2, 'u-c');
INSERT INTO `collect` VALUES (5, 2, 1, 'u-c');
INSERT INTO `collect` VALUES (6, 3, 1, 'u-c');
INSERT INTO `collect` VALUES (7, 1, 2, 'c-u');
INSERT INTO `collect` VALUES (8, 1, 2, 'c-u');
INSERT INTO `collect` VALUES (9, 1, 1, 'u-c');

-- ----------------------------
-- Table structure for company
-- ----------------------------
DROP TABLE IF EXISTS `company`;
CREATE TABLE `company`  (
  `companyId` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `companyName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '公司名称',
  `companyTypeId` int(11) NOT NULL COMMENT '公司类型编号',
  `companyMinEmployeeNum` int(11) NULL DEFAULT NULL COMMENT '公司规模最小人数',
  `companyMaxEmployeeNum` int(11) NULL DEFAULT NULL COMMENT '公司规模最大人数',
  PRIMARY KEY (`companyId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of company
-- ----------------------------
INSERT INTO `company` VALUES (1, '玄子责任有限公司', 1, 20, 99);

-- ----------------------------
-- Table structure for companytype
-- ----------------------------
DROP TABLE IF EXISTS `companytype`;
CREATE TABLE `companytype`  (
  `companyTypeId` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id',
  `companyTypeName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '公司类型名称',
  PRIMARY KEY (`companyTypeId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of companytype
-- ----------------------------
INSERT INTO `companytype` VALUES (1, '软件开发公司');
INSERT INTO `companytype` VALUES (2, '平面设计公司');

-- ----------------------------
-- Table structure for deliver
-- ----------------------------
DROP TABLE IF EXISTS `deliver`;
CREATE TABLE `deliver`  (
  `deliverId` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id',
  `companyId` int(11) NOT NULL COMMENT '公司Id',
  `userInfoId` int(11) NOT NULL COMMENT '用户Id',
  PRIMARY KEY (`deliverId`) USING BTREE,
  INDEX `companyId`(`companyId`) USING BTREE,
  INDEX `userInfoId`(`userInfoId`) USING BTREE,
  CONSTRAINT `deliver_ibfk_1` FOREIGN KEY (`companyId`) REFERENCES `company` (`companyId`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `deliver_ibfk_2` FOREIGN KEY (`userInfoId`) REFERENCES `userinfo` (`userInfoId`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of deliver
-- ----------------------------
INSERT INTO `deliver` VALUES (1, 1, 1);
INSERT INTO `deliver` VALUES (2, 1, 2);
INSERT INTO `deliver` VALUES (3, 1, 3);
INSERT INTO `deliver` VALUES (4, 1, 1);

-- ----------------------------
-- Table structure for education
-- ----------------------------
DROP TABLE IF EXISTS `education`;
CREATE TABLE `education`  (
  `educationId` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id',
  `educationName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '学历名称',
  PRIMARY KEY (`educationId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of education
-- ----------------------------
INSERT INTO `education` VALUES (1, '中专');
INSERT INTO `education` VALUES (2, '大专');
INSERT INTO `education` VALUES (3, '本科');
INSERT INTO `education` VALUES (4, '硕士');
INSERT INTO `education` VALUES (5, '研究生');

-- ----------------------------
-- Table structure for job
-- ----------------------------
DROP TABLE IF EXISTS `job`;
CREATE TABLE `job`  (
  `jobId` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id',
  `jobName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '职业名称',
  `jobTypeId` int(11) NULL DEFAULT NULL COMMENT '职业类型编号',
  PRIMARY KEY (`jobId`) USING BTREE,
  INDEX `jobTypeId`(`jobTypeId`) USING BTREE,
  CONSTRAINT `job_ibfk_1` FOREIGN KEY (`jobTypeId`) REFERENCES `jobtype` (`jobTypeId`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of job
-- ----------------------------
INSERT INTO `job` VALUES (1, 'java开发', 1);
INSERT INTO `job` VALUES (2, '前端开发', 1);
INSERT INTO `job` VALUES (3, 'android开发', 1);
INSERT INTO `job` VALUES (4, 'PS美工', 2);

-- ----------------------------
-- Table structure for jobinfo
-- ----------------------------
DROP TABLE IF EXISTS `jobinfo`;
CREATE TABLE `jobinfo`  (
  `jobInfoId` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id',
  `userInfoId` int(11) NULL DEFAULT NULL COMMENT '用户Id',
  `jobId` int(11) NULL DEFAULT NULL COMMENT '职业名称Id',
  `jobInfoTitle` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '职业信息标题',
  `jobInfoMinSalary` double(10, 2) NULL DEFAULT NULL COMMENT '最低薪资(xxK)',
  `jobInfoMaxSalary` double(10, 2) NULL DEFAULT NULL COMMENT '最高薪资(xxK)',
  `experienceMinYear` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '工作经验最低年限',
  `experienceMaxYear` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '工作经验最高年限',
  `educationMinId` int(11) NULL DEFAULT NULL COMMENT '最低学历编号',
  `region` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地区',
  PRIMARY KEY (`jobInfoId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of jobinfo
-- ----------------------------
INSERT INTO `jobinfo` VALUES (1, 2, 1, 'java开发【五险一金】', 1.00, 1000.00, '1', '3', 2, '河南省-郑州市-金水区');
INSERT INTO `jobinfo` VALUES (2, 2, 1, 'java开发【五险一金】', 300.00, 5000.00, '1', '3', 3, '金水区');
INSERT INTO `jobinfo` VALUES (3, 3, 3, '前端开发', 100.00, 10000.00, '1', '3', 2, '中原区');

-- ----------------------------
-- Table structure for jobtype
-- ----------------------------
DROP TABLE IF EXISTS `jobtype`;
CREATE TABLE `jobtype`  (
  `jobTypeId` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id',
  `jobTypeName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '职业类型名',
  PRIMARY KEY (`jobTypeId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of jobtype
-- ----------------------------
INSERT INTO `jobtype` VALUES (1, '软件开发');
INSERT INTO `jobtype` VALUES (2, '平面设计');
INSERT INTO `jobtype` VALUES (3, '影视后期');
INSERT INTO `jobtype` VALUES (4, '室内设计');

-- ----------------------------
-- Table structure for resume
-- ----------------------------
DROP TABLE IF EXISTS `resume`;
CREATE TABLE `resume`  (
  `resumeId` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id',
  `userInfoId` int(11) NULL DEFAULT NULL COMMENT '用户Id',
  `jobId` int(11) NULL DEFAULT NULL COMMENT '职业名称Id',
  `jobInfoMinSalary` double(10, 2) NULL DEFAULT NULL COMMENT '最低薪资(xxK)',
  `jobInfoMaxSalary` double(10, 2) NOT NULL COMMENT '最高薪资(xxK)',
  `experienceMinYear` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '工作经验最低年限',
  `experienceMaxYear` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '工作经验最高年限',
  `educationMaxId` int(11) NULL DEFAULT NULL COMMENT '最低学历编号',
  `region` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地区',
  PRIMARY KEY (`resumeId`) USING BTREE,
  INDEX `userInfoId`(`userInfoId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of resume
-- ----------------------------
INSERT INTO `resume` VALUES (1, 1, 1, 1.00, 1.00, '1', '1', 3, '中原区');
INSERT INTO `resume` VALUES (2, 1, 1, 1.00, 1.00, '1', '1', 3, '中原区');
INSERT INTO `resume` VALUES (3, 2, 1, 300.00, 500.00, '1', '3', 3, '金水区');
INSERT INTO `resume` VALUES (4, 3, 1, 300.00, 500.00, '1', '3', 3, '金水区2');
INSERT INTO `resume` VALUES (5, 3, 1, 300.00, 500.00, '1', '3', 3, '金水区2');

-- ----------------------------
-- Table structure for userinfo
-- ----------------------------
DROP TABLE IF EXISTS `userinfo`;
CREATE TABLE `userinfo`  (
  `userInfoId` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id',
  `userInfoName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
  `userInfoAccount` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '账号',
  `userInfoPwd` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
  `userInfoPhone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '联系电话',
  `userInfoTypeId` int(11) NOT NULL COMMENT '用户类型Id',
  `companyId` int(11) NULL DEFAULT NULL COMMENT '公司Id',
  PRIMARY KEY (`userInfoId`) USING BTREE,
  INDEX `userInfoTypeId`(`userInfoTypeId`) USING BTREE,
  CONSTRAINT `userinfo_ibfk_1` FOREIGN KEY (`userInfoTypeId`) REFERENCES `usertype` (`userTypeId`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of userinfo
-- ----------------------------
INSERT INTO `userinfo` VALUES (1, '玄子', '12345', '12345', '12314211', 1, -1);
INSERT INTO `userinfo` VALUES (2, '玉玉诏', '54321', '12345', '13534151', 2, 1);
INSERT INTO `userinfo` VALUES (3, '123', '3213', '321', '32131', 3, 1);
INSERT INTO `userinfo` VALUES (5, '123', '123456', '12345', '32131', 3, 1);
INSERT INTO `userinfo` VALUES (6, '玄子', '3214', '2142', '1412421', 1, -1);
INSERT INTO `userinfo` VALUES (7, 'eqw', 'ewq', 'eqw', 'eeqw', 2, 1);

-- ----------------------------
-- Table structure for usertype
-- ----------------------------
DROP TABLE IF EXISTS `usertype`;
CREATE TABLE `usertype`  (
  `userTypeId` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id',
  `userTypeName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户类型',
  PRIMARY KEY (`userTypeId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of usertype
-- ----------------------------
INSERT INTO `usertype` VALUES (1, '求职者');
INSERT INTO `usertype` VALUES (2, '招聘公司');
INSERT INTO `usertype` VALUES (3, '猎头');

SET FOREIGN_KEY_CHECKS = 1;
WantedJob 求职项目
用户信息userInfo
userInfoIduserInfoNameuserInfoAccountuserInfoPwduserInfoPhoneuserInfoTypeIdcompanyId
用户名账号密码联系电话用户类型Id公司Id
用户类型userType
userTypeIduserTypeName
用户类型求职者、招聘公司、猎头
职业类型jobType
jobTypeIdjobTypeName
职业类型名
职业名称job
jobIdjobNamejobTypeId
职业名称职业类型编号
公司类型companyType
companyTypeIdcompanyTypeName
公司类型名称
公司信息company
companyIdcompanyNamecompanyTypeIdcompanyMinEmployeeNumcompanyMaxEmployeeNum
公司名称公司类型编号公司规模最小人数公司规模最大人数
职业信息(发布者)jobInfo
jobInfoIduserInfoIdjobIdjobInfoTitlejobInfoMinSalaryjobInfoMaxSalaryexperienceMinYearexperienceMaxYeareducationMinIdregion
用户Id职业名称Id职业信息标题最低薪资(xxK)最高薪资(xxK)工作经验最低年限工作经验最高年限最低学历编号地区
简历信息(求职者)resume
resumeIduserInfoIdjobIdjobInfoMinSalaryjobInfoMaxSalaryexperienceMinYearexperienceMaxYeareducationMinIdregion
用户Id职业名称Id最低薪资(xxK)最高薪资(xxK)工作经验最低年限工作经验最高年限最低学历编号地区
投递简历(用户投递公司)deliver
deliverIdcompanyIduserInfoId
公司Id用户Id
收藏collect
collectIdinitiativeCollectpassiveCollect
收藏者被收藏者
学历education
educationIdeducationName
学历名称

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pGz8bOk7-1680618008643)(./assets/%E6%B1%82%E8%81%8C%E8%A7%92%E8%89%B2%E5%8A%9F%E8%83%BD.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UA7IUc8v-1680618008644)(./assets/%E6%8B%9B%E8%81%98%E8%80%85%E5%8A%9F%E8%83%BD.png)]


九、MySQL 优化

sql 优化是一个大家都比较关注的热门话题,无论你在面试,还是工作中,都很有可能会遇到,如果某天你负责的某个线上接口,出现了性能问题,需要做优化。那么你首先想到的很有可能是优化sql语句,因为它的改造成本相对于代码来说也要小得多


9.1 避免使用 select *

很多时候,我们写 sql 语句时,为了方便,喜欢直接使用select *,一次性查出表中所有列的数据

反例:

select * from user where id=1;

在实际业务场景中,可能我们真正需要使用的只有其中一两列。查了很多数据,但是不用,白白浪费了数据库资源,比如:内存或者cpu。

此外,多查出来的数据,通过网络IO传输的过程中,也会增加数据传输的时间。

还有一个最重要的问题是:select *不会走覆盖索引,会出现大量的回表操作,而从导致查询sql的性能很低。

那么,如何优化呢?

正例:

select name,age from user where id=1;

sql语句查询时,只查需要用到的列,多余的列根本无需查出来。


9.2 用union all代替union

我们都知道sql语句使用union关键字后,可以获取排重后的数据。

而如果使用union all关键字,可以获取所有数据,包含重复的数据。

反例:

(select * from user where id=1) 
union 
(select * from user where id=2);

排重的过程需要遍历、排序和比较,它更耗时,更消耗cpu资源。

所以如果能用union all的时候,尽量不用union。

正例:

(select * from user where id=1) 
union all
(select * from user where id=2);

除非是有些特殊的场景,比如union all之后,结果集中出现了重复数据,而业务场景中是不允许产生重复数据的,这时可以使用union。


9.3 小表驱动大表

小表驱动大表,也就是说用小表的数据集驱动大表的数据集。

假如有order和user两张表,其中order表有10000条数据,而user表有100条数据。

这时如果想查一下,所有有效的用户下过的订单列表。

可以使用in关键字实现:

select * from order
where user_id in (select id from user where status=1)

也可以使用exists关键字实现:

select * from order
where exists (select 1 from user where order.user_id = user.id and status=1)

前面提到的这种业务场景,使用in关键字去实现业务需求,更加合适。

为什么呢?

因为如果sql语句中包含了in关键字,则它会优先执行in里面的子查询语句,然后再执行in外面的语句。如果in里面的数据量很少,作为条件查询速度更快。

而如果sql语句中包含了exists关键字,它优先执行exists左边的语句(即主查询语句)。然后把它作为条件,去跟右边的语句匹配。如果匹配上,则可以查询出数据。如果匹配不上,数据就被过滤掉了。

这个需求中,order表有10000条数据,而user表有100条数据。order表是大表,user表是小表。如果order表在左边,则用in关键字性能更好。

总结一下:

  • in 适用于左边大表,右边小表。
  • exists 适用于左边小表,右边大表。

不管是用in,还是exists关键字,其核心思想都是用小表驱动大表。


9.4 批量操作

如果你有一批数据经过业务处理之后,需要插入数据,该怎么办?

反例:

for(Order order: list){
   orderMapper.insert(order):
}

在循环中逐条插入数据。

insert into order(id,code,user_id) 
values(123,'001',100);

该操作需要多次请求数据库,才能完成这批数据的插入。

但众所周知,我们在代码中,每次远程请求数据库,是会消耗一定性能的。而如果我们的代码需要请求多次数据库,才能完成本次业务功能,势必会消耗更多的性能。

那么如何优化呢?

正例:

orderMapper.insertBatch(list):

提供一个批量插入数据的方法。

insert into order(id,code,user_id) 
values(123,'001',100),(124,'002',100),(125,'003',101);

这样只需要远程请求一次数据库,sql性能会得到提升,数据量越多,提升越大。

但需要注意的是,不建议一次批量操作太多的数据,如果数据太多数据库响应也会很慢。批量操作需要把握一个度,建议每批数据尽量控制在500以内。如果数据多于500,则分多批次处理。


9.5 多用limit

有时候,我们需要查询某些数据中的第一条,比如:查询某个用户下的第一个订单,想看看他第一次的首单时间。

反例:

select id, create_date 
 from order 
where user_id=123 
order by create_date asc;

根据用户id查询订单,按下单时间排序,先查出该用户所有的订单数据,得到一个订单集合。然后在代码中,获取第一个元素的数据,即首单的数据,就能获取首单时间。

List<Order> list = orderMapper.getOrderList();
Order order = list.get(0);

虽说这种做法在功能上没有问题,但它的效率非常不高,需要先查询出所有的数据,有点浪费资源。

那么,如何优化呢?

正例:

select id, create_date 
 from order 
where user_id=123 
order by create_date asc 
limit 1;

使用limit 1,只返回该用户下单时间最小的那一条数据即可。

此外,在删除或者修改数据时,为了防止误操作,导致删除或修改了不相干的数据,也可以在sql语句最后加上limit。

例如:

update order set status=0,edit_time=now(3) 
where id>=100 and id<200 limit 100;

这样即使误操作,比如把id搞错了,也不会对太多的数据造成影响。


9.6 in中值太多

对于批量查询接口,我们通常会使用in关键字过滤出数据。比如:想通过指定的一些id,批量查询出用户信息。

sql语句如下:

select id,name from category
where id in (1,2,3...100000000);

如果我们不做任何限制,该查询语句一次性可能会查询出非常多的数据,很容易导致接口超时。

这时该怎么办呢?

select id,name from category
where id in (1,2,3...100)
limit 500;

可以在sql中对数据用limit做限制。

不过我们更多的是要在业务代码中加限制,伪代码如下:

public List<Category> getCategory(List<Long> ids) {
   if(CollectionUtils.isEmpty(ids)) {
      return null;
   }
   if(ids.size() > 500) {
      throw new BusinessException("一次最多允许查询500条记录")
   }
   return mapper.getCategoryList(ids);
}

还有一个方案就是:如果ids超过500条记录,可以分批用多线程去查询数据。每批只查500条记录,最后把查询到的数据汇总到一起返回。

不过这只是一个临时方案,不适合于ids实在太多的场景。因为ids太多,即使能快速查出数据,但如果返回的数据量太大了,网络传输也是非常消耗性能的,接口性能始终好不到哪里去。


9.7 增量查询

有时候,我们需要通过远程接口查询数据,然后同步到另外一个数据库。

反例:

select * from user;

如果直接获取所有的数据,然后同步过去。这样虽说非常方便,但是带来了一个非常大的问题,就是如果数据很多的话,查询性能会非常差。

这时该怎么办呢?

正例:

select * from user 
where id>#{lastId} and create_time >= #{lastCreateTime} 
limit 100;

按id和时间升序,每次只同步一批数据,这一批数据只有100条记录。每次同步完成之后,保存这100条数据中最大的id和时间,给同步下一批数据的时候用。

通过这种增量查询的方式,能够提升单次查询的效率。


9.8 高效的分页

有时候,列表页在查询数据时,为了避免一次性返回过多的数据影响接口性能,我们一般会对查询接口做分页处理。

在mysql中分页一般用的limit关键字:

select id,name,age 
from user limit 10,20;

如果表中数据量少,用limit关键字做分页,没啥问题。但如果表中数据量很多,用它就会出现性能问题。

比如现在分页参数变成了:

select id,name,age 
from user limit 1000000,20;

mysql会查到1000020条数据,然后丢弃前面的1000000条,只查后面的20条数据,这个是非常浪费资源的。

那么,这种海量数据该怎么分页呢?

优化sql:

select id,name,age 
from user where id > 1000000 limit 20;

先找到上次分页最大的id,然后利用id上的索引查询。不过该方案,要求id是连续的,并且有序的。

还能使用between优化分页。

select id,name,age 
from user where id between 1000000 and 1000020;

需要注意的是between要在唯一索引上分页,不然会出现每页大小不一致的问题。


9.9 用连接查询代替子查询

mysql中如果需要从两张以上的表中查询出数据的话,一般有两种实现方式:子查询连接查询

子查询的例子如下:

select * from order
where user_id in (select id from user where status=1)

子查询语句可以通过in关键字实现,一个查询语句的条件落在另一个select语句的查询结果中。程序先运行在嵌套在最内层的语句,再运行外层的语句。

子查询语句的优点是简单,结构化,如果涉及的表数量不多的话。

但缺点是mysql执行子查询时,需要创建临时表,查询完毕后,需要再删除这些临时表,有一些额外的性能消耗。

这时可以改成连接查询。具体例子如下:

select o.* from order o
inner join user u on o.user_id = u.id
where u.status=1

9.10 join的表不宜过多

根据阿里巴巴开发者手册的规定,join表的数量不应该超过3个。

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

反例:

select a.name,b.name.c.name,d.name
from a 
inner join b on a.id = b.a_id
inner join c on c.b_id = b.id
inner join d on d.c_id = c.id
inner join e on e.d_id = d.id
inner join f on f.e_id = e.id
inner join g on g.f_id = f.id

如果join太多,mysql在选择索引的时候会非常复杂,很容易选错索引。

并且如果没有命中中,nested loop join 就是分别从两个表读一行数据进行两两对比,复杂度是 n^2。

所以我们应该尽量控制join表的数量。

正例:

select a.name,b.name.c.name,a.d_name 
from a 
inner join b on a.id = b.a_id
inner join c on c.b_id = b.id

如果实现业务场景中需要查询出另外几张表中的数据,可以在a、b、c表中冗余专门的字段,比如:在表a中冗余d_name字段,保存需要查询出的数据。

不过我之前也见过有些ERP系统,并发量不大,但业务比较复杂,需要join十几张表才能查询出数据。

所以join表的数量要根据系统的实际情况决定,不能一概而论,尽量越少越好。


9.11 join时要注意

我们在涉及到多张表联合查询的时候,一般会使用join关键字。

而join使用最多的是left join和inner join。

  • left join:求两个表的交集外加左表剩下的数据。
  • inner join:求两个表交集的数据。

使用inner join的示例如下:

select o.id,o.code,u.name 
from order o 
inner join user u on o.user_id = u.id
where u.status=1;

如果两张表使用inner join关联,mysql会自动选择两张表中的小表,去驱动大表,所以性能上不会有太大的问题。

使用left join的示例如下:

select o.id,o.code,u.name 
from order o 
left join user u on o.user_id = u.id
where u.status=1;

如果两张表使用left join关联,mysql会默认用left join关键字左边的表,去驱动它右边的表。如果左边的表数据很多时,就会出现性能问题。

要特别注意的是在用left join关联查询时,左边要用小表,右边可以用大表。如果能用inner join的地方,尽量少用left join。


9.12 控制索引的数量

众所周知,索引能够显著的提升查询sql的性能,但索引数量并非越多越好。

因为表中新增数据时,需要同时为它创建索引,而索引是需要额外的存储空间的,而且还会有一定的性能消耗。

阿里巴巴的开发者手册中规定,单表的索引数量应该尽量控制在5个以内,并且单个索引中的字段数不超过5个。

mysql使用的B+树的结构来保存索引的,在insert、update和delete操作时,需要更新B+树索引。如果索引过多,会消耗很多额外的性能。

那么,问题来了,如果表中的索引太多,超过了5个该怎么办?

这个问题要辩证的看,如果你的系统并发量不高,表中的数据量也不多,其实超过5个也可以,只要不要超过太多就行。

但对于一些高并发的系统,请务必遵守单表索引数量不要超过5的限制。

那么,高并发系统如何优化索引数量?

能够建联合索引,就别建单个索引,可以删除无用的单个索引。

将部分查询功能迁移到其他类型的数据库中,比如:Elastic Seach、HBase等,在业务表中只需要建几个关键索引即可。


9.13 选择合理的字段类型

char表示固定字符串类型,该类型的字段存储空间的固定的,会浪费存储空间。

alter table order 
add column code char(20) NOT NULL;

varchar表示变长字符串类型,该类型的字段存储空间会根据实际数据的长度调整,不会浪费存储空间。

alter table order 
add column code varchar(20) NOT NULL;

如果是长度固定的字段,比如用户手机号,一般都是11位的,可以定义成char类型,长度是11字节。

但如果是企业名称字段,假如定义成char类型,就有问题了。

如果长度定义得太长,比如定义成了200字节,而实际企业长度只有50字节,则会浪费150字节的存储空间。

如果长度定义得太短,比如定义成了50字节,但实际企业名称有100字节,就会存储不下,而抛出异常。

所以建议将企业名称改成varchar类型,变长字段存储空间小,可以节省存储空间,而且对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

我们在选择字段类型时,应该遵循这样的原则:

  1. 能用数字类型,就不用字符串,因为字符的处理往往比数字要慢。
  2. 尽可能使用小的类型,比如:用bit存布尔值,用tinyint存枚举值等。
  3. 长度固定的字符串字段,用char类型。
  4. 长度可变的字符串字段,用varchar类型。
  5. 金额字段用decimal,避免精度丢失问题。

还有很多原则,这里就不一一列举了。


9.14 提升group by的效率

我们有很多业务场景需要使用group by关键字,它主要的功能是去重和分组。

通常它会跟having一起配合使用,表示分组后再根据一定的条件过滤数据。

反例:

select user_id,user_name from order
group by user_id
having user_id <= 200;

这种写法性能不好,它先把所有的订单根据用户id分组之后,再去过滤用户id大于等于200的用户。

分组是一个相对耗时的操作,为什么我们不先缩小数据的范围之后,再分组呢?

正例:

select user_id,user_name from order
where user_id <= 200
group by user_id

使用where条件在分组前,就把多余的数据过滤掉了,这样分组时效率就会更高一些。

其实这是一种思路,不仅限于group by的优化。我们的sql语句在做一些耗时的操作之前,应尽可能缩小数据范围,这样能提升sql整体的性能。


9.15 索引优化

sql优化当中,有一个非常重要的内容就是:索引优化

很多时候sql语句,走了索引,和没有走索引,执行效率差别很大。所以索引优化被作为sql优化的首选。

索引优化的第一步是:检查sql语句有没有走索引。

那么,如何查看sql走了索引没?

可以使用explain命令,查看mysql的执行计划。

例如:

explain select * from `order` where code='002';

结果:

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

通过这几列可以判断索引使用情况,执行计划包含列的含义如下图所示:

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

说实话,sql语句没有走索引,排除没有建索引之外,最大的可能性是索引失效了。

下面说说索引失效的常见原因:

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

如果不是上面的这些原因,则需要再进一步排查一下其他原因。

此外,你有没有遇到过这样一种情况:明明是同一条sql,只有入参不同而已。有的时候走的索引a,有的时候却走的索引b?

没错,有时候mysql会选错索引。

必要时可以使用force index来强制查询sql走某个索引。


十、附件

10.1 BCSP 配套数据库hospital

/*
 Navicat Premium Data Transfer

 Source Server         : MySQL
 Source Server Type    : MySQL
 Source Server Version : 50730
 Source Host           : localhost:3306
 Source Schema         : hospital

 Target Server Type    : MySQL
 Target Server Version : 50730
 File Encoding         : 65001

 Date: 04/08/2021 09:00:32
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for checkitem
-- ----------------------------
DROP TABLE IF EXISTS `checkitem`;
CREATE TABLE `checkitem`  (
  `checkItemID` int(4) NOT NULL AUTO_INCREMENT COMMENT '检查项目编号',
  `checkItemName` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '检查项目名称',
  `checkItemCost` float NOT NULL COMMENT '检查项目价格',
  PRIMARY KEY (`checkItemID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '检查项目表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of checkitem
-- ----------------------------
INSERT INTO `checkitem` VALUES (1, '血常规', 28);
INSERT INTO `checkitem` VALUES (2, '尿常规', 20);
INSERT INTO `checkitem` VALUES (3, '血脂、血糖检查', 25);
INSERT INTO `checkitem` VALUES (4, '凝血五项', 50);
INSERT INTO `checkitem` VALUES (5, '肺炎支、衣原体(快速)', 66);
INSERT INTO `checkitem` VALUES (6, 'CT', 110);

-- ----------------------------
-- Table structure for department
-- ----------------------------
DROP TABLE IF EXISTS `department`;
CREATE TABLE `department`  (
  `depID` int(4) NOT NULL AUTO_INCREMENT COMMENT '科室编号',
  `depName` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '科室名称',
  PRIMARY KEY (`depID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '科室表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of department
-- ----------------------------
INSERT INTO `department` VALUES (1, '急诊科');
INSERT INTO `department` VALUES (2, '呼吸科');
INSERT INTO `department` VALUES (3, '内科');

-- ----------------------------
-- Table structure for department_checkitem
-- ----------------------------
DROP TABLE IF EXISTS `department_checkitem`;
CREATE TABLE `department_checkitem`  (
  `id` int(4) NOT NULL AUTO_INCREMENT COMMENT '关系编号',
  `depID` int(4) NOT NULL COMMENT '科室编号',
  `checkItemID` int(4) NOT NULL COMMENT '检查项目编号',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `fk_department_checkitem_checkitem`(`checkItemID`) USING BTREE,
  INDEX `fk_department_checkitem_department`(`depID`) USING BTREE,
  CONSTRAINT `fk_department_checkitem_checkitem` FOREIGN KEY (`checkItemID`) REFERENCES `checkitem` (`checkItemID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `fk_department_checkitem_department` FOREIGN KEY (`depID`) REFERENCES `department` (`depID`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '科室可开检查项目关系表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of department_checkitem
-- ----------------------------
INSERT INTO `department_checkitem` VALUES (1, 1, 1);
INSERT INTO `department_checkitem` VALUES (2, 1, 2);
INSERT INTO `department_checkitem` VALUES (3, 2, 1);
INSERT INTO `department_checkitem` VALUES (4, 2, 5);
INSERT INTO `department_checkitem` VALUES (5, 3, 1);
INSERT INTO `department_checkitem` VALUES (6, 3, 2);
INSERT INTO `department_checkitem` VALUES (7, 3, 3);
INSERT INTO `department_checkitem` VALUES (8, 3, 4);

-- ----------------------------
-- Table structure for patient
-- ----------------------------
DROP TABLE IF EXISTS `patient`;
CREATE TABLE `patient`  (
  `patientID` int(4) NOT NULL AUTO_INCREMENT COMMENT '病人编号',
  `password` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '登录密码',
  `birthDate` date NULL DEFAULT NULL COMMENT '出生日期',
  `gender` varchar(4) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '男' COMMENT '性别',
  `patientName` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '病人姓名',
  `phoneNum` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '联系电话',
  `email` varchar(70) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
  `identityNum` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '身份证号',
  `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '地址不详' COMMENT '地址',
  PRIMARY KEY (`patientID`) USING BTREE,
  UNIQUE INDEX `identityNum`(`identityNum`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '病人表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of patient
-- ----------------------------
INSERT INTO `patient` VALUES (1, '123456', '1985-06-07', '女', '夏颖', '13800000001', 'ying.xia@qq.com', '110000198506071100', '厦门市');
INSERT INTO `patient` VALUES (2, '234567', '1985-06-08', '男', '李政', '13800000002', 'lizheng@163.com', '210000198506082100', '长春市');
INSERT INTO `patient` VALUES (3, '345678', '2010-03-02', '女', '李沁', '13800000003', 'liqin@sohu.com', '120000201003021200', '合肥市');
INSERT INTO `patient` VALUES (4, '456789', '1999-01-02', '女', '李思雨', '13800000004', 'siyu.li@hotmail.com', '150000199901021500', '北京市');
INSERT INTO `patient` VALUES (5, '567890', '2008-10-17', '男', '夏天', '13800000005', 'tian.xia@qq.com', '210000200810172100', '长春市');
INSERT INTO `patient` VALUES (6, '678901', '1999-03-08', '男', '刘占波', '13800000006', 'zhanbo@163.com', '210000199903082101', '惠州市');
INSERT INTO `patient` VALUES (7, '789012', '1987-05-02', '女', '廖慧颖', '13800000007', 'huiying@qq.com', '220000198705022200', '广州市');
INSERT INTO `patient` VALUES (8, '890123', '1975-03-02', '男', '李伟忠', '13800000008', 'wz@qq.com', '230000197503022300', '沈阳市');
INSERT INTO `patient` VALUES (9, '901234', '1986-10-11', '男', '姚维新', '13800000009', 'ywx@hotmail.com', '310000198610113100', '北京市');
INSERT INTO `patient` VALUES (10, '012345', '1975-03-04', '男', '陈建', '13800000010', 'cz@qq.com', '320000197503043200', '北京市');
INSERT INTO `patient` VALUES (11, '098765', '1992-01-01', '女', '林永清', '13800000011', 'yongqing@qq.com', '330000199201013300', '长春市');
INSERT INTO `patient` VALUES (12, '987654', '1993-03-02', '女', '李亚', '13800000012', 'liya@qq.com', '340000199303023400', '保定市');
INSERT INTO `patient` VALUES (13, '909000', '1995-02-12', '女', '张菲', '13887676500', 'fei.zhang@qq.com', '610000199502126100', '北京市');

-- ----------------------------
-- Table structure for prescription
-- ----------------------------
DROP TABLE IF EXISTS `prescription`;
CREATE TABLE `prescription`  (
  `examID` int(4) NOT NULL AUTO_INCREMENT COMMENT '检查编号',
  `patientID` int(4) NOT NULL COMMENT '病人编号',
  `depID` int(4) NOT NULL COMMENT '开处方的科室编号',
  `checkResult` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '检查结果',
  `checkItemID` int(4) NOT NULL COMMENT '检查项目编号',
  `examDate` datetime(0) NOT NULL COMMENT '检查日期',
  UNIQUE INDEX `examID`(`examID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '处方表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of prescription
-- ----------------------------
INSERT INTO `prescription` VALUES (1, 1, 1, '正常', 1, '2020-01-02 00:00:00');
INSERT INTO `prescription` VALUES (2, 1, 1, '正常', 2, '2020-01-02 00:00:00');
INSERT INTO `prescription` VALUES (3, 3, 2, '肺炎支原体阳性', 5, '2020-04-05 00:00:00');
INSERT INTO `prescription` VALUES (4, 1, 1, '正常', 1, '2020-02-06 00:00:00');
INSERT INTO `prescription` VALUES (5, 8, 3, '正常', 4, '2020-03-02 00:00:00');
INSERT INTO `prescription` VALUES (6, 8, 3, '血糖偏高', 3, '2020-03-02 00:00:00');
INSERT INTO `prescription` VALUES (7, 8, 3, '正常', 1, '2020-03-02 00:00:00');
INSERT INTO `prescription` VALUES (8, 10, 3, '正常', 3, '2020-03-02 00:00:00');
INSERT INTO `prescription` VALUES (9, 1, 1, '白细胞数量偏高', 1, '2020-07-08 00:00:00');

-- ----------------------------
-- View structure for v_patient_cost
-- ----------------------------
DROP VIEW IF EXISTS `v_patient_cost`;
CREATE ALGORITHM = UNDEFINED DEFINER = `root`@`localhost` SQL SECURITY DEFINER VIEW `v_patient_cost` AS select `p1`.`patientID` AS `patientID`,`patient`.`patientName` AS `patientName`,sum(`checkitem`.`checkItemCost`) AS `totalcost` from ((`prescription` `p1` join `checkitem` on((`p1`.`checkItemID` = `checkitem`.`checkItemID`))) join `patient` on((`p1`.`patientID` = `patient`.`patientID`))) group by `p1`.`patientID`;

-- ----------------------------
-- View structure for v_prescription
-- ----------------------------
DROP VIEW IF EXISTS `v_prescription`;
CREATE ALGORITHM = UNDEFINED DEFINER = `root`@`localhost` SQL SECURITY DEFINER VIEW `v_prescription` AS select `pa`.`patientName` AS `姓名`,`pa`.`gender` AS `性别`,`pa`.`birthDate` AS `年龄`,`c`.`checkItemName` AS `检查项目`,`pr`.`checkResult` AS `检查结果`,`d`.`depName` AS `检查科室`,`pr`.`examDate` AS `检查日期` from (((`prescription` `pr` join `patient` `pa` on((`pr`.`patientID` = `pa`.`patientID`))) join `department` `d` on((`pr`.`depID` = `d`.`depID`))) join `checkitem` `c` on((`pr`.`checkItemID` = `c`.`checkItemID`)));

-- ----------------------------
-- Procedure structure for LOOPLoopProc
-- ----------------------------
DROP PROCEDURE IF EXISTS `LOOPLoopProc`;
delimiter ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `LOOPLoopProc`(OUT patientNum INT)
BEGIN
               DECLARE x  INT;
               DECLARE str  VARCHAR(255);
               SET x = 1;
               SET str =  '';
               loop_label:  LOOP
                           IF  x > 10 THEN
                               LEAVE  loop_label;
                           END  IF;
                           SET  x = x + 1;
                           IF  (x mod 2) THEN
                               ITERATE  loop_label;
                           ELSE
                               SET  str = CONCAT(str,x,',');
                           END  IF;

               END LOOP;    
               SELECT str;
END
;;
delimiter ;

-- ----------------------------
-- Procedure structure for proc1
-- ----------------------------
DROP PROCEDURE IF EXISTS `proc1`;
delimiter ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `proc1`( )
BEGIN
  SET @name = '王明';
END
;;
delimiter ;

-- ----------------------------
-- Procedure structure for proc2
-- ----------------------------
DROP PROCEDURE IF EXISTS `proc2`;
delimiter ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `proc2`( )
BEGIN
   SELECT CONCAT('name:',@name);
END
;;
delimiter ;

-- ----------------------------
-- Procedure structure for proc_calc_patient_avg_cost
-- ----------------------------
DROP PROCEDURE IF EXISTS `proc_calc_patient_avg_cost`;
delimiter ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_calc_patient_avg_cost`( IN p_patientID INT, OUT avg_cost DOUBLE)
BEGIN
  DECLARE t_totalcost FLOAT; #病人总的医疗费用
	DECLARE t_visit_count INT; #病人来看病的次数
	#根据病人编号统计病人总的医疗费用和看病次数并将结果报错到局部变量中
	SELECT SUM(checkItemCost) INTO t_totalcost FROM prescription p1 INNER JOIN checkitem ON p1.checkItemID = checkitem.checkItemID WHERE patientID = p_patientID;
	SELECT COUNT(DISTINCT examDate) INTO t_visit_count FROM prescription WHERE patientID = p_patientID;
	#计算病人每次就医的平均费用
	SET avg_cost = t_totalcost/t_visit_count;
END
;;
delimiter ;

-- ----------------------------
-- Procedure structure for proc_checkitem_insert
-- ----------------------------
DROP PROCEDURE IF EXISTS `proc_checkitem_insert`;
delimiter ;;
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;
    END IF;
	END LOOP loop_label;
END
;;
delimiter ;

-- ----------------------------
-- Procedure structure for proc_checkitem_patients
-- ----------------------------
DROP PROCEDURE IF EXISTS `proc_checkitem_patients`;
delimiter ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_checkitem_patients`( IN item_name VARCHAR(100))
BEGIN
	SELECT patientName FROM patient WHERE patientID IN(
		SELECT patientID FROM prescription WHERE checkItemID IN(
			SELECT checkItemID FROM checkitem WHERE checkItemName LIKE CONCAT('%',item_name,'%')));
END
;;
delimiter ;

-- ----------------------------
-- Procedure structure for proc_exam_GetLastExamDateByPatientNameAndDepID
-- ----------------------------
DROP PROCEDURE IF EXISTS `proc_exam_GetLastExamDateByPatientNameAndDepID`;
delimiter ;;
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
;;
delimiter ;

-- ----------------------------
-- Procedure structure for proc_income_calSubsidy
-- ----------------------------
DROP PROCEDURE IF EXISTS `proc_income_calSubsidy`;
delimiter ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_income_calSubsidy`(IN i_patientID INT ,IN i_year VARCHAR(10), OUT o_subsidy FLOAT)
BEGIN
	DECLARE t_totalCost Float;
	DECLARE t_income Float DEFAULT -1;
	SELECT SUM(checkItemCost) INTO t_totalCost FROM prescription p1 INNER JOIN checkitem ON p1.checkItemID = checkitem.checkItemID 
WHERE patientID = 1 AND examDate >= CONCAT(i_year,'-01-01') AND examDate <= CONCAT(i_year,'-12-31');
	SELECT income INTO t_income FROM subsidy WHERE patientID = i_patientID;
#根据规则计算返还金额
	CASE 
		WHEN t_income >=0 AND t_income < 5000 THEN SET o_subsidy = t_totalCost * 0.2;
		WHEN t_income < 1000 THEN SET o_subsidy = t_totalCost * 0.15;
		WHEN t_income < 30000 THEN SET o_subsidy = t_totalCost * 0.05;
		WHEN t_income >= 30000 OR t_income < 0 THEN SET o_subsidy = 0;
	END CASE;
END
;;
delimiter ;

-- ----------------------------
-- Procedure structure for proc_patient_countPatient
-- ----------------------------
DROP PROCEDURE IF EXISTS `proc_patient_countPatient`;
delimiter ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_patient_countPatient`()
    SQL SECURITY INVOKER
BEGIN            
    SELECT COUNT(*) FROM patient;
END
;;
delimiter ;

-- ----------------------------
-- Procedure structure for proc_patient_countPatient2
-- ----------------------------
DROP PROCEDURE IF EXISTS `proc_patient_countPatient2`;
delimiter ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_patient_countPatient2`(OUT patientNum INT)
BEGIN
SELECT COUNT(*) INTO patientNum FROM patient;
END
;;
delimiter ;

SET FOREIGN_KEY_CHECKS = 1;

10.2 BCSP 配套数据库mySchool

#先确定数据库是否存在
DROP DATABASE  IF EXISTS myschool;

CREATE DATABASE myschool; #创建数据库
SHOW DATABASES;  #查看数据库

USE myschool; #选择已存在的数据库

#先确定表是否存在 
DROP TABLE IF EXISTS student; 
#创建学生表
CREATE TABLE student(
	studentNo INT(4) NOT NULL COMMENT '学号',
	loginPwd VARCHAR(20) NOT NULL COMMENT '密码',
	studentName VARCHAR(50) NOT NULL COMMENT '学生姓名',
	sex CHAR(2) DEFAULT '男' NOT NULL  COMMENT '性别',
	gradeId INT(4)  UNSIGNED COMMENT '年级编号',
	phone VARCHAR(50)  COMMENT '联系电话',
	address VARCHAR(255) DEFAULT '地址不详'  COMMENT '地址',
	bornDate DATETIME  COMMENT '出生时间',
	email VARCHAR(50) COMMENT '邮件账号',
	identityCard VARCHAR(18)  UNIQUE COMMENT'身份证号码',
	PRIMARY KEY(studentNo)	
)COMMENT '学生表';


#如果subject表已存在,则删除
DROP TABLE IF EXISTS subject;#创建课程表
CREATE TABLE subject(
    subjectNo INT(4) NOT NULL AUTO_INCREMENT COMMENT '课程编号',
    subjectName VARCHAR(50) COMMENT '课程名称',
    classHour INT(4) COMMENT '学时',
    gradeID INT(4) COMMENT '年级编号' ,  
    PRIMARY KEY (subjectNo)
)COMMENT '课程表';

#如果result表已存在,则删除
DROP TABLE IF EXISTS result;
#创建result表
CREATE TABLE result(
    studentNo INT(4) NOT NULL COMMENT '学号',
    subjectNo INT(4) NOT NULL COMMENT '课程编号',
    examDate DATETIME NOT NULL  COMMENT '考试日期',
    studentResult INT(4) COMMENT '考试成绩' NOT NULL
)COMMENT '成绩表';

#如果grade表已存在,则删除
DROP TABLE IF EXISTS grade; 
#创建年级表
CREATE TABLE grade (
	gradeID INT(4) NOT NULL AUTO_INCREMENT COMMENT '年级编号',	
	gradeName VARCHAR(50) NOT NULL COMMENT '年级名称'	,
	PRIMARY KEY (gradeID)
)COMMENT '年级表';

SHOW TABLES;

添加数据

#为年级表添加数据
INSERT INTO grade(gradeID, gradeName) VALUES (1, 'Y1');
INSERT INTO grade(gradeID, gradeName) VALUES (2, 'Y2');
INSERT INTO grade(gradeID, gradeName) VALUES (3, 'Y3');


#为科目表添加数据
INSERT INTO subject(subjectName,classHour,gradeId) VALUES('Logic Java',220,1),('HTML',160,1),('Java OOP',230,2);

/*学生表数据*/

insert into student (studentNo, loginPwd, studentName, sex, gradeId, phone, address, bornDate, email, identityCard) values('10000','012345','郭靖','男','1','13645667783','天津市河西区','1990-09-08 00:00:00',NULL,NULL);
insert into student (studentNo, loginPwd, studentName, sex, gradeId, phone, address, bornDate, email, identityCard) values('10001','123456','李文才','男','1','13645667890','地址不详','1994-04-12 00:00:00',NULL,NULL);
insert into student (studentNo, loginPwd, studentName, sex, gradeId, phone, address, bornDate, email, identityCard) values('10002','234567','李斯文','男','1','13645556793','河南洛阳','1993-07-23 00:00:00',NULL,NULL);
insert into student (studentNo, loginPwd, studentName, sex, gradeId, phone, address, bornDate, email, identityCard) values('10003','345678','张萍','女','1','13642345112','地址不详','1995-06-10 00:00:00',NULL,NULL);
insert into student (studentNo, loginPwd, studentName, sex, gradeId, phone, address, bornDate, email, identityCard) values('10004','456789','韩秋洁','女','1','13812344566','北京市海淀区','1995-07-15 00:00:00',NULL,NULL);
insert into student (studentNo, loginPwd, studentName, sex, gradeId, phone, address, bornDate, email, identityCard) values('10005','567890','张秋丽','女','1','13567893246','北京市东城区','1994-01-17 00:00:00',NULL,NULL);
insert into student (studentNo, loginPwd, studentName, sex, gradeId, phone, address, bornDate, email, identityCard) values('10006','678901','肖梅','女','1','13563456721','河北省石家庄市','1991-02-17 00:00:00',NULL,NULL);
insert into student (studentNo, loginPwd, studentName, sex, gradeId, phone, address, bornDate, email, identityCard) values('10007','789012','秦洋','男','1','13056434411','上海市卢湾区','1992-04-18 00:00:00',NULL,NULL);
insert into student (studentNo, loginPwd, studentName, sex, gradeId, phone, address, bornDate, email, identityCard) values('10008','890123','何睛睛','女','1','13053445221','广州市天河区','1997-07-23 00:00:00',NULL,NULL);
insert into student (studentNo, loginPwd, studentName, sex, gradeId, phone, address, bornDate, email, identityCard) values('20000','901234','王宝宝','男','2','15076552323','地址不详','1996-06-05 00:00:00',NULL,NULL);
insert into student (studentNo, loginPwd, studentName, sex, gradeId, phone, address, bornDate, email, identityCard) values('20010','012345','何小华','女','2','13318877954','地址不详','1995-09-10 00:00:00',NULL,NULL);
insert into student (studentNo, loginPwd, studentName, sex, gradeId, phone, address, bornDate, email, identityCard) values('30011','098765','陈志强','男','3','13689965430','地址不详','1994-09-27 00:00:00',NULL,NULL);
insert into student (studentNo, loginPwd, studentName, sex, gradeId, phone, address, bornDate, email, identityCard) values('30012','987654','李露露','女','3','13685678854','地址不详','1992-09-27 00:00:00',NULL,NULL);


/*成绩表数据*/
insert into result (studentNo, subjectNo, examDate, studentResult) values('10000','1','2019-02-17 00:00:00','71');
insert into result (studentNo, subjectNo, examDate, studentResult) values('10001','1','2019-02-17 00:00:00','46');
insert into result (studentNo, subjectNo, examDate, studentResult) values('10002','1','2019-02-17 00:00:00','83');
insert into result (studentNo, subjectNo, examDate, studentResult) values('10003','1','2019-02-17 00:00:00','60');
insert into result (studentNo, subjectNo, examDate, studentResult) values('10004','1','2019-02-17 00:00:00','60');
insert into result (studentNo, subjectNo, examDate, studentResult) values('10005','1','2019-02-17 00:00:00','95');
insert into result (studentNo, subjectNo, examDate, studentResult) values('10006','1','2019-02-17 00:00:00','93');
insert into result (studentNo, subjectNo, examDate, studentResult) values('10007','1','2019-02-17 00:00:00','23');


select * from grade;
select * from subject;
select * from student;
select * from result;

8.2 连接数据库配置文件database.properties

driver=com.mysql.jdbc.Driver
url=jdbc\:mysql\://localhost\:3306/hospital?useUnicode\=true&characterEncoding\=UTF-8&serverTimezone\=Asia/Shanghai
user=root
password=root

10.3 JDBC 工具类BaseDao.java

package xuanzishare.dao;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class BaseDao {
    public static String driver;
    // 数据库驱动

    public static String url;
    // url

    public static String user;
    // 数据库用户名

    public static String password;
    // 数据库密码

    static {
        //静态代码块,在类加载的时候执行
        init();
    }

    public static void init() {
        String configFile = "database.properties";
        //配置文件路径
        //加载配置文件到输入流中
        InputStream is = BaseDao.class.getClassLoader().getResourceAsStream(configFile);
        Properties params = null;
        try {

            params = new Properties();
            //从输入流中读取属性列表
            params.load(is);

        } catch (IOException e) {
            e.printStackTrace();
        }
        //根据指定的获取对应的值
        driver = params.getProperty("driver");
        url = params.getProperty("url");
        user = params.getProperty("user");
        password = params.getProperty("password");
    }

    /**
     * 得到数据库连接
     *
     * @return 数据库连接
     */

    public static Connection getConn() {

        Connection conn = null;
        try {
            Class.forName(driver);
            // 注册驱动
            conn = DriverManager.getConnection(url, user, password);
            // 获得数据库连接
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return conn;
        // 返回连接
    }

    public static void closeAll(Connection conn, PreparedStatement ps, ResultSet rs) {

        /* 如果rs不空,关闭rs */
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        /* 如果pstmt不空,关闭pstmt */
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        /* 如果conn不空,关闭conn */
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }

    public int updateBuffer(String sql, Object[] objects) {
        Connection conn = null;
        PreparedStatement ps = null;
        int i = 0;
        try {
            conn = getConn();
            ps = conn.prepareStatement(sql);
            if (objects != null) {
                for (int j = 0; j < objects.length; j++) {
                    ps.setObject(j + 1, objects[j]);
                }
            }
            System.out.println("\u001B[34mupdateBuffer:" + ps + "\u001B[0m");
            i = ps.executeUpdate();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        closeAll(conn, ps, null);
        return i;
    }

    public double selectAllId(String sql, Object[] objects) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        double allId = 0;
        try {
            conn = getConn();
            ps = conn.prepareStatement(sql);
            if (objects != null) {
                for (int i = 0; i < objects.length; i++) {
                    ps.setObject(i + 1, objects[i]);
                }
            }
            System.out.println("\u001B[34mselectAllId:" + ps + "\u001B[0m");
            rs = ps.executeQuery();
            allId = 0;
            while (rs.next()) {
                allId = rs.getDouble(1);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        closeAll(conn, ps, rs);
        return allId;
    }
//    只查询数字类型 把值返还回去
}

10.4 MySQL 函数

MySQL 字符串函数

函数描述实例
ASCII(s)返回字符串 s 的第一个字符的 ASCII 码。返回 CustomerName 字段第一个字母的 ASCII 码:SELECT ASCII(CustomerName) AS NumCodeOfFirstChar FROM Customers;
CHAR_LENGTH(s)返回字符串 s 的字符数返回字符串 RUNOOB 的字符数SELECT CHAR_LENGTH("RUNOOB") AS LengthOfString;
CHARACTER_LENGTH(s)返回字符串 s 的字符数,等同于 CHAR_LENGTH(s)返回字符串 RUNOOB 的字符数SELECT CHARACTER_LENGTH("RUNOOB") AS LengthOfString;
CONCAT(s1,s2…sn)字符串 s1,s2 等多个字符串合并为一个字符串合并多个字符串SELECT CONCAT("SQL ", "Runoob ", "Gooogle ", "Facebook") AS ConcatenatedString;
CONCAT_WS(x, s1,s2…sn)同 CONCAT(s1,s2,…) 函数,但是每个字符串之间要加上 x,x 可以是分隔符合并多个字符串,并添加分隔符:SELECT CONCAT_WS("-", "SQL", "Tutorial", "is", "fun!")AS ConcatenatedString;
FIELD(s,s1,s2…)返回第一个字符串 s 在字符串列表(s1,s2…)中的位置返回字符串 c 在列表值中的位置:SELECT FIELD("c", "a", "b", "c", "d", "e");
FIND_IN_SET(s1,s2)返回在字符串s2中与s1匹配的字符串的位置返回字符串 c 在指定字符串中的位置:SELECT FIND_IN_SET("c", "a,b,c,d,e");
FORMAT(x,n)函数可以将数字 x 进行格式化 “#,###.##”, 将 x 保留到小数点后 n 位,最后一位四舍五入。格式化数字 “#,###.##” 形式:SELECT FORMAT(250500.5634, 2); -- 输出 250,500.56
INSERT(s1,x,len,s2)字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串从字符串第一个位置开始的 6 个字符替换为 runoob:SELECT INSERT("google.com", 1, 6, "runoob"); -- 输出:runoob.com
LOCATE(s1,s)从字符串 s 中获取 s1 的开始位置获取 b 在字符串 abc 中的位置:SELECT LOCATE('st','myteststring'); -- 5返回字符串 abc 中 b 的位置:SELECT LOCATE('b', 'abc') -- 2
LCASE(s)将字符串 s 的所有字母变成小写字母字符串 RUNOOB 转换为小写:SELECT LCASE('RUNOOB') -- runoob
LEFT(s,n)返回字符串 s 的前 n 个字符返回字符串 runoob 中的前两个字符:SELECT LEFT('runoob',2) -- ru
LOWER(s)将字符串 s 的所有字母变成小写字母字符串 RUNOOB 转换为小写:SELECT LOWER('RUNOOB') -- runoob
LPAD(s1,len,s2)在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len将字符串 xx 填充到 abc 字符串的开始处:SELECT LPAD('abc',5,'xx') -- xxabc
LTRIM(s)去掉字符串 s 开始处的空格去掉字符串 RUNOOB开始处的空格:SELECT LTRIM(" RUNOOB") AS LeftTrimmedString;-- RUNOOB
MID(s,n,len)从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s,n,len)从字符串 RUNOOB 中的第 2 个位置截取 3个 字符:SELECT MID("RUNOOB", 2, 3) AS ExtractString; -- UNO
POSITION(s1 IN s)从字符串 s 中获取 s1 的开始位置返回字符串 abc 中 b 的位置:SELECT POSITION('b' in 'abc') -- 2
REPEAT(s,n)将字符串 s 重复 n 次将字符串 runoob 重复三次:SELECT REPEAT('runoob',3) -- runoobrunoobrunoob
REPLACE(s,s1,s2)将字符串 s2 替代字符串 s 中的字符串 s1将字符串 abc 中的字符 a 替换为字符 x:SELECT REPLACE('abc','a','x') --xbc
REVERSE(s)将字符串s的顺序反过来将字符串 abc 的顺序反过来:SELECT REVERSE('abc') -- cba
RIGHT(s,n)返回字符串 s 的后 n 个字符返回字符串 runoob 的后两个字符:SELECT RIGHT('runoob',2) -- ob
RPAD(s1,len,s2)在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len将字符串 xx 填充到 abc 字符串的结尾处:SELECT RPAD('abc',5,'xx') -- abcxx
RTRIM(s)去掉字符串 s 结尾处的空格去掉字符串 RUNOOB 的末尾空格:SELECT RTRIM("RUNOOB ") AS RightTrimmedString; -- RUNOOB
SPACE(n)返回 n 个空格返回 10 个空格:SELECT SPACE(10);
STRCMP(s1,s2)比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1<s2 返回 -1比较字符串:SELECT STRCMP("runoob", "runoob"); -- 0
SUBSTR(s, start, length)从字符串 s 的 start 位置截取长度为 length 的子字符串从字符串 RUNOOB 中的第 2 个位置截取 3个 字符:SELECT SUBSTR("RUNOOB", 2, 3) AS ExtractString; -- UNO
SUBSTRING(s, start, length)从字符串 s 的 start 位置截取长度为 length 的子字符串,等同于 SUBSTR(s, start, length)从字符串 RUNOOB 中的第 2 个位置截取 3个 字符:SELECT SUBSTRING("RUNOOB", 2, 3) AS ExtractString; -- UNO
SUBSTRING_INDEX(s, delimiter, number)返回从字符串 s 的第 number 个出现的分隔符 delimiter 之后的子串。 如果 number 是正数,返回第 number 个字符左边的字符串。 如果 number 是负数,返回第(number 的绝对值(从右边数))个字符右边的字符串。SELECT SUBSTRING_INDEX('a*b','*',1) -- a SELECT SUBSTRING_INDEX('a*b','*',-1) -- b SELECT SUBSTRING_INDEX(SUBSTRING_INDEX('a*b*c*d*e','*',3),'*',-1) -- c
TRIM(s)去掉字符串 s 开始和结尾处的空格去掉字符串 RUNOOB 的首尾空格:SELECT TRIM(' RUNOOB ') AS TrimmedString;
UCASE(s)将字符串转换为大写将字符串 runoob 转换为大写:SELECT UCASE("runoob"); -- RUNOOB
UPPER(s)将字符串转换为大写将字符串 runoob 转换为大写:SELECT UPPER("runoob"); -- RUNOOB

MySQL 数字函数

函数名描述实例
ABS(x)返回 x 的绝对值返回 -1 的绝对值:SELECT ABS(-1) -- 返回1
ACOS(x)求 x 的反余弦值(单位为弧度),x 为一个数值SELECT ACOS(0.25);
ASIN(x)求反正弦值(单位为弧度),x 为一个数值SELECT ASIN(0.25);
ATAN(x)求反正切值(单位为弧度),x 为一个数值SELECT ATAN(2.5);
ATAN2(n, m)求反正切值(单位为弧度)SELECT ATAN2(-0.8, 2);
AVG(expression)返回一个表达式的平均值,expression 是一个字段返回 Products 表中Price 字段的平均值:SELECT AVG(Price) AS AveragePrice FROM Products;
CEIL(x)返回大于或等于 x 的最小整数SELECT CEIL(1.5) -- 返回2
CEILING(x)返回大于或等于 x 的最小整数SELECT CEILING(1.5); -- 返回2
COS(x)求余弦值(参数是弧度)SELECT COS(2);
COT(x)求余切值(参数是弧度)SELECT COT(6);
COUNT(expression)返回查询的记录总数,expression 参数是一个字段或者 * 号返回 Products 表中 products 字段总共有多少条记录:SELECT COUNT(ProductID) AS NumberOfProducts FROM Products;
DEGREES(x)将弧度转换为角度SELECT DEGREES(3.1415926535898) -- 180
n DIV m整除,n 为被除数,m 为除数计算 10 除于 5:SELECT 10 DIV 5; -- 2
EXP(x)返回 e 的 x 次方计算 e 的三次方:SELECT EXP(3) -- 20.085536923188
FLOOR(x)返回小于或等于 x 的最大整数小于或等于 1.5 的整数:SELECT FLOOR(1.5) -- 返回1
GREATEST(expr1, expr2, expr3, …)返回列表中的最大值返回以下数字列表中的最大值:SELECT GREATEST(3, 12, 34, 8, 25); -- 34返回以下字符串列表中的最大值:SELECT GREATEST("Google", "Runoob", "Apple"); -- Runoob
LEAST(expr1, expr2, expr3, …)返回列表中的最小值返回以下数字列表中的最小值:SELECT LEAST(3, 12, 34, 8, 25); -- 3返回以下字符串列表中的最小值:SELECT LEAST("Google", "Runoob", "Apple"); -- Apple
LN返回数字的自然对数,以 e 为底。返回 2 的自然对数:SELECT LN(2); -- 0.6931471805599453
LOG(x) 或 LOG(base, x)返回自然对数(以 e 为底的对数),如果带有 base 参数,则 base 为指定带底数。SELECT LOG(20.085536923188) -- 3 SELECT LOG(2, 4); -- 2
LOG10(x)返回以 10 为底的对数SELECT LOG10(100) -- 2
LOG2(x)返回以 2 为底的对数返回以 2 为底 6 的对数:SELECT LOG2(6); -- 2.584962500721156
MAX(expression)返回字段 expression 中的最大值返回数据表 Products 中字段 Price 的最大值:SELECT MAX(Price) AS LargestPrice FROM Products;
MIN(expression)返回字段 expression 中的最小值返回数据表 Products 中字段 Price 的最小值:SELECT MIN(Price) AS MinPrice FROM Products;
MOD(x,y)返回 x 除以 y 以后的余数5 除于 2 的余数:SELECT MOD(5,2) -- 1
PI()返回圆周率(3.141593)SELECT PI() --3.141593
POW(x,y)返回 x 的 y 次方2 的 3 次方:SELECT POW(2,3) -- 8
POWER(x,y)返回 x 的 y 次方
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值