Mysql学习

1、初识Mysql

前端(页面,展示,数据)
后台 (连接点 链接数据库,连接前端(控制,控制视图跳转和,给前端传递数据))
数据库 (存数据 txt、word、Excel)

1.1、为什么学Mysql

  1. 岗位需求
  2. 现在的大世界,大数据时代,打的就是信息战,数据站
  3. 被迫需求:存数据
  4. 数据库是所有软件体系中,最核心的存在

1.2、什么是数据库

数据库 (DB DateBase)
概念:数据仓库,是一个软件,安装在操作系统(Windows、Linux、mac....)之上的,核心是操作sql,存储的数据在500万以下,以上需要做索引优化
作用:存储数据,管理数据

1.3、数据库分类

关系型数据库: (SQL)

  • Mysql、oracle、sql server、DB2、sqlLite
  • 通过表和表、行和列之间的关系进行数据的存储

非关系型数据库: (No Sql) Not Only Sql

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

DBMS(数据库管理系统)

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

1.4、MySQL简介

MySQL是一个关系型数据库管理系统
前世:瑞典MySQL AB 公司开发
今生:属于 Oracle 旗下产品
MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。
开源的数据库软件
体积小、速度快、总体拥有成本低,招人的成本也低,所有人都得会
中小型网站、或者大型网站
官网:https://www.mysql.com/
官网下载:https://dev.mysql.com/downloads/mysql/
安装建议:

  1. 不要安装exe,会注册到注册表,不好卸载
  2. 尽可能使用压缩安装包

1.5、使用IDEA创建数据库

  1. 连接数据源 MySQL
  2. 创建数据源格式
  3. 新建Schemas

  4. 新建一张表 student
  5. 字段 id,name,age
  6. 查看表
  7. 添加数据

1.6、连接数据库

命令行连接:

mysql -u root -proot  --连接数据库
flush privileges  --刷新权限
--所有的语句都必须使用 ; 结尾
show databases; --查看所有的数据库

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

show tables; --查看数据库中所有的表
describe student; --显示表中的所有数据

create database westos; --创建数据库

exit; --退出连接
-- 单行注释 (SQL的本来注释)
/*
多行注释
*/

数据库 xxx 语言
DDL 定义
DML 操作
DQL 查询
DCL 控制

2、操作数据库

操作数据库 > 操作表 > 操作字段
MySQL数据库不区分大小写

2.1、

  1. 创建数据库
create database [if not exists] westos --if not exists判断是否存在,不存在则创建,否则报一个异常,但不报错
  1. 使用数据库
--tab键的上面,如果表名或者字段名是特殊字符,就使用``括起来
use `school`;
  1. 删除数据库
drop database [if exists] hello; --if exists判断是否存在,存在则删除,否则报一个异常,但不报错
  1. 查看所有的数据库
show databases ;

2.2、数据库的列类型

  1. 数值
    • tinyint 十分小的数据 1个字节
    • smallint 较小的数据 2个字节
    • mediumint 中等大小的字节 3个字节
    • int 标准的整数 4个字节 常用
    • bigint 较大的数据 8个字节
    • float 单精度浮点型 4个字节
    • double 双精度浮点型 8个字节
    • decimal 字符串形式的浮点数,防止精度缺失,一般用于金融运算
  2. 字符串
    • char 字符串固定大小,0~255
    • varchar 可变字符串,0~65535 常用
    • tinytext 微型文本, 2^8-1
    • text 文本串,2^16-1 保存大文本
  3. 时间日期
    • date YYYY-MM-DD 日期格式
    • time HH:mm:ss 时间格式
    • datetime YYYY-MM-DD HH:mm:ss 最常用的时间格式
    • timestamp 时间戳, 1970/1/1 到现在的毫秒数
    • year 年份表示
  4. null
    • 未知,没有值
    • 不要用NULL进行运算,结果也是NULL,没有意义

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

Unsigned

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

zerofill

  • 0填充
  • 不足的位数,使用0来填充, int(3) 5 --> 005

自增

  • 自动在上一条记录的基础上 + 1(默认)
  • 通常用来设置主键,必须是整数
  • 可以自定义设置自增的起始值和步长

非空 null, not null

  • 假设设置为not null,插入数据的时候必须要填写该字段,否则报错
  • null,如果不填写值,默认为null

默认值

  • 设置默认的值
  • 如果不指定该列的值,就会有默认的值

IDEA中对应的位置:

2.4、创建数据库表

-- 注意点,使用英文(),表的名称和字段尽量使用``括起来
-- if not exists 如果不存在则创建
-- auto_increment 自增
-- 字符串使用''括起来
-- comment 字段的含义
-- 所有的语句最后加 英文逗号,最后一句不用加
-- primary key一般写在最后面,为表级约束,也可以放在行内,为行级约束
-- engine 引擎
-- default charset默认编码
create table if not exists `student`(
    `id` int(4) not null auto_increment comment '学号',
    `name` varchar(20) 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(50) default '西安' comment '家庭住址',
    `email` varchar(15) default null comment '邮箱',
    primary key (id)
)engine=innoDB default charset=utf8;

格式:

create table [if not exists] `表名`(
    `字段` 列类型 [属性] [索引] [注释],
    `字段` 列类型 [属性] [索引] [注释],
    .......
    `字段` 列类型 [属性] [索引] [注释]
)[表类型] [字符集设置] [注释];

常用命令:

show create database school; -- 查看创建数据库的语句
show create table student; -- 查看创建student表的定义语句
desc student; -- 显示表的结构

2.5、数据库表的类型

-- 关于数据库引擎
/*
    INNODB 默认使用的
    MYISAM 早些年使用的
*/
INNODB MYISAM
事务支持 支持 不支持
数据行锁定 支持行锁 不支持行锁,但支持表锁
外键约束 支持 不支持
全文索引 不支持 支持
表空间的大小 较大,约为MYISAM的2倍 较小

常规使用操作:

  • MYISAM 节约空间,速度较快
  • INNODB 安全性高,支持事务的处理,支持多表多用户操作

物理空间存放的位置:

  • 所有的数据库都存放在 data目录下
  • 本质还是文件的存储

MySQL引擎在物理文件上的区别:

  • InnoDB在数据库表中只有一个*.frm文件,以及上级目录下的ibdata1文件
  • MYISAM 对应文件
    • *.frm 存储表结构的定义文件
    • *.MYD 数据文件
    • *.MYI 索引文件

设置数据库表的字符集编码:

charset=utf8

不设置会使用mysql的默认字符集编码,不支持中文

2.6、删除以及修改表

修改表:

-- 修改表名 alter table 旧表名 rename as 新表名;
alter table teacher rename as teacher1;

-- 添加字段 alter table 表名 add 字段名 字段类型;
alter table teacher1 add age int(3);

-- 修改表的字段 (重命名,修改约束)
-- alter table 表名 modify 字段名 字段类型; 
alter table teacher1 modify age varchar(20); -- 修改约束
-- alter table 表名 change 旧列名 新列名 字段类型; 
alter table teacher1 change age age1 int(3); -- 重命名

-- 删除表的字段 alter table 表名 drop 列名;
alter table teacher1 drop age1;

删除表:

-- 删除表 (判断一下,如果表存在则删除)
drop table if exists teacher1;

所有的删除及创建都建议判断是否存在,防止报错
注意点:

  • 表名、字段名尽量使用 `` 括起来
  • 注释 -- /**/
  • sql关键字大小写不敏感,建议小写
  • 所有的符号全部用英文

3、MySQL数据管理

3.1、外键(了解)

create table if not exists `student`(
    `id` int(4) not null auto_increment comment '学号',
    `name` varchar(20) not null default '匿名' comment '姓名',
    `pwd` varchar(20) not null default '123456' comment '密码',
    `sex` varchar(2) not null default '男' comment '性别',
    `gradeId` int(10) not null comment '年级id' ,
    `address` varchar(50) default '西安' comment '家庭住址',
    `email` varchar(15) default null comment '邮箱',
    primary key (id)
)engine=innoDB default charset=utf8;
drop table `grade`;
create table `grade`(
    `gradeId` int(10) not null auto_increment comment '年级id',
    `gradeName` varchar(30)not null comment '年纪名称',
    primary key(`gradeId`)
)ENGINE=INNODB default charset=utf8;

-- 创建表的时候灭有添加外键
-- constraint 约束名
-- foreign key外键
-- references 引用的哪张表的主键
ALTER table student
add constraint `FK_gradeId` foreign key (`gradeId`) references `grade`(`gradeId`);

-- ALTER table 表名 add constraint 约束名 foreign key (作为外键的列) references 哪张表(哪个列)

以上外键为物理级别的外键,就是数据库级别的外键,不建议使用,避免数据库过多,删除起来麻烦
最佳用法

  • 数据表就是单纯的表,只有行和列
  • 使用多张表的话,使用外键可由程序去实现

3.2、DML语言(全部记住)

数据库意义:数据存储,数据管理
DML语言:数据操作语言

  • Insert
  • Updata
  • Delete

3.3、添加

insert

-- 插入语句(添加)
-- insert into 表名([字段1,字段2,字段3...]) values ('值1','值2','值3'....);
insert into `grade`(`gradeName`) values ('大四');
-- 由于主键自增,可以省略不写,但是如果不写表的字段,就会一一匹配
insert into `grade` values ('大三'); -- Column count doesn't match value count at row 1

-- 一般写插入语句,必须使字段和数据一一对应
-- 一次性插入多个字段
insert into `grade`(`gradeName`)
 values ('大二'),
        ('大一');

insert into `student`(`name`,`pwd`,`sex`) values ('张三','aaa','男');

insert into `student`(`name`,`pwd`,`sex`)
 values ('张三','aaa','男'),('李四','bbb','男'),('王五','ccc','女');
-- 重点是 一 一对应

语法:nsert into 表名([字段1,字段2,字段3...]) values ('值1','值2','值3'....);
注意事项:

  • 字段和字段之间使用 英文逗号 隔开
  • 字段可以省略,但是后面的值必须要一一对应,不能少
  • 可以同时插入多个值,values后面的值用逗号隔开 values(),(),.....

3.4、修改

updata

-- 修改学员姓名,带了条件
update student set name = '爱因斯坦' where id=1;
-- 不指定条件的情况下,会改变所有的表
update `student` set name = '特斯拉';

-- 操作多个字段,使用逗号隔开
update `student` set name = '小行星',email = '15268595@qq.com' where id =  1;

-- 通过多个条件定位数据
update student
set name = '小破孩'
where name = '特斯拉' and sex = '女';              

语法:updata 表名 set 字段名 = 新值[,字段名 = 新值,....] where 条件
注意事项:

  • 字段名使数据库的列,尽量带上``
  • 条件,是为了筛选、定位某些需要修改的记录,如果没有,默认修改所有的列
  • 新值,可以是一个值,也可以是变量

3.5、删除

语法: delete from 表名 where 条件

-- 删除数据,避免这样写,会删除这个表内的所有数据
delete from student;

-- 删除数据,删除指定的数据
delete from student where id = 1;

truncate 命令
作用:完全清空一张表,表的结构和索引不会改变

-- 清空student表
truncate `student`

delete 和 truncate 的区别

  • 相同点:都会删除表里面的所有数据,不会改变表的结构
  • 相同点
    • truncate 会重新设置自增列,计数器会归零
    • truncate不会影响事务
-- 测试 delete 和 truncate 的区别
create table `test`(
    `id` int not null auto_increment,
    `coll` varchar(20) not null ,
    primary key (`id`)
);

insert into `test`(`coll`) values ('1'),('2'),('3');

delete from `test`; -- 不会影响自增

truncate `test`; -- 自增会归零

delete删除问题

  • 如果数据库引擎使用 InnoDB,数据库重启后会从1开始(存在于内存当中,断电即失)
  • 如果数据库引擎使用 MyISAM,数据库重启后,从上一个自增量开始(存在于文件当中)

4、DQL查询数据(最重点)

4.1、DQL

DQL:数据查询语言

  • 所有的查询操作都用它select
  • 简单的、复杂的查询都可以使用
  • 数据库中最核心的语言
  • 使用最频繁的语句

sql语句,创建四张表:

create database if not exists `school`;
-- 创建一个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);

4.2、指定查询数据

-- 查询所有学生信息   select 字段 from 表名;
select * from student;

-- 查询指定字段
select studentno,studentname from student;

-- 别名,给查询到的结果起名字,使用 as 关键字
select studentno as 学号,studentname as 学生姓名 from student;

-- concat 拼接字符串,属于函数 concat(a,b) --> ab
select concat('姓名:',studentname) as sname from student;

语法: select 字段,... from 表名;
去重:distinct
作用:去除select查询出来的重复的数据,重复的数据只显示一条

-- 查询有哪些学生参加了考试,就是有成绩的学生
select * from result; -- 查询全部的考试成绩
select studentno from result; -- 查询参加了考试的学生学号
select distinct studentno from result; -- 由于重复项太多了,使用distinct关键词去重

数据库的表达式(列)

select version(); -- 查询版本(函数)
select 100*3-1 as 计算结果; -- 计算结果(表达式)
select @@auto_increment_increment; -- 查询自增的步长 (变量)

-- 给查询出来的成绩 + 1 分
select studentno,studentresult + 1 as studentresult from result;

数据库中的表达式:文本值,列,null,函数,计算表达式,系统变量
select 表达式 from 表名;

4.3、where条件语句

作用:检索数据中符合条件的值
搜索的条件由一个或者多个表达式组成,返回结果为布尔值
模糊查询:like in null not null 关键字

-- ===========================  模糊查询  ================================
-- 查询姓赵的同学
-- like 结合 %(代表0~任意个字符)   _(代表1个字符)
select studentname,studentno from student where studentname like '赵%';

-- 查询姓赵的同学,后面只有一个字的
select studentname,studentno from student where studentname like '赵_';

-- 查询名字里面含有 伟 字的同学  %伟%  伟字不知道在何处出现,所以使用%
select studentname,studentno from student where studentname like '%伟%';

-- 使用in关键字,in(具体的一个或者多个值)
-- 查询学号为 1001,1002,1003号学生
select studentname,studentno from student where studentno in(1001,1002,1003);

-- 使用 null 或 not null 关键字
-- 查询地址为空的学生,可能地址字段为null 或者 ''空字符串
select studentname,studentno from student where address = '' or address is null;

-- 查询有出生日期的学生  就是出生生日不为空
select studentname,studentno from student where borndate is not null;

4.4、联表查询

-- ================== 联表查询 join ===============================
-- 查询参加了考试的学生 (学号,姓名,学科编号,成绩)
select * from student;
select * from result;

/* 思路:
 1.分析需求,确定需要查询哪些数据,在哪几张表里面
 2.确定使用哪种连接查询
 3. 确定交叉点,找这些表里面的公共字段
 4.判断条件:学生表 studentno = 成绩表 studentno
 */
 select s.studentno,studentname,subjectno,studentresult
from student s inner join result r
where s.studentno = r.studentno;

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

-- left join
select s.studentno,studentname,subjectno,studentresult
from student s left join result r
on s.studentno = r.studentno;
操作 描述
inner join 两个表中有一个字段名相同,类型也必须相同,则返回结果
right join 会将右表的所有记录返回,即使左表没有匹配的记录,未匹配的记录赋值为null
left join 会将左表的所有记录返回,即使右表没有匹配的记录,未匹配的记录赋值为null
-- ================== 联表查询 join ===============================
-- 查询参加了考试的学生 (学号,姓名,学科编号,成绩)
select * from student;
select * from result;

/* 思路:
 1.分析需求,确定需要查询哪些数据,在哪几张表里面
 2.确定使用哪种连接查询
 3. 确定交叉点,找这些表里面的公共字段
 4.判断条件:学生表 studentno = 成绩表 studentno
 */

-- join(连接的表) on(判断的条件)   连接查询
-- where     等值查询
 select s.studentno,studentname,subjectno,studentresult
from student s inner join result r
where s.studentno = r.studentno;

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

-- left join
select s.studentno,studentname,subjectno,studentresult
from student s left 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;

-- 查询参加考试的同学信息(学号,学生姓名,科目名,成绩)
/* 思路:
 1.分析需求,确定需要查询哪些数据,在哪几张表里面  student、result、subject
 2.确定使用哪种连接查询 左连接
 3. 确定交叉点,找这些表里面的公共字段
 4.判断条件:右联表查出来的结果里面有subjectNo,使用查询出来subjectNo和subject表再联表查询,就会得到课程名
 */
select s.studentno,studentname,subjectname,studentresult
from student s right join result r
on s.studentno = r.studentno
    -- 右联表查出来的结果里面有subjectNo,使用查询出来subjectNo和subject表再联表查询,就会得到课程名
inner join subject sub
on r.subjectno = sub.subjectno

-- 格式:
-- 查询哪些数据 : select ...
-- 从哪些表中去查 : from 表名 inner/right/left join 连接的表 on 交叉条件
-- 假设是多张表查询,先查询两张表,再慢慢增加

-- 查询学生所属的年级(学号,姓名,年纪名称)
select studentname,studentno,gradename
from student s inner join grade g
on s.gradeid = g.gradeid;

-- 查询科目所属的年级
select subjectname,gradename
from subject s inner join grade g
on s.gradeid = g.gradeid;

-- 查询参加高等数学-1考试的同学信息(学号,学生姓名,科目名,成绩)
select s.studentno,studentname,subjectname,studentresult
from student s right join result r
on s.studentno = r.studentno
inner join subject sub
on r.subjectno = sub.subjectno
where sub.subjectname = '高等数学-1'

自链接:就是自己的表和自己的表连接,核心:一张表拆成两张一样的表
一张表里面含有包含关系的两个字段,pid包含categoryid ,才能使用自链接

-- 查询父子信息:将一张表拆分为两张表
select a.categoryname '父栏目',b.categoryname '子栏目'
from category a,category b
where a.categoryid = b.pid;

4.5、分页和排序

排序:

-- 排序 :升序 asc 降序 desc
-- order by 哪个字段排序 怎么排
-- 根据查询出来的结果,按成绩降序进行排列
select s.studentno,studentname,subjectname,studentresult
from student s right join result r
on s.studentno = r.studentno
inner join subject sub
on r.subjectno = sub.subjectno
order by studentresult desc;

分页:
为什么要有分页?
假设有100万条数据,分页可以缓解数据库压力,给人的体验更好

-- 分页,每页只显示五条数据
-- 语法: limit startIndex,pageSize
select s.studentno,studentname,subjectname,studentresult
from student s right join result r
                          on s.studentno = r.studentno
               inner join subject sub
                          on r.subjectno = sub.subjectno
order by studentresult desc
limit 0,5;

-- 第一页 limit 0~5    5*(1-1)
-- 第二页 limit 5~5    5*(2-1)
-- 第三页 limit 10~5   5*(3-1)
-- .....
-- 第N页 limit 10~5   pageSize*(N-1)
-- pageSize:页面大小
-- pageSize*(N-1) :起始下标
-- N :当前页
-- 总页数 = 数据总数/当前页

分页和排序应用

-- 查询 高等数学-1 课程成绩排名前十的,并且分数大于80的学生信息 (姓名,学号,课程名,成绩)
select studentname,s.studentno,subjectname,studentresult
from student s inner join result r
on s.studentno = r.studentno
inner join subject sub
on r.subjectno = sub.subjectno
where studentresult > 80 and subjectname = '高等数学-1'
order by studentresult desc
limit 0,10;

4.6、子查询

where(这个值是经过计算得出来的)
本质:相当于在where 下面在嵌套一个查询

-- =============== where ===================
-- 1.查询高等数学-4的所有考试结果(学号,考试编号,成绩),成绩降序排列
-- 方式一:联表查询
select studentno,s.subjectno,studentresult
from result r inner join subject s
    on r.subjectno = s.subjectno
where subjectname = '高等数学-4'
order by studentresult desc;
-- 方式二:子查询(由里及外)
select studentno,subjectno,studentresult
from result r
where r.subjectno in
      (select subjectno from subject where subjectname = '高等数学-4')-- 子查询语句就是为了确定 高等数学-4 的课程号
order by studentresult desc;

-- 查询分数不小于80分的学生学号和姓名
select s.studentno,studentname
from student s inner join result r
on s.studentno = r.studentno
where studentresult >= 80;

-- 在这个基础上在添加一个科目 高等数学-1 ,但是student表和result表没有 科目名称,只有科目编号,所以还得知道 高等数学-1 的编号
select s.studentno,studentname,studentresult
from student s inner join result r
on s.studentno = r.studentno
where studentresult >= 80 and subjectno = (select subjectno from subject where subjectname = '高等数学-1');

-- 如果使用联表查询
select s.studentno,studentname,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;

-- 再改造
select studentno,studentname
from student where studentno in (
    select studentno from result where studentresult >= 80 and subjectno = (
        select subjectno from subject where subjectname = '高等数学-1'
        )
    );

4.7、分组和过滤

-- 查询不同课程的平均分,最高分,最低分
-- 核心:不同的课程
select subjectname,avg(studentresult) as 平均分,max(studentresult) as 最高分,
min(studentresult) as 最低分 from result inner join
subject s on result.subjectno = s.subjectno
group by result.subjectno -- 通过什么来分组,在这一组里面再去计算平均值,最高分,最低分
having avg(studentresult) >= 80;  -- 分组完成后,每个组还需要满足的次要条件  avg(studentresult) 也可以使用别名

4.8、select语法小结

select 去重 要查询的字段 from 表名 (字段和表名都可以取别名)
xxx join 要连接的表 on 等值判断
where(具体的值或子查询语句)
Group by (通过哪个字段分组)
having (过滤分组后的信息,条件和where一样,只是位置不同)
order by 通过那个字段排序[升序/降序]
limit startIndex,PageSize

5、MySQL函数

5.1、常用函数

-- =========== 常用函数 ==============
-- 数学运算
select abs(-5);   -- 绝对值  5
select ceiling(9.2);  -- 向上取整  10
select floor(9.9);  -- 向下取整  9
select rand();  -- 返回0~1之间的随机数
select sign();  -- 返回参数的符号  0返回0,负数返回-1 正数返回1

-- 字符串函数
select char_length('这个年纪你能睡得着?');  -- 返回字符串的长度  10
select concat('你','管','我'); -- 拼接字符串
-- 从某个位置开始替换多少长度,
-- insert(原字符串,插入的起始下标(从一开始),插入字符串的长度,需要插入的字符串)。
select insert('hello,world',1,2,'ss'); -- ssllo,world
select lower('Computer'); -- 大写转小写
select upper('Computer'); -- 小写转大写
select instr('hello','h'); -- 返回子串第一次出现的索引,如果没有找到返回 0  结果为 1
select replace('坚持就是胜利','坚持','努力'); -- 替换出现的指定字符串  努力就是胜利
select substr('坚持就是胜利',3,2); -- 返回指定的字符串(原字符串,开始截取位置,截取长度) 结果为 就是
select reverse('123456'); -- 反转字符串  654321
-- 查询姓 张的同学,将张替换成 葬
select replace(studentname,'张','葬') from student where studentname like '张%';
-- 时间和日期函数(重要)
select current_date(); -- 获取当前日期 2021-12-09
select curdate();  -- 获取当前日期 2021-12-09
select now(); -- 获取当前的时间 2021-12-09 13:14:02
select localtime; -- 获取本地时间 2021-12-09 13:14:58
select SYSDATE(); -- 获取系统时间 2021-12-09 13:16:11

select year(now()); -- 年
select month(now()); -- 月
select day(now()); -- 日
select hour(now()); -- 时
select minute(now()); -- 分
select second(now()); -- 秒

-- 系统
select system_user(); -- 当前系统用户 root@localhost
select user(); -- 当前系统用户 root@localhost
select version(); -- 当前版本号  8.0.25

5.2、聚集函数(常用)

函数 描述
count() 计数
sum() 求和
avg() 平均值
max() 最大值
min() 最小值
..... ....
-- =============== 聚合函数 ==================
-- 这三个count()都能够统计个数,查询一张表中有多少个记录
select count(studentname) from student; -- count(字段) 会忽略所有的null值
select count(*) from student; -- count(*) 不会忽略 null 值 本质:计算行数
select count(1) from student; -- count(1)不会忽略 null 值  本质:计算行数
-- 没有主键的话,count(1)查询效率最快,有主键,字段查询效率更快

select sum(studentresult) as 总和 from result;
select avg(studentresult) as 平均分 from result;
select max(studentresult) as 最高分 from result;
select min(studentresult) as 最低分 from result;

5.3、数据库级别的MD5加密(扩展)

什么是MD5?
主要增强算法复杂度和不可逆
具体的值的 MD5 是一样的
ND5网站破解原理,背后有一个字典,输入加密后的值获得加密前的值,如果过于复杂则查找不到

-- ============== 测试MD5加密=================
create table testMd5(
    id int not null auto_increment comment '用户id',
    name varchar(30) not null comment '用户名',
    pwd varchar(50) not null comment '密码',
    primary key (id)
)engine = innoDB default charset = utf8;
alter table testmd5 modify pwd varchar(100);
-- 明文密码
insert into testmd5 values (1,'张三','123456'),(2,'李四','123456'),(3,'王五','123456');
-- 加密
update testmd5 set pwd = md5(pwd) where id = 1;

update testmd5 set pwd = md5(pwd); -- 加密全部的密码

-- 在插入的时候就加密
insert into testmd5 values(4,'小明',md5('123456'));
-- 如何校验:将用户传进来的值,进行md5加密,然后比对加密后的值
select * from testmd5 where name = '小明' and pwd = md5('123456');

6、事务

要么都成功,要么都失败

将一组 SQL 放在一个批次中去执行

事务原则:ACID原则,原子性,一致性,隔离性,持久性

原子性:事务要么都完成,要么都失败
一致性:事务完成后和完成前的状态必须一致
隔离性:多个用户同时操作,主要排除其他事务对本次事物的影响
持久性:在事务提交前出现问题,必须将数据恢复原状,在事务提交后出现问题,数据必须已经持久化到了数据库,也就是说事务一旦提交就不可逆了

隔离性存在的一些问题:

  1. 脏读:一个事务读取了另一个事务未提交的问题
  2. 不可重复读:事务在多次读取数据的时候出现了数据前后不一致的问题
  3. 幻读:一个事务内读取到了其他事务插入的数据

处理事务的流程

-- ============事务===============
-- mysql中默认开启自动提交提交
set autocommit = 0; -- 关闭自动提交事务
set autocommit = 1; -- 开启自动提交事务(是默认的)
-- 手动处理事务
-- 1.关闭自动提交
set autocommit = 0;
-- 2.开启事务
start transaction; -- 标记事务的开始,从这里开始的sql语句,都在同一个事务内


-- 3.1提交: 持久化
commit;
-- 3.2回滚: 回到原来的样子
rollback;
-- 事务结束
-- 4.开启自动提交
set autocommit = 1;

模拟转账场景

-- 模拟转账
create database shop character set utf8 collate utf8_general_ci;
use shop;

create table account(
  id int not null auto_increment,
  name varchar(50) not null ,
  money decimal(8,2) not null ,
  primary key (id)
)engine = innoDB default charset utf8;

insert into account(name,money)values ('A',2000.00),('B',10000.00);

-- 模拟转账
set autocommit = 0; -- 关闭自动提交

start transaction; -- 开启事务(一组事务)

update account set money = money - 500 where name = 'A'; -- A减去500
update account set money = money + 500 where name = 'B'; -- B加上500

commit; -- 提交事务,数据被持久化
rollback; -- 回滚事务

set autocommit = 1; -- 恢复自动提交

7、索引

索引(Index)是帮助MySQL高效获取数据的数据结构

7.1、索引的分类

  • 主键索引 (primary key)
    • 唯一的标识,主键不可重复
  • 唯一索引 (unique key)
    • 避免该列含有重复的数据出现,而多个列都可以标识为 唯一索引
  • 常规索引 (key/index)
    • 可以用index或者key关键字来设置
  • 全文索引 (FullText)
    • 快速的定位数据
-- 索引的使用
-- 1.在创建表的时候给字段添加索引
-- 2.使用alter关键字给表添加索引

-- 显示所有的索引信息
show index from student;

-- 给student表添加全文索引  alter table 表名 add 索引类型 索引名(需要添加索引的列名);
alter table student add fulltext index studentName(studentname);

-- explain 分析sql执行的状况
explain select * from student; -- 非全文索引

explain select * from student where match(studentname) against('伟');

7.2、测试索引创建一张表,含有100万条数据

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 NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                            PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT = 'app用户表';

-- 插入100万条数据
delimiter $$ -- 写函数之前的标志,必须写
create function mock_data()
    returns int
    DETERMINISTIC
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),'1515151@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();

执行查询语句,查看执行时间以及查找的行数

select * from app_user where name = '用户9999'; -- 1 s 827 ms

explain select * from app_user where name = '用户9999'; -- rows=992778

添加索引之后,再执行上面的操作

-- 索引命名规范 id_表名_字段名
-- create index 索引名 on 表名(字段名);
create index id_app_user_name on app_user(name);
select * from app_user where name = '用户9999'; -- 16 ms

explain select * from app_user where name = '用户9999'; -- rows=1

结论:

  1. 创建完索引之后,每一个用户名都有一个唯一定位的index,查询用户名,相当于查询的就是index,能够唯一定位
  2. 索引只有在数据量非常大的时候才能体现出来,区别十分明显

7.3、索引原则

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

InnoDB默认的数据结构是 BTree(B树)

mysql索引背后的数据结构及算法原理 参考博客:https://www.cnblogs.com/pandang/p/7359532.html

8、权限管理和备份

8.1、用户管理

SQL命令:用户操作在MySQL.user表中
本质就是对这张表进行增删改查

-- 创建一个用户 create user 用户名 identified by '密码';
create user person identified by '123456';

-- 修改密码(修改当前用户的密码)
set password = password ('111111');

-- 修改密码(修改指定用户的密码) set password for 用户名 = password ('密码');
set password for person = password ('111111');

-- 重命名  rename user 旧用户名 to 新用户名;
rename table testmd5 to test2; -- 给表重命名
rename user person to person2; -- 给用户重命名

-- 用户授权 all privileges所有的权限 *.* 所有的库.所有的表
-- 除了给别人授权的权限
grant all privileges on *.* to person;

-- 查看用户权限
show grants for person2; -- 查看指定用户的权限
show grants for root@localhost; -- 查看root用户的权限
-- ROOT用户的权限:GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION

-- 撤销权限 revoke 撤销的权限,在哪个库.哪张表撤销,撤销的哪个用户的
revoke all privileges on *.* from person2;

-- 删除用户
drop user person2;

8.2、数据库备份

为什么备份?

  • 保证重要的数据不丢失
  • 方便数据转移

MySQL数据库备份的方式

  • 拷贝物理文件,MySQL数据库都在data文件下面
  • 使用可视化工具手动导出
  • 使用命令行导出 命令是 mysqldump
#mysqldump -h 主机名 -u 用户名 -p 密码 数据库 表名 >物理磁盘位置/文件名.sql
C:\Users\lzc>mysqldump -hlocalhost -uroot -proot school student >d:/a.sql

#导出多张表 mysqldump -h 主机名 -u 用户名 -p 密码 数据库 表名1 表名2 ... >物理磁盘位置/文件名.sql
C:\Users\lzc>mysqldump -hlocalhost -uroot -proot school student result >d:/b.sql

#导出数据库 mysqldump -h 主机名 -u 用户名 -p 密码 数据库 >物理磁盘位置/文件名.sql
C:\Users\lzc>mysqldump -hlocalhost -uroot -proot school >d:/c.sql
  • 使用命令行导入数据库
# 登陆的情况下
# 切换到需要导入的那个数据库,如果导入的是数据库就不用切换
# source 备份文件
source d:/a.sql
# 未登录的情况下
# mysql -u用户名 -p密码 库名< 备份文件

9、规范数据库设计

9.1、为什么需要设计数据库

当数据库过于复杂的时候,就需要设计了
糟糕的数据库设计:

  • 数据冗余
  • 数据库插入和删除都是比较复杂的
  • 程序运行的性能差

良好的数据库设计:

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

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

  1. 需求分析:分析业务和需要处理数据的需求
  2. 概要设计:设计关系图,一般是 E-R图

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

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

9.2、数据库的三大范式

为什么需要数据规范化?

  • 避免数据重复
  • 避免插入异常
  • 避免更新异常
    • 无法正常显示信息
  • 避免删除异常
    • 丢失有效信息

三大范式:

  1. 第一范式(1NF)
    • 原子性:每一列的属性不可再分
  2. 第二范式(2NF)
    • 前提是满足第一范式
    • 主键能够唯一的确定一条记录,这条记录必须依赖于所有的主键信息,主键里面的一部分无法确定该条信息
  3. 第三范式(3NF)
    • 前提是满足第一、第二范式
    • 数据表中的每一条数据都必须与主键相关,而不能与其他的非主键相关,也就是其他非主键不能够去确定任意一个字段

规范性和性能的问题
两者不可兼得
关联查询的表不得超过三张表

  • 考虑商业化的需求和目标,基于成本以及用户体验,数据库的性能更加重要
  • 在考虑性能的问题时,适当的考虑一下规范性
  • 有时候会故意增加一些冗余的字段,将其从多表查询变为单表查询
  • 故意增加一些计算列

10、JDBC

10.1、数据库驱动

为了能够使得应用程序能够连接到数据库,需要数据库驱动作为中间件进行连接,应用程序通过数据库驱动和数据库打交道

10.2、JDBC

SUN公司为了简化开发人员对数据库的统一操作,提供了一套Java操作数据库的规范:称为JDBC
这些规范由具体的厂商去做,我们只需要掌握JDBC的接口操作即可
需要导入的数据库驱动依赖:

  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.27</version>
  </dependency>

10.3、第一个JDBC程序

  1. 创建一个maven项目
  2. 导入数据库依赖
  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.27</version>
  </dependency>
  1. 连接创建数据库
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')
  1. 编写测试代码
package com.lzc.lesson01;

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

//第一个JDBC程序
public class JdbcFirstDemo {
    public static void main(String[] args) throws Exception {
        //1.加载驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.用户信息,和url
        //useUnicode=true  支持中文编码
        // useSSL=true      保证安全连接
        // characterEncoding=utf8 字符编码格式为utf8
        String url = "jdbc:mysql://localhost:3306/jdbcstudy?
                     useUnicode=true&useSSL=true&characterEncoding=utf8";
        String username = "root";
        String password = "root";
        //3.开始连接,连接成功,返回数据库对象 connection 就代表数据库
        Connection connection = DriverManager.getConnection(url, username, password);
        //4.获取 执行sql的对象 statement
        Statement statement = connection.createStatement();
        //5.用得到的对象去 执行sql 可能存在结果,查看返回结果
        String sql = "select * from jdbcstudy.users";
        //5.1 resultSet 返回的结果集,结果集里卖弄封装了所有查出来的对象
        ResultSet resultSet = statement.executeQuery(sql);
        //5.2 遍历获得到的结果集,resultSet.getObject(1)查询第几列的数据
        while (resultSet.next()){
            System.out.println("id:" + resultSet.getObject(1));
            System.out.println("name:" + resultSet.getObject(2));
            System.out.println("password:" + resultSet.getObject(3));
            System.out.println("email:" + resultSet.getObject(4));
            System.out.println("birthday:" + resultSet.getObject(5));
        }
        //6.释放连接
        resultSet.close();
        statement.close();
        connection.close();
    }
}

步骤总结:

  1. 加载驱动
  2. 连接数据库 DriverManager
  3. 获取执行sql的对象
  4. 获得返回的结果集
  5. 关闭连接

10.4、statement对象

jdbc中的statement对象用于向数据库发送sql,想完成数据库的crud,只需要通过这个对象对数据库发送crud语句即可
statement.executeUpdate(sql)方法,用于向数据库发送增删改语句,返回一个整数,即增删改语句导致的数据库几行数据发生了改变
statement.executeQuery(sql)方法,用于向数据库发送查询语句,返回一个ResultSet对象,结果集
代码实现:

  1. 提取工具类
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 {
            //获取db.properties资源流
            InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
            //加载 properties 文件
            Properties properties = new Properties();
            properties.load(in);
            //获取里面的数据
            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 connection, Statement statement, ResultSet resultSet){
        if (resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
  1. 编写增删改的方法,都是使用executeUpdate(sql)方法

增加数据:

public class TestInsert {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        int rows = 0;
        try {
            connection = JdbcUtils.getConnection(); //获得数据库连接
            statement = connection.createStatement(); // 获得执行sql的对象
            String sql = "insert into jdbcstudy.users values(4,'zhaoliu','123212','add@qq.com','1995-02-26')";
            rows = statement.executeUpdate(sql);
            if (rows>0){
                System.out.println("插入成功");
            }else {
                System.out.println("失败");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(connection,statement,null);
        }
    }
}

删除数据:

public class TestDelete {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        try {
            connection =  JdbcUtils.getConnection();
            statement = connection.createStatement();
            String sql = "delete from jdbcstudy.users where id = 4";
            int i = statement.executeUpdate(sql);
            if (i>0){
                System.out.println("删除成功");
            }else {
                System.out.println("失败");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(connection,statement,null);
        }
    }
}

更新数据

public class TestUpdate {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        try {
            connection = JdbcUtils.getConnection();
            statement = connection.createStatement();
            String sql = "update jdbcstudy.users set name = '赵六' where id = 4";
            int i = statement.executeUpdate(sql);
            if (i > 0){
                System.out.println("更新成功");
            }else {
                System.out.println("失败");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(connection,statement,null);
        }
    }
}

核心就是sql语句的不同,其他内容一摸一样
3. 查询数据,使用executeQuery(sql)方法

public class TestQuery {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            connection = JdbcUtils.getConnection();
            statement = connection.createStatement();
            String sql =  "select * from jdbcstudy.users";
            //获取结果集
            resultSet = statement.executeQuery(sql);
            //遍历打印结果集
            //resultSet.next() 如果还有下一个数据则为真
            while (resultSet.next()){
                System.out.println("id:" + resultSet.getObject(1));;
                System.out.println("name:" + resultSet.getObject(2));;
                System.out.println("password:" + resultSet.getObject(3));;
                System.out.println("email:" + resultSet.getObject(4));;
                System.out.println("birthday:" + resultSet.getObject(5));;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(connection,statement,resultSet);
        }
    }
}

sql注入的问题:就是sql存在漏洞,会被攻击,导致数据泄露
本质就是在原来的sql基础上再拼接一句sql语句(or 语句),导致查询出来的结果与预期结果不符合

//注意:此处命名不规范
public class SQL注入 {
    public static void main(String[] args) {
        //正常登录
        // login("zhangsan","123456");
        //拼接sql语句登录,不需要正确的用户名以及密码,就能查询出来所有的数据
        login(" ' or '1=1","' or '1=1");
    }

    public static void login(String username,String password){
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            connection = JdbcUtils.getConnection();
            statement = connection.createStatement();
            String sql =  "select * from jdbcstudy.users where NAME = '"+username+"' and PASSWORD = '" + password +"'";
            //获取结果集
            resultSet = statement.executeQuery(sql);
            //遍历打印结果集
            //resultSet.next() 如果还有下一个数据则为真
            while (resultSet.next()){
                System.out.println("name:" + resultSet.getObject("name"));;
                System.out.println("password:" + resultSet.getObject("password"));;
             }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(connection,statement,resultSet);
        }
    }
}

10.5、PreparedStatement对象

PreparedStatement可以防止sql注入,效率更好

  1. 增加
public class TestInsert {
    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement pstm = null;
        ResultSet resultSet = null;
        try {
            connection = JdbcUtils.getConnection();
            //编写sql语句,使用 ? 占位符,一个?代表一位参数
            String sql = "insert into jdbcstudy.users values(?,?,?,?,?)";
            //预编译sql,但是不执行
            pstm = connection.prepareStatement(sql);
            //现在给 pstm对象里面的 ?赋值
            pstm.setInt(1,5); //给第一个 ? 赋值为5
            pstm.setString(2,"天启");//给第二个 ? 赋值为天启
            pstm.setString(3,"155115");
            pstm.setString(4,"151115@qq.com");
            //  java.sql.Date 是数据库层面的时间
            //  java.util.Date() 是java的时间
            //需要将java得到的时间戳转换为数据库的时间
            pstm.setDate(5,new java.sql.Date(new java.util.Date().getTime()));
            //执行sql ,这里不需要参数
            int i = pstm.executeUpdate();
            if (i > 0){
                System.out.println("插入成功");
            }else {
                System.out.println("失败");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(connection,pstm,resultSet);
        }
    }
}
  1. 删除
public class TestDelete {
    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement pstm = null;
        try {
            connection =  JdbcUtils.getConnection();
            String sql = "delete from jdbcstudy.users where id = ?";
            //预编译sql
            pstm = connection.prepareStatement(sql);
            //设置参数
            pstm.setInt(1,5);
            //执行
            int i = pstm.executeUpdate();
            if (i>0){
                System.out.println("删除成功");
            }else {
                System.out.println("失败");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(connection,pstm,null);
        }
    }
}
  1. 修改
public class TestUpdate {
    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement pstm = null;
        try {
            connection =  JdbcUtils.getConnection();
            String sql = "update jdbcstudy.users set NAME = ? where id = ?";
            //预编译sql
            pstm = connection.prepareStatement(sql);
            //设置参数
            pstm.setString(1,"爱因斯坦");
            pstm.setInt(2,4);
            //执行
            int i = pstm.executeUpdate();
            if (i>0){
                System.out.println("更新成功");
            }else {
                System.out.println("失败");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(connection,pstm,null);
        }
    }
}
  1. 查询
public class TestSelcte {
    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        try {
            connection = JdbcUtils.getConnection();
            //预编译sql
            String sql = "select * from jdbcstudy.users where id = ?";
            pstm  = connection.prepareStatement(sql);
            //设置参数
            pstm.setInt(1,4);
            //执行
            rs = pstm.executeQuery();
            //处理结果
            if (rs.next()){
                System.out.println(rs.getString("name"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(connection,pstm,rs);
        }
    }
}
  1. 防止sql注入
//注意:此处命名不规范
public class SQL注入 {
    public static void main(String[] args) {
        //正常登录
        //login("zhangsan","123456");
        //拼接sql语句登录,不需要正确的用户名以及密码
        login("\'\' or \'1=1\'","123456");
    }

    public static void login(String username,String password){
        Connection connection = null;
        PreparedStatement pstm = null;
        ResultSet resultSet = null;
        try {
            connection = JdbcUtils.getConnection();
            //prepareStatement能够防止sql注入的本质就是,会将传递进来的参数当成字符串,不会当作关键字,所以 or 无法使用
            //假设其中存在转义字符,比如 ' 会被直接转义
            String sql =  "select * from jdbcstudy.users where NAME = ? and PASSWORD = ?";
            pstm = connection.prepareStatement(sql);
            //设置参数
            pstm.setString(1,username);
            pstm.setString(2,password);
            //获取结果集
            resultSet = pstm.executeQuery();
            //遍历打印结果集
            //resultSet.next() 如果还有下一个数据则为真
            while (resultSet.next()){
                System.out.println("name:" + resultSet.getObject("name"));;
                System.out.println("password:" + resultSet.getObject("password"));;
             }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(connection,pstm,resultSet);
        }
    }
}

10.6、事务

事物的四大特性:ACID原则

  1. 原子性:要么都成功,要么都失败
  2. 一致性:事务提交前和提交后的状态不能发生变化
  3. 隔离性:一个事务在运行的时候必须隔离其他事务,防止数据不一致
  4. 持久性:事务一旦提交成功,就会持久化到数据库

隔离性的问题:

  1. 脏读:事务读取到了其他事务未提交的数据
  2. 不可重复读:事务在重复多次读取数据的时候,前一次读取到的数据和这一次的数据不一致
  3. 幻读:在一个事务内,读取到了别人插入的数据,导致前后结果不一致

不可重复读和幻读很相似,不过不可重复读读取的是更新的数据,幻读读取的是插入的数据
代码实现:

  1. 开启事务 connection.setAutoCommit(false);
  2. 一组业务执行完毕,提交事务
  3. 可以在cache语句中显示的定义回滚,如果不定义,默认就会回滚
public class TestTransaction {
    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        try {
            connection = JdbcUtils.getConnection();
            //关闭自动提交,关闭之后会自动开启事务
            connection.setAutoCommit(false);

            //执行事务
            String sql1 = "update jdbcstudy.account set money = money -100 where name = 'A'";
            pstm = connection.prepareStatement(sql1);
            pstm.executeUpdate();

            //手动制造错误,事务会回滚,上一条sql语句不会被持久化到数据库
            int i = 1/0;

            String sql2 = "update jdbcstudy.account set money = money + 100 where name = 'B'";
            pstm = connection.prepareStatement(sql2);
            pstm.executeUpdate();

            //提交数据
            connection.commit();
            System.out.println("成功");
        } catch (SQLException e) {
            try {
                connection.rollback();// 如果提交失败,则回滚事务
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        }finally {
            JdbcUtils.release(connection,pstm,rs);
        }
    }
}

10.9、数据库连接池

从数据库连接 --> 执行完毕 --> 释放资源 频繁的这种过程中是十分浪费资源的,所以有了池化技术
池化技术:准备一些预先的资源,连接的时候就连接这些预先准备好的
编写连接池,只需要实现DataSource接口
常用的开源数据源实现:DBCP,C3P0,Druid(阿里巴巴的)

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

DBCP:

  1. 需要导入的依赖
<dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.4</version>
  </dependency>
 <dependency>
      <groupId>commons-pool</groupId>
      <artifactId>commons-pool</artifactId>
      <version>1.6</version>
  </dependency>
  1. DBCP工具类
public class JdbcUtils_dbcp {
    public static DataSource dataSource = null;
    static {
        try {
            //获取db.properties资源流
            InputStream in = JdbcUtils_dbcp.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
            //加载 properties 文件
            Properties properties = new Properties();
            properties.load(in);
            //创建数据源,工厂模式.创建
            dataSource = BasicDataSourceFactory.createDataSource(properties);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    //获取连接
    public static Connection getConnection() throws SQLException {
            //从数据源中获取连接
           return dataSource.getConnection();
    }
    //释放连接资源
    public static void release(Connection connection, Statement statement, ResultSet resultSet){
        if (resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
  1. dbcp的使用
public class TestDBCPInsert {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        int rows = 0;
        try {
            connection = JdbcUtils_dbcp.getConnection(); //获得数据库连接
            statement = connection.createStatement(); // 获得执行sql的对象
            String sql = "insert into jdbcstudy.users values(4,'zhaoliu','123212','add@qq.com','1995-02-26')";
            rows = statement.executeUpdate(sql);
            if (rows>0){
                System.out.println("插入成功");
            }else {
                System.out.println("失败");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils_dbcp.release(connection,statement,null);
        }
    }
}

c3p0:

  1. 需要导入的依赖
<dependency>
  <groupId>com.mchange</groupId>
  <artifactId>c3p0</artifactId>
  <version>0.9.5.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mchange/mchange-commons-java -->
<dependency>
  <groupId>com.mchange</groupId>
  <artifactId>mchange-commons-java</artifactId>
  <version>0.2.19</version>
</dependency>
  1. c3p0工具类
public class JdbcUtils_c3p0 {
    public static ComboPooledDataSource dataSource = null;
    static {
        try {
            //代码写法,往数据源里面手动设置数据,不推荐
            /*dataSource = new ComboPooledDataSource();
            dataSource.setDriverClass();
            dataSource.setUser();
            dataSource.setPassword();
            dataSource.setJdbcUrl();

            dataSource.setMaxPoolSize();
            dataSource.setMinPoolSize();
            dataSource.setMaxIdleTime();*/

            //配置文件写法,直接去读取xml文件即可
            dataSource = new ComboPooledDataSource();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    //获取连接
    public static Connection getConnection() throws SQLException {
           return dataSource.getConnection();
    }
    //释放连接资源
    public static void release(Connection connection, Statement statement, ResultSet resultSet){
        if (resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
  1. c3p0的使用
public class TestInsert {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        int rows = 0;
        try {
            connection = JdbcUtils_c3p0.getConnection(); //获得数据库连接
            statement = connection.createStatement(); // 获得执行sql的对象
            String sql = "insert into jdbcstudy.users values(5,'zhaoliu','123212','add@qq.com','1995-02-26')";
            rows = statement.executeUpdate(sql);
            if (rows>0){
                System.out.println("插入成功");
            }else {
                System.out.println("失败");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils_c3p0.release(connection,statement,null);
        }
    }
}
posted @ 2021-12-14 19:18  小破孩儿~  阅读(127)  评论(0)    收藏  举报