MySQL 入门

340 阅读1小时+

一、初识 MySQL

1. 为什么学习数据库

  • 存储数据的方法;
  • 程序、网站中、大量数据长久保存;
  • 数据库是几乎软件体系中最核心的一个存在

2. 数据库概述

  • 数据库 ( DataBase , 简称DB );
  • 概念 : 长期存放在计算机内,有组织,可共享的,大量数据的集合,是一个数据 仓库
  • 作用 : 保存数据,并能安全管理数据(如:增删改查等),减少冗余...
  • 分类 :
    • 关系型数据库 ( SQL ) :
      • MySQL、Oracle 、SQL Server 、SQLite 、DB2 ... ;
      • 通过外键关联,来建立表与表之间的关系。
    • 非关系型数据库 ( NoSQL 不只是数据库 ) :
      • Redis、MongoDB ...;
      • 数据以对象的形式,存储在数据库中,而对象之间的关系,通过每个对象自身的属性来决定。

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
    • 设置环境变量后,可以在任意目录打开;
  • 连接数据库语句 :

    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 字节
类型大小用途
CHAR0-255字节定长字符串
VARCHAR0-65535 字节变长字符串
TINYBLOB0-255字节不超过 255 个字符的二进制字符串
TINYTEXT0-255字节短文本字符串
BLOB0-65535字节二进制形式的长文本数据
TEXT0-65535字节长文本数据
MEDIUMBLOB0-16777215字节二进制形式的中等长度文本数据
MEDIUMTEXT0-16777215字节中等长度文本数据
LONGBLOB0-4294967295字节二进制形式的极大文本数据
LONGTEXT0-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。
名称MyISAMInnoDB
事务支持不支持支持
数据行锁定不支持支持
外键约束不支持支持
全文索引支持不支持
表空间大小较小较大,约为 MyISAM 两倍
  • 适用场合(经验) :

    • MyISAM : 节约空间及相应速度;

    • InnoDB : 安全性 , 事务处理及多用户操作数据表。

数据表的存储位置

  • MySQL 数据表以文件方式存放在磁盘中:

    • 包括表文件 , 数据文件 , 以及数据库的选项文件;
    • 位置:Mysql 安装目录\data\下存放数据表 ;
    • 目录名对应数据库名 , 该目录下文件名对应数据表。
  • 注意:(MySQL 8,取消了*.frm

    • InnoDB 类型数据表只有一个 *.frm 文件 , 以及上一级目录的 ibdata1 文件;
    • MyISAM 类型数据表,对应三个文件:
      • *.frm:表结构定义文件;
      • *.MYD:数据文件(data);
      • *.MYI:索引文件(index)。

设置数据表字符集

  • 可为数据库、数据表、数据列设定不同的字符集,设定方法 :

    • 命令: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=6false
<> 或 !=不等于5!=6true
大于5>6false
<小于5<6true
>=大于等于5>=6false
<=小于等于5<=6true
BETWEEN在某个范围之间BETWEEN 5 AND 10
AND并且5 > 1 AND 1 > 2(&&)false
OR5 > 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 条件语句

  • 作用:检索数据表中,符合条件的记录;
  • 搜索条件:一个或多个逻辑表达式,结果一般为 truefalse

逻辑操作符 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 NULLa IS NULL操作符为 NULL,结果为真
IS NOT NULLa IS NOT NULL操作符不为 NULL,结果为真
BETWEENa BETWEEN b AND ca 范围在 b 与 c 之间,结果为真
LIKEa LIKE bSQL 模式匹配:a匹配b,结果为真
INa 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 判断条件
-- 查询参加考试的学生信息(学号、学生姓名、科目编号、分数)
-- 所有学生信息
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','办公信息');

  • 表折分:

    • 父类

      categoryidcategoryName
      2信息技术
      3软件开发
      5美术设计
    • 子类

      pidcategoryidcategoryName
      34数据库
      28办公信息
      36web开发
      57ps技术
    • 操作:查询父类对应子类的关系

      父类子类
      信息技术办公信息
      软件开发数据库
      软件开发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 UNCOMMITTEDYYYN
READ COMMITTEDNYYN
REPEATABLE READNNYN
SERIALIZABLENNNY

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. 用户管理

  1. 使用 SQLyog 创建用户,并授予权限演示
  1. 基本命令
/* 用户和权限管理 */ 
-- 用户信息表: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 的数据库实现(即数据库驱动);

  • MySQL 8.028 驱动

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();
    }
}

连接数据库七大步骤:

  1. 加载驱动;
    • MySQL8:com.mysql.cj.jdbc.Driver
    • MySQL5.7:com.mysql.jdbc.Driver
  2. 连接数据库 DriverManager(驱动管理);
  3. 获取 sql 的执行对象 Statement;
  4. 写 sql;
  5. 执行 sql;
  6. 获得返回的结果集;
  7. 释放连接。

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;

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 对象,对数据库操作

  1. 插入数据
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);
        }
    }
}
  1. 删除数据:只更改 SQL 语句
String sql = "DELETE FROM `users` WHERE `id`=4;";
  1. 更新数据:只更改 SQL 语句
String sql = "UPDATE `users` SET `name`='测试2' WHERE `id`=2;";
  1. 查询数据
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 对象(安全):推荐使用

  • PreparedStatementStatement 的子类;
    • 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文件中,标签内容不能换行,&用转义符&amp;代替,否则程序出现异常 -->
    <!--
        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&amp;characterEncoding=utf8&amp;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&amp;characterEncoding=utf8&amp;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 接口不会变,方法就不会变。