SQL (Structured Query Language) ,是高级的非过程化编程语言,是用于数据操纵和数据定义等多种功能的数据库语言,允许用户在高层数据结构上工作。它不要求用户指定对数据的存放方法,也不需要用户了解具体的数据存放方式,所以具有完全不同底层结构的不同数据库系统可以使用相同的SQL语言作为数据输入与管理的接口。
SQL的分类:
1、DDL(Data Definition Language) 数据定义语言,用来操作数据库、表、列等; 常用语句:CREATE、 ALTER、DROP
2、DML(Data Manipulation Language) 数据操作语言,用来操作数据库中表里的数据;常用语句:INSERT、 UPDATE、 DELETE
3、DCL(Data Control Language) 数据控制语言,用来操作访问权限和安全级别; 常用语句:GRANT、DENY
4、DQL(Data Query Language) 数据查询语言,用来查询数据 常用语句:SELECT
MySQL是一个关系型数据库管理系统(DBMS,Database Management System),由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。
MySQL所使用的 SQL 语言是用于访问数据库的最常用标准化语言。MySQL 软件采用了双授权政策,分为社区版和商业版,由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特点,一般中小型和大型网站的开发都选择 MySQL 作为网站数据库。
总结:
MySQL自顶向下可分为连接层、服务层、可插拔存储引擎、存储层四层。
网络连接层位于MySQL体系架构的最上层,主要担任客户端服务器的角色,主要完成一些类似于连接处理、授权认证、校验用户和密码等工作,如:Java、C、C++、Python等,各种语言都是通过各自的API接口与MySQL建立连接。
数据库服务层是整个数据库服务器的核心,包括系统管理和控制工具、连接池、提供实现SQL语句的接口、解析器、查询优化器等部分。所有跨存储引擎的功能也在这一层实现,如,过程、函数等。
真正负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信,不同的存储引擎具有不同的功能,我们可以根据自己的需求,来选取合适的存储引擎。
主要将数据存储在文件系统之上,并完成与存储引擎的交互。其存储的文件主要有:日志文件、数据文件、配置文件、MySQL的进行pid文件和socket文件等。
show variables like '%log_error%';
show variables like '%general%';
二进制日志:记录对MySQL数据库执行的插入、修改和删除操作,并且会记录SQL语句执行的时间、时长,但不记录select、show等不修改数据库的SQL,主要用于恢复数据库的数据和实现MySQL主从复制。
show variables like '%log_bin%';
慢查询日志:记录执行时间超过指定时间的SQL语句,这个时间默认是10s。
查看是否开启慢查询日志:
show variables like '%slow_query%';
查看慢查询设置的时长:
show variables like '%long_query_time%'
关于MySQL的安装此处不再赘述,仅记录以下基本连接操作:
启动
net start mysql80
停止
net stop mysql
连接
mysql [-h 127.0.0.1] [-P 3306] -u root -p
基本概念:
关系模型:
常用的数据模型有,层次模型、网状模型、关系模型、面向对象数据模型、对象关系数据模型等,而MySQL采用的是关系模型。在关系模型建立在严格的数学概念基础上,无论是实体还是实体之间的联系都用关系表示,且,关系模型要求关系必须是规范化的,关系的每一个分量必须是不可分的数据项。
术语对比:
以下内容参考:https://blog.csdn.net/weixin_45851945/article/details/114287877
1.整数类型
MySQL中一共定义了五种整数类型,默认是有符号整数,可用unsigned修饰表无符号整数。
- 迷你类型:tinyint,使用1个字节存储整数,最多存储256个整数。
- 短整型:smallint,使用2个字节存储整数。
- 中整型:mediumint,使用3个字节存储整数。
- 标准整型:int,使用4个字节存储整数。
- 大整型:bigint,使用8个字节存储整数。
显示宽度:整数在数据库中显示的位数(符号+数字),其并不会影响类型所能存储的最大整数,可用zerofill让不够宽度的数值补充道对应宽度。
整型类型(L)
2.浮点数和定点数类型
在MySQL数据库中使用浮点数和定点数来存储小数。浮点数的类型有两种:单精度浮点数类型(FLOAT)和双精度浮点数类型(DOUBLE)。而定点数类型只有一种即DECIMAL类型。下图列举了 MySQL中浮点数和定点数类型所对应的字节大小及其取值范围:
- 单精度:float,使用4个字节存储,精度范围为6~7位有效数字。
- 双精度:double,使用8个字节存储,精度范围为14~15位有效数字。
- 定点型:decimal,能够保证精度的小数。
对于单/双精度,统称为浮点数,在超出精度范围时会自动进行四舍五入(故在存储对精度较高的数据时一般不用浮点数),可在定义时指定小数和整数部分,默认情况下整数部分不超过最大值,小数部分保留2位,指定语法:
float/double(总长度,小数部分长度);
可使用科学记数法插入数据,如,AEB,表示A*10^B。
对于定点型,它的存储模式并不固定,可指定整数部分长度和小数部分长度(默认是10位有效整数,0位小数),且,有效数位不超过65位,指定语法为:
decimal(有效数位,小数部分数位);
当整数超出时会报错,小数部分超出时四舍五入。
3.字符串类型
在MySQL中常用CHAR 和 VARCHAR 表示字符串。两者不同的是:VARCHAR存储可变长度的字符串。
对于指定固定长度的char(L),L的最大值是255.
对于根据实际存储的数据变化存储空间的varchar(L),L是最大存储的数据长度,最大值为65535,且需要额外产生1~2个字节用于记录实际数据的长度,虽然效率没有定长字符串高,但能更好利用存储空间。
此外,还有一些特殊类型用于存储字符串:
类型 | 名称 | 存储空间 |
---|---|---|
tinytext | 迷你文本 | 不超过2^8-1个字符 |
text | 普通文本 | 不超过2^16-1个字符 |
mediumtext | 中长型文本 | 不超过2^24-1个字符 |
longtext | 长文本 | 不超过2^32-1个字符(4GB) |
例:
# 标题一般不会超过50个字符,varchar
# 作者一般不会超过10个字符:varchar
# 内容通常都很长,使用text
create table t_15(
author varchar(10),
title varchar(50),
content text
)charset utf8;
insert into t_15 values('佚名','给联合国的一封信','给联合国的一封信...');
enum(元素1,元素2,...);
# 爱好可以是多种,并非固定的,但是只能从规定的类型中选择
create table t_17(
hobby set('足球','篮球','羽毛球','网球','乒乓球','排球','台球','冰球')
)charset utf8;
insert into t_17 values('足球');
insert into t_17 values('冰球,台球,篮球');
4.日期和时间类型
MySQL提供的表示日期和时间的数据类型分别是 :YEAR、DATE、TIME、DATETIME 和 TIMESTAMP。下图列举了日期和时间数据类型所对应的字节数、取值范围、日期格式以及零值:
create table t_18(
y1 year,
y2 year(4)
)charset utf8;
insert into t_18 values(1901,2155);
create table t_19(
goods_name varchar(10),
goods_inventory int unsigned,
change_time timestamp
)charset utf8;
insert into t_19 values('Nokia3110',100,'1971-01-01 00:00:00');
insert into t_19 values('Nokia7100',100,'19710101000000');
create table t_20(
name varchar(10),
birth date
)charset utf8;
insert into t_20 values('Jim','2000-12-12');
insert into t_20 values('Tom','10011212');
# 具体登录时间可以使用时间戳(包含年月日时分秒信息)
# 也可以时间datetime格式,或者date+time双字段格式
create table t_22(
login_time1 int unsigned,
login_time2 datetime,
login_date date,
login_time3 time
)charset utf8;
insert into t_22 values(12345678,'2000-12-12 12:12:12','2000-12-12','12:12:12');
insert into t_22 values(1234567,'2000-12-12 12:12:12','2000-12-12','3 12:12:12');
数据格式format的表示:
建立在字段类型之后,对字段进行除类型之外的其他约束,定义好的属性可使用desc进行查看。
删除主键:
alter table t_26 drop primary key;
新增主键:
alter table t_26 add primary key(account,name);
show variables like "auto_increment%";
进行查看。
修改自增长的值,下一条数据将直接从该值开始:
alter table t_28 auto_increment = 50;
修改自增长控制:
set auto_increment_increment = 2; # 当前用户当前连接有效(局部)
set @auto_increment_increment = 2; # 所有用户一直有效(全局)
unique key(字段1,字段2,...);
删除唯一键:
alter table t_30 drop index stu_name;
追加唯一键:
alter table t_30 add unique key stu_course (stu_name,course);
称dept_id为外键,此时,将部门表称为父表,而员工表称为子表。
注意,目前上述的两张表,在数据库层面并未建立外键关联,所以是无法保证数据的一致性和完整性的。
添加外键约束:
①创建表时添加
CREATE TABLE 表名(
字段名 字段类型,
...
[CONSTRAINT] [外键名称] FOREIGN KEY(外键字段名) REFERENCES 主表(主表列名)
);
②对表进行修改
ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名) REFERENCES 主表(主表列名);
-- 例子
alter table emp add constraint fk_emp_dept_id foreign key(dept_id) references dept(id);
删除外键约束:
alter table 表名 drop foreign key 外键名称'
-- 例子
alter table emp drop foreign key fk_emp_dept_id;
alter table 表名 add constraint 外键名称 foreign (外键字段) references 主表名(主表字段名) on update cascade on delete cascade;
例:建立表emp和dept表的外键约束,当父表dept的键id被更新、删除时,子表中的外键亦会被更新、删除。
alter table emp add constraint fk_emp_dept_id foreign key(dept_id) references dept(id) on update cascade delete cascade;
例:建立表emp和dept表的外键约束,当父表dept的键id被更新、删除时,子表中的外键亦会被设置为null。
alter table emp add constraint fk_emp_dept_id foreign key(dept_id) references dept(id) on update set null delete set null;
create database Test;
use Test;
create table user(
id int primary key auto_increment comment '主键',
name varchar(10) not null unique comment '姓名',
age int check ( age > 0 && age <= 120 ) comment '年龄',
status char(1) default '1' comment '状态',
gender char(1) comment '性别'
)comment '用户表';
#插入数据
insert into user(name,age,status,gender)values('Tom1',19,'1','男'),('Tom2',25,'0','男');
#id不用插入,因为其是自动插入的.
DDL(Data Definition Language),数据库定义语言,用来定义数据库对象(数据库、表、字段)
创建数据库
create database [if not exists] 数据库名;
删除数据库
DROP DATABASE [if EXISTS] 数据库名;
使用数据库
--如果表名或者字段名是特殊字符,则需要带``
use 数据库名;
查看所有数据库
SHOW DATABASES;
查看当前数据库
select database();
创建数据表
数据库创建成功后可在该数据库中创建数据表(简称为表)存储数据。请注意:在操作数据表之前应使用“USE 数据库名;”指定操作是在哪个数据库中进行先关操作,否则会抛出“No database selected”错误。
CREATE TABLE IF NOT EXISTS `student`(
'字段名' 列类型 [属性] [索引] [注释],
'字段名' 列类型 [属性] [索引] [注释],
......
'字段名' 列类型 [属性] [索引] [注释]
)[表的类型][字符集设置][注释]
查看当前数据库所有数据表
show tables;
查看表的创建语句
show create table 表名;
查看表属性
desc 表名;
#Field:字段名称
#Type:数据类型
# Null:是否为空(属性)
# Key:索引类型(属性)
# Default:默认值(属性)
# Extra:额外属性
修改表名
alter table 旧表名 rename to 新表名;
删除数据表
drop table 表名;
修改字段名
alter table 表名 change 旧字段名 新字段名 字段类型;
修改字段数据类型
alter table 表名 modify 字段名 数据类型
增加字段
alter table 表名 add 字段名 数据类型;
删除字段
alter table 表名 drop 字段名;
DML(Data Manipulation Language),数据库操作语言,用来对数据库表中的数据进行增删改。
基本语法:
insert into 表名(字段1,字段2,...)values(值1,值2,...)(值1,值2,...);
既可插入一条数据,也可同时插入多条数据。
既可插入所有字段,亦可选择字段进行插入。
基本语法:
UPDATE 表名 SET 字段名1=值1[,字段名2 =值2,…] [WHERE 条件表达式];
当不写where表达式时则会更新表中该字段的所有属性值。
基本语法:
delete from 表名 [where 条件];
当不写where表达式时则会删除数据库表中所有记录。
DQL(Data Query Language),数据库查询语言,用来查询数据库中表的记录。
SELECT [ALL | DISTINCT]
{* | table.* | [table.field1[as alias1][,table.field2[as alias2]][,...]]}
FROM table_name [as table_alias]
[left | right | inner join table_name2] -- 联合查询
[WHERE ...] -- 指定结果需满足的条件
[GROUP BY ...] -- 指定结果按照哪几个字段来分组
[HAVING] -- 过滤分组的记录必须满足的次要条件
[ORDER BY ...] -- 指定查询记录按一个或多个条件排序
[LIMIT {[offset,]row_count | row_countOFFSET offset}];
-- 指定查询的记录从哪条至哪条
简化:
select(可使用聚合函数)
字段列表
from
表名列表
where(对应条件查询)
条件列表
group by(对应分组查询)
分组字段列表
having
分组后条件列表
order by(对应排序查询)
排序字段列表
limit(分页查询)
分页参数
-- 数学运算
SELECT ABS(-8); -- 绝对值
SELECT CEIL(5.1); -- 向上取整
SELECT CEILING(5.1); -- 向上取整
SELECT RAND(); -- 返回0~1之间的一个随机数
SELECT SIGN(-10); -- 返回一个数的符号;0返回0;正数返回1;负数返回-1
round(x,保留位数);--四舍五入; 当对正数进行四舍五入:按照正常的计算方式,四舍五入即可。当对负数进行四舍五入:先把符号丢到一边,对去掉负号后的正数进行四舍五入,完成以后,再把这个负号,补上即可。
floor(x):向下取整
truncate(x,D);--截断函数,截取不要的部分,然后删掉(断掉)它.在小数点的D位置处,截取数字直接删去数字,若在左边就是位置取整不使用任何法则.
mod(被除数,除数);--求余
pow(x,D);--求x的D次幂
-- 字符串函数
SELECT LENGTH('我喜欢你'); -- 字符串长度
SELECT CONCAT('我','喜欢','你'); -- 拼接字符串
SELECT INSERT('我喜欢',1,1,'超级') -- INSERT(str,pos,len,newstr) 从str的pos位置开始替换为长度为len的newstr
SELECT UPPER('zsr'); -- 转大写
SELECT LOWER('ZSR'); -- 转小写
SELECT INSTR('zsrs','s'); -- 返回第一次出现字串索引的位置
SELECT REPLACE('加油就能胜利','加油','坚持'); -- 替换出现的指定字符串
SELECT SUBSTR('坚持就是胜利',3,6); -- 返回指定的字符串(源字符串,截取位置,截取长度)
SELECT REVERSE('rsz'); -- 反转字符串
SELECT INSTR(str,要查询的子串); -- 返回子串第一次出现的索引,凡存在则返回0(索引从1开始)
SELECT TRIM(str);--去掉字符串的前后空格
lpad(str,len,填充字符)--用指定的字符,将字符左填充至指定长度
rpad(str,len,填充字符)--用指定的字符,将字符右填充至指定长度
replace(str,子串,另一个字符串)--将字符串str中的子串替换为另一个字符串
-- 时间日期函数
SELECT CURRENT_DATE(); -- 获取当前日期
SELECT CURDATE(); -- 获取当前日期
SELECT now(); -- 获取当前时间
SELECT LOCALTIME(); -- 本地时间
SELECT SYSDATE(); -- 系统时间
weekofyear();--获取当前所属周数
quarter();--获取当前所属季度
str_to_date();--将日期格式转为字符串,可用%Y、%m、%d进行格式化,但格式是固定的,例:
select str_to_date("2023,1,15",'%Y,%c,%d');
会转换为:2023-01-15
select date_format(日期,指定格式);--将日期格式转为字符串,可用%Y、%m、%d进行格式化,格式可自行指定
date_add(日期,interval num 时间);--向前、后偏移时间和日期,用正负号来确定,例:
select date_add(curdate(),interval 1 day);
select last_day(now());--获取某个月的最后一天
datediff(end_date,start_date);--计算两个时间相差的天数
--获取年、月、日、小时、分钟、秒
SELECT YEAR(NOW());
SELECT MONTH(NOW());
SELECT DAY(NOW());
SELECT HOUR(NOW());
SELECT MINUTE(NOW());
SELECT SECOND(NOW());
-- 系统信息
SELECT SYSTEM_USER();
SELECT USER();
SELECT VERSION();
show processlist;--查看用户连接信息
select 字段1,字段2,... from table 表名;
可使用通配符*来查询表中所有数据。
用于检索数据中符合条件的值,检索的条件可由一个或多个表达式组成,结果为布尔值。
运算符 | 语法 | 描述 |
---|---|---|
and/&& | exp1 and exp2/exp1 && exp2 | 逻辑与 |
or/|| | exp1 or exp2/exp1 || exp2 | 逻辑或 |
not/! | not exp/!exp | 逻辑非 |
运算符 | 语法 | 描述 |
---|---|---|
is null | E is null | 若字段E为null,则结果为真 |
is not null | E is not null | 若字段E不为null,则结果为真 |
between … and … | A between B and C. | 若A在B和C之间,则结果为真 |
like | A like B | SQL匹配,若A匹配B,则结果为真 |
in | A in (A1,A2,A3,…) | 若A是A1、A2、A3中的任意一个,则结果为真 |
对于like查询,其目的是判断两个字符串是否匹配,有以下三种情况:
select 字段名 from 表名 where 字段名 like 字符串;
当字段值和字符串完全相等时才为true。
2. %通配符
%通配符用于匹配任意长度的任意字符串,如:匹配以"l"结尾的字符串:
select 字段名 from 表名 where 字段名 like "%l";
select 字段名 from 表名 where 字段名 like "ax__";
分组查询可将表中数据分组后再进行查询,使用group by来选择分组的字段,having则用于分组后的过滤。
select 分组函数,分组后的字段
from 表
【where 筛选条件】
group by 分组的字段
【having 分组后的筛选】
【order by 排序列表】
select name from test group by name;
使用group by函数前的规定:
过滤分组:group by可以对数据进行分组,但是分组后所有的组都会被查询,但有时,我们并不需要所有的分组,此时可以对分组进行过滤。where并不能对分组进行过滤,因为where的过滤对象是行(一个单元格只含有一个数据),MySQL提供了having来对分组进行过滤,实际上,目前为止所有的where子句都可以用having来代替,唯一的差别就是,where对行过滤,而having对分组进行过滤。且二者不能同时使用。
分组的过滤常常和聚合函数一起使用。
例:
-- 查询不同科目的平均分、最高分、最低分且平均分大于90
-- 核心:根据不同的课程进行分组
SELECT SubjectName,AVG(StudentResult),MAX(`StudentResult`),MIN(`StudentResult`)
FROM result r
INNER JOIN `subject` s
on r.SubjectNo=s.SubjectNo
GROUP BY r.SubjectNo
HAVING AVG(StudentResult)>90;
注意:HAVING语句可与GROUP BY配合使用,但不能和WHERE语句配合使用。且,分组查询经常和聚合函数一起使用。
连接查询中,一共有左连接(LEFT JOIN)、右连接(RIGHT JOIN)、内连接(INNER JOIN)、外连接(OUTER JOIN)四种连接方式,并使用ON来指定连接键,结合where的条件语句,可得到以下七种连接方式:
-- 查询学员所属的年级(学号,学生姓名,年级名称)
SELECT `StudentNo`,`StudentName`,`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;
-- 查询列参加程序设计考试的同学信息(学号,姓名,科目名,分数)
SELECT s.`StudentNo`,`StudentName`,`SubjectName`,`StudentResult`
FROM student s
INNER JOIN result r
on s.StudentNo=r.StudentNo
INNER JOIN `subject` sub
on r.SubjectNo=sub.SubjectNo
where SubjectName='课程设计';
自连接
自连接可将同一张表当做两张表来使用。
用法一:在同一张表内进行比较
例:查找收入超过各自经理的员工姓名
Id | Name | Salary | ManagerId
----+-------+--------+-----------
1 | Joe | 70000 | 3
2 | Henry | 80000 | 4
3 | Sam | 60000 | NULL
4 | Max | 90000 | NULL
————————————————
SELECT e1.Name AS employee_name
FROM Employee AS e1, Employee AS e2
WHERE e1.ManagerId=e2.Id
AND e1.Salary>e2.Salary
例:查找比昨天温度高的所有日期的id
| Id(INT) | RecordDate(DATE) | Temperature(INT) |
+---------+------------------+------------------+
| 1 | 2015-01-01 | 10 |
| 2 | 2015-01-02 | 25 |
| 3 | 2015-01-03 | 20 |
| 4 | 2015-01-04 | 30 |
SELECT w1.Id
FROM weather w1
JOIN weather w2
ON DATEDIFF(w1.RecordDate,w2.RecordDate)=1
WHERE w1.Temperature>w2.Temperature
用法二:找出列的组合
例:查找共用同一车站的所有公交路线
SELECT * FROM route R1, route R2
WHERE R1.stop=R2.stop;
用法三:找出部分内容重复的记录
例:查找价格相同但商品名不同的商品信息
SELECT DISTINCT P1.name, P1.price
FROM Products P1, Products P2
WHERE P1.price = P2.price
AND P1.name != P2.name;
例:删除Person表中所有重复的电子邮箱
DELETE p1 FROM Person p1, Person p2
WHERE p1.Email = p2.Email
AND p1.Id > p2.Id
子查询是指一个查询语句嵌套在另一个查询语句内部的查询;该查询语句可以嵌套在一个 SELECT、SELECT…INTO、INSERT…INTO等语句中。在执行查询时,首先会执行子查询中的语句,再将返回的结果作为外层查询的过滤条件。在子査询中通常可以使用比较运算符和IN、EXISTS、ANY、ALL等关键字。
带比较运算符的子查询
例:比张三所在班级编号还大的班级的信息
select * from class where cid>(select classid from student where sname='张三');
带EXISTS的子查询
EXISTS关键字后面的参数可以是任意一个子查询, 它不产生任何数据只返回TRUE或FALSE。当返回值为TRUE时外层查询才会 执行
例:假如王五同学在学生表中则从班级表查询所有班级信息
select * from class where exists (select * from student where sname='王五');
带ANY的子查询
ANY关键字表示满足其中任意一个条件就返回一个结果作为外层查询条件。
select * from class where cid > any (select classid from student);
带ALL的子查询
带ALL关键字的子査询返回的结果需同时满足所有内层査询条件。
select * from class where cid > all (select classid from student);
事实上,子查询可分为相关子查询和不相关子查询。
-- 主语句
select * from world where 成绩>分科平均分;
-- 子查询:分科平均分
#方法1.子查询用having子句通过条件筛选这个group by 数组后,就能使得每一行的数据对应一个avg(成绩)
select * ,
(select avg(成绩) from score as s2 group by 课程号 having s1.课程号=s2.课程号 ) as 分科平均成绩
from score as s1;
#方法2.这里的where子句可以起到和having一样的效果,where已经起到选组作用,group by 子句可以省略
select * ,
(select avg(成绩) from score as s2 where s1.课程号=s2.课程号 /*group by 课程号*/) as 分科平均成绩
from score as s1;
分页查询是在页面上将本来很多的数据分段显示,每页显示用户自定义的行数。可提高用户体验度,同时减少一次性加载,内存溢出风险。
在开发过程中的效果:
语法:
select 字段列表 from 表名 limit 起始索引,查询记录数;
select * from emp limit 10;
--起始索引为0,可以省略
查询第二页员工数据,每页显示10条记录
select * from emp limt 10 10;
使用ORDER BY可根据指定字段对查询结果进行排序,字段优先级取决于字段的设置次序。
SELECT 字段名1,字段名2,…
FROM 表名
ORDER BY 字段名1 [ASC 丨 DESC],字段名2 [ASC | DESC];
DCL(Database Control Language),数据库控制语言,用来管理数据库,控制数据库用户访问权限。
-- 查看所有用户
select * from mysql.user;
-- 查看当前所用用户
select user();
-- 创建用户,并指定密码
CREATE USER 用户名 IDENTIFIED BY '123456'
-- 删除用户
DROP USER 用户名
-- 修改当前用户密码
SET PASSWORD = PASSWORD('200024')
-- 修改指定用户密码
SET PASSWORD FOR 用户名 = PASSWORD('200024')
-- 重命名
RENAME USER 用户名 to 新用户名
-- 用户授权(授予全部权限,除了给其他用户授权)
GRANT ALL PRIVILEGES on 数据库名.表名 TO 用户名
--用户授权(授予指定权限)
GRANT 权限 on 数据库名.表名 TO 用户名
-- 查询用户权限
SHOW GRANTS FOR 用户名
-- 查看root用户权限
SHOW GRANTS FOR root@localhost
-- 撤销所有权限
REVOKE ALL PRIVILEGES ON 数据库名.表名 FROM 用户名
可指定主机,使得用户只能在该主机上访问数据库,格式为:‘用户名’@‘主机名’。可使用’%'代替主机名,表示任意地址均可登录。
GRANT语句中可选权限:
事实上,在MySQL中,会自带四个用户,其中root用户是经常使用的,它对整个MySQL服务器有完全的控制,但一般来说,最好不要使用root,因为其权限过大,容易出错。用户信息存放在mysql数据库下的user表,可以用select * from mysql.user;进行查看。
在DataGrip、IDEA中连接数据库时就可指定用户,而在命令行中,可使用:
mysql -u 用户名 -p
进行登录。
例:将下表变为规范的表(主键为(学号,课程名))
①1NF的改造:使得没个列都不可再分。
从表中可知,"系"列又分为了系名和系主任,因此,"系"列是可再分的,需要进行改造:
②2NF的改造:1NF的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。
对于上表的主键(学号,课程名),和该表中其他列的关系,有:
(学号,课程名)->姓名,姓名对(学号,课程名)是部分函数依赖。
(学号,课程名)->系名,系名对(学号,课程名)是部分函数依赖。
(学号,课程名)->系主任,系主任对(学号,课程名)是部分函数依赖。
(学号,课程名)->分数,分数对(学号,课程名)是完全函数依赖。
而2NF的目的就是在1NF的基础上消除部分函数依赖。由于姓名、系名、系主任均可由学号唯一完全确定,而分数由(学号,课程名)唯一完全确定,故可分解为两个表:
可以发现,很多数据出现次数减少,如,"张无忌"有1NF中的3次出现变成了一次出现,这大大减少了存储空间,提高查询效率。
事实上,2NF可以理解为:在1NF的基础上消除了非主键对主键的部分函数依赖。
③3NF的改造:在2NF的基础上,非主键列值依赖于主键,而不依赖其他非主键。
对于选课表,属性列:课程名称和分数均完全函数依赖于学号,且二者之间并无函数依赖关系,故满足3NF。
对于学生表,系主任可由系名唯一完全确定,即,系主任对系名存在函数依赖(与是完全函数依赖还是部分函数依赖无关),且系名并非主键,其函数完全函数依赖于主键,故,这里存在传递函数依赖,可以进行化简:
由此可见,符合3NF要求的数据库设计,基本上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。当然,在实际中,往往为了性能上或者应对扩展的需要,经常 做到2NF或者1NF,但是作为数据库设计人员,至少应该知道,3NF的要求是怎样的。
已知函数依赖:
由于,仓库名->管理员 管理员->仓库名 ,的存在,故,存在主属性对码的部分函数依赖和传递函数依赖,因而,不属于BCNF。
转化为BCNF的方法便是,将仓库名或管理员分离出表,而数量由(仓库名,物品名)唯一完全确定,故应将管理员分离出:
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表的,而不是基于库的,所以,存储引擎也可被称为表类型,且,同一数据库的不同表可以指定不同的存储引擎。
在Oracle、SQL Server数据库管理系统中只有InnoDB一种存储引擎,而MySQL数据库提供了多种存储引擎。用户可以预先设置或者在MySQL服务器中启用。也可以选择适用于服务器、数据库和表格的存储引擎,以便在选择如何存储你的信息,如何检索这些信息以及所需要的数据结合什么样的性能和功能的时候提供了最大的灵活性。
--查看数据库支持的所有引擎
show engines;
--查看当前表所用的引擎
show table status like 表名;
--创建表时指定存储引擎
create table 表名{
字段1 字段1类型 [comment 字段1注释],
...
字段n 字段n类型 [comment 字段n注释]
}engine=InnoDB [comment 表注释];
视图是从一个或多个表中导出来的表,是一种虚拟存在的表。视图就像一个窗口,通过这个窗口可以看到系统专门提供的数据,这样用户可以不看整个数据库表中的数据,而只关心对自己有用的数据。视图可以使用户的操作更方便,而且可以保障数据库系统的安全性。
即:视图是虚拟表,本身不存储数据,而是按照指定的方式进行查询。
视图的好处:
视图的规则:
视图的基本操作:
创建视图
create view 视图名 (列1,列2,列3,...) as select 列1,列2 from 表名
查看创建视图的语句
show create view 视图名;
删除视图
drop view 视图名;
视图的更新:视图是可更新的(利用insert、update、delete语句),更新一个视图是会影响到其基表的。但并非所有视图均可更新,当视图含义以下操作时,其是不可更新的:
存储过程(PROCEDURE),是事先经过编译并存储在数据库中的一段SQL语句的集合。调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是很有好处的。
即,存储过程在SQL语言中相当于Java中定义的函数,是对操作的封装。
查看所有存储过程:
show procedure status ;
显示存储过程源码:
show create procedure 存储过程名;
删除存储过程:
drop procedure 存储过程名;
创建存储过程:
语法格式
delimiter 自定义结束符
create procedure 存储过程名(参数列表)
begin
--1.局部变量
--定义局部变量,仅在begin/end块中有效
declare 局部变量名 数据类型;
--给局部变量赋值
set 局部变量名=值;
--2.用户变量
--定义用户变量,当前会话中有效,不需要声明,直接赋值即可
set @用户变量名=值;
--3.系统变量
--系统变量,分为全局变量和会话变量
--全局变量:全局变量在MYSQL启动的时候由服务器自动将他的初始化为默认值,这些默认值可以通过更改my.ini这个文件来更改
--会话变量:会话变量在每次建立一个新的连接的时候,由MYSQL来初始化,MYSQL会将当前所有全局变量的值复制一份,来作为会话变量.也就是说,如果在建立会话以后,没有手动更改过会话变量与全局变量的值,那所有这些变量的值都是一样的.
/*全局变量和会话变量的区别:对全局变量的修改会影响整个服务器,但是对会话变量的修改,
只会影响到当前的会话(也就是当前的数据库连接).有些系统变量的值是可以利用语句来动态进行更改的,
但是有些系统变量的值缺是只读的,对于那些可以更改的系统变量,我们可以利用set语句进行更改*/
--查看全局变量
show global variables;
--查看某全局变量
select @@global.auto_increment_increment;
--设置全局变量
set global sort_buffer_size=40000;
--修改全局变量
set @@global.sort_buffer_size=30000;
--函数体:SQL语句集合
end 自定义结束符
delimiter ;
参数的传递:存储过程一共提供三种参数供使用。
存储过程的调用:
call 存储过程名(参数传递);
实例:
定义一个存储过程,可用来计算不同性别的人数。
(此处使用的是名为mybatis的数据库,存储过程名为fun,且需要定义一个用户变量,作用域为当前会话)
DELIMITER $$
CREATE
PROCEDURE `mybatis`.`fun`(IN s_sex CHAR(1),OUT s_count INT)
-- 存储过程体
BEGIN
-- 把SQL中查询的结果通过INTO赋给变量
SELECT COUNT(*) INTO s_count FROM student WHERE SSex= s_sex;
SELECT s_count;
END$$
DELIMITER ;
call fun('男',@s_count);
select @s_count;
if else,可用于流程控制,进行条件判断。
语法格式:
if 条件表达式1 then
执行语句1;
elseif 条件表达式2 then
执行语句2;
else
执行语句3;
end if;
实例:
DELIMITER $$
create
definer = root@localhost procedure getWeekday(IN day int, OUT res varchar(3))
BEGIN
if `day` = 0 then
set res= '星期天';
elseif `day` = 1 THEN
set res= '星期一';
elseif `day` = 2 THEN
set res= '星期二';
elseif `day` = 3 THEN
set res= '星期三';
elseif `day` = 4 THEN
set res= '星期四';
elseif `day` = 5 THEN
set res= '星期五';
else
set res= '星期六';
END IF;
END$$
DELIMITER;
call getWeekday(week(now()),@week);
select @week as '今天';
case,亦可用于流程控制,共有两种用法。
第一种:
DELIMITER $$
CREATE
PROCEDURE demo(IN num INT)
BEGIN
CASE num -- 条件开始
WHEN 1 THEN
SELECT '输入为1';
WHEN 0 THEN
SELECT '输入为0';
ELSE
SELECT '不是1也不是0';
END CASE; -- 条件结束
END$$
DELIMITER;
第二中:
DELIMITER $$
CREATE
PROCEDURE demo(IN num INT)
BEGIN
CASE -- 条件开始
WHEN num<0 THEN
SELECT '负数';
WHEN num>0 THEN
SELECT '正数';
ELSE
SELECT '不是正数也不是负数';
END CASE; -- 条件结束
END$$
DELIMITER;
DELIMITER $$
CREATE
PROCEDURE demo(IN num INT,OUT SUM INT)
BEGIN
SET SUM = 0;
WHILE num<10 DO -- 循环开始
SET num = num+1;
SET SUM = SUM+num;
END WHILE; -- 循环结束
END$$
DELIMITER;
REPEATE…UNTLL 语句的用法和 Java中的 do…while 语句类似,都是先执行循环操作,再判断条件,区别是REPEATE 表达式值为 false时才执行循环操作,直到表达式值为 true停止。
-- 创建过程
DELIMITER $$
CREATE
PROCEDURE demo(IN num INT,OUT SUM INT)
BEGIN
SET SUM = 0;
REPEAT-- 循环开始
SET num = num+1;
SET SUM = SUM+num ;
UNTIL num>=10
END REPEAT; -- 循环结束
END$$
DELIMITER;
用来重复执行某些语句,执行过程中可用leave(相当于break)、iterate(相当于continue)跳出循环。
DELIMITER $$
CREATE
PROCEDURE demo(IN num INT,OUT SUM INT)
BEGIN
SET SUM = 0;
demo_sum:LOOP-- 循环开始(定义循环的名称)
SET num = num+1;
IF num > 10 THEN
LEAVE demo_sum; -- 结束此次循环
ELSEIF num <= 9 THEN
ITERATE demo_sum; -- 跳过此次循环
END IF;
SET SUM = SUM+num;
END LOOP demo_sum; -- 循环结束
END$$
DELIMITER;
<parameterMap type="savemap" id=“usermap">
" jdbcType=" VARCHAR" mode="IN"/>
" jdbcType=" CHAR" mode="IN"/>
" jdbcType=" VARCHAR" mode="OUT"/>
" parameterMap=" savemap" statementType="CALLABLE">
{call saveuser(?, ?, ?)}
</insert >
游标是一个存储在MySQL服务器上的数据库查询,它相当于是一个指针,可以定位每一条数据,向前定位、向后定位,或随意定位到某一条数据,并对指向的记录中的数据进行操作。
使用步骤:
①声明游标
使用查询语句会将结果集返回给游标、
declare 游标名称 cursor for 查询语句;
②打开游标
打开游标后,查询结果集会送到游标工作区,为后面游标逐条读取数据作准备。
open 游标名称;
③使用游标
使用游标来读取当前行,可以把数据保存到变量中,如果游标读取的数据有多列,可以在into后面赋值给多个变量,且var_name需要在之前就定义好类型,游标查询结果集中的字段数必须和into后面的变量数一样,否则会报错。
fetch 游标名 into 变量1,变量2,...
用一个游标来读取当前行,可以把数据保存到变量中,游标指针指到下一行。
④关闭游标
游标会占用系统资源,所以需要及时关闭,如果没有及时关闭,游标会一直保存到存储过程结束,影响系统运行的效率,关闭游标可以释放游标占用的系统资源。
close 游标名;
例:实现累加薪资最高的几个员工的薪资值,直到薪资总和达到limit_total_salary参数的值,返回累加的人数给total_count。
DELIMITER //
CREATE PROCEDURE get_count_by_limit_total_salary(IN limit_total_salary DOUBLE,OUT total_count INT)
BEGIN
DECLARE sum_salary DOUBLE DEFAULT 0; #记录累加的总工资
DECLARE cursor_salary DOUBLE DEFAULT 0; #记录某一个工资值
DECLARE emp_count INT DEFAULT 0; #记录循环个数
#定义游标
DECLARE emp_cursor CURSOR FOR SELECT salary FROM employees ORDER BY salary DESC;
#打开游标
OPEN emp_cursor;
REPEAT
#使用游标(从游标中获取数据)
FETCH emp_cursor INTO cursor_salary;
SET sum_salary = sum_salary + cursor_salary;
SET emp_count = emp_count + 1;
UNTIL sum_salary >= limit_total_salary
END REPEAT;
SET total_count = emp_count;
#关闭游标
CLOSE emp_cursor;
END //
DELIMITER ;
事务,就是由单独单元的一个或多个sql语句组成,在这个单元中,每个sql语句都是相互依赖的。而整个单独单元是作为一个不可分割的整体存在,类似于物理当中的原子(一种不可分割的最小单位)。
四大特性(ACID):
事务的分类:
查看当前autocommit变量值:
SHOW VARIABLES LIKE 'autocommit';
开启、关闭自动提交:
# 开启自动提交(默认)
SET autocommit = 1;
# 关闭自动提交
SET autocommit = 0;
在自动提交状态下,如果没有显示的开启事务,那每一条语句都是一个事务,系统会自动对每一条 sql 执行 commit 操作。使用 BEGIN 或 START TRANSACTION 开启一个事务之后,自动提交将保持禁用状态,直到使用 COMMIT(提交) 或 ROLLBACK(回滚) 结束事务之后,自动提交模式会恢复到之前的状态。
正常使用事务的流程为:
#步骤一:开启事务
start transaction;
#步骤二:编写事务中的sql语句(insert、update、delete)
sql语句...
#步骤三:结束事务
commit; #提交事务
在DataGrip上进行模拟:
建立test表:
CREATE TABLE `test` (
`id` int(11) NOT NULL
) ENGINE=InnoDB;
INSERT INTO `test` VALUES ('1');
INSERT INTO `test` VALUES ('3');
INSERT INTO `test` VALUES ('5');
在第一个查询控制台执行:
start transaction ;
insert into test value ('2'),('4'),('6');
在第二个查询控制台进行查询:
select * from test;
可见,此时虽然第一个查询控制台执行了SQL语句,但表中数据并未更新,但再次执行:
commit;
此时在第二个查询控制台上即可查询到数据。
回滚:当在编写事务处理SQL语句时,若想要回到事务处理前数据库的状态,就可使用回滚。
rollback;
例:
start transaction ;
insert into test value ('10');
insert into test value ('11');
insert into test value ('12');
rollback ;
commit ;
select * from test;
保留点:上述回滚操作会回滚到当前事务前的状态,可设置保留点,回到指定的位置,例:
BEGIN;
insert into test value ('10') ;
SAVEPOINT s1;
insert into test value ('12') ;
ROLLBACK TO s1; # 回滚到保留点s1,因此10成功写入,12被回滚
RELEASE SAVEPOINT s1; # 释放保留点
事实上,以上事务的并发执行若未采用必要的隔离机制,就会产生各种并发问题,如:
为了避免以上出现的各种并发问题,mysql数据库系统提供了四种事务的隔离级别,用来隔离并发运行各个事务,使得它们相互不受影响,这就是数据库事务的隔离性。
查看当前会话隔离级别:
show variables like 'transaction_isolation';
查看系统隔离级别:
select @@global.transaction_isolation;
设置当前会话的隔离级别:
set session transaction isolation level read uncommitted;
设置数据库系统的全局的隔离级别:
set global transaction isolation level read uncommitted;
默认隔离级别:
JDBC(Java DataBase Connectivity:java数据库连接)是一种用于执行SQL语句的Java API,是java官方提供的一套规范(接口),可以为多种关系型数据库提供统一访问,它是由一组用Java语言编写的类和接口组成的。
事实上,基本上每一种语言都有自己提供给数据库的接口,数据库开发商依据这些接口(规范)来进行完善,并提供自己数据库的服务,例:
好处:
使用JDBC开发用到的包:
包 | 说明 |
---|---|
java.sql | 存在于JDK中,提供所有与JDBC访问数据库相关的接口和类 |
javax.sql | 数据库扩展包,提供数据库额外的功能,如,数据库连接池 |
数据库驱动 | 由各大数据库厂商提供,需要额外下载,是对JDBC接口实现的类,如,MySQL的mysql-connector-java |
Maven导入jar包:
MySQL提供的JDBC实现称为mysql-connector-java,不同的数据库版本需要使用不同的Connector。实际开发时根据数据库版本、JDK版本、选择不同的Connector。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
使用步骤:
案例:
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class MyTest {
@Test
public void test1() throws Exception {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接对象
String url="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=false&serverTimezone=GMT%2B8";
String username="root";
String password="123456";
Connection connection= DriverManager.getConnection(url,username,password);
//3.获取SQL的执行对象
Statement statement=connection.createStatement();
//4.执行SQL语句
String sql="select * from student";
ResultSet resultSet=statement.executeQuery(sql);
//5.处理返回结果
while(resultSet.next()){
//根据列编号获取数据
String SNo=resultSet.getString(1);
String SName=resultSet.getString(2);
String SSex=resultSet.getString(3);
String SBirthday=resultSet.getString(4);
String ClassNo=resultSet.getString(5);
System.out.println("学生学号为:"+SNo+",姓名为:"+SName+",性别为:"+SSex+",生日为:"+SBirthday+",班级号为:"+ClassNo);
}
//6.释放资源
resultSet.close();
statement.close();
}
}
JDBC中定义了各种操作数据库的接口和类型:
接口 | 作用 |
---|---|
Driver | 驱动接口,定义建立链接的方式 |
DriverManager | 工具类,用于管理驱动,可以获取数据库的链接 |
Connection | 表示Java与数据库建立的连接对象(接口) |
Statement | 发送SQL语句的工具(接口) |
PreparedStatement | 发送SQL语句的工具,是Statement的子接口 |
CallableStatement | 是PreparedStatement的子接口,专门用于执行数据库中的存储过程。 |
ResultSet | 结果集接口,用于获取查询语句的结果 |
Driver,数据库驱动,可用于加载驱动、注册驱动:
Class.forName(数据库驱动实现类);
MySQL厂商提供了该实现类,位置为:“com.mysql.cj.jdbc.Driver”,在使用反射机制后,会自动执行相关的静态代码来完成驱动的加载。
源码:
// Driver 接口,所有数据库厂商必须实现的接口,表示这是一个驱动类。
public class Driver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
//注册数据库驱动
}
catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
注:可见,其底层使用的是DriverManager的静态方法。
DriverManager,驱动管理类,位于java.sql包中,主要用于管理和注册驱动、获取数据库连接。
常用方法:
方法名 | 说明 |
---|---|
static void registerDriver(java.sql.Driver driver) | 静态方法,用于注册驱动,其参数即为"com.mysql.cj.jdbc.Driver" |
static Connection getConnection(String url, String user, String password) | 静态方法,用于获取数据库连接并返回连接对象 |
事实上,在之前的案例当中并未使用registerDriver注册数据库驱动,这是因为使用了:
Class.forName("com.mysql.cj.jdbc.Driver");
底层有静态代码块,自动调用了该函数。
对于getConnection方法:
MySQL中URL的写法:
(ip地址可用域名,本地即为localhost,而127.0.0.1即为本地ip地址)
常用参数:
//mysql编码默认为GBK,此参数的作用就是允许插入unicode字符集中的数据,使用UTF-8编码方式进行编码,可用于解决乱码问题.
useUnicode = true&characterEncoding=UTF-8
//SSL是高版本Mysql提供的数据加密、安全保障的新协议,为了向下兼容所以设置为false关闭此协议.
useSSL=false
//为了确保日期类型数据能够正确存储,需要指定时区为上海时区(上海时区与北京一致),默认为美国时区。
serverTimezone=Asia/Shanghai
常用数据库的URL写法:
注:MySQL 5之后的驱动包,可以省略注册驱动的步骤,因为其会自动加载jar包中的META-INF/services/java.sql.Driver文件中的驱动类。
Connection,数据库连接对象,可以通过对象DriverManager或者DataSource(数据库连接池)中的getConnection()方法拿到Connection对象,获取到数据库链接,有以下功能:
对于事务管理:
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接对象
String url="jdbc:mysql://127.0.0.1:3306/student_course?useSSL=false&serverTimezone=GMT%2B8";
String username="root";
String password="123456";
Connection connection=DriverManager.getConnection(url,username,password);
//获取SQL执行对象
Statement statement= connection.createStatement();
try {
//开启事务
connection.setAutoCommit(false);
//编写并执行SQL语句
String sql1="update Student set SAge=SAge+1 where Sno like '1001';";
statement.executeUpdate(sql1);
String sql2="update Student set SAge=SAge-1 where Sno like '1002';";
statement.executeUpdate(sql2);
connection.commit();
} catch (Exception e) {
//回滚事务
connection.rollback();
e.printStackTrace();
}
//释放资源
statement.close();
connection.close();
Statement,是SQL语句的执行对象,常用方法:
方法声明 | 作用 |
---|---|
int executeUpdate(String sql) | 可执行增、删、改的SQL语句,返回受执行影响到的行数。 |
ResultSet executeQuery(String sql) | 执行SQL查询语句,并返回ResultSet对象。 |
boolean execute(String sql) | 可执行任何SQL语句,返回一个布尔值,表示是否返回ResultSet对象,只有执行查询时才为true。 |
void close() | 用于释放执行对象资源 |
PreparedStatement,是Statement的子接口,是预编译的SQL语句的对象,可使用占位符,且,PreparedStatement是预编译的,其批处理效率要高于Statement。
关于预编译:
对于Statement对象,若要执行两条SQL语句:
select SName from student where SNo like '101';
select SName from student where SNo like '102';
这会生成两个执行计划,而PreparedStatement可使用绑定变量重用执行计划,即,将:
select SName from student where SNo like ;
绑定到一个变量上,使得之后再使用相似语句时不用多次重复编译,而只需传入参数即可,当然,在对数据库只执行一次性存取的时侯,用 Statement 对象进行处理。PreparedStatement对象的开销比Statement大,对于一次性操作并不会带来额外的好处(不同的SQL语句需创建多个PreparedStatement对象)。
即:无论多少次地使用同一个SQL命令,PreparedStatement都只对它解析和编译一次,而当使用Statement对象时,每次执行一个SQL命令时,都会对它进行解析和编译。这就使得preparedstatement支持批处理,且此时性能优于Statement对象。
开启预编译功能:
事实上,我们可手动开启PreparedStatement对象的预编译功能,需要在URL中添加参数(因为检查、预编译是由数据库完成的):
useServerPrepStmts=true
使用方式:
通过Connection对象来获取:
PreparedStatement prepareStatement(String sql)
可见,PreparedStatement对象需要先传入SQL语句来进行预编译,在明确sql语句的格式后,就不会改变了,剩余的内容都会认为是参数。
而参数的传入,使用的是占位符"?",可使用:
setXxx(参数1,参数2)
//参数1:?的位置编号,从1开始.
//参数2:?的实际传入参数.
//Xxx:?传入参数的数据类型
例:
//3.获取SQL的执行对象
String sql="select * from score where SNo = ? and ID = ? ";
PreparedStatement prepareStatement=connection.prepareStatement(sql);
prepareStatement.setString(1,"103");
prepareStatement.setInt(2,4);
//4.执行SQL语句
ResultSet resultSet=prepareStatement.executeQuery();
常用方法:
方法声明 | 作用 |
---|---|
int executeUpdate() | 可执行增、删、改的SQL语句,返回受执行影响到的行数。 |
ResultSet executeQuery() | 执行SQL查询语句,并返回ResultSet对象。 |
boolean execute() | 可执行任何SQL语句,返回一个布尔值,表示是否返回ResultSet对象,只有执行查询时才为true。 |
void close() | 用于释放执行对象资源 |
注:因为PreparedStatement对象在创建时就指定了SQL语句,故而,执行时不需要再传递参数。
CallableStatement,存储过程执行者对象,是PreparedStatement的子接口,可用于调用存储在数据库当中的存储过程,同时也具有对PreparedStatement接口输入参数功能的支持。
使用方式:
CallableStatement,通过 Connection 对象的方法 prepareCall 创建,需要指定执行语句的String字符串,语法格式为:
CallableStatement callableStatement= connection.prepareCall("{call 存储过程名(占位符1,占位符2,...)}");
通过使用占位符来传递IN、OUT和INOUT参数,对于IN类型参数的设置同样使用PreparedStatement中的setXxx方法,而OUT参数的设置需要使用CallableStatement中的registerOutParameter(int parameterIndex, int sqlType)来进行设置(用于注册OUT参数的JDBC类型),再通过getXxx来获取输出值(将参数从JDBC类型转换为Java类型),例如,现有存储过程getCountBySex,即传入性别字符串来统计人数:
create
definer = root@localhost procedure getCountBySex(IN s_sex char, OUT s_count int)
BEGIN
-- 把SQL中查询的结果通过INTO赋给变量
SELECT COUNT(*) INTO s_count FROM student WHERE SSex= s_sex;
END;
Java代码:
//获取存储过程的执行对象
CallableStatement callableStatement= connection.prepareCall("{call getCountBySex(?,?)}");
//设置IN类型参数
callableStatement.setString(1,"男");
//设置OUT类型参数
callableStatement.registerOutParameter(2, Types.TINYINT);
//获取返回值,使用getXxx方法
int count=callableStatement.getInt(2);
System.out.println(count);
//释放资源
callableStatement.close();
对于INOUT类型的参数,则既需要使用setXxx进行传入,也需要使用registerOutParameter进行注册,再通过getXxx来获取输出值。
SQL注入,是web应用程序对用户输入数据的合法性判断不严格,攻击者可以在web应用程序中事先定义好的查询语句结尾加上额外的SQL语句,使得在管理员不知情的情况下事先非法操作,实现欺骗数据库执行非授权的任意查询,从而进一步得到相应的数据信息
SQL注入的演示:
show databases ;
create database MyDatabase;
use MyDatabase;
create table tb_user(
id int,
username varchar(20),
password varchar(32)
);
insert into tb_user values (1,'zhangsan','123'),(2,'lisi','234');
select * from tb_user;
后端代码:
package com.example.jdbcmysql;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
@SpringBootApplication
public class JdbcMySqlApplication {
public static void main(String[] args) throws Exception {
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接对象
String url="jdbc:mysql://127.0.0.1:3306/MyDatabase?useSSL=false&serverTimezone=GMT%2B8";
String username="root";
String password="123456";
Connection connection=DriverManager.getConnection(url,username,password);
//获取SQL执行对象
Statement statement= connection.createStatement();
String name="zhangsan";
String pwd="123";
String sql="select * from tb_user where username='"+name+"'and password ='"+pwd+"'";
ResultSet resultSet=statement.executeQuery(sql);
//若结果集不为空,即为登录成功
if(resultSet.next()){
System.out.printf("登录成功");
}else {
System.out.printf("登录失败");
}
resultSet.close();
statement.close();
connection.close();
}
}
//获取SQL执行对象
Statement statement= connection.createStatement();
String name="cascascwa";
String pwd="' or '1' = '1";
String sql="select * from tb_user where username='"+name+"'and password ='"+pwd+"'";
ResultSet resultSet=statement.executeQuery(sql);
这是因为,Statement对象在执行SQL语句和前端传递来的参数时,底层实际上是字符串的拼接,将以上代码拼接后即可得到:
select * from tb_user where username='cascascwa'and password ='' or '1' = '1'
而这是永真的。
使用PreparedStatement对象预防SQL注入:
SQL注入的核心是拼接字符串而改变了原先的SQL语句,而PreparedStatement是先将SQL语句框架编译,并用反斜杠的方式来避免此问题.
//获取SQL执行对象
Statement statement= connection.createStatement();
String name="zhangsan";
String pwd="' or '1' = '1";
String sql="select * from tb_user where username = ? and password = ?";
//获取PreparedStatement对象
PreparedStatement preparedStatement=connection.prepareStatement(sql);
//设置?的值
//给第一个?设置值,并指定参数类型
preparedStatement.setString(1,name);
//给第二个?设置值,并指定参数类型
preparedStatement.setString(2,pwd);
# 配置MySQL执行日志
log-output=FILE
general-log=1
general_log_file="D:\MySQL\mysql.log"
slow-query-log=1
slow_query_log_file="D:\MySQL\mysql_slow.log"
long_query_time=2
ResultSet,结果集对象,用于接收查询操作所返回的结果集,常用方法有:
方法声明 | 作用 |
---|---|
boolean next() | 若当前游标的指向有数据,则返回true,并下移一行,否则返回false |
xx getXx(int index) | 获取该行某个字段的数据,index为该字段的索引编号,从1开始,xx则表示该列的数据类型。 |
xx getXx(String name) | 根据字段名获取该行某个字段的数据,xx则表示该列的数据类型。 |
void close() | 释放结果集对象资源 |
JDBC编程中,每次创建和断开Connection对象都会消耗一定的时间和IO资源。这是因为在Java程序与数据库之间建立连接时,数据库端要验证用户名和密码,并且要为这个连接分配资源,Java程序则要把代表连接的java.sql.Connection对象等加载到内存中,所以建立数据库连接的开销很大。尤其是在大量的并发访问时,假如某网站一天的访问量是10万,那么,该网站的服务器就需要创建、断开连接10万次,频繁地创建、断开数据库连接势必会影响数据库的访问效率,甚至导致数据库崩溃。为了避免频繁地创建数据库连接,工程师们提出了数据库连接池技术。
数据库连接池是一个容器,负责分配、管理数据库连接,可以提前申请多个数据库连接,当用户需要时从中取出即可,当不需要时重新归还给数据库连接池即可,从而实现了资源复用,也不需要避免不断地申请、释放。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池的请求连接数目大于最大连接数时,这些请求将被加入等待队列中。
好处:
Connection getConnection(String username, String password)
常见的数据库连接池:
Druid数据库连接池:
Druid连接池是由阿里巴巴开源的数据库连接池项目,功能强大,性能优秀,是Java语言最好的数据库连接池之一。
基本配置参数:
1.导入依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
2.定义配置文件:
jdbc.driverClassName=com.mysql.cj.jdbc.Driver;
url=jdbc:mysql://127.0.0.1:3306/MyDatabase?useSSL=false&serverTimezone=GMT%2B8&useServerPrepStmts=true&characterEncoding=utf-8&useUnicode=true
username=root
password=nishiweiyi456
#初始化连接数量
initialSize=5
#最大连接数
maxActive=10
#最大等待时间,此处为3s
maxWait=3000
3.获取连接池对象:
package com.example.jdbcmysql;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
public class Druid {
public static void main(String[] args) throws Exception{
//加载配置文件
Properties properties=new Properties();
properties.load(new FileInputStream("D:/IntelliJ IDEA/JDBCMySQL/src/main/resources/druid.properties"));
//获取连接池对象
DataSource dataSource= DruidDataSourceFactory.createDataSource(properties);
//获取数据库连接
Connection connection= dataSource.getConnection();
System.out.println(connection);
}
}
先留个坑,还是以考研为主,且要先复习完数据结构,对于所有和JDBC源码的解析有时间会补上。
最后,提供一些创建数据库表的案例,方便以后使用:
学生表:
CREATE TABLE IF NOT EXISTS `student`(
`id` INT(4) NOT NULL AUTO_INCREMENT COMMENT '学号',
`name` VARCHAR(30) NOT NULL DEFAULT '匿名' COMMENT '姓名',
`pwd` VARCHAR(20) NOT NULL DEFAULT '123456' COMMENT '密码',
`sex` VARCHAR(2) NOT NULL DEFAULT '女' COMMENT '性别',
`birthday` DATETIME DEFAULT NULL COMMENT '出生日期',
`address` VARCHAR(100) DEFAULT NULL COMMENT '家庭住址',
`email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
show columns from student;
学生表:
create database student_course;
show databases;
use student_course;
create table Student(Sno char(9) primary key,SName char(20) not null,SSex char(2),SAge smallint,SDept char(20));
create table Course(Cno char(4) primary key,CName char(40) not null,CPno char(4) references Course(Cno),CCredit smallint);
create table SC(Sno char(9),Cno char(4),Grade smallint,primary key(Sno,Cno),foreign key(Sno) references Student(Sno),foreign key (Cno) references Course(Cno));
insert into Student(Sno,Sname,Ssex,Sage,Sdept)values('201215121','李勇','男','20','CS');
insert into Student(Sno,Sname,Ssex,Sage,Sdept)values('201215122','刘晨','女','19','CS');
insert into Student(Sno,Sname,Ssex,Sage,Sdept)values('201215123','王敏','女','18','MA');
insert into Student(Sno,Sname,Ssex,Sage,Sdept)values('201215125','张立','男','19','IS');
insert into Course(Cno,Cname,Cpno,Ccredit)values('1','数据库','5','4');
insert into Course(Cno,Cname,Cpno,Ccredit)values('2','数学','null','2');
insert into Course(Cno,Cname,Cpno,Ccredit)values('3','信息系统','1','4');
insert into Course(Cno,Cname,Cpno,Ccredit)values('4','操作系统','6','3');
insert into Course(Cno,Cname,Cpno,Ccredit)values('5','数据结构','7','4');
insert into Course(Cno,Cname,Cpno,Ccredit)values('6','数据处理','null','2');
insert into Course(Cno,Cname,Cpno,Ccredit)values('7','C语言','6','4');
insert into SC(Sno,Cno,Grade)values('201215121','1','92');
insert into SC(Sno,Cno,Grade)values('201215121','2','85');
insert into SC(Sno,Cno,Grade)values('201215121','3','88');
insert into SC(Sno,Cno,Grade)values('201215122','2','90');
insert into SC(Sno,Cno,Grade)values('201215122','3','80');
学生、教师、课程、分数表
create schema student_course;
CREATE TABLE `student` (
`SNo` varchar(20) NOT NULL COMMENT '学号(主码)',
`SName` varchar(20) NOT NULL COMMENT '学生姓名',
`SSex` varchar(20) NOT NULL COMMENT '学生性别',
`SBirthday` datetime DEFAULT NULL COMMENT '学生出生年月',
`ClassNo` varchar(20) DEFAULT NULL COMMENT '学生所在班级',
PRIMARY KEY (`SNo`),
KEY `student_fk_class` (`ClassNo`),
CONSTRAINT `student_fk_class` FOREIGN KEY (`ClassNo`) REFERENCES `class` (`CLNo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
CREATE TABLE `course` (
`CNo` varchar(20) NOT NULL COMMENT '课程号(主码)',
`CName` varchar(20) NOT NULL COMMENT '课程名称',
`TNo` varchar(20) NOT NULL COMMENT '教工编号',
PRIMARY KEY (`CNo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
CREATE TABLE `score` (
`ID` int NOT NULL AUTO_INCREMENT COMMENT '主键自增',
`SNo` varchar(20) NOT NULL COMMENT '学号',
`CNo` varchar(20) NOT NULL COMMENT '课程号',
`Degree` decimal(4,1) DEFAULT NULL COMMENT '成绩',
PRIMARY KEY (`ID`),
KEY `score_fk_student` (`SNo`),
KEY `score_fk_course` (`CNo`),
CONSTRAINT `score_fk_course` FOREIGN KEY (`CNo`) REFERENCES `course` (`CNo`),
CONSTRAINT `score_fk_student` FOREIGN KEY (`SNo`) REFERENCES `student` (`SNo`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
CREATE TABLE `teacher` (
`TNo` varchar(20) NOT NULL COMMENT '教工编号(主码)',
`TName` varchar(20) NOT NULL COMMENT '教工姓名',
`TSex` varchar(20) NOT NULL COMMENT '教工性别',
`TBirthday` datetime DEFAULT NULL COMMENT '教工出生年月',
`Prof` varchar(20) DEFAULT NULL COMMENT '职称',
`Depart` varchar(20) NOT NULL COMMENT '教工所在部门',
`ClassNo` varchar(20) DEFAULT NULL COMMENT '班级编号',
PRIMARY KEY (`TNo`),
KEY `teacher_fk_class` (`ClassNo`),
CONSTRAINT `teacher_fk_class` FOREIGN KEY (`ClassNo`) REFERENCES `class` (`CLNo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
CREATE TABLE `class` (
`CLNo` varchar(20) NOT NULL COMMENT '班级序号',
`TNo` varchar(20) NOT NULL COMMENT '教工编号',
PRIMARY KEY (`CLNo`),
KEY `class_fk_teacher` (`TNo`),
CONSTRAINT `class_fk_teacher` FOREIGN KEY (`TNo`) REFERENCES `teacher` (`TNo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='班级表'COLLATE=utf8mb4_0900_ai_ci
insert into student values
('108','曾华','男','1977-09-11','95033'),
('105','匡明','男','1978-08-01','95031'),
('107','王丽','女','1979-06-15','95033'),
('101','李军','男','1976-07-21','95033'),
('109','王芳','女','1977-03-30','95031'),
('103','陆君','男','1977-12-16','95031');
insert into course values
('3-105','计算机导论','825'),
('3-245','操作系统','804'),
('6-166','数字电路','856'),
('9-888','高等数学','831');
insert into score(sno,cno,degree) values
('103','3-245','86'),
('105','3-245','75'),
('109','3-245','68'),
('103','3-105','92'),
('105','3-105','88'),
('109','3-105','76'),
('101','3-105','64'),
('107','3-105','91'),
('108','3-105','78'),
('101','6-166','85'),
('107','6-166','79'),
('108','6-166','81');
insert into teacher values
('804','李诚','男','1958-12-02','副教授','计算机系','95033'),
('856','张旭','男','1969-03-12','讲师','电子工程系'),
('825','王萍','女','1972-05-05','助教','计算机系'),
('831','刘冰','女','1977-08-14','助教','电子工程系','95031');
insert into class values
('95033','804'),
('95031','831');