存放数据的仓库,用来按照特定的结构去组织和管理我们的数据,有数据库就可以更加方便的储存数据,英文名:DataBase,简称:DB.
数据库只是存储数据的一种手段,最终数据是存放在硬盘中的,只是存放的格式不同于文本文件。
Oracle: 收费的大型数据库,Oracle 公司的产品。Oracle 收购 SUN 公司,收购 MYSQL
DB2 : IBM 公司的数据库产品,收费的。常应用在银行系统中.
SQLServer: MicroSoft 公司收费的中型的数据车。C#、.net 等语言常使用。
SyBase:已经淡出历史舞台。提供了一个非常专业数据建模的工具 PowerDesigner。
SQLite:嵌入式的小型数据库,应用在手机端。
常用数据库:MYSQL,Oracle
在 web 应用中,使用的最多的就是 MySQL 数据库,原因如下:
1)开源、免费
2)功能足够强大,足以应付 web 应用开发(最高支持千万级别的并发访问)
windows安装MySQL很简单,可以参考阅读:https://blog.csdn.net/weixin_46758988/article/details/116368575
SQL全称Structured Query Language,意为结构化查询语言。说白了就是定义了操作所有关系型数据库的语言。每一种数据库的操作存在一定的差异,这个差异可以称为方言。
1.SQL语句可以单行或多行书写,以分号结尾。
-- 单行sql,查询数据库:
SHOW DATABASES;
# 多行sql:
SELECT *
FROM user;
2.SQL语句不区分大小写,但是关键字建议大写。
show databases;
SHOW databases;
3.注释:
-- 单行注释,横线后面空格不能省略
# 单行注释,空格可省略
/*多行注释*/
sql可以大致分四类:
1.DDL:操作数据库和表,如:
#操作数据库:
# 1.C(Create)创建
CREATE DATABASE db1;#创建一个db1数据库
CREATE DATABASE IF NOT EXISTS db2;#如果没有数据库db2就创建一个,否则不创建
CREATE DATABASE IF NOT EXISTS db3 CHARACTER SET gbk;#如果没有数据库db3就创建一个,否则不创建,创建时并指定字符编码为gbk
CREATE DATABASE db4 CHARACTER SET gbk;#创建一个数据库db4并指定字符编码集为gbk
# 2.R(Retrieve)查询
SHOW DATABASES;#查看数据
SHOW CREATE DATABASE mysql;#查数据库mysql的创建信息(创建字符集,创建语句)
# 3.U(Update)修改
ALTER DATABASE db3 CHARACTER SET utf8;#修改db3数据库字符编码集为utf-8,且记这里是utf8
# 4.D(Delete)删除
DROP DATABASE db2;#删除数据库db2,谨慎删除。
DROP DATABASE IF EXISTS db1;#如果数据库db1存在就删除
# 5.使用数据库
SELECT DATABASE();#查看当前正在使用的数据库
USE db1;#使用数据库db1
# 操作表:
# 1.新增
CREATE TABLE user_root(id INT, age INT); #给当前数据库新增一个user_root表,并给id和age两个字段,每个字段都需要指定类型
CREATE TABLE stu LIKE user_root; #复制user_root表并命名为stu(创建一个表,让它跟user_root表一样)
ALTER TABLE users ADD nickname VARCHAR(10); #给users表新增一列nickname,数据类型为可变字符varchar10个
# 2.查询
SHOW TABLES; # 查询当前使用的数据库中的所有表名称
SHOW TABLES FROM db1; # 查询数据库db1中所有的表
DESC user_tab; # 查询user_tab表结构
SHOW CREATE TABLE users; #查询users表的结构信息
# 3.删除
DROP TABLE stu; #删除stu表
DROP TABLE IF EXISTS user_root; #如果user_root表存在就删除
ALTER TABLE users DROP sex; #删除users表中的sex字段
# 4.修改
ALTER TABLE user_root RENAME TO users; #将user_root表名称修改为users;
ALTER TABLE users CHARACTER SET utf8; #修改users表的字符集为utf-8编码
ALTER TABLE users CHANGE gender sex INT; #修改users表中gender字段为sex字段并设置类型为int
ALTER TABLE users MODIFY sex VARCHAR(10); #修改users表中sex字段数据类型为varchar长度为10
DML:操作表中的数据,对表中数据进行增删改
# 1.新增数据
INSERT INTO users(id,age,nickname) VALUES (0,12,'苦海123'); #给users表中新增一条数据,全部字段给值(0,12,'苦海123')可以在后面继续用逗号隔开,如:(0,12,'苦海123'),(1,22,'123')表示一次插入多条数据
INSERT INTO users(id,nickname) VALUES (0,'苦海123'); #给users表中新增一条数据,只给id和nickname
INSERT INTO users VALUES (0,15,'苦海123');#给users表中新增一条数据,如果给所有字段都给值,那么前面的字段可以省略,直接给值
# 2.删除数据
DELETE FROM users;#删除表中所有数据,不推荐这种方式,因为很耗费性能,可以使用下面方式代替删除表中所有数据,如:
TRUNCATE TABLE users; #表示删除users表(表被删除了,数据也就没了),再创建一个一模一样的表
DELETE FROM users WHERE id = 1; #删除users表中id为1的数据
DELETE FROM users WHERE id IN (2,3);#删除users表中id存在后面括号中的值的数据
# 3.修改数据
UPDATE users SET age=15,nickname='kuhai123'; #修改users表中所有数据的age字段值为15,nickname值为kuhai123
UPDATE users SET age=18,nickname='kuhai123' WHERE id = 1;#修改users表中id为1的数据age字段值为18,nickname值为kuhai123
SQL:查询表中的数据
查询数据的语句结构:select 字段 from 表名 where 筛选条件 group bu 分组字段 having 分组之后的条件 order by 排序 limit 分页限定
基础查询:
SELECT * FROM student; #查询student表中所有字段对应的值
SELECT nams,age FROM student; #查询student表中nams和age字段对应的数据
SELECT DISTINCT address FROM student; #查询student表中address字段对应的数据并去重,这里的字段可以写多个,也可以写*,但是业务一般都是查一个字段,比如学生都来自哪里,DISTINCT表示去重
SELECT nams,math,english, math + english AS totalsco FROM student; #查询student表中的nams、math、math + english求和重命名为totalsco展示,AS可以用空格代替
SELECT nams,math + english FROM student; #查询nams字段、math + english求和并作为math + english字段展示所有数据
SELECT nams,math + IFNULL(english,0) FROM student;#求和的过程中可能会出现某个字段对应的值为null,此时可以使用IFNULL函数替换,这个函数第一个值表示字段,第二个值表示要替换的数值
SELECT * FROM student WHERE id <> 1;#查询student表中id不等于1的值,在sql中不等于用<>或!=表示
SELECT * FROM student WHERE id BETWEEN 2 AND 5;#查询student表中id在2和5之期间的数据,这里2和5都是可以取到的。
SELECT * FROM student WHERE id IN (1,2,5);#查询student表中id包含在1,2,5中的数据
SELECT * FROM student WHERE nams LIKE '%李%';#查询student中nams字段包含李的数据,这里是模糊查询,其中百分号表示模糊匹配,李字前后都写百分号表示包含李字都会被匹配到,如果只在李字后面写,则只会匹配李字开头的
SELE CT * FROM student WHERE nams LIKE '李%';#查询到student表中nams字段值李字开头的数据
SELECT * FROM student WHERE nams LIKE '%李';#匹配到student表中nams字段值李字结尾的所有数据
SELECT * FROM student WHERE nams LIKE '%华_';#匹配到student表中nams字段值华后面一个字的数据,其中下划线表示占1个字符
SELECT * FROM student WHERE nams LIKE '__';#匹配到student表中nams字段值为2个字符的数据,其中下划线表示占位,一个下划线表示占一个字符串的位置
SELECT * FROM student WHERE english IS NULL;#查询student表中englis字段值为null的数据,这里不能使用=做判断,也不可以使用!=判断不为null的数据,判断不是NULL用IS NOT NUll;
SELECT * FROM student WHERE math > 90 AND english > 90;#查询student表中math和english都大于90的数据,这里的AND也可以使用&&代替,如:
SELECT * FROM student WHERE math > 90 && english > 90;
SELECT * FROM student WHERE math > 90 OR english > 90;#查询student表中math大于90或english大于90的数据,这里的OR也可以使用||代替,如:
SELECT * FROM student WHERE math > 90 || english > 90;
SELECT * FROM student ORDER BY math DESC, english DESC;#按照math字段对数据进行降序排序,如果math相同则再按照english字段降序排序,排序通过order by实现,多个条件用逗号隔开
聚合函数:将一列数据作为一个整体,进行纵向计算=-
SELECT COUNT(nams) FROM student;#统计student表中有多少条数据,count用来计算一个表中有多少数据,括号里面一般给一个字段,也可以给星号,但是不推荐,因为查星号毫无意义,需要强调的是count统计的是非null的值,所有如果nams字段对应的值有为null的数据,那么统计的结果并不等于表中所有数据的数量
SELECT MAX(math) FROM student;#查询student表中math字段对应的最大值
SELECT MIN(math) FROM student;#查询student表中math字段对应的最小值
SELECT AVG(math) FROM student;#查询student表中math字段对应的平均值
SELECT SUM(math) FROM student;#查询student表中math字段对应的和
分组查询:将一类属性相同的数据分成一组然后做统计,分组查询的字段必须是分组字段或者聚合函数,其他值都没有任何意义
SELECT sex,AVG(math),SUM(math),COUNT(id) FROM student GROUP BY sex;#统计student表中math字段对应值按sex字段分类,各类数据的平均值,求和,个数统计(一句话:统计student表中男女的平均值,求和,个数统计)
SELECT sex,AVG(english),SUM(english),COUNT(id) FROM student WHERE english > 60 GROUP BY sex;#统计student表中english值大于60的数据,english字段对应值按sex字段分类,各类数据的平均值,求和,个数统计
SELECT sex,AVG(english),SUM(english),COUNT(id) FROM student WHERE english > 60 GROUP BY sex HAVING COUNT(id) >2;#查询student表中english大于60的数据并按sex分组统计平均值,求和,统计个数,并留下分组后个数大于2的分组,需要注意的是where在分组之前限定,而having在分组后进行限定,where后不能跟聚合函数,而having后可以跟聚合函数
SELECT * FROM student LIMIT 2,4;#表示越过2条查询4条数据,limit在mysql数据库中做分页查询,limit在SQLServer等数据库中不支持。
约束:
约束指对表中的数据进行限定,保证数据的正确性,有效性和完整性。约束可分为:主键约束primary key、非空约束not null、唯一约束unique、外键约束foreign key
#非空约束:值不能为null
CREATE TABLE stu(id INT, nams VARCHAR(20) NOT NULL); #创建一个stu表,对nams字段进行约束不能为null
ALTER TABLE stu MODIFY nams VARCHAR(20);#当一个表中字段被进行了约束,可以通过此方法对其重新定义字段约束(取消某种约束,唯一约束、主键约束、除外)
#唯一约束:不能有重复的值出现
ALTER TABLE stu MODIFY id INT UNIQUE;#对stu表中id字段进行唯一约束,使其字段的值不能重复,此约束和上面一样,在创建时也可以添加,任意约束都可以在创建和修改中添加,只需要用空格隔开,null值不划分到重复中,需要注意。
ALTER TABLE stu DROP INDEX id;#取消对stu表中id键的唯一约束,这里需要注意,主键不能通过修改取消。
#主键约束:值非空且唯一,主键一张表只能有一个,主键是表中唯一标识
ALTER TABLE stu MODIFY id INT PRIMARY KEY;#修改stu表中id为主键,在创建的时候也可以指定主键
ALTER TABLE stu DROP PRIMARY KEY; #删除主键
ALTER TABLE stu MODIFY id INT PRIMARY KEY AUTO_INCREMENT;#如果一个表中主键是唯一且是int类型,此时可以给主键加AUTO_INCREMENT,使其自动递增,当然可以在新增数据的时候可以指定值,指定值后,后面的值会在新的基础上增加1
ALTER TABLE stu MODIFY id INT;#删除自动递增
#外键约束:当两个表中有关联时,如:员工表中地址关联pid和地址表中id做关联时,如果员工表中有数据关联了地址表中数据,此时如果删除了地址表中被关联的数据就会用问题,此时可以借助外键约束来解决这个问题,当对某个表做外键约束时,关联的表中数据被删除时,如果这个关联的表中被删除的数据被有外键约束的表所引用,那么就提示不能被删除,也删除不掉,插入数据时,如果外键关联的数据不存在,则也添加不成,但是可以为NULL
CREATE TABLE emplay (id INT PRIMARY KEY AUTO_INCREMENT, nams VARCHAR(30),dep_id INT, CONSTRAINT emp_dep_id FOREIGN KEY (dep_id) REFERENCES department(id));#创建表时做外键约束,创建一个emplay表,CONSTRAINT表示限制,限制名定义称为了emp_dep_id,FOREIGN KEY表示外键为emplay表中的dep_id,REFERENCES表示引用为department表中的主键id,一般都是关联主键id。
ALTER TABLE emplay DROP FOREIGN KEY emp_dep_id; #删除emplay表的外键约束emp_dep_id
ALTER TABLE emplay ADD CONSTRAINT emp_dep_id FOREIGN KEY (dep_id) REFERENCES department(id);#给已经创建好的表添加外键约束
级联操作:当一个表有外键约束时,所引用的表中主键被修改时,此时所关联的数据就会发生错乱,当然这也是不能被修改的,如果想要修改,可以将表中关联id设置为NULL,之后在修改应用表中的id,然后将表中为NULL的值再改成新的id,当然这是比较麻烦且很笨的方式, 此时就可以借助级联操作。
如果想要清楚的看到表之间的关联关系,可以借助sqlyog中的架构设计器,可以清除得看到各表间用线连接的视图。
ALTER TABLE emplay ADD CONSTRAINT emp_dep_id FOREIGN KEY (dep_id) REFERENCES department(id) ON UPDATE CASCADE;#如果想要在修改了关联表中数据id,数据表中也同步自动更改,可以在创建表,外键时添加N UPDATE CASCADE,表示自动同步更新关联id
ALTER TABLE emplay ADD CONSTRAINT emp_dep_id FOREIGN KEY (dep_id) REFERENCES department(id) ON DELETE CASCADE;#删除级联
#级联比较耗性能,而且不便于操作,使用需谨慎。
DCL:授权,是否有权操作数据库,可以对用户进行管理,授权是指对某一个用户进行权限的处理,此时就要操作mysql这个数据库:
1.管理用户:
USE mysql;#切换到mysql数据库:
SELECT * FROM USER;#查询user表:
#2.创建用户:
CREATE USER 'kuhai123'@'localhost' IDENTIFIED BY '123456';#CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';这里创建的用户指定了主机,如果想要用户在任意的主机上面都能访问数据库,那么就要将localhost改为百分号%,表示通配符。
#3.删除用户:
DROP USER 'kuhai123'@'localhost';#DROP USER '用户名'@'主机名';
#4.修改用户信息:(修改用户的密码)
UPDATE USER SET PASSWORD = PASSWORD('sfs123') WHERE USER = 'kuhai123';#PASSWORD函数可以对密码进行加密处理,这里可能应版本问题导致报错,此时可以使用:
ALTER USER 'kuhai123'@'localhost' IDENTIFIED WITH mysql_native_password BY '123123123fgv';#其中kuhai123为用户名,localhost为主机名,123123123fgv为新密码。
修改root用户密码:
#修改root用户的密码:想要修改其他用户的密码,首先你得知道root用户的密码,但是有的时候难免密码是会被忘记的,此时可以使用以下方式修改密码:
#1.以管理员身份进入cmd终端:
net stop mysql #停止mysql服务
#2.使用无验证方式启动mysql服务:
mysqld --skip-grant-tables #终端继续执行此命令无验证开启mysql服务,这里可能开启失败,导致后面无法进入,此时可以尝试使用命令:mysqld --console --skip-grant-tables --shared-memory 无验证开启myslq服务
#3.当执行完第二步后,此时光标会卡在终端,此时新打开一个终端(无需管理员身份),继续输入命令:mysql -uroot -p,此时会提示输入密码,直接回车无需输入
#4.回车后提示进入mysql的提示语,此时表示进入mysql数据库成功。
#5.切换数据:use mysql;
#6.修改新密码:alter user root@localhost identified by '123456'; 此时可能提示修改失败,那么就需要先刷新一下权限:flush privileges; 然后在执行:alter user root@localhost identified by '123456'; 之后会提示修改成功的提示语。
#7.关闭两个终端即可。
#8.以管理员权限打开终端输入命令启动mysql服务:net start MySQL
#9.终端继续使用命令:mysql -uroot -p回车输入新的密码登录即可,SQLyog用新密码登录root账户可能会失败,失败请参考博客 https://blog.csdn.net/weixin_46758988/article/details/116368575 中MySQL数据可视化工具部分。
2.权限管理:
#1.查询权限:
SHOW GRANTS FOR 'kuhai123'@'localhost';#SHOW GRANTS FOR '用户名'@'主机名';其中权限分别表示:USAGE(表示只能登录,使用其它数据库是不可以的)、SELECT(查询权限)
#2.授予权限:
GRANT SELECT ON db3.account TO 'kuhai123'@'localhost';#给localhost主机下的kuhai123用户授予db3数据库中account表的查询权限,此时使用kuhai123登录数据库后,只能看到db3一个数据库,且此数据库中只能看到一个account表,且只能查看数据,不能对数据修改删除等操作
如果想要授权多个权限,则用逗号隔开即可,如:GRANT SELECT,DELETE,UPDATE,INSERT ON db3.account TO 'kuhai123'@'localhost';
给某个用户一次性授权所有数据库和所有表:GRANT ALL ON *.* TO 'kuhai'@'localhost';#如果刚开始已经单个授予某些表,那么使用此方式授权会有部分库和表授权不上的情况。新用户一次性授权可授权全部库和表。
#3.撤销权限:
REVOKE SELECT ON db3.account FROM 'kuhai123'@'localhost'; #撤销localhost主机下kuhai123用户表对db3数据库下account表的查询权限。
REVOKE ALL ON *.* FROM 'kuhai'@'localhost';#撤销localhost主机下kuahi用户的所有数据库和表的权限,注意,系统的部分数据库是无法撤销的如:information_schena
设计数据库时,需要遵循的一些规范被称为数据库设计的范式。它分为三类,分别是:
第一范式:每一列都是不可分割的原子数据项,意思就是说每个字段只能存一种数据,如:姓名和性别推荐存两个字段,不推荐存一个字段。
第二范式:在第一范式的基础上,非码属性必须完全依赖于候选码,分解理解:1.函数依赖:如果通过a属性的值可以确定唯一b的属性值,则称b依赖于a,如:通过学号可以确定姓名。2.完全函数依赖:如果a是一个属性组(学号+课程名称)确定一个属性值(该课程学分)3.部分函数依赖: 如果a是一个属性组,b属性的确定只需要依赖于a属性组中某个属性值即可,如:学号+课程名称属性组中,只通过学号就可以确定姓名 4.传递函数依赖:如果通过a属性或属性组的值可以确定唯一b属性的值,在通过b属性或属性组的值可以确定唯一c属性的值,则称c传递函数依赖于a如:学号依赖于系名,系名依赖于系主任 5.码:一张表中一个属性或属性组,被其他所有属性或属性组完全依赖,则称这个属性或属性组为该表的码,跟主键差不多一个意思。
第三范式:在第二范式的基础上,任何非主属性不依赖于其它非主属性(在第二范式的基础上消除传递依赖)
数据库的备份是防止数据异常导致数据丢失的情况出现。数据库备份的方式有两种:
1.通过命令行:mysqldump -u 用户名 -p 密码 > 保存的路径,如:
mysqldump -uroot -ptgbyhn123tgbyhn db1 > E://db1.sql
还原:登录数据库、创建数据库、使用数据库、执行文件(source 文件路径)
CREATE DATABASE db1;
USE db1;
souurce E://db1.sql;命令行执行
提示: 在备份时明文输入密码会提示Warning. Using a password on the command line interface can be insecure.告诉这样不安全,但是数据是备份成功的,如果需要改这个提示,win则需要修改my.cnf配置文件夹,Linux则需要新建一个密码和用户名的配置文件,具体百度即可。
2.通过图像化工具sqlyog:
备份:鼠标移动到某个数据库上右击–备份导出–备份数据库
还原:鼠标移动到数据库虚拟机上root@127.0.0.1右击 – 执行sql脚本(备份的sql脚本)
笛卡尔积:当查询一个表时有2条数据,当查询另一个表时有4条数据,当同时对这两个表进行查询时就会有8条数据(两表数据条数之积),然而连个表是有关系的,a表中有个关联键是关联了b表的主键,按道理查到的数据条数是a表条数数量才对,但是通过如下方式查询到的数据条数不对,且这种现象被称为笛卡尔积
SELECT * FROM emplay, department;
解决笛卡尔积现象:
多表查询可以解决笛卡尔积现象,多表查询分类:
内连接查询:
1.隐式内连接:使用where条件进行筛选无用数据:
SELECT * FROM emplay, department WHERE emplay.dep_id = department.id; #通过emplay表中的dep_id等于department表中的id可以选择出有用的数据,这里查询到的是emplay表中所有的数据,并查到emplay表中所有数据分别对应在department表中的字段
2.显式内连接:通过inner join on关键字进行数据的查询,inner可以省略:
SELECT * FROM emplay INNER JOIN department ON emplay.dep_id = department.id;# 查询emplay表中的数据,并加入department表中id等于emplay表中dep_id的数据
外连接查询:
1.左外连接:通过LEFT OUTER JOIN ON关键字查询数据,左外连接的含义是查询左边表的所有数据,以及它们的交际部分,on后面等号不满足的字段用null显式,此处OUTER可以省略掉。
SELECT * FROM emplay LEFT OUTER JOIN department ON emplay.dep_id = department.id;#查询到emplay表中所有的数据,即使emplay表中dep_id的值为null时,也是查询到所有,只是emplay表中dep_id为null的此条数据查询到后面department表中的数据都为null
2.右外连接:通过RIGHT OUTER JOIN ON关键字查询数据,右外连接的含义是查询右边表的所有数据,以及它们的交际部分,on后面等号不满足的字段用null显式,此处OUTER可以省略掉,和左外连接查询时相对的,方向换掉一样。
SELECT * FROM emplay RIGHT OUTER JOIN department ON emplay.dep_id = department.id;
子查询:查询中嵌套查询,称嵌套查询为子查询。
SELECT * FROM emplay WHERE salary = (SELECT MAX(salary) FROM emplay);#查询emplay表中salary最大值所对应的数据,按之前的方式一步是无法实现,可以分两步实现:1.查询最大值 2.使用where查询最大值所对应的数据,但是在很多时候分两条语句查询在后端处理时又比较繁琐,此时可以利用子查询,先查询括号中的值,然后通过where再筛选数据。
1.子查询的结果是单行单列的:子查询的结果作为前面条件查询的条件,判断符多为:>、<、=、>=、<=等。
SELECT * FROM emplay WHERE salary = (SELECT MAX(salary) FROM emplay);
2.子查询的结果是多行单列的:判断符号一般为in
SELECT * FROM emplay WHERE emplay.dep_id IN (SELECT id FROM department WHERE department.dep_name = '财务部' OR department.dep_name = '研发部');
3.子查询的结果是多行多列的:如果子查询到的结果是多行多列的,此时子查询到的结果可以作为一张虚拟表hightSalary
SELECT * FROM department, (SELECT * FROM emplay WHERE emplay.salary > 6000) hightSalary WHERE department.id = hightSalary.dep_id;
练习:
1.查询所有员工信息。查询员工编号,员工姓名,工资,职务名称,职务描述
SELECT e.id,e.ename,e.salary,j.jname,j.description FROM emp e JOIN job j ON e.job_id = j.id;
SELECT e.id,e.ename,e.salary,j.jname,j.description FROM emp e, job j WHERE e.job_id = j.id;
2.查询员工编号,员工姓名,工资,职务名称,职务描述,部门名称部门位置
SELECT e.id,e.ename,e.salary,j.jname,j.description,d.dname,d.loc FROM emp e, job j, dept d WHERE e.job_id = j.id AND e.dept_id = d.id;
SELECT e.id,e.ename,e.salary,j.jname,j.description,d.dname,d.loc FROM emp e JOIN job j ON e.job_id = j.id JOIN dept d ON e.dept_id = d.id;
3.查询员工姓名,工资,工资等级
SELECT e.ename,e.salary, s.grade FROM salarygrade s, emp e WHERE e.salary >= s.losalary AND e.salary <= s.hisalary;
SELECT e.ename,e.salary, s.grade FROM salarygrade s JOIN emp e ON e.salary >= s.losalary AND e.salary <= s.hisalary;
SELECT e.ename,e.salary, s.grade FROM salarygrade s JOIN emp e ON e.salary BETWEEN s.losalary AND s.hisalary;
SELECT e.ename,e.salary, s.grade FROM salarygrade s, emp e WHERE e.salary BETWEEN s.losalary AND s.hisalary;
4.查询员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级
SELECT e.ename,e.salary,s.grade,d.dname,d.loc,j.jname,j.description FROM salarygrade s, emp e,dept d,job j WHERE e.dept_id = d.id AND e.job_id = j.id AND e.salary >= s.losalary AND e.salary <= s.hisalary;
SELECT e.ename,e.salary,s.grade,d.dname,d.loc,j.jname,j.description FROM salarygrade s JOIN emp e ON e.salary >= s.losalary AND e.salary <= s.hisalary JOIN dept d ON e.dept_id = d.id JOIN job j ON j.id = e.job_id;
SELECT e.ename,e.salary,s.grade,d.dname,d.loc,j.jname,j.description FROM salarygrade s, emp e,dept d,job j WHERE e.dept_id = d.id AND e.job_id = j.id AND e.salary BETWEEN s.losalary AND s.hisalary;
SELECT e.ename,e.salary,s.grade,d.dname,d.loc,j.jname,j.description FROM salarygrade s JOIN emp e ON e.salary BETWEEN s.losalary AND s.hisalary JOIN dept d ON e.dept_id = d.id JOIN job j ON j.id = e.job_id;
5.查询出部门编号、部门名称、部门位置、部门人数
SELECT d.id,d.dname,d.loc,grouptotal.totalnum FROM dept d,(SELECT dept_id,COUNT(id) totalnum FROM emp GROUP BY dept_id) grouptotal WHERE d.id = grouptotal.dept_id;
SELECT d.id,d.dname,d.loc,grouptotal.totalnum FROM dept d JOIN (SELECT dept_id,COUNT(id) totalnum FROM emp GROUP BY dept_id) grouptotal ON d.id = grouptotal.dept_id;
6.查询所有员工的姓名及其直接上级的姓名,没有领导的员工也需要查询
SELECT e1.id,e1.ename,e1.mgr,e2.id,e2.ename FROM emp e1, emp e2 WHERE e1.mgr = e2.id;#只查询有领导的也查
SELECT e1.id,e1.ename,e1.mgr,e2.id,e2.ename FROM emp e1 LEFT JOIN emp e2 ON e1.mgr = e2.id; #没有领导的也查
1.事务的基本介绍:如果一个包含多个步骤的业务操作,被事务管理,那么这些操作要么同时成功,要么同时失败;事务的操作可分为:开启事务(start transaction)、回滚(rollback)、提交(commit);事务可以确保一套业务安全的完成;mysql数据库中事务默认自动提交(一条DML增删改语句就可以将数据库中的数据真实的修改掉,而Oracle数据库默认是不会自动提交的)
#1.开启事务:在多个业务的最开始部分开启事务
START TRANSACTION;
#下面写每个业务
UPDATE account SET balance = balance - 500 WHERE id = 1;
出错。。
UPDATE account SET balance = balance + 500 WHERE id = 2;
#2.提交事务:前面业务执行成功,则提交事务
COMMIT;
#3.回滚事务:前面业务如果失败,则回滚到最初的状态
ROLLBACK;
#4.查询是否开启自动提交:1代表自动提交,0代表不自动提交
SELECT @@autocommit;
#5.修改默认提交方式:当提交方式改为0时表示需要手动提交,也就是说每次执行完增删改语句后,不COMMIT提交的话,其他地方查询的结果为未执行增删改前的数据,再次查询自己也是未更新前的数据
SET @@autocommit = 0;
2.事务的四大特征:
1.原子性:是不可分割的最小操作单位,要么同时失败,要么同时成功
2.持久性:当事务提交或回滚后,数据库会持久化保存数据
3.隔离性:多个事务之间,相互独立。
4.一致性:事务操作前后,数据总量不变
3.事务的隔离级别:
3. 事务的隔离级别(了解)
* 概念:多个事务之间隔离的,相互独立的。但是如果多个事务操作同一批数据,则会引发一些问题,设置不同的隔离级别就可以解决这些问题。
* 存在问题:
1. 脏读:一个事务,读取到另一个事务中没有提交的数据
2. 不可重复读(虚读):在同一个事务中,两次读取到的数据不一样。
3. 幻读:一个事务操作(DML)数据表中所有记录,另一个事务添加了一条数据,则第一个事务查询不到自己的修改。
* 隔离级别:
1. read uncommitted:读未提交,没有提交的数据,另一个事务也是可以读到
* 产生的问题:脏读、不可重复读、幻读
2. read committed:读已提交,只有提交的数据,另一个事务才可以读取到 (Oracle默认隔离级别)
* 产生的问题:不可重复读、幻读
3. repeatable read:可重复读 (MySQL默认隔离级别)
* 产生的问题:幻读
4. serializable:串行化
* 可以解决所有的问题
* 注意:隔离级别从小到大安全性越来越高,但是效率越来越低
* 数据库查询隔离级别:mysql版本不同查询语句不同,具体按实际情况操作
* select @@tx_isolation; 或 SELECT @@transaction_isolation;
* 数据库设置隔离级别:
* set global transaction isolation level 级别字符串(上面4种);
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
* 演示:
set global transaction isolation level read uncommitted;
start transaction;
-- 转账操作
update account set balance = balance - 500 where id = 1;
update account set balance = balance + 500 where id = 2;
1.jdbc的感念:其全称:Java DataBase Connectivity,意为Java数据库连接,Mysql、Oracle、DB2等各个数据库之间都是存在着差异的,使用JDBC可以使用一套代码就能操作不同的关系型数据库,它定义了一套操作所有关系型数据库的规则,即接口,而下面各个数据库之间又有各自对应的实现类(也就是继承与多态,Java操作数据库使用的父类中的方法,而实际操作不同数据库时,实际是new了不同的子类,子类又继承于父类的规则,各个数据库厂商提供了遵循同一套接口的jar包),来达到同一个接口不同实现类对象,操作不同的关系型数据库,这类实现类被称为数据库驱动。
2.jdbc操作mysql快速入门:
先打开idea新建一个项目,这里我命名为:jdbcConnectMysqlDemo,文件下面继续创建一个模块并命名为jdbcdemo,在jdbcdemo下的src目录上面鼠标右击创建一个包,这里我起名为:cn.kuhai.jdbc,继续在cn.kuhai.jdbc包上鼠标右击创建一个类,这里我命名为:JdbcDemo.java
// 1.导入jdbc操作mysql的jar包到项目:到官方https://dev.mysql.com/downloads/file/?id=477058,找到某个版本下载,这里我使用的版本为:8.0.11,老版本可能有漏洞,但是不影响学习,实际开发中或学习中遇到问题在解决就好了。
// 2.注册驱动:
// 3.获取数据库连接对象Connection:
// 4.定义sql:
// 5.获取指定sql语句的对象Statement:
// 6.执行sql,接收返回结果:
// 7.处理结果:
// 8.释放资源:
//具体操作实例如下:
package cn.kuhai.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
// JDBC快速入门:
public class JdbcDemo {
public static void main(String[] args) throws Exception {
// 1.导入mysql数据库驱动jar包:导入前先在jdbcdemo模块下新建一个libs文件夹(目录),用来存放依赖,并将mysql-connector-java-8.0.11文件夹下的mysql-connector-java-8.0.11.jar包复制到libs文件夹中,鼠标右击libs文件夹并添加为库...
// 2.注册驱动:
Class.forName("com.mysql.cj.jdbc.Driver"); // 部分低版本注册表为:com.mysql.jdbc.Driver
// 3.获取数据库的连接对象:DriverManager类下的getConnection方法用来连接数据库,url参数格式为:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2...,问号传参可以传递字符编码、时区等参数,其中jdbc:mysql表示为jdbc的mysql协议;后面两个参数分别为用户名和密码
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3?serverTimezone=GMT&useSSL=false","root","123456"); // serverTimezone=GMT设置时区,用来解决时区未知异常;useSSL=false禁用SSL连接,用来解决高版本mysql中默认建立SSL连接,当useSSL的值为true时需要提供证书,将verifyServerCertificate属性设置为“false”。
// 4.定义一个sql语句,用来操作数据库:
String updateSql = "update account set balance = 2000"; // 修改account表中balance的值为2000
// 5.获取执行sql语句的对象Statement:Connection对象下的createStatement方法可以创建一个执行sql的对象
Statement stmt = connection.createStatement();
// 6.执行SQL:Statement对象下有很多方法,其中增删改查各不一样,executeUpdate用来更新数据,此方法返回一个int类型的值
int count = stmt.executeUpdate(updateSql);
// 7.处理结果,这里暂时没有什么业务可写,就打印看看是什么结果:
System.out.println(count); // 2 ,修改了2条数据,这里返回了2
// 8.释放资源:这里要释放Statement资源和Connection,实际项目中如果项目一直是在运行的,Connection可能要一直为程序服务,此时可能就会把Connection单独封装进行抽离到单独的文件中供全局项目使用,此时可能都没必要释放Connection
stmt.close();
connection.close();
};
}
3.对JDBC各个类和接口的详细介绍:
//1.DriverManager:驱动管理对象,它有两个功能:
//1-1.注册驱动:static void registerDriver(Driver driver):注册给定的驱动程序DriverManager,在上面代码中做快速连接mysql的时候并未使用此方法,而是通过加载字节码文件的方式注册的:Class.forName("com.mysql.cj.jdbc.Driver"),经查看com.mysql.cj.jdbc.Driver的源码可以发现里面有一段静态代码块:(静态代码块会自动执行,因此当字节码文件加载到内存中时会自动执行一段registerDriver代码进行注册驱动,通过加载字节码文件的方式注册驱动只是为了代码简洁)
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
//1-2.获取数据库连接:
//static Connection getConnection(String url,String user,String password)
//url:指定连接的路径:url参数格式为:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2...,如果是本地ip且端口为3306,则可简写为:jdbc:mysql:///数据库名称?参数键值对
//2.Connection: 数据库连接对象:它有几个常用功能:
//2-1.获取执行sql的对象:createStatement和PreparedStatement都用来获取执行sql的对象,
//2-2.管理事务:
//开启事务:void setAutoCommit(boolean autoCommit),该方法的参数设置为false则开启事务
//提交事务:commit()
//回滚事务:rollback()
//3.Statement:执行SQL的对象,它用来执行静态sql语句
//3-1.boolean execute(String sql):一般不怎么用,返回一个执行sql布尔值
//3-2.int executeUpdate(String sql):执行DML(insert、update、delete)语句,DDL(create、alter、drop)在java中一般不使用executeUpdate执行DDL语句对表和库进行操作,返回值表示影响的行数,如果大于0则表示执行成功。
//3-3.ResultSet executeQuery(String sql):执行DQL(select)语句,返回结果集
//4.ResultSet: 结果集对象,封装查询结果
// 4-1.next()方法用来移动光标,向下移动一行光标。
// 4-2.getXxx()获取数据,Xxx代表数据类型,如getInt(),获取int类型的值、此方法接收一个int或string类型的值,当为int时表示获取哪一列的值(这个值从1开始),当为string时,表示获取哪个字段的值。
//5.PreparedStatement: 还是执行SQL的对象,只是功能比Statement强大,它用来执行动态sql
package cn.kuhai.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcDemo2 {
public static void main (String[] args){
// 防止后面try/catch中无法访问到变量,这里先将变量提升一下:
Connection connection = null;
Statement stmt = null;
try {
// 每个模块导入一次mysql的jar包即可,后面再无需导入,因此这里就不需要在导入了
// 1.注册驱动:
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.定义sql: 往accout表中插入一条数据
String sql = "insert into account values (null, 'kuhai12', 2000)";
// 3.获取Connection对象,并使用其getConnection方法连接数据库:
connection =DriverManager.getConnection("jdbc:mysql://localhost:3306/db3?serverTimezone=GMT&useSSL=false","root","123456");
// 4.获取执行sql的Statement对象:
stmt = connection.createStatement();
// 5.执行sql:调用Statement对象的executeUpdate执行DML语句
int count = stmt.executeUpdate(sql);
// 查看执行结果,这里为影响行数:
if (count > 0) {
System.out.println("执行成功");
} else {
System.out.println("执行失败");
}
System.out.println(count);
// 6.处理异常:
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
// 刚开始stmt为null,此时调用close会抛异常,此时应该先做判断,不等于null时再去调用close方法
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// connection同stmt变量一样处理:
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
};
}
// 处理结果集:
package cn.kuhai.jdbc;
import java.sql.*;
// JDBC快速入门:
public class JdbcDemo3 {
public static void main(String[] args) {
ResultSet result = null;
Statement stmt = null;
Connection connection = null;
// 1.第二次使用,无需再导包
try {
// 2.注册驱动:
Class.forName("com.mysql.cj.jdbc.Driver");
// 3.获取数据库的连接对象:
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3?serverTimezone=GMT&useSSL=false","root","123456"); // serverTimezone=GMT设置时区,用来解决时区未知异常;useSSL=false禁用SSL连接,用来解决高版本mysql中默认建立SSL连接,当useSSL的值为true时需要提供证书,将verifyServerCertificate属性设置为“false”。
// 4.定义一个sql语句:
String selectSql = "select * from account"; // 查询account表中所有数据
// 5.获取执行sql语句的对象Statement:
stmt = connection.createStatement();
// 6.执行SQL:Statement对象下有很多方法,executeQuery用来查询ResultSet类型的结果集:
result = stmt.executeQuery(selectSql);
// 光标向下移动一行:查询第一条数据前,应该先移动一次光标,查下一条时也需要先移动光标,当下一行没有数据时,再移动会抛异常,首次不移动也会报错
// result.next();
// // 获取数据:
// int id = result.getInt(1);
// String names = result.getString("NAME");
// Double balance = result.getDouble("balance");
// System.out.println(id + "-------" + names + "------" + balance);
// result.next();
// // 获取数据:
// int id2 = result.getInt(1);
// String names2 = result.getString("NAME");
// Double balance2 = result.getDouble("balance");
// System.out.println(id2 + "-------" + names2 + "------" + balance2);
// 正确使用步骤:next返回一个布尔值,表示是否有数据
while (result.next()) {
int id = result.getInt(1);
String names = result.getString("NAME");
Double balance = result.getDouble("balance");
System.out.println(id + "-------" + names + "------" + balance);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// connection同stmt变量一样处理:
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
};
}
定义一个方法,查询emp表中的数据,并将查询到的数据封装为一个个对象,然后装入集合中,并返回
package cn.domai;
import java.util.Date;
// 封装emp表中对应字段对象的类:字段名称不一定和数据库表中一样,但是推荐一样
public class Emp {
private int id;
private String ename;
private int job_id;
private Date joindate;
private int mgr;
private double salary;
private double bounds;
private int dept_id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public int getJob_id() {
return job_id;
}
public void setJob_id(int job_id) {
this.job_id = job_id;
}
public Date getJoindate() {
return joindate;
}
public void setJoindate(Date joindate) {
this.joindate = joindate;
}
public int getMgr() {
return mgr;
}
public void setMgr(int mgr) {
this.mgr = mgr;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public double getBounds() {
return bounds;
}
public void setBounds(double bounds) {
this.bounds = bounds;
}
public int getDept_id() {
return dept_id;
}
public void setDept_id(int dept_id) {
this.dept_id = dept_id;
}
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", ename='" + ename + '\'' +
", job_id=" + job_id +
", joindate=" + joindate +
", mgr=" + mgr +
", salary=" + salary +
", bounds=" + bounds +
", dept_id=" + dept_id +
'}';
}
}
package cn.kuhai.jdbc;
import cn.domai.Emp;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class JdbcDemo4 {
public static void main(String[] args){
// List list = findAll(); // 下面方法申明为静态方法,这里可以直接调用:
List<Emp> list = new JdbcDemo4().findAll();
System.out.println(list);
}
/*
* 查询所有emp对象:
* @return
* */
public static List<Emp> findAll(){
ResultSet results = null;
Statement stmt = null;
Connection connection = null;
Emp emp = null; // 定义一个每个对象共用的寄存器地址:
List<Emp> list = new ArrayList<Emp>();
try {
// 1.注册驱动:可写可不写,mysql5后自动载入,因为包中有处理
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.获取连接:
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3?serverTimezone=GMT&useSSL=false","root","123456");
// 3.定义sql:
String sql = "select * from emp;";
// 4.获取执行sql语句的对象Statement:
stmt = connection.createStatement();
// 5.执行sql查询数据:
results = stmt.executeQuery(sql);
// 6.遍历结果集,封装对象并装到集合中:
while(results.next()){
int id = results.getInt("id");
String ename = results.getString("ename");
int job_id = results.getInt("job_id");
int mgr = results.getInt("mgr");
Date joindate = results.getDate("joindate");
Double salary = results.getDouble("salary");
Double bonus = results.getDouble("bonus");
int dept_id = results.getInt("dept_id");
// 创建对象:
emp = new Emp();
// 给对象设置值
emp.setId(id);
emp.setEname(ename);
emp.setJob_id(job_id);
emp.setMgr(mgr);
emp.setJoindate(joindate);
emp.setSalary(salary);
emp.setBounds(bonus);
emp.setDept_id(dept_id);
// 将对象加入集合:
list.add(emp);
System.out.println(emp.toString());
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// connection同stmt变量一样处理:
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
return list;
};
}
jdbc公共代码抽离封装:
定义一个数据库信息配置文件jdbcConfig.properties:
url=jdbc:mysql://localhost:3306/db3?serverTimezone=GMT&useSSL=false
user=root
password=123456
driver=com.mysql.cj.jdbc.Driver
JDBCutils工具类:
package cn.util;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.sql.*;
import java.util.Properties;
// 这是一个对jdbc公共代码块的抽离:
public class JDBCutils {
// 定义变量:
private static String url;
private static String user;
private static String password;
private static String driver;
// 1.定义数据库配置文件,并注册驱动:
// 1-1.在src目录下创建jdbcConfig.properties配置文件,配置数据库相关配置和驱动配置
// 1-2.定义静态代码块:自动执行一次文件,只执行一次操作获取配置文件中的值:
static {
try {
// 1-2-1.创建Properties集合类来读取文件:Properties类可以读取文件
Properties pro = new Properties();
// 1-2-2.加载文件:这里文件路径写成src/jdbcConfig.properties是获取不到的,除非绝对路径,当然绝对路径是不合理的,这里最好的方法是通过ClassLoader这个类,它提供了获取src路径下的文件的类加载器:
ClassLoader classLoader = JDBCutils.class.getClassLoader();
URL urlObj = classLoader.getResource("jdbcConfig.properties"); // 它是以src为基础的,所以值传src后面的路径即可,返回一个URL对象,URL对象下的getPath可以获取到相对路径
String paths = urlObj.getPath();
pro.load(new FileReader(paths));
// 1-2-3.获取配置文件中的数据:
url = pro.getProperty("url");
user = pro.getProperty("user");
password = pro.getProperty("password");
url = pro.getProperty("url");
driver = pro.getProperty("driver");
// 1-2-4.注册驱动:
Class.forName(driver);
} catch (IOException ex) {
throw new RuntimeException(ex);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
;
}
// 1.获取连接:抽取可能会用到数据库路径,密码,用户等等参数,每次调用getConnection方法都要传入这几个参数,太麻烦了,这里可以使用另一种方式:通过配置文件的方法,具体如下:
public static Connection getConnection(){
try {
return DriverManager.getConnection(url,user,password);
} catch (SQLException e) {
throw new RuntimeException(e);
}
};
// 2.释放资源:
public static void close(ResultSet rs, Statement stmt, Connection conn){
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
};
}
测试JDBCutils工具类:
package cn.kuhai.jdbc;
import cn.domai.Emp;
import cn.util.JDBCutils;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class JdbcDemo5 {
public static void main(String[] args){
// List list = findAll(); // 下面方法申明为静态方法,这里可以直接调用:
List<Emp> list = new JdbcDemo5().findAll();
System.out.println(list);
}
/*
* 使用封装的jdbc工具类,查询所有emp对象:
* @return
* */
public static List<Emp> findAll(){
ResultSet results = null;
Statement stmt = null;
Connection connection = null;
Emp emp = null; // 定义一个每个对象共用的寄存器地址:
List<Emp> list = new ArrayList<Emp>();
try {
// // 1.注册驱动:可写可不写,mysql5后自动载入,因为包中有处理
// Class.forName("com.mysql.cj.jdbc.Driver");
// // 2.获取连接:
// connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3?serverTimezone=GMT&useSSL=false","root","123456");
// 使用jdbc工具类代替上面代码:
connection = JDBCutils.getConnection();
// 3.定义sql:
String sql = "select * from emp;";
// 4.获取执行sql语句的对象Statement:
stmt = connection.createStatement();
// 5.执行sql查询数据:
results = stmt.executeQuery(sql);
// 6.遍历结果集,封装对象并装到集合中:
while(results.next()){
int id = results.getInt("id");
String ename = results.getString("ename");
int job_id = results.getInt("job_id");
int mgr = results.getInt("mgr");
Date joindate = results.getDate("joindate");
Double salary = results.getDouble("salary");
Double bonus = results.getDouble("bonus");
int dept_id = results.getInt("dept_id");
// 创建对象:
emp = new Emp();
// 给对象设置值
emp.setId(id);
emp.setEname(ename);
emp.setJob_id(job_id);
emp.setMgr(mgr);
emp.setJoindate(joindate);
emp.setSalary(salary);
emp.setBounds(bonus);
emp.setDept_id(dept_id);
// 将对象加入集合:
list.add(emp);
System.out.println(emp.toString());
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//if (stmt != null) {
// try {
// stmt.close();
// } catch (SQLException e) {
// throw new RuntimeException(e);
// }
//}
connection同stmt变量一样处理:
//if (connection != null) {
// try {
// connection.close();
// } catch (SQLException e) {
// throw new RuntimeException(e);
// }
//}
// 使用封装工具类JDBCutils中的close代替上面代码
JDBCutils.close(results,stmt,connection);
}
return list;
};
}
PreparedSta防止sql注入:
此接口用来解决sql注入问题,将来一系列的增删改查都会用PreparedSta来实现,那么,什么是sql注入呢,比如一个登录接口的SQL如下:
String sql = 'select * from user where username = ' + username and 'password = ' + password
如果给最后一个变量password传入 'xxx or 'a' = 'a',此时前面xxx和前面sql拼接起来用户名和密码不匹配是查不到数据的,当在后面继续拼接 or 'a' = 'a'时,此sql为恒成立,那么就会查到所有用户的信息,此时就不安全了,为了解决这个问题,sun公司提供了PreparedSta来解决sql注入的问题,它是一个预编译sql对象。
其实和之前拼接sql的区别主要是:将变量通过?占位,然后在执行sql前进行处理,如:
// 1.使用PreparedSta解决sql注入问题,第一步是将所有变量改为?进行占位:
String sql = 'select * from user where username = ? and password = ?';
// 2.使用Connection的prepareStatement()方法将定义含有占位的sql传入此方法,返回一个PreparedStatement对象,PreparedStatement对象也是要被释放的。
PreparedStatement preStmt = connection.prepareStatement(sql);
// 3.给占位符赋值:PreparedStatement对象有系列方法:setXXX,其中XXX表示类型,该类方法接收两个参数,第一个为第几个占位符,第二个参数为该占位所用到的变量,如:
preStmt.setString(1, username);
preStmt.setString(2, password);
// 4.使用PreparedStatement对象空参数方法:executeQuery执行sql
results = preStmt.executeQuery();
JDBC管理事务:
JDBC中开启事务用Connection类的方法:setAutoCommit(false);提交事务用Connection的commit方法,回滚事务用Connection的rollback方法,其使用步骤是:在执行sql前开启事务,在执行sql完提交事务,在异常中回滚事务,如:
package cn.kuhai.jdbc;
import cn.domai.Emp;
import cn.util.JDBCutils;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class JdbcDemo6 {
public static void main(String[] args){
ResultSet results = null;
PreparedStatement pstmt1 = null;
PreparedStatement pstmt2 = null;
Connection connection = null;
try {
// 获取数据库连接:
connection = JDBCutils.getConnection();
// 1.开启事务:
connection.setAutoCommit(false);
// 定义两条转账的sql:
String sql1 = "update account set balance = balance - ? where id = ?";
String sql2 = "update account set balance = balance + ? where id = ?";
// 获取执行sql的PreparedStatement对象:
pstmt1 = connection.prepareStatement(sql1);
pstmt2 = connection.prepareStatement(sql2);
// 设置参数创建有效的sql:
pstmt1.setDouble(1, 500);
pstmt1.setInt(2, 1);
pstmt2.setDouble(1, 500);
pstmt2.setInt(2, 2);
// 执行SQL:
pstmt1.executeUpdate();
// 手动给个异常,看看事务是否生效:发现设置这个错误代码后,数据库的数据没有修改
// int num = 10 / 0;
pstmt2.executeUpdate();
// 2.提交事务:
connection.commit();
} catch (Exception e) {
try {
// 3.当有异常抛出时,回滚事务:
if (connection != null) connection.rollback();
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
throw new RuntimeException(e);
}
};
}
数据库连接池: 每次操作数据库之后都将资源释放掉了,其实每次申请资源是比较耗时的,此时可以使用数据库连接池来解决这个问题。数据库连接池是指一容器(集合)中有多个操作数据的元素,每个元素每次操作完数据库后并不会被释放,而是归还给池子了,供下一次操作数据库时使用。
实现数据库连接池:
C3P0:是一个比较老的数据库连接池,这里只是学习一下使用数据库连接池的步骤,实际中可能使用阿里的Druid比较多,c3p0包下载因仓库在国外,下载可能需要翻墙,或者百度找别人下载好的即可。
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class C3p0Demo {
public static void main(String[] args) throws SQLException {
// 1.导包:将c3p0-0.9.5.5.jar和mchange-commons-java-0.2.19.jar两个包放到项目下libs文件夹中外,还要导入之前用过的数据库驱动jar包mysql-connector-java-8.0.11.jar,选中libs下所有jar包,鼠标右击添加到库...
// 2.配置c3p0-config.xml配置文件,下面会有配置实例:(特别提醒:在高版本数据库驱动jar包下,c3p0的版本也要相对跟上,第一条经测试是连接成功的,还有一点就是,可能会报异常,此时可能连接地址还需要配置:?characterEncoding=utf-8&serverTimezone=UTC)
// 3.创建数据库连接池对象:
DataSource ds = new ComboPooledDataSource(); // 可接收一个c3p0-config.xml数据库配置的名称,用于连接对应名称配置的数据库,不传则连接默认配置的
// 4.获取连接对象(这里可以获取maxPoolSize设置的最大个数个,超出最大数量且过响应时间就会报异常):
Connection connection = ds.getConnection();
System.out.println(connection);
// 5.归还连接对象到池中:close在数据连接池中并非关闭释放资源,而是将数据库对象归还给池中,此时它还是存在于池中的,并且还是可以继续使用的。
connection.close();
};
}
c3p0-config.xml配置文件如下:
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.cj.jdbc.Driverproperty>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/db3?characterEncoding=utf-8&serverTimezone=UTCproperty>
<property name="user">rootproperty>
<property name="password">123456property>
<property name="initialPoolSize">5property>
<property name="checkoutTimeout">3000property>
<property name="idleConnectionTestPeriod">30property>
<property name="maxIdleTime">30property>
<property name="maxPoolSize">100property>
<property name="minPoolSize">10property>
<property name="maxStatements">200property>
default-config>
<named-config name="kuhai123C3p0">
<property name="driverClass">com.mysql.cj.jdbc.Driverproperty>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/db4?characterEncoding=utf-8&serverTimezone=UTCproperty>
<property name="user">rootproperty>
<property name="password">123456property>
<property name="initialPoolSize">5property>
<property name="checkoutTimeout">3000property>
<property name="idleConnectionTestPeriod">30property>
<property name="maxIdleTime">30property>
<property name="maxPoolSize">100property>
<property name="minPoolSize">10property>
<property name="maxStatements">200property>
named-config>
c3p0-config>
**Druid:**数据库连接池,是阿里巴巴提供的,速度和性能都是比较客观的,用法和c3p0基本一致,后面项目中也是推荐使用Druid连接池,其下载地址为:https://repo1.maven.org/maven2/com/alibaba/druid/ 可根据自己数据库驱动器的版本选择不同的版本。
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
public class DruidDemo {
// druid使用步骤
public static void main(String[] args) throws Exception {
// 1.在项目根目录下新建libs文件夹,并将druid-1.2.9.jar包,外加数据库驱动jar包:mysql-connector-java-8.0.11.jar复制到libs文件夹中,选中这两个包,鼠标右击添加为库...
// 2.定义配置文件:以properties形式配置、可以任意名称,任意目录下、需要手动加载文件(这里我也是放到src下面了,并命名为:druid.properties)
InputStream ins = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties"); // getClassLoader获取当前类的类加载器,getResourceAsStream然后在加载器中加载某个资源,并以流的形式返回
Properties pro = new Properties(); // 用于获取配置文件:druid.properties的Properties对象
pro.load(ins); // 加载配置文件
// 3.获取数据库连接池对象:通过工厂类获取(DruidDataSourceFactory)
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
// 4.获取连接:getConnection
Connection connection = ds.getConnection();
System.out.println(connection);
};
}
druid.properties配置文件:
driverClassName=com.mysql.cj.jdbc.Driver
# 如果不传参数,根据默认配置会报异常的,所以这里推荐将serverTimezone=GMT&useSSL=false配置上
url=jdbc:mysql://localhost:3306/db3?serverTimezone=GMT&useSSL=false
username=root
password=123456
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
maxWait=3000
druid工具类(JdbcDruidPool)封装:实际开发中连接池也不是在每一个模块或类中都会创建,而是在项目中创建一个连接池(如果是同一个数据库配置的话,一般是一个连接池就好了),如:
JdbcDruidPool工具类:
package utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
// 封装一个数据库连接池工具类:在src下面新建一个文件夹(软件包)并命名为:utils,在utils包下新建一个JdbcDruidPool数据库连接池类
public class JdbcDruidPool {
// 1.定义一个成员变量DataSource
private static DataSource ds;
// 利用静态代码块自动加载配置文件
static {
try {
// 2.加载配置文件:
Properties pro = new Properties();
pro.load(JdbcDruidPool.class.getClassLoader().getResourceAsStream("druid.properties")); // 加载配置文件
// 3.获取DataSource并赋值给ds成员变量:
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/*
* 4.获取连接对象(从连接池中取一个连接对象)的方法:
* */
public static Connection getConnection() throws SQLException {
return ds.getConnection();
};
/*
* 5.释放资源(归还连接对象)的方法:
* */
public static void close(Statement stmt, Connection conn){
// if (stmt != null) {
// try {
// stmt.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// 调用close重置方法优化上面代码块:
close(null, stmt, conn);
};
// 重置close方法
public static void close(ResultSet rs, Statement stmt, Connection conn){
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
};
/*
* 6.获取连接池方法:
* */
public static DataSource getDataSource(){
return ds;
};
}
测试JdbcDruidPool工具类:
import utils.JdbcDruidPool;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Properties;
public class DruidUtilDemo {
public static void main(String[] args){
Connection connection = null;
PreparedStatement pstmt = null;
// 使用druid封装的工具类给数据库新增一条数据:
try {
// 1.获取连接:
connection = JdbcDruidPool.getConnection();
// 2.定义sql:
String sql = "insert into account values(null,?,?)";
// 3.获取PreparedStatement对象并初始化sql语句:
pstmt = connection.prepareStatement(sql);
pstmt.setString(1, "苦海123");
pstmt.setDouble(2, 299);
// 4.执行sql语句,并接收返回结果:
int count = pstmt.executeUpdate();
// 5.处理数据及业务
System.out.println(count); // 1
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
// 6.释放资源归还数据库连接对象
JdbcDruidPool.close(pstmt, connection);
}
};
}
Spring JDBC: Spring框架对JDBC的简单封装,提供了JDBCTemplate对象,简化JDBC的开发,上面DruidUtilDemo中尽管封装了JdbcDruidPool工具类,但是还是很不方便,因为一些prepareStatement等的操作。使用JdbcTemplate可以简化这些操作,如下:
使用案例:提示,此demo用到前面封装的JdbcDruidPool数据库连接池工具类、封装emp表中对应字段对象的类Emp
import org.junit.Test;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class SpringJdbcTest {
// 2.获取JDBCTemplate对象:
private JdbcTemplate tempJdbc = new JdbcTemplate(JdbcDruidPool.getDataSource()); // JdbcTemplate方法接收一个DataSource,这里使用之前封装好的JdbcDruidPool数据库连接池工具类获取DataSource,之后在传给JdbcTemplate方法
// 使用Junit单元测试,可以让方法独立执行(不借助main方法)
@Test //加入@Test注解,此方法就称为Junit单元测试方法,这里需要导入Junit4包,否则报异常,alt+enter,之所以写单元测试方法,是因为方便学习,实际开发中不建议这么写,一般是由测试人员写单元测试方法进行测试的
public void test1(){ // 点击编辑器左侧绿色箭头即可执行方法
// 使用JdbcTemplate的基本步骤:
// 1.在项目下新建一个libs文件夹,去官网https://commons.apache.org/proper/commons-logging/download_logging.cgi下载二进制commons-logging-1.2.jar包、以及一些其他的包如下:
// spring-beans-5.1.10.RELEASE.jar、spring-core-5.1.10.RELEASE.jar、spring-jdbc-5.1.10.RELEASE.jar、spring-tx-5.1.10.RELEASE.jar,最后别忘了数据库驱动器mysql-connector-java-8.0.11.jar和数据库连接池druid-1.2.9.jar,将所有的jar包都放到libs文件夹中,然后添加为库...
// 3.定义sql: 修改emp表中所有用户的salary值为1000
String sql = "update emp set salary = 1000";
// 4.执行SQL:
int count = tempJdbc.update(sql);
System.out.println(count); // 14
};
@Test // 在定义一个方法,用于执行另一个业务,后面提供给前端的每一个接口都有可能以这种形式书写,这里只是用单元测试方法做练习:
public void test2(){
// 定义sql:新增一条新用户:
String sql = "INSERT INTO `db3`.`emp` (`id`, `ename`, `job_id`, `mgr`, `joindate`, `salary`, `bonus`, `dept_id`) VALUES (?, ?, '4', '2000', '2023-09-08', '100.00', '0.00', '10')";
int count = tempJdbc.update(sql,1017, "方淑奇"); // 可以直接给问号赋值,无需多余的setXXX赋值就可以解决SQL注入问题
System.out.println(count); // 1
};
@Test
public void test3(){
// 定义sql:删除id大于等于1015的数据
String sql = "delete from emp where id >= ?";
int count = tempJdbc.update(sql,1015);
System.out.println(count); // 3,因为id大于等于1015的数据有3条
};
@Test
public void test4(){
// 查询emp表中的数据,并将其结果封装为Map集合
String sql = "select * from emp where id = ?";
Map<String, Object> map = tempJdbc.queryForMap(sql,1012); // queryForMap可以将查询到的结果封装为Map对象,前提是查到数据是一条才行,如果有多条或0条数据的话就会异常。
System.out.println(map); // {id=1012, ename=李逵, job_id=4, mgr=1006, joindate=2001-12-03, salary=1000.00, bonus=null, dept_id=30}
};
@Test
public void test5(){
// 查询emp表中的数据,并将其结果封装为List,List下每个元素都是Map集合
String sql = "select * from emp where id < ?";
List<Map<String,Object>> list = tempJdbc.queryForList(sql,1003); // queryForList可以将查询到的结果封装为Map对象然后装进一个List集合中。
System.out.println(list); // [{id=1001, ename=孙悟空, job_id=4, mgr=1004, joindate=2000-12-17, salary=1000.00, bonus=null, dept_id=20}, {id=1002, ename=卢俊义, job_id=3, mgr=1006, joindate=2001-02-20, salary=1000.00, bonus=3000.00, dept_id=30}]
};
// 实际开发中,一般在List集合中并不会装载Map集合,而是将查到的结果转为Java对象,然后在装载到List集合
@Test
public void test6(){
// 查询emp表中的数据,并将其结果封装为List,List下每个元素都是Java对象,实际开发中也不会这么写,因为太麻烦了,一般使用test7这种使用自带的方法生成,使用Spring提供的方法,泛型接收一个字段对应的类
String sql = "select * from emp where id < 1003";
List<Emp> list = tempJdbc.query(sql,new RowMapper<Emp>() { // query方法第一个参数可以new一个RowMapper,后面泛型接收一个定义的数据库字段的类
// 重新mapRow,用于获取每一行中的值,然后再将值设置给一个对象(Emp),之后将这个对象return出去,最后就是一个List集合下装的java对象
@Override
public Emp mapRow(ResultSet resultSet, int i) throws SQLException {
// 获取需要返回的字段:
int id = resultSet.getInt("id");
String ename = resultSet.getString("ename");
int job_id = resultSet.getInt("job_id");
int mgr = resultSet.getInt("mgr");
Date joindate = resultSet.getDate("joindate");
Double salary = resultSet.getDouble("salary");
Double bonus = resultSet.getDouble("bonus");
int dept_id = resultSet.getInt("dept_id");
// new一个Emp对象,并一一设置值:
Emp emp = new Emp();
emp.setId(id);
emp.setEname(ename);
emp.setJob_id(job_id);
emp.setMgr(mgr);
emp.setJoindate(joindate);
emp.setSalary(salary);
emp.setBounds(bonus);
emp.setDept_id(dept_id);
return emp;
}
});
System.out.println(list); // [Emp{id=1001, ename='孙悟空', job_id=4, joindate=2000-12-17, mgr=1004, salary=1000.0, bounds=0.0, dept_id=20}, Emp{id=1002, ename='卢俊义', job_id=3, joindate=2001-02-20, mgr=1006, salary=1000.0, bounds=3000.0, dept_id=30}]
};
// 查列表数据终极版:
@Test
public void test7(){
String sql = "select * from emp where id < 1003";
List<Emp> list = tempJdbc.query(sql,new BeanPropertyRowMapper<Emp>(Emp.class)); // BeanPropertyRowMapper方法泛型接收一个字段类,参数接收这个类的class字节码,这样可以省略很多代码而实现同样的效果
System.out.println(list); // [Emp{id=1001, ename='孙悟空', job_id=4, joindate=2000-12-17 08:00:00.0, mgr=1004, salary=1000.0, bounds=0.0, dept_id=20}, Emp{id=1002, ename='卢俊义', job_id=3, joindate=2001-02-20 08:00:00.0, mgr=1006, salary=1000.0, bounds=0.0, dept_id=30}]
};
// 查询总记录条数:
@Test
public void test8() {
String sql = "select count(id) from emp";
Long total = tempJdbc.queryForObject(sql,Long.class); // queryForObject一般用于查询聚合函数
System.out.println(total); // 14,总共14条数据
};
}
提示:本文图片等素材来源于网络,若有侵权,请发邮件至邮箱:810665436@qq.com联系笔者删除。
笔者:苦海123
其它问题可通过以下方式联系本人咨询:
QQ:810665436
微信:ConstancyMan