数据库

注意事项

有道云笔记8-22-一

1.数据库简介

1.1DBMS

DBMS是数据库管理系统(database management system),如mysql(开源)、oracla(付费)、sql server(微软)、pgSQL…

1.2DB

DB是数据库(database),database中有多个表,如下就是四个数据库
在这里插入图片描述

1.3关系型数据库

mysql、oracle…都是关系型数据库

1.4非关系型数据库(no sql)

存k-v对,比如redis就是存k-v对

1.5mysql

开源的关系型数据库,已经被oracle收购了,可以支持千万条数据,mariaDB社区版

2.常用命令

2.1登录数据库

mysql -uroot -p,用户名是root,密码是root
注:数据库中的数据存在硬盘中,我们想操作这些数据,如插入一条,查询…等但数据在磁盘的文件中,我们没法直接操作,我们可以让mysql这个数据库管理系统,帮我们操作,但是mysql要求我们登录后才能操作,登录后,需要使用mysql提供的指令来具体操作

2.2显示所有数据库

show databases;

2.3查看数据库版本

select version();

2.4进入某个数据库

use information_schema,进入后会提示Database changed

2.5查看某库中所有的表

show tables;

2.6查看某个表中的数据

select * from 表名;

2.7创建数据库

create database 数据库名;
注:mysql的数据库命名用下划线,大小写要统一,windows不区分大小写

2.8删除数据库

drop database 数据库名;;如果不存在要删的这个数据库,我们还要删就会报错,安全的删除方式为drop database if exists bb;

2.9创建表

  • 首先要进入想要创建表的数据库—use 数据库名
  • 建表—create table test1(username varchar(10),age int);,其中test1是表名;username varchar(10)代表表中其中一列是字符串类型的username;age int代表表中另一列是int类型的age
  • 查询建表是否成功—show tables

2.10查看表结构

desc 表名;
在这里插入图片描述

2.11 查看建表语句

show create table test1;
在这里插入图片描述

2.12删除表

  • 删除表—drop table test1;
  • 安全的删除表—drop table if exists test1;

3.navicat

  • 如果没有navicat,我们可以在dos窗口中,通过mysql提供的指令来操作数据库,但在小黑屏看着比较麻烦
  • navicat是一个可视化工具,它不是数据库,它只是一个软件,我们可以通过这个软件,直观的看到数据库的内容,比小黑屏方便
  • 使用navicat来连接数据库
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

4.主键

主键唯一不能为空,如学生的学号,表中通常都会有主键,通常管主键叫id
在这里插入图片描述

5.SQL语句

5.1数据库定义语言—DDL(Data Definition Language)

create、drop、use…

5.2数据查询语言—DQL(Data Query Language)

select * from.......

5.3数据操纵语言—DML(Data Manipulation Language)

insert、update、delete…

5.4数据控制语言—DCL(Data Control Language)

commit、rollback…

6.数据查询语言—DQL(Data Query Language)

6.1注释

-- 单行注释,有空格

#单行注释

/*
我是多行注释
......
*/

6.2Select

语法:select * from 表

-- 从menu表中查询所有数据,*代表所有
Select * from menu;

-- 通过列名,查询某些列的数据
select id,menu_name,price from menu;

-- 可以使用表名.字段的方式,这样会给提示信息
select menu.menu_name,menu.price,menu.id from menu;

-- 可以给表起别名,如下,m就是别名
select m.menu_name,m.url_image,m.menu_type_id from menu m;

-- 可以给列起别名,其中as可以省略,如果别名中有特殊字符,单引号不能省
select menu_name as '菜名',price as '菜价' from menu;
select menu_name '菜名',price '菜价' from menu;

-- 查询的字段不一定都是表中的字段,如下,'不好吃'和'真贵'都是我们自己加的字段
select m.menu_name as '菜名','不好吃',m.price as '原价','真贵',m.price+10 as '跳楼价','还敢涨价!' from menu m;

6.3过滤(where)

  • 过滤关键字为where
  • 不能用别名
select * from menu where price<10;
select * from menu where price>10 and price<50;
select * from menu where price<10 or price>50;

-- 查询id不是5的所有的菜的菜名和菜价
select menu.id,menu.menu_name as '菜名',menu.price as '菜价' from menu where id=5;

-- 查询学生表中生日为'2013-08-03'的学生的信息
select * from student where birthday = '2013-08-03';

-- 查询学生表中生日为'2013-08-03'之前出生的学生的信息
select * from student where birthday < '2013-08-03';

-- 查询id在5-10之间的菜
select * from menu where id > 5 and id < 10;
select * from menu where id between 5 and 10;

-- 查询id=2,5,18的菜,注意这里没有18这条记录,但不属于错误,只是没查到
select * from menu where id=2 or id=5 or id=18;
select * from menu where id in(2,5,18);

-- 模糊查询
-- 查询所有有"鱼"字的菜,%代表0-多个字符
select menu.menu_name from menu where menu_name like '%鱼%';

-- 查询第二个字是"鱼"字的菜
select menu.menu_name from menu where menu_name like '_鱼%';

-- not,取非,相当于Java中的!
-- 查询id不是2,5,8的菜们
select menu.id,menu_name from menu where id not in(2,5,8);

-- 查询学生中没录入性别的同学
-- mysql中null的判断不能用=或!=来处理,要用is
select * from student where Ssex is null;

-- 查询学生中已录入性别的同学
select * from student where Ssex is not null;

-- 查询价格在0-50之间有图片信息的菜
select menu.menu_name,menu.url_image,menu.price from menu where price between 20 and 40 and url_image is not null and url_image != '';

-- mysql允许我们在where后面写true和false,0相当于false,非0相当于true
select * from menu where 1=2;

6.4排序(order by)

  • 排序关键字为order by
  • 格式:排序 order by 字段名 排序规则:升序asc(默认)/降序desc
select * from menu order by price desc;

-- 菜价按升序排列,如果菜价相同按id降序排列,升序(asc)可以省略
select * from menu order by price asc,id desc;

-- 查找50元以下的菜并按上述方式排序
select * from menu where price < 50 order by price asc,id desc;

6.5限制(limit)

  • 限制关键字为limit
  • 常用于分页,如:百度搜索出来的页面分成好多页
-- 一个参数,下标从1开始
select * from menu limit 5; 
-- 两个参数,下标从0开始算,从下标为3(第三条)开始截取3个
select * from menu limit 3,3;  
select * from menu limit 0,4;

-- 一共上三道菜,挑最贵的来
select * from menu order by price desc limit 0,3;

select menu.id,menu.menu_name,menu.price from menu where price < 30 order by price desc limit 3;

-- api
-- mysql、oracle都有自己的API,但有些使用上可能不太相同
select 5*4;

6.6单行处理函数

6.6.1字符串相关的函数

select 
UPPER("yeonjun"), -- 转大写
LOWER("YEONJUN"), -- 转小写
3+"4", -- 这种会得7,因为mysql做了隐式转换
CONCAT("i","loveu"), -- 用这种方式进行字符串的拼接
CONCAT(3,4),
SUBSTR("iloveyeonjun",2,4), -- 下标从1开始,取4个
SUBSTR("iloveyeonjun",2), -- 从2开始到最后
LENGTH("yeonjun"), -- 长度
LENGTH("你好啊"), -- 计算汉字时不准确,因为是按照按utf-8计算,每个汉字三个字节
CHAR_LENGTH("你好啊"); -- 使用CHAR_LENGTH计算汉字准确

-- 显示这样的菜单:辣子鸡(28元),皮蛋豆腐(15元)
select CONCAT(menu_name,'(',price,'元)') as '菜品价格' from menu;

6.6.2Math相关的函数

select 
ROUND(3.64), -- 四舍五入到整数
ROUND(3.5678,2), -- 保留两位小数
ROUND(35.456,-1), -- 40,-1相当于向前移一位,在十位四舍五入,所以相当于*10
ROUND(34.456,-1), -- 30
TRUNCATE(3.5678,2), -- TRUNCATE是截取的意思,没有四舍五入,要求两个参数,只写一个会报错
TRUNCATE(35.456,-1), -- 30
CEIL(3.1), -- 向上取整
FLOOR(3.9), -- 向下取整
ABS(-2), -- 绝对值
SIGN(9),SIGN(-7),SIGN(0), -- 判断正负
LEAST(4,6,1),GREATEST(4,6,1) -- LEAST最小值,GREATEST最大值
;

6.6.3日期相关的函数

select 
NOW(), -- 获取系统时间
YEAR(NOW()), -- 获取指定日期对应的年
MONTH(NOW()), -- 获取指定日期对应的月
DAY(NOW()), -- 获取指定日期对应的日
date(NOW()), -- 获取指定日期的date部分
time(NOW()), -- 获取指定日期的time部分
YEAR('2022-8-15'),
CURDATE(), -- 获取当前时间的date值
CURTIME(), -- 获取当前时间的time值
date('2022-8-15 09:46:34'),
WEEKDAY(NOW())+1 as '星期', -- 获取当前时间的星期,从0开始
NOW()-INTERVAL 12 day, -- 12天之前
NOW()-INTERVAL 10 year+INTERVAL 12 day, -- 10年前的12天之后
adddate(NOW(),interval - 10 year), -- 这种也表示10年前
DATEDIFF(NOW(),'2001-08-15'), -- 两个参数相隔多少天
DATE_FORMAT(NOW(),'%Y-%m-%d'), -- 对日期格式化
STR_TO_DATE('20230815','%Y%m%d') -- 字符串转日期

-- 查询2019年出生的同学
select * from student where YEAR(birthday) = 2019;

6.6.4ifnull

  • 格式:ifnull(expr1,expr2)
  • 如果第一个参数的值是null,就显示第二个参数,否则显示第一个参数
select menu_name,url_image,IFNULL(url_image,'❤') from menu;

6.6.5if

  • if(boolean表达式,值1,值2)
select id,menu_name as '菜品',url_image as '原图片信息',IF(url_image is null or url_image = '','❤',url_image) as '图片信息' from menu;

-- 用if实现:20元以下好吃不贵,辣子鸡 28 好吃不贵,米饭 2 经济实惠
select menu_name,price,IF(price < 20,'好吃不贵',if(price between 20 and 40,'经济实惠','萝卜开会')) as '介绍' from menu;

6.6.6case when

select 
  Ssex,
  case Ssex
    when '男' then 1
    when '女' then 0
    else '-1'
end as '性别符号'
from student;

-- case的另一种写法
select 
   Ssex,
	case
	  when Ssex = '男' then 1
	  when Ssex = '女' then 0
	  else -1
	end
from student;

select
  price,
	case
	  when price < 20 then '实惠'
	  when price < 40 then '好吃'
	  when price < 50 then '好贵'
	  else '天价'
  end
from menu;

-- 按学生年龄分为90后,00后,10后,老年人,小崽子
select
  birthday,
	case 
	    when year(birthday) < 1990 then '老年人'
		when year(birthday) < 2000 then '90后'
		when year(birthday) < 2010 then '00后'
		else '小崽子'
  end as '你是几世代'
from student;

6.6.7类型转换(convert)

-- convert可以进行类型转换,SIGNED不忽略符号,UNSIGNED忽略符号,a是原数据,b是要转换的类型
select convert(5.4,UNSIGNED integer)
select NOW(),CONVERT(NOW(),date)
-- CAST(a AS b)可以进行类型转换,a是原数据,b是要转换的类型
select CAST(NOW() as date)

6.7多行处理函数(统计函数、聚合函数)

多行处理函数会忽略为null的那一行记录,不进行统计,不会忽略空串
注:统计函数不能在where里出现

-- count(*)可查出共多少条记录
select count(*) from menu; -- 一共多少道菜
select COUNT(price) from menu; -- 会忽略price为null的那条记录

-- avg求平均值,没有要求一定是表上的某列,可能是处理过的数据,如AVG(price + 100)
select AVG(price) as '平均菜价',AVG(price + 100) from menu;
-- 如果不想忽略值为null的行,可以加ifnull来处理当前字段的值
select avg(IFNULL(price,0)) as '菜品' from menu;

-- sum求和
select SUM(price) from menu;

-- max、min,最大值、最小值
select MAX(price),min(price) from menu;

-- 查询有“鱼”字的菜一共有多少个,最高价最低价均价总价格是多少
select 
  COUNT(*) as '数量',
  MAX(price) as '最高价',
  min(price) as '最低价',
  avg(price) as '均价',
  SUM(price)as '总价格' 
from menu where menu_name like '%鱼%';


6.7.1分组(group by)

  • 分组,关键字为group by,分多少组,就有多少条记录
-- 按菜的类别分了个组并求出每组有多少道菜
select menu_type_id as '菜类',count(*) as '类的个数' from menu group by menu_type_id;

-- 求每类菜的平均价
select menu_type_id as '菜类',avg(IFNULL(price,0)) as '平均价格' from menu group by menu_type_id;

-- 查询每种菜卖了多少份,如皮蛋豆腐卖了两份
select menu_id,SUM(num) from orders group by menu_id;

-- 查询每笔付完款的订单的价格
select orders_id,SUM(sum_price) from orders where orders_status = 0 group by orders_id;

-- 按桌和订单分组
select table_num as '桌子编号',orders_id as '订单编号',sum(sum_price) as '营业额' from orders group by table_num,orders_id;	

6.7.2对已形成的组进行过滤(having)

  • having在分组之后将某些组过滤掉,having不能用带引号的别名
-- 查询订单价格大于100的订单
select 
  orders_id as 订单序号,
	SUM(sum_price) as '总价格'
	from orders 
	where orders_status = 0 
	group by orders_id
	having 总价格 > 100;

6.7.3去重(distinct)

-- distinct去重,前面不能再有其他字段
select distinct menu_type_id from menu;

-- distinct后面可以有多个字段,当多个字段的值都相同时才可以去重
-- 如下,此时distinct按照menu_type_id,menu_name这两个字段来进行去重
select distinct menu_type_id,menu_name from menu;
-- 按照menu_type_id,price这两个字段进行去重
select distinct menu_type_id,price from menu;

-- distinct可以和聚合函数一起使用
select count(distinct menu_type_id) from menu;
select avg(distinct price) from menu;
select price from menu order by price;
select count(distinct price) from menu;

6.8多表查询

  • 多个表进行关联会以笛卡尔积的方式生成一个新的表
  • 多表关联通常需要关联条件,否则就会是笛卡尔积
select * from menu,menu_type where menu.menu_type_id = menu_type.id;

-- 可以给表起别名方便调用
select * from menu m,menu_type mt where m.menu_type_id = mt.id;

-- 显示菜名,菜价,菜的类别名称
select m.id,menu_name,price,menu_type_name from menu m,menu_type mt where m.menu_type_id = mt.id order by m.id;

-- 等值连接
-- 查询订单表中的信息,显示订单id,菜名,菜价,份数,菜的类别名称
select 
o.orders_id       as '订单编号',
m.menu_name       as '菜品名称',
m.price           as '菜品价格',
o.num             as '菜品份数',
mt.menu_type_name as '菜品类别'
from menu m,menu_type mt,orders o 
where m.id=o.menu_id and m.menu_type_id=mt.id;

-- 不等值连接
-- 通过查询score表和score_grade表显示成绩及对应的等级
select 
s.student_id  as '学号',
s.course_id   as '课程id',
s.number      as '成绩',
sg.grade      as '成绩等级'
from  score s,score_grade sg
where s.number between sg.lower_score and sg.upper_score;

-- 多表关联的另一种写法 join on,写全为inner join on
-- 格式:select * from 表1 join 表2 on 关联条件
select * 
from menu m 
join menu_type mt on m.menu_type_id = mt.id
join orders o on o.menu_id = m.id
where m.menu_name = '东北乱炖'

select *
from score s
join score_grade sg on s.number between sg.lower_score and sg.upper_score

-- 查询所有的菜并显示订单相关信息
-- 正常来说做连接是取交集,数据可能会丢失
-- 左连接,以左表为主,保证数据不丢,写全为:left outer join
select *
from menu m
left join orders o on m.id = o.menu_id

-- 右连接,以右表为主,写全为:right outer join
select *
from orders o
right join menu m on m.id = o.menu_id

-- 查找没被点过的菜
select * 
from menu m
left join orders o on m.id = o.menu_id
where orders_id is null;

-- 查询有绩效的所有员工的姓名,部门名称
-- 注意,有时可能需要使用left join,避免数据丢失
select 
concat(e.first_name,'.',e.last_name) as '员工姓名',
d.department_name                    as '部门名称'
from employees e
left join departments d on e.department_id = d.department_id
where e.commission_pct is not null;

6.9子查询

-- 子查询
-- 查找最便宜的那道菜的菜名和菜价
-- 先查找价格最便宜的菜的菜名和菜价
select menu_name,price from menu where price = 2
-- 根据上面的查出来的价格进行查询
select * from menu where price = (select min(price) from menu)

-- 查找和Bruce一个部门的员工
-- 先找到Bruce的部门id
select department_id from employees where first_name = 'Bruce'
-- 根据上面的部门id查找员工
select 
concat(first_name,'.',last_name)    as '名称',
department_id                       as '部门id'
from employees 
where department_id = 
(
select department_id from employees where first_name = 'Bruce'
) 
and first_name != 'Bruce'

-- 找到最热销的菜,显示它的菜名
-- 第一种写法:子查询
-- 先找到最热销的菜的id
select menu_id as '菜品序号',sum(num) as '销售数量' from orders group by menu_id order by 销售数量 desc limit 1
-- 根据上面找到的id显示菜名
select menu_name
from menu 
where id = 
(
select menu_id as '菜品序号' from orders group by menu_id order by sum(num) desc limit 1
)
-- 第二种写法:多表关联
select 
menu_name as '菜品名称',
price as '菜品单价',
menu_type_id as '菜品类别序号',
test1.菜品序号,
test1.销售数量,
test1.销售总额
from menu m
join (select menu_id as '菜品序号',sum(num) as '销售数量',sum(sum_price) as '销售总额' from orders group by menu_id order by 销售数量 desc limit 1) test1 
on m.id  = test1.菜品序号

-- 子循环也可以如下这样写,子循环写在列的位置时只能有一个值
select menu_name,(select count(price) from menu) from menu;

6.10自连接

-- 自连接
-- 查询员工的id和名字,以及该员工经理的id和名字
select 
e.employee_id  as '员工id',
e.first_name   as '员工名称',
m.employee_id  as '经理id',
m.first_name   as '经理名称'
from employees e
left join employees m on e.manager_id = m.employee_id

6.11sql语句书写顺序和执行顺序

-- sql语句书写顺序
select ... from ...where 条件 group by 分组 having 条件 order by ... limit ...
-- 执行顺序
from ..where..group by..having..select ..distinct ..order by..limit;
	1.from 笛卡尔积-->虚表 VT1  select * from t1 join t2 
	2.on过滤 --> 将两个表中符合条件的再存一个新的虚表VT2   on 关联条件
	3.看一下,如果有leftright,就把主表中有、子表中没有的那部分添进去,行成VT3
	4.where过滤-->形成VT4
	5.group by-->形成VT5
	6.having对已行成的组进行过滤-->形成VT6
	7.select 将不需要的字段去掉了-->形成VT7
	8.distinct-->形成VT8
	9.order by-->形成VT9
	10.limit-->形成VT10

6.12union

  • union用来整合两个查询的结果,会自动去重,如果不想去重,使用union all
  • 查询的列数和顺序需要相同,顺序不同有时可能不会报错但结果是不正确的
select * from menu where id < 5
union all
select * from menu where id < 10

6.13是否存在(exists)

  • 先查一条主表,在进入子查询对应查找
  • 只要子查询中能返回结果,不管几条不管内容是什么,结果都是true
  • 如果子查询中一条记录都没查出来,结果就是false,主表这条记录就不会输出
-- 卖过的菜
select * from menu m where exists(
select * from orders o where m.id = o.menu_id
)

-- 没卖过的菜
select * from menu m where not exists(
select * from orders o where m.id = o.menu_id
)

7.数据操纵语言—DML(Data Manipulation Language)

  • 增删改

7.1插入数据(insert)

-- 插入数据到menu表中
-- insert into 表 (字段1,字段2,...)values(字段1对应的值)
insert into menu
(id,menu_name,url_image,price,menu_status,menu_type_id)
values
(null,'测试1','asda',33,null,8);
select * from menu

-- 如果所有字段都需要插入,可以省略第一个括号
-- 要求后面括号中的数据个数和顺序都要和建表语句一致
-- 使用时要考虑到顺序问题,如果写死了代码,万一表结构有变化这里就出错了
insert into menu
values
(null,'测试1','asda',33,null,8);

-- 可以选择性的插入数据
-- 但是不能为空的字段还是要填
-- 即使主键自增,但是选择性插入时,主键写几,插入的记录主键就为几,在这之后若插入数据时不写主键,那么主键随着最大的数进行自增
insert into menu(id,menu_name,price,menu_type_id)values(null,'测试4',90,2)
insert into menu_copy1 (id,menu_name,menu_type_id)values(6,'gfjs',8)

-- 主键是自增的,当进行选择性插入时,可以省略不写
insert into menu(menu_name,price,menu_type_id)values('测试2',90,2)

-- 批量插入
insert into menu
(menu_name,price,menu_type_id)
values
('测试3',90,2),('测试5',90,2),('测试6',90,2),('测试7',90,2)

-- 可以从其他表中查询数据,并插入本表
insert into menu_copy1 select * from menu where id in (8,9,7)

-- 主键不能重复不能为空
-- 如果想插入个别字段
insert into menu_copy1(id,menu_name,menu_type_id) select id,menu_name,menu_type_id from menu where id in (13)

insert into menu_copy1(menu_name,price,menu_type_id) select menu_name,price,menu_type_id from menu where id in (2,3,4)

7.2修改数据(update)

  • 格式:update 表名 set 字段1 = 值1,字段2 = 值2,…
  • 切记,通常做修改时都需要加where条件
  • 它会将符合条件的记录都改了
update menu set price = 100 where id = 8

7.3删除数据(delete、truncate)

  • 格式:delete from 表名 where…
delete from menu_copy1 where id = 5

-- 添加一条记录,发现id值不是从1开始
-- 使用delete进行删除操作,自增不会重新计数,自增的值是mysql自动帮我们维护的
insert into menu_copy1 (menu_name,menu_type_id)values('test1',2)

-- truncate删除数据后自增从头开始记录
-- 其实是把表给删了又重新建了一个表
-- 不可以写where
truncate table menu_copy1

-- 伪删除
-- 我们可能并不会进行真正的删除操作,因为这样数据会丢失
-- 可以对表添加一个状态字段

8.数据库定义语言—DDL(Data Definition Language)

-- 库、表的创建、删除和修改
-- 创建数据库
create database gg;

-- 删除数据库
drop database if exists gg;

-- 创建表
create table test1(
username varchar(10) not null,
age int
);

-- 查看建表语句
show create table test1

-- 查看表结构
desc test1

-- 按menu表的样式创建一个新表,只有表结构,没有数据
create table test2 like menu;

-- 删除表
drop table if exists test2;

-- 修改表结构
-- 给tset1表增加一列,该列名为t1,类型是int
alter table test1 add t1 int;
alter table test2 add tt int;

-- 删除test1表中的t1这一列
alter table test1 drop t1;
alter table test2 drop tt;
desc test2;

-- 修改列名
-- 修改列的属性时要注意
alter table test2 change gg t2 int;
alter table test1 change t2 t3 varchar(10) not null;
alter table test2 change tt gg varchar(10) not null;
desc test2;

-- 修改列的数据类型,没有改名字的想法
alter table test1 modify t3 int not null;
alter table test2 modify gg int;

-- 想改表名
alter table test1 rename test2;

9.数据类型

9.1日期类型

  • date(‘2021-1-1’)
  • time(‘12:12:12’)
  • datetime(‘2021-1-1 12:12:12’)
  • timestamp
create table test3(
t1 date,
t2 time
);
alter table test3 drop t2;
-- 测试date可以添加成功的格式:('20220101'),('2022-1-1'),('2022-01-01'),(20220101),('2022/1/1')
insert into test3 values('2022/1/1')
desc test3;
select * from test3;

-- 测试time可以添加成功的格式:('11:11:11'),('11:11'),('111111'),11:11:11
create table test4(
t1 time
);
insert into test4 values(111111)
select * from test4;

-- datetime
-- 范围'1000-1-1 00:00:00---9999-12-31'
create table test5(
t1 datetime
);
insert into test5 values(now());
select * from test5;

-- 修改test5表,将字段t1改为非空并给默认值
alter table test5 modify t1 datetime not null default CURRENT_TIMESTAMP;

-- 给test5表添加一个字段名叫t2,类型是timestamp
-- timestamp是时间戳,可以设置为非空,默认添加系统当前时间,并可以在记录被修改时改时间
-- timestamp范围:1970...~2038...
-- 可以按时区进行显示
alter table test5 add t2 timestamp;
desc test5;

-- 测试正常添加数据,会成功
insert into test5 values(now(),now())

-- 测试不处理t1这一列,mysql会给我们添加默认值,值为当前系统时间
insert into test5 (t2)values(now())

-- 测试只传入date,mysql会将time部分补充为00:00:00
insert into test5 values('20230825','20230825');
select * from test5;

-- 测试timestamp在记录被修改时的变化
-- 给test5表添加一列
alter table test5 add t3 int
update test5 set t3 = 7

9.2数值类型

-- int和integer没什么区别
-- bigint,相当于java中的long
-- float和double会失精度
-- int中的那个11和取值范围无关,是和ZEROFILL这个关键字配合使用的,长度不够会左补0
create table test6(
t1 int
);
desc test6;

-- 2147483647整数最大值 ,这句会超范围
insert into test6 values(2147483650);

-- unsigned无符号,可以多存一倍
alter table test6 modify t1 int unsigned;

select * from test6;

-- decimal(m,n),总长度为m,保留n个小数,会四舍五入
-- 测试decimal,总长度为5,小数为2,整数位有3位
create table test7(
t1 decimal(5,2)
);
insert into test7 values(343.566);
select * from test7;

-- tinyint,相当于java中的byte
create table test8(
t1 tinyint
);
insert into test8 values(127) -- 128会超范围
alter table test8 modify t1 tinyint unsigned;
insert into test8 values(200)
select * from test8

9.3字符类型

-- char(固定长度)和varchar(变长)
-- char固定大小,假设设置char(100),那么就算只存一个字符,mysql底层也会留出100这么大的位置,最多到255
-- varchar不固定大小,假设设置varchar(100),那么就算只存一个字符,mysql底层就按一个字符算,最多到65535
create table test9(
t1 char(10)
);

-- char长度为10,就可以存10个汉字
insert into test9 values('哈哈哈哈哈哈哈哈哈哈')

9.4enum

create table test10(
t1 enum('男','女')
);
insert into test10 values('男'),(1),('女')
select * from test10;

10.约束

-- 对表中的数据设置一些限制条件,比如非空,不重复
-- 非空约束
create table test11(
t1 int not null,
t2 int
);
insert into test11 values(2,3)

-- default,设置默认值
create table test12(
t1 varchar(10) not null default 'gg',
t2 varchar(10) default 'pg',
t3 int
);
-- 主键传空值不会报错
insert into test12 (t3)values(2)
insert into test12 (t2,t3)values('gg',3)
select * from test12;

-- 唯一约束(列级约束),unique
-- 只能处理单列
create table test13(
t1 int unique
);
insert into test13 values(5); -- 执行第二遍就会报错

-- 表级约束
-- 可以处理多列
create table test14(
t1 int,
t2 int,
t3 int,
unique(t1,t2) -- t1和t2合在一起必须唯一
);
insert into test14 values(1,2,3);
insert into test14 values(2,2,3);
insert into test14 values(1,2,5);
select * from test14;

-- 以下这种不是表级约束
create table test18
(
t1 int unique,
t2 int unique,
t3 int
)
insert into test18 values(1,2,3)
insert into test18 values(1,2,6)
insert into test18 values(1,5,3)
select * from test18

-- 主键约束
-- 主键非空且唯一
-- 主键如果不自增是不可以设置为null的
create table test15
(
id int primary key auto_increment,
username varchar(10),
age int
-- primary key (id)
-- auto_increment 自动递增
)

insert into test15 values(null,'gg',2)
insert into test15 values(3,'gg',2)
insert into test15 ()values()
select * from test15

-- 复合主键:多个列作为主键
create table test16
(
username varchar(10),
age int,
gender char(1),
primary key (username,age)
)

insert into test16 values('gg',2,'女');
insert into test16 values('gg',3,'女');
insert into test16 values('gg',2,'女');
select * from test16
desc test16

-- 外键约束
-- 外键应引用引用表中primary key的字段
-- mysql对外建的约束管理有多种方式
	-- RESTRICT 默认的方式:如果有冲突不让增删改
	-- CASCADE级联处理:如果menu_type表删了id为6的记录,menu表中所有menu_type_id为6的记录,会被级联删除
	-- SET NULL 置空:如果menu_type表删了id为6的记录,menu表中所有menu_type_id为6的记录,中的menu_type_id字段会被置为null
-- 现在通常会在代码中进行外键操作的约束,而不是将这个约束功能交给mysql
create table test17
(
id int auto_increment,
name varchar(10),
primary key (id)
)

insert into test17 values(null,'开发部'),(null,'营销部')
select * from test17;

create table test17_emp
(
id int auto_increment,
username varchar(10),
dept_id int,
primary key (id),
foreign key (dept_id) references test17(id)
)

insert into test17_emp values(null,'gg',2)
insert into test17_emp values(null,'pg',1)
insert into test17_emp values(null,'pg',1)
truncate table test17_emp
delete from test17_emp where id in (1,2,3)
select * from test17_emp

11.事务

  • 事务能够保证sql代码们一起成功一起失败
  • 通常事务都是针对多条sql语句
  • 提交或回滚都会结束一个事务

11.1事务的特点(ACID)

  • 原子性
    • 事务不可再分,要么都成功,要么都失败
  • 一致性
    • 事务从一个一致性状态到另一个一致性状态
  • 隔离性
    • 多个事务一起工作相互不被打扰
    • 隔离是有级别的
  • 持久性
    • 是指事务一旦被提交,对数据库的影响就是永久性的

11.2事务的隔离级别

  • 读未提交(B事务读到了A还未提交的事务)
    • 会造成脏读
  • 读已提交(B事务可以读到A已提交的事务)
    • 可以处理脏读
  • 可重复读
    • B事务修改数据库记录并提交,不影响A事务对该条记录的读取结果
    • 如花呗月末结算,发现用户借款30元,但此时用户又借了50元,花呗此时结算只需要看30元的借款,而不能查看用户又借的50元
  • 串行化

12.存储引擎

  • 在mysql中的数据可以用不同的技术存储在文件或内存中
  • show engines;查看mysql支持的引擎
  • InnoDB是默认引擎;MyISAM是以前版本使用的默认引擎;MEMORY是内存级的引擎
  • InnoDB:支持事务,支持外键,支持崩溃修复和并发,支持行锁
  • MyISAM:插入数据快,空间和内存使用比较低,但不支持事务,不支持行锁
  • MEMORY:所有数据都存在内存中,处理速度快,但不支持事务,不支持外键

13.索引

  • 索引的目的是提高查询效率
  • 如果我们查询某条记录,像for循环一样遍历可能会比较慢,但是如果有“目录”就会快很多
  • 创建索引也是耗时的,不能随便使用
  • 聚簇索引(聚集索引),辅助索引(二级索引),全文索引
  • 聚簇索引(聚集索引)基本上就是指主键
  • 辅助索引如唯一约束就是由辅助索引来处理的
  • 主键索引,唯一索引,普通索引,全文索引…
  • mysql中的数据存储是使用B+树来存的
  • 叶子节点存的是具体的数据,非叶子结点存的都是主键和页的关系
  • 我们给表每建一个索引,mysql都会维护一棵树
  • 只有聚簇索引树里存着表中的所有数据
  • 其他的小索引树只存索引值+id
  • 什么场景会创建索引
    • 数据量大
    • 经常在where中查询的字段可以考虑给这个字段建个索引
    • 经常进行order by或group by的字段
    • join时那个连接的字段
  • 不适合建索引
    • 经常有修改的列不建议
    • 比例均衡的,比如性别就不适合建索引
    • 记录少的不适合建索引

14.视图

-- 虚拟表
-- 比如可以出于安全考虑只提供有限的数据给用户
-- 或不同的权限可以查看不同的内容
-- 或者为了方便用户查询,将复杂的查询生成一个视图
-- 或者进行一些统计操作,统计结果放到视图中
-- 视图用show tables可以查询到
create view pg as select id,menu_name,price from menu where price < 20;
select * from pg
update pg set price = 21 where id = 5

15.范式

-- 表和表之间的关系
-- 一对一,一对多,多对多
select * from menu m
join menu_type mt on m.menu_type_id = mt.id

-- 三范式
-- 1.第一范式:关系R中的属性都是不可分割的项
select id,stu_info from student;-- 1  '念念,10岁,女,没上学'
select id,name,age,gender,status from student;-- 1  '念念' 10 '女''没上学'
-- 2.第二范式:在一范式的基础上,每个非主属性(普通字段)完全函数依赖于码
-- 需要有主键
-- 联合主键:每一个普通的字段都需要同时和两个联合的主键都有关系
-- 3.第三范式:任何非主属性(普通字段)不依赖于其他的非主属性,就是指不建议orders表中添加menu_name或price,可以只添加menu_id,然后通过多表关联来查询到menu的详细信息
  -- 不过有时为了空间换时间,我们可能会不按三范式走

16.数据导入导出

16.1导出

  • 在dos命令行,某盘符下
  • 执行mysqldump -uroot -p --databases my_db1>my_db1.sql,会在该盘符下 生成my_db1.sql文件
  • 或执行mysqldump -uroot -p my_db1 menu > menu01.sql,可以导出my_db1库中的menu表

16.2导入

  • 先进入mysql—>mysql -uroot -p
  • 进入某数据库—>use my_db2
  • 将d盘下的aa.sql中的数据导入my_db2库中—>source d:\aa.sql

17.登录

  • mysql -uroot -p
  • 写全后:mysql -uroot -hlocalhost -P3306 -p,想要连别人的数据库必须把ip和端口写全,平时不写是因为我们连的是本机的数据库
  • 如果不写-h或-P默认就找localhost和3306
  • mysql -uroot -h10.11.0.86 -P3306 -proot登不上去,是因为数据库权限问题,也就是说数据库没设置其他人可以访问的权限,如下图,在mysql这个数据库中查看users表,root用户的权限为localhost,也就是说仅本机访问,使用ip地址访问不了
    在这里插入图片描述
    在这里插入图片描述

17.1权限

-- 进入mysql这个数据库中
use mysql;

-- 创建一个新用户,用户名是gg,密码是123,本机可以访问,使用ip地址不能访问
create user 'gg'@'localhost' identified by '123'

-- 创建一个用户,用户名是gg,密码是123,本机或者使用ip地址都可以访问
-- 我的ip10.11.100.114
create user 'gg'@'%' identified by '123'

-- 给gg用户赋查询和修改的权限
grant select,update on my_db1.menu to 'gg'@'localhost';

-- 撤销赋给gg的权限
revoke select,update on my_db1.menu from 'gg'@'localhost';

-- 将全部的权限赋给gg
grant all privileges on *.* to 'gg'@'localhost';
grant all privileges on *.* to 'gg'@'%';

-- 撤销赋给gg的所有权限
revoke all privileges on *.* from 'gg'@'localhost';
revoke all privileges on *.* from 'gg'@'%';

-- 删除仅本机进入数据库的gg用户
drop user 'gg'@'localhost';

-- 删除本机和ip都能进入数据库的gg用户
drop user 'gg'@'%';

注:修改用户权限后可能需要退出数据库再进入数据库后才会生效

18.sql优化

-- 最左匹配
-- 对于联合索引 来说,会按最左匹配来走
-- 但是如果从某个索引列开始,有范围查询,后面的索引 会失效
-- 假设 联合索引  a,b,c   访问 where a=0,c=0 ,C部分就没走
-- 假设 联合索引  a,b,c   访问 where b,c,a 这个会走,myql底层帮我们优化了
-- 索引下推、mysql8(索引跳跃扫描)
use my_db1;-- 切换回我们的数据库
explain select menu_name,price from menu where price >0 and menu_name like 'a%' ;
-- 1.尽量避免全表扫描 可以考虑在where、order by 建索引 
-- 2.where num>2*10   where num/10>2  会导致索引失效 
-- 3.select * from menu where id=5 or id=10;有or可能 会导致索引失效,可以替换成下面的方案
			select * from menu where id=5
			union all
			select * from menu where id=10
-- 4.不要使用select * from 表
-- 5.尽量少用子查询

19.java连接数据库

  • java连接数据库是有一套API的
  • mysql-connector-java是Java的标准JDBC(Java数据库连接)驱动程序,是一种面向Java语言的驱动,可以与MySQL数据库连接并操作,需要根据所使用的MySQL版本选择相应的驱动版本,这样才能正常连接到MySQL数据库
  • 需要导入mysql提供的jar包,拷过来之后不能直接使用,需要加载到项目中
  • 右键jar包–>add as library,会发现jar包左侧,有小箭头,就可以了

19.1JDBC

JDBC全称为Java数据库连接(Java Database Connectivity),是一套用于执行SQL语句的Java API,应用程序可以通过这套API连接到关系型数据库,并使用SQL语句完成对数据中数据的查询、增加、更新和删除等操作

JDBC在应用程序与数据库之间起到了一个桥梁作用,当应用程序使用JDBC访问特定的数据库时,需要通过不同数据库驱动与不同数据库进行连接,连接后即可对该数据库进行相应的操作

JDBC常用API主要位于Java.sql包中,该包定义了一系列访问数据库的接口和类:

1.Driver 接口:代表驱动程序;

2.DriverManager 类:驱动程序管理员;

3.Connection 接口:代表数据库连接;

4.Statement 、PreparedStatement、CallableStatement 接口:代表数据库操作对象;

5.ResultSet 接口:代表结果集;

6.DatabaseMetadata、ResultSetMetadata接口:代表元数据;

7.Types 类:代表JDBC类型。

19.2查询

public static void main(String[] args) throws ClassNotFoundException, SQLException {

        //(可以理解为我们在使用前安了个驱动)类的全限定名,该句的目的,是要将com.mysql.jdbc.Driver这个类加载到内存中
        //如果报ClassNotFoundException,一种是我们写错了;一种是真没这个类
        Class.forName("com.mysql.jdbc.Driver");//驱动管理器会帮我们处理这些事,但我们需要告诉他我们是哪个驱动,如是mysql还是oracle

        //连接数据库 ip、port、用户名、密码
        //jdbc:mysql: 是mysql厂商专用表达式
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_db1", "root", "root");

        //从conn上获取一个执行器,执行器的主要作用,就是执行sql语句
        //它就相当于我们navicat上的那个 执行的小按钮
        Statement statement = connection.createStatement();

        //准备要执行的sql(注意,这只是个字符串)
        String sql = "select * from menu";

        //执行器开始执行sql语句  result:结果  Set集合  -->结果集
        ResultSet resultSet = statement.executeQuery(sql);

        //将结果集中的数据取出
        while(resultSet.next()){//resultSet.next()方法会返回一个boolean值,如果还能拿出数据,就返回true
            //每次执行resultSet.next()都会获取到一条记录
            System.out.println(resultSet.getString("menu_name")+","+resultSet.getBigDecimal("price"));
        }

        //关闭资源
        resultSet.close();
        statement.close();
        connection.close();
}

运行结果如下
在这里插入图片描述

19.3增删改

增删改都使用statement.executeUpdate(),返回值为int类型,没有result结果集

public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_db1", "root", "root");
        Statement statement = connection.createStatement();
        String sql = "insert into menu (menu_name,menu_type_id)values('gg',2)";
        int i = statement.executeUpdate(sql);
        System.out.println(i);

        String sql1 = "insert into menu (menu_name,menu_type_id)values('gg',2)";
        int i1 = statement.executeUpdate(sql1);
        System.out.println(i1);

    }

20.sql注入

模拟登陆,需要输入用户名、密码,此时用sql注入,即使密码不对,也可以登录成功,正确用户名和密码如下图
在这里插入图片描述

public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_db1", "root", "root");
        Statement statement = connection.createStatement();
        String username = "gg";
        String password = "626' or 1 = '1";
        //select * from users where username = 'gg' and password = '815' or 1 = '1'
        String sql = "select * from users where username = '"+username+"' and password = '"+password+"'";
        System.out.println(sql);
        ResultSet resultSet = statement.executeQuery(sql);
        //resultSet结果集中埋了一个指针,刚开始谁也没指,每次next后才会指向下一条记录
        if (resultSet.next()){
            System.out.println("欢迎━(*`∀´*)ノ亻!");
        }else{
            System.out.println("账号或密码输入有误");
        }

        connection.close();
        statement.close();
        resultSet.close();
    }

运行结果如下
在这里插入图片描述

使用sql语句select * from users where username = 'gg' and password = '626' or 1 = '1'运行出来的结果如下,所以resultSet.next()的结果总为true,所以可以登录成功
在这里插入图片描述

21.阻止sql注入

  • 可以将半成品sql传给mysql数据库,如select * from users where username = ? and password = ?
  • 此时就相当于传入两个参数,不会改变sql语句的结构
  • 使用set这个方法传入参数时会自动给参数加双引号
public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_db1", "root", "root");
        String sql = "select * from users where username = ? and password = ?";
        PreparedStatement ps = connection.prepareStatement(sql);
        ps.setString(1,"gg"); //第一个参数的1代表第一个问号
        ps.setString(2,"' or '1'='1"); //第一个参数的2代表第二个问号
        ResultSet rs = ps.executeQuery();
        if (rs.next()){
            System.out.println("欢迎━(*`∀´*)ノ亻!");
        }else{
            System.out.println("账号或密码输入有误");
        }

        connection.close();
        ps.close();
        rs.close();

    }

运行结果如下
在这里插入图片描述

22.元数据

public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_db1", "root", "root");
        String sql = "select sid as '学生id',sname from student";
        PreparedStatement ps = connection.prepareStatement(sql);

        ResultSetMetaData metaData = ps.getMetaData();
        System.out.println("获取列名:"+metaData.getColumnName(1));
        System.out.println("获取列的别名:"+metaData.getColumnLabel(1));
        System.out.println("获取第一列的类型的名字:"+metaData.getColumnTypeName(1));
        System.out.println("获取数据库的名字"+metaData.getCatalogName(1));

        connection.close();
        ps.close();
    }

23.封装数据库中的数据

  • 数据从数据库中查询出来需要封装起来,方便后续使用
  • 通常的方案是每一条记录用一个实例来装,再准备一个ArrayList来存这些实例对象
  • 数据库表的int类型字段映射到实体类中要使用Integer类型,因为如果返回字段值为null,int类型会报错,Integer不会报错
public class homework01_取出Student表中的数据 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_db1", "root", "root");
        String sql = "select * from student";
        PreparedStatement ps = connection.prepareStatement(sql);
        ResultSet rs = ps.executeQuery();
        List<Student> list = new ArrayList<>();
        while (rs.next()) {
            Student stu = new Student(
                    rs.getInt("SId"),
                    rs.getString("Sname"),
                    rs.getString("birthday"),
                    rs.getString("Ssex")
            );
            list.add(stu);
        }
        for (Student student : list) {
            System.out.println(student);
        }
    }
}

class Student {
    private Integer id;
    private String name;
    private String birthday;
    private String gender;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBirthday() {
        return birthday;
    }

    public void setBirthday(String birthday) {
        this.birthday = birthday;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Student(Integer id, String name, String birthday, String gender) {
        this.id = id;
        this.name = name;
        this.birthday = birthday;
        this.gender = gender;
    }

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", birthday='" + birthday + '\'' +
                ", gender='" + gender + '\'' +
                '}';
    }
}

注:sql语句查出来的表存在结果集中,循环输出时要注意不能输出表(结果集)中没有的列,否则会报错

24.解耦合

24.1自己获取连接

  • 工具类
  • 获取连接,方便其他需要用到连接的java文件
  • 关闭连接
public class DButil_解耦合 {
  	//之所以把变量定义在外面是因为如果定义在静态代码块内部,那么出了静态代码块后,变量就失效了,其他方法就用不了了,所以需要定义在外面,而之所以定义为静态变量是因为静态代码块只能给静态变量初始化
    static String driverName;
    static String url;
    static String username;
    static String password;

    /**
     * 将获取文件这个IO操作,改成在静态代码块中执行,避免多次执行
     * 也说明一件事,如果我们修改了配置文件,需要重新启动一下
     * 重启会使static代码块重新执行一次,因为它就执行一次
     * 首次加载该类时,会调用下面的静态代码块
     * 但如果再次调用,就不会再加载了
     */
    static{
        //创建一个Properties对象
        Properties prop = new Properties();
        //将文件中的数据加载到prop中
//            prop.load(new FileInputStream("E:\\Java\\IdeaProjects\\JavaSe\\day26\\src\\day26\\aa.properties"));
        try {
            //使用类加载器来加载资源,相对路径和加载我们平时的类是一个路径,就不容易出问题
            //它是相对于src目录下
            prop.load(DButil_解耦合.class.getClassLoader().getResourceAsStream("day26/aa.properties"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        driverName = prop.getProperty("driverName");
        url = prop.getProperty("url");
        username = prop.getProperty("username");
        password = prop.getProperty("password");
    }

    //获取连接
    public static Connection getConn() {
        Connection connection = null;
        try {
            //(可以理解为我们在使用前安了个驱动)类的全限定名,该句的目的,是要将com.mysql.jdbc.Driver这个类加载到内存中
            //如果报ClassNotFoundException,一种是我们写错了;一种是真没这个类
            Class.forName(driverName);//驱动管理器会帮我们处理这些事,但我们需要告诉他我们是哪个驱动,如是mysql还是oracle

            //连接数据库 ip、port、用户名、密码
            //jdbc:mysql: 是mysql厂商专用表达式
            connection = DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return connection;
    }

    //处理PreparedStatement
    public static void close(ResultSet resultSet, PreparedStatement statement, Connection connection) {
        //关闭资源
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    //处理Statement
    public static void close(ResultSet resultSet, Statement statement, Connection connection) {
        //关闭资源
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

配置文件如下图
在这里插入图片描述

24.2连接池帮我们获取连接

  • 三种连接池dbcp,c3p0,druid,这里只练习druid这一种
  • dbcp连接池需要从配置文件中读取四个参数driver、url、username、pwd并传给连接池
  • c3p0默认配置文件名叫c3p0-config.xml,默认位置在src下,c3p0配置文件中有两种配置,区别是密码不同,可以选择其中一个配置传入连接池中
  • druid连接池提供一个工厂类,可以接收Properties对象
  • 使用连接池需要做的工作
    • 1.导包
    • 2.修改配置文件
    • 3.写工具类
public class DruidUtil连接池 {
    static DataSource dataSource;

    //加载静态资源
    static {
        Properties prop = new Properties();
        try {
            //读取druid.properties文件中的数据
            prop.load(DruidUtil连接池.class.getClassLoader().getResourceAsStream("druid/druid(1).properties"));
            //Druid看我们自己一句一句的set太麻烦,提供给我们一个工厂类,它说它帮我们set,我们只需要将prop传给它
            dataSource = DruidDataSourceFactory.createDataSource(prop);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Connection getConn() {
        try {
            //之前我们自己去连数据库,用Class.forName()和DriverManger.getConn
            //现在交给连接池了,既然让连接池帮我们连数据库,就必须把4个参数给它
            return dataSource.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public static void close(ResultSet resultSet, PreparedStatement statement, Connection connection) {
        //关闭资源
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void close(ResultSet resultSet, Statement statement, Connection connection) {
        //关闭资源
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

配置文件和需要的jar包如下(其中druid(1).properties为配置文件)
在这里插入图片描述

25.事务

  • 事务:一起成功,一起失败
  • 一个事务只有被提交或回滚时才会结束
  • 当一个事务没提交也没回滚时,即使表中的数据没改,但在这个事务中是能查看到数据的改变的,因为是在同一个事务中
  • set autocommit=0 ==> conn.setAutoCommmit(false);
  • commit; ==> conn.commit();
  • rollback; ==> conn.rollback();
public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = DruidUtil连接池.getConn();
            conn.setAutoCommit(false); //设置不能自动提交并开启事务
            String sql = "update stu set age = 1 where id = 3";
            ps = conn.prepareStatement(sql);
            ps.executeUpdate();
            int a = 2 / 0;
            conn.commit(); //手动提交,不过因为前面有异常,这句话执行不到
        } catch (Exception e) {
            try {
                conn.rollback(); //捕获异常后,记得回滚
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }
            e.printStackTrace();
        }

        //这里查询表,发现查到的结果是:
        //上面没写回滚,查到的就是上面修改的结果,
        //如果上面写回滚了,查到的就是最原始的数据
        try {
            String sql = "select * from stu where id = 3";
            ps = conn.prepareStatement(sql);
            ResultSet rs = ps.executeQuery();
            while (rs.next()) {
                System.out.println(
                        rs.getInt("id")
                                + "," +
                                rs.getString("username")
                                + "," +
                                rs.getInt("age")
                );
            }
            conn.commit(); //设置手动提交
        } catch (Exception e) {
            e.printStackTrace();
        }

        DruidUtil连接池.close(null, ps, conn);
    }

26.批处理

  • 批量处理
  • mysql的url需要添加这个值:&rewriteBatchedStatements=true
  • addBatch():将要执行的SQL先保存起来,先不执行,这个方法在设置完所有的占位符之后调用
public static void main(String[] args)throws Exception {
        //获取连接
        Connection conn = DruidUtil连接池.getConn();

        //准备半成品sql
        String sql = "insert into cc (num)values(?)";

        //预编译执行器
        PreparedStatement stat = conn.prepareStatement(sql);
        for (int i = 0; i < 100000; i++) {
            stat.setInt(1,5); //把占位符填充好
            stat.addBatch(); //将待执行的sql添加到一个内存区域
            if(i%5000==0){
                stat.executeBatch(); //满5000,再执行一次 写入操作
                stat.clearBatch(); //清空Batch
            }
        }

        //再执行一次executeBatch
        stat.executeBatch(); //如果我们插入的数据,是100008条,不写这句话,就会丢了8条数据

        DruidUtil连接池.close(null,stat,conn);
    }

27.自动返回主键

public static void main(String[] args) throws SQLException {
        //获取连接
        Connection conn = DruidUtil连接池.getConn();

        //准备半成品sql
        String sql = "insert into stu (id,username,age)values(null,?,?)";

        //准备预编译处理器,这里添加了一个参数,要求执行后返回主键
        PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

        //将占位符填满
        ps.setString(1,"yy");
        ps.setInt(2,5);

        //执行sql语句
//        int i = ps.executeUpdate(); //这里的返回值返回的是成功的条数

        //循环添加数据并取出主键的值
        for (int j = 0; j < 2; j++) {
            int i = ps.executeUpdate();
            ResultSet rs = ps.getGeneratedKeys();
            rs.next();
            int anInt = rs.getInt(1);
            System.out.println(anInt);
        }
//        System.out.println("成功的条数为:"+i);

        //用执行器获取新添加的记录的主键,但主键会被包在ResultSet这个对象中
//        ResultSet rs = ps.getGeneratedKeys();
//        while (rs.next()){
//            //这里我们之前是用的rs.getInt(字段名),但此次没字段名,我们通过另一种方式
//            //通过第N列来获取数据。此次返回值 就一列,所以写1
//            int i1 = rs.getInt(1);
//            System.out.println(i1);
//        }

        //结果集想获取数据,需要先next一下
//        resultSet.next();
//        //这里我们之前是用的rs.getInt(字段名),但此次没字段名,我们通过另一种方式
//        //通过第N列来获取数据。此次返回值 就一列,所以写1
//        int key = resultSet.getInt(1);
//        System.out.println(key);
    }
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值