mysql

发布时间 2024-01-08 22:13:27作者: PursueExcellence

1.初识MySQL

JavaEE:企业级Java开发 Web

前端(页面:展示:数据)

后台 (连接点:连接数据库JDBC,连接前端(控制视图跳转,给前端传递数据))

数据库(存数据,Txt,Excel,Word)

只会写代码,学好数据库,基本混饭吃:

操作系统,数据结构与算法!当一个不错的程序猿!

离散数学,数字电路,体系结构,编译原理。+实战经验,优秀程序猿

为什么学数据库

1、岗位需求

2、现在的世界,大数据时代,得数据者得天下

3、被迫需求:存数据

4、数据库是所有软件体系中最核心的存在 DBA

什么是数据库

数据库:(DB,DataBase)

概念:数据仓库,软件,安装在操作系统之(windows,Linux。mac)上的!SQL,可以存储大量的数据,500万!

作用:存储数据,管理数据 Excel

数据库分类

关系型数据库:(SQL)

MySQL, Oracle, sql Server, DB2, SQLite 通过表和表之间,行和列之间的关系进行数据的存储

非关系型数据库:(NoSQL) Not Only SQL

Redis, MongDB 非关系型数据库,对象存储,通过对象自身的属性来决定。

DBMS(数据库管理系统)

数据库的管理软件,科学有效的管理我们的数据,维护和获取 MySQL ,数据管理系统!

MySQL简介

MySQL是一个关系型数据库管理系统

前世: 瑞典MySQL AB 公司

今身: 属于 Oracle 旗下产品

MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。

开源的数据库软件

体积小,速度快,总体拥有成本低,招人成本比较低。

中小型网站,或者大型网站,集群

官网: https://www.mysql.com/

连接数据库

-- 连接数据库
mysql -u root -p123456 

-- 修改密码
update mysql.user set authentication_string=password('123456') where user='root' and Host='localhost';  

-- 刷新权限
flush privileges;

-- 查看所有的数据库
show databases;

-- 切换数据库, use 数据库名
mysql> use school
Database changed

-- 查看数据库中所有的表
show tables;
-- 显示表的信息
describe student;
-- 创建一个数据库
create database westos;

-- 退出连接
exit;

-- 单行注释(sql本来注释)

/*
多行注释
*/

2.操作数据库

操作数据库》操作数据库中的表》操作数据库中表的数据

MySQL不区分大小写

操作数据库

创建数据库

CREATE DATABASE IF NOT EXISTS westos;

删除数据库

DROP DATABASE IF EXISTS westos;

使用数据库

-- 如果你的表名或者字段名是一个特殊字符,需要带``   
USE 'school';

查看数据库

-- 查看所有数据库
SHOW DATABASES;

数据库的列类型

数值

tinyint 十分小的数据 1个字节

smallint 较小的数据 2个字节

mediumint 中等大小 3个字节

int 标准的整数 4个字节(常用)

bigint 较大的数据 8个字节

float 浮点数 4个字节

double 浮点数 8个字节 (精度问题)

decimal 字符串形式的浮点数,金融计算的时候,一般用

字符串

char 字符串固定大小 0-255

varchar 可变字符串 0-65535(常用)

tinytext 微型文本 28-1

text 文本串 216-1 (保存大文本)

时间日期

date 日期 YYYY-MM-DD

time 时间 HH:mm:ss

datetime 最常用的时间格式 YYYY-MM-DD HH:mm:ss

timestamp 时间戳 1970.1.1到现在的毫秒数

null

没有值,未知

注意:不要使用null进行运算,结果为null

数据库的字段属性 (重点)

数据库字段的属性用来限制字段,比如指定字段的类型、长度....

unsigened

无符号的整数,声明该列不能为负数

image-20231231132733183

zerofill

0填充长度不够的位数

比如指定字段长度是10,存储的数据是1,长度不够用0填充,则为0000000001

image-20231231133121826

自增

  • 通常理解为自增,自动在上一条记录的基础上+1(默认)

  • 通常用来设计唯一的主键 index,必须是整数类似

  • 可以自定义设置主键自增的起始值和步长

image-20231231133404768

非空 not null

image-20231231133540722

  • 假设设置为 not null,如何不给他赋值,就会报错
  • null 如果不填写,默认为NULL

默认

设置默认的值!

image-20231231133616089

创建数据库表

-- 目标:创建一个schoo1数据库

-- 创建学生表(列,字段)使用SQL 创建

-- 学号int 登录密码varchar(20)姓名,性别varchar(2),出生日期(datatime),家庭住址,emai1

-- 注意点,使用英文(),表的名称和字段尽量使用括起来

--  AUTO_ INCREMENT 自增

-- 字符串使用单引号括起来!

-- 所有的语句后面加,(英文的),最后一个不用加

-- PRIMARY KEY 主键,一般- 一个表只有一个唯一 -的主键!

CREATE TABLE IF NOT EXISTS `student` (
`id` INT(4) 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 '性别',
`birthday` DATETIME DEFAULT NULL COMMENT '出生日期',
`address` VARCHAR(100) DEFAULT NULL COMMENT '家庭住址',
`email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

格式

CREATE TABLE [IF NOT EXISTS] `表名`(
`字段名` 列类型 [属性] [索引] [注释],
`字段名` 列类型 [属性] [索引] [注释],
			...
`字段名` 列类型 [属性] [索引] [注释]
)[表类型] [表的字符集设置] [注释]

常用命令

-- 查看创建数据库的语句
SHOW CREATE DATABASE school 

-- 查看student数据表的定义语句
SHOW CREATE TABLE student 

-- 显示表的结构
DESC student 

数据表的类型

MYISAM和INNODB区别

使用上的区别

数据库的存储引擎

image-20231231143521673

INNODB 默认使用

MYISAM 早些年使用

MYISAM INNODB
事务支持 不支持 支持
数据行锁定 不支持(表锁) 支持
外键约束 不支持 支持
全文索引 支持 不支持
表空间的大小 较小 较大,约为MYISAM的两倍

MYISAM

  • 节约空间
  • 速度较快

INNODB

  • 安全性高
  • 事务处理
  • 多表多用户操作

物理空间存在的位置区别

在物理空间存在的位置

所有的数据库文件都存在data目录下,一个文件夹就对应一个数据库

image-20231231184944455

本质还是文件的存储

INNODB对应文件的存储位置

  • 数据库文件夹下的*.frm文件

    image-20231231185543284

  • 上级目录下的ibdata1文件

    image-20231231185613121

MYISAM对应文件的存储位置

  • *.frm文件:表结构的定义文件
  • *.MYD文件:数据文件
  • *.MYI 文件: 索引文件

设置数据库字符集编码

如果不设置数据库的字符编码集,会使用mysql默认的字符集编码(不支持中文)

设置字符编码集有两种方式,在建表的时候指定字符编码集、或者在mysql的核心配置文件中设置

建表的时候指定(推荐)

image-20231231190344808

mysql的核心配置文件

image-20231231190424083

my.ini文件

character-set-server=utf8

image-20231231190508949

修改删除表

修改

-- 修改表名 
-- ALTER TABLE 旧表面 RENAME AS 新表名
ALTER TABLE student RENAME  AS student1

-- 增加表的字段 
-- ALTER TABLE 表名 ADD 字段名 列属性
ALTER TABLE student1 ADD age INT(11)

-- 修改表的字段(重命名,修改约束)
-- 修改约束和字段的类型
ALTER TABLE student1 MODIFY age VARCHAR(11) 
-- 字段重命名
ALTER TABLE student1 CHANGE age age1 INT(1) 
 
-- 删除表的字段
ALTER TABLE student1 DROP age1

删除

-- 删除表
DROP TABLE IF EXISTS student1

所有的创建和删除操作尽量加上判断,以免报错

注意点

  • 字段名,使用``这个包裹
  • 注释 -- /**/
  • sql 关键字大小写不敏感,建议写小写
  • 所有的符号全部用英文

3.MySQL数据管理

外键(了解)

在创建表的时候,增加约束(麻烦,比较复杂)

CREATE TABLE `grade`(
`gradeid` INT(10) NOT NULL AUTO_INCREMENT COMMENT '年级id',
`gradename` VARCHAR(50) NOT NULL COMMENT '年级名称',
PRIMARY KEY (`gradeid`)
)ENGINE=INNODB DEFAULT CHARSET=utf8
 
-- 学生表的 gradeid 字段 要去引用年级表的gradeid
-- 定义外键KEY
-- 给这个外键添加约束(执行引用) references 引用
CREATE TABLE IF NOT EXISTS `student` (
`id` INT(4) 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 '性别',
`birthday` DATETIME DEFAULT NULL COMMENT '出生日期',
`gradeid` INT(10) NOT NULL COMMENT '学生年级',
`address` VARCHAR(100) DEFAULT NULL COMMENT '家庭住址',
`email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`),
KEY `FK_gardeid` (`gradeid`),
CONSTRAINT `FK_gardeid` FOREIGN KEY (`gradeid`) REFERENCES `grade` (gradeid)
)ENGINE=INNODB DEFAULT CHARSET=utf8

删除有外键关系的表的时候,必须先删除引用的表(从表),再删除被引用的表(主表)

方式二: 创建表成功后添加外键

CREATE TABLE `grade`(
`gradeid` INT(10) NOT NULL AUTO_INCREMENT COMMENT '年级id',
`gradename` VARCHAR(50) NOT NULL COMMENT '年级名称',
PRIMARY KEY (`gradeid`)
)ENGINE=INNODB DEFAULT CHARSET=utf8
 
-- 学生表的 gradeid 字段 要去引用年级表的gradeid
-- 创建表的时候没有外键关系
CREATE TABLE IF NOT EXISTS `student` (
`id` INT(4) 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 '性别',
`birthday` DATETIME DEFAULT NULL COMMENT '出生日期',
`gradeid` INT(10) NOT NULL COMMENT '学生年级',
`address` VARCHAR(100) DEFAULT NULL COMMENT '家庭住址',
`email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8
 
-- 添加外键关系
-- ALTER TABLE `表` ADD CONSTRAINT '约束名' FOREIGN KEY(作为外键的列)REFERENCES 引用的字段(表)
ALTER TABLE `student`
ADD CONSTRAINT `FK_gradeid` FOREIGN KEY (`gradeid`) REFERENCES `grade`(`gradeid`);

以上的操作都是物理外键(数据库级别外键),不建议使用。(避免数据库过多造成困扰,这里了解即可)

最佳实践:

  • 数据库就是单纯的表,只用来存数据,只有行(数据)和列(字段)
  • 我们想使用多张表的数据,想使用外键(程序去实现)

DML语言(全记住)

数据库存在的意义:数据存储,数据管理

DML语言:数据操作语言

  • Insert
  • update
  • delete

添加(insert)

表结构

grade表

image-20231231200347452

student表

image-20231231200858176

-- 插入语句(添加)
-- insert into 表名([字段一], [字段二])values('值1'),('值2')
INSERT INTO `grade` (`gradename`) VALUES('大四')
 
-- 由于主键自增我们可以省略(如何不写表的字段,他会一一匹配)
-- 插入失败,没有写表的字段会进行一一匹配,这里只添加了一个字段的值,所以失败
INSERT INTO `grade` VALUES('大三')
-- 成功
INSERT INTO `grade` (`gradeid`,`gradename`) VALUES ('大三','null')
 
-- 一般写插入语句,我们一定要数据和字段一一对应。
-- 插入多条数据
INSERT INTO `grade`(`gradename`) VALUES ('大二'),('大一');

INSERT INTO `student`(`name`,`pwd`,`sex`) VALUES ('张三','aaaaa','男')
INSERT INTO `student`(`name`,`pwd`,`sex`) 
VALUES ('李四','aaaaa','男'),('王五','23232','女')

注意事项:

  • 字段和字段之间用逗号分开
  • 字段可以省略,但是后面的值必须一一对应
  • 可以同时插入多条数据,VALUES后面的值需要使用,号隔开即可

修改

update 修改谁(条件) set 原来的值=新值

-- 修改学员名字
UPDATE `student` SET `name`='囷' WHERE id =1;
-- 不指定条件的情况下,会改动所有表
UPDATE `student` SET `name`='233'
 
-- 语法;
-- UPDATE 表名 set column_name== value,[column_name== value] where 条件

条件:where 子句 运算符 id 等于 某个值,大于某个值,在某个区间内修改

操作符返回布尔值

操作符 含义 范围 结果
= 等于 5=6 false
!= <> 不等于 5!=6 true
> 大于
< 小于
between and 在某个范围内,闭合区间 [2,5]
and && 5>1 and 1>2 false
or || 5>1 or 1>2 true

注意点:

  • column_name 是数据库的列,带上``
  • 条件,是筛选的条件,如果没有指定,则会修改所有的列
  • value 是一个具体的值,也可以是一个变量
  • 多个设置的属性之间,使用英文逗号隔开

删除

-- 语法 
delete from 表名 [where 条件]
-- 删除数据 (避免这样写)
DELETE FROM `student`

-- 删除指定
DELETE FROM `student` where id= 1

TRUNCATE 命令

作用:清空表数据,表的结构和索引不会变

TRUNCATE student

DELETE 和 TRUNCATE 区别

相同点:

  • 都能删除数据,
  • 都不会删除表结构

不同点:

  • TRUNCATE 重新设置自增列 计数器会归零
  • TRUNCATE 不会影响事务

了解即可:delete删除的问题 重启数据库,现象

  • InnoDB 自增列会从1开始(存在内存当中,断电即失)

  • MyISAM 继续从上一个自增量开始(存在文件中,不会丢失)

4.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}]; -- 指定查询的记录从哪条至哪条

select语句的执行顺序

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

数据准备

-- 创建一个school数据库
create database if not exists `school`;
use `school`;

-- 创建学生表drop table if exists `student`;
create table `student`(
    `studentno` int(4) not null comment '学号',
    `loginpwd` varchar(20) default null,
    `studentname` varchar(20) default null comment '学生姓名',
    `sex` tinyint(1) default null comment '性别,0或1',
    `gradeid` int(11) 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=myisam default charset=utf8;

-- 创建年级表
drop table if exists grade;
create table grade(
gradeid int(11) not null auto_increment comment '年级编号',
gradename varchar(50) not null comment '年级名称',
primary key (gradeid)
) engine=innodb auto_increment = 6 default charset = utf8;

-- 创建科目表
drop table if exists subject;
create table subject(
subjectno int(11) not null auto_increment comment '课程编号',
subjectname varchar(50) default null comment '课程名称',
classhour int(4) default null comment '学时',
gradeid int(4) default null comment '年级编号',
primary key (subjectno)
)engine = innodb auto_increment = 19 default charset = utf8;

-- 创建成绩表
drop table if exists result;
create table result(
studentno int(4) not null comment '学号',
subjectno int(4) not null comment '课程编号',
examdate datetime not null comment '考试日期',
studentresult int (4) not null comment '考试成绩',
key subjectno (subjectno)
)engine = innodb default charset = utf8;

-- 插入学生数据 其余自行添加 这里只添加了2行
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');

-- 插入成绩数据  这里仅插入了一组,其余自行添加
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);

-- 插入年级数据
insert into grade (gradeid,gradename) values(1,'大一'),(2,'大二'),(3,'大三'),(4,'大四'),(5,'预科班');

-- 插入科目数据
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);

查询指定字段

基本查询

-- 查询全部的学生 select 字段 from 表
SELECT * FROM student
SELECT * FROM result

-- 查询指定字段
SELECT studentNo,studentname FROM student

-- 别名,给结果起一个名字 AS 可以给字段起别名,也可以给表起别名
SELECT studentno AS 学号,studentname AS 学生姓名 FROM student

-- 函数 concat(a,b)
SELECT CONCAT('姓名:',studentname) AS 新名字 FROM student

有的时候,列名字不是那么的见名知意.我们起别名 AS

  • 字段名 as 别名
  • 表名 as 别名

去重

distinct

作用:去除select查询出来的结果中重复的数据,只显示一条

-- 查询一下有哪些同学参加了考试,成绩
-- 查询全部的考试成绩
SELECT * FROM result 

-- 查询有哪些同学参加了考试
SELECT `studentno` FROM result

-- 发现重复数据, 去重
SELECT DISTINCT `studentno` FROM result

-- 多个字段去重
SELECT DISTINCT `studentno`,`subjectno` FROM result

数据库的列

-- 查询系统版本(函数)
SELECT VERSION()

-- 查询自增的步长(变量)
SELECT @@auto_increment_increment

-- 用来计算(表达式)
SELECT 100*3-1 AS 计算结果

-- 学员考试成绩 +1分查看
SELECT studentno,studentresult+1 AS '提分后'FROM result

数据库中的表达式:文本值,列,Null,函数,计算表达式,系统变量....

where条件子句

作用:检索数据中"符合条件"的值

搜索的条件由一个或多个表达式组成,where字句的结果是一个布尔值

逻辑运算符

运算符 语法 描述
and && a and b a&&b 逻辑与,两个为真,结果为真
or || a or b a||b 逻辑或,一个为真,结果为真
Not ! not a !a 逻辑非,真为假,假为真

尽量使用英文字母

select studentno,studentresult from result 

-- and &&
where studentresult>=95 and studentresult<=100

-- 区间查询,闭区间
where studentresult between 95 and 100

-- not !
-- 除了1000号学生之外的同学的成绩
where studentno != 1000;
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,z3...) 假设a在a1,或者a2...其中的某一个值中,结果为真
-- like

select `studentno`,`studentname` from `student`

-- 查询姓刘的同学
where studentname like '刘%'

-- 查询姓刘的同学,名字后面只有一个字的
where studentname like '刘_'

-- 查询姓刘的同学,名字后面只有两个字的
select `studentno`,`studentname` from `student`
where studentname like '刘__'

-- 查询名字中有嘉字的同学 %嘉%
where studentname like '%嘉%'


-- in

select `studentno`,`studentname` from `student`

-- 查询1001,1002,1003号学员
where studentno in(1001,1002,1003);

-- 查询在北京的学生
select studentno,studentname from student
where address in ('北京');


-- null not null

select studentno,studentname from student

-- 查询地址为空的学生 null ''
where address='' or address is null

-- 查询有出生日期的同学 不为空
where borndate is not null

-- 查询出生日期不为null的同学
where borndate is not null

联表查询

join

img

-- 查询参加考试的同学 (学号,姓名,考试编号,分数)

/*
    1.分析需求,分析查询的字段来自哪些表
    2.确定使用哪种连接查询?7种
    	确定交叉点(这两个表中哪个数据是相同的)
    	判断的条件: 学生表中 studentNo = 成绩表中 studentNo 
*/

-- 等值查询
select s.studentno,s.studentname,r.subjectno,r.studentresult
from student s
inner join result r where s.studentno=r.studentno

-- inner join
select s.studentno,s.studentname,r.subjectno,r.studentresult
from student s
inner join result r on s.studentno=r.studentno

-- left join
select s.studentno,s.studentname,r.subjectno,r.studentresult
from student s
left join result r on s.studentno=r.studentno

-- left join where
select s.studentno,s.studentname,r.subjectno,r.studentresult
from student s
left join result r on s.studentno=r.studentno
where r.studentno is not null

-- right join
select s.studentno,s.studentname,r.subjectno,r.studentresult
from student s
right join result r on s.studentno=r.studentno

-- right join where
select s.studentno,s.studentname,r.subjectno,r.studentresult
from student s
right join result r on s.studentno=r.studentno
where s.studentno is not null

-- full outer join
select s.studentno,s.studentname,r.subjectno,r.studentresult
from student s
left join result r on s.studentno=r.studentno
UNION
select s.studentno,s.studentname,r.subjectno,r.studentresult
from student s
right join result r on s.studentno=r.studentno

-- full outer join where
select s.studentno,s.studentname,r.subjectno,r.studentresult
from student s
left join result r on s.studentno=r.studentno
where r.studentno is null
UNION
select s.studentno,s.studentname,r.subjectno,r.studentresult
from student s
right join result r on s.studentno=r.studentno
where s.studentno is null

-- 我要查询哪些数据 select...

-- 从哪几个表查 FROM 表 XXX Join 连接的表 on 交叉条件

-- 假设存在一种多张表查询,慢慢来,先查询两张表然后再慢慢增加

-- From a left join b  以左表为准

-- From a right join b 以右表为准
操作 描述
inner join 取交集
left join 会从左表中返回所有的值,即使右表中没有匹配
right join 会从右表中返回所有的值,即使左表中没有匹配

自连接

自己的表跟自己的表连接,核心为:一张表拆为两张一样的表

数据信息

image-20240101110221603

分析

父类

categoryid categoryName
2 信息技术
3 软件开发
5 美术设计

子类

pid categoryid categoryName
3 4 数据库
2 8 办公信息
3 6 WEB开发
5 7 PS技术

关系:父类的categoryid对应子类的pid

select c1.categoryName,c2.categoryName
from category c1
inner join category c2 on c1.categoryid=c2.pid
父类 子类
信息技术 办公信息
软件开发 数据库
软件开发 WEB开发
美术设计 PS技术

分页和排序

分页

limit

语法:limit 查询起始下标(从0开始),pagesize

-- 分页,每页显示2条数据

-- 语法: limit 查询起始下标(从0开始),显示数据条数大小
-- limit 0,2 1-2
-- limit 1,2 2-3
-- limit 6,2 7-8
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` like '%高等数学%'
order by studentResult ASC
limit 0,2

-- 规律,如果每页显示5条
-- 第1页 limit 0,5
-- 第2页 limit 5,5
-- 第3页 limit 10,5
-- 第n页 limit 5*(n-1),5

排序

order by

  • 升序:ASC
  • 降序:DESC
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` like '%高等数学%'
-- 升序
order by studentResult ASC

练习:查询,Java第一学年,课程成绩排名前十的学生,并且分数要不小于80的学生信息(学号,姓名,课程名称,分数)

select s.`StudentNo`,`StudentNmae`,`SubjectName`,`StudentResult`
from `student` s
inner join `result` r
on s.StudentNo = r.`SubjectNo`
where SubjectName =`Java第一学年`AND StudentResult >=80
order by StudentResult DESC
limit 0,10

子查询

where字句后面的值一般是固定的,子查询这个值是计算出来的

本质:在where语句中嵌套一个子查询语句

-- 1.查询数据库结构-1的所有考试结果(学号,科目编号,成绩),降序排列

-- 连表查询
select `StudentNo`,r.`SubjectNo`,`StudentResult`
from `result` r
inner join `subject` sub
on r.subjectNo =sub.subjectNo
where subjectNo ='数据库结构-1'
order by StudentResult DESC
-- 子查询
SELECT `StudentNo`,r.`SubjectName`,`StudentResult`
FROM `result`
WHERE StudentNo=(
	SELECT SubjectNo FROM  `subject` 
    WHERE SubjectName = '数据库结构-1'
)
ORDER BY StudentResult DESC

-- 分数不少于80分的学生的学号和姓名

-- 连表查询
SELECT DISTINCT s.`StudentNo`,`StudentName`
FROM student s
INNER JOIN result r
ON r.StudentNo = s.StudentNo
WHERE StudentResult>=80

-- 在这个基础上 增加一个科目 ,高等数学-2
SELECT DISTINCT s.`StudentNo`,`StudentName`
FROM student s
INNER JOIN result r
ON r.StudentNo = s.StudentNo
WHERE StudentResult>=80 AND `SubjectNo`=(
    SELECT Subject FROM `subject`
    WHERE SubjectName='高等数学-2'
)

-- 查询课程为 高等数学-2 且分数不小于80分的同学的学号和姓名
SELECT s.`StudentNo`,`StudentName`
FROM student s
INNER JOIN result r
ON s.StudentNo = r.StudentNo
INNER JOIN `subject` sub
ON r.`SubjectName`='高等数学-2'
WHERE `SubjectaName`='高等数学-2' AND StudentResult >=80

-- 再改造 (由里即外)
SELECT `StudentNo`,`StudentName` FROM student
WHERE StudentNo IN(
SELECT StudentNo result WHERE StudentResult >80 AND SubjectNo =(
SELECT SubjectNo FROM `subject` WHERE `SubjectaName`='高等数学-2'
)

5.MySQL函数

官网: https://dev.mysql.com/doc/refman/8.0/en/functions.html

常用函数(并不常用)

数学计算

-- 绝对值
SELECT ABS(-8)  -- 8

-- 向上取整
SELECT CEILING(9.4) -- 10

-- 向下取整
SELECT FLOOR(9.4) -- 9

-- 返回一个0~1之间的随机数
SELECT RAND() 

-- 判断一个数的符号 负数返回-1,正数返回1
SELECT SIGN(-10) -- -1

字符串

 -- 字符串长度
SELECT CHAR_LENGTH('大幅的部分风格的服饰')

-- 拼接字符串
SELECT CONCAT('I','LOVE','U')

-- 替换,输出超级爱helloworld 1 起始位置 2 替换的长度
SELECT INSERT('我爱helloworld',1,2,'超级爱') 

-- 转成小写
SELECT LOWER('HELLOWORLD') 

-- 转成大写
SELECT UPPER('helloworld') 

-- 查询字符位置,输出7
SELECT INSTR('kuangshen','h') 

-- 替换出现的指定字符串 输出6shen
SELECT REPLACE('kuangshen','kuang','6')

-- 返回指定的子字符串 输出 坚持就能成功
SELECT SUBSTR('狂神说坚持就能成功',4,6) 

-- 反转
SELECT REVERSE('狂神说坚持就能成功')

-- 查询姓 张的同学, 名字 换成 赞
SELECT studentname FROM student WHERE studentname LIKE '张%'

时间和日期函数(记住)

-- 获取当前日期
SELECT CURRENT_DATE()
 
-- 获取当前时间
SELECT CURRENT_TIME() 

-- 获取当前日期时间
SELECT NOW() 

 -- 本地时间
SELECT LOCALTIME()

-- 系统时间 
SELECT SYSDATE() 

SELECT YEAR(NOW())

SELECT MONTH(NOW())

SELECT DAY(NOW())

SELECT HOUR(NOW())

SELECT MINUTE(NOW())

SELECT SECOND(NOW())

系统函数

-- 系统

SELECT SYSTEM_USER()

SELECT USER()

SELECT VERSION()

聚合函数(常用)

函数名称 描述
count() 计数
sum() 求和
avg() 平均值
max() 最大值
min() 最小值
... ...

count()

-- count(指定列)
select count(studentname) from student;

-- count(1)
select count(1) from student;

-- count(*)
select count(*) from student;

从执行效果来看,count(指定列)、count(1)、count(*) 基本没差别

区别

计数
  • count(列名) 会统计该列字段在表中出现的次数,会忽略字段为null 的情况,即不统计字段为null 的记录。
  • count(1) 会统计表中的所有的记录数,不会忽略NULL,包含字段为null 的记录。
  • count(*)包括了所有的列,相当于行数,在统计结果的时候,不会忽略NULL
执行效率
  • 如果有表有主键
    • count(主键)最快
    • count(1)会比count(非主键列)快
  • 若表多个列并且没有主键
    • count(1)最快
  • 若表只有一个字段
    • count(*)最快。

所以实际业务中一般用count(1)比较普遍,但是如果需要聚合多个列,则用count(列名)比较合适。

其他

select sum(StudentResult) as 总和 from result

select avg(StudentResult) as 平均分 from result

select max(StudentResult) as 最高分 from result

select min(StudentResult) as 最低分 from result

分组查询

-- 查询不同课程的平均分,最高分,最低分
select subjectName,AVG(StudentResult),MAX(StudentResult),MIN(StudentResult)
From result r
INNER JOIN `subject` sub ON r.subjectNo= sub.subjectNo
group by r.subjectNo -- 通过什么字段来分组

-- 查询不同课程的平均分,最高分,最低分,并且平均分大于80的课程
select subjectName,AVG(StudentResult),MAX(StudentResult),MIN(StudentResult)
from result r
INNER JOIN `subject` sub ON r.subjectNo= sub.subjectNo
group by r.subjectNo HAVING AVG(StudentResult) > 80

数据库级别的MD5加密(拓展)

什么是MD5?

MD5信息摘要算法 (英语:MD5 Message-Digest Algorithm),一种被广泛使用的 密码散列函数 ,可以产生出一个128位(16 字节 )的散列值(hash value),用于确保信息传输完整一致。

主要增强算法复杂度和不可逆性

MD5不可逆,具体的值的md5是一样的

MD5破解网站的原理,背后有一个字典,MD5加密后的值,加密前的值

-- ==================测试MD5加密====================
CREATE TABLE `testmd5`(
`id` INT(4) NOT NULL,
`name` VARCHAR(20) NOT NULL,
`pwd` VARCHAR(50) NOT NULL,
PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

-- 明文密码
INSERT INTO testmd5 VALUES(1,'zhangsan','123456'),(2,'lisi','123456'),(3,'wangwu','123456')

-- 加密
UPDATE testmd5 SET pwd=MD5(pwd) WHERE id =1

-- 加密全部的密码
UPDATE testmd5 SET pwd=MD5(pwd) where id !=1

-- 插入的时候加密
INSERT INTO testmd5 VALUES(4,'小明',MD5('123456'))

-- 如何校验:将用户传递进来的密码,进行md5加密,然后比对加密后的值
SELECT *FROM testmd5 WHERE `name`='小明' AND pwd=MD5('123456')

6.事务

什么是事务

一组sql,要么全部执行成功,要么全部执行失败

事务原则(ACID):

  • 原子性:要么都成功,要么都失败
  • 一致性:事务前后的数据完整性要保持一致
  • 隔离性:事务产生并发时候,互不干扰
  • 持久性:事务一旦提交就不可逆转,被持久化到数据库中

隔离性产生的问题:

  • 脏读:一个事务读取到了另一个事务未提交的数据
  • 不可重复读:在一个事务内读取表中的某一行数据,多次读取的结果不同。
    • 这个不一定是错误的,只是场合不对
  • 幻读:在一个事务内读取到了别的事务插入的数据,导致数据前后读取不一致。(一般是行影响,多了一行)

执行事务

-- MySQL是默认开启事务自动提交的
set autocommit = 0     -- 关闭
set autocommit = 1     -- 开启(默认)

-- 手动处理事务
-- 关闭自动提交
set autocommit = 0 


-- 事务开启
-- 标记一个事务的开启,从这个之后的sql都在同一个事务内
start TRANSACTION   

-- 提交:持久化(成功)
commit 

-- 回滚:回到原来的样子(失败)
ROLLBACK

-- 事务结束
set autocommit = 1   -- 开启事务提交

-- 了解
-- 设置一个事务的保存点
SAVEPOINT 保存点名称    
ROLLBACK to SAVEPOINT 回滚到保存点
RELEASE SAVEPOINT  撤销保存点

模拟转账场景

CREATE DATABASE shop CHARACTER SET utf8 COLLATE utf8_general_ci

USE shop

CREATE TABLE `account`(
`id` INT(3) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(30) NOT NULL,
`money` DECIMAL(9,2) NOT NULL,
PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO account(`name`,`money`) VALUES ('A',2000),('B',10000)

-- 模拟转账:事务
SET autocommit = 0; -- 关闭自动提交

START TRANSACTION -- 开启事务(一组事务)

UPDATE account SET money = money-500 WHERE `name` = 'A' -- A 转账给B
UPDATE account SET money = money+500 WHERE `name` = 'B' -- B 收到钱

COMMIT ; -- 提交事务
ROLLBACK ; -- 回滚

SET autocommit=1 -- 恢复默认值

7.索引

http://blog.codinglabs.org/articles/theory-of-mysql-index.html

MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构

提取句子主干,就可以得到索引的本质:索引是数据结构

索引的分类

在一个表中,主键索引只能有一个,唯一索引可以有多个

  • 主键索引 (primary key)
    • 唯一标识,主键不可重复,只能有一个列作为主键
  • 唯一索引 (unique key)
    • 避免重复的列出现,唯一索引可以重复,多个列都可以表示为唯一索引
    • 唯一索引:不允许具有索引值相同的行,从而禁止重复的索引或键值
  • 常规索引 (key)
    • 默认的,index.key关键字来设置
  • 全文索引 (fulltext)
    • 在特定的数据库引擎下才有,MyISAM
    • 快速定位数据

使用索引

在创建表的时候给字段增加索引

创建完毕后,增加索引

-- 增加一个全文索引
ALTER TABLE `student` ADD FULLTEXT INDEX `studentName`(`studentName`);

-- 显示所有的索引信息
SHOW INDEX FROM student;

-- explain 分析sql执行的状况
-- 非全文索引
EXPLAIN SELECT * FROM student; 

EXPLAIN SELECT * FROM student WHERE MATCH(studentName) AGAINST('刘');

测试索引

-- 创建表
CREATE TABLE `app_user`(
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`name`    VARCHAR(50) DEFAULT '' COMMENT'用户昵称',
`email` VARCHAR(50) NOT NULL COMMENT '用户邮箱',
`phone` VARCHAR(20) DEFAULT '' COMMENT '手机号',
`gender` TINYINT(4) UNSIGNED DEFAULT '0' COMMENT '性别(0:男;1:女)',
`password` VARCHAR(100) NOT NULL COMMENT '密码',
`age` TINYINT(4) 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用户表'

-- 创建存储函数,插入100w数据
-- 写函数之前必写,当作标志
DELIMITER $$
CREATE FUNCTION mock_data()
RETURNS     INT
-- DETERMINISTIC -- 8.0版本必须写这一句
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),'123456@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;

-- 调用存储函数
SELECT mock_data();

-- 查询数据,用时0.7s
SELECT * FROM app_user WHERE `name` = '用户9999';

-- 创建一个索引
-- create index 索引名 on 表(字段)
CREATE INDEX id_app_user_name ON app_user(`name`);

-- 再次查询数据,用时0.02s
SELECT * FROM app_user WHERE `name` = '用户9999';

索引在小数据量时用处不大,但在大数据时,区别十分明显

索引原则

  • 索引不是越多越好
  • 不要对经常变动的数据加索引
  • 小数据量的表不要加索引
  • 索引一般加在常用来查询的字段上

索引的数据结构

Hash 类型的索引

Btree: innodb 的默认数据结构

MySQL索引背后的数据结构及算法原理

索引失效情况

最左匹配原则

主要针对联合索引(顺序 + 多个索引列)

不满足最左匹配原则,不满足的部分就会失效

  • 没按顺序

    • 没有最左前缀
    • 中间位置的前缀缺失
  • 范围查找

    • 除最左前缀使用bteween and外,其余前缀使用bteween and 都认定是范围查找
    • 使用 > < 等比较运算符的也属于范围查找

使用函数

-- length(name) 使用了函数,name列建立的索引就会失效
select create_time from user where length(name) = 3 

使用表达式

-- code-1 = 3 ,使用了表达式,code列建立的索引就会失效
select create_time from user where code-1 = 3 

Like %

%号在左边会失效

-- 失效
select create_time from user where name like '%派大星'

%号在右边不会失效

-- 失效
select create_time from user where name like '派大星%'

使用Or导致索引失效

索引结构

image-20240103214630949

WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。

explain select create_time from user where name ='派大星' or code = 1001 

image-20240103214021586

优化方式就是在Or的时候两边都加上索引。

in使用不当

in 在索引的key中,大于等于key数的30%的时候索引失效

image-20240103214220809

age建立单独索引后,叶子节点key的个数是10个

不失效的场景

-- in用了1个key,占1/10,不失效
select create_time from user where  age in (22)

失效的场景

-- in用了3个key,占3/10,失效
select create_time from user where  age in (21,22,23)

order By

select create_time from user order by age

8.权限管理和备份

权限管理

-- 创建用户
CREATE USER kuangshen IDENTIFIED BY '123456'

-- 用户重命名
rename user kuangshen to kuangshen2

-- 修改当前用户密码
-- 8.0版本之前
SET PASSWORD = PASSWORD('111111') 

-- 修改指定用户密码
-- 8.0版本之前
SET PASSWORD FOR kuangshen = PASSWORD('111111') 

-- 8.0版本修改语句的方法
ALTER USER 'kuangshen'@'%' IDENTIFIED BY'123456'

-- 用户授权 all privileges 库,表等全部权限,但是不能给别人授权
-- *.* 所有的库下的所有的表
grant all privileges on *.* to kuangshen2

-- 查看指定用户的权限
show grants for kuangshen2

-- 查看管理员的权限
show grants for root@localhost

-- 撤销权限 revoke 哪些权限 在哪个库撤销 给谁撤销
revoke all privileges on *.* from kuangshen2

-- 删除用户
drop user kuangshen

备份

为什么要备份

  • 保证重要的数据不丢失
  • 转移A---->B

MySQL数据库备份的方式

  • 直接拷贝data文件

  • 在SQLyog/navicat这种可视化工具中手动导出

  • 使用命令行导出 mysqldump 命令

    mysqldump -hlocalhost -uroot -p123456 school student >D:/a.sql
    

    image-20240101201031089

    image-20240101201036928

假如要备份数据库,防止数据丢失

把数据库给朋友,sql文件给别人即可

9.规范数据库设计

为什么需要设计

当数据库比较复杂的时候,我们就需要设计了

糟糕的数据库设计:

  • 数据冗余,浪费空间
  • 数据插入和删除都会麻烦,异常[屏蔽使用物理外键]
  • 程序的性能差

良好的数据库设计:

  • 节省内存空间
  • 保证数据的完整性
  • 方便我们开发系统

软件开发中关于数据库的设计:

  • 分析需求:分析业务和需要处理的数据库的需求
  • 概要设计:设计关系图E-R图

设计数据库的步骤:(个人博客):

  • 收集信息,分析需求
    • 用户表(用户登录注销,用户个人信息,写博客,创建分类)
    • 分类表(文章分类,谁创建的)
    • 文章表(文章的信息)
    • 有链表(友链信息)
    • 自定义表(系统信息,某个关键的字,或者一些主字段) key:value
  • 标识实体(把需求落地到)
    • 写博客:user -->blog
    • 创建分类:user -->category
    • 关注:user -->user
    • 友链:links
    • 评论:user-user-blog

三大范式

https://www.cnblogs.com/wsg25/p/9615100.html

为什么需要数据规范化?

信息重复

更新异常

插入异常

  • 无法正常显示异常

删除异常

  • 丢失有效的信息

第一范式(1NF)

原子性:要求数据库表的每一列都是不可分割的原子数据项

第二范式(2NF)

前提:满足第一范式

每张表只描述一件事

第三范式(3NF)

前提:满足第一范式,第二范式

规范性和性能的问题

关联查询的表不得超过三张表(阿里)

  • 考虑商业化的需求和目标,(成本,用户体验)数据库的性能更重要
  • 再规范性能的问题时,需要适当的考虑一下规范性
  • 故意给某些表增加一些冗余的字段.(从多表查询中变为单表查询)
  • 故意增加一些计算列(从大数据量降低为小数据量的查询: 索引)

10.JDBC(重点)

数据库驱动

驱动:声卡,显卡,数据库

在这里插入图片描述

我们的程序会通过数据库驱动,和数据库打交道

JDBC

SUN公司为了简化开发人员的(对数据库的统一)操作,提供了一个(Java操作数据库的)规范,俗称JDBC

这些规范的实现有具体的厂商去做

对于开发人员来说,我们只需要掌握jdbc接口的操作即可

在这里插入图片描述

两个包

  • java.sql
  • javax.sql

还需要导入数据库驱动包 mysql-connector-java-5.1.47.jar (这是5.7版本用的)

MySQL8.0用的是 mysql-connector-java-8.0.25.jar

第一个JDBC程序

创建测试数据库

CREATE DATABASE `jdbcStudy` CHARACTER SET utf8 COLLATE utf8_general_ci;

USE jdbcStudy;

CREATE TABLE users(
id INT PRIMARY KEY,
NAME VARCHAR(40),
PASSWORD VARCHAR(40),
email VARCHAR(60),
birthday DATE
);

INSERT INTO users(id,NAME,PASSWORD,email,birthday) VALUES
(1,'zhangsan','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');

创建项目

创建一个普通项目(用IDEA)

导入数据库驱动(创建一个lib目录,把jar包放进去,然后右键,add as library)

image-20240101211915330

编写测试代码

package com.jjh.lesson01;

import java.sql.*;

//我的第一个JDBC程序
public class JdbcFirstDemo {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        //1.加载驱动
        Class.forName("com.mysql.jdbc.Driver"); // 固定写法,加载驱动(5.7版本)
        // Class.forName("com.mysql.cj.jdbc.Driver"); // 固定写法,加载驱动(8.0版本)

        //2.用户信息和url
        //useUnicode=true&characterEncoding=utf8&useSSL=true
        //支持中文编码 &设定字符为utf8 &使用安全连接 jdbcstudy是数据库名
        String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=false";
        String username ="root";
        String password ="123456";

        //3.连接成功,数据库对象  Connection 代表数据库
        Connection connection = DriverManager.getConnection(url,username,password);

        //4.执行SQL的对象
        Statement statement= connection.createStatement();

        //5.执行SQL的对象去执行SQL,可能存在结果,查看返回结果
        String sql ="SELECT * FROM USERS";

        //返回的结果集,结果集中封装了我们全部的查询出来的结果
        ResultSet resultSet = statement.executeQuery(sql);

        while (resultSet.next()){
            System.out.println("====================================================");
            System.out.println("id="+resultSet.getObject("id"));
            System.out.println("name="+resultSet.getObject("NAME"));
            System.out.println("pwd="+resultSet.getObject("PASSWORD"));
            System.out.println("email="+resultSet.getObject("email"));
            System.out.println("birth="+resultSet.getObject("birthday"));
        }

        //6.释放连接
        resultSet.close();
        statement.close();
        connection.close();
    }
}

jdbc对象解释

DriverManager

//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");//固定写法

URL

String url ="jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&&useSSL=false";

//mysql 默认3306
//协议://主机地址:端口号/数据库名?参数1&参数2&参数3

//Oracle   1521
//jdbc:oralce:thin:@localhost:1521:sid

Connection

Connection connection= DriverManager.getConnection(url,name,password);

//connection代表数据库
//数据库设置自动提交
//事务提交
//事务回滚
connection.rollback();
connection.commit();
connection.setAutoCommit();
//数据库能干的connection都能干

Statement

Statement 执行SQL的对象

PrepareStatement 执行SQL的对象

String sql="SELECT * FROM users";//编写Sql

statement.executeQuery();
statement.execute();
//更新,插入,删除,返回一个受影响的行数
statement.executeUpdate();

ResultSet

ResultSet查询的结果集:封装了所有的查询结果

//返回的结果集,结果集中封装了我们全部查询的结果
ResultSet resultSet = statement.executeQuery(sql);

//在不知道列类型下使用
resultSet.getObject();

//如果知道则指定使用
resultSet.getString();
resultSet.getInt();

遍历,指针

//移动到下一个
resultSet.next();
//移动到最后
resultSet.afterLast();
//移动到最前面
resultSet.beforeFirst();
//移动到前一行
resultSet.previous();
//移动到指定行
resultSet.absolute(row);

释放内存

//6. 释放连接
resultSet.close();
statement.close();
//很耗资源,用完关掉!
connection.close();

Statement对象

jdbc中的Statement对象用于向数据库发送sql语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查语句即可。

statement.executeUpdate方法

  • 用于向数据库发送增、删、改的sql语句
  • executeUpdate执行完后,将返回一个整数(即增删改语句导致了数据库几行发生了变化)

statement.executeQuery方法

  • 用于向数据库发生查询语句
  • executeQuery方法返回代表查询结果的resultSet对象

CRUD操作

CRUD操作-create

使用executeUpdate(String sql)方法完成数据添加操作,示例操作

Statement st =conn.createStatement();
String sql ="insert into user(...)values(...)";
int num =st.executeUpdate(sql);
if(num>0){
    System.out.println("插入成功!!");
}

CRUD操作-delete

使用executeUpdate(String sql)方法完成数据删除操作,示例操作:

Statement st=conn.createStatement();
String sql ="delete from user where id=1";
int num =st.executeUpdate(sql);
if(num>0){
    System.out.println("删除成功!!")
}

CRUD操作-update

Statement st=conn.createStatement();
String sql ="update user set name='' where name=''";
int num =st.executeUpdate(sql);
if(num>0){
    System.out.println("修改成功!!")
}

CRUD操作-select

Statement st=conn.createStatement();
String sql ="select*from user where id=1";
ResultSet rs=st.executeQuery(sql);
while(rs.next()){
    //根据获取列的数据类型,分别调用rs的相应方法映射到Java对象中
}

代码实现

image-20240101221225978

创建一个db.properties文件在src目录下,等下引用更方便

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy useUnicode=true&characterEncoding=utf8&useSSL=false
username=root
password=123456

提取工具类

JdbcUtils

package com.jjh.lesson02.utils;

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

public class JdbcUtils {
    private static String driver = null;
    private static String url = null;
    private static String username = null;
    private static String password = null;

    static{
        try {
             InputStream in = JdbcUtils
                    .class
                    .getClassLoader()
                    .getResourceAsStream("db.properties");

            driver = properties.getProperty("driver");
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");

            Class.forName(driver);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //获取连接
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, username, password);
    }
    //释放连接
    public static void release(Connection coon , Statement st, ResultSet rs){
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if(st!=null){
            try {
                st.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if(coon!=null){
            try {
                coon.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

TestInsert

public class TestInsert {
    public static void main(String[] args) throws SQLException {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();
            st = conn.createStatement();
            String sql =
                    "insert into users(id,NAME,PASSWORD,email,birthday) " +
                            "values(4,'xy','4654656','2568798778@qq.com','2020-01-01')";
            int i = st.executeUpdate(sql);
            if(i>0){
                System.out.println("插入成功");
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JdbcUtils.release(conn,st,rs);
        }
    }
}

SQL注入问题

sql存在漏洞,会被攻击导致数据泄露,SQL会被拼接

//登录业务
//当用户输入的用户名和密码为下述情况时,会返回数据库中所有数据,导致数据泄露
Login("'or'1=1","'or'1=1")

SQL注入

public class SQL注入 {
    public static void main(String[] args) throws SQLException {
        login("1' or '1=1","1' or '1=1");
    }

    public static void login(String username,String password){
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();
            st = conn.createStatement();
            String sql = "select * from users where `NAME`='"+username+"'and 		  			    PASSWORD='"+password+"'";
            ResultSet resultSet = st.executeQuery(sql);

            while (resultSet.next()){
                System.out.println("================================");
                System.out.println("id="+resultSet.getObject("id"));
                System.out.println("name="+resultSet.getObject("NAME"));
                System.out.println("pwd="+resultSet.getObject("PASSWORD"));
                System.out.println("email="+resultSet.getObject("email"));
                System.out.println("birth="+resultSet.getObject("birthday"));
            }

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JdbcUtils.release(conn,st,rs);
        }
    }
}

image-20240101225155714

PreparedStatement对象

PreparedStatement可以防止SQL注入,效率更高

防注入的本质:把传递进来的参数当做字符,假设其中存在转义字符,就直接忽略,比如说 ‘ 会被直接转义

SQL防注入

public class SQL防注入 {
    public static void main(String[] args) throws SQLException {
        login("1' or '1=1","1' or '1=1");
    }

    public static void login(String username,String password){
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();
            
            // 真正执行的sql:
            // select * from users where `NAME`= '1 or 1=1' and PASSWORD= '1 or 1=1'
            String sql = " select * from users where `NAME`= ? and PASSWORD=? ";
            st = conn.prepareStatement(sql);
            st.setString(1,username);
            st.setString(2,password);
            
            ResultSet resultSet = st.executeQuery(sql);

            while (resultSet.next()){
                System.out.println("=========================");
                System.out.println("id="+resultSet.getObject("id"));
                System.out.println("name="+resultSet.getObject("NAME"));
                System.out.println("pwd="+resultSet.getObject("PASSWORD"));
                System.out.println("email="+resultSet.getObject("email"));
                System.out.println("birth="+resultSet.getObject("birthday"));
            }

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JdbcUtils.release(conn,st,rs);
        }
    }
}

事务

要么都成功,要么都失败

ACID原则

原子性

一致性

隔离性

持久性

隔离性的问题

脏读:读取另一个事务未提交的数据

不可重复读:在同一个事务内,重复读取表中的数据,表数据发生了改变

幻读:在一个事务内,读取到了别人插入的数据,导致前后读出来结果不一致

代码实现

1、关闭事务的自动提交:conn.setAutoCommit(flase);

2、一组业务执行完毕,提交事务

3、可以在catch语句中显示的定义回滚语句,但默认失败就会回滚

正常情况

public class TestTransaction1 {
    public static void main(String[] args) {
        Connection con = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            con = JDBCUtils.getConnection();
            //关闭自动提交 默认会开启事务自动提交
            con.setAutoCommit(false); // 开启一个事务
            
            // A 转 B 100元
            String sql1 = "update account set money=money-100 where name='A'";
            ps = con.prepareStatement(sql1);
            ps.executeUpdate();
            String sql2 = "update account set money=money+100 where name='B'";
            ps = con.prepareStatement(sql2);
            ps.executeUpdate();
            
            //业务完毕,提交事务
            con.commit();

            System.out.println("A 转 B 100元 成功!");
        } catch (SQLException e) {
            e.printStackTrace();
            try {
                // 事务回滚
                con.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            JDBCUtils.release(con, ps, rs);
        }
    }
}

异常情况

public class TestTransaction2 {
    public static void main(String[] args) {
        Connection con = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            con = JDBCUtils.getConnection();
            
            //关闭自动提交 自动会开启事务
            con.setAutoCommit(false);//开启事务
            
            // A 转 B 100元
            String sql1 = "update account set money=money-100 where name='A'";
            ps = con.prepareStatement(sql1);
            ps.executeUpdate();

            //默认失败
            int x = 1/0; //一定会异常

            String sql2 = "update account set money=money+100 where name='B'";
            ps = con.prepareStatement(sql2);
            ps.executeUpdate();
            
            //业务完毕,提交事务
            con.commit();

            System.out.println("A 转 B 100元 成功!");
        } catch (SQLException e) {
            e.printStackTrace();
            //如果异常,默认也会回滚,下面不写也可以
            try {
                // 回滚事务
                con.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            JDBCUtils.release(con, ps, rs);
        }
    }
}

数据连接池

数据库连接---执行完毕---释放

连接 -- 释放 十分浪费资源

池化技术:准备一些预先的资源,过来就连接预先准备好的

  • 最小连接数(一般就是常用连接数)
  • 最大连接数(业务承载能力上限)
  • 等待超时(超出业务承载能力上限后,排队等待的最长时间)

编写连接池,实现一个接口 DateSource

public interface DataSource  extends CommonDataSource, Wrapper {

  Connection getConnection() throws SQLException;
  
  Connection getConnection(String username, String password)
    throws SQLException;
}

开源数据源实现(拿来即用)

DBCP

C3P0

Druid:阿里巴巴

使用了这些数据库连接池之后,我们在项目开发中就不需要编写连接数据库的代码了

DBCP

需要用到的jar包

commons-dbcp-1.4

commons-pool-1.6

配置文件 dbcp.properties

#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=false
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_COMMITTED

工具类 JDBCDBCPUtils

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class JDBCDBCPUtils {
    private static DataSource dataSource = null;

    static {
        try {
            InputStream in = JDBCDBCPUtils
            .class
            .getClassLoader()
            .getResourceAsStream("dbcp.properties");
            
            Properties properties = new Properties();
            properties.load(in);
            //创建数据源 工厂模式
            dataSource = BasicDataSourceFactory.createDataSource(properties);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取连接
     */
    public static Connection getConnection() throws SQLException {
        //从数据源中获取连接
        return dataSource.getConnection();
    }

    /**
     * 释放资源
     */
    public static void release(Connection con, Statement st, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (st != null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (con != null) {
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

测试类 TestDBCP

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class TestDBCP {
    public static void main(String[] args) {
        Connection con = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        try {
            con = JDBCDBCPUtils.getConnection();
            //使用?占位符代替参数
            String sql = "INSERT INTO users(`id`,`name`,`password`,`email`,`birthday`) 	   			 VALUES (?,?,?,?,?)";
            //预编译SQL,先写SQL,然后不执行
            st = con.prepareStatement(sql);
            //手动给参数赋值
            st.setInt(1, 5);
            st.setString(2, "钱七");
            st.setString(3, "123456");
            st.setString(4, "qianqi@sina");
            st.setDate(5, new java.sql.Date(new java.util.Date().getTime()));
            int num = st.executeUpdate();
            if (num > 0) {
                System.out.println("插入成功!");
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCDBCPUtils.release(con, st, rs);
        }
    }
}

C3P0

需要用到的jar包

c3p0-0.9.5.5.jar

mchange-commons-java-0.2.19.jar

配置文件 c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <!--
    c3p0的缺省(默认)配置
    如果在代码中ComboPooledDataSource ds=new ComboPooledDataSource();这样写就表示使用的是c3p0的	  缺省(默认)
    -->
    <default-config>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?	     					 	useUnicode=true&amp;
            characterEncoding=utf8&amp;
            useSSL=false&amp;
            serverTimezone=UTC
        </property>
        <property name="user">root</property>
        <property name="password">123456</property>
        <property name="acquiredIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </default-config>


    <!--
    c3p0的命名配置
    如果在代码中ComboPooledDataSource ds=new ComboPooledDataSource("MySQL");这样写就表示使用的	  	  是name是MySQL
    -->
    <name-config name="MySQL">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?	  					     	useUnicode=true&amp;
            characterEncoding=utf8&amp;
            useSSL=false&amp;
            serverTimezone=UTC
        </property>
        <property name="user">root</property>
        <property name="password">123456</property>
        <property name="acquiredIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </name-config>
</c3p0-config>

工具类JDBCC3P0Utils

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

public class JDBCC3P0Utils {
    private static DataSource dataSource = null;
    //private static ComboPooledDataSource dataSource = null;


    static {
        try {
            // 代码的方式配置
            // dataSource = new ComboPooledDataSource();
            // dataSource.setDriverClass();
            // dataSource.setJdbcUrl();
            // dataSource.setUser();
            // dataSource.setPassword();
            // dataSource.setMaxPoolSize();
            // dataSource.setMinPoolSize();
            // 配置文件写法
            dataSource = new ComboPooledDataSource("MySQL");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取连接
     */
    public static Connection getConnection() throws SQLException {
        //从数据源中获取连接
        return dataSource.getConnection();
    }

    /**
     * 释放资源
     */
    public static void release(Connection con, Statement st, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (st != null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (con != null) {
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

测试类 TestC3P0

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class TestC3P0 {
    public static void main(String[] args) {
        Connection con = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        try {
            con = JDBCC3P0Utils.getConnection();
            //使用?占位符代替参数
            String sql = "INSERT INTO users(`id`,`name`,`password`,`email`,`birthday`) 		        VALUES (?,?,?,?,?)";
            //预编译SQL,先写SQL,然后不执行
            st = con.prepareStatement(sql);
            //手动给参数赋值
            st.setInt(1, 6);
            st.setString(2, "刘八");
            st.setString(3, "123456");
            st.setString(4, "liuba@sina");
            st.setDate(5, new java.sql.Date(new java.util.Date().getTime()));
            int num = st.executeUpdate();
            if (num > 0) {
                System.out.println("插入成功!");
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCC3P0Utils.release(con, st, rs);
        }

    }
}

总结

无论用什么数据源,本质还是一样的,DataSource接口不会变,方法就不会变