初识MySQL:
文章目录
数据库分类
关系型数据:
通过表和表之间,行和列之间的关系进行数据的存储,学院信息表,考勤表,…
- MySQL
- Oracle
- sqlserver
- DB2
- sqlite
非关系型数据库
非关系型数据库,对象存储,通过对象的自身的属性来决定
- Redis
- mongDB
DBMS(数据库关系系统)
DDL(数据库定义语言):
操作数据库(了解)
操作数据库–> 数据库的表–> 数据库表中的数据
mysql 不区分大小写,但是约定俗成用小写
创建数据库
-- 创建数据库:create database [if not exists] 数据库名;
CREATE database yf03203;
删除数据库
-- 删除数据库:drop database [if exists] 数据库名;
drop database yf03203;
修改数据库
-- 修改数据库:alter database 数据库名 default character set 旧编码方式 collate 新编码方式
查询数据库
-- 查看创建数据库的语句
show create databases 数据库名
-- 查看所有的数据库
show databases
-- 使用数据库
use 表名;
数据类型:
数据库的字段属性(重点)
无符号(unsigned):声明该列不能为负数
零填充:不足的位数补零 5:0005
自增:
- 自动在上一条记录的基础上+1(默认)
- 通常用来设计唯一的主键 index ,
- 必须是整数类型
- 可以自定义设计主键自增的步长和起始值
非空(not null):不填值会报错
默认:设置默认值
长度:如果int型,设置了长度,只针对零填充时候零的长度,对实际没印象,比如(类型:int ,长度:1,数值:789)这个值还是789,如果设置了零填充的话就是(常规零填充是:000000456。 长度:5,值:456:00456),char 和varchar有影响。
拓展(阿里巴巴规范):
每个表 必须存在以下五个字段!,阿里巴巴规范, 学习阶段可以省略节约时间,做项目必须要
/*
id 主键
‘version' 乐观锁
is_delete 伪删除
gmt_create 创建时间
gmt_update 修改时间
*/
表的约束用于保证数据的完整性和准确性,分为
实体完整性约束
主键约束
域完整性约束
检查约束、默认值约束、唯一约束、非空约束
引用完整性约束
外键约束
自定义完整性约束
触发器
创建表(重点)
create table[if not exists] `表名`(
`字段名1` 列类型 [约束条件],
`字段名2` 列类型 [约束条件],
...
`字段名n` 列类型 [约束条件]
)
删除表:
-- 删除表:drop table [if exists] 表名;
drop table student;
修改表
-- 修改表名
alter table student rename as teacher;
-- 增加表的字段
alter teble teacher add age int;
-- 修改表的字段(重命名,修改约束)
-- alter table 表名 modify 字段名 列属性[]
alter table teacher modify age varchar(11) -- 修改约束
-- alter table 表名 change 旧字段名 新字段名 列属性[]
alter table teacher change age age1 int; -- 修改整个字段(约束+名字)
-- 删除表的字段 :alter table 表名 drop 字段名;
alter table teacher drop age1;
查看表
-- 查看student 数据表的定义语句
show create table 表名
-- 显示表的结构
desc student
DML(数据库操作语言)
CRUD:增加(Create)、检索(Retrieve)、更新(Update) 和 删除(Delete)
增(c:create)
添加数据:
-- 语法:
insert into student (字段名1,字段名2,字段3...)
values (值1,值2,值3),
(值1,值2,值3),
(值1,值2,值3);
-- 字段可以省略,但是后面的值必须要一一对应。
insert into student (id,`name`,age,sex,`describe`)
values
(8,'张三',18,'男',null),
(9,'李四',11,'女','我们班美女');
删除数据:
-- 删除数据语法:
delete from 表名 [where 条件]
-- 清空数据
truncate 表名
delete 和 truncate 区别?
相同:都能删除数据,都不能删除表结构
不同:
- truncate 重新设置 自增列计数器
- truncate 不会影响事务
修改数据:
-- 语法:
update 表名 set 字段1 = 值1,字段2 = 值2 [where 条件]
update student set `describe` = '凤凰传奇真传奇' where id = 8;
注意点:
- 没有条件的话,会修改所有的列,
- 值可以是个变量
DQL(数据库查询语言)最重点
简单查询:
-- 语法
select [distinct] 字段1,字段2 from 表名 [where 条件]
[group by 字段名 [having 条件]]
[order by 字段名 [asc|desc]]
[limit [offset]记录数]
distinct : 去重
场景:某个公司很多人,也很多部门,这时可以查看有哪些部门,就可以去重
where 子句
- 关系运算符
- in
- between and
- is null
- like
- and
- or
- 取别名(as)貌似,取别名后就不能使用原名字了
where 条件的运算符
运算符 | 含义 | 示例 |
---|---|---|
= | 等于 | 5=4:false |
<> 或 != | 不等于 | 5<>4 : true |
> | 大于 | |
< | 小于 | |
>= | 大于等于 | |
<= | 小于等于 | |
between…and… | 在… … 之间 | 【2,5】闭区间 |
and | 和 (同java的&&) | |
or | 或(同java的||) | |
in | 在什么之中 | in(值1,值2)匹配括号里的 和and 的作用一样 |
like | 模糊匹配 | %:0个或任意个字符 _ : 一个字符 |
is null / is not null | 是否为空 |
/*
where 子句
*/
#1)关系运算符的使用 = != <> < > <= >=
#查询不是吴好的学习的信息
select * from student1 where sname <> '吴好';
#查询年龄小于18岁的学生信息
select * from student1 where age <=18;
#2)in 指定了一些满足的值
#查询姓名是吴好或者崔平的学生信息
select * from student1 where sname in('吴好','崔平','蔡徐坤');
#查询姓名不是蔡徐坤的学生信息
select * from student1 where sname not in('蔡徐坤');
#3) between A and B 在A 和 B 的范围内 A必须小于等于B
#查询年龄在18-19岁的学生的信息
select * from student1 where age between 18 and 19;
#4)查询字段为空的信息
#查询部门字段为空的学生信息
select * from student1 where dept is null;
#查询部门字段不为空的学生信息
select * from student1 where dept is not null;
#5)distinct 去掉重复的记录
select * from student1;
select distinct sname from student1;
#6) like 模糊查询
/* 需要配合通配符使用 共有四种匹配方式
1.% 表示长度大于等于0的任意一个字符串
2._ 表示长度为1的任意单个字符
3.[]指定一个字符、字符串或范围,要求所匹配对象为它们中的任一个
4.[^]表示不在括号所列之内的单个字符
注意:如果要匹配'%' '_' 等特殊字符 需要使用[]
*/
#查询姓吴的学生
select * from student1 where sname like '吴%';
#查询姓吴并且名称中中至少有两个字的学生
select * from student1 where sname like '吴_%';
#查询不姓吴的学生
select * from student1 where sname not like '吴%';
#查询名称中带有_的人 使用escape 可以将指定字符后面的字符作为非转义字符
select * from student1 where sname like '%,%%' ESCAPE ',';
#7 A and B 两个条件都要满足才可以匹配到
#查询年龄大于18岁并且是女性的学生
select * from student1 where age>18 and sex='女';
#8 A or B 两者之间有一个满足就可以匹配到
#查询年龄小于20岁的学生或者音乐系的学生
select * from student1 where age<20 or dept ='音乐系';
#组合使用 and 和 or
#查询年龄小于20岁的学生或者音乐系的学生 并且 这些学生都是女性
select * from student1 where (age<20 or dept ='音乐系') and sex ='女';
#9 给表取别名
#给表取名字
select * from student1 s1 where s1.age>19;
select * from student1 as s1 where s1.age>20;
# 取别名 不是改名字
-- select * from s1 where s1.age>20;
#10给列取别名
select sname 姓名, age 年龄 from student1;
select sname as '姓名', age as '年龄' from student1;
字段表达式
select 表达式 from 表名
select version() -- 查看系统版本(函数)
select 100*7+4 -- 计算
select 100*7+4 as 计算 -- 计算
select @@auto_increment_increment -- 查看自增的步长(变量)
-- 年龄加100岁
select `name`,age = age+100 as age from student
联表查询(join on)
建表的数据: student_kuang.sql
-- 联表查询
-- 查询 学生ID,姓名,课程名称,课程成绩
/*
-- 分析:
1. 这些字段来自哪个表,若两个表都有同一字段则需明确是哪个表的字段
2. 确定使用哪种连接查询(左连接,右连接,inner join)
3. 确定交叉点(这两个表中的数据哪个是相同的
4. 判断的条件
*/
-- 交集
select student.studentno,studentname,subjectno,studentresult
from student
INNER JOIN result
ON student.studentno = result.studentno;
-- 左连接
select student.studentno,studentname,subjectno,studentresult
from student
LEFT JOIN result
ON student.studentno = result.studentno;
-- 查询缺考的同学
select student.studentno,studentname,subjectno,studentresult
from student
LEFT JOIN result
ON student.studentno = result.studentno
where studentresult is null;
-- 右连接
select student.studentno,studentname,subjectno,studentresult
from student
RIGHT JOIN result
ON student.studentno = result.studentno;
-- 多表
-- 学号,姓名是student表的, subjectname 是subject表的, studentresult 是result表的
-- 之间的关系,student 的 学号 和 studentresult 的学号是相同的
-- studentresult 的 课程编号 和 subject 的课程编号是一样的
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 和 on的区别 :join on:连接查询 where :等值查询
where 先联表再查询,
on 是 查询再联表
自连接
把一张表拆分两张表来对应
-- 练习:自连接 把一张表拆分两张表来对应
select c.categoryname,p.categoryname
from category as c,category as p
where c.categoryid = p.pid;
-- 自连接数据库建表:
CREATE TABLE `school`.`category`(
`categoryid` INT(3) NOT NULL COMMENT 'id',
`pid` INT(3) NOT NULL COMMENT '父id 没有父则为1',
`categoryname` VARCHAR(10) NOT NULL COMMENT '种类名字',
PRIMARY KEY (`categoryid`)
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;
INSERT INTO `school`.`category` VALUES
(2, 1, '信息技术'),
(3, 1, '软件开发'),
(5, 1, '美术设计'),
(4, 3, '数据库'),
(8, 2, '办公信息'),
(6, 3, 'web开发'),
(7, 5, 'ps技术');
分组和过滤
使用了聚合函数的话,必须使用分组
-- 练习:查询不同课程的平均分,最高分,最低分
-- 分组和过滤 GROUP BY 字段名 having 条件
select subjectname,AVG(studentresult) as 平均分,max(studentresult),min(studentresult)
from result as r
inner JOIN `subject` as sub
on r.subjectno = sub.subjectno
GROUP BY subjectname
having 平均分>60
多条件分组
-- 多条件分组
-- 练习: 统计各部门的男女各有多少人
-- 分析: 通过部门的名称和员工的性别分组 统计人数
use supersys;
select e_dept as 部门,e_sex as 性别,COUNT(1) as 人数 from employee
GROUP BY e_dept,e_sex
分页
-- limit :分页
-- 语法:limit [第几条数据开始] 记录数
select stu.studentno,studentname,subjectname,studentresult
from student as stu
inner JOIN result as res
on stu.studentno = res.studentno
INNER JOIN `subject` as sub
on sub.subjectno = res.subjectno
order by studentresult asc
LIMIT 0,3;
排序
-- order by:排序
-- 语法:order by 排序的字段 升序(asc)/降序(desc),排序的字段2 升序/降序
select stu.studentno,studentname,subjectname,studentresult
from student as stu
inner JOIN result as res
on stu.studentno = res.studentno
INNER JOIN `subject` as sub
on sub.subjectno = res.subjectno
order by studentresult asc;
-- 练习:查询java第二学年 课程成绩排名前十的学生,并且分数要及格的学生信息(学号,姓名,课程名称,分数)
select s.studentno,studentname,sub.subjectname,r.studentresult
from
student as s
inner join result as r
on s.studentno = r.studentno
inner join subject as sub
on r.subjectno = sub.subjectno
where s.gradeid =2 and subjectname like '%Java%' and studentresult>60
order by studentresult DESC
limit 10;
子查询
子查询比联表查询慢
-- 子查询
-- 练习:查询参加C语言-1 学生的学生信息(学号,名字,成绩)
/*
分析:
1.学生学号,姓名 在 student 表中, 成绩在 result 表中 科目名 在 subject 表中
2.共通点:学号 是 student 和 result表的相同字段 科目编号是 result 和 subject 表的相同字段
3.查学号,姓名,成绩 在哪个表中,条件是什么(相同点):学号
4.什么样的学号能返回?科目编号相同的,
5.什么样的科目编号能返回?科目名是‘C语言-1’的;
*/
use school;
select s.studentno,studentname,studentresult
from student as s,
result as r
where s.studentno in (
select r.studentno from result where r.subjectno =(
select sub.subjectno from `subject` as sub where subjectname = 'C语言-1'
)
);
-- 联表的方式
-- 联表
select s.studentno,studentname,studentresult
from student as s
INNER JOIN result as r
on s.studentno = r.studentno
INNER JOIN `subject` as sub
on r.subjectno = sub.subjectno
where sub.subjectname like '%C语言%'
order by studentresult DESC
limit 0 5;
select 小结:
select [distinct] *| 字段....
from 表 (多表用逗号隔开)
xxx join 表 on 条件 (可以多个联表)
where 条件 (还可以子查询)
group by 字段 having 条件
order by 字段 升序asc|降序desc
limit 第几条记录 记录数
- 顺序不能错,但是可以省略
MySQL函数:
常用函数(不常用)
-- 数学运算
select abs(-9); -- 绝对值
select ceiling(9.8) -- 向上取整
select floor(9.8) -- 向下取整
select RAND() -- 返回一个 0 -1 之间的随机数
select SIGN(10) -- 负数返回-1 0返回0 正数返回1
-- 字符串函数
select CHAR_LENGTH('我就是玩儿'); -- 字符串长度
select CONCAT('我','还','就是玩'); -- 拼接字符串
select LOWER('XXhh'); -- 全转小写
select upper('XXhh'); -- 转大写
select SUBSTR('键盘敲烂月薪过万', 5 ,2) -- 截取字符串
-- 练习:查询姓张的同学,换成章
select REPLACE(studentname,'张','章')
from student
where studentname like '张%';
-- 时间和日期
select CURRENT_DATE() -- 获取当前日期
select CURDATE() -- 获取当前日期
select NOW() -- 获取当前的时间(日期+时间)
select LOCALTIME() -- 获取当前的时间(日期+时间)
select SYSDATE() -- 获取系统的时间(日期+时间)
-- 年月日时分秒
select YEAR(NOW());
select month(NOW());
select DAY(NOW());
select hour(NOW());
select MINUTE(NOW());
select SECOND(NOW());
聚合函数(常用)
函数名称 | 描述 |
---|---|
count() | 计数 |
sum() | 求和 |
avg() | 求平均值 |
max() | 最大值 |
min() | 最小值 |
… | … |
-- 聚合函数
-- 计数 -- 三个结果都一样
use
select COUNT(studentname) from student; -- 会忽略null值
select COUNT(*) from student; -- 不会忽略null值
select count(1) from student; -- 不会忽略null值
-- 练习:查询不同课程的平均分,最高分,最低分
-- 分组和过滤 GROUP BY 字段名 having 条件
-- 聚合函数必须使用分组
select subjectname,AVG(studentresult) as 平均分,max(studentresult),min(studentresult)
from result as r
inner JOIN `subject` as sub
on r.subjectno = sub.subjectno
GROUP BY subjectname
having 平均分>60
注意:使用了group by后,select后出现的字段要么只能出现在group by子句中,要么只能出现在聚合函数中
扩展:数据库级别的MD5加密
什么是MD5?
增强算法复杂度,且不可逆
-- ======== 测试MD5 加密===========
-- 建表
create table testmd5 (
id int not NULL,
`name` varchar(20) not null,
pwd varchar(100) not null,
primary key (id)
)ENGINE = INNODB default charset =utf8
-- 添加数据
-- 明文密码
insert into testmd5 values
(1,'张三','12345'),
(2,'李四','12345'),
(3,'王五','12345');
-- 加密
update testmd5 set pwd=MD5(pwd)
-- 插入数据的时候加密
insert into testmd5 values
(4,'小明',md5('123456'))
-- 如何校验:将用户传递过来的密码,进行md5加密
select * from testmd5 where `name`='小明' and pwd = MD5('123456')
事务
什么是事务
要么都成功,要么都失败,
将一组SQL放在一个批次中执行
事务管理(ACID)是指数据库管理系统(DBMS)在写入或更新资料的过程中,为保证事务是正确可靠的,所必须具备的四个特性:
- 原子性(atomicity,或称不可分割性):要么都成功,要么都失败,
- ==一致性 consistency):==事务前后的数据完整性要保持一致,
- ==隔离性(Isolation),又称独立性):==事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
- (脏读 虚读)
- 持久性(Durability)::持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
参考博客链接:https://blog.youkuaiyun.com/dengjili/article/details/82468576
-- 事务
-- MySQL 是默认开启事务自动提交的
set autocommit = 0 -- 关闭
set autocommit = 1 -- 开启 (默认)
-- 手动处理事务
set autocommit = 0
-- 事务开启
start transaction -- 标记一个事务的开始,从这个之后的SQL都在同一个事务内
insert ...
-- 提交:持久化(成功)
commit
-- 回滚:回到原来的样子 (失败)
rollback
-- 事务结束
set autocommit = 1 -- 开启自动提交
--
set autocommit = 0;
start TRANSACTION;
use shop ;
update shop set money = money -500 where `name` = '张三';
update shop set money = money +500 where `name` = '李四';
commit;
rollback;
set autocommit = 1;
索引
MySQL 官方对索引的定义为:索引(index)是帮助MySQL高效获取数据的数据结构,提取橘子主干,就可以得到索引的本质,索引是数据结构。
索引分类
- 主键索引(primary key):唯一标识,主键不可以重复,
- 唯一索引(unique key):避免重复的列出现,唯一索引可以重复,多个列都可以标识
- 常规索引(index)
- 全文索引(FullText)
基础语法
-- 索引的使用
-- 1.在创建表的时候给字段添加所有
-- 2.创建完毕后,增加所有
-- 显示所有的所有信息
show index from student
-- 增加一个全文索引
alter table student add fulltext index `studentname_idx`(studentname);
-- explain 分析SQL执行的情况
explain select * from student; -- 非全文索引
explain select * from student where match(studentname) asainst('章');
测试索引
-- 建表
CREATE TABLE `app_user` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) ,
`email` VARCHAR(50) NOT NULL ,
`phone` VARCHAR(20) ,
`gender` TINYINT ,
`password` VARCHAR(100) NOT NULL ,
`age` TINYINT(4) ,
`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 1001;
WHILE i<num DO
INSERT INTO `app_user`(`name`,`email`,`phone`,`gender`)VALUES(CONCAT('用户',i),'19224305@qq.com','123456789',FLOOR(RAND()*2));
SET i=i+1;
END WHILE;
RETURN i;
END;
SELECT mock_data() -- 执行此函数 生成一百万条数据
-- =================================这里开始才是重点=================================================
-- 增加索引1 :命名方式:idx_表名_字段名
-- 为name 这个字段添加索引
-- 格式: create index 索引名 on 表名(字段名)
create index idx_app_user_name on app_name(`name`)
-- 没有索引的速度
select * from app_user where name = '用户858789'; -- 时间: 2.602s
explain select * from app_user where name = '用户858789'; -- 查了1990281行
-- 加了索引的速度 添加索引也很耗时间,所以小量数据的时候大可不必建索引,反而浪费时间
create index idx_app_user_name on app_user(`name`);
select * from app_user where name = '用户824389'; -- 第一次:时间:0.022s 第二,三,四,五次:时间: 0.001s 第二次或更多次时会比第一次快
explain select * from app_user where name = '用户824389'; -- 查了2行 (有两条数据
-- 增加索引2 全文索引
use school ;
alter table student add fulltext index `studentname_idx`(studentname);
explain select * from student where match(studentname) against('章');
索引原则
- 索引不是越多越好
- 不要多经常变动数据加索引
- 小数据的表不需要加索引
- 索引一般加载常用来查询的字段上!
课外知识:索引的数据结构
参考博客:http://blog.codinglabs.org/articles/theory-of-mysql-index.html
权限管理
数据库备份
为什么要备份:
- 保证重要的数据不丢失
- 数据转移
- 防止删库跑路
怎么备份:
-
复制物理文件
-
在Navicat可视化工具中手动导出
-
命令行,mysqldump
-- 导出 #mysqldump -h主机 -u 用户名 -p 密码 数据库 表名 >物理路径 -- 导入 source 备份文件路径 mysqldump -hlocalhost -uroot -p123456
规范数据库设计
为什么需要设计
当数据库比较复杂的时候,我们就需要设计了
糟糕的数据库设计:
- 数据冗余,浪费空间
- 数据库插入和删除都会麻烦,异常
- 程序性能差
良好的数据库设计
- 节省内存空间
- 包装数据库的完整性
- 方便开发
软件开发中,关于数据库的设计
- 分析需求:分析业务和需要处理的数据库的需求
- 概要设计:设计关系图E-R图
设计数据库案例
设计数据库的步骤(个人博客)
- 收集信息,分析需求
- 用户表(用户登入注销,用户的个人信息,写博客,创建分类
- 分类表(文章分类,谁创建的)
- 文章表(文章的信息)
- 友链表(友链信息)
- 标识实体(把需求落地到每个字段)
- 标识实体之间的关系
- user --> blog:写博客
- user -->category:分类
- user- -> user -->blog:评论
ant design pro elements 前端组件
三大范式
参考博客:https://www.cnblogs.com/wsg25/p/9615100.html
第一范式:
原子性:保证每一列不可再分
第二范式:
前提:满足第一范式
每张表只描述一件事情
第三范式:
前提:满足第一范式 和 第二范式
第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。
规范性 和 性能的问题
关联查询的表不得超过三张表
- 考虑商业化的需求和目标,(成本,用户体验!) 数据库的性能更加重要
- 在规范性能的问题的时候,需要适当的考虑一下 规范性!
- 故意给某些表增加一些冗余的字段。(从多表查询中变为单表查询)
- 故意增加一些计算列(从大数据量降低为小数据量的查询:
JDBC(重点)
sun公司为了简化开发人员的(对数据库的同一)操作,提供了一个(java操作数据库的)规范,俗称jdbc
java.sql
javax.sql
数据库驱动
需要导入一个数据库驱动包
参考博客:https://blog.youkuaiyun.com/weixin_43173093/article/details/103135955
mySQL Connector 8.0.11:https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.11
第一个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,'zhansan','123456','zs@sina.com','1980-12-04'),
(2,'lisi','123456','lisi@sina.com','1981-12-04'),
(3,'wangwu','123456','wangwu@sina.com','1979-12-04')
添加数据库驱动
右击项目—> 建一个目录文件: lib —> 将下载的驱动jar包拖过来,—> 右击jar包有一个 add as Library.—>ok
编写测试代码
package testJDBC;
import java.sql.*;
// 我的第一个JDBC 程序
public class TestJDBCDemo1 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1. 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.用户信息和 url serverTimezone=UTC
String url = "jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&&useSSL=false";
String username = "root";
String password = "adminxhb";
// 3.连接成功,返回数据库duixiang
Connection connection = DriverManager.getConnection(url,username,password);
//4.执行SQL 的对象 Statement 执行sql的对象
Statement statement = connection.createStatement();
//5.执行SQL的对象去执行sql
String sql = "select * from users";
ResultSet resultSet = statement.executeQuery(sql);
while(resultSet.next()){
System.out.println("id=" + resultSet.getObject("id"));
System.out.println("NAME=" + resultSet.getObject("NAME"));
System.out.println("PASSWORD=" + resultSet.getObject("PASSWORD"));
System.out.println("email=" + resultSet.getObject("email"));
System.out.println("birthday=" + resultSet.getObject("birthday"));
System.out.println("==========");
}
//6.释放连接
resultSet.close();
statement.close();
connection.close();
}
}
//1. 加载驱动 固定写法
Class.forName("com.mysql.cj.jdbc.Driver");//5.0:com.mysql.jdbc.Driver
URL
String url = "jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&&useSSL=false"; // 协议://主机地址:端口号/数据库名/参数1&参数2&参数3
DriverManager 驱动管理者
Connection connection = DriverManager.getConnection(url,username,password); // connection 代表数据库 // 数据库设置自动提交 connection.setAutoCommit(); // 事务提交 connection.commit(); // 事务回滚 connection.rollback();
Statemen 执行SQL的对象: 写SQL语句的t
statement.executeQuery(); // 查询操作 返回resultSet对象 statemnet.execute(); // 执行任何sql, statemnet.executeUpdate();// 更新,插入,删除,都是用这个,返回一个受影响的行数
ResultSet 查询的结果集:封装了所有的查询结构
获得指定的数据类型
resultSet.getObject(); // 不知道列类型的话可以用object接收 //知道就用指定类型接收 resultSet.getString(); resultSet.getInt(); .......
释放资源
//6.释放连接 resultSet.close(); statement.close(); connection.close();
statement 对象
Jdbc中的statement对象用于向数据库发送SQL语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查语句即可。
1、提取工具类(db.properties , JdbcUtils)
# db.properties 配置文件工具类
driver = com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&&useSSL=false
# jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&&useSSL=false;
username=root
password=adminxhb
// JdbcUtils 工具类
package 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 is = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(is);
driver=properties.getProperty("driver");
url=properties.getProperty("url");
username=properties.getProperty("username");
password=properties.getProperty("password");
// 1. 驱动只用加载一次
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 throwables) {
throwables.printStackTrace();
}
}
if (statement!=null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
statement 增删改查
增删改是用 statement.executeUpdate()
查询是用statement.executeQuery()
package testJDBC;
import utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TestInsert {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
//1 建立连接 返回数据库对象
connection = JdbcUtils.getConnection();
//2.获取操作数据库执行对象(表
statement = connection.createStatement();
//3.操作数据的sql语句
// 增加一条数据
String sqlInsert = "insert into users(id,name,PASSWORD,email,birthday)values(7,'张三','123456','1350099076@qq.com','2001-10-26');";
// 删除一条数据
String sqlDelete = "delete from users where id = 6";
// 修改一条数据
String sqlUpdate = "update users set name = '凤凰传奇',email='2830105247@qq.com' where name='张三'";
// 返回受影响的行数 增删改都是用executeUpdate 查询使用executeQuery
int i = statement.executeUpdate(sqlUpdate);
if(i>0){
// System.out.println("插入成功");
// System.out.println("删除成功");
System.out.println("修改成功");
}
// 查询
String sqlSelect = "select id,name from users where name='凤凰传奇'";
resultSet = statement.executeQuery(sqlSelect);
while (resultSet.next()){
System.out.println("id="+resultSet.getObject("id"));
System.out.println("name="+resultSet.getObject("name"));
}
resultSet.beforeFirst(); // 结构像链表 回到第一个
while (resultSet.next()){
System.out.println("id="+resultSet.getObject("id"));
System.out.println("name="+resultSet.getObject("name"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
// 释放资源
JdbcUtils.release(connection,statement,resultSet);
}
}
}
Statement SQL注入问题
SQL 存在漏洞,会被攻击导致数据泄露
直接看代码
package testJDBC;
import utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* SQL注入问题
*/
public class SQLInjection {
public static void main(String[] args) throws SQLException {
String username = "凤凰传奇";
String password = "123456";
// 正常写法
// login(username,password);
// 带有注入的写法 及时没有输入正确的用户名和密码也能破解掉
// 相当于:select * from users where name = '' or 1=1 or '' and password ='' or 1=1 or''
username = "' or 1=1 or '";
password = "' or 1=1 or '";
login(username,password);
}
private static void login(String username, String password) throws SQLException {
// 连接数据库
Connection connection = JdbcUtils.getConnection();
// 返回数据库执行对象
Statement statement = connection.createStatement();
String sql = "select * from users where name='"+username+"' and password = '"+password+"'";
// 接收查询的数据 结果集
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()){
System.out.println("id="+ resultSet.getString("id"));
System.out.println("name="+ resultSet.getString("name"));
System.out.println("password="+ resultSet.getString("password"));
System.out.println("=====================================");
}
// 释放资源
JdbcUtils.release(connection,statement,null);
}
}
PreparedStatement 对象
PreparedStatement 可以防止SQL注入。效率更好!
package testJDBC;
import utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class PrepareStatementDemo {
public static void main(String[] args) throws SQLException {
// 获取数据库连接对象
Connection connection = JdbcUtils.getConnection();
// 没有注入问题的sql执行对象
String sql = "insert into users (id,name,password,email,birthday) values (?,?,?,?,?)";
// 预处理 sql 语句
PreparedStatement pstatement = connection.prepareStatement(sql);
//给 预处理的SQL 语句的问号 附具体值 1 :代表第一个问号,后面则是内容
pstatement.setInt(1,7);
pstatement.setString(2,"李四");
pstatement.setString(3,"123456789");
pstatement.setString(4,"1350099076@qq.com");
// pstatement.setDate(5,java.sql.Date.valueOf("2001/10/26"));
pstatement.setDate(5,new java.sql.Date(new java.util.Date("2001/10/26").getTime()));
// 执行SQL语句 注意:这里使用参数的,前面已经预处理了 返回受影响的行数
int i =pstatement.executeUpdate();
if(i>0){
System.out.println("插入成功");
}else {
System.out.println("插入失败");
}
// 释放资源
JdbcUtils.release(connection,pstatement,null);
}
}
PreparedStatement 防注入问题
防止注入原理:传递过去的 ’ 会被转义,所以也不会报错也不会出结果
直接看代码
package testJDBC;
import utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class SQLInjection_pStatement {
public static void main(String[] args) {
String username = "李四";
String password = "123456789";
// 正常的查询
// login(username,password);
// 带注入的查询 这样是查询不出来的,因为这个' 会被转义了。所以不会报错也不会有效
username = "'' or 1=1";
password = "'' or 1=1";
login(username,password);
}
private static void login( String username,String password) {
Connection connection=null;
PreparedStatement pstatement = null;
ResultSet resultSet = null;
try {
// 获取数据库连接对象
connection = JdbcUtils.getConnection();
// 获取数据库执行对象 预处理sql
String sql = "select * from users where name = ? and password = ?";
pstatement = connection.prepareStatement(sql);
pstatement.setString(1,username);
pstatement.setString(2,password);
// 执行sql 返回结果集
resultSet = pstatement.executeQuery();
while(resultSet.next()){
System.out.println("id="+resultSet.getInt("id"));
System.out.println("name="+resultSet.getString("name"));
System.out.println("password="+resultSet.getString("password"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
// 释放资源
JdbcUtils.release(connection,pstatement,resultSet);
}
}
}
使用IDEA连接数据库
-
添加数据库
-
用户名密码登入数据库
8.0 以上的还好设置时区 在上面直接点击测试连接 他会报错给出提示,点一下也可以。手动添加亦可
-
选择数据库
- SQL控制台
java代码写事务
ACID 原则 :
原子性:要么全部完成,要么都不完成
一致性:总数不变
持久性:一旦提交不可以,持久化到数据库了
隔离性:多个事务互不干扰
隔离性问题:
脏读:一个书屋读取了另一个没有提交的事务
不可重复读:在同一事务内,重读读取表中的数据,表数据发生了改变
虚读(幻读):在一个事务内,读取到别人插入的数据,导致前后读取的结果不一致
# 建表
CREATE TABLE `account` (
`id` int NOT NULL AUTO_INCREMENT,
`NAME` varchar(40) DEFAULT NULL,
`money` float DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
// 事务代码
package testJDBC;
import utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Sql事务 {
public static void main(String[] args) {
Connection connection = null;
// 1.建立数据库连接
try {
connection = JdbcUtils.getConnection();
// 2.关闭数据库自动提交,会自动开启事务
connection.setAutoCommit(false);
//2.获取数据库执行对象
String sql1 = "update jdbcstudy.account set money = money-100 where name='A'; ";
PreparedStatement ps = connection.prepareStatement(sql1);
ps.executeUpdate();
String sql2 = "update jdbcstudy.account set money = money+100 where name='B'; ";
ps = connection.prepareStatement(sql2);
ps.executeUpdate();
// 业务完成 提交事务
connection.commit();
System.out.println("ok");
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
// 若失败 则回滚
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
数据库连接池
数据库连接 — 执行完毕 — 释放
连接 — 释放 十分浪费系统资源
池化技术:准备一些预先的资源,过来就连接预先准备好的
编写连接池,只需要实现一个接口 DataSource
开源数据源实现
DBCP
C3P0
Druid:阿里巴巴
使用了连接池之后,我们在项目开发中就不需要编写连接数据库的代码了
JDBC:Java DataBase Connectivity,java连接数据库和执行SQL语句的API。
数据源:Data Source。就是将IP、数据库、用户名、密码封装起来对外只提供一个JNDI名称,在应用中只要调用这个JNDI就能连接数据库,而不需要在代码中写入用户名密码等信息。这样做的好处是,当用户名密码乃至数据库变化时只需要去修改JNDI的xml文件而不需要去修改代码
JNDI:Java Naming and Directory Interface,Java命名和目录接口。J
DBCP:DataBase Connection Pool,数据库连接池。