MySQL快速复习笔记
MySQL数据库基础
(一) 基础定义
-
数据库,英文名称为Database,简称DB。从字面意思来看就是存储数据的仓库;从专业角度解释为存储在计算机磁盘上的有组织、可共享的大量数据的集合。
-
数据库分为关系型数据库和非关系型数据库两大类。
| 名称 | 描述 | 命令 |
|---|---|---|
| 数据定义语言 ( DDL ) | 数据库、数据表的创建、修改和删除 | CREATE、ALTER、DROP |
| 数据操作语言 ( DML ) | 数据的增加、修改和删除 | INSERT、UPDATE、DELETE |
| 数据查询语言 ( DQL ) | 数据的查询 | SELECT |
| 数据控制语言 ( DCL ) | 用户授权、事务的提交和回滚 | GRANT、COMMIT、ROLLBACK |
(二) 数据库操作
以lesson举例
2.1创建
CREATE DATABASE IF NOT EXISTS lesson DEFAULT CHARACTER SET GBK COLLATE
GBK_CHINESE_CI;
创建数据库lesson,并指定字符集为 GBK ,排序规则为 GBK_CHINESE_CI
2.2 修改
ALTER DATABASE lesson CHARACTER SET UTF8 COLLATE UTF8_GENERAL_CI;
修改数据库lesson的字符集为 UTF8 ,排序规则为 UTF8_GENERAL_CI
2.3 删除
DROP DATABASE IF EXISTS lesson;
2.4 查看
SHOW DATABASES;
2.5 使用
USE lesson;
(三) 列类型
在 MySQL 中,常用列类型主要分为数值类型、日期时间类型、字符串类型。
| tinyint | smallint | mediumint | int | bigint | float | double | decimal(m, d) |
|---|---|---|---|---|---|---|---|
| 1字节 | 2字节 | 3字节 | 4字节 | 8字节 | 4字节 | 8字节 | m个字节 |
| DATE | TIME | DATETIME | TIMESTAMP | YEAR |
|---|---|---|---|---|
| YYYY-MM-dd | HH:mm:ss | YY-MM-dd HH:mm:ss | YYYY-MM-dd HH:mm:ss | YYYY |
| char [(M)] | varchar [(M)] | text |
|---|---|---|
| 固定长字符串,检索快但费空间, 0 <= M <= 255 | 可变字符串0 <= M <= 65535 | 文本串 |
| 属性 | 示例 |
|---|---|
| UNSIGNED | INT(4) UNSIGNED,表示只能为4位大于等于0的整数 |
| ZEROFILL | INT(4) ZEROFILL ,如果给定的值为10,不足的2位由0填充,最终值为0010 |
| NOT NULL | VARCHAR (20) NOT NULL,表示该列数据不能为空值 |
| DEFAULT | INT(4) DEFAULT 0,表示该列不赋值时默认为0 |
| AUTO_INCREMENT | INT(11) AUTO_INCREMENT NOT NULLPRIMARY KEY。第一次为该列中插入值时为1,第二次为2 |
(四)数据表操作
4.1 类型
MySQL 中的数据表类型有许多,如 MyISAM 、 InnoDB 、 HEAP 、 BOB 、 CSV 等。其中最常用的就是MyISAM 和 InnoDB
InnoDB支持事务处理,数据行锁定,外键约束,不支持外键约束,MyISAM相反
InnoDB表空间大小较大,约为MyISAM2倍
事务:涉及的所有操作是一个整体,要么都执行,要么都不执行。
数据行锁定:一行数据,当一个用户在修改该数据时,可以直接将该条数据锁定。
如何选择
当涉及的业务操作以查询居多,修改和删除较少时,可以使用MyISAM。当涉及的业务操作经常会有修改和删除操作时,使用InnoDB。
4.2创建
CREATE TABLE [IF NOT EXISTS] 数据表名称(
字段名1 列类型(长度) [修饰属性] [键/索引] [注释],
字段名2 列类型(长度) [修饰属性] [键/索引] [注释],
字段名3 列类型(长度) [修饰属性] [键/索引] [注释],
......
字段名n 列类型(长度) [修饰属性] [键/索引] [注释]
) [ENGINE = 数据表类型][CHARSET=字符集编码] [COMMENT=注释];
示例:创建学生表,表中有字段学号、姓名、性别、年龄和成绩
CREATE TABLE IF NOT EXISTS student(
`number` VARCHAR(30) NOT NULL PRIMARY KEY COMMENT '学号,主键',
name VARCHAR(30) NOT NULL COMMENT '姓名',
sex TINYINT(1) UNSIGNED DEFAULT 0 COMMENT '性别:0-男 1-女 2-其他',
age TINYINT(3) UNSIGNED DEFAULT 0 COMMENT '年龄',
score DOUBLE(5, 2) UNSIGNED COMMENT '成绩'
)ENGINE=InnoDB CHARSET=UTF8 COMMENT='学生表';
4.4 修改
- 修改表名
ALTER TABLE 表名 RENAME AS 新表名;
- 增加字段
ALTER TABLE 表名 ADD 字段名 列类型(长度) [修饰属性] [键/索引] [注释];
- 查看表结构
DESC 表名; -- 查看表结构
- 修改字段
-- MODIFY 只能修改字段的修饰属性
ALTER TABLE 表名 MODIFY 字段名 列类型(长度) [修饰属性] [键/索引] [注释];
-- CHANGE 可以修改字段的名字以及修饰属性
ALTER TABLE 表名 CHANGE 字段名 新字段名 列类型(长度) [修饰属性] [键/索引] [注释];
示例: 将 stu 表中的 sex 字段的类型设置为 VARCHAR ,长度为2,默认值为'男',注释为 "性别,男,女,其他
ALTER TABLE stu MODIFY sex VARCHAR(2) DEFAULT '男' COMMENT '性别:男,女,其他';
示例:将 stu 表中 phone 字段修改为 mobile ,属性保持不变
ALTER TABLE stu CHANGE phone mobile VARCHAR(11) NOT NULL COMMENT '联系电话';
- 删除字段
ALTER TABLE 表名 DROP 字段名;
- 删除数据表
DROP TABLE [IF EXISTS] 表名;
(五)MySQL数据的增删改查
5.1 DML
DML全称为Data Manipulation Language,表示数据操作语言。主要体现于对表数据的增删改操作,仅包括INSERT、UPDATE和DELEETE语句。
INSERT
-- 需要注意,VALUES后的字段值必须与表名后的字段名一一对应
INSERT INTO 表名(字段名1, 字段名2, ..., 字段名n) VALUES(字段值1, 字段值2, ..., 字段值n);
-- 需要注意,VALUES后的字段值必须与创建表时的字段顺序保持一一对应
INSERT INTO 表名 VALUES(字段值1, 字段值2, ..., 字段值n);
-- 一次性插入多条数据
INSERT INTO 表名(字段名1, 字段名2, ..., 字段名n) VALUES(字段值1, 字段值2, ..., 字段值n),(字段值1, 字段值2, ..., 字段值n), ... , (字段值1, 字段值2, ..., 字段值n);
INSERT INTO 表名 VALUES(字段值1, 字段值2, ..., 字段值n), (字段值1, 字段值2, ..., 字段值n), ..., (字段值1, 字段值2, ..., 字段值n);
eg:
INSERT INTO course(`number`, name, score, `time`) VALUES (1, 'Java基础', 4, 40);
INSERT INTO course VALUES (2, '数据库', 3, 20);
INSERT INTO course(`number`, score, name, `time`) VALUES (3, 5, 'Jsp', 40);
INSERT INTO course(`number`, name, score, `time`) VALUES (4, 'Spring', 4, 5),(5,'Spring Mvc', 2, 5);
INSERT INTO course VALUES (6, 'SSM', 2, 3), (7, 'Spring Boot', 2, 2);
UPDATE
UPDATE 表名 SET 字段名1=字段值1[,字段名2=字段值2, ..., 字段名n=字段值n] [WHERE 修改条件]
在Java中,条件的表示通常都是使用关系运算符来表示,在SQL语句中也是一样,使用 >, <, >=, <=, != 来表示。
在Java中,条件之间的衔接通常都是使用逻辑运算符来表示,在SQL语句中也是一样,但通常使用AND来表示逻辑与(&&),使用OR来表示逻辑或(||)
WHERE time > 20 && time < 40; <=> WHERE time > 20 and time <40;
-- 将数据库的学分更改为4,学时更改为15
UPDATE course SET score=4, `time`=15 WHERE name='数据库';
DELETE
DELETE FROM 表名 [WHERE 删除条件];
-- 删除课程表中课程编号为1的数据
DELETE FROM course WHERE `number`=1;
TRUNCATE
-- 清空表中数据
TRUNCATE [TABLE] 表名;
-- 清空课程表数据
TRUNCATE course;
DELETE与TRUNCATE区别
- DELETE语句根据条件删除表中数据,而TRUNCATE语句则是将表中数据全部清空;如果DELETE语句要删除表中所有数据,那么在效率上要低。
- 如果表中有自增长列,TRUNCATE语句会重置自增长的计数器,但DELETE语句不会。
- TRUNCATE语句执行后,数据无法恢复,而DELETE语句执行后,可以使用事务回滚进行恢复。
5.2 DQL
DQL全称是Data Query Language,表示数据查询语言。体现在数据的查询操作上,因此,DQL仅包括SELECT语句。
SELECT
SELECT ALL/DISTINCT * | 字段名1 AS 别名1[,字段名1 AS 别名1, ..., 字段名n AS 别名n]FROM表名 WHERE 查询条件
-- 从课程表中查询课程编号小于5的课程名称
SELECT name FROM course WHERE `number`<5;
-- 从课程表中查询课程名称为"Java基础"的学分和学时
SELECT score, `time` FROM course WHERE name='Java基础';
ALL表示查询所有满足条件的记录,可以省略;DISTINCT表示去掉查询结果中重复的记录
AS可以给数据列、数据表取一个别名
5.3比较操作符
| IS NULL | IS NOT NULL | BETWEEN...AND | LIKE'%匹配内容% | IN(值1,值2,..., 值n) |
|---|---|---|---|---|
| 如果字段的值为NULL,则条件满足 | 如果字段的值不为NULL,则条件满足 | 如果字段的值在最小值与最大值之间(能够取到最小值和最大值),则条件满足 | 如果字段值包含有匹配内容,则条件满足 | 如果字段值在值1,值2, ...,值n中,则条件满足 |
eg:
-- 从课程表查询课程名为NULL的课程信息
SELECT * FROM course WHERE name IS NULL;
-- 从课程表查询课程名不为NULL的课程信息
SELECT * FROM course WHERE name IS NOT NULL;
-- 从课程表查询学分在2~4之间的课程信息
SELECT * FROM course WHERE score BETWEEN 2 AND 4;
-- 从课程表查询课程名包含"V"的课程信息
SELECT * FROM course WHERE name LIKE '%v%';
-- 从课程表查询课程名以"J"开头的课程信息
SELECT * FROM course WHERE name LIKE 'J%';
-- 从课程表查询课程名以"p"结尾的课程信息
SELECT * FROM course WHERE name LIKE '%p';
-- 从课程表查询课程编号为1,3,5的课程信息
SELECT * FROM course WHERE `number` IN (1, 3, 5);
5.4 分组
数据表准备:新建学生表student,包含字段学号(no),类型为长整数,长度为20,是主键,自增长,非空;姓名(name),类型为字符串,长度为20,非空;性别(sex),类型为字符串,长度为2,默认值为"男";年龄(age),类型为整数,长度为3,默认值为0;成绩(score),类型为浮点数,长度为5,小数点后面保留2位有效数字
DROP TABLE IF EXISTS student;
CREATE TABLE student(
no BIGINT(20) AUTO_INCREMENT NOT NULL PRIMARY KEY COMMENT '学号,主键',
name VARCHAR(20) NOT NULL COMMENT '姓名',
sex VARCHAR(2) DEFAULT '男' COMMENT '性别',
age INT(3) DEFAULT 0 COMMENT '年龄',
score DOUBLE(5, 2) COMMENT '成绩'
)ENGINE=InnoDB CHARSET=UTF8 COMMENT='学生表';
分组查询
SELECT ALL/DISTINCT * | 字段名1 AS 别名1[,字段名1 AS 别名1, ..., 字段名n AS 别名n]FROM 表名 WHERE 查询条件 GROUP BY 字段名1,字段名2,..., 字段名n
-- 从学生表查询成绩在80分以上的学生信息并按性别分组
SELECT * FROM student WHERE score>80 GROUP BY sex;
-- 从学生表查询成绩在60~80之间的学生信息并按性别和年龄分组
SELECT * FROM student WHERE score BETWEEN 60 AND 80 GROUP BY sex, age;
聚合函数
-
COUNT() :统计满足条件的数据总条数
-- 从学生表查询成绩在80分以上的学生人数 SELECT COUNT(*) total FROM student WHERE score>80; -
SUM():只能用于数值类型的字段或者表达式,计算该满足条件的字段值的总和
-- 从学生表查询不及格的学生人数和总成绩 SELECT COUNT(*) totalCount, SUM(score) totalScore FROM student WHERE score<60; -
AVG():只能用于数值类型的字段或者表达式,计算该满足条件的字段值的平均值
-
MAX():只能用于数值类型的字段或者表达式,计算该满足条件的字段值的最大值
-
MIN():只能用于数值类型的字段或者表达式,计算该满足条件的字段值的最小值
eg:
SELECT sex, AVG(score) avgScore FROM student GROUP BY sex;
SELECT MAX(age) FROM student;
SELECT MIN(score) FROM student;
5.5分组查询结果筛选
SELECT ALL/DISTINCT * | 字段名1 AS 别名1[,字段名1 AS 别名1, ..., 字段名n AS 别名n]FROM 表名 WHERE 查询条件 GROUP BY 字段名1,字段名2,..., 字段名n HAVING 筛选条件
eg:从学生表查询年龄在20~30之间的学生信息并按性别分组,找出组内平均分在74分以上的组
SELECT * FROM student WHERE age BETWEEN 20 AND 30 GROUP BY sex HAVING avg(score)>74;
5.6排序
ORDER BY 必须位于WHERE 条件之后。
SELECT ALL/DISTINCT * | 字段名1 AS 别名1[,字段名1 AS 别名1,..., 字段名n AS 别名n]
FROM 表名 WHERE 查询条件 ORDER BY 字段名1 ASC|DESC,字段名2 ASC|DESC,..., 字段名n
ASC|DESC
eg:从学生表查询年龄在18~30岁之间的学生信息并按成绩从高到低列,如果成绩相同,则按年龄从小到大排列
SELECT * FROM student WHERE age BETWEEN 18 AND 30 ORDER BY score DESC, age ASC;
5.7分页
SELECT ALL/DISTINCT * | 字段名1 AS 别名1[,字段名1 AS 别名1, ..., 字段名n AS 别名n]FROM 表名 WHERE 查询条件 LIMIT 偏移量, 查询条数
LIMIT第一个参数表示偏移量,也就是跳过的行数。
第二个参数表示查询返回的最大行数,可能没有给定的数量那么多行。
eg:从学生表分页查询成绩及格的学生信息,每页显示3条,查询第2页学生信息
SELECT * FROM student WHERE score>=60 LIMIT 3, 3;
注:如果一个查询中包含分组、排序和分页,那么它们之间必须按照分组->排序->分页的先后顺序排列。
(六)常用函数
6.1数学:

6.2字符串:

6.3日期:

6.4条件判断函数
if函数:IF(条件, 表达式1,表达式2) IFNULL(字段, 表达式)
eg:将学生成绩展示为及格和不及格
eg:将未参加考试的学生成绩展示为缺考
SELECT id,stu_name,course, IF(score>=60, '及格','不及格') score FROM score;
SELECT id,stu_name,course, IFNULL(score, '缺考') score FROM score;
CASE...THEN 语句(相当于if...else函数)
CASE WHEN 条件1 THEN 表达式1 [WHEN 条件2 THEN 表达式2 ...] ELSE 表达式n END
eg:查询Java成绩
SELECT (CASE WHEN (course = 'Java') THEN score ELSE 0 END) Java FROM score;
CASE ... WHEN语句(相当于switch函数)
CASE 表达式 WHEN 值1 THEN 表达式1 [WHEN 值2 THEN 表达式2 ...] ELSE 表达式n END
eg:查询Java成绩
SELECT (CASE course WHEN 'Java' THEN score ELSE 0 END) Java FROM score;
6.5其他函数
数字格式化函数
FORMAT(X,D),将数字X格式化,将X保留到小数点后D位,截断时要进行四舍五入。
系统信息函数

(七)多表查询
7.1表的关系
表是用来描述实体信息,数据库通过主外键关联关系来体现表与表之间的关联关系:在一张表中添加一个字段引用另一张表的主键,该字段称为外键,这种关联关系称为主外键关联关系。
示例:创建班级表cls和学生表student,student表中的cls_number字段与cls表的number主键相关联
CREATE TABLE cls(
number INT(11) AUTO_INCREMENT NOT NULL PRIMARY KEY COMMENT '班级编号,主键',
name VARCHAR(20) NOT NULL COMMENT '班级名称',
grade VARCHAR(20) NOT NULL COMMENT '年级'
)ENGINE=InnoDB CHARSET=UTF8 COMMENT='班级表';
CREATE TABLE student(
number BIGINT(20) AUTO_INCREMENT NOT NULL PRIMARY KEY COMMENT '学号,主键',
name VARCHAR(20) NOT NULL COMMENT '姓名',
sex VARCHAR(2) DEFAULT '男' COMMENT '性别',
age TINYINT(3) DEFAULT 0 COMMENT '年龄',
cls_number INT(11) NOT NULL COMMENT '所属班级',
FOREIGN KEY(cls_number) REFERENCES cls(number)
)ENGINE=InnoDB CHARSET=UTF8 COMMENT='学生表';
7.2 约束
-- 主键约束:保证数据的唯一性
ALTER TABLE 表名 ADD PRIMARY KEY(字段名1, 字段名2, ..., 字段名n);
ALTER TABLE 表名 DROP PRIMARY KEY;
-- 外键约束
ALTER TABLE 表名1 ADD CONSTRAINT 外键名称 FOREIGN KEY(表名1的字段名) REFERENCES 表名2(表名2的字段名);
ALTER TABLE 表名 DROP FOREIGN KEY 外键名称;
-- 唯一约束
ALTER TABLE 表名 ADD CONSTRAINT 约束名称 UNIQUE(字段名1, 字段名2, ..., 字段名n);
ALTER TABLE 表名 DROP KEY 约束名称;
-- 非空约束
ALTER TABLE 表名 MODIFY 字段名 列类型 NOT NULL;
ALTER TABLE 表名 MODIFY 字段名 列类型 NULL;
-- 默认值约束
ALTER TABLE 表名 ALTER 字段名 SET DEFAULT 默认值;
ALTER TABLE 表名 ALTER 字段名 DROP DEFAULT;
-- 自增约束
ALTER TABLE 表名 MODIFY 字段名 列类型 AUTO_INCREMENT;
ALTER TABLE 表名 MODIFY 字段名 列类型;
7.3 索引
索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的存储结构,可以类比书籍的目录来理解。
作用:保证数据的准确性、提高检索速度、提高系统性能
类型
| 类型 | 说明 |
|---|---|
| 唯一索引(UNIQUE) | 不可以出现相同的值,可以有NULL值 |
| 普通索引(INDEX) | 允许出现相同的索引内容 |
| 主键索引(PRIMARY KEY) | 不允许出现相同的值 |
| 全文索引(FULLTEXT) | 可以针对值中的某个单词,但效率不高 |
| 组合索引 | 将多个字段建到一个索引里,列值的组合必须唯一 |
创建、查看、删除
-- 创建索引
ALTER TABLE 表名 ADD INDEX 索引名称 (字段名1, 字段名2, ..., 字段名n);
-- 创建全文索引
ALTER TABLE 表名 ADD FULLTEXT 索引名称 (字段名1, 字段名2, ..., 字段名n);
-- 查看索引
SHOW INDEX FROM 表名;
-- 删除索引
ALTER TABLE 表名 DROP INDEX 索引名称;
使用注意事项
- 索引提高查询速度,但会降低更新表的速度(INSERT、UPDATE、DELETE时还要维护索引文件)
- 索引文件占用磁盘空间,索引创建过多反而降低数据库性能,索引要建立在经常进行查询操作的字段上
- 不要在列上进行运算(包括函数运算),这会忽略索引的使用
- like '%查询内容%'不会使用索引,而like '查询内容%'可以使用索引
- 避免使用IS NULL、NOT IN、<>、!=、OR操作,这些操作会忽略索引而进行全表扫描
7.4 连接查询
笛卡尔积:表示两个集合相乘的结果。每一张表可以看做是一个数据的集合,多表关联时,这些表中的数据就会形成笛卡尔积。
内连接:相当于在笛卡尔积的基础上加上了连接条件。当没有连接条件时,内连接上升为笛卡尔积。
SELECT 字段名1, 字段名2, ..., 字段名n FROM 表1 [INNER] JOIN 表2 [ON 连接条件];
SELECT 字段名1, 字段名2, ..., 字段名n FROM 表1, 表2 [WHERE 关联条件 AND 查询条件];
eg:
-- 两种写法等价,统计学生表与成绩表关联的记录数
SELECT COUNT(*) FROM stu INNER JOIN score ON stu.id=score.stu_id;
SELECT COUNT(*) FROM stu, score WHERE stu.id=score.stu_id;
外连接:涉及到主表和从表,要查询的信息主要来自于哪张表,哪张表就是主表。外连接查询的结果为主表中所有的记录,如果从表中有匹配的则显示匹配的值,没有匹配的则显示null。
外连接查询的结果 = 内连接的结果 + 主表中有的而内连接结果中没有的记录
- 左外连接:使用LEFT JOIN关键字,LEFT JOIN左边的是主表
- 右外连接:使用RIGHT JOIN关键字,RIGHT JOIN右边的是主表
SELECT 字段名1, 字段名2, ..., 字段名n FROM 主表 LEFT JOIN 从表 [ON 连接条件];
SELECT 字段名1, 字段名2, ..., 字段名n FROM 从表 RIGHT JOIN 主表 [ON 连接条件];
eg:
-- 查询没有成绩的学生信息
SELECT * FROM stu a LEFT JOIN score b ON a.id=b.stu_id WHERE score IS NULL;
7.5 子查询
子查询就是嵌套在其他查询中的查询。子查询出现的位置只有3种情况:
| 位置 | 执行时机 |
|---|---|
| SELECT...FROM之间 | 查询结果出来之后执行 |
| FROM...WHERE之间 | 一开始就先执行 |
| WHERE之后 | 筛选数据时执行 |
eg:
-- 在SELECT...FROM之间:查询stu表所有学生信息,并将性别按男、女、其他展示
SELECT
id,
`name`,
(SELECT text FROM dict WHERE type='sex' AND value=sex) sex,
birthday,
class
FROM stu;
-- 在FROM...WHERE之间:查询年龄与Java成绩都与汤辰宇的年龄与Java成绩相同的学生信息
SELECT c.*, d.* FROM stu c
INNER JOIN score d ON c.id=d.stu_id
INNER JOIN
(SELECT
TIMESTAMPDIFF(YEAR, a.birthday, NOW()) age,
b.score
FROM stu a INNER JOIN score b ON a.id=b.stu_id
WHERE a.name='汤辰宇' AND b.course='Java') e
ON TIMESTAMPDIFF(YEAR, c.birthday, NOW())=e.age AND d.score=e.score
WHERE d.course='Java';
-- 在WHERE之后:查询Java成绩最高的学生信息
SELECT a.*, b.* FROM stu a INNER JOIN score b ON a.id=b.stu_id
WHERE b.score=(SELECT MAX(score) FROM score WHERE course='Java')
AND b.course='Java';
(八)存储过程、函数、触发器和视图
8.1 变量
变量分为四种类型:局部变量、用户变量、会话变量和全局变量。其中局部变量和用户变量使用较多。
| 类型 | 说明 |
|---|---|
| 全局变量 | 影响服务器整体操作,修改需管理员权限,作用域为服务器的整个生命周期 |
| 会话变量 | 服务器为每个连接的客户端维护的变量,作用域仅限于当前连接 |
| 用户变量 | 不用提前声明,直接用"@变量名"使用,作用域为当前连接 |
| 局部变量 | 只能用在BEGIN/END语句块中,用DECLARE声明 |
-- 全局变量:设置时GLOBAL不能省略
SHOW GLOBAL VARIABLES;
SET GLOBAL sql_warnings = ON;
SELECT @@GLOBAL.sql_warnings;
-- 会话变量:SESSION可省略(默认缺省为SESSION),也可用LOCAL替代
SHOW SESSION VARIABLES;
SET auto_increment_increment = 3;
SELECT @@auto_increment_increment;
-- 用户变量:SET时可用"="或":="赋值;SELECT时必须用":="赋值
SET @age = 19;
SELECT @age := age FROM stu WHERE `name` = '张华';
SELECT age INTO @age FROM stu WHERE `name` = '张华';
-- 局部变量
DECLARE age INT(3) DEFAULT 0;
SET age = 10;
8.2 存储过程
存储过程是一组为了完成特定功能而存储在数据库中的SQL语句集,一次编译后永久有效。
优点:运行速度快(创建时已解析和优化,执行后保留在内存中可直接调用);减少网络传输(直接在数据库服务器上执行);增强安全性(防止SQL被截获、篡改)
-- 声明分隔符
[DELIMITER $$]
CREATE PROCEDURE 存储过程名称([IN | OUT | INOUT] 参数名1 数据类型, ..., [IN | OUT | INOUT] 参数名n 数据类型)
BEGIN
-- SQL语句集
END[$$]
-- 还原分隔符
[DELIMITER ;]
-- 调用存储过程
CALL 存储过程名(参数1, 参数2, ...);
示例:使用存储过程完成银行转账业务
CREATE PROCEDURE transfer(IN transferFrom BIGINT, IN transferTo BIGINT, IN transferMoney BIGINT(20))
BEGIN
UPDATE account SET balance = balance - transferMoney WHERE account = transferFrom;
UPDATE account SET balance = balance + transferMoney WHERE account = transferTo;
END
CALL transfer(123456, 123457, 2000);
注:若转账账户已将钱转出,而给目标账户增加余额时出现异常或账号输入错误,就会造成数据不一致,此时需要使用事务。
8.3 事务
事务(Transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。
事务的特性(ACID)
| 特性 | 说明 |
|---|---|
| 原子性(Atomicity) | 事务的各元素是一个整体,要么都执行,要么都不执行 |
| 一致性(Consistency) | 事务完成时必须保证所有数据保持一致状态,如转账前后总金额不变 |
| 隔离性(Isolation) | 多个并发事务彼此独立,互不影响 |
| 持久性(Durability) | 已提交事务对数据库的改变不会丢失,即使数据库出现故障 |
示例:使用事务解决银行转账问题
DROP PROCEDURE IF EXISTS transfer;
CREATE PROCEDURE transfer(IN transferFrom BIGINT, IN transferTo BIGINT, IN transferMoney BIGINT(20), OUT result TINYINT(1))
BEGIN
-- 声明SQLEXCEPTION处理器,出现异常时将result更改为0;CONTINUE表示异常后继续执行,EXIT表示直接退出
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET result = 0;
-- 开启事务
START TRANSACTION;
UPDATE account SET balance = balance - transferMoney WHERE account = transferFrom AND balance >= transferMoney;
SET result = ROW_COUNT();
IF result = 1 THEN
UPDATE account SET balance = balance + transferMoney WHERE account = transferTo;
SET result = ROW_COUNT();
END IF;
-- result为1表示所有操作都成功,提交事务;否则回滚,数据恢复到更改之前的状态
IF result = 1 THEN COMMIT;
ELSE ROLLBACK;
END IF;
END
CALL transfer(123456, 123457, 2000, @rs);
SELECT @rs;
8.4 自定义函数
函数是一组为了完成特定功能而存储在数据库中的SQL语句集,一次编译后永久有效。当内置函数不能满足开发需要时,可以自定义函数。
CREATE FUNCTION 函数名称(参数名1 数据类型, ..., 参数名n 数据类型)
RETURNS 数据类型
-- 函数特征:DETERMINISTIC不确定的 | NO SQL没有SQL语句 | READS SQL DATA只读取数据
-- MODIFIES SQL DATA要修改数据 | CONTAINS SQL包含SQL语句
BEGIN
RETURN 结果;
END
-- 调用函数
SELECT 函数名(参数);
eg:
-- 求score表中的成绩最大差值
CREATE FUNCTION getMaxDiff()
RETURNS DOUBLE(5, 2)
DETERMINISTIC
BEGIN
RETURN (SELECT MAX(score) - MIN(score) FROM score);
END
SELECT getMaxDiff();
循环结构
WHILE 循环条件 DO
-- SQL语句集
END WHILE;
REPEAT
-- SQL语句集
UNTIL 循环终止条件 END REPEAT;
标号: LOOP
-- SQL语句集
IF 循环终止条件 THEN LEAVE 标号;
END IF;
END LOOP;
eg:求0~给定整数的累加和
DROP FUNCTION IF EXISTS getTotal;
CREATE FUNCTION getTotal(maxNum INT(11))
RETURNS INT(11)
NO SQL
BEGIN
DECLARE i INT(11) DEFAULT 0;
DECLARE total INT(11) DEFAULT 0;
WHILE i < maxNum DO
SET total = total + i;
SET i = i + 1;
END WHILE;
RETURN total;
END
SELECT getTotal(100);
8.5 触发器
触发器(trigger)是用来保证数据完整性的一种方法,由事件来触发,比如当对一个表进行增删改操作时就会被激活执行。经常用于加强数据的完整性约束和业务规则。
-- 触发时机为BEFORE或者AFTER,触发事件为INSERT、UPDATE或者DELETE
CREATE TRIGGER 触发器名称 {BEFORE|AFTER} {INSERT|UPDATE|DELETE} ON 表名 FOR EACH ROW
BEGIN
-- 执行的SQL操作
END
DROP TRIGGER [IF EXISTS] 触发器名称;
触发器类型
| 触发器类型 | NEW和OLD的使用 |
|---|---|
| INSERT触发器 | NEW表示将要或者已经新增的数据 |
| UPDATE触发器 | OLD表示修改前的数据,NEW表示修改后的数据 |
| DELETE触发器 | OLD表示将要或者已经删除的数据 |
eg:商品表goods和订单表order,订单的生成、取消、购买数量的更新都意味着商品数量的变动
-- 新增订单后,商品数量减少
CREATE TRIGGER addOrder AFTER INSERT ON `order` FOR EACH ROW
BEGIN
UPDATE goods SET number = number - NEW.sale_count WHERE id = NEW.goods_id;
END
-- 取消订单后,商品数量增加
CREATE TRIGGER deleteOrder AFTER DELETE ON `order` FOR EACH ROW
BEGIN
UPDATE goods SET number = number + OLD.sale_count WHERE id = OLD.goods_id;
END
-- 更新订单购买数量后,商品数量对应变动
CREATE TRIGGER updateOrder AFTER UPDATE ON `order` FOR EACH ROW
BEGIN
DECLARE changeNum INT(11) DEFAULT 0;
SET changeNum = NEW.sale_count - OLD.sale_count;
UPDATE goods SET number = number - changeNum WHERE id = OLD.goods_id;
END
8.6 视图
视图是一张虚拟表,本身并不存储数据,当SQL操作视图时所有数据都是从其他表中查出来的。
-- 创建视图
CREATE VIEW 视图名称 AS SELECT 列1[,列2,...] FROM 表名 WHERE 条件;
-- 更新视图
CREATE OR REPLACE VIEW 视图名称 AS SELECT 列1[,列2,...] FROM 表名 WHERE 条件;
-- 删除视图
DROP VIEW IF EXISTS 视图名称;
作用
- 定制用户数据,聚焦特定的数据
- 简化数据操作:进行关联查询时涉及的表可能很多、SQL很长,如果这个动作频繁发生,可以创建视图
- 提高安全性能:过滤掉隐私字段,如用户密码
eg:创建视图过滤掉用户表中的密码字段
CREATE OR REPLACE VIEW userInfo AS
SELECT username, salt, failure_times, last_log_time FROM `user`;
SELECT username, salt FROM userInfo;
注:视图并不能提升查询速度,只是方便了业务开发,但同时也加大了数据库服务器的压力,因此需要合理地使用视图。
(九)数据库设计
9.1 设计数据库
- 实体:软件开发过程中所涉及到的事物,通常是一类数据对象的个体
- 数据库设计:将实体与实体之间的关系进行规划和结构化的过程
- 为什么要设计:设计不当会造成数据冗余、修改复杂、操作数据异常等问题;好的设计可以减少不必要的数据冗余,通过合理的数据规划提高系统的性能
- 设计步骤:收集信息 → 标识实体(实体一般是名词,每个实体只描述一件事情) → 标识实体的详细属性 → 标识实体之间的关系
9.2 ER图
ER = Entity Relational(实体关系),ER图就是实体关系图。
| 图形 | 表示 |
|---|---|
| 矩形 | 实体,一般是名词 |
| 椭圆 | 实体属性,一般是名词 |
| 菱形 | 实体关系,一般是动词 |
eg:学生(学号,姓名,性别,年龄)--属于--班级(班级编号,班级名称)
9.3 数据库模型图
实体关系的描述称为关系模式,关系模式通常使用二维表的形式表示。
eg:学生(学号,姓名,性别,年龄,所属班级)、班级(班级编号,班级名称)
将关系模式转换为数据库模型图的步骤:
- 将各实体转换为对应的表,将各属性转换为各表对应的列
- 标识每个表的主键列
- 在表之间建立主外键,体现实体关系
9.4 数据库三大范式
| 范式 | 要求 |
|---|---|
| 第一范式 | 最基本的范式,确保每列保持原子性,每列不可再分 |
| 第二范式 | 在第一范式的基础上,每张表的属性完全依赖于主键,每张表只描述一件事情 |
| 第三范式 | 在第二范式的基础上,确保每列都直接依赖于主键,不能存在传递依赖 |
eg:地址"四川省成都市"按省、市拆分为两列满足第一范式;学生表中重复的班级信息拆出班级表满足第二范式;学生表中间接依赖主键的专业学费拆出专业表满足第三范式。
注:在实际开发过程中,为了满足性能的需要,数据库的设计可能会打破三大范式的约束。以空间换时间:当数据量越来越大、查询效率下降时,可能会在表中增加冗余字段来提升查询效率。
(十)JDBC
10.1 概念
JDBC(Java Database Connection)是Java数据库连接技术的简称,提供连接数据库的能力。Java在java.sql包中提供了统一的接口,由各大数据库厂商去实现。
10.2 JDBC API
| 接口/类 | 说明 |
|---|---|
| Driver | 数据库厂商提供的JDBC驱动包中必须包含该接口的实现,包含连接数据库的功能 |
| DriverManager | 管理各厂商的JDBC驱动,主要负责获取数据库连接对象Connection |
| Connection | 连接接口,主要提供与数据库的交互功能 |
| Statement | SQL语句执行接口,主要提供执行SQL语句的功能 |
| ResultSet | 查询结果集接口,主要提供查询结果的获取功能 |
// Connection 常用方法
Statement createStatement(); // 创建SQL语句执行对象
PreparedStatement prepareStatement(String sql); // 创建预处理SQL语句执行对象
void setAutoCommit(boolean autoCommit); // 设置该连接上的操作是否自动提交
void commit(); // 提交事务
void rollback(); // 回滚事务
void close(); // 关闭连接
// 事务隔离级别:TRANSACTION_NONE=0 不支持事务,READ_UNCOMMITTED=1 读取未提交,
// READ_COMMITTED=2 读取已提交,REPEATABLE_READ=4 可重复读,SERIALIZABLE=8 串行化
// Statement 常用方法
ResultSet executeQuery(String sql); // 执行查询,得到一个结果集
int executeUpdate(String sql); // 执行更新,得到受影响的行数
void close(); // 关闭SQL语句执行器
// ResultSet 常用方法
boolean next(); // 光标向前移动一行,如果存在数据则返回true,否则返回false
String getString(int columnIndex | String columnName); // 获取指定列的值,还有getInt、getDouble、getObject等
ResultSetMetaData getMetaData(); // 获取结果集元数据:列名称、列数量、列别名等
10.3 JDBC操作步骤
- 引入驱动包:将 mysql-connector-java.jar 引入工程中
- 加载驱动
- 获取连接
- 创建SQL语句执行器
- 执行SQL语句
- 释放资源
// 2.加载驱动(MySQL 5.0为com.mysql.jdbc.Driver,MySQL 8.0如下)
Class.forName("com.mysql.cj.jdbc.Driver");
// 3.获取连接
Connection conn = DriverManager.getConnection(url, username, password);
// 4.创建SQL语句执行器
Statement statement = conn.createStatement();
// 5.执行SQL语句
ResultSet rs = statement.executeQuery(sql); // 查询
int affectedRows = statement.executeUpdate(sql); // 更新
// 6.释放资源
rs.close();
statement.close();
conn.close();
示例:
String url = "jdbc:mysql://localhost:3306/lesson?serverTimezone=Asia/Shanghai";
String username = "root";
String password = "root";
try {
// 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
Connection conn = DriverManager.getConnection(url, username, password);
// 在连接上创建SQL语句执行器
Statement s = conn.createStatement();
// 使用执行器执行查询,得到一个结果集,遍历时通过列名称或列索引获取列的值
ResultSet rs = s.executeQuery("SELECT account,balance,state FROM account");
while (rs.next()) {
String account = rs.getString("account");
double balance = rs.getDouble(2);
int state = rs.getInt("state");
}
// 执行更新时,返回的都是受影响的行数
int affectedRows = s.executeUpdate("UPDATE account SET balance = balance + 1000 WHERE account=123457");
// 释放资源
rs.close();
s.close();
conn.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
10.4 预处理SQL
拼接SQL时,如果用户输入 张华' or 1='1,SQL语句会变为:
SELECT id, name, sex, age FROM stu WHERE name='张华' or 1='1'
查询结果发生改变,这样的情况被称为SQL注入。为防止SQL注入,Java提供了PreparedStatement接口对SQL进行预处理,它是Statement的子接口。使用时SQL语句中的参数一律使用?号占位,然后通过setXxx()方法对占位的?号进行替换,将参数作为一个整体进行查询。
String sql = "SELECT id, name, sex, age FROM stu WHERE name=?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1, name); // 还有setInt、setLong、setDouble、setObject等
ResultSet rs = ps.executeQuery();
10.5 反射
JVM加载类的过程相当于对class文件内容进行解析,解析时需要找到类的共有特征并存储起来,即java.lang.Class对象(包含类名、包名、构造方法、字段、方法、接口、父类等)。通过Class对象反向推到类的内容再进行操作,这个过程就称为反射。
获取Class对象
Class<Student> c1 = Student.class; // 类名.class
Class<? extends Student> c2 = stu.getClass(); // 对象名.getClass()
Class<? super Student> c3 = c1.getSuperclass(); // 获取父类
Class c4 = Class.forName("com.cyx.jdbc.reflection.Student"); // 类的全限定名=包名+"."+类名
Class c5 = Integer.TYPE; // 包装类.TYPE
Class类常用方法
Field[] getFields(); // 获取类中使用public修饰的字段
Field[] getDeclaredFields(); // 获取类中定义的所有字段
Field getDeclaredField(String name); // 通过给定的字段名获取类中定义的字段
Method[] getDeclaredMethods(); // 获取类中定义的所有方法
Method getDeclaredMethod(String name, Class<?>... parameterTypes); // 按方法名和参数类型获取方法
Constructor<?>[] getDeclaredConstructors(); // 获取类中定义的所有构造方法
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes);
String getName(); // 获取类的全限定名
Package getPackage(); // 获取类所在的包
T newInstance(); // 通过类的无参构造创建一个实例
// Field、Method、Constructor的共同父类AccessibleObject:
void setAccessible(boolean flag); // 修改访问权限
示例:通过反射创建对象、为私有字段赋值、调用getter/setter
Class<Student> clazz = Student.class;
Constructor<? extends Student> c = clazz.getDeclaredConstructor();
c.setAccessible(true); // Student类中的无参构造方法是私有的,需要先修改访问权限
Student s = c.newInstance();
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(s, "李四"); // 给指定对象中的该字段赋值
// 按字段名拼接getter方法名:get name => "get" + N + ame
String fieldName = nameField.getName();
String methodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
Method m = clazz.getDeclaredMethod(methodName);
m.setAccessible(true);
String name = (String) m.invoke(s);
10.6 反射与数据库
数据库查询出的每一条数据基本上都会封装为一个对象,数据库中的每一个字段值都会存储在对象相应的属性中。如果查询结果的每一个字段都与对象中的属性名保持一致,就可以使用反射完成万能查询(可用列别名对齐,如 agent_id agentId)。
JdbcUtil工具类核心代码:
public class JdbcUtil {
private static final String url = "jdbc:mysql://localhost:3306/lesson?serverTimezone=Asia/Shanghai";
private static final String username = "root";
private static final String password = "root";
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver"); // 静态代码块中加载驱动
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// 万能更新:INSERT、UPDATE、DELETE
public static int update(String sql, Object...params){
Connection conn = null;
PreparedStatement ps = null;
int result = 0;
try {
conn = DriverManager.getConnection(url, username, password);
ps = createPreparedStatement(conn, sql, params);
result = ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(ps, conn);
}
return result;
}
// 为占位符设置参数
private static PreparedStatement createPreparedStatement(Connection conn, String sql, Object...params) throws SQLException {
PreparedStatement ps = conn.prepareStatement(sql);
if(params != null && params.length > 0){
for(int i = 0; i < params.length; i++){
ps.setObject(i + 1, params[i]);
}
}
return ps;
}
// 万能查询:通过反射将结果集封装为对象列表
public static <T> List<T> query(String sql, Class<T> clazz, Object...params){
List<T> dataList = new ArrayList<>();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection(url, username, password);
ps = createPreparedStatement(conn, sql, params);
rs = ps.executeQuery();
while (rs.next()){
T t = createInstance(clazz, rs);
dataList.add(t);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
close(rs, ps, conn);
}
return dataList;
}
// 反射创建对象:调用setter为字段赋值
private static <T> T createInstance(Class<T> clazz, ResultSet rs) throws Exception{
Constructor<T> c = clazz.getConstructor(); // 获取无参构造
T t = c.newInstance(); // 创建对象
Field[] fields = clazz.getDeclaredFields(); // 获取类中定义的字段
for(Field field : fields){
String fieldName = field.getName();
// setId => "set" + I + d
String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
Method m = clazz.getDeclaredMethod(methodName, field.getType());
try {
Object value = rs.getObject(fieldName, field.getType());
m.invoke(t, value);
} catch (Exception e){}
}
return t;
}
// 关闭连接、执行器、结果集
private static void close(AutoCloseable... closeables){
if(closeables != null && closeables.length > 0){
for(AutoCloseable ac : closeables){
if(ac != null){
try {
ac.close();
} catch (Exception e) {}
}
}
}
}
}
(十一)分层开发
11.1 三层结构
生活中的分层也可以应用于软件开发中,软件开发分层主要分为三层:
| 层次 | 包名 | 职责 |
|---|---|---|
| 界面层,又称控制层(controller) | controller | 与用户进行交互,主要负责数据采集和展示 |
| 业务逻辑层(service) | service | 处理功能模块的业务逻辑,以及界面层和数据访问层的数据流转 |
| 数据访问层(data access object) | dao | 只负责与数据库进行交互 |
11.2 分层原则
- 封装性原则:每层只向外公开接口,但隐藏了内部实现细节
- 顺序访问原则:下层为上层服务,但下层不能使用上层服务
- 开闭原则:对扩展开放,对修改关闭
11.3 分层的优点
各层专注于自己所做的事情,便于提高开发质量;便于分工协作,提高开发效率;便于程序扩展;便于代码复用;易于维护。
11.4 分层开发案例:用户注册与登录
// 包结构
// com.cyx.layer
// ├── controller UserController(控制层,调用业务层)
// ├── service UserService / impl.UserServiceImpl(业务层,调用数据访问层)
// ├── dao UserDao / impl.UserDaoImpl(数据访问层,只与数据库交互)
// ├── model User(实体类)
// └── util JdbcUtil、MD5(工具类)
// 控制层:调用业务层完成业务处理
public class UserController {
private UserService userService = new UserServiceImpl();
public String register(String username, String password){
return userService.register(username, password);
}
public String login(String username, String password){
return userService.login(username, password);
}
}
// 业务层:处理业务逻辑,需要数据时调用数据访问层
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
public String register(String username, String password) {
String salt = MD5.randomStr(30);
String encrypt = MD5.encrypt(password, salt); // 密码加盐加密
int affectedRows = userDao.saveUser(username, encrypt, salt);
return affectedRows == 1 ? "注册成功" : "注册失败";
}
public String login(String username, String password) {
User user = userDao.getUserByUsername(username);
if(user == null) return "账号不存在";
String encrypt = MD5.encrypt(password, user.getSalt());
return encrypt.equals(user.getPassword()) ? "登录成功" : "账号或密码错误";
}
}
// 数据访问层:只负责与数据库交互
public class UserDaoImpl implements UserDao {
public int saveUser(String username, String password, String salt) {
String sql = "INSERT INTO `user` (`username`, `password`, `salt`) VALUES (?, ?, ?)";
return JdbcUtil.update(sql, username, password, salt);
}
public User getUserByUsername(String username) {
String sql = "SELECT username,password,salt FROM user WHERE username=?";
List<User> users = JdbcUtil.query(sql, User.class, username);
return users.size() == 0 ? null : users.get(0);
}
}

浙公网安备 33010602011771号