目录
4.2.2 MySQL 数据库的 SQL 语句区分大小写吗?
一、MySQL基础
01 - 数据库相关概念
以前我们做系统,数据持久化的存储采用的是文件存储。存储到文件中可以达到系统关闭数据不会丢失的效果,当然文件存储也有它的弊端。
假设在文件中存储以下的数据:
姓名 年龄 性别 住址 张三 23 男 北京西三旗 李四 24 女 北京西二旗 王五 25 男 西安软件新城
现要修改李四这条数据的性别数据改为男,我们现学习的IO技术可以通过将所有的数据读取到内存中,然后进行修改再存到该文件中。通过这种方式操作存在很大问题,现在只有三条数据,如果文件中存储1T的数据,那么就会发现内存根本就存储不了。
现需要既能持久化存储数据,也要能避免上述问题的技术使用在我们的系统中。数据库就是这样的一门技术。
1.1 什么是数据库?
存储和管理数据的仓库。数据是有组织的进行存储
1.2 什么是数据库管理系统?
管理数据库的大型软件
1.3 常见的数据库管理系统
1.4 SQL
02 - MySQL
2.1 MySQL安装
2.1.1 下载
不用理会上面的登录和注册按钮,直接点击 No thanks, just start my download. 就可以下载。
2.1.2 安装(解压)
2.2 MySQL卸载
2. 再敲入 mysqld -remove mysql ,回车。
3. 最后删除MySQL目录及相关的环境变量。
至此,MySQL卸载完成!
2.3 MySQL配置
2.3.1 添加环境变量
点击 环境变量
在 系统变量 中新建MYSQL_HOME
在 系统变量 中找到并双击 Path
点击 新建
最后点击确定。
2.3.2 新建配置文件
[mysql]
default-character-set=utf8
[mysqld]
character-set-server=utf8
default-storage-engine=INNODB
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CR
EATE_USER,NO_ENGINE_SUBSTITUTION
上面代码意思就是配置数据库的默认编码集为utf-8和默认存储引擎为INNODB。
2.3.3 初始化MySQL
tips:如果出现如下错误
是由于权限不足导致的,去 C:\Windows\System32 下以管理员方式运行 cmd.exe
2.3.4 注册MySQL服务
在黑框里敲入 mysqld -install ,回车。
2.3.5 启动MySQL服务
2.3.6 修改默认账户密码
2.4 MySQL登陆和退出
2.4.1 登陆
到这里你就可以开始你的MySQL之旅了!
2.4.2 退出
03 - MySQL数据模型
3.1 如何理解关系型数据库?
关系型数据库是建立在关系模型基础上的数据库,简单说,关系型数据库是由多张能互相连接的
二维表 组成的数据库
如下图,订单信息表
和 客户信息表
都是有行有列二维表我们将这样的称为关系型数据库。
3.2 关系型数据库的优点有哪些?
1.使用表结构,格式一致,易于维护
2.使用通用的SQL语言操作,使用方便,可用于复杂查询。
-
关系型数据库都可以通过SQL进行操作,所以使用方便。
-
复杂查询。现在需要查询001号订单数据,我们可以看到该订单是1号客户的订单,而1号订单是李聪这个客户。以后也可以在一张表中进行统计分析等操作。
3.数据存储在硬盘上,安全
3.3 数据模型:
数据库,表,数据的关系是什么?
一个数据库可以创建多张表
一张表可以存储多条数据
如上图,我们通过客户端可以通过数据库管理系统创建数据库,在数据库中创建表,在表中添加数
据。创建的每一个数据库对应到磁盘上都是一个文件夹。比如可以通过SQL语句创建一个数据库
(数据库名称为db1),语句如下。
我们可以在数据库安装目录下的data目录下看到多了一个 db1
的文件夹。所以,在MySQL中一个
数据库对应到磁盘上的一个文件夹。
而一个数据库下可以创建多张表,我们到MySQL中自带的mysql数据库的文件夹目录下:
而上图中右边的 db.frm
是表文件,db.MYD
是数据文件,通过这两个文件就可以查询到数据展示成
二维表的效果。
小结:
-
MySQL中可以创建多个数据库,每个数据库对应到磁盘上的一个文件夹
-
在每个数据库中可以创建多个表,每张都对应到磁盘上一个 frm 文件
-
每张表可以存储多条数据,数据会被存储到磁盘中 MYD 文件中
04 - SQL
4.1 SQL概述
4.1.1 SQL简介
-
英文:Structured Query Language,简称 SQL
-
结构化查询语言,一门操作关系型数据库的编程语言
-
定义操作所有关系型数据库的统一标准
-
对于同一个需求,每一种数据库操作的方式可能会存在一些不一样的地方,我们称为“方言”
4.2 通用语法
4.2.1 SQL 语句可以单行或多行书写,以分号结尾。
如上,以分号结尾才是一个完整的sql语句。
4.2.2 MySQL 数据库的 SQL 语句区分大小写吗?
不区分大小写,关键字建议使用大写。
4.2.3 SQL的注释有哪些?
单行注释:
-- 要注释的内容
# 要注释的内容(mysql特有)
注意:使用-- 添加单行注释时,--后面一定要加空格,而#没有要求。
多行注释
/*要注释的内容*/
4.3 SQL分类
DDL(Data Definition Language) 数据定义语言,用来定义数据库对象:数据库,表,列等。
DDL简单理解就是用来操作数据库,表等。
DML(Data Manipulation Language) 数据操作语言,用来对数据库中表的数据进行增删改。
DML简单理解就对表中数据进行增删改。
DQL(Data Query Language) 数据查询语言,用来查询数据库中表的记录(数据)。
DQL简单理解就是对数据进行查询操作,从数据库表中查询到我们想要的数据。
DCL(Data Control Language) 数据控制语言,用来定义数据库的访问权限和安全级别,及创建用户。
DCL简单理解就是对数据库进行权限控制。比如我让某一个数据库表只能让某一个用户进行操作等。
注意:以后我们最常操作的是 DML
和 DQL
,因为我们开发中最常操作的就是数据。
05 - DDL-操作数据库
5.1 创建数据库
create database 数据库名称;
运行语句效果如下:
而在创建数据库的时候,我并不知道db1数据库有没有创建,直接再次创建名为db1的数据库就会
出现错误。
为了避免上面的错误,在创建数据库的时候先做判断,如果不存在再创建。
创建数据库(判断,如果不存在则创建)
create database [if not exists] 数据库名称
运行语句效果如下:
从上面的效果可以看到虽然db1数据库已经存在,再创建db1也没有报错,而创建db2数据库则创
建成功。
5.2 删除数据库
drop database 数据库名称;
删除数据库(判断,如果存在则删除)
drop database [if exists] 数据库名称;
运行效果如下:
5.3 查询所有的数据库
show databases;
运行上面语句效果如下:
上述查询到的是的这些数据库是mysql安装好自带的数据库,我们以后不要操作这些数据库。
5.4 使用数据库
数据库创建好了,要在数据库中创建表,得先明确在哪儿个数据库中操作,此时就需要使用数据
库。
使用(进入)数据库格式
use 数据库名称;
select database();
运行效果如下:
总结:
06 - DDL-操作表
操作表也就是对表进行增(Create)删(Retrieve)改(Update)查(Delete)。
6.1 查询表
6.1.1 查询当前数据库下所有表的名称的语句是什么?
show tables;
我们创建的数据库中没有任何表,因此我们进入mysql自带的mysql数据库,执行上述语句查看
6.1.2 查询指定表的结构的语句是什么?
desc 表名;
查看mysql数据库中func表的结构,运行语句如下:
总结:
6.2 创建表
CREATE TABLE 表名 ( 字段名1 数据类型1,字段名2 数据类型2, … 字段名n 数据类型n );
注意:最后一行末尾,不能加逗号
知道了创建表的语句,那么我们创建如下结构的表
create table tb_user (
id int,
username varchar(20),
password varchar(32)
);
运行语句如下:
6.3 数据类型
MySQL 支持多种类型,可以分为三类:
-
数值
tinyint : 小整数型,占一个字节 int : 大整数类型,占四个字节 eg : age int double : 浮点类型 使用格式: 字段名 double(总长度,小数点后保留的位数) eg : score double(5,2)
-
日期
date : 日期值。只包含年月日 eg :birthday date : datetime : 混合日期和时间值。包含年月日时分秒
-
字符串
char : 定长字符串。 优点:存储性能高 缺点:浪费空间 eg : name char(10) 如果存储的数据字符个数不足10个,也会占10个的空间 varchar : 变长字符串。 优点:节约空间 缺点:存储性能底 eg : name varchar(10) 如果存储的数据字符个数不足10个,那就数据字符个数是几就占几个的空间
6.4 删除表
drop table 表名;
删除表时判断表是否存在
drop table [if exists] 表名;
运行效果如下:
总结:
6.5 修改表
6.5.1 修改表名
ALTER TABLE 表名 RENAME TO 新的表名;
-- 将表名student修改为stu
alter table student rename to stu;
6.5.2 添加一列
ALTER TABLE 表名 ADD 列名 数据类型;
-- 给stu表添加一列address,该字段类型是varchar(50)
alter table stu add address varchar(50);
6.5.3 修改数据类型
ALTER TABLE 表名 MODIFY 列名 新数据类型;
-- 将stu表中的address字段的类型改为 char(50)
alter table stu modify address char(50);
6.5.4 修改列名和数据类型
ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型;
-- 将stu表中的address字段名改为 addr,类型改为varchar(50)
alter table stu change address addr varchar(50);
6.5.5 删除一列
ALTER TABLE 表名 drop 列名
-- 将stu表中的addr字段 删除
alter table stu drop addr;
总结:
07 - DML
7.1 添加数据
7.1.1 给指定列添加数据
insert into 表名 (列名1,列名2,...) values (值1,值2,...);
-- 给指定列添加数据
INSERT INTO stu (id, NAME) VALUES (1, '张三');
7.1.2 给全部列添加数据
insert into 表名 values (值1,值2,…);
-- 给所有列添加数据,列名的列表可以省略的
INSERT INTO stu (id,NAME,sex,birthday,score,email,tel,STATUS) VALUES (2,'李四','男','1999-11-11',88.88,'lisi@itcast.cn','13888888888',1);
7.1.3 批量添加数据
insert into 表名(列名1,列名2,…) values (值1,值2,…),(值1,值2,…),(值1,值2,…)…;
insert into 表名 values (值1,值2,…),(值1,值2,…),(值1,值2,…)…;
-- 批量添加数据
INSERT INTO stu VALUES
(2,'李四','男','1999-11-11',88.88,'lisi@itcast.cn','13888888888',1),
(2,'李四','男','1999-11-11',88.88,'lisi@itcast.cn','13888888888',1),
(2,'李四','男','1999-11-11',88.88,'lisi@itcast.cn','13888888888',1);
总结:
7.2 修改数据
7.2.1 修改表数据
UPDATE 表名 SET 列名1=值1,列名2=值2,… [WHERE 条件] ;
注意:
-
修改语句中如果不加条件,则将所有数据都修改!
-
像上面的语句中的中括号,表示在写sql语句中可以省略这部分
例:
- 将张三的性别改为女
update stu set sex = '女' where name = '张三';
- 将张三的生日改为 1999-12-12 分数改为99.99
update stu set birthday = '1999-12-12', score = 99.99 where name = '张三';
- 注意:如果update语句没有加where条件,则会将表中所有数据全部修改!
update stu set sex = '女';
上面语句的执行完后查询到的结果是:
总结:
7.3 删除数据
7.3.1 删除表中数据
DELETE FROM 表名 [WHERE 条件] ;
7.3.2 删除数据不加条件会有什么效果?
删除所有的数据
-- 删除张三记录
delete from stu where name = '张三';
-- 删除stu表中所有的数据
delete from stu;
总结:
08 - DQL
为了给大家演示查询的语句,我们需要先准备表及一些数据:
-- 删除stu表
drop table if exists stu;
-- 创建stu表
CREATE TABLE stu (
id int, -- 编号
name varchar(20), -- 姓名
age int, -- 年龄
sex varchar(5), -- 性别
address varchar(100), -- 地址
math double(5,2), -- 数学成绩
english double(5,2), -- 英语成绩
hire_date date -- 入学时间
);
-- 添加数据
INSERT INTO stu(id,NAME,age,sex,address,math,english,hire_date)
VALUES
(1,'马运',55,'男','杭州',66,78,'1995-09-01'),
(2,'马花疼',45,'女','深圳',98,87,'1998-09-01'),
(3,'马斯克',55,'男','香港',56,77,'1999-09-02'),
(4,'柳白',20,'女','湖南',76,65,'1997-09-05'),
(5,'柳青',20,'男','湖南',86,NULL,'1998-09-01'),
(6,'刘德花',57,'男','香港',99,99,'1998-09-01'),
(7,'张学右',22,'女','香港',99,99,'1998-09-01'),
(8,'德玛西亚',18,'男','南京',56,65,'1994-09-02');
8.1 基础查询
8.1.1 语法
查询多个字段
SELECT 字段列表 FROM 表名;
查询所有数据
SELECT * FROM 表名;
去除重复记录
SELECT DISTINCT 字段列表 FROM 表名;
起别名
AS: AS 也可以省略
8.1.2 演示
查询name、age两列
select name,age from stu;
查询所有列的数据,列名的列表可以使用*替代
select * from stu;
查询地址信息
select address from stu;
执行上面语句结果如下:
从上面的结果我们可以看到有重复的数据,我们也可以使用 distinct 关键字去重重复数据。
去除重复记录
select distinct address from stu;
查询姓名、数学成绩、英语成绩。并通过as给math和english起别名(as关键字可以省略)
select name,math as 数学成绩,english as 英文成绩 from stu;
select name,math 数学成绩,english 英文成绩 from stu;
总结:
8.2 条件查询
8.2.1 语法
SELECT 字段列表 FROM 表名 WHERE 条件列表;
条件规则
mysql中的+号:仅仅只有一个功能:运算符
两个操作数都为数值型,则做加法运算 例:
select 100+90
只要其中一方为字符型,试图将字符型数值转换成数值型,如果转换成功,则继续做加法运算 例:
select '123'+90
如果转换失败,则将字符型数值转换成0 例:
select 'john'+90
只要其中一方为null,则结果肯定为null;例:
select null+10
8.2.2 条件查询演示
-- 查询年龄大于20岁的学员信息
select * from stu where age > 20;
-- 查询年龄大于等于20岁的学员信息
select * from stu where age >= 20;
-- 查询年龄大于等于20岁 并且 年龄 小于等于 30岁 的学员信息
select * from stu where age >= 20 && age <= 30;
select * from stu where age >= 20 and age <= 30;
-- 上面语句中 && 和 and 都表示并且的意思,建议使用 and,也可以使用 between ... and 来实现上面需求
select * from stu where age BETWEEN 20 and 30;
-- 查询入学日期在'1998-09-01' 到 '1999-09-01' 之间的学员信息
select * from stu where hire_date BETWEEN '1998-09-01' and '1999-09-01';
-- 查询年龄等于18岁的学员信息
select * from stu where age = 18;
-- 查询年龄不等于18岁的学员信息
select * from stu where age != 18;
select * from stu where age <> 18;
-- 查询年龄等于18岁 或者 年龄等于20岁 或者 年龄等于22岁的学员信息
select * from stu where age = 18 or age = 20 or age = 22;
select * from stu where age in (18,20 ,22);
-- 查询英语成绩为 null的学员信息
-- null值的比较不能使用 = 或者 != 。需要使用 is 或者 is not
select * from stu where english = null; -- 这个语句是不行的
select * from stu where english is null;
select * from stu where english is not null;
8.2.3 模糊查询
模糊查询使用like关键字,可以使用通配符进行占位:
(1)_ : 代表单个任意字符
(2)% : 代表任意个数字符
-- 查询姓'马'的学员信息
select * from stu where name like '马%';
-- 查询第二个字是'花'的学员信息
select * from stu where name like '_花%';
-- 查询名字中包含 '德' 的学员信息
select * from stu where name like '%德%';
8.3 排序查询
8.3.1 语法
排序方式有哪些?
ASC: 升序(默认值)
DESC:降序
注意:如果有多个排序条件,当前边的条件值一样时,才会根据第二条件进行排序
8.3.2 演示
- 查询学生信息,按照年龄升序排列
select * from stu order by age ;
- 查询学生信息,按照数学成绩降序排列
select * from stu order by math desc ;
- 查询学生信息,按照数学成绩降序排列,如果数学成绩一样,再按照英语成绩升序排列
select * from stu order by math desc , english asc ;
8.4 聚合函数
8.4.1 概念
将一列数据作为一个整体,进行纵向计算,得到一个结果。
8.4.2 聚合函数分类
count(列名) : 统计某一列满足条件的数量
max(列名) : 求某一列的最大值
min(列名) : 求某一列的最小值
sum(列名) : 求某一列的数据值和
avg(列名) : 求某一列的数据的平均值
8.4.3 聚合查询语法
null值是否参与聚合运算?
不参与
8.4.4 演示
- 统计班级一共有多少个学生
select count(id) from stu;
select count(english) from stu;
-- 上面语句根据某个字段进行统计,如果该字段某一行的值为null的话,将不会被统计。
-- 所以可以在count(*) 来实现。* 表示所有字段数据,一行中也不可能所有的数据都为null,所以建议使用 count(*)
select count(*) from stu;
- 查询数学成绩的最高分
select max(math) from stu;
- 查询数学成绩的最低分
select min(math) from stu;
- 查询数学成绩的总分
select sum(math) from stu;
- 查询数学成绩的平均分
select avg(math) from stu;
- 查询英语成绩的最低分
select min(english) from stu;
8.5 分组查询
8.5.1 语法
注意:分组之后,查询的字段为聚合函数和分组字段,查询其他字段无任何意义
8.5.2 演示
- 查询男同学和女同学各自的数学平均分
select sex, avg(math) from stu group by sex;
- 注意:分组之后,查询的字段为聚合函数和分组字段,查询其他字段无任何意义
select name, sex, avg(math) from stu group by sex; -- 这里查询name字段就没有任何意义
- 查询男同学和女同学各自的数学平均分,以及各自人数
select sex, avg(math),count(*) from stu group by sex;
- 查询男同学和女同学各自的数学平均分,以及各自人数,要求:分数低于70分的不参与分组
select sex, avg(math),count(*) from stu where math > 70 group by sex;
- 查询男同学和女同学各自的数学平均分,以及各自人数,要求:分数低于70分的不参与分组,分组之后人数大于2个的
select sex, avg(math),count(*) from stu where math > 70 group by sex having count(*) > 2;
8.5.3 where 和 having 区别
执行时机不一样:where 是分组之前进行限定,不满足where条件,则不参与分组,而having是分组之后对结果进行过滤。
可判断的条件不一样:where 不能对聚合函数进行判断,having 可以。
注意:执行顺序 : where > 聚合函数 > having
8.6 分页查询
如下图所示,大家在很多网站都见过类似的效果,如京东、百度、淘宝等。分页查询是将数据一页一页的展示给用户看,用户也可以通过点击查看下一页的数据。
8.6.1 语法
分页查询使用的关键字是什么?
limit
起始索引如何计算?
起始索引:从0开始
8.6.2 演示
- 从0开始查询,查询3条数据
select * from stu limit 0 , 3;
- 每页显示3条数据,查询第1页数据
select * from stu limit 0 , 3;
- 每页显示3条数据,查询第2页数据
select * from stu limit 3 , 3;
- 每页显示3条数据,查询第3页数据
计算公式:起始索引 = (当前页码-1) * 每页显示的条数
注意:limit是mysql的方言,其他数据库分页不是使用limit实现
二、MySQL高级
01 - 约束
上面表中可以看到表中数据存在一些问题:
-
id 列一般是用标示数据的唯一性的,而上述表中的id为1的有三条数据,并且
马花疼
没有id进行标示 -
柳白
这条数据的age列的数据是3000,而人也不可能活到3000岁 -
马运
这条数据的math数学成绩是-5,而数学学得再不好也不可能出现负分 -
柳青
这条数据的english列(英文成绩)值为null,而成绩即使没考也得是0分
针对上述数据问题,我们就可以从数据库层面在添加数据的时候进行限制,这个就是约束。
1.1 概念
1.1.1 约束的作用
约束是作用于表中列上的规则,用于限制加入表的数据
例如:我们可以给id列加约束,让其值不能重复,不能为null值。
约束的存在保证了数据库中数据的正确性、有效性和完整性
添加约束可以在添加数据的时候就限制不正确的数据,年龄是3000,数学成绩是-5分这样无效的数据,继而保障数据的完整性。
1.2 分类
1.2.1 非空约束:关键字是 NOT NULL
保证列中所有的数据不能有null值。
例如:id列在添加 马花疼
这条数据时就不能添加成功。
1.2.2 唯一约束:关键字是 UNIQUE
保证列中所有数据各不相同。
例如:id列中三条数据的值都是1,这样的数据在添加时是绝对不允许的。
1.2.3 主键约束:关键字是 PRIMARY KEY
主键是一行数据的唯一标识,要求非空且唯一。
一般我们都会给每张表添加一个主键列用来唯一标识数据。
例如:上图表中id就可以作为主键,来标识每条数据。那么这样就要求数据中id的值不能重复,不能为null值。
1.2.4 检查约束:关键字是 CHECK
保证列中的值满足某一条件。
例如:我们可以给age列添加一个范围,最低年龄可以设置为1,最大年龄就可以设置为300,这样的数据才更合理些。
注意:MySQL不支持检查约束。
这样是不是就没办法保证年龄在指定的范围内了?从数据库层面不能保证,但是可以在java代码中进行限制,一样也可以实现要求。
1.2.5 默认约束:关键字是 DEFAULT
保存数据时,未指定值则采用默认值。
例如:我们在给english列添加该约束,指定默认值是0,这样在添加数据时没有指定具体值时就会采用默认给定的0。
1.2.6 外键约束:关键字是 FOREIGN KEY
外键用来让两个表的数据之间建立链接,保证数据的一致性和完整性。
1.3 非空约束
1.3.1 概念
非空约束用于保证列中所有数据不能有NULL值。
1.3.2 语法
- 添加约束
-- 创建表时添加非空约束
CREATE TABLE 表名(
列名 数据类型 NOT NULL,
…
);
-- 建完表后添加非空约束
ALTER TABLE 表名 MODIFY 字段名 数据类型 NOT NULL;
- 删除约束
ALTER TABLE 表名 MODIFY 字段名 数据类型;
1.4 唯一约束
1.4.1 概念
唯一约束用于保证列中所有数据各不相同
1.4.2 语法
- 添加约束
-- 创建表时添加唯一约束
CREATE TABLE 表名(
列名 数据类型 UNIQUE [AUTO_INCREMENT],
-- AUTO_INCREMENT: 当不指定值时自动增长
…
);
CREATE TABLE 表名(
列名 数据类型,
…
[CONSTRAINT] [约束名称] UNIQUE(列名)
);
-- 建完表后添加唯一约束
ALTER TABLE 表名 MODIFY 字段名 数据类型 UNIQUE;
- 删除约束
ALTER TABLE 表名 DROP INDEX 字段名;
1.5 主键约束
1.5.1 概念
主键是一行数据的唯一标识,要求非空且唯一
一张表只能有一个主键
1.5.2 语法
- 添加约束
-- 创建表时添加主键约束
CREATE TABLE 表名(
列名 数据类型 PRIMARY KEY [AUTO_INCREMENT],
…
);
CREATE TABLE 表名(
列名 数据类型,
[CONSTRAINT] [约束名称] PRIMARY KEY(列名)
);
-- 建完表后添加主键约束
ALTER TABLE 表名 ADD PRIMARY KEY(字段名);
- 删除约束
ALTER TABLE 表名 DROP PRIMARY KEY;
1.6 默认约束
1.6.1 概念
保存数据时,未指定值则采用默认值
1.6.2 语法
- 添加约束
-- 创建表时添加默认约束
CREATE TABLE 表名(
列名 数据类型 DEFAULT 默认值,
…
);
-- 建完表后添加默认约束
ALTER TABLE 表名 ALTER 列名 SET DEFAULT 默认值;
- 删除约束
ALTER TABLE 表名 ALTER 列名 DROP DEFAULT;
1.7 约束演示
根据需求,为表中添加合适的约束
-- 员工表
CREATE TABLE emp (
id INT, -- 员工id,主键且自增长
ename VARCHAR(50), -- 员工姓名,非空且唯一
joindate DATE, -- 入职日期,非空
salary DOUBLE(7,2), -- 工资,非空
bonus DOUBLE(7,2) -- 奖金,如果没有将近默认为0
);
上面一定给出了具体的要求,我们可以根据要求创建这张表,并为每一列添加对应的约束。建表语句如下:
DROP TABLE IF EXISTS emp;
-- 员工表
CREATE TABLE emp (
id INT PRIMARY KEY, -- 员工id,主键且自增长
ename VARCHAR(50) NOT NULL UNIQUE, -- 员工姓名,非空并且唯一
joindate DATE NOT NULL , -- 入职日期,非空
salary DOUBLE(7,2) NOT NULL , -- 工资,非空
bonus DOUBLE(7,2) DEFAULT 0 -- 奖金,如果没有奖金默认为0
);
通过上面语句可以创建带有约束的 emp
表,约束能不能发挥作用呢。接下来我们一一进行验证,先添加一条没有问题的数据
INSERT INTO emp(id,ename,joindate,salary,bonus)
values(1,'张三','1999-11-11',8800,5000);
验证主键约束,非空且唯一
INSERT INTO emp(id,ename,joindate,salary,bonus)
values(null,'张三','1999-11-11',8800,5000);
执行结果如下:
从上面的结果可以看到,字段 id
不能为null。那我们重新添加一条数据,如下:
INSERT INTO emp(id,ename,joindate,salary,bonus)
values(1,'张三','1999-11-11',8800,5000);
执行结果如下:
从上面结果可以看到,1这个值重复了。所以主键约束是用来限制数据非空且唯一的。那我们再添加一条符合要求的数据
INSERT INTO emp(id,ename,joindate,salary,bonus)
values(2,'李四','1999-11-11',8800,5000);
执行结果如下:
验证非空约束
INSERT INTO emp(id,ename,joindate,salary,bonus)
values(3,null,'1999-11-11',8800,5000);
执行结果如下:
从上面结果可以看到,ename
字段的非空约束生效了。
验证唯一约束
INSERT INTO emp(id,ename,joindate,salary,bonus)
values(3,'李四','1999-11-11',8800,5000);
执行结果如下:
从上面结果可以看到,ename
字段的唯一约束生效了。
验证默认约束
INSERT INTO emp(id,ename,joindate,salary)
values(3,'王五','1999-11-11',8800);
执行完上面语句后查询表中数据,如下图可以看到王五这条数据的bonus列就有了默认值0。
注意:默认约束只有在不给值时才会采用默认值。如果给了null,那值就是null值。
如下:
INSERT INTO emp(id,ename,joindate,salary,bonus)
values(4,'赵六','1999-11-11',8800,null);
执行完上面语句后查询表中数据,如下图可以看到赵六这条数据的bonus列的值是null。
1.8 外键约束
1.8.1 概述
外键用来让两个表的数据之间建立链接,保证数据的一致性和完整性。
如何理解上面的概念呢?如下图有两张表,员工表和部门表:
员工表中的dep_id字段是部门表的id字段关联,也就是说1号学生张三属于1号部门研发部的员工。现在我要删除1号部门,就会出现错误的数据(员工表中属于1号部门的数据)。而我们上面说的两张表的关系只是我们认为它们有关系,此时需要通过外键让这两张表产生数据库层面的关系,这样你要删除部门表中的1号部门的数据将无法删除。
1.8.2 语法
- 添加外键约束
-- 创建表时添加外键约束
CREATE TABLE 表名(
列名 数据类型,
…
[CONSTRAINT] [外键名称] FOREIGN KEY(外键列名) REFERENCES 主表(主表列名)
);
-- 建完表后添加外键约束
ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名称) REFERENCES 主表名称(主表列名称);
- 删除外键约束
ALTER TABLE 表名 DROP FOREIGN KEY 外键名称;
1.8.3 演示
根据上述语法创建员工表和部门表,并添加上外键约束:
-- 删除表
DROP TABLE IF EXISTS emp;
DROP TABLE IF EXISTS dept;
-- 部门表
CREATE TABLE dept(
id int primary key auto_increment,
dep_name varchar(20),
addr varchar(20)
);
-- 员工表
CREATE TABLE emp(
id int primary key auto_increment,
name varchar(20),
age int,
dep_id int,
-- 添加外键 dep_id,关联 dept 表的id主键
CONSTRAINT fk_emp_dept FOREIGN KEY(dep_id) REFERENCES dept(id)
);
添加数据
-- 添加 2 个部门
insert into dept(dep_name,addr) values
('研发部','广州'),('销售部', '深圳');
-- 添加员工,dep_id 表示员工所在的部门
INSERT INTO emp (NAME, age, dep_id) VALUES
('张三', 20, 1),
('李四', 20, 1),
('王五', 20, 1),
('赵六', 20, 2),
('孙七', 22, 2),
('周八', 18, 2);
此时删除 `研发部` 这条数据,会发现无法删除。
删除外键
alter table emp drop FOREIGN key fk_emp_dept;
重新添加外键
alter table emp add CONSTRAINT fk_emp_dept FOREIGN key(dep_id) REFERENCES dept(id);
02 - 数据库设计
2.1 数据库设计简介
2.1.1 软件的研发步骤
2.1.2 数据库设计概念
数据库设计就是根据业务系统的具体需求,结合我们所选用的DBMS,为这个业务系统构造出最优的数据存储模型。
建立数据库中的==表结构==以及==表与表之间的关联关系==的过程。有哪些表?
表里有哪些字段?表和表之间有什么关系?
2.1.3 数据库设计的步骤
需求分析(数据是什么? 数据具有哪些属性? 数据与属性的特点是什么)
逻辑分析(通过ER图对数据库进行逻辑建模,不需要考虑我们所选用的数据库管理系统)
如下图就是ER(Entity/Relation)图:
物理设计(根据数据库自身的特点把逻辑设计转换为物理设计)
维护设计(1.对新的需求进行建表;2.表优化)
2.2 表关系(一对多)
2.2.1 一对多
如:部门 和 员工
一个部门对应多个员工,一个员工对应一个部门。
2.2.2 实现方式
在多的一方建立外键,指向一的一方的主键
2.2.3 案例
我们还是以 “员工表” 和 “部门表” 举例:
经过分析发现,员工表属于多的一方,而部门表属于一的一方,此时我们会在员工表中添加一列(dep_id),指向于部门表的主键(id):
建表语句如下:
-- 删除表
DROP TABLE IF EXISTS tb_emp;
DROP TABLE IF EXISTS tb_dept;
-- 部门表
CREATE TABLE tb_dept(
id int primary key auto_increment,
dep_name varchar(20),
addr varchar(20)
);
-- 员工表
CREATE TABLE tb_emp(
id int primary key auto_increment,
name varchar(20),
age int,
dep_id int,
-- 添加外键 dep_id,关联 dept 表的id主键
CONSTRAINT fk_emp_dept FOREIGN KEY(dep_id) REFERENCES tb_dept(id)
);
查看表结构模型图:
2.3 表关系(多对多)
2.3.1 多对多
如:商品 和 订单
一个商品对应多个订单,一个订单包含多个商品
2.3.2 实现方式
建立第三张中间表,中间表至少包含两个外键,分别关联两方主键
2.3.3 案例
我们以“订单表” 和 “商品表” 举例:
经过分析发现,订单表和商品表都属于多的一方,此时需要创建一个中间表,在中间表中添加订单表的外键和商品表的外键指向两张表的主键:
建表语句如下:
-- 删除表
DROP TABLE IF EXISTS tb_order_goods;
DROP TABLE IF EXISTS tb_order;
DROP TABLE IF EXISTS tb_goods;
-- 订单表
CREATE TABLE tb_order(
id int primary key auto_increment,
payment double(10,2),
payment_type TINYINT,
status TINYINT
);
-- 商品表
CREATE TABLE tb_goods(
id int primary key auto_increment,
title varchar(100),
price double(10,2)
);
-- 订单商品中间表
CREATE TABLE tb_order_goods(
id int primary key auto_increment,
order_id int,
goods_id int,
count int
);
-- 建完表后,添加外键
alter table tb_order_goods add CONSTRAINT fk_order_id FOREIGN key(order_id) REFERENCES tb_order(id);
alter table tb_order_goods add CONSTRAINT fk_goods_id FOREIGN key(goods_id) REFERENCES tb_goods(id);
查看表结构模型图:
2.4 表关系(一对一)
2.4.1 一对一
如:用户 和 用户详情
一对一关系多用于表拆分,将一个实体中经常使用的字段放一张表,不经常使用的字段放另一张
表,用于提升查询性能
2.4.2 实现方式
在任意一方加入外键,关联另一方主键,并且设置外键为唯一(UNIQUE)
2.4.3 案例
我们以 "用户表" 举例:
而在真正使用过程中发现 id、photo、nickname、age、gender 字段比较常用,此时就可以将这张表查分成两张表。
建表语句如下:
create table tb_user_desc (
id int primary key auto_increment,
city varchar(20),
edu varchar(10),
income int,
status char(2),
des varchar(100)
);
create table tb_user (
id int primary key auto_increment,
photo varchar(100),
nickname varchar(50),
age int,
gender char(1),
desc_id int unique,
-- 添加外键
CONSTRAINT fk_user_desc FOREIGN KEY(desc_id) REFERENCES tb_user_desc(id)
);
查看表结构模型图:
03 - 多表查询
3.1 概念
多表查询顾名思义就是从多张表中一次性的查询出我们想要的数据。
3.1.1 演示
DROP TABLE IF EXISTS emp;
DROP TABLE IF EXISTS dept;
-- 创建部门表
CREATE TABLE dept(
did INT PRIMARY KEY AUTO_INCREMENT,
dname VARCHAR(20)
);
-- 创建员工表
CREATE TABLE emp (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10),
gender CHAR(1), -- 性别
salary DOUBLE, -- 工资
join_date DATE, -- 入职日期
dep_id INT,
FOREIGN KEY (dep_id) REFERENCES dept(did) -- 外键,关联部门表(部门表的主键)
);
-- 添加部门数据
INSERT INTO dept (dNAME) VALUES ('研发部'),('市场部'),('财务部'),('销售部');
-- 添加员工数据
INSERT INTO emp(NAME,gender,salary,join_date,dep_id) VALUES
('孙悟空','男',7200,'2013-02-24',1),
('猪八戒','男',3600,'2010-12-02',2),
('唐僧','男',9000,'2008-08-08',2),
('白骨精','女',5000,'2015-10-07',3),
('蜘蛛精','女',4500,'2011-03-14',1),
('小白龙','男',2500,'2011-02-14',null);
执行下面的多表查询语句
select * from emp , dept; -- 从emp和dept表中查询所有的字段数据
结果如下:
从上面的结果我们看到有一些无效的数据,如 孙悟空
这个员工属于1号部门,但也同时关联的2、3、4号部门。所以我们要通过限制员工表中的 dep_id
字段的值和部门表 did
字段的值相等来消除这些无效的数据。
select * from emp , dept where emp.dep_id = dept.did;
执行后结果如下:
3.1 内连接查询
3.1.1 概念
内连接相当于查询 A B 表中的交集数据
3.1.2 语法(隐式内连接)
- 隐式内连接
SELECT 字段列表 FROM 表1,表2… WHERE 条件;
演示如下
-- 隐式内连接
SELECT
*
FROM
emp,
dept
WHERE
emp.dep_id = dept.did;
执行上述语句结果如下:
查询 emp的 name, gender,dept表的dname
SELECT
emp. NAME,
emp.gender,
dept.dname
FROM
emp,
dept
WHERE
emp.dep_id = dept.did;
执行语句结果如下:
上面语句中使用表名指定字段所属有点麻烦,sql也支持给表指别名,上述语句可以改进为
SELECT
t1. NAME,
t1.gender,
t2.dname
FROM
emp t1,
dept t2
WHERE
t1.dep_id = t2.did;
3.1.3 语法(显式内连接)
- 显示内连接
SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 条件;
演示如下:
-- 显式内连接
select * from emp inner join dept on emp.dep_id = dept.did;
-- 上面语句中的inner可以省略,可以书写为如下语句
select * from emp join dept on emp.dep_id = dept.did;
执行结果如下:
3.2 外连接查询
3.2.1 概念
左外连接:相当于查询A表所有数据和交集部分数据
右外连接:相当于查询B表所有数据和交集部分数据
3.2.2 语法
-- 左外连接
SELECT 字段列表 FROM 表1 LEFT [OUTER] JOIN 表2 ON 条件;
-- 右外连接
SELECT 字段列表 FROM 表1 RIGHT [OUTER] JOIN 表2 ON 条件;
3.2.3 演示
查询emp表所有数据和对应的部门信息(左外连接)
select * from emp left join dept on emp.dep_id = dept.did;
执行语句结果如下:
结果显示查询到了左表(emp)中所有的数据及两张表能关联的数据。
查询dept表所有数据和对应的员工信息(右外连接)
select * from emp right join dept on emp.dep_id = dept.did;
执行语句结果如下:
结果显示查询到了右表(dept)中所有的数据及两张表能关联的数据。
要查询出部门表中所有的数据,也可以通过左外连接实现,只需要将两个表的位置进行互换:
select * from dept left join emp on emp.dep_id = dept.did;
3.3 子查询
3.3.1 概念
查询中嵌套查询,称嵌套查询为子查询。
什么是查询中嵌套查询呢?我们通过一个例子来看:
3.3.2 演示
需求:查询工资高于猪八戒的员工信息 。
来实现这个需求,我们就可以通过二步实现,第一步:先查询出来 猪八戒的工资
select salary from emp where name = '猪八戒'
第二步:查询工资高于猪八戒的员工信息
select * from emp where salary > 3600;
第二步中的3600可以通过第一步的sql查询出来,所以将3600用第一步的sql语句进行替换
select * from emp where salary > (select salary from emp where name = '猪八戒');
这就是查询语句中嵌套查询语句。
3.3.3 子查询根据查询结果不同,作用不同
子查询语句结果是单行单列,子查询语句作为条件值,使用 = != > < 等进行条件判断。
子查询语句结果是多行单列,子查询语句作为条件值,使用 in 等关键字进行条件判断。
子查询语句结果是多行多列,子查询语句作为虚拟表。
04 - 事务
4.1 概述
数据库的事务(Transaction)是一种机制、一个操作序列,包含了一组数据库操作命令。
事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么同时成功,要么同时失败。
事务是一个不可分割的工作逻辑单元。
举例说明,如下图有一张表:
张三和李四账户中各有100块钱,现李四需要转换500块钱给张三,具体的转账操作为
第一步:查询李四账户余额
第二步:从李四账户金额 -500
第三步:给张三账户金额 +500
现在假设在转账过程中第二步完成后出现了异常第三步没有执行,就会造成李四账户金额少了
500,而张三金额并没有多500;这样的系统是有问题的。
如果解决呢?使用事务可以解决上述问题。
从上图可以看到在转账前开启事务,如果出现了异常回滚事务,三步正常执行就提交事务,这样就可以完美解决问题。
4.2 语法
4.2.1 开启事务
START TRANSACTION;
或者
BEGIN;
4.2.2 提交事务
commit;
4.2.3 回滚事务
rollback;
4.3 代码验证
4.3.1 环境准备
DROP TABLE IF EXISTS account;
-- 创建账户表
CREATE TABLE account(
id int PRIMARY KEY auto_increment,
name varchar(10),
money double(10,2)
);
-- 添加数据
INSERT INTO account(name,money) values('张三',1000),('李四',1000);
4.3.2 不加事务演示问题
-- 转账操作
-- 1. 查询李四账户金额是否大于500
-- 2. 李四账户 -500
UPDATE account set money = money - 500 where name = '李四';
出现异常了... -- 此处不是注释,在整体执行时会出问题,后面的sql则不执行
-- 3. 张三账户 +500
UPDATE account set money = money + 500 where name = '张三';
整体执行结果肯定会出问题,我们查询账户表中数据,发现李四账户少了500。
4.3.3 添加事务
-- 开启事务
BEGIN;
-- 转账操作
-- 1. 查询李四账户金额是否大于500
-- 2. 李四账户 -500
UPDATE account set money = money - 500 where name = '李四';
出现异常了... -- 此处不是注释,在整体执行时会出问题,后面的sql则不执行
-- 3. 张三账户 +500
UPDATE account set money = money + 500 where name = '张三';
-- 提交事务
COMMIT;
-- 回滚事务
ROLLBACK;
上面sql中的执行成功进选择执行提交事务,而出现问题则执行回滚事务的语句。以后我们肯定不
可能这样操作,而是在java中进行操作,在java中可以抓取异常,没出现异常提交事务,出现异常
回滚事务。
4.4 事务的四大特征
4.4.1 原子性(Atomicity)
事务是不可分割的最小操作单位,要么同时成功,要么同时失败
4.4.2 一致性(Consistency)
事务完成时,必须使所有的数据都保持一致状态
4.4.3 隔离性(Isolation)
多个事务之间,操作的可见性
4.4.4 持久性(Durability)
事务一旦提交或回滚,它对数据库中的数据的改变就是永久的
说明:
mysql中事务是自动提交的。
也就是说我们不添加事务执行sql语句,语句执行完毕会自动的提交事务。
可以通过下面语句查询默认提交方式:
SELECT @@autocommit;
查询到的结果是1 则表示自动提交,结果是0表示手动提交。当然也可以通过下面语句修改提交方式:
set @@autocommit = 0;
三、JDBC
01 - JDBC概述
1.1 JDBC概念
JDBC 就是使用Java语言操作关系型数据库的一套API
全称:( Java DataBase Connectivity ) Java 数据库连接
我们开发的同一套Java代码是无法操作不同的关系型数据库,因为每一个关系型数据库的底层实现细节都不一样。如果这样,问题就很大了,在公司中可以在开发阶段使用的是MySQL数据库,而上线时公司最终选用oracle数据库,我们就需要对代码进行大批量修改,这显然并不是我们想看到的。我们要做到的是同一套Java代码操作不同的关系型数据库,而此时sun公司就指定了一套标准接口(JDBC),JDBC中定义了所有操作关系型数据库的规则。众所周知接口是无法直接使用的,我们需要使用接口的实现类,而这套实现类(称之为:驱动)就由各自的数据库厂商给出。
1.2 JDBC本质
官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口
各个数据库厂商去实现这套接口,提供数据库驱动jar包
我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类
1.3 JDBC好处
各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发
可随时替换底层数据库,访问数据库的Java代码基本不变
以后编写操作数据库的代码只需要面向JDBC(接口),操作哪儿个关系型数据库就需要导入该数据库的驱动包,如需要操作MySQL数据库,就需要再项目中导入MySQL数据库的驱动包。如下图就是MySQL驱动包
1.4 JDBC快速入门
先来看看通过Java操作数据库的流程
第一步:编写Java代码
第二步:Java代码将SQL发送到MySQL服务端
第三步:MySQL服务端接收到SQL语句并执行该SQL语句
第四步:将SQL语句执行的结果返回给Java代码
1.4.1 编写代码步骤
- 创建工程,导入驱动jar包
- 注册驱动
Class.forName("com.mysql.jdbc.Driver");
- 获取连接
Connection conn = DriverManager.getConnection(url, username, password);
Java代码需要发送SQL给MySQL服务端,就需要先建立连接
- 定义SQL语句
String sql = “update…” ;
- 获取执行SQL对象
执行SQL语句需要SQL执行对象,而这个执行对象就是Statement对象
Statement stmt = conn.createStatement();
- 执行SQL
stmt.executeUpdate(sql);
- 处理返回结果
- 释放资源
1.4.2 具体操作
创建新的空的项目
定义项目的名称,并指定位置
对项目进行设置,JDK版本、编译版本
创建模块,指定模块的名称及位置
导入驱动包
将mysql的驱动包放在模块下的lib目录(随意命名)下,并将该jar包添加为库文件
在添加为库文件的时候,有如下三个选项
Global Library:全局有效
Project Library:项目有效
Module Library:模块有效
在src下创建类
编写代码如下 :
public class JDBCDemo {
public static void main(String[] args) throws Exception {
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接
String url = "jdbc:mysql://127.0.0.1:3306/db1";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql = "update account set money = 2000 where id = 1";
//4. 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//5. 执行sql
int count = stmt.executeUpdate(sql);//受影响的行数
//6. 处理结果
System.out.println(count);
//7. 释放资源
stmt.close();
conn.close();
}
}
02 - JDBC API 详解
2.1 DriverManager
DriverManager(驱动管理类)作用:
注册驱动
registerDriver方法是用于注册驱动的,但是我们之前做的入门案例并不是这样写的。而是如下实现:
Class.forName("com.mysql.jdbc.Driver");
我们查询MySQL提供的Driver类,看它是如何实现的,源码如下:
在该类中的静态代码块中已经执行了 DriverManager 对象的 registerDriver() 方法进行驱动的注
册了,那么我们只需要加载 Driver 类,该静态代码块就会执行。
而Class.forName("com.mysql.jdbc.Driver"); 就可以加载 Driver 类。
MySQL 5之后的驱动包,可以省略注册驱动的步骤。
自动加载jar包中META-INF/services/java.sql.Driver文件中的驱动类。
获取数据库连接
参数说明:
-
url : 连接路径
语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2…
示例:jdbc:mysql://127.0.0.1:3306/db1
==细节:==
-
如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称?参数键值对
-
配置 useSSL=false 参数,禁用安全连接方式,解决警告提示
-
-
user :用户名
-
poassword :密码
2.2 Connection
2.2.1 Connection(数据库连接对象)作用
获取执行 SQL 的对象
管理事务
2.2.2 获取执行对象
普通执行SQL对象
Statement createStatement()
预编译SQL的执行SQL对象:防止SQL注入
PreparedStatement prepareStatement(sql)
执行存储过程的对象
CallableStatement prepareCall(sql)
2.2.3 事务管理
Connection接口中定义了3个对应的方法:
开启事务
参与autoCommit 表示是否自动提交事务,true表示自动提交事务,false表示手动提交事务。而开启事务需要将该参数设为为false。
提交事务
回滚事务
具体代码实现如下:
public class JDBCDemo3_Connection {
public static void main(String[] args) throws Exception {
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql1 = "update account set money = 3000 where id = 1";
String sql2 = "update account set money = 3000 where id = 2";
//4. 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
try {
// ============开启事务==========
conn.setAutoCommit(false);
//5. 执行sql
int count1 = stmt.executeUpdate(sql1);//受影响的行数
//6. 处理结果
System.out.println(count1);
int i = 3/0;
//5. 执行sql
int count2 = stmt.executeUpdate(sql2);//受影响的行数
//6. 处理结果
System.out.println(count2);
// ============提交事务==========
//程序运行到此处,说明没有出现任何问题,则需求提交事务
conn.commit();
} catch (Exception e) {
// ============回滚事务==========
//程序在出现异常时会执行到这个地方,此时就需要回滚事务
conn.rollback();
e.printStackTrace();
}
//7. 释放资源
stmt.close();
conn.close();
}
}
2.3 Statement
2.3.1 概述
Statement对象的作用就是用来执行SQL语句。而针对不同类型的SQL语句使用的方法也不一样。
执行DDL、DML语句
执行DQL语句
2.3.2 代码实现
执行DML语句
/**
* 执行DML语句
* @throws Exception
*/
@Test
public void testDML() throws Exception {
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql = "update account set money = 3000 where id = 1";
//4. 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//5. 执行sql
int count = stmt.executeUpdate(sql);//执行完DML语句,受影响的行数
//6. 处理结果
//System.out.println(count);
if(count > 0){
System.out.println("修改成功~");
}else{
System.out.println("修改失败~");
}
//7. 释放资源
stmt.close();
conn.close();
}
执行DDL语句
/**
* 执行DDL语句
* @throws Exception
*/
@Test
public void testDDL() throws Exception {
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql = "drop database db2";
//4. 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//5. 执行sql
int count = stmt.executeUpdate(sql);//执行完DDL语句,可能是0
//6. 处理结果
System.out.println(count);
//7. 释放资源
stmt.close();
conn.close();
}
注意: 开发很少使用java代码操作DDL语句
2.4 ResultSet
2.4.1 概述
ResultSet(结果集对象)作用:
封装了SQL查询语句的结果。
而执行了DQL语句后就会返回该对象,对应执行DQL语句的方法如下:
ResultSet executeQuery(sql):执行DQL 语句,返回 ResultSet 对象
那么我们就需要从 ResultSet
对象中获取我们想要的数据。ResultSet
对象提供了操作查询结果数据的方法,如下:
boolean next()
将光标从当前位置向前移动一行
判断当前行是否为有效行
方法返回值说明:
true : 有效航,当前行有数据
false : 无效行,当前行没有数据
xxx getXxx(参数):获取数据
xxx : 数据类型;如: int getInt(参数) ;String getString(参数)
参数
int类型的参数:列的编号,从1开始
String类型的参数: 列的名称
如下图为执行SQL语句后的结果
一开始光标指定于第一行前,如图所示红色箭头指向于表头行。当我们调用了 next()
方法后,光标就下移到第一行数据,并且方法返回true,此时就可以通过 getInt("id")
获取当前行id字段的值,也可以通过 getString("name")
获取当前行name字段的值。如果想获取下一行的数据,继续调用 next()
方法,以此类推。
2.4.2 代码实现
/**
* 执行DQL
* @throws Exception
*/
@Test
public void testResultSet() throws Exception {
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql = "select * from account";
//4. 获取statement对象
Statement stmt = conn.createStatement();
//5. 执行sql
ResultSet rs = stmt.executeQuery(sql);
//6. 处理结果, 遍历rs中的所有数据
/* // 6.1 光标向下移动一行,并且判断当前行是否有数据
while (rs.next()){
//6.2 获取数据 getXxx()
int id = rs.getInt(1);
String name = rs.getString(2);
double money = rs.getDouble(3);
System.out.println(id);
System.out.println(name);
System.out.println(money);
System.out.println("--------------");
}*/
// 6.1 光标向下移动一行,并且判断当前行是否有数据
while (rs.next()){
//6.2 获取数据 getXxx()
int id = rs.getInt("id");
String name = rs.getString("name");
double money = rs.getDouble("money");
System.out.println(id);
System.out.println(name);
System.out.println(money);
System.out.println("--------------");
}
//7. 释放资源
rs.close();
stmt.close();
conn.close();
}
2.5 案例
需求:查询account账户表数据,封装为Account对象中,并且存储到ArrayList集合中
2.5.1 代码实现
/**
* 查询account账户表数据,封装为Account对象中,并且存储到ArrayList集合中
* 1. 定义实体类Account
* 2. 查询数据,封装到Account对象中
* 3. 将Account对象存入ArrayList集合中
*/
@Test
public void testResultSet2() throws Exception {
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql = "select * from account";
//4. 获取statement对象
Statement stmt = conn.createStatement();
//5. 执行sql
ResultSet rs = stmt.executeQuery(sql);
// 创建集合
List<Account> list = new ArrayList<>();
// 6.1 光标向下移动一行,并且判断当前行是否有数据
while (rs.next()){
Account account = new Account();
//6.2 获取数据 getXxx()
int id = rs.getInt("id");
String name = rs.getString("name");
double money = rs.getDouble("money");
//赋值
account.setId(id);
account.setName(name);
account.setMoney(money);
// 存入集合
list.add(account);
}
System.out.println(list);
//7. 释放资源
rs.close();
stmt.close();
conn.close();
}
2.6 PreparedStatement
PreparedStatement作用:
预编译SQL语句并执行:预防SQL注入问题
2.6.1 SQL注入
SQL注入是通过操作输入来修改事先定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法。
2.6.2 代码模拟SQL注入问题
@Test
public void testLogin() throws Exception {
//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
// 接收用户输入 用户名和密码
String name = "sjdljfld";
String pwd = "' or '1' = '1";
String sql = "select * from tb_user where username = '"+name+"' and password = '"+pwd+"'";
// 获取stmt对象
Statement stmt = conn.createStatement();
// 执行sql
ResultSet rs = stmt.executeQuery(sql);
// 判断登录是否成功
if(rs.next()){
System.out.println("登录成功~");
}else{
System.out.println("登录失败~");
}
//7. 释放资源
rs.close();
stmt.close();
conn.close();
}
上面代码是将用户名和密码拼接到sql语句中,拼接后的sql语句如下:
select * from tb_user where username = 'sjdljfld' and password = ''or '1' = '1'
从上面语句可以看出条件 username = 'sjdljfld' and password = ''
不管是否满足,而 or
后面的 '1' = '1'
是始终满足的,最终条件是成立的,就可以正常的进行登陆了。
2.6.3 PreparedStatement概述
PreparedStatement作用:
预编译SQL语句并执行:预防SQL注入问题
获取 PreparedStatement 对象
// SQL语句中的参数值,使用?占位符替代
String sql = "select * from user where username = ? and password = ?";
// 通过Connection对象获取,并传入对应的sql语句
PreparedStatement pstmt = conn.prepareStatement(sql);
-
设置参数值
上面的sql语句中参数使用 ? 进行占位,在之前之前肯定要设置这些 ? 的值。
PreparedStatement对象:setXxx(参数1,参数2):给 ? 赋值
-
Xxx:数据类型 ; 如 setInt (参数1,参数2)
-
参数:
-
参数1: ?的位置编号,从1 开始
-
参数2: ?的值
-
-
-
执行SQL语句
executeUpdate(); 执行DDL语句和DML语句
executeQuery(); 执行DQL语句
==注意:==
-
调用这两个方法时不需要传递SQL语句,因为获取SQL语句执行对象时已经对SQL语句进行预编译了。
-
2.6.4 使用PreparedStatement改进
@Test
public void testPreparedStatement() throws Exception {
//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
// 接收用户输入 用户名和密码
String name = "zhangsan";
String pwd = "' or '1' = '1";
// 定义sql
String sql = "select * from tb_user where username = ? and password = ?";
// 获取pstmt对象
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置?的值
pstmt.setString(1,name);
pstmt.setString(2,pwd);
// 执行sql
ResultSet rs = pstmt.executeQuery();
// 判断登录是否成功
if(rs.next()){
System.out.println("登录成功~");
}else{
System.out.println("登录失败~");
}
//7. 释放资源
rs.close();
pstmt.close();
conn.close();
}
执行上面语句就可以发现不会出现SQL注入漏洞问题了。那么PreparedStatement又是如何解决的呢?它是将特殊字符进行了转义,转义的SQL如下:
select * from tb_user where username = 'sjdljfld' and password = '\'or \'1\' = \'1'
2.6.5 PreparedStatement原理
PreparedStatement 好处:
预编译SQL,性能更高
防止SQL注入:将敏感字符进行转义
Java代码操作数据库流程如图所示:
-
将sql语句发送到MySQL服务器端
-
MySQL服务端会对sql语句进行如下操作
-
检查SQL语句
检查SQL语句的语法是否正确。
-
编译SQL语句。将SQL语句编译成可执行的函数。
检查SQL和编译SQL花费的时间比执行SQL的时间还要长。如果我们只是重新设置参数,那么检查SQL语句和编译SQL语句将不需要重复执行。这样就提高了性能。
-
执行SQL语句
-
接下来我们通过查询日志来看一下原理。
-
开启预编译功能
在代码中编写url时需要加上以下参数。而我们之前根本就没有开启预编译功能,只是解决了SQL注入漏洞。
useServerPrepStmts=true
-
配置MySQL执行日志(重启mysql服务后生效)
在mysql配置文件(my.ini)中添加如下配置
log-output=FILE general-log=1 general_log_file="D:\mysql.log" slow-query-log=1 slow_query_log_file="D:\mysql_slow.log" long_query_time=2
-
java测试代码如下:
/**
* PreparedStatement原理
* @throws Exception
*/
@Test
public void testPreparedStatement2() throws Exception {
//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
// useServerPrepStmts=true 参数开启预编译功能
String url = "jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
// 接收用户输入 用户名和密码
String name = "zhangsan";
String pwd = "' or '1' = '1";
// 定义sql
String sql = "select * from tb_user where username = ? and password = ?";
// 获取pstmt对象
PreparedStatement pstmt = conn.prepareStatement(sql);
Thread.sleep(10000);
// 设置?的值
pstmt.setString(1,name);
pstmt.setString(2,pwd);
ResultSet rs = null;
// 执行sql
rs = pstmt.executeQuery();
// 设置?的值
pstmt.setString(1,"aaa");
pstmt.setString(2,"bbb");
// 执行sql
rs = pstmt.executeQuery();
// 判断登录是否成功
if(rs.next()){
System.out.println("登录成功~");
}else{
System.out.println("登录失败~");
}
//7. 释放资源
rs.close();
pstmt.close();
conn.close();
}
执行SQL语句,查看 D:\mysql.log
日志如下:
上图中第三行中的 Prepare
是对SQL语句进行预编译。第四行和第五行是执行了两次SQL语句,而第二次执行前并没有对SQL进行预编译。
小结 :
1. 在获取PreparedStatement对象时,将sql语句发送给mysql服务器进行检查,编译(这些步骤很耗时)
2. 执行时就不用再进行这些步骤了,速度更快
3. 如果sql模板一样,则只需要进行一次检查、编译
03 - 数据库连接池
3.1 数据库连接池简介
数据库连接池是个容器,负责分配、管理数据库连接(Connection)
它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏
好处
资源重用
提升系统响应速度
避免数据库连接遗漏
之前代码中使用连接是没有使用都创建一个Connection对象,使用完毕就会将其销毁。这样重复创建销毁的过程是特别耗费计算机的性能的及消耗时间的。
而数据库使用了数据库连接池后,就能达到Connection对象的复用,如下图:
连接池是在一开始就创建好了一些连接(Connection)对象存储起来。用户需要连接数据库时,不需要自己创建连接,而只需要从连接池中获取一个连接进行使用,使用完毕后再将连接对象归还给连接池;这样就可以起到资源重用,也节省了频繁创建连接销毁连接所花费的时间,从而提升了系统响应的速度。
3.2 数据库连接池实现
3.2.1 标准接口:DataSource
官方(SUN) 提供的数据库连接池标准接口,由第三方组织实现此接口。该接口提供了获取连接的功能:
Connection getConnection()
那么以后就不需要通过 DriverManager 对象获取 Connection 对象,而是通过连接池(DataSource)获取 Connection 对象。
3.2.2 常见的数据库连接池
DBCP
C3P0
Druid
我们现在使用更多的是Druid,它的性能比其他两个会好一些。
3.2.3 Druid(德鲁伊)
Druid连接池是阿里巴巴开源的数据库连接池项目
功能强大,性能优秀,是Java语言最好的数据库连接池之一
3.3 Driud使用
1. 导入jar包 druid-1.1.12.jar
2. 定义配置文件
3. 加载配置文件
4. 获取数据库连接池对象
5. 获取连接
现在通过代码实现,首先需要先将druid的jar包放到项目下的lib下并添加为库文件:
项目结构如下 :
编写配置文件如下:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true
username=root
password=1234
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000
使用druid的代码如下:
/**
* Druid数据库连接池演示
*/
public class DruidDemo {
public static void main(String[] args) throws Exception {
//1.导入jar包
//2.定义配置文件
//3. 加载配置文件
Properties prop = new Properties();
prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));
//4. 获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//5. 获取数据库连接 Connection
Connection connection = dataSource.getConnection();
System.out.println(connection); //获取到了连接后就可以继续做其他操作了
//System.out.println(System.getProperty("user.dir"));
}
}
三、Maven
01 - Maven简介
1.1 什么是Maven
Maven是专门用于管理和构建Java项目的工具,它的主要功能有:
提供了一套标准化的项目结构
提供了一套标准化的构建流程(编译,测试,打包,发布……)
提供了一套依赖管理机制
1.2 Maven有什么好处(为什么要使用Maven)
标准化的项目结构
每一个开发工具(IDE)都有自己不同的项目结构,它们互相之间不通用。我再eclipse中创建的目录,无法在idea中进行使用,这就造成了很大的不方便,如下图:前两个是以后开发经常使用的开发工具
而Maven提供了一套标准化的项目结构,所有的IDE使用Maven构建的项目完全一样,所以IDE创建的Maven项目可以通用。如下图右边就是Maven构建的项目结构。
标准化的构建流程
如上图所示我们开发了一套系统,代码需要进行编译、测试、打包、发布,这些操作如果需要反复进行就显得特别麻烦,而Maven提供了一套简单的命令来完成项目构建。
依赖管理
依赖管理其实就是管理你项目所依赖的第三方资源(jar包、插件)。如之前我们项目中需要使用JDBC和Druid的话, 就需要去网上下载对应的依赖包 ,复制到项目中,还要将jar包加入工作环境这一系列的操作。如下图所示
而Maven使用标准的 ==坐标== 配置来管理各种依赖,只需要简单的配置就可以完成依赖管理。
如上图右边所示就是mysql驱动包的坐标,在项目中只需要写这段配置,其他都不需要我们担心,Maven都帮我们进行操作了。
1.3 Maven模型
项目对象模型 (Project Object Model)
依赖管理模型(Dependency)
插件(Plugin)
如上图所示就是Maven的模型,而我们先看紫色框框起来的部分,他就是用来完成标准化构建流程。如我们需要编译,Maven提供了一个编译插件供我们使用,我们需要打包,Maven就提供了一个打包插件提供我们使用等。
上图中紫色框起来的部分,项目对象模型就是将我们自己抽象成一个对象模型,有自己专属的坐标,如下图所示是一个Maven项目:
依赖管理模型则是使用坐标来描述当前项目依赖哪儿些第三方jar包,如下图所示
上述Maven模型图中还有一部分是仓库。如何理解仓库呢?
1.4 仓库
1.4.1 仓库的作用
大家想想这样的场景,我们创建Maven项目,在项目中使用坐标来指定项目的依赖,那么依赖的jar包到底存储在什么地方呢?其实依赖jar包是存储在我们的本地仓库中。而项目运行时从本地仓库中拿需要的依赖jar包。
1.4.2 仓库分类
本地仓库:自己计算机上的一个目录
中央仓库:由Maven团队维护的全球唯一的仓库
远程仓库(私服):一般由公司团队搭建的私有仓库
当项目中使用坐标引入对应依赖jar包后,首先会查找本地仓库中是否有对应的jar包:
如果有,则在项目直接引用;
如果没有,则去中央仓库中下载对应的jar包到本地仓库。
如果还可以搭建远程仓库,将来jar包的查找顺序则变为:
本地仓库 --> 远程仓库--> 中央仓库
02 - Maven安装配置
2.1 安装
解压 apache-maven-3.6.1.rar 既安装完成
解压缩后的目录结构如下:
bin目录 : 存放的是可执行命令。mvn 命令重点关注。
conf目录 :存放Maven的配置文件。settings.xml
配置文件后期需要修改。
lib目录 :存放Maven依赖的jar包。Maven也是使用java开发的,所以它也依赖其他的jar包。
2.2 配置环境变量
配置环境变量 MAVEN_HOME 为安装路径的bin目录
此电脑
右键 --> 高级系统设置
--> 高级
--> 环境变量
在系统变量处新建一个变量 MAVEN_HOME
在 Path
中进行配置
打开命令提示符进行验证,出现如图所示表示安装成功
2.3 配置本地仓库
修改 conf/settings.xml 中的 <localRepository> 为一个指定目录作为本地仓库,用来存储jar包。
2.4 配置阿里云私服
中央仓库在国外,所以下载jar包速度可能比较慢,而阿里公司提供了一个远程仓库,里面基本也都有开源项目的jar包。
修改 conf/settings.xml 中的 <mirrors>标签,为其添加如下子标签:
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
03 - Maven基本使用
3.1 Maven 常用命令
-
compile :编译
-
clean:清理
-
test:测试
-
package:打包
-
install:安装
3.1.1 命令演示
项目结构如下:
而我们使用上面命令需要在磁盘上进入到项目的 pom.xml
目录下,打开命令提示符
3.1.2 编译命令演示
compile :编译
执行上述命令可以看到:
-
从阿里云下载编译需要的插件的jar包,在本地仓库也能看到下载好的插件
-
在项目下会生成一个
target
目录
同时在项目下会出现一个 target
目录,编译后的字节码文件就放在该目录下
3.1.3 清理命令演示
mvn clean
执行上述命令可以看到
-
从阿里云下载清理需要的插件jar包
-
删除项目下的
target
目录
3.1.4 打包命令演示
mvn package
执行上述命令可以看到:
-
从阿里云下载打包需要的插件jar包
-
在项目的
terget
目录下有一个jar包(将当前项目打成的jar包)
3.1.5 测试命令演示
mvn test
该命令会执行所有的测试代码。执行上述命令效果如下
3.1.6 安装命令演示
mvn install
该命令会将当前项目打成jar包,并安装到本地仓库。执行完上述命令后到本地仓库查看结果如下:
3.2 Maven 生命周期
Maven 构建项目生命周期描述的是一次构建过程经历经历了多少个事件
Maven 对项目构建的生命周期划分为3套:
-
clean :清理工作。
-
default :核心工作,例如编译,测试,打包,安装等。
-
site : 产生报告,发布站点等。这套声明周期一般不会使用。
同一套生命周期内,执行后边的命令,前面的所有命令会自动执行。例如默认(default)生命周期如下:
当我们执行 install
(安装)命令时,它会先执行 compile
命令,再执行 test
命令,再执行 package
命令,最后执行 install
命令。
当我们执行 package
(打包)命令时,它会先执行 compile
命令,再执行 test
命令,最后执行 package
命令。
默认的生命周期也有对应的很多命令,其他的一般都不会使用,我们只关注常用的:
04 - IDEA使用Maven
以后开发中我们肯定会在高级开发工具中使用Maven管理项目,而我们常用的高级开发工具是IDEA,所以接下来我们会讲解Maven在IDEA中的使用。
4.1 IDEA配置Maven环境
我们需要先在IDEA中配置Maven环境:
选择 IDEA中 File --> Settings
搜索maven
设置 IDEA 使用本地安装的 Maven,并修改配置文件路径
4.2 Maven 坐标详解
4.2.1 什么是坐标
Maven 中的坐标是资源的唯一标识
使用坐标来定义项目或引入项目中需要的依赖
4.2.2 Maven 坐标主要组成
-
groupId:定义当前Maven项目隶属组织名称(通常是域名反写,例如:com.itheima)
-
artifactId:定义当前Maven项目名称(通常是模块名称,例如 order-service、goods-service)
-
version:定义当前项目版本号
如下图就是使用坐标表示一个项目:
注意:
上面所说的资源可以是插件、依赖、当前项目。
我们的项目如果被其他的项目依赖时,也是需要坐标来引入的。
4.3 IDEA 创建 Maven项目
创建模块,选择Maven,点击Next
填写模块名称,坐标信息,点击finish,创建完成
创建好的项目目录结构如下:
编写 HelloWorld,并运行
4.4 IDEA 导入 Maven项目
选择右侧Maven面板,点击 + 号
选中对应项目的pom.xml文件,双击即可
4.4.1 配置 Maven-Helper 插件
选择 IDEA中 File --> Settings
选择 Plugins
搜索 Maven,选择第一个 Maven Helper,点击Install安装,弹出面板中点击Accept
重启 IDEA
安装完该插件后可以通过 选中项目右键进行相关命令操作,如下图所示:
05 - 依赖管理
5.1 使用坐标引入jar包
5.1.1 使用坐标引入jar包的步骤
-
在项目的 pom.xml 中编写 <dependencies> 标签
-
在 <dependencies> 标签中 使用 <dependency> 引入坐标
-
定义坐标的 groupId,artifactId,version
点击刷新按钮,使坐标生效
注意:
-
具体的坐标我们可以到如下网站进行搜索
5.1.2 自动导入设置
选择 IDEA中 File --> Settings
在弹出的面板中找到 Build Tools
选择 Any changes,点击 ok 即可生效
5.2 依赖范围
通过设置坐标的依赖范围(scope),可以设置 对应jar包的作用范围:编译环境、测试环境、运行环境。
如下图所示给 junit
依赖通过 scope
标签指定依赖的作用范围。 那么这个依赖就只能作用在测试环境,其他环境下不能使用。
那么 scope
都可以有哪些取值呢?
依赖范围 | 编译classpath | 测试classpath | 运行classpath | 例子 |
campile | Y | Y | Y | logback |
test | - | Y | - | Junit |
provided | Y | Y | - | servlet-api |
runtime | - | Y | Y | jbdc驱动 |
system | Y | Y | - | 存储在本地的jar包 |
-
compile:作用于编译环境、测试环境、运行环境。
-
test:作用于测试环境。典型的就是Junit坐标,以后使用Junit时,都会将scope指定为该值
-
provided:作用于编译环境、测试环境。
-
runtime:作用于测试环境、运行环境。jdbc驱动一般将
scope
设置为该值,当然不设置也没有任何问题 -
注意:如果引入坐标不指定
scope
标签时,默认就是 compile 值。以后大部分jar包都是使用默认值。
四、Mybatis
01 - Mybatis概述
1.1 Mybatis概念
-
MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发
-
MyBatis 本是 Apache 的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github
1.1.1 持久层
-
负责将数据到保存到数据库的那一层代码。
以后开发我们会将操作数据库的Java代码作为持久层。而Mybatis就是对jdbc代码进行了封装。
-
JavaEE三层架构:表现层、业务层、持久层
1.1.2 框架
-
框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型
-
在框架的基础之上构建软件编写更加高效、规范、通用、可扩展
1.2 JDBC 缺点
下面是 JDBC 代码,我们通过该代码分析都存在什么缺点:
-
硬编码
-
注册驱动、获取连接
上图标1的代码有很多字符串,而这些是连接数据库的四个基本信息,以后如果要将Mysql数据库换成其他的关系型数据库的话,这四个地方都需要修改,如果放在此处就意味着要修改我们的源代码。
-
SQL语句
上图标2的代码。如果表结构发生变化,SQL语句就要进行更改。这也不方便后期的维护。
-
-
操作繁琐
-
手动设置参数
-
手动封装结果集
上图标4的代码是对查询到的数据进行封装,而这部分代码是没有什么技术含量,而且特别耗费时间的。
-
1.3 Mybatis 优化
-
硬编码可以配置到配置文件
-
操作繁琐的地方mybatis都自动完成
如图所示
02 - Mybatis快速入门
2.1 演示
需求:查询user表中所有的数据
创建user表,添加数据
create database mybatis;
use mybatis;
drop table if exists tb_user;
create table tb_user(
id int primary key auto_increment,
username varchar(20),
password varchar(20),
gender char(1),
addr varchar(30)
);
INSERT INTO tb_user VALUES (1, 'zhangsan', '123', '男', '北京');
INSERT INTO tb_user VALUES (2, '李四', '234', '女', '天津');
INSERT INTO tb_user VALUES (3, '王五', '11', '男', '西安');
创建模块,导入坐标
在创建好的模块中的 pom.xml 配置文件中添加依赖的坐标
<dependencies>
<!--mybatis 依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!--mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--junit 单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- 添加slf4j日志api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.20</version>
</dependency>
<!-- 添加logback-classic依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- 添加logback-core依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
注意:需要在项目的 resources 目录下创建logback的配置文件
编写 MyBatis 核心配置文件 -- > 替换连接信息 解决硬编码问题
在模块下的 resources 目录下创建mybatis的配置文件 mybatis-config.xml
,内容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.pojo"/>
</typeAliases>
<!--
environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment
-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--加载sql映射文件-->
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
编写 SQL 映射文件 --> 统一管理sql语句,解决硬编码问题
在模块的 resources
目录下创建映射配置文件 UserMapper.xml
,内容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
<select id="selectAll" resultType="pojo.User">
select * from tb_user;
</select>
</mapper>
编码
在 com.mapper 包下创建 User类
public class User {
private Integer id;
private String username;
private String password;
private String gender;
private String addr;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", gender='" + gender + '\'' +
", addr='" + addr + '\'' +
'}';
}
}
在 com
包下编写 MybatisDemo 测试类
public class MyBatisDemo {
public static void main(String[] args) throws IOException {
//1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 执行sql
List<User> users = sqlSession.selectList("test.selectAll"); //参数是一个字符串,该字符串必须是映射配置文件的namespace.id
System.out.println(users);
//4. 释放资源
sqlSession.close();
}
}
2.2 解决SQL映射文件的警告提示
SQL映射配置文件中存在报红的情况。问题如下:
产生的原因:Idea和数据库没有建立连接,不识别表信息。但是大家一定要记住,它并不影响程序的执行。
2.3 解决方式
IDEA中配置MySQL数据库连接
点击IDEA右边框的 Database
,在展开的界面点击 +
选择 Data Source
,再选择 MySQL
在弹出的界面进行基本信息的填写
点击完成后就能看到如下界面
而此界面就和 navicat
工具一样可以进行数据库的操作。也可以编写SQL语句
03 - Mapper代理开发
3.1 Mapper代理开发概述
之前我们写的代码是基本使用方式,它也存在硬编码的问题,如下:
这里调用 selectList()
方法传递的参数是映射配置文件中的 namespace.id值。这样写也不便于后期的维护。如果使用 Mapper 代理方式(如下图)则不存在硬编码问题。
通过上面的描述可以看出 Mapper 代理方式的目的:
-
解决原生方式中的硬编码
-
简化后期执行SQL
Mybatis 官网也是推荐使用 Mapper 代理的方式。下图是截止官网的图片
3.2 使用Mapper代理要求
使用Mapper代理方式,必须满足以下要求:
1. 定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下。
2. 设置SQL映射文件的namespace属性为Mapper接口全限定名。
3. 在 Mapper 接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致。
3.3 案例代码实现
在 com.mapper
包下创建 UserMapper接口,代码如下:
public interface UserMapper {
List<User> selectAll();
User selectById(int id);
}
在 resources
下创建 com/mapper
目录,并在该目录下创建 UserMapper.xml 映射配置文件
<!--
namespace:名称空间。必须是对应接口的全限定名
-->
<mapper namespace="com.mapper.UserMapper">
<select id="selectAll" resultType="com.pojo.User">
select *
from tb_user;
</select>
</mapper>
在 com
包下创建 MybatisDemo2 测试类,代码如下:
/**
* Mybatis 代理开发
*/
public class MyBatisDemo2 {
public static void main(String[] args) throws IOException {
//1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 执行sql
//3.1 获取UserMapper接口的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();
System.out.println(users);
//4. 释放资源
sqlSession.close();
}
}
注意:
如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载。也就是将核心配置文件的加载映射配置文件的配置修改为
<mappers>
<!--加载sql映射文件-->
<!-- <mapper resource="com/mapper/UserMapper.xml"/>-->
<!--Mapper代理方式-->
<package name="com.mapper"/>
</mappers>
04 - 核心配置文件
我们可以通过查询官网看可以配置的内容
接下来我们先对里面的一些配置进行讲解。
4.1 多环境配置
在核心配置文件的 environments
标签中其实是可以配置多个 environment
,使用 id
给每段环境起名,在 environments
中使用 default='环境id'
来指定使用哪儿段配置。我们一般就配置一个 environment
即可。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>=
4.2 类型别名
在映射配置文件中的 resultType
属性需要配置数据封装的类型(类的全限定名)。而每次这样写是特别麻烦的,Mybatis 提供了 类型别名
(typeAliases) 可以简化这部分的书写。
首先需要现在核心配置文件中配置类型别名,也就意味着给pojo包下所有的类起了别名(别名就是类名),不区分大小写。内容如下:
<typeAliases>
<!--name属性的值是实体类所在包-->
<package name="com.pojo"/>
</typeAliases>
通过上述的配置,我们就可以简化映射配置文件中 resultType
属性值的编写
<mapper namespace="com.mapper.UserMapper">
<select id="selectAll" resultType="user">
select * from tb_user;
</select>
</mapper>