一、初识 MySQL
1. 为什么学习数据库
- 存储数据的方法;
- 程序、网站中、大量数据长久保存;
- 数据库是几乎软件体系中最核心的一个存在。
2. 数据库概述
- 数据库 ( DataBase , 简称DB );
- 概念 : 长期存放在计算机内,有组织,可共享的,大量数据的集合,是一个数据 仓库 ;
- 作用 : 保存数据,并能安全管理数据(如:增删改查等),减少冗余...
- 分类 :
- 关系型数据库 ( SQL ) :
- MySQL、Oracle 、SQL Server 、SQLite 、DB2 ... ;
- 通过外键关联,来建立表与表之间的关系。
- 非关系型数据库 ( NoSQL 不只是数据库 ) :
- Redis、MongoDB ...;
- 数据以对象的形式,存储在数据库中,而对象之间的关系,通过每个对象自身的属性来决定。
- 关系型数据库 ( SQL ) :
3. DBMS
- 数据库管理系统 ( DataBase Management System );
- 数据库管理软件:科学组织和存储数据 , 高效地获取和维护数据;
- MySQL 只是一个数据库管理系统。
4. MySQL 简介 官网 MySQL8 文档
- 概念 : 是现在流行的开源、免费、关系型数据库;
- 历史 : 由瑞典 MySQL AB 公司开发,目前属于 Oracle 旗下产品;
- 特点 :
- 免费 , 开源数据库;
- 小巧 , 功能齐全;
- 使用便捷;
- 可运行于 Windows 或 Linux 操作系统;
- 可适用于中小型,甚至大型网站应用。
5. 安装 MySQL
6. SQLyog 下载地址
- 可手动操作,管理 MySQL 数据库的软件工具;
- 特点 : 简洁 、易用、 图形化;
使用 SQLyog 创建数据库及数据表:
-
使用 SQLyog 连接本地 MySQL 数据库 :
-
创建数据库:
-
创建数据表:
-
在历史记录中,可以看到相对应的数据库操作的语句。
7. 命令行连接数据库
-
打开 MySQL 命令窗口:
- 在 DOS 命令行进入:
安装目录\mysql\bin; - 设置环境变量后,可以在任意目录打开;
- 在 DOS 命令行进入:
-
连接数据库语句 :
mysql -h 服务器主机地址 -u 用户名 -p用户密码; -
注意 : -p 后面不能加空格(空格会作为密码的一部分),导致登录失败 !
-
数据库操作常用命令:命令以英文
;结尾,Ctrl + c强制终止;-- 单行注释 /* 多行注释 */ -- 修改密码 mysql8 alter user 'root'@'localhost'IDENTIFIED BY '新密码'; -- 修改密码 5.7版本 update user set password=password('新密码')where user='root'; -- 刷新数据库 flush privileges; -- 显示所有数据库 show databases; -- 创建数据库 create database dbname; -- 使用数据库 use dbname; -- 创建表 create table name; -- 显示数据库表 show tables; -- 显示数据表的信息 describe name; -- 退出Mysql exit; -- 查看帮助 ? 命令关键词;
二、操作数据库
1. 结构化查询语句分类
| 名称 | 解释 | 命令 |
|---|---|---|
| DDL(数据定义语言) | 定义和管理数据对象,如数据库,数据表等 | CREATE、DROP、 ALTER |
| DML(数据操作语言) | 用于操作数据库对象中所包含的数据 | INSERT、UPDATE、 DELETE |
| DQL(数据查询语言) | 用于查询数据库数据(重点) | SELECT |
| DCL(数据控制语言) | 用于管理数据库的语言,包括管理权限及数据更改 | GRANT、commit、 rollback |
2. 数据库操作
命令行操作数据库(重点)
-- [ ] 可选
-- 创建数据库
CREATE DATABASE [IF NOT EXISTS] 数据库名;
-- 删除数据库
DROP DATABASE [IF EXISTS] 数据库名;
-- 查看数据库
SHOW DATABASES;
-- 使用数据库
USE 数据库名;
工具操作数据库
- 学习方法:对照 SQLyog 工具,自动生成的语句学习;
- 固定语法中的单词,需要记忆;
3. 数据值和列类型(了解)
- 列类型 : 规定数据库中,该列存放的数据类型;
- 数据类型,对数据库的优化非常重要;
- 分三类:数值、日期/时间、字符串(字符)类型。
数值类型
- tinyint:小整数,1个字节;
- smallint:较小整数,2个字节;
- mediumint:中等整数,3个字节;
- int(常用):标准整数,4个字节;
- bigint:大整数,8个字节;
- float:浮点数,4个字节;
- double:浮点数,8个字节;
- decimal:字符串浮点数,金融计算。
字符串类型
- char:字符串固定大小,
0~255字节; - varchar(常用):可变字符串,
0~65535字节; - tinytext:微型文本,
2^8-1字节; - text:文本串,
2^16-1字节。
| 类型 | 大小 | 用途 |
|---|---|---|
| CHAR | 0-255字节 | 定长字符串 |
| VARCHAR | 0-65535 字节 | 变长字符串 |
| TINYBLOB | 0-255字节 | 不超过 255 个字符的二进制字符串 |
| TINYTEXT | 0-255字节 | 短文本字符串 |
| BLOB | 0-65535字节 | 二进制形式的长文本数据 |
| TEXT | 0-65535字节 | 长文本数据 |
| MEDIUMBLOB | 0-16777215字节 | 二进制形式的中等长度文本数据 |
| MEDIUMTEXT | 0-16777215字节 | 中等长度文本数据 |
| LONGBLOB | 0-4294967295字节 | 二进制形式的极大文本数据 |
| LONGTEXT | 0-4294967295字节 | 极大文本数据 |
日期/时间类型
- date:
YYYY-MM-DD,日期格式; - time:
HH:mm:ss,时间格式; - datatime(常用):
YYYY-MM-DD HH:mm:ss; - timestamp:时间戳,
1970.1.1 ~ 现在的毫秒数; - year:年份。
Null 值
- 理解为 没有值 或 未知值;
- 注意:不要用 NULL 进行算术运算 , 结果仍为NULL。
4. 数据字段属性(重点)
UnSigned:不允许负数- 无符号的;
- 声明该数据列不允许负数。
Zerofill:补 0- 用 0 填充;
- 不足位数的用 0 来填充 , 如 int(3),5 则为 005。
- 自增:
Auto_InCrement- 自动增长,每添加一条数据,自动在上一个记录数上加 1(默认);
- 通常用于设置 主键 , 且为整数类型;
- 可定义起始值和步长:
- 当前表设置步长(AUTO_INCREMENT=100):只影响当前表;
SET @@auto_increment_increment=5;影响所有使用自增的表(全局)。
- 非空:
- 默认为 NULL,即没有插入该列的数值;
- 如果设置为 NOT NULL , 则该列 必须有值。
- 默认:
- 用于设置默认值;
- 例如:
- 性别字段,默认为 "男" , 否则为 "女" ;
- 若无指定,则默认值为 "男"。
5. 创建数据表(DDL 的一种)
- 手动创建数据表
-- 目标:创建一个school数据库
-- 创建学生表(列,字段)
-- 学号int 登录密码varchar(20) 姓名,性别varchar(2),出生日期(datatime),家庭住址,email
-- 创建表之前 , 一定要先选择数据库
CREATE TABLE IF NOT EXISTS `student`(
-- MySQL8 INT(M) 改为 INT
`id` INT NOT NULL AUTO_INCREMENT COMMENT '学号',
`name` VARCHAR(30) NOT NULL DEFAULT '匿名' COMMENT '姓名',
`pwd` VARCHAR(20) NOT NULL DEFAULT '123456' COMMENT '密码',
`sex` VARCHAR(2) NOT NULL DEFAULT '男' COMMENT '性别',
`brithday` DATETIME DEFAULT NULL COMMENT '生日',
`address` VARCHAR(100) DEFAULT NULL COMMENT '地址',
`email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
-- 设置主键
PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8mb4
-
MySQL8 中更改内容:
- 需要将 INT(M) 改为 INT,否则告警;
- 字符集格式:
utf8mb4,或在 my.ini 中做相应配置,否则告警;
-
创建数据表格式:DDL
CREATE TABLE [IF NOT EXISTS] `表名`(
'字段名1' 列类型 [属性][索引][注释],
'字段名2' 列类型 [属性][索引][注释],
#...
'字段名n' 列类型 [属性][索引][注释]
)[表类型][表字符集][注释];
- 常用命令(重点):
-- 查看数据库的定义
show create database 数据库名;
-- 查看数据表的定义
show create table 数据表名;
-- 显示表结构
DESC 数据表名;
-- 设置严格检查模式(不能容错了)
SET sql_mode='STRICT_TRANS_TABLES';
注意点:
- AUTO_INCREMENT 自增;
- PRIMARY KEY:主键,一般一个表只有一个唯一的主键;
- 使用英文
(),表的名称和字段,尽量使用反引号括起来; 反引号用于区别 MySQL 保留字与普通字符(键盘esc下面的键)- 字符串使用单引号括起来;
- 所有的语句后面加
,(英文的),最后一个不用加。
规范:
- 每一个表,都必须存在以下五个字段:
- id:主键;
- version:乐观锁;
- is_delete:伪删除;
- gmt_create:创建时间;
- gmt_update:修改时间。
6. 数据表的类型
设置数据表的类型
CREATE TABLE `表名`(
-- 省略一些代码
-- Mysql注释
-- 1. # 单行注释
-- 2. /*...*/ 多行注释
)ENGINE = MyISAM (or InnoDB)
-- 查看mysql所支持的引擎类型 (表类型)
SHOW ENGINES;
- MySQL 的数据表的类型:MyISAM、InnoDB、HEAP、BOB、CSV 等;
- 常用:InnoDB、MyISAM。
| 名称 | MyISAM | InnoDB |
|---|---|---|
| 事务支持 | 不支持 | 支持 |
| 数据行锁定 | 不支持 | 支持 |
| 外键约束 | 不支持 | 支持 |
| 全文索引 | 支持 | 不支持 |
| 表空间大小 | 较小 | 较大,约为 MyISAM 两倍 |
-
适用场合(经验) :
-
MyISAM : 节约空间及相应速度;
-
InnoDB : 安全性 , 事务处理及多用户操作数据表。
-
数据表的存储位置
-
MySQL 数据表以文件方式存放在磁盘中:
- 包括表文件 , 数据文件 , 以及数据库的选项文件;
- 位置:Mysql 安装目录\data\下存放数据表 ;
- 目录名对应数据库名 , 该目录下文件名对应数据表。
-
注意:(MySQL 8,取消了
*.frm)- InnoDB 类型数据表只有一个
*.frm文件 , 以及上一级目录的ibdata1文件; - MyISAM 类型数据表,对应三个文件:
*.frm:表结构定义文件;*.MYD:数据文件(data);*.MYI:索引文件(index)。
- InnoDB 类型数据表只有一个
设置数据表字符集
-
可为数据库、数据表、数据列设定不同的字符集,设定方法 :
-
命令:
CREATE TABLE 表名()CHARSET = utf8mb4; -
MySQL 数据库配置文件
my.ini中的参数设定;# 服务端使用的字符集默认为utf8mb4(mysql8) character-set-server=utf8mb4 -
建议在创建时设置。
-
7. 修改数据库
修改表 ( ALTER TABLE )
-
修改表名:
ALTER TABLE 旧表名 RENAME AS 新表名;alter table `teacher` rename as `teacher1`; -
添加字段:
ALTER TABLE 表名 ADD 字段名 列属性[属性];-- MySQL8 去掉了int位数 alter table `teacher1` add age int; -
修改字段:
ALTER TABLE 表名 MODIFY 字段名 列类型[属性];ALTER TABLE 表名 CHANGE 旧字段名 新字段名 列属性[属性];
-- 修改字段属性 alter table `teacher1` modify age varchar(10); -- 更改字段名,并修改字段属性 alter table `teacher1` change age age01 int;区别:
- change:一般用来重命名字段;
- modify:只能修改字段类型和约束,不能重命名字段。
-
删除字段:
ALTER TABLE 表名 DROP 字段名;alter table `teacher` drop `age`;
删除数据表
- 语法:
DROP TABLE [IF EXISTS] 表名;- IF EXISTS 为可选, 判断是否存在该数据表;
- 删除不存在的数据表,会抛出错误。
drop table if exists `teacher`;
其它:
- 可用反引号(`)为标识符(库名、表名、字段名、索引、别名)包裹,以避免与关键字重名!中文也可以作为标识符;
- 模式通配符:
_:任意单个字符;%: 任意多个字符,甚至包括零字符;- 单引号,需要进行转义
\'。
- SQL 对大小写不敏感 (关键字)。
三、MySQL 数据管理
1. 外键(了解)
外键的概念
- 外键:如果公共关键字,在一个关系中是主关键字,那么这个公共关键字,被称为另一个关系的外键;
- 两个关系之间的相关联系;
- 以另一个关系的外键,作主关键字的表,被称为 主表,具有此外键的表被称为 主表的从表;
- 在实际操作中:
- 将一个表的值,放入第二个表来表示关联,所使用的值,是第一个表的主键值(在必要时可包括复合主键值);
- 第二个表中,保存这些值的属性,称为外键(foreign key)。
- 外键作用:保持数据一致性、完整性,控制存储在外键表中的数据(约束),使两张表形成关联,外键只能引用外表中的列的值,或使用空值。
创建外键
- 方式一:创建子表同时创建外键;
-- 年级表 (id 年级名称)
CREATE TABLE `grade` (
`gradeid` INT NOT NULL AUTO_INCREMENT COMMENT '年级ID',
`gradename` VARCHAR(50) NOT NULL COMMENT '年级名称',
PRIMARY KEY (`gradeid`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4
-- 学生信息表 (学号,姓名,性别,年级,手机,地址,出生日期,邮箱,身份证号)
CREATE TABLE `student` (
`studentno` INT NOT NULL COMMENT '学号',
`studentname` VARCHAR(20) NOT NULL DEFAULT '匿名' COMMENT '姓名',
`sex` TINYINT DEFAULT '1' COMMENT '性别',
`gradeid` INT DEFAULT NULL COMMENT '年级',
`phoneNum` VARCHAR(50) NOT NULL COMMENT '手机',
`address` VARCHAR(255) DEFAULT NULL COMMENT '地址',
`birthday` DATETIME DEFAULT NULL COMMENT '生日',
`email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
`idCard` VARCHAR(18) DEFAULT NULL COMMENT '身份证号',
PRIMARY KEY (`studentno`),
-- 设置外键
KEY `FK_gradeid` (`gradeid`),
-- 外键添加约束(执行引用)references 引用
CONSTRAINT `FK_gradeid` FOREIGN KEY (`gradeid`) REFERENCES `grade` (`gradeid`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4
- 方式二:创建子表完毕后,修改子表添加外键;
-- alter table 表名 add constraint 约束名 foreign key(作为外键的列) references 主表(字段)
alter table `student`
add constraint `FK_gradeid` foreign key(`gradeid`) references `grade`(`gradeid`);
删除外键
- 操作:删除 grade 表,报错
- 注意:删除具有主外键关系的表时,要先删从表,后删主表;
-- 删除外键
ALTER TABLE student DROP FOREIGN KEY FK_gradeid;
-- 发现执行完上面的,索引还在,所以还要删除索引
-- 注:这个索引是建立外键的时候默认生成的
ALTER TABLE student DROP INDEX FK_gradeid;
- 以上操作,都是物理外键,数据库级别的外键,不建议使用!!
- 数据库为单纯的表,只用来存储数据,只有行(数据)和列(字段);
- 多张表同时操作,使用外键时,用程序实现。
2. DML 语言(重点):数据操作语言
- 数据库意义:数据存储、数据管理;
- 管理数据库数据方法:
- 通过 SQLyog 等管理工具,管理数据库数据;
- 通过 DML 语句,管理数据库数据。
- 操作数据库对象,所包含的数据:
- INSERT:添加数据;
- UPDATE:更新数据;
- DELETE:删除数据。
3. 添加数据
INSERT 命令
- 语法:
INSERT INTO 表名[(字段1,字段2,字段3,...)] VALUES('值1','值2','值3')
- 注意:
- 字段或值之间,用英文
,隔开 ; 字段1,字段2..该部分可省略,但添加的值,务必与表结构,数据列,顺序相对应,且数量一致;- 可同时插入多条数据,values 后用英文
,隔开。
- 字段或值之间,用英文
-- INSERT INTO 表名[(字段1,字段2,字段3,...)] VALUES('值1','值2','值3')
INSERT INTO `grade`(`gradename`) VALUES ('大一');
-- 一次插入多条数据
INSERT INTO `grade`(`gradename`) VALUES ('大二'),('大三');
- 主键自增,测试省略字段,进行插入操作:
-- 报错,与表结构不对应
INSERT INTO `grade` VALUES ('大四');
-- 操作成功,与表结构对应
INSERT INTO `grade` VALUES ('4','大四');
- 结论:
字段1,字段2...该部分可省略,前提是,添加的值,务必与表结构,数据列,顺序相对应,且数量一 致。
4. 修改数据
update 命令
- 语法:
UPDATE 表名 SET column_name=value [,column_name2=value2,...] [WHERE condition];
- 注意:
- column_name:要更改的数据列;
- value:修改后的数据,可以为变量,具体指,表达式或者嵌套的 SELECT 结果;
- condition:为筛选条件,如不指定,则修改该表的所有列数据。
where 条件子句
- 简单理解为:有条件的,从表中筛选数据;
| 运算符 | 含义 | 范围 | 结果 |
|---|---|---|---|
| = | 等于 | 5=6 | false |
| <> 或 != | 不等于 | 5!=6 | true |
| 大于 | 5>6 | false | |
| < | 小于 | 5<6 | true |
| >= | 大于等于 | 5>=6 | false |
| <= | 小于等于 | 5<=6 | true |
| BETWEEN | 在某个范围之间 | BETWEEN 5 AND 10 | |
| AND | 并且 | 5 > 1 AND 1 > 2(&&) | false |
| OR | 或 | 5 > 1 OR 1 > 2(||) | true |
- 测试:
-- 修改年级信息
UPDATE `grade` SET `gradename` = '高中' WHERE `gradeid` = 1;
-- value值可以是一个变量 CURRENT_TIME:时间变量
UPDATE `student` SET `birthday`=CURRENT_TIME WHERE `name`='测试' AND `sex`='0';
5. 删除数据
DELETE 命令
- 语法:
DELETE FROM `表名` [WHERE condition];
- 注意:condition 为筛选条件,如不指定,则删除该表的所有列数据;
-- 删除最后一个数据
DELETE FROM `grade` WHERE `gradeid`=5;
TRUNCATE 命令
-
作用:清空表数据,表结构、索引、约束等不变 ;
-
语法:
TRUNCATE [TABLE] `数据表名`;
-- 清空年级表
truncate `grade`;
- 测试:
-- 创建一个测试表
CREATE TABLE `test` (
`id` INT NOT NULL AUTO_INCREMENT,
`coll` VARCHAR(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4
-- 插入测试数据
INSERT INTO `test`(`coll`) VALUE('row1'),('row2'),('row3');
-- 方式1:清空表数据 (delete不带where条件)
DELETE FROM `test`;
-- 方式2:清空表数据 (truncate)
TRUNCATE TABLE `test`;
- 注意:
- 区别于 DELETE 命令;
- 相同:都能删除数据,不删除表结构,但 TRUNCATE 速度更快;
- 不同:
- TRUNCATE 重新设置 索引 计数器,不会记录日志;
- TRUNCATE 不会对事务有影响。
- delete 问题:重启数据库后(了解)
- InnoDB:自增列会从1开始(存在内存中的,断电即失);
- MyISAM:继续从上一个自增量开始(存在文件中,不丢失)。
四、使用 DQL 查询数据(重点)
1. DQL 语言
- DQL(Data Query Language 数据查询语言):
- 查询数据库数据,如 SELECT 语句;
- 单表查询、多表的复杂查询、嵌套查询;
- 数据库语言中最核心、最重要的语句;
- 使用频率高。
SELECT 语法
SELECT [ALL | DISTINCT]
{* | table.* | [table.field1[as alias1][,table.field2[as alias2]][,...]]}
FROM table_name [as table_alias]
[left | right | inner join table_name2] -- 联合查询
[WHERE ...] -- 指定结果需满足的条件
[GROUP BY ...] -- 指定结果按照哪几个字段来分组
[HAVING] -- 过滤分组的记录必须满足的次要条件
[ORDER BY ...] -- 指定查询记录按一个或多个条件排序
[LIMIT {[offset,]row_count | row_countOFFSET offset}];
-- 指定查询的记录从哪条至哪条
- 注意:
[ ]可选,{ }必选,可选参数必须按语法的前后顺序,否则报错。 - 导入 SQL 文件:
-- 创建一个school数据库
DROP DATABASE IF EXISTS `school`;
CREATE DATABASE IF NOT EXISTS `school`;
USE `school`;
-- 创建年级表
DROP TABLE IF EXISTS `grade`;
CREATE TABLE IF NOT EXISTS `grade` (
`GradeID` INT NOT NULL AUTO_INCREMENT COMMENT '年级编号',
`GradeName` VARCHAR(50) NOT NULL COMMENT '年级名称',
PRIMARY KEY (`GradeID`)
) ENGINE=INNODB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
-- 插入年级数据
INSERT INTO `grade` (`GradeID`,`GradeName`)
VALUES
(1,'大一'),(2,'大二'),(3,'大三'),(4,'大四'),(5,'预科班');
-- 创建成绩表
DROP TABLE IF EXISTS `result`;
CREATE TABLE IF NOT EXISTS `result` (
`StudentNo` INT NOT NULL COMMENT '学号',
`SubjectNo` INT NOT NULL COMMENT '课程编号',
`ExamDate` DATETIME NOT NULL COMMENT '考试日期',
`StudentResult` INT NOT NULL COMMENT '考试成绩',
KEY `SubjectNo` (`SubjectNo`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
-- 插入成绩数据
INSERT INTO `result`(`StudentNo`,`SubjectNo`,`ExamDate`,`StudentResult`)
VALUES
(1000,1,'2013-11-11 16:00:00',85),
(1000,2,'2013-11-12 16:00:00',70),
(1000,3,'2013-11-11 09:00:00',68),
(1000,4,'2013-11-13 16:00:00',98),
(1000,5,'2013-11-14 16:00:00',58),
(1001,1,'2013-11-11 16:00:00',80),
(1001,2,'2013-11-12 16:00:00',90),
(1001,3,'2013-11-11 09:00:00',78),
(1001,4,'2013-11-13 16:00:00',90),
(1001,5,'2013-11-14 16:00:00',68),
(1002,1,'2013-11-11 16:00:00',88),
(1002,2,'2013-11-12 16:00:00',79),
(1002,3,'2013-11-11 09:00:00',52),
(1002,4,'2013-11-13 16:00:00',69),
(1002,5,'2013-11-14 16:00:00',77),
(1003,1,'2013-11-11 16:00:00',90),
(1003,2,'2013-11-12 16:00:00',89),
(1003,3,'2013-11-11 09:00:00',96),
(1003,4,'2013-11-13 16:00:00',83),
(1003,5,'2013-11-14 16:00:00',79);
-- 创建学生表
DROP TABLE IF EXISTS `student`;
CREATE TABLE IF NOT EXISTS `student` (
`StudentNo` INT NOT NULL COMMENT '学号',
`LoginPwd` VARCHAR(20) DEFAULT NULL,
`StudentName` VARCHAR(20) DEFAULT NULL COMMENT '学生姓名',
`Sex` TINYINT DEFAULT NULL COMMENT '性别,0或1',
`GradeId` INT DEFAULT NULL COMMENT '年级编号',
`Phone` VARCHAR(50) NOT NULL COMMENT '联系电话,允许为空',
`Address` VARCHAR(255) NOT NULL COMMENT '地址,允许为空',
`BornDate` DATETIME DEFAULT NULL COMMENT '出生时间',
`Email` VARCHAR(50) NOT NULL COMMENT '邮箱账号允许为空邮箱账号允许为空',
`IdentityCard` VARCHAR(18) DEFAULT NULL COMMENT '身份证号',
PRIMARY KEY (`StudentNo`),
UNIQUE KEY `IdentityCard` (`IdentityCard`),
KEY `Email` (`Email`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
-- 插入学生数据
INSERT INTO `student`(`StudentNo`,`LoginPwd`,`StudentName`,`Sex`,`GradeId`,`Phone`,`Address`,`BornDate`,`Email`,`IdentityCard`)
VALUES
(1000,'123456','张伟',0,2,'13800001234','北京朝阳','1980-1-1','text123@qq.com','123456198001011234'),
(1001,'123456','赵强',1,3,'13800002222','广东深圳','1990-1-1','text111@qq.com','123456199001011233'),
(1002,'123456','张三',0,2,'13800001207','北京海淀','1983-2-15','text207@qq.com','123456198301011234'),
(1003,'123456','李四',0,2,'13800003333','北京通州','1985-10-18','text333@qq.com','123456198501011234');
-- 创建科目表
DROP TABLE IF EXISTS `subject`;
CREATE TABLE IF NOT EXISTS `subject` (
`SubjectNo` INT NOT NULL AUTO_INCREMENT COMMENT '课程编号',
`SubjectName` VARCHAR(50) DEFAULT NULL COMMENT '课程名称',
`ClassHour` INT DEFAULT NULL COMMENT '学时',
`GradeId` INT DEFAULT NULL COMMENT '年级编号',
PRIMARY KEY (`SubjectNo`)
) ENGINE=INNODB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb4;
-- 插入科目数据
INSERT INTO `subject`(`SubjectNo`,`SubjectName`,`ClassHour`,`GradeId`)
VALUES
(1,'高等数学-1',110,1),
(2,'高等数学-2',110,2),
(3,'高等数学-3',100,3),
(4,'高等数学-4',130,4),
(5,'C语言-1',110,1),
(6,'C语言-2',110,2),
(7,'C语言-3',100,3),
(8,'C语言-4',130,4),
(9,'Java程序设计-1',110,1),
(10,'Java程序设计-2',110,2),
(11,'Java程序设计-3',100,3),
(12,'Java程序设计-4',130,4),
(13,'数据库结构-1',110,1),
(14,'数据库结构-2',110,2),
(15,'数据库结构-3',100,3),
(16,'数据库结构-4',130,4),
(17,'C#基础',130,1);
2. 指定查询字段
-- 查询表中所有的数据列结果,采用 *,效率低,不推荐
-- 查询所有学生信息
SELECT * FROM `student`;
-- 查询指定列(字段:学号 , 姓名)
SELECT `StudentNo`,`StudentName` FROM `student`;
AS 子句作为别名(AS 关键词可以省略)
-
作用:
- 可给数据列,取一个新别名;
- 可给表,取一个新别;
- 可把经计算,或总结的结果,用另一个新名称来代替。
-
注意:别名可以不加引号,可以是中文;
-- 为列取别名(AS可以省略)
SELECT `StudentNo` AS 学号,`StudentName` AS 姓名 FROM `student`;
-- 为表取别名
SELECT `StudentNo` 学号,`StudentName` 姓名 FROM`student` s;
-- 为查询结果设置别名
-- CONCAT(a,b)函数,拼接字符串
SELECT CONCAT('姓名:',`StudentName`) AS 新姓名 FROM `student`;
distinct 关键字(去重复)
- 作用 : 去掉 SELECT 查询返回结果中,重复的记录 ( 列值相同 ) , 只返回一条;
-- 查看考试成绩(所有)
SELECT * FROM `result`;
-- 查看参加考试人员(按学号),有重复
SELECT `StudentNo` FROM `result`;
-- distinct 去除重复项(默认是ALL)
SELECT DISTINCT `StudentNo` 学号去重 FROM `result`;
数据库的列(使用表达式)
- 语法:
select 表达式 from 表名; - 数据库中的表达式:文本值、列值、NULL、函数、操作符等组成;
- 应用场景:
- SELECT 返回结果列中使用;
- SELECT 的 ORDER BY , HAVING 等子句中使用;
- DML 语句中的 where 条件语句中使用。
-- selcet查询中使用表达式
-- 查询自增步长(变量)
SELECT @@auto_increment_increment;
-- 查询版本号(函数)
SELECT VERSION();
-- 计算结果(表达式)
SELECT 100*2+10 AS 计算结果;
-- 查看学生成绩,并将成绩整体 +1
SELECT `StudentNo` 学号,`StudentResult`+1 提分后 FROM `result`;
3. where 条件语句
- 作用:检索数据表中,符合条件的记录;
- 搜索条件:一个或多个逻辑表达式,结果一般为
true或false。
逻辑操作符 MySQL8 运算符
| 运算符 | 语法 | 描述 |
|---|---|---|
| ADN 或 && | a AND b 或 a && b | 逻辑与:两个都为真,结果才为真 |
| OR 或 || | a OR b 或 a || b | 逻辑或:一个为真,结果为真 |
| NOT ! | not a 或 !a | 逻辑非:取反 |
- 运算符尽量使用英文字母
- 满足条件的查询(where):
-- 查询成绩85-100之间的数据
SELECT `StudentNo` 学号,`StudentResult` 成绩 FROM `result`
WHERE `StudentResult`>=85 AND `StudentResult`<=100;
-- AND也可以写成 &&
SELECT `StudentNo`,`StudentResult` FROM `result`
WHERE `StudentResult`>=85 && `StudentResult`<=100
-- 模糊查询 between(对应:精确查询)
SELECT `StudentNo`,`StudentResult` FROM `result`
WHERE `StudentResult` BETWEEN 85 AND 100;
-- 查看,除学号为1000的,其它学生成绩(!= 或 <>)
SELECT `StudentNo`,`StudentResult` FROM `result`
WHERE `StudentNo`!=1000;
-- 使用not
SELECT `StudentNo`,`StudentResult` FROM `result`
WHERE NOT `StudentNo`=1000;
模糊查询:比较操作符
| 操作符 | 语法 | 描述 |
|---|---|---|
| IS NULL | a IS NULL | 操作符为 NULL,结果为真 |
| IS NOT NULL | a IS NOT NULL | 操作符不为 NULL,结果为真 |
| BETWEEN | a BETWEEN b AND c | a 范围在 b 与 c 之间,结果为真 |
| LIKE | a LIKE b | SQL 模式匹配:a匹配b,结果为真 |
| IN | a IN (a1,a2,a3,...) | a 等于 a1,a2...中的一个,结果为真 |
- 注意:
- 数值数据类型的记录之间,才能进行算术运算;
- 相同数据类型的数据之间,才能进行比较;
- IN 括号中,是具体的一个或多个值,不能用通配符。
- 模糊查询:between、and、like、in、null
- 通配符:
%:0 到任意多个字符;_:任意单个字符;- 特殊字符,需要进行转义
\。
- 通配符:
-- 多字符匹配:like 结合 %
SELECT `StudentNo`,`StudentName` FROM `student`
WHERE `StudentName` LIKE '张%';
-- 单字符匹配:like 结合 _
SELECT `StudentNo`,`StudentName` FROM `student`
WHERE `StudentName` LIKE '张_';
-- 查询姓张的同学,后面只有两个字的:LIKE '张__'
SELECT `StudentNo`,`StudentName` FROM `student`
WHERE `StudentName` LIKE '张__'
-- 查询名字中含有 嘉 字的
SELECT `StudentNo`,`StudentName` FROM `student`
WHERE `StudentName` LIKE '%嘉%';
-- 查询姓名中含有特殊字符的需要使用转义符号 '\'
-- 自定义转义符关键字: ESCAPE ':'
-- =============================================
-- IN(具体的一个或多个值,不能用通配符)
-- =============================================
-- 查询学号为1000,1001,1002的学生姓名
SELECT `StudentNo`,`StudentName` FROM`student`
WHERE `StudentNo` IN('1000','1001','1002');
-- 查询地址在北京朝阳,广东深圳的学生
SELECT `StudentNo`,`StudentName` FROM `student`
WHERE `Address` IN('北京朝阳','广东深圳');
-- NULL 空
-- =============================================
-- 查询email没有填写的学生信息
-- 不能直接写=NULL(错误),用 is null
SELECT `StudentName` FROM `student`
WHERE `Email`='' OR `Email` IS NULL;
-- 查询出生日期填写的学生信息(不为空)
SELECT `StudentName`,`BornDate` FROM `student`
WHERE `BornDate` IS NOT NULL
4. 联表查询
JOIN 对比
| 操作符 | 描述 |
|---|---|
| inner join | 如果表中,至少有一个匹配,则返回行 |
| left join | 右表 中没有匹配,也从 左表 中返回所有行 |
| right jion | 左表 中没有匹配,也从 右表 中返回所有行 |
连接查询:
- 多张数据表数据查询,可通过连接运算符
JOIN实现; - inner join(内连接):查询两个表中结果的交集;
- 外接接(outer join):
- left join(左外连接):以左表为基准,右表进行匹配,匹配不上的,返回左表的记录,右表以 NULL 填充;
- right join(右外连接):以右表为基准,左表进行匹配,匹配不上的,返回右表的记录,左表以 NULL 填充;
- 等值连接和非等值连接;
- 自连接。
- 测试:
- 连接查询:join
连接的表on判断条件; - 等值查询:join
连接的表where判断条件。
- 连接查询:join
-- 查询参加考试的学生信息(学号、学生姓名、科目编号、分数)
-- 所有学生信息
SELECT * FROM student;
-- 所有成绩信息
SELECT * FROM result;
/*
思路:
1:分析需求,确定查询的列来源于两个类 student、result,连接查询
2:确定使用哪种连接查询?(内连接)
确定交叉点:不同表中,共有的列信息(如:学号)
判断条件:学生表中的 StudentNo= 成绩表中的 StudentNo
*/
-- 等值连接
SELECT s.`StudentNo`,`StudentName`,`SubjectNo`,`StudentResult`
FROM `student` s,`result` r
WHERE s.`StudentNo`=r.`StudentNo`;
-- 内连接(inner join)
SELECT s.`StudentNo`,`StudentName`,`SubjectNo`,`StudentResult`
FROM `student` s INNER JOIN `result` r
ON s.`StudentNo`=r.`StudentNo`;
-- 在student插入一条学生记录(无考试成绩)
-- 左连接(left join):查询结果中,包含新插入无成绩学生信息
SELECT s.`StudentNo`,`StudentName`,`SubjectNo`,`StudentResult`
FROM `student` s LEFT JOIN `result` r
ON s.`StudentNo`=r.`StudentNo`;
-- 右连接(right join):只包含有成绩学生信息
SELECT s.`StudentNo`,`StudentName`,`SubjectNo`,`StudentResult`
FROM `student` s RIGHT JOIN `result` r
ON s.`StudentNo`=r.`StudentNo`;
-- 查询缺考学生(左连接)
SELECT s.`StudentNo`,`StudentName`,`SubjectNo`,`StudentResult`
FROM `student` s LEFT JOIN `result` r
ON s.`StudentNo`=r.`StudentNo`
WHERE `StudentResult` IS NULL;
-- 查询参加考试的同学信息(4张表:学号、年级名称、学生姓名、科目名、分数)
SELECT s.`StudentNo`,`GradeName`,`StudentName`,`SubjectName`,`StudentResult`
FROM `student` s INNER JOIN `result` r
ON s.`StudentNo`=r.`StudentNo`
INNER JOIN `subject` sub
ON r.`SubjectNo`=sub.`SubjectNo`
INNER JOIN `grade` g
ON s.`GradeId`=g.`GradeID`;
-- 查询 高等数学-4 的所有考试结果(学号、学生姓名、科目名称、成绩)
SELECT s.`StudentNo` 学号,`StudentName` 学生姓名,`SubjectName` 科目名称,`StudentResult` 成绩
FROM `student` s INNER JOIN `result` r
ON s.`StudentNo`=r.`StudentNo`
INNER JOIN `subject` sub
ON r.`SubjectNo`=sub.`SubjectNo`
WHERE `SubjectName`='高等数学-4';
分析:
- 要查询哪些数据:select ...;
- 从哪几个表中查:from
表名xxx join连接的表on 交叉条件; - 多张表查询:先查两张,再逐个表增加。
自连接(了解)
- 自连接:
- 数据表与自身进行连接;
- 核心:一张表,拆为两张一样的表即可。
- SQL 文件:
-- 创建表
CREATE TABLE `category` (
`categoryid` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主题id',
`pid` int NOT NULL COMMENT '父id',
`categoryName` varchar(50) NOT NULL COMMENT '主题名字',
PRIMARY KEY (`categoryid`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4;
-- 插入记录
INSERT INTO `category`(`categoryid`,`pid`,`categoryName`)
VALUES('2','1','信息技术'),
('3','1','软件开发'),
('4','3','数据库'),
('5','1','美术设计'),
('6','3','web开发'),
('7','5','ps技术'),
('8','2','办公信息');
-
表折分:
-
父类
categoryid categoryName 2 信息技术 3 软件开发 5 美术设计 -
子类
pid categoryid categoryName 3 4 数据库 2 8 办公信息 3 6 web开发 5 7 ps技术 -
操作:查询父类对应子类的关系
父类 子类 信息技术 办公信息 软件开发 数据库 软件开发 web开发 美术设计 ps技术
-
-
自连接测试:
/*
将栏目的父子关系呈现出来 (父栏目名称,子栏目名称)
核心思想:把一张表看成两张一模一样的表,然后将这两张表连接查询
*/
-- 内连接方式:
SELECT a.`categoryName` AS 父栏目,b.`categoryName` AS 子栏目
FROM `category` AS a INNER JOIN `category` AS b
ON a.`categoryid`=b.`pid`;
-- 等值查询:
SELECT a.`categoryName` AS 父栏目,b.`categoryName` AS 子栏目
FROM `category` AS a,`category` AS b
WHERE a.`categoryid`=b.`pid`;
5. 排序和分页
排序
-
根据指定的列(字段),对结果集进行排序;
-
语法 :
ORDER BY ...- ASC:升序(默认);
- DESC:降序。
-
测试:
-- 查询 高等数学-4 的所有考试结果(学号、学生姓名、科目名称、成绩)
-- 按成绩降序排序
SELECT s.`StudentNo`,`StudentName`,`SubjectName`,`StudentResult`
FROM `student` s INNER JOIN `result` r
ON s.`StudentNo`=r.`StudentNo`
INNER JOIN `subject` sub
ON r.`SubjectNo`=sub.`SubjectNo`
WHERE `SubjectName`='高等数学-4'
ORDER BY `StudentResult` DESC;
分页
-
语法 :
SELECT * FROM 表名 LIMIT [offset,] rows | rows OFFSET offset; -
作用:
- 缓解数据库压力;
- 提高网络传输速度;
- 更好的用户体验。
-
推导:
- 第一页 : limit 0,5(记录起始值,记录条数);
- 第二页 : limit 5,5;
- 第三页 : limit 10,5;
-
公式:
limit (n-1)*pageSzie,pageSzie;- pageSzie:每页显示数据条数;
- (n-1)*pageSize:起始值;
- n:当前页;
- 总页数:数据总数/页面显示条数。
-
测试:
-- 每页显示3条数据
SELECT s.`StudentNo`,`StudentName`,`SubjectName`,`StudentResult`
FROM `student` s INNER JOIN `result` r
ON s.`StudentNo`=r.`StudentNo`
INNER JOIN `subject` sub
ON r.`SubjectNo`=sub.`SubjectNo`
WHERE `SubjectName`='高等数学-4'
ORDER BY `StudentResult` DESC
LIMIT 0,3
-- 查询 高等数学-1 课程成绩前2名并且分数大于80的学生信息(学号、姓名、课程名、分数)
SELECT s.`StudentNo`,`StudentName`,`SubjectName`,`StudentResult`
FROM `student` s INNER JOIN `result` r
ON s.`StudentNo`=r.`StudentNo`
INNER JOIN `subject` sub
ON r.`SubjectNo`=sub.`SubjectNo`
WHERE `SubjectName`='高等数学-1' AND `StudentResult`>80
ORDER BY `StudentResult` DESC
LIMIT 0,2
6. 子查询
-
在查询语句 WHERE 条件语句中,嵌套另一个查询语句;
select * from user where id = (select id from grade); -
嵌套查询,可由多个子查询组成(由里及外);
-
子查询返回的结果,一般都是集合,建议使用 IN 关键字;
-
测试:
-- 查询 高等数学-1 的所有考试结果(学号,科目编号,成绩),并且成绩降序排列
-- 方式1:连接查询
SELECT `StudentNo`,r.`SubjectNo`,`StudentResult`
FROM `result` r INNER JOIN `subject` sub
ON r.`SubjectNo`=sub.`SubjectNo`
WHERE `SubjectName`='高等数学-1'
ORDER BY `StudentResult` DESC;
-- 方式2:使用子查询(由里及外)
SELECT `StudentNo`,`SubjectNo`,`StudentResult`
FROM `result`
WHERE `SubjectNo`=(
SELECT `SubjectNo` FROM `subject`
WHERE `SubjectName`='高等数学-1'
)
ORDER BY `StudentResult` DESC;
-- 查询课程为 高等数学-1 且分数不小于80分的学生的学号和姓名
-- 方式1:连接查询
SELECT s.`StudentNo`,`StudentName` FROM `student` s
INNER JOIN `result` r
ON s.`StudentNo`=r.`StudentNo`
INNER JOIN `subject` sub
ON r.`SubjectNo`=sub.`SubjectNo`
WHERE `SubjectName`='高等数学-1' AND `StudentResult`>=80
-- 方法2:使用连接查询+子查询
SELECT s.`StudentNo`,`StudentName` FROM `student` s
INNER JOIN `result` r
ON s.`StudentNo`=r.`StudentNo`
WHERE `SubjectNo`=(
SELECT `SubjectNo` FROM `subject`
WHERE `SubjectName`='高等数学-1' AND `StudentResult`>=80
);
-- 方法3:使用子查询
-- 分步写简单sql语句,然后将其嵌套起来
SELECT `StudentNo`,`StudentName` FROM `student`
WHERE `StudentNo` IN(
SELECT `StudentNo` FROM `result`
WHERE `StudentResult`>=80 AND `SubjectNo`=(
SELECT `SubjectNo` FROM `subject`
WHERE `SubjectName`='高等数学-1'
)
);
7. 分组和过滤
- 分组:
GROUP BY... - 过滤:
HAVING...
-- 查询不同课程的平均分,最高分,最低分
-- 前提:根据不同的课程进行分组
SELECT `SubjectName`,AVG(`StudentResult`) AS 平均分,
MAX(`StudentResult`) AS 最高分,
MIN(`StudentResult`) AS 最低分
FROM `result` r
INNER JOIN `subject` sub
ON r.`SubjectNo`=sub.`SubjectNo`
-- 分组:按课程编号
GROUP BY r.`SubjectNo`
-- 过滤:可使用别名
HAVING 平均分>80
- 注意:
- where 写在 group by(分组)前面;
- 分组后面的筛选,要使用 HAVING...;
- HAVING 是从前面筛选的字段再筛选,where 是从数据表的字段,直接进行筛选。
8. select 小结
- 顺序非常重要:
select 去重 要查询的字段 from 表名(注意:表和字段可以取别名 as)
xxx join 要连接的表 on 等值判断
where(具体的值,子查询语句)
group by(用哪个字段来分组)
having(过滤分组后的信息,条件与where一样,位置不同)
order by(排序 升序,降序)
limit 分页起始值,记录条数;
五、MySQL 函数
1. 常用函数(了解)
- 数据函数
-- 绝对值
SELECT ABS(-8);
-- 向上取整
SELECT CEILING(9.4);
-- 向下取整
SELECT FLOOR(9.4);
-- 随机数:返回一个0-1之间的随机数
SELECT RAND();
-- 符号函数: 负数返回-1,正数返回1,0返回0
SELECT SIGN(0);
- 字符串函数
-- 返回字符串包含的字符数
SELECT CHAR_LENGTH('字符串');
-- 合并字符串,参数可以有多个
SELECT CONCAT('Hello','W','orld');
-- 替换字符串,从某个位置开始替换某个长度
SELECT INSERT('helloworld',1,5,'Hi,');
-- 转小写
SELECT LOWER('HelloWorld');
-- 转大写
SELECT UPPER('helloworld');
-- 截取字符串:从左边截取
SELECT LEFT('hello,world',5);
-- 截取字符串:从右边截取
SELECT RIGHT('hello,world',5);
-- 替换字符串
SELECT REPLACE('helloworld','hello','Hi!');
-- 截取字符串,开始和长度
SELECT SUBSTR('helloworld',4,6);
-- 反转字符串
SELECT REVERSE('helloworld');
-- 查询姓张的同学,改成姓王
SELECT REPLACE(`StudentName`,'张','王') AS 新名字
FROM `student` WHERE `StudentName` LIKE '张%';
- 日期和时间函数
-- 获取当前日期
SELECT CURRENT_DATE();
-- 获取当前日期
SELECT CURDATE();
-- 获取当前日期和时间
SELECT NOW();
-- 获取当前日期和时间
SELECT LOCALTIME();
-- 获取当前日期和时间
SELECT SYSDATE();
-- 获取年月日,时分秒
SELECT YEAR(NOW());
SELECT MONTH(NOW());
SELECT DAY(NOW());
SELECT HOUR(NOW());
SELECT MINUTE(NOW());
SELECT SECOND(NOW());
- 系统信息函数
-- MySQL 版本
SELECT VERSION();
-- 用户
SELECT USER();
2. 聚合函数(重点)
| 函数名称 | 描述 |
|---|---|
| COUNT() | 计数,如:select count(*) *不建议使用 ,效率低 |
| SUM() | 列的总和:数值字段或表达式 |
| AVG() | 列的平均值:数值字段或表达式 |
| MAX() | 最大值:数值字段、字符字段或表达式 |
| MIN() | 最小值:数值字段、字符字段或表达式 |
- 测试
-- COUNT() 计数:对全部数据行的查询;
-- 方式1:指定列
SELECT COUNT(`StudentName`) FROM `student`;
-- 方式2:COUNT(*)
SELECT COUNT(*) FROM `student`;
-- 方式3 推荐:COUNT(1)
SELECT COUNT(1) FROM `student`;
SELECT SUM(`StudentResult`) AS 总和 FROM `result`;
SELECT AVG(`StudentResult`) AS 平均分 FROM `result`;
SELECT MAX(`StudentResult`) AS 最高分 FROM `result`;
SELECT MIN(`StudentResult`) AS 最低分 FROM `result`;
count() 三种方式区别
- count(字段):不统计字段为
(NULL)的记录; - count(*) :统计
(NULL)的记录; - count(1):用 1 代表代码行,统计
(NULL)的记录; - count(*)、count(1) 都是计算行数。
- 效率对比:
- 没有主键时:count(1) 比 count(*) 快;
- 有主键时:主键作为计算条件,count(主键) 效率最高;
- 若表格只有一个字段,则 count(*) 效率较高。
数据库级别的 MD5 加密(扩展)
-
MD5 简介:Message-Digest Algorithm 5(信息-摘要算法5),是计算机广泛使用的杂凑算法之一(摘要算法、哈希算法);
-
实现数据加密:
update 表名 set 字段名 = md5(字段名);
-- 创建表
CREATE TABLE `testmd5` (
`id` INT NOT NULL,
`name` VARCHAR(20) NOT NULL,
`pwd` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4
-- 插入数据
INSERT INTO testmd5 VALUES(1,'aaa','123456'),(2,'bbb','456789');
-- 单独对某个用户(如:aaa)的密码加密
UPDATE testmd5 SET pwd = MD5(pwd) WHERE NAME = 'aaa';
-- 插入新数据时自动加密
INSERT INTO testmd5 VALUES(3,'ccc',MD5('123456'));
-- 查询登录用户信息(将用户输入密码,加密后进行比对)
SELECT * FROM testmd5 WHERE `name`='aaa' AND pwd=MD5('123456');
3. 内置函数(小结)
-- 数值函数
-- 绝对值 abs(-10.9) = 10
abs(x);
-- 格式化千分位数值 format(1234567.456, 2) = 1,234,567.46
format(x, d);
ceil(x); -- 向上取整 ceil(10.1) = 11
floor(x); -- 向下取整 floor (10.1) = 10
round(x); -- 四舍五入去整
mod(m, n); -- m%n m mod n 求余 10%3=1
pi(); -- 获得圆周率
pow(m, n); -- m^n
sqrt(x); -- 算术平方根
rand(); -- 随机数
truncate(x, d); -- 截取d位小数
-- 时间日期函数
now(), current_timestamp(); -- 当前日期时间
current_date(); -- 当前日期
current_time(); -- 当前时间
date('yyyy-mm-dd hh:ii:ss'); -- 获取日期部分
time('yyyy-mm-dd hh:ii:ss'); -- 获取时间部分
date_format('yyyy-mm-dd hh:ii:ss', '%d %y %a %d %m %b %j'); -- 格式化时间
unix_timestamp(); -- 获得unix时间戳
from_unixtime(); -- 从时间戳获得时间
-- 字符串函数
length(string); -- string长度,字节
char_length(string); -- string的字符个数
-- 从str的position开始,取length个字符
substring(str, position [,length]);
-- 在str中用replace_str替换search_str
replace(str ,search_str ,replace_str);
-- 返回substring首次在string中出现的位置
instr(string ,substring);
concat(string [,...]); -- 连接字串
charset(str); -- 返回字串字符集
lcase(string); -- 转换成小写
left(string, length); -- 从string2中的左边起取length个字符
load_file(file_name); -- 从文件读取内容
-- 同instr,但可指定开始位置
locate(substring, string [,start_position]);
-- 重复用pad加在string开头,直到字串长度为length
lpad(string, length, pad);
ltrim(string); -- 去除前端空格
repeat(string, count); -- 重复count次
rpad(string, length, pad); --在str后用pad补充,直到长度为length
rtrim(string); -- 去除后端空格
strcmp(string1 ,string2); -- 逐字符比较两字串大小
-- 聚合函数
count();
sum();
max();
min();
avg();
group_concat();
-- 其他常用函数
md5();
default();
六、事务
1. 概述
什么是事务
- 事务就是将一组 SQL 语句放在同一批次内去执行;
- 如果 SQL 语句出错,则该批次内的所有 SQL,都将被取消执行;
- MySQL 事务处理只支持 InnoDB 和 BDB 数据表类型。
事务的 ACID 原则
-
原子性(Atomic):
- 一个事务要么全部提交成功,要么全部失败回滚(未执行前状态),不能只执行其中的一部分操作。
-
一致性(Consist):
- 数据库总是从一个一致性的状态,转换到另外一个一致性状态,不会部分数据状态改变了,部分状态没有改变。
-
隔离性(Isolated):
- 通常来说,一个事务所做的修改,在最终提交之前,对其他事务是不可见的;
- 这和数据库的隔离级别有关,所以只能通常来说。
-
持久性(Durable):
- 一旦事务提交,则其所做的修改,会被永久保存到数据库中。
隔离级别
- READ UNCOMMITTED(未提交读):
- 事务中的修改,即使没提交,对其他事务也是可见的;
- READ COMMITTED(提交读):
- 一个事务开始时,只能看见已经提交的事务所做的修改(不可重复读);
- REPEATABLE READ(可重复读):
- 同一个事务中,多次读取 同样记录 的结果,是一致的;
- MySQL 默认的隔离级别;
- SERIALIZABLE(可串行化):
- 读取每一行数据都加锁,可能导致大量的超时和锁争用问题;
- 最高隔离级别,一般不使用;
- 隔离导致的问题:
- 脏读:一个事务读取了另外一个事务,未提交的数据;
- 不可重复读:在一个事务内读取表中的某一行数据,多次读取结果不同(不一定错误,只是某些场合不对);
- 虚读(幻读):一个事务内,读取到了别的事务插入的数据,导致前后读取数量总量不一致。
| 隔离级别 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 | 加锁度 |
|---|---|---|---|---|
| READ UNCOMMITTED | Y | Y | Y | N |
| READ COMMITTED | N | Y | Y | N |
| REPEATABLE READ | N | N | Y | N |
| SERIALIZABLE | N | N | N | Y |
2. 事务实现
-
MySQL 中默认是自动提交;
-
使用事务时,应先关闭自动提交;
-
基本语法:
-- 使用set语句来改变自动提交模式
SET autocommit = 0; -- 关闭
SET autocommit = 1; -- 开启(默认)
-- 1.关闭自动提交
SET autocommit = 0;
-- 2.开始一个事务,标记事务的起始点
START TRANSACTION
INSERT ...
-- 3.提交一个事务给数据库
COMMIT
-- 将事务回滚,数据回到本次事务的初始状态
ROLLBACK
-- 4.还原MySQL数据库的自动提交
SET autocommit =1;
-- 保存点
SAVEPOINT 保存点名称 -- 设置一个事务保存点
ROLLBACK TO SAVEPOINT 保存点名称 -- 回滚到保存点
RELEASE SAVEPOINT 保存点名称 -- 删除保存点
- 事务处理步骤:
- 实例:
/*
A在线买一款价格为500元商品,网上银行转账.
A的银行卡余额为2000,然后给商家B支付500.
商家B一开始的银行卡余额为10000
创建数据库shop和创建表account并插入2条数据
*/
-- 创建数据库
CREATE DATABASE `shop` CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE `shop`;
-- 创建数据表
CREATE TABLE `account` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(32) NOT NULL,
`cash` DECIMAL(9,2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
-- 插入记录
INSERT INTO `account` (`name`,`cash`)
VALUES('A',2000.00),('B',10000.00);
-- 转账实现(事务)
-- 1.关闭自动提交
SET autocommit = 0;
-- 2.开始一个事务,标记事务的起始点
START TRANSACTION;
UPDATE `account` SET `cash`=`cash`-500 WHERE `name`='A';
UPDATE `account` SET `cash`=`cash`+500 WHERE `name`='B';
-- 3.提交事务
COMMIT;
-- 回滚
# rollback;
-- 4.恢复自动提交
SET autocommit = 1;
七、索引
1. 索引分类
索引的作用
- 提高查询速度;
- 确保数据的唯一性;
- 可以加速表和表之间的连接,实现表与表之间的参照完整性;
- 使用分组和排序,子句进行数据检索时,可以显著减少分组和排序的时间;
- 全文检索字段,进行搜索优化。
分类
- 主键索引(Primary Key);
- 唯一索引(Unique);
- 常规索引(默认:Key/Index );
- 全文索引(FullText)。
2. 主键索引(Primary Key)
- 主键:某一个属性组,能唯一标识一条记录;
- 特点:
- 最常见的索引类型;
- 确保数据记录的唯一性;
- 确定特定数据,记录在数据库中的位置。
3. 唯一索引(Unique)
- 作用:避免同一个表中,某数据列中的值重复;
- 与主键索引的区别:
- 主键索引只能有一个;
- 唯一索引可能有多个。
CREATE TABLE `grade`(
`grade_id` INT AUTO_INCREMENT PRIMARYKEY,
`grade_name` VARCHAR(32) NOT NULL UNIQUE
-- 或 UNIQUE KEY `grade_id` (`grade_id`)
)
4. 常规索引(Key/Index)
- 作用:快速定位特定数据;
- 注意:
- index 和 key 关键字都可以设置常规索引;
- 应加在查询找条件的字段;
- 不宜添加太多,影响数据的插入、删除、修改操作。
-- 方式1:创建表时添加
CREATE TABLE `result`(
-- 省略一些代码
-- 常规索引(INDEX或KEY)
KEY `studentNo` (`studentNo`),
KEY `subjectNo` (`subjectNo`)
)
-- 方式2:创建后添加(INDEX或KEY)
ALTER TABLE `result` ADD INDEX `student_no`(`student_no`);
5. 全文索引(FullText)
- 作用:快速定位特定数据;
- 注意:
- 只能用于 MyISAM 类型的数据表;
- 只能用于 CHAR , VARCHAR , TEXT 数据列类型;
- 适合大型数据集。
创建索引方式:
- 方法1:创建表时
CREATE TABLE 表名 (
字段名1 数据类型 [完整性约束条件…],
字段名2 数据类型 [完整性约束条件…],
[UNIQUE | FULLTEXT | SPATIAL ] INDEX | KEY
[索引名] (字段名[(长度)] [ASC |DESC])
);
- 方法2:CREATE 在已存在的表上创建索引
CREATE [UNIQUE | FULLTEXT | SPATIAL ] INDEX 索引名
ON 表名 (字段名[(长度)] [ASC |DESC]);
- 方法3:ALTER TABLE 在已存在的表上创建索引
ALTER TABLE 表名 ADD [UNIQUE | FULLTEXT | SPATIAL ] INDEX
索引名 (字段名[(长度)] [ASC |DESC]);
- 索引相关操作
-- 删除索引:
DROP INDEX 索引名 ON 表名字;
-- 删除主键索引:
ALTER TABLE 表名 DROP PRIMARY KEY;
-- 显示索引信息:
SHOW INDEX FROM 表名;
-- 增加全文索引
ALTER TABLE `student` ADD FULLTEXT
INDEX `studentname`(`StudentName`);
-- EXPLAIN : 分析SQL语句执行性能
EXPLAIN SELECT * FROM student WHERE studentno='1000';
- 使用全文索引
- 通过
MATCH()函数完成; - 搜索字符串做为
against()的参数被给定; - 搜索以忽略字母大小写的方式执行;
- 对于表中的每个记录行,
MATCH()返回一个相关性值; - 搜索字符串与记录行,在
MATCH()列表中,指定的列的文本之间,相似性尺度。
- 通过
EXPLAIN SELECT *FROM `student` WHERE MATCH(`StudentName`) AGAINST('李');
- 注意:
- MySQL 5.6 以前的版本,只有 MyISAM 存储引擎支持全文索引;
- MySQL 5.6 及以后的版本,MyISAM 和 InnoDB 存储引擎均支持全文索引;
- 只有字段的数据类型为 char、varchar、text 及其系列,才可以建全文索引;
- 测试或使用全文索引时,要先看一下 MySQL 版本、存储引擎、数据类型,是否支持全文索引。
拓展:测试索引
- 创建表
CREATE TABLE `app_user` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) DEFAULT '' COMMENT '用户昵称',
`email` VARCHAR(50) NOT NULL COMMENT '用户邮箱',
`phone` VARCHAR(20) DEFAULT '' COMMENT '手机号',
`gender` TINYINT UNSIGNED DEFAULT '0' COMMENT '性别(0:男;1:女)',
`password` VARCHAR(100) NOT NULL COMMENT '密码',
`age` TINYINT DEFAULT '0' COMMENT '年龄',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`update_time` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='app用户表'
- 批量插入数据:
-- 函数代码:生成 100 万条数据
DROP FUNCTION IF EXISTS mock_data;
DELIMITER $$
CREATE FUNCTION mock_data()
RETURNS INT
BEGIN
DECLARE num INT DEFAULT 1000000;
DECLARE i INT DEFAULT 0;
WHILE i<num DO
INSERT INTO app_user(`name`,`email`,`phone`,`gender`,`password`,`age`) VALUES(CONCAT('用户',i),'24736743@qq.com',CONCAT('18',FLOOR(RAND()*(999999999-100000000)+100000000)),FLOOR(RAND()*2),UUID(),FLOOR(RAND()*100));
SET i=i+1;
END WHILE;
RETURN i;
END;
-- 如果报错(错误代码:1418),先执行这条代码,然后重新执行函数代码
SET GLOBAL log_bin_trust_function_creators=TRUE;
-- 函数代码执行成功后,再执行此处代码
SELECT mock_data();
-- 数据生成后,可恢复设置
SET GLOBAL log_bin_trust_function_creators=FALSE;
- 无索引 效率测试
-- 查看耗时
SELECT * FROM `app_user` WHERE `name` = '用户9999';
-- 效率测试
EXPLAIN SELECT * FROM `app_user` WHERE `name` = '用户9999';
- 创建索引
CREATE INDEX `id_app_user_name` ON `app_user`(`name`);
- 普通索引效率测试
-- 查看耗时
SELECT * FROM `app_user` WHERE `name` = '用户9999';
-- 效率测试
EXPLAIN SELECT * FROM `app_user` WHERE `name` = '用户9999';
6. 索引准则
- 索引不是越多越好;
- 不要对经常变动的数据加索引;
- 小数据量的表,建议不要加索引;
- 索引一般应加在,查找条件的字段。
7. 索引的数据结构
- 在创建索引时,为其指定索引类型,分两类:
- Hash 索引:查询单条快,范围查询慢;
- Btree 索引:b+树,层数越多,数据量指数级增长(推荐:InnoDB 默认支持)
不同存储引擎,支持的索引类型
- InnoDB 支持事务,支持行级别锁定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
- MyISAM 不支持事务,支持表级别锁定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
- Memory 不支持事务,支持表级别锁定,支持 B-tree、Hash 等索引,不支持 Full-text 索引;
- NDB 支持事务,支持行级别锁定,支持 Hash 索引,不支持 B-tree、Full-text 等索引;
- Archive 不支持事务,支持表级别锁定,不支持 B-tree、Hash、Full-text 等索引。
八、权限管理
1. 用户管理
- 使用 SQLyog 创建用户,并授予权限演示
- 基本命令
/* 用户和权限管理 */
-- 用户信息表:mysql.user
-- 刷新权限
FLUSH PRIVILEGES;
-- 增加用户
CREATE USER 用户名 IDENTIFIED BY [PASSWORD] 密码(字符串)
- 必须拥有mysql数据库的全局CREATE USER权限,或拥有INSERT权限。
- 只能创建用户,不能赋予权限。
- 用户名,注意引号:如 'user_name'@'192.168.1.1'
- 密码也需引号,纯数字密码也要加引号
- 要在纯文本中指定密码,需忽略PASSWORD关键词。要把密码指定为由PASSWORD()函数返回的混编值,需包含关键字PASSWORD
-- 重命名用户
RENAME USER old_user TO new_user
-- 为当前用户设置密码
SET PASSWORD = PASSWORD('密码');
-- 为指定用户设置密码
SET PASSWORD FOR 用户名 = PASSWORD('密码');
-- 删除用户
DROP USER 用户名;
-- 分配权限/添加用户
GRANT 权限列表 ON 表名 TO 用户名 [IDENTIFIED BY [PASSWORD] 'password'];
- all privileges 表示所有权限
- *.* 表示所有库的所有表
- 库名.表名 表示某库下面的某表
-- 查看权限 SHOW GRANTS FOR root@localhost;
SHOW GRANTS FOR 用户名;
-- 查看当前用户权限
SHOW GRANTS;
SHOW GRANTS FOR CURRENT_USER;
SHOW GRANTS FOR CURRENT_USER();
-- 撤消权限
REVOKE 权限列表 ON 表名 FROM 用户名;
-- 撤销所有权限
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 用户名;
- 权限解释
-- 权限列表
ALL [PRIVILEGES] -- 设置除GRANT OPTION之外的所有简单权限
ALTER -- 允许使用ALTER TABLE
ALTER ROUTINE -- 更改或取消已存储的子程序
CREATE -- 允许使用CREATE TABLE
CREATE ROUTINE -- 创建已存储的子程序
CREATE TEMPORARY TABLES -- 允许使用CREATE TEMPORARY TABLE
CREATE USER -- 允许使用CREATE USER, DROP USER, RENAME USER和REVOKE ALLPRIVILEGES。
CREATE VIEW -- 允许使用CREATE VIEW
DELETE -- 允许使用DELETE
DROP -- 允许使用DROP TABLE
EXECUTE -- 允许用户运行已存储的子程序
FILE -- 允许使用SELECT...INTO OUTFILE和LOAD DATA INFILE
INDEX -- 允许使用CREATE INDEX和DROP INDEX
INSERT -- 允许使用INSERT
LOCK TABLES -- 允许对您拥有SELECT权限的表使用LOCK TABLES
PROCESS -- 允许使用SHOW FULL PROCESSLIST
REFERENCES -- 未被实施
RELOAD -- 允许使用FLUSH
REPLICATION CLIENT -- 允许用户询问从属服务器或主服务器的地址
REPLICATION SLAVE -- 用于复制型从属服务器(从主服务器中读取二进制日志事件)
SELECT -- 允许使用SELECT
SHOW DATABASES -- 显示所有数据库
SHOW VIEW -- 允许使用SHOW CREATE VIEW
SHUTDOWN -- 允许使用mysqladmin shutdown
SUPER -- 允许使用CHANGE MASTER, KILL, PURGE MASTER LOGS和SET GLOBAL语句,mysqladmin debug命令;允许您连接(一次),即使已达到max_connections。
UPDATE -- 允许使用UPDATE
USAGE -- “无权限”的同义词
GRANT OPTION -- 允许授予权限
/* 表维护 */
-- 分析和存储表的关键字分布
ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE 表名 ...
-- 检查一个或多个表是否有错误
CHECK TABLE tbl_name [, tbl_name] ... [option] ...
option = {QUICK | FAST | MEDIUM | EXTENDED | CHANGED}
-- 整理数据文件的碎片
OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...
2. MySQL 备份
- 数据库备份必要性:
- 保证重要数据不丢失;
- 数据转移;
- MySQL 数据库备份方法:
- mysqldump 备份工具;
- 数据库管理工具,如 SQLyog;
- 直接拷贝数据库文件和相关配置文件。
mysqldump 客户端:
- 作用:
- 转储数据库;
- 搜集数据库,进行备份;
- 将数据转移到另一个 SQL 服务器,不一定是 MySQL 服务器。
- mysqldump 导出:命令行(未进入mysql)
# 导出 可以-w携带备份条件
# 1.导出一张表
mysqldump -u用户名 -p密码 库名 表名 >文件名(D:/a.sql)
# 2.导出多张表
mysqldump -u用户名 -p密码 库名 表1 表2 表3 >文件名(D:/a.sql)
# 3.导出所有表
mysqldump -u用户名 -p密码 库名 >文件名(D:/a.sql)
# 4.导出一个库
mysqldump -u用户名 -p密码 -B 库名 > 文件名(D:/a.sql)
- 导入
# 1.登录mysql的情况下:
source 备份文件(D:/a.sql)
# 2.不登录的情况下
mysql -u用户名 -p密码 库名 <备份文件(D:/a.sql)
SQLyog 备份
九、规范化数据库设计
1. 为什么需要数据库设计
- 当数据库比较复杂时,需要设计数据库;
- 糟糕的数据库设计:
- 数据冗余,存储空间浪费;
- 数据更新和插入的异常;
- 程序性能差。
- 良好的数据库设计:
- 节省数据存储空间;
- 保证数据的完整性;
- 方便进行数据库应用系统的开发。
- 软件项目开发周期中数据库设计:
- 需求分析阶段:分析客户的业务和数据处理需求;
- 概要设计阶段:设计数据库的 E-R 模型图,确认需求信息的正确和完整。
- 设计数据库步骤:
- 收集信息:充分了解用户需求;
- 标识实体:标识数据库要管理的关键对象或实体;
- 标识每个实体需要存储的详细信息;
- 标识实体之间的关系。
2. 三大范式
- 为什么需要数据规范化?
- 不合规范的表设计,会导致的问题:
- 信息重复;
- 更新异常;
- 插入异常:
- 无法正确表示信息;
- 删除异常:
- 丢失有效信息。
三大范式
- 第一范式 (1st NF) :
- 确保每列的原子性,如果每列都是不可再分的最小数据单元,则满足第一范式;
- 第二范式(2nd NF):
- 在第一范式的基础上建立起来的,满足第二范式,必须先满足第一 范式;
- 第二范式,要求每个表只描述一件事情;
- 第三范式(3rd NF):
- 如果一个关系,满足第二范式,并且除了主键以外的其他列,都不传递依赖于主键列,则满足第三范式;
- 第三范式,需要确保数据表中的每一列数据,都和主键直接相关,而不能间接相关。
规范化和性能的关系
- 数据库性能,比规范化数据库更重要;
- 在数据规范化的同时,要综合考虑数据库的性能;
- 通过在给定的表中,添加额外的字段,以大量减少需要从中搜索信息所需的时间;
- 通过在给定的表中,插入计算列,以方便查询。
十、JDBC(重点)
1. 数据库驱动
- 应用程序通过驱动,连接数据库,如:MySQL 驱动、Oracle 驱动;
2. JDBC 介绍
-
JDBC :Java Data Base Connectivity(Java 数据库连接),是 Java 操作数据库的统一规范,主要由接口组成;
-
组成 JDBC 的2个包:java.sql、javax.sql;
-
开发 JDBC 应用需要以上 2 个包的支持外,还需要导入相应 JDBC 的数据库实现(即数据库驱动);
-
3. 编写 JDBC 程序
- 创建测试数据库
CREATE DATABASE `jdbcStudy` CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE `jdbcStudy`;
CREATE TABLE `users` (
`id` INT NOT NULL,
`name` VARCHAR(40) DEFAULT NULL,
`password` VARCHAR(40) DEFAULT NULL,
`email` VARCHAR(60) DEFAULT NULL,
`birthday` DATE DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
INSERT INTO `users`(`id`,`name`,`password`,`email`,`birthday`)
VALUES(1,'zhansan','123456','zs@sina.com','1980-12-04'),
(2,'lisi','123456','lisi@sina.com','1981-12-04'),
(3,'wangwu','123456','wangwu@sina.com','1979-12-04');
- 新建 Java 工程,并导入数据驱动
- 连接测试:
package com.xxx.demo01;
import java.sql.*;
public class JdbcFirstDemo {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 要连接的数据库 URL
/*
1.jdbc:mysql://主机地址:端口号/数据库名?参数1&参数2&...;
2.?:连接参数 &:参数分隔符
2.useUnicode=true:使用Unicode字符集,支持中文编码
3.characterEncoding=utf8:编码
4.useSSL=true:使用安全的链接
5.serverTimezone=UTC:设置时区
*/
String url = "jdbc:mysql://localhost:3306/jdbcStudy?" +
"useUnicode=true&characterEncoding=utf8&useSSL=true";
// 连接的数据库时使用的用户名
String username = "root";
// 连接的数据库时使用的密码
String password = "123456";
// 1.加载驱动(通过反射)
// mysql8驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// mysql5.7驱动
// Class.forName("com.mysql.jdbc.Driver");
// 不推荐使用这种方式来加载驱动
// DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
// 2.连接数据库
Connection conn = DriverManager.getConnection(url, username, password);
// 3.获取用于向数据库发送sql语句的执行对象Statement
Statement st = conn.createStatement();
// 4.向数据库发sql,并获取代表结果集的resultset
String sql = ("SELECT * FROM `users`;");
// 5.执行sql
ResultSet rs = st.executeQuery(sql);
// 6.取出结果集的数据
while (rs.next()) {
System.out.println("id=" + rs.getObject("id"));
System.out.println("name=" + rs.getObject("name"));
System.out.println("password=" + rs.getObject("password"));
System.out.println("email=" + rs.getObject("email"));
System.out.println("birthday=" + rs.getObject("birthday"));
System.out.println("=====================");
}
// 7.关闭链接,释放资源(从后向前)
rs.close();
st.close();
conn.close();
}
}
连接数据库七大步骤:
- 加载驱动;
- MySQL8:
com.mysql.cj.jdbc.Driver; - MySQL5.7:
com.mysql.jdbc.Driver。
- MySQL8:
- 连接数据库 DriverManager(驱动管理);
- 获取 sql 的执行对象 Statement;
- 写 sql;
- 执行 sql;
- 获得返回的结果集;
- 释放连接。
4. 对象说明
DriverManager 类:加载驱动
- Jdbc 程序中的 DriverManager 用于加载驱动,并创建与数据库的链接,这个 API 的常用方法:
DriverManager.registerDriver(new Driver());
DriverManager.getConnection(url, user, password);
- 注意:实际开发中,不推荐 registerDriver 方法注册驱动:
- 通过 Driver 的源代码看到,此种方式,驱动程序会注册两次,内存中会有两个 Driver 对象;
- 程序依赖 mysql 的 API,脱离 mysql 的 jar 包,程序将无法编译,以后程序切换底层数据库,会非常麻烦。
- 推荐方式:
Class.forName("com.mysql.cj.jdbc.Driver"); - 此方式不会导致驱动对象,在内存中重复出现,并且程序只需要一个字符串, 不需要依赖具体的驱动,使程序的灵活性更高。
数据库 URL
- URL 用于标识数据库的位置,通过 URL 地址告诉 JDBC 程序连接哪个数据库,写法为:
- 常用数据库 URL 地址的写法:
- Oracle 写法:
jdbc:oracle:thin:@localhost:1521:sid; - SqlServer 写法:
jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=sid; - MySQL 写法:
jdbc:mysql://localhost:3306/sid;
- Oracle 写法:
Connection 类(重要):代表数据库
- Connection:用于代表数据库的链接,是数据库编程中,最重要的一个对象,客户端与数据库所有交互,都是通过 connection 对象完成的;
- 常用方法:
createStatement():创建向数据库发送 sql 的 statement 对象;prepareStatement(sql):创建向数据库发送预编译 sql 的PrepareSatement 对象;setAutoCommit(boolean autoCommit):设置事务是否自动提交;commit():在链接上提交事务;rollback():在此链接上回滚事务。
Statement 类:执行 sql 对象
- 用于向数据库发送 SQL 语句,常用方法:
executeQuery(String sql):查询语句;executeUpdate(String sql):insert、update或delete语句;execute(String sql):任意 sql 语句;addBatch(String sql):把多条 sql 语句放到一个批处理中;executeBatch():向数据库发送一批 sql 语句执行。
ResultSet 类:查询的结果集
- sql 语句的执行结果;
- Resultset 封装执行结果时,类似于表格的方式;
- ResultSet 对象维护了一个,指向表格数据行的游标,初始时,游标在第一行之前,调用 ResultSet.next() 方法,可以使游标指向具体的数据行,进行调用方法,获取该行的数据。
- 该对象提供的都是,用于获取数据的 get 方法:
- 获取任意类型的数据:
- getObject(int index);
- getObject(string columnName);
- 获取指定类型的数据:
- getString(int index);
- getString(String columnName)。
- 获取任意类型的数据:
- ResultSet 还提供了,对结果集进行滚动的方法:
- next():移动到下一行;
- Previous():移动到前一行;
- absolute(int row):移动到指定行;
- beforeFirst():移动 resultSet 的最前面;
- afterLast():移动到 resultSet 的最后面。
释放资源
- JDBC 程序运行完,切记要释放程序在运行过程中,创建的与数据库进行交互的对象 ResultSet、Statement、Connection对象;
- Connection 对象,是非常稀有的资源,用完后必须马上释放,如果不能及时、正确的关闭,极易导致系统宕机;
- Connection 的使用原则:晚创建、早释放;
- 为确保资源释放代码能运行,资源释放代码,一定要放在 finally 语句中。
5. Statement 对象(不安全)
- 向数据库发送 SQL 语句,完成对数据库的增、删、改、查;
- executeUpdate 方法,用于向数据库发送增、删、改的 SQL 语句,执行完后,将会返回一个整数(数据库记录变化的条数);
- executeQuery 方法,用于向数据库发送查询语句,返回结果集(ResultSet 对象)。
CRUD 操作
- create:使用 executeUpdate(String sql) 方法,插入数据;
Statement st = conn.createStatement();
String sql = "select * from user where id=1";
ResultSet rs = st.executeUpdate(sql);
while(rs.next()){
// 根据获取列的数据类型,分别调用rs的相应方法映射到java对象中
}
创建 JDBC 工具类:
- 在
src 目录下创建db.properties数据库配置文件:
# MySQL5.7 com.mysql.jdbc.Driver
# MySQL8
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcStudy?useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=123456
- 新建 utils 包,新建类 JdbcUtils
package com.xxx.demo02.utils;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils {
public static String driver = null;
public static String url = null;
public static String username = null;
public static String password = null;
static {
try {
// 通过反射,读取数据库连接信息,得到输入流
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
// 创建属性集合
Properties props = new Properties();
// 读取流
props.load(in);
// 获取数据库配置属性
// 连接驱动
driver = props.getProperty("driver");
// URL地址
url = props.getProperty("url");
// 用户名
username = props.getProperty("username");
// 密码
password = props.getProperty("password");
// 加载驱动
Class.forName(driver);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
// 获取数据库连接对象
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
// 释放资源:从后往前关闭
public static void release(Connection conn, Statement st, ResultSet rs) {
if (rs != null) {
try {
// 关闭结果集ResultSet对象
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null) {
try {
// 关闭执行SQL命令的Statement对象
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
// 关闭Connection数据库连接对象
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
使用 Statement 对象,对数据库操作
- 插入数据
package com.xxx.demo02.utils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TestInsert {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 获取数据库连接
conn = JdbcUtils.getConnection();
// 获取SQL的执行对象
st = conn.createStatement();
// SQL语句:插入记录
String sql = "INSERT INTO `users`(`id`,`name`,`password`,`email`,`birthday`)" +
"VALUES(4,'测试','123','aa@aa.com','2020-1-1');";
// 执行SQL
int i = st.executeUpdate(sql);
if (i > 0) {
System.out.println("操作成功!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 释放资源
JdbcUtils.release(conn, st, null);
}
}
}
- 删除数据:只更改 SQL 语句
String sql = "DELETE FROM `users` WHERE `id`=4;";
- 更新数据:只更改 SQL 语句
String sql = "UPDATE `users` SET `name`='测试2' WHERE `id`=2;";
- 查询数据
package com.xxx.demo02.utils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TestSelect {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 获取数据库连接
conn = JdbcUtils.getConnection();
// 获取SQL的执行对象
st = conn.createStatement();
// SQL语句:查询
String sql = "SELECT * FROM `users` WHERE `id`=3;";
// 执行SQL:返回结果集
rs = st.executeQuery(sql);
if (rs.next()) {
System.out.println(rs.getString("name"));
System.out.println(rs.getDate("birthday"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 释放资源
JdbcUtils.release(conn, st, rs);
}
}
}
SQL 注入问题
- 通过巧妙的技巧来 拼接字符串,造成 SQL 短路,从而获取数据库数据;
package com.xxx.demo02.utils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class SQL注入 {
public static void main(String[] args) {
// 正常登录
// login("zhansan", "123456");
// SQL注入
login("' or '1=1", "' or '1=1");
}
// 登录业务
public static void login(String name, String password) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
st = conn.createStatement();
// 注入方式登录后,SQL语句变为
// SELECT * FROM `users` WHERE `name`='' or '1=1' AND `password`='' or '1=1';
String sql = "SELECT * FROM `users` WHERE `name`='" +
name + "' AND `password`='" + password + "';";
rs = st.executeQuery(sql);
while (rs.next()) {
System.out.println(rs.getString("name"));
System.out.println(rs.getString("password"));
System.out.println("==================");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
6. PreparedStatement 对象(安全):推荐使用
PreparedStatement是Statement的子类;Connection.preparedStatement()方法,获得实例对象;PreperedStatement可以避免 SQL 注入。
Statement会使数据库,频繁编译 SQL,可能造成数据库缓冲区溢出;PreparedStatement可对 SQL 进行 预编译,提高执行效率;PreperedStatement对于 SQL 中的参数,允许使用占位符的形式进行替换,简化 SQL 语句的编写。
使用 PreparedStatement 对象对数据库操作
- 插入数据
package com.xxx.demo03;
import com.xxx.demo02.utils.JdbcUtils;
import java.sql.*;
import java.util.Date;
public class TestInsert {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
try {
// 获取数据库连接
conn = JdbcUtils.getConnection();
// 区别:获取SQL的执行对象
// 要执行的SQL命令,SQL中的参数使用?作为占位符
String sql = "INSERT INTO `users`(`id`,`name`,`password`,`email`,`birthday`)" +
"VALUES(?,?,?,?,?);";
// 对SQL进行预编译
// 通过conn对象,获取执行SQL命令的prepareStatement对象
st = conn.prepareStatement(sql);
// 为SQL语句中的参数赋值,索引是从1开始的
// id是int类型的
st.setInt(1, 5);
//name是varchar(字符串类型)
st.setString(2, "测试3");
//password是varchar(字符串类型)
st.setString(3, "123");
//email是varchar(字符串类型)
st.setString(4, "cc@cc.com");
// birthday是date类型
// sql.Date:数据库 util.Date:java new Date().getTime():时间戳
st.setDate(5, new java.sql.Date(new Date().getTime()));
// 执行SQL
int i = st.executeUpdate();
if (i > 0) {
System.out.println("操作成功");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 释放资源
JdbcUtils.release(conn, st, null);
}
}
}
- 删除数据:变动部分
// SQL语句:删除
String sql = "DELETE FROM `users` WHERE `id`=?;";
// 对SQL进行预编译
st = conn.prepareStatement(sql);
// 为SQL语句中的参数赋值
st.setInt(1, 5);
- 更新数据:变动部分
// SQL语句:更新
String sql = "UPDATE `users` SET `name`=? WHERE `id`=?;";
// 对SQL进行预编译
st = conn.prepareStatement(sql);
// 第一个参数:名字
st.setString(1, "lisi");
// 第个的参数:id=3
st.setInt(2, 3);
- 查询数据
package com.xxx.demo03;
import com.xxx.demo02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TestSelect {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
// 获取数据库连接
conn = JdbcUtils.getConnection();
// SQL语句:查询
String sql = "SELECT * FROM `users` WHERE `id`=?;";
// 对SQL进行预编译
st = conn.prepareStatement(sql);
// 为SQL语句中的参数赋值
st.setInt(1, 3);
// 执行SQL
rs = st.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("name"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 释放资源
JdbcUtils.release(conn, st, rs);
}
}
}
避免 SQL 注入
package com.xxx.demo02.utils;
import java.sql.*;
public class SQL注入 {
public static void main(String[] args) {
// 正常登录
// login("lisi", "123456");
// SQL注入
login("' or '1=1", "' or '1=1");
}
// 登录业务
public static void login(String name, String password) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
// 参数会用引号包起来,防止注入
String sql = "SELECT * FROM `users` WHERE `name`=? AND `password`=?;";
st = conn.prepareStatement(sql);
st.setString(1, name);
st.setString(2, password);
rs = st.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("name"));
System.out.println(rs.getString("password"));
System.out.println("==================");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
- 原理:执行的时,参数会用引号包起来,并把参数中的引号,作为转义字符,从而避免了参数也作为条件的一部分。
7. 使用 IDEA 连接数据库
- 选择数据源
- 连接数据库
- 选择数据库
- 查看:双击数据表
- 更新数据:绿色键头提交或快捷键
Ctrl+Enter
- SQL 语句
- 如连接失败,查看原因
8. 事务
- 概念:事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功;
ACID 原则:
- 原子性(Atomic);
- 一致性(Consist);
- 隔离性(Isolated);
- 持久性(Durable)。
隔离性问题
- 脏读:指一个事务,读取了另外一个事务未提交的数据;
- 不可重复读:指在一个事务内,读取表中的某一行数据,多次读取结果不同;
- 虚读(幻读):指在一个事务内,读取到了别的事务插入的数据,导致前后读取不一致。
代码测试
-- 创建账户表
CREATE TABLE `account`(
`id` INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(40),
`money` FLOAT
);
-- 插入测试数据
insert into `account`(`name`,`money`) values('A',1000);
insert into `account`(`name`,`money`) values('B',1000);
insert into `account`(`name`,`money`) values('C',1000);
-
当 JDBC 程序向数据库,获得一个
Connection对象时,默认情况下,这个Connection对象,会 自动向数据库提交 在它上面发送的 SQL 语句; -
若要关闭这种默认提交方式,让多条 SQL 在一个事务中执行,可使用下列的 JDBC 控制事务语句;
- 开启事务:
Connection.setAutoCommit(false);- 事务自动关闭;
- 回滚事务:
Connection.rollback();- 默认自动回滚;
- 提交事务:
Connection.commit();
- 开启事务:
-
测试 1:模拟转账成功时的业务场景
package com.xxx.demo04;
import com.xxx.demo02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
// 模拟转账
public class TestTransaction01 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
try {
conn = JdbcUtils.getConnection();
// 通知数据库开启事务(start transaction)
conn.setAutoCommit(false);
String sql1 = "update `account` set `money` = `money`-100 where `name`='A';";
String sql2 = "update `account` set `money` = `money`+100 where `name`='B';";
// 执行sql1
st = conn.prepareStatement(sql1);
st.executeUpdate();
// 执行sql2
st = conn.prepareStatement(sql2);
st.executeUpdate();
// SQL都执行完毕,通知数据库提交事务(commit)
conn.commit();
System.out.println("操作成功!");
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn, st, null);
}
}
}
- 测试 2:模拟程序异常,导致一部分 SQL 执行失败(自动回滚事务)
package com.xxx.demo04;
import com.xxx.demo02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
// 模拟转账
public class TestTransaction02 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
try {
conn = JdbcUtils.getConnection();
// 通知数据库开启事务(start transaction)
conn.setAutoCommit(false);
String sql1 = "update `account` set `money` = `money`-100 where `name`='A';";
String sql2 = "update `account` set `money` = `money`+100 where `name`='B';";
// 执行sql1
st = conn.prepareStatement(sql1);
st.executeUpdate();
// 模拟执行完sql1后,程序出现异常,事务无法正常提交,数据库会自动执行回滚操作
int x = 1 / 0;
// 执行sql2
st = conn.prepareStatement(sql2);
st.executeUpdate();
// SQL都执行完毕,通知数据库提交事务(commit)
conn.commit();
System.out.println("操作成功!");
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn, st, null);
}
}
}
- 测试 3:模拟程序异常,导致一部分 SQL 执行失败(手动通知回滚事务)
package com.xxx.demo04;
import com.xxx.demo02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
// 模拟转账
public class TestTransaction03 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
try {
conn = JdbcUtils.getConnection();
// 通知数据库开启事务(start transaction)
conn.setAutoCommit(false);
String sql1 = "update `account` set `money` = `money`-100 where `name`='A';";
String sql2 = "update `account` set `money` = `money`+100 where `name`='B';";
// 执行sql1
st = conn.prepareStatement(sql1);
st.executeUpdate();
// 模拟执行完sql1后,程序出现异常,事务无法正常提交
int x = 1 / 0;
// 执行sql2
st = conn.prepareStatement(sql2);
st.executeUpdate();
// SQL都执行完毕,通知数据库提交事务(commit)
conn.commit();
System.out.println("操作成功!");
} catch (SQLException e) {
// 捕获到异常,手动通知数据库,执行回滚事务的操作
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
JdbcUtils.release(conn, st, null);
}
}
}
9. 数据库连接池
- 用户每次请求都需要向数据库获得链接,而数据库创建连接,通常需要消耗相对较大的资源,创建时间也较长;
- 如果网站一天10万的访问量,数据库服务器,就需要创建 10 万次连接,极大的浪费数据库资源,并且极易造成数据库服务器内存溢出、宕机。
数据库连接池的基本概念
- 数据库连接池,负责分配、管理和释放数据库连接,它允许应用程序,重复使用一个现有的数据库连接,而不是重新建立一个;
- 数据库连接池,在初始化时,创建一定数量的数据库连接,放到连接池中,这些数据库连接的数量,是由最小数据库连接数来设定的,无论这些数据库连接是否被使用,连接池都将一直保证,最小值的连接数量;
- 连接池的最大数据库连接数量,限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数,超过最大连接数时,这些请求,将被加入到等待队列中;
- 最小连接数和最大连接数的设置,要考虑到以下因素:
- 最小连接数:一直保持的数据库连接数,如果应用程序,对数据库连接的使用量不大,将会有大量的资源被浪费;
- 最大连接数:能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求,将被加入到等待队列中,影响以后的数据库操作;
- 如果最小连接数与最大连接数相差很大:最先连接请求将会获利,之后,超过最小连接数量的连接请求,等价于建立一个新的数据库连接,不过,这些大于最小连接数的数据库连接,在使用完,不会马上被释放,将被放到连接池中,等待重复使用或是空间超时后被释放。
- 编写连接池,需实现
java.sql.DataSource接口;
开源数据库连接池
- 现在很多 WEB 服务器(Weblogic、 WebSphere、Tomcat),都提供了
DataSoruce的实现,即连接池的实现; - 通常把
DataSource的实现,按其英文含义称之为 数据源,数据源中,都包含了数据库连接池的实现; - 也有一些开源组织,提供了数据源的独立实现:
- DBCP 数据库连接池;
- C3P0 数据库连接池;
- Druid
- 使用了数据库连接池后,在项目的实际开发中,就不需要编写连接数据库的代码,直接从数据源,获得数据库的连接。
DBCP 数据源
- Apache 下的开源连接池实现,需要在系统中增加两个 jar 文件:
- Tomcat 的连接池,正是采用该连接池来实现的,该数据库连接池,既可以与应用服务器整合使用,也可由应用程序独立使用;
DBCP 测试:
-
导入相关 jar 包;
-
类目录下,创建 DBCP 配置文件:
dbcpconfig.properties
# 连接设置
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcStudy?useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=123456
# 初始化连接
#initialSize=10
# 最大连接数量
#maxActive=50
# 最大空闲连接
#maxIdle=20
# 最小空闲连接
#minIdle=5
# 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒
#maxWait=60000
# JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
# 注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
#connectionProperties=useUnicode=true;characterEncoding=UTF8
# 指定由连接池所创建的连接的自动提交(auto-commit)状态。
#defaultAutoCommit=true
# driver default 指定由连接池所创建的连接的只读(read-only)状态。
# 如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
#defaultReadOnly=
# driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
# 可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
#defaultTransactionIsolation=READ_UNCOMMITTED
- 编写工具类:JdbcUtils_DBCP
package com.xxx.demo05.utils;
// dbcp1.4 导入类
// import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils_DBCP {
/*
在java中,编写数据库连接池需实现java.sql.DataSource接口,
每一种数据库连接池都是DataSource接口的实现
DBCP连接池就是java.sql.DataSource接口的一个具体实现
*/
private static DataSource ds = null;
// 在静态代码块中创建数据库连接池
static {
try {
// 加载dbcpconfig.properties配置文件
InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
// 创建属性集合
Properties props = new Properties();
// 读取流
props.load(in);
// 创建数据源 工厂模式->创建
ds = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
}
// 从数据源中获取数据库连接
public static Connection getConnection() throws SQLException {
// 从数据源中获取数据库连接
return ds.getConnection();
}
// 释放资源:从后往前关闭
public static void release(Connection conn, Statement st, ResultSet rs) {
if (rs != null) {
try {
// 关闭结果集ResultSet对象
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null) {
try {
// 关闭执行SQL命令的Statement对象
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
// 关闭Connection数据库连接对象
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
- 测试类
package com.xxx.demo05;
import com.xxx.demo05.utils.JdbcUtils_DBCP;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
public class TestDBCP {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
try {
// 通过数据库连接池,获取数据库连接
conn = JdbcUtils_DBCP.getConnection();
// 执行SQL命令
String sql = "INSERT INTO `users`(`id`,`name`,`password`,`email`,`birthday`)" +
"VALUES(?,?,?,?,?);";
// 对SQL进行预编译
st = conn.prepareStatement(sql);
st.setInt(1, 5);
st.setString(2, "测试3");
st.setString(3, "123");
st.setString(4, "cc@cc.com");
st.setDate(5, new java.sql.Date(new Date().getTime()));
// 执行SQL
int i = st.executeUpdate();
if (i > 0) {
System.out.println("插入成功");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 释放资源
JdbcUtils_DBCP.release(conn, st, null);
}
}
}
C3P0
- 下载地址
- C3P0 是一个开源的 JDBC 连接池,实现了数据源和 JNDI 绑定,支持 JDBC3 规范和 JDBC2 的标准扩展;
- C3P0 数据源,在项目开发中使用得较多,有Hibernate,Spring等;
- C3P0 与 DBCP 区别:
- DBCP 没有自动回收空闲连接的功能;
- C3P0 有自动回收空闲连接功能。
测试
- 导入相关 jar 包;
- 在类目录下创建 C3P0 的配置文件:
c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!-- 注意:XML文件中,标签内容不能换行,&用转义符&代替,否则程序出现异常 -->
<!--
C3P0的缺省(默认)配置:
如果在代码中“ComboPooledDataSource ds = new ComboPooledDataSource();”
这样写就表示使用的是C3P0的缺省(默认)配置信息来创建数据源
-->
<default-config>
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcStudy?useUnicode=true&characterEncoding=utf8&useSSL=true</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property>
</default-config>
<!--
C3P0的命名配置:
如果在代码中“ComboPooledDataSource ds = newComboPooledDataSource("MySQL");”
这样写就表示使用的是name是MySQL的配置信息来创建数据源
-->
<named-config name="MySQL">
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcStudy?useUnicode=true&characterEncoding=utf8&useSSL=true</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property>
</named-config>
</c3p0-config>
- 创建工具类
package com.xxx.demo06.utils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcUtils_C3P0 {
private static ComboPooledDataSource ds = null;
// 在静态代码块中创建数据库连接池
static {
try {
// 通过代码创建C3P0数据库连接池
/*
ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.cj.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbcstudy");
ds.setUser("root");
ds.setPassword("123456");
ds.setInitialPoolSize(10);
ds.setMinPoolSize(5);
ds.setMaxPoolSize(20);
*/
// 通过读取C3P0的xml配置文件创建数据源,C3P0的xml配置文件,必须放在src目录下
// ds = new ComboPooledDataSource(); 默认配置来创建数据源
// C3P0的命名配置来创建数据源
ds = new ComboPooledDataSource("MySQL");
} catch (Exception e) {
e.printStackTrace();
}
}
// 从数据源中获取数据库连接
public static Connection getConnection() throws SQLException {
// 从数据源中获取数据库连接
return ds.getConnection();
}
// 释放资源:从后往前关闭
public static void release(Connection conn, Statement st, ResultSet rs) {
if (rs != null) {
try {
// 关闭结果集ResultSet对象
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null) {
try {
// 关闭执行SQL命令的Statement对象
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
// 关闭Connection数据库连接对象
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
- 测试:
package com.xxx.demo06;
import com.xxx.demo06.utils.JdbcUtils_C3P0;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
public class TestC3P0 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
try {
// 通过 C3P0 数据库连接池,获取数据库连接
conn = JdbcUtils_C3P0.getConnection();
// 执行SQL命令
String sql = "INSERT INTO `users`(`id`,`name`,`password`,`email`,`birthday`)" +
"VALUES(?,?,?,?,?);";
// 对SQL进行预编译
st = conn.prepareStatement(sql);
st.setInt(1, 5);
st.setString(2, "测试3");
st.setString(3, "123");
st.setString(4, "cc@cc.com");
st.setDate(5, new java.sql.Date(new Date().getTime()));
// 执行SQL
int i = st.executeUpdate();
if (i > 0) {
System.out.println("插入成功");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 释放资源
JdbcUtils_C3P0.release(conn, st, null);
}
}
}
Druid
druid-1.2.8.jar:下载链接- Druid 连接池(德鲁伊),阿里巴巴的连接池,Java 性能最好的数据库连接池,类似于 DBCP连接池;
- Druid 和 DBCP 的连接属性是完全相同的,只是创建连接池的对象不同;
// DBCP 的数据库连接池实现
// import org.apache.commons.dbcp2.BasicDataSourceFactory;
// Druid 的替换为
import com.alibaba.druid.pool.DruidDataSourceFactory;
// ds = BasicDataSourceFactory.createDataSource(props);
// Druid
ds = DruidDataSourceFactory.createDataSource(props);
结论:
- 无论使用什么数据源,本质还是一样,
DataSource接口不会变,方法就不会变。