1. MySQL 数据库
第一节 数据库简介
1.1 简介
数据库(DataBase,DB):指长期保存在计算机的存储设备(硬盘)上,按照一定规则组织起来,可以被各种用户或应用共享的数据集合。简单理解数据的仓库。
数据库管理系统(DataBase Management System,DBMS):指一种操作和管理数据库的大型软件,用于建立、使用和维护数据库,对数据库进行统一管理和控制,以保证数据库的安全性和完整性。用户通过数据库管理系统访问数据库中的数据。
数据库是通过数据库管理系统创建和操作的。
关系型数据库:oracle,mysql,sqlserver,DB2,sqlLite,数据都是存储在磁盘里面
非关系型数据库:no-sql: redis,数据以键值对的形式存储在内存(中间件)
1.2 常见数据库管理系统
- Oracle:Oracle数据库被认为是业界目前比较成功的关系型数据库管理系统。Oracle数据库可以运行在UNIX、Windows等主流操作系统平台,完全支持所有的工业标准,并获得最高级别的ISO标准安全性认证。
- MySQL:MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件。
- DB2:DB2是IBM公司的产品,DB2数据库系统采用多进程多线索体系结构,其功能足以满足大中公司的需要,并可灵活地服务于中小型电子商务解决方案。
- Microsoft SQL Server:SQL Server 是Microsoft 公司推出的关系型数据库管理系统。具有使用方便可伸缩性好与相关软件集成程度高等优点。
- SQLLite:应用在手机端的数据库。
注意:
oracle数据库中有表空间这样的概念
mysql 可以集群
第二节 SQL语言
2.1 概述
SQL:Structure Query Language(结构化查询语言),SQL被美国国家标准局(ANSI)确定为关系型数据库语言的美国标准,后来被国际化标准组织(ISO)采纳为关系数据库语言的国际标准。
各数据库厂商都支持ISO的SQL标准,普通话
各数据库厂商在标准的基础上做了自己的扩展,方言
SQL 是一种标准化的语言,它允许你在数据库上执行操作,如创建数据库、表等等,查询内容,更新内容,并删除条目等操作。
Create, Read, Update, and Delete 通常称为CRUD操作。
2.2 SQL语句分类
- DDL(Data Definition Language):数据定义语言,用来定义数据库对象:库、表、列等。
- DML(Data Manipulation Language):数据操作语言,用来定义数据库记录(数据)。
- DCL(Data Control Language):数据控制语言,用来定义访问权限和安全级别。
- DQL(Data Query Language):数据查询语言,用来查询记录(数据)。
MySQL的使用注意:
sql语句以;结尾。
MySQL注释:(1)#开头 (2)–空格 开头 (3)/* 多行注释 */
2.3 DDL操作数据库
(create drop alter show)
show databases; #显示当前mysql中的数据库
系统默认数据库:
information_schema:
information_schema数据库是MySQL自带的,它提供了访问数据库元数据的方式。什么是元数据呢?元数据是关于数据的数据,如数据库名或表名,列的数据类型,或访问权限等。
mysql:
mysql:这个是mysql的核心数据库,主要负责存储数据库的用户、权限设置、关键字等mysql自己需要使用的控制和管理信息,不可以删除。
performance_schema:
性能优化的数据库
test:
这个是安装时候创建的一个测试数据库,和它的名字一样,是一个完全的空数据库,没有任何表,可以删除。
1创建数据库:
CREATE DATABASE语句用于创建新的数据库:
语法:CREATE DATABASE [IF NOT EXISTS] db_name
SQL> CREATE DATABASE mydb1; #创建mydb1数据库
SQL> CREATE DATABASE IF NOT EXISTS mydb2 character SET GBK;
SQL> CREATE DATABASE mydb3 character SET GBK COLLATE gbk_chinese_ci;
2查看
查看当前数据库服务器中的所有数据库
SQL> SHOW DATABASES;
查看前面创建的mydb2数据库的定义信息
SQL> ShOW CREATE DATABASE mydb2;
3修改
查看服务器中的数据库,并把mydb2的字符集修改为utf8;
SQL> ALTER DATABASE mydb2 character SET utf8;
4删除
语法:DROP DATABASE [IF EXISTS] db_name
SQL> DROP DATABASE IF EXISTS mydb3;
5其他语句
查看当前使用的数据库
SQL> Select database(); #没有选择数据 null
使用/切换据库
SQL> USE mydb1; #使用mydb1数据库
退出数据库
quit;或exit;
2.4 DDL操作表(重点)
CREATE TABLE语句用于创建新表。
语法:
CREATE TABLE [IF NOT EXISTS] 表名(
字段1 字段类型(长度) 约束,
字段2 字段类型(长度) 约束,
…
字段n 字段类型(长度) 约束
) [charset=utf8];
示例:
SQL> CREATE TABLE IF NOT EXISTS student
(
id INT NOT NULL,
name VARCHAR(50),
age INT,
address VARCHAR(100)
) CHARSET=utf8;
或
SQL> CREATE TABLE IF NOT EXISTS `student`
(
`id` INT NOT NULL,
`name` VARCHAR(50),
`age` INT,
`address` VARCHAR(100)
) CHARSET=utf8;
注意:数据库名、表名、字段名可以使用反勾号` 括住,也可以不括。如果SQL关键字一般要括住。
常用数据类型:
int:整型(4个字节)
double:浮点型(8个字节),近似值
例如double(5,2)表示最多5位,其中必须有2位小数,即最大值为999.99
decimal:精确数值数据,最大位数可以是65
例如decimal(5,2)能够存储具有五位数和两位小数的任何值,因此可以存储范围为-999.99至999.99。
char:固定长度字符串类型; char(10) 'aaa ’ 固定10个字符,不足补空格, 长度0-255
缺点:浪费空间,优点:查询速度快
varchar:可变长度字符串类型,大小在0-65535字节; varchar(10) ‘aaa’ 最多存储10个字符
缺点:查询速度慢,优点:节省空间
text:大文本字符串类型;有字符编码,存储比较大的文本数据。
blob:Binary Large Object二进制大对象数据;可以存储图片、音频、视频
date:日期类型,格式为:yyyy-MM-dd;
time:时间类型,格式为:hh:mm:ss
timestamp:时间戳类型 yyyy-MM-dd hh:mm:ss 会自动赋值
datetime:日期时间类型 yyyy-MM-dd hh:mm:ss
常用约束:
主键约束:primary key (保证数据唯一性),不能重复,不能为null
唯一约束:unique [key],不能重复,可以为null
非空约束:not null
默认约束:default
外键约束:foreign key
自动增长:auto_increment
DROP TABLE语句用于删除现有表。
语法: DROP TABLE [IF EXISTS] table_name;
SQL> DROP TABLE table_name;
当前数据库中的所有表
SHOW TABLES;
查看表的字段信息
DESC student;
在上面学生表的基础上增加一个image列。
ALTER TABLE student ADD image blob; // ALTER 表 表名 增加 列 类型;
修改address列,使其长度为60。
ALTER TABLE student MODIFY address varchar(60);
删除image列,一次只能删一列。
ALTER TABLE student DROP image;
修改表名改为user。
RENAME TABLE student TO user;
查看表的创建细节
SHOW CREATE TABLE user;
修改表的字符集为gbk
ALTER TABLE user CHARACTER SET gbk;
列名name修改列名为username
ALTER TABLE user CHANGE name username varchar(100);
建表:建立一张dvd的表
2.5 DML操作(重要)
DML是对表中的数据进行增、删、改的操作。不要与DDL混淆了。
主要包括:INSERT 、UPDATE、 DELETE
小知识:
在mysql中,字符串类型和日期类型都要用单引号括起来。
空值:null。
(1)插入操作:INSERT:
语法: INSERT INTO 表名(列名1,列名2 …)VALUES(列值1,列值2…);
注意:列名与列值的类型、个数、顺序要一一对应。
可以把列名当做java中的形参,把列值当做实参。
参数不要超出列定义的长度。
如果插入空值,请使用null
插入的日期和字符一样,都使用单引号括起来。
SQL> CREATE TABLE student
(
id INT NOT NULL,
name VARCHAR(50),
age INT,
address VARCHAR(100)
);
一次添加一条数据
INSERT INTO student(id,name,age,address) values (1,'zhangsan',20,'北京海淀');
INSERT INTO student(id,name,age,address) values (2,'lisi',22,'上海浦东');
INSERT INTO student(id,name,age,address) values (3,'wangwu',23,'北京昌平');
一次添加多条数据
INSERT INTO student(id,name,age,address) values (4,'曹操',27,'北京海淀'),
(5,'周瑜',28,'北京朝阳'),
(6,'赵云',30,'北京大兴');
(2)修改操作:UPDATE:
语法:UPDATE 表名 SET 列名1=列值1,列名2=列值2 … WHERE 列名=值
练习:
将所有学生的年龄修改为25。
UPDATE student SET age=25;
将id为’1’的地址修改为“河北保定”。
UPDATE student SET address=‘河北保定’ WHERE id=1;
将姓名为’曹操’’的学生年龄改为50,地址改为"河南郑州"’。
UPDATE student SET age=50, address=‘河南郑州’ WHERE name=‘曹操’;
将所有学生的年龄加5岁。
UPDATE student SET age=age+5;
(3)删除操作:DELETE
语法 : DELETE FROM 表名 【WHERE 列名=值】
练习 :
删除表中名称为’zhangsan’的记录。
DELETE FROM student WHERE name=‘zhangsan’;
删除表中所有记录。
DELETE FROM emp; //是对表数据做删除操作
使用truncate删除表中记录。(先把表删除,然后再创建空表)
TRUNCATE TABLE emp;
- DELETE 删除表中的数据,表结构还在;删除后的数据使用日志可以找回。
- TRUNCATE 删除是把表直接DROP掉,然后再创建一个同样的新表。
- TRUNCATE 删除的数据不能找回。执行速度比DELETE快。
(4)清空表数据: TRUNCATE
TRUNCATE TABLE 表名; //是对表销毁后,按原来表的格式创建了一张新表
第三节 DQL数据查询(重点)
数据库执行DQL语句不会对数据进行改变,而是让数据库发送结果集给客户端。
查询返回的结果集是一张虚拟表。
结果集:select 和 from中间的内容称之为结果集
查询关键字:SELECT
语法: SELECT 列名 FROM 表名 【WHERE --> GROUP BY–>HAVING–> ORDER BY–>LIMIT】
执行顺序 FROM -WHERE -GROUP BY-HAVING-SELECT-ORDER BY-LIMIT
SELECT selection_list /要查询的列名称/
FROM table_list /要查询的表名称/
WHERE condition /行条件/
GROUP BY grouping_columns /对结果分组/
HAVING condition /分组后的行条件/
ORDER BY sorting_columns /对结果排序/
LIMIT offset_start, row_count /结果限定/
示例操作:
1>创建学生表并添加数据
#创建表stu
CREATE TABLE stu (
sid CHAR(6),
sname VARCHAR(50),
age INT,
gender VARCHAR(50)
);
#添加数据
INSERT INTO stu VALUES('S_1001', 'liuYi', 35, 'male');
INSERT INTO stu VALUES('S_1002', 'chenEr', 15, 'female');
INSERT INTO stu VALUES('S_1003', 'zhangSan', 95, 'male');
INSERT INTO stu VALUES('S_1004', 'liSi', 65, 'female');
INSERT INTO stu VALUES('S_1005', 'wangWu', 55, 'male');
INSERT INTO stu VALUES('S_1006', 'zhaoLiu', 75, 'female');
INSERT INTO stu VALUES('S_1007', 'sunQi', 25, 'male');
INSERT INTO stu VALUES('S_1008', 'zhouBa', 45, 'female');
INSERT INTO stu VALUES('S_1009', 'wuJiu', 85, 'male');
INSERT INTO stu VALUES('S_1010', 'zhengShi', 5, 'female');
INSERT INTO stu VALUES('S_1011', 'xxx', NULL, NULL);
2>创建雇员表并添加数据
#创建雇员表
CREATE TABLE emp(
empno INT,
ename VARCHAR(50),
job VARCHAR(50),
mgr INT,
hiredate DATE,
sal DECIMAL(7,2),
comm decimal(7,2),
deptno INT
);
#添加数据
INSERT INTO emp values(7369,'SMITH','CLERK',7902,'1980-12-17',800,NULL,20);
INSERT INTO emp values(7499,'ALLEN','SALESMAN',7698,'1981-02-20',1600,300,30);
INSERT INTO emp values(7521,'WARD','SALESMAN',7698,'1981-02-22',1250,500,30);
INSERT INTO emp values(7566,'JONES','MANAGER',7839,'1981-04-02',2975,NULL,20);
INSERT INTO emp values(7654,'MARTIN','SALESMAN',7698,'1981-09-28',1250,1400,30);
INSERT INTO emp values(7698,'BLAKE','MANAGER',7839,'1981-05-01',2850,NULL,30);
INSERT INTO emp values(7782,'CLARK','MANAGER',7839,'1981-06-09',2450,NULL,10);
INSERT INTO emp values(7788,'SCOTT','ANALYST',7566,'1987-04-19',3000,NULL,20);
INSERT INTO emp values(7839,'KING','PRESIDENT',NULL,'1981-11-17',5000,NULL,10);
INSERT INTO emp values(7844,'TURNER','SALESMAN',7698,'1981-09-08',1500,0,30);
INSERT INTO emp values(7876,'ADAMS','CLERK',7788,'1987-05-23',1100,NULL,20);
INSERT INTO emp values(7900,'JAMES','CLERK',7698,'1981-12-03',950,NULL,30);
INSERT INTO emp values(7902,'FORD','ANALYST',7566,'1981-12-03',3000,NULL,20);
INSERT INTO emp values(7934,'MILLER','CLERK',7782,'1982-01-23',1300,NULL,10);
3>创建部门表并添加数据
#创建部门表
CREATE TABLE dept(
deptno INT,
dname varchar(14),
loc varchar(13)
);
#添加数据
INSERT INTO dept values(10, '财务部', 'beijing');
INSERT INTO dept values(20, 'java开发部', 'tianjin');
INSERT INTO dept values(30, '测试部', 'shanghai');
INSERT INTO dept values(40, '销售部', 'shenzheng');
3.1 简单查询
查询所有列 *表示所有列 (执行查询语句 返回的结果集 是一张虚拟表)
注意
:在实际开发中严禁使用(生产环境下优先使用列名查询,的方式效率低,可读性差)
SELECT * FROM stu;
查询指定列
SELECT sid, sname, age FROM stu;
去重 distinct
SELECT DISTINCT sname from stu;
- 思考:数据库中存在基本信息相同的数据,如何实现去重?
返回 员工id作为编号,姓名作为姓氏,月薪乘以12作为年薪的一张虚拟表
SELECT EMPLOYEE_ID AS '编号',FIRST_NAME AS '姓氏',SALARY*12 AS '年薪' FROM t_employee;
3.2 条件查询
where 条件 在查询结果后筛选符合条件的查询结果,条件为布尔表达式
条件查询就是在查询时给出WHERE子句,在WHERE子句中可以使用如下运算符及关键字:
比较运算符(mysql中等值判断使用= 与java 的 == 是一个意思)
l =、!=、<>、<、<=、>、>=; ( != 与 <> 都表示不等于 )
l BETWEEN…AND; (区间判断 闭区间包含区间边界的两个值)
l IN(set);(枚举查询)
l IS NULL; IS NOT NULL ; (NULL值判断 例:WHERE gender IS NULL )
关系运算符(逻辑判断)
l AND;
l OR;
l NOT;
算术运算符:
+ - * / % 注意是占位符,不是取模运算符
(1)查询性别为女,并且年龄小于50的记录
SELECT * FROM stu WHERE gender='female' AND age<50;
(2)查询学号为S_1001,或者姓名为liSi的记录
SELECT * FROM stu WHERE sid ='S_1001' OR sname='liSi';
(3)查询学号为S _ 1001 ,S _ 1002,S _ 1003的记录(枚举)
SELECT * FROM stu
WHERE sid IN ('S_1001','S_1002','S_1003');
等同于
SELECT * FROM stu
WHERE sid='S_1001' or sid='S_1002' or sid='S_1003';
(4)查询学号不是S_1001,S_1002,S_1003的记录
SELECT * FROM tab_student
WHERE sid NOT IN('S1001','S1002','S_1003');
(5)查询年龄为null的记录
SELECT * FROM stu WHERE age IS NULL;
(6)查询年龄在20到40之间的学生记录
SELECT *
FROM stu
WHERE age>=20 AND age<=40;
或者
SELECT *
FROM stu
WHERE age BETWEEN 20 AND 40;
(7) 查询性别非男的学生记录
SELECT *
FROM stu
WHERE gender!='male';
或者
SELECT *
FROM stu
WHERE gender<>'male';
或者
SELECT *
FROM stu
WHERE NOT gender='male';
(8) 查询姓名不为null的学生记录
SELECT *
FROM stu
WHERE NOT sname IS NULL;
或者
SELECT *
FROM stu
WHERE sname IS NOT NULL;
3.3 模糊查询
当想查询姓名中包含a字母的学生时就需要使用模糊查询了。模糊查询需要使用关键字LIKE。
通配符:
_ 单个任意字符
%:任意长度的任意字符
‘张%’ —》张三丰 张三 张无忌
‘李_’ —>李四 李逵 李刚
(1)查询姓名由3个字符构成的学生记录
SELECT *
FROM stu
WHERE sname LIKE '___';
模糊查询必须使用LIKE关键字。其中 “”匹配任意一个字母,5个“”表示5个任意字母。
(2)查询姓名由5个字符构成,并且第5个字符为“i”的学生记录
SELECT *
FROM stu
WHERE sname LIKE '____i';
(3)查询姓名以“z”开头的学生记录
SELECT *
FROM stu
WHERE sname LIKE 'z%';
其中“%”匹配0~n个任何字符。
(4)查询姓名中第2个字符为“i”的学生记录
SELECT *
FROM stu
WHERE sname LIKE '_i%';
(5)查询姓名中包含“a”字符的学生记录
SELECT *
FROM stu
WHERE sname LIKE '%a%';
3.4 字段控制查询
(1)去除重复记录
去除重复记录(两行或两行以上记录中列的数据都相同),例如emp表中sal字段就存在相同的记录。当只查询emp表的sal字段时,那么会出现重复记录,那么想去除重复记录,需要使用DISTINCT:
SELECT DISTINCT sal FROM emp;
SELECT DISTINCT sal,comm FROM emp;
(2)查看雇员的月薪与佣金之和
因为sal和comm两列的类型都是数值类型,所以可以做加运算。如果sal或comm中有一个字段不是数值类型,那么会出错。
SELECT *,sal+comm FROM emp;
字符串的合并不能使用+ ,使用concat(ename,'____',job);
select *,concat(ename,'______',job) from emp;
comm列有很多记录的值为NULL,因为任何东西与NULL相加结果还是NULL,所以结算结果可能会出现NULL。下面使用了把NULL转换成数值0的函数IFNULL:
SELECT *,sal+IFNULL(comm,0) FROM emp;
(3)给列名添加别名
在上面查询中出现列名为sal+IFNULL(comm,0),这很不美观,现在我们给这一列给出一个别名,为total:
SELECT *, sal+IFNULL(comm,0) AS total FROM emp;
给列起别名时,是可以省略AS关键字的:
SELECT *,sal+IFNULL(comm,0) total FROM emp;
(4)给表加别名,使用时很方便
SELECT CONCAT(e.ename,'的工作是',e.job) as 描述 from emp e
3.5 排序
语法: SELECT 列名 FROM 表名 ORDER BY 排序列 [排序规则]
ASC 对前面排序列做升序排序 DESC 对前面排序列做降序排序
(1) 查询所有学生记录,按年龄升序排序
SELECT *
FROM stu
ORDER BY age ASC;
(2) 查询所有学生记录,按年龄降序排序
SELECT *
FROM stu
ORDER BY age DESC;
(3) 查询所有雇员,按月薪降序排序,如果月薪相同时,按编号升序排序
SELECT * FROM emp
ORDER BY sal DESC,empno ASC;
3.6 聚合函数
聚合函数是用来进行纵向操作的函数
l COUNT():统计指定列不为NULL的记录行数;
l MAX():计算指定列的最大值,如果指定列是字符串类型,那么使用字符串排序运算;
l MIN():计算指定列的最小值,如果指定列是字符串类型,那么使用字符串排序运算;
l SUM():计算指定列的数值和,如果指定列类型不是数值类型,那么计算结果为0;
l AVG():计算指定列的平均值,如果指定列类型不是数值类型,那么计算结果为0;
(1) COUNT
当需要纵向统计时可以使用COUNT()。
l 查询emp表中记录数:
SELECT COUNT(*) AS ‘cnt’ FROM emp;
l 查询emp表中有佣金的人数:
SELECT COUNT(comm) ‘cnt’ FROM emp;
注意,因为count()函数中给出的是comm列,那么只统计comm列非NULL的行数。
l 查询emp表中月薪大于2500的人数:
SELECT COUNT(*) FROM emp
WHERE sal > 2500;
l 统计月薪与佣金之和大于2500元的人数:
SELECT COUNT(*) AS cnt FROM emp WHERE sal+IFNULL(comm,0) > 2500;
l 查询有佣金的人数,以及有领导的人数:
SELECT COUNT(comm), COUNT(mgr)FROM emp;
(2) SUM和AVG
当需要纵向求和时使用sum()函数。
l 查询所有雇员月薪和:
SELECT SUM(sal) FROM emp;
l 查询所有雇员月薪和,以及所有雇员佣金和:
SELECT SUM(sal), SUM(comm) FROMemp;
l 查询所有雇员月薪+佣金和:
SELECT SUM(sal+IFNULL(comm,0))FROM emp;
l 统计所有员工平均工资:
SELECT AVG(sal) FROM emp;
(3) MAX和MIN
l 查询最高工资和最低工资:
SELECT MAX(sal), MIN(sal) FROM emp;
3.7 分组查询
语法 :SELECT 列名 FROM 表名 WHERE 条件 GROUP BY 分组依据(列)
当需要分组查询时需要使用GROUP BY子句,例如查询每个部门的工资和,这说明要使用部分来分组。
注:凡是和聚合函数同时出现的列名,则一定要写在group by 之后
一旦使用了分组查询,那么结果集中就只能允许出现分组条件的列或聚合函数列
3.7.1 分组查询
l 查询每个部门的部门编号和每个部门的工资和:
SELECT deptno, SUM(sal)
FROM emp
GROUP BY deptno;
l 查询每个部门的部门编号以及每个部门的人数:
SELECT deptno,COUNT(*)
FROM emp
GROUP BY deptno;
l 查询每个部门的部门编号以及每个部门工资大于1500的人数:
SELECT deptno,COUNT(*)
FROM emp
WHERE sal>1500
GROUP BY deptno;
3.7.2 HAVING子句
l 查询工资总和大于9000的部门编号以及工资和:
SELECT deptno, SUM(sal)
FROM emp
GROUP BY deptno
HAVING SUM(sal) > 9000;
注:having与where的区别:
1.having是在分组后对数据进行过滤,where是在分组前对数据进行过滤
2.having后面可以使用分组函数(统计函数)
where后面不可以使用分组函数。
where是对分组前记录的条件,如果某行记录没有满足WHERE子句的条件,那么这行记录不会参加分组;而 having是对分组后数据的约束。
3.8 LIMIT限制(方言) 常用于分页
- 注意
oracle使用的是rowNum这个伪列
LIMIT用来限定查询结果的起始行,以及总行数(行的下标是从0开始)。
pageIndex 当前页
pageSize 每页展示多少条数据
limit (pageIndex-1)*pageSize,pageSize;
1查询前5行记录,起始行从0开始
SELECT * FROM emp LIMIT 0, 5;
思考:
# pageIndex 当前页
# pageSize 每页的记录数
# (pageIndex-1)*pageSize,pageSize
# 每页分五条,求第二页数据
SELECT * from emp LIMIT 10,5
# 思考题:求pageMax(最大页数)
SELECT count(*) from emp;
# 第一步,执行上述语句求出总记录数totalCount
#第二步:pageMax=(totalCount+pageSize-1)/pageSize
# 或者:pageMax=totalCount%pageSize==0?totalCount/pageSize:totalCount/pageSize+1
3.9时间查询
语法: SELECT 时间函数([参数列表])
执行时间函数查询,会自动生成一张虚表(一行一列)
#当前系统时间
SELECT SYSDATE();
#获取指定日期为一年中的第几周 例如,当前日期
SELECT WEEK(SYSDATE());
3.10字符串查询
sql中字符串下标从1开始
3.11总结
select *from 表名 where 条件 group by 分组条件 having 筛选条件 order by 排序规则 limit ?,?
以上顺序绝对不能乱,可以缺省某些字段
查询语句书写顺序:select 列 from 表 【where- group by- having- order by-limit】
查询语句执行顺序:from 表 where -group by -having - select - order by-limit
-- 将工资高于1000的人按领导进行分组,将下属工资不足1300的领导筛选,对剩下的领导按下属工资升序排序,展示前两行数据
SELECT
e.mgr,
sum(e.sal)
FROM
emp e
WHERE
e.sal >= 1000
GROUP BY
e.mgr
HAVING
sum(e.sal) > 1300
ORDER BY
sum(e.sal)
LIMIT
0,2;
第四节 数据完整性
作用:保证用户输入的数据保存到数据库中是正确的。
确保数据的完整性 = 在创建表时给表中添加约束
完整性的分类:
- 实体完整性:
- 域完整性:
- 引用完整性: 学生表(学号 ,姓名) 成绩表( 学号,科目,成绩)
1.1 实体完整性约束
实体:即表中的一行(一条记录)代表一个实体(entity)
实体完整性的作用:标识每一行数据不重复。
约束类型:
主键约束(primary key)
唯一约束(unique)
自动增长列(auto_increment)
主键约束和唯一约束又名索引,在查询时,根据索引字段查询,查询速度会大大提高
1.1.1 主键约束(primary key)
注:每个表中要有一个主键。
特点:数据唯一,且不能为null
示例:
第一种添加方式:
CREATE TABLE student(
id int primary key,
name varchar(50)
);
第二种添加方式:此种方式优势在于,可以创建联合主键
CREATE TABLE student(
id int,
name varchar(50),
primary key(id)
);
CREATE TABLE student(
classid int,
stuid int,
name varchar(50),
primary key(classid,stuid)
);
第三种添加方式:(不推荐)
CREATE TABLE student(
id int,
name varchar(50)
);
ALTER TABLE student ADD PRIMARY KEY (id);
1.1.2 唯一约束(unique)
特点:数据不能重复。可以为null
CREATE TABLE student(
Id int primary key,
Name varchar(50) unique
);
1.1.3 自动增长列(auto_increment)
sqlserver数据库 (identity) oracle数据库( sequence)序列
自动增长不能单独使用,一般需要和主键配合。
给主键添加自动增长的数值,列只能是数值类型
CREATE TABLE student(
Id int primary key auto_increment,
Name varchar(50)
) auto_increment=100;
INSERT INTO student(name) values(‘tom’);
自动增长:是从上一次添加的值之后往后增长
1.2 域完整性约束
域完整性约束的作用:限制此单元格的数据正确,不对其它单元格起作用,域代表当前单元格
域完整性约束:数据类型、非空约束(not null)、默认值约束(default)
check约束(mysql不支持)check(sex='男’or sex=‘女’)
1.2.1 数据类型
数值类型
类型 | 大小 | 范围(有符号) | 范围(无符号) | 用途 |
---|---|---|---|---|
TINYINT | 1 字节 | (-128,127) | (0,255) | 小整数值 |
SMALLINT | 2 字节 | (-32 768,32 767) | (0,65 535) | 大整数值 |
MEDIUMINT | 3 字节 | (-8 388 608,8 388 607) | (0,16 777 215) | 大整数值 |
INT或INTEGER | 4 字节 | (-2 147 483 648,2 147 483 647) | (0,4 294 967 295) | 大整数值 |
BIGINT | 8 字节 | (-9 233 372 036 854 775 808,9 223 372 036 854 775 807) | (0,18 446 744 073 709 551 615) | 极大整数值 |
FLOAT | 4 字节 | 查看帮助文档 | 查看帮助文档 | 单精度浮点数值 |
DOUBLE | 8 字节 | 查看帮助文档 | 查看帮助文档 | 双精度浮点数值 |
DOUBLE(M,D) | 8个字节,M表示长度,D表示小数位数 | 同上,受M和D的约束 DUBLE(5,2) -999.99-999.99 | 同上,受M和D的约束 | 双精度浮点数值 |
DECIMAL(M,D) | 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 | 依赖于M和D的值,M最大值为65 | 依赖于M和D的值,M最大值为65 | 小数值 |
日期类型:
表示时间值的日期和时间类型为DATETIME、DATE、TIMESTAMP、TIME和YEAR。
每个时间类型有一个有效值范围和一个"零"值,当指定不合法的MySQL不能表示的值时使用"零"值。
TIMESTAMP类型有专有的自动更新特性
类型 | 大小(字节) | 范围 | 格式 | 用途 |
---|---|---|---|---|
DATE | 3 | 1000-01-01/9999-12-31 | YYYY-MM-DD | 日期值 |
TIME | 3 | ‘-838:59:59’/‘838:59:59’ | HH:MM:SS | 时间值或持续时间 |
YEAR | 1 | 1901/2155 | YYYY | 年份值 |
DATETIME | 8 | 1000-01-01 00:00:00/9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | 混合日期和时间值 |
TIMESTAMP | 4 | 1970-01-01 00:00:00/2038 结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038年1月19日 凌晨 03:14:07 | YYYYMMDD HHMMSS | 混合日期和时间值,时间戳 |
字符串类型:
字符串类型指CHAR、VARCHAR、BINARY、VARBINARY、BLOB、TEXT、ENUM和SET
类型 | 大小 | 用途 |
---|---|---|
CHAR | 0-255字符 | 定长字符串 char(10) 10个字符 |
VARCHAR | 0-65535 字节 | 变长字符串 varchar(10) 10个字符 |
TINYBLOB | 0-255字节 | 不超过 255 个字符的二进制字符串 |
TINYTEXT | 0-255字节 | 短文本字符串 |
BLOB(binary large object) | 0-65 535字节 | 二进制形式的长文本数据 |
TEXT | 0-65 535字节 | 长文本数据 |
MEDIUMBLOB | 0-16 777 215字节 | 二进制形式的中等长度文本数据 |
MEDIUMTEXT | 0-16 777 215字节 | 中等长度文本数据 |
LONGBLOB | 0-4 294 967 295字节 | 二进制形式的极大文本数据 |
LONGTEXT | 0-4 294 967 295字节 | 极大文本数据 |
CHAR和VARCHAR类型类似,但它们保存和检索的方式不同。它们的最大长度和是否尾部空格被保留等方面也不同。在存储或检索过程中不进行大小写转换。
BINARY和VARBINARY类类似于CHAR和VARCHAR,不同的是它们包含二进制字符串而不要非二进制字符串。也就是说,它们包含字节字符串而不是字符字符串。这说明它们没有字符集,并且排序和比较基于列值字节的数值值。
BLOB是一个二进制大对象,可以容纳可变数量的数据。有4种BLOB类型:TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB。它们只是可容纳值的最大长度不同。
有4种TEXT类型:TINYTEXT、TEXT、MEDIUMTEXT和LONGTEXT。
1.2.2 非空约束
not null
CREATE TABLE student(
Id int primary key,
Name varchar(50) not null,
Sex varchar(10)
);
INSERT INTO student values(1,’tom’,null);
1.2.3 默认值约束
default
CREATE TABLE student(
Id int primary key,
Name varchar(50) not null,
Sex varchar(10) default '男'
);
insert intostudent1 values(1,'tom','女');
insert intostudent1 values(2,'jerry',default);
1.3 引用完整性约束
(参照完整性约束)
外键约束:FOREIGN KEY
示例:
第一种添加外键方式。推荐
#学生表(主表)
CREATE TABLE student(
sid int primary key,
name varchar(50) not null,
sex varchar(10) default '男'
);
#成绩表(从表)
create table score(
id int,
score int,
stuid int ,
CONSTRAINT fk_score_stuid foreign key(stuid) references student(sid)
);
-- 外键列的数据类型一定要与主键的类型一致
第二种添加外键方式。
ALTER TABLE score ADD CONSTRAINT fk_stu_score FOREIGN KEY(sid) REFERENCES student(sid);
注意:当两张表存在引用关系,要执行删除操作,一定要先删除从表,再删除主表
第五节 多表查询
多个表之间是有关系的,那么关系靠谁来维护?
多表约束:外键约束。
5.1 多表的关系
5.1.1 一对多关系
客户和订单,分类和商品,部门和员工.
一对多建表原则:在多的一方创建一个字段,字段作为外键指向一方的主键.
天平理论:一个人和多个订单之间达成平衡,一个人和一个订单就会失衡,需要在订单上加点东西才能达成平衡,在订单表上多添加一个字段,去引入用户表的主键
5.1.2 多对多关系
学生和课程,用户和角色,召唤师和英雄,商品和购物车
多对多关系建表原则:需要创建第三张表,中间表中至少两个字段,这两个字段分别作为外键指向各自一方的主键.
5.1.3 一对一关系
一个丈夫对应一个妻子
一个员工对应一个简历
在实际的开发中应用不多.因为一对一可以创建成一张表.
两种建表原则:
唯一外键对应:在多的一方创建一个外键指向一的一方的主键,将外键设置为unique和非空.
主键对应:让一对一的双方的主键进行建立关系.
5.2 多表查询
多表查询有如下几种:
1 合并结果集;UNION 、 UNION ALL
2 连接查询
2.1内连接 [INNER] JOIN ON
2.2外连接 OUTER JOIN ON
- 左外连接 LEFT [OUTER] JOIN
- 右外连接 RIGHT [OUTER] JOIN
3 子查询
5.2.1 合并结果集
作用:合并结果集就是把两个select语句的查询结果合并到一起!
合并结果集有两种方式:
l UNION:去除重复记录,例如:SELECT* FROM t1 UNION SELECT * FROM t2;
l UNION ALL:不去除重复记录,例如:SELECT * FROM t1 UNION ALL SELECT * FROM t2。
注意:被合并的两个结果:列数必须相同,列类型可以不同。
5.2.2 连接查询
{a,b}
{1,2,3}
笛卡尔积
{a1,a2,a3,b1,b2,b3}
连接查询就是求出多个表的乘积,例如t1连接t2,那么查询出的结果就是t1*t2。
连接查询会产生笛卡尔积,假设集合A={a,b},集合B={0,1,2},则两个集合的笛卡尔积为{(a,0),(a,1),(a,2),(b,0),(b,1),(b,2)}。可以扩展到多个集合的情况。
那么多表查询产生这样的结果并不是我们想要的,那么怎么去除重复的,不想要的记录呢,当然是通过条件过滤。通常要查询的多个表之间都存在关联关系,那么就通过关联关系去除笛卡尔积。
示例 1:现有两张表
emp表
CREATE TABLE emp(
empno int,
ename varchar(50),
job varchar(50),
mgr int,
hiredate date,
sal decimal(7,2),
comm decimal(7,2),
deptno int
);
#添加数据SQL语句省略
dept表
CREATE TABLE dept(
deptno int,
dname varchar(14),
loc varchar(13)
);
#添加数据SQL语句省略
执行如下SQL语句
select * from emp,dept;
使用主外键关系做为条件来去除无用信息
SELECT * FROM emp,dept WHERE emp.deptno=dept.deptno;
上面查询结果会把两张表的所有列都查询出来,也许你不需要那么多列,这时就可以指定要查询的列了。
SELECT emp.ename,emp.sal,emp.comm,dept.dname
FROM emp,dept
WHERE emp.deptno=dept.deptno;
一:内连接(INNER JOIN ON)
上面的连接语句就是内连接,但它不是SQL标准中的查询方式,可以理解为方言!
SQL标准的内连接为:
SELECT *
FROM emp e
INNER JOIN dept d
ON e.deptno=d.deptno;
注意:on后面 主外键关系
方言:
SELECT e.empno,e.ename,e.job,e.mgr,e.hiredate,e.sal,e.comm,d.dname,d.loc from
dept d,emp e
where
d.deptno=e.deptno
内连接的特点:只展示两者公有的部分
二:外连接
包括左外连接(LEFT JOIN ON)和右外连接(RIGHT JOIN ON),外连接的特点:查询出的结果存在不满足条件的可能。
主表中的内容全部展示,匹配表中不满足on的就不展示
a.左外连接:以左表为主表,右表是从表
SELECT * FROM emp e
LEFT OUTER JOIN dept d
ON e.deptno=d.deptno;
左连接是先查询出左表(即以左表为主),然后查询右表,左表中满足条件和不满足条件都显示出来,右边不满足条件的显示NULL。
我们还是用上面的例子来说明。其中emp表中“张三”这条记录中,部门编号为50,而dept表中不存在部门编号为50的记录,所以“张三”这条记录,不能满足e.deptno=d.deptno这条件。但在左连接中,因为emp表是左表,所以左表中的记录都会查询出来,即“张三”这条记录也会查出,但相应的右表部分显示NULL。
b.右外连接
右连接就是先把右表中所有记录都查询出来,然后左表满足条件的显示,不满足显示NULL。例如在dept表中的40部门并不存在员工,但在右连接中,如果dept表为右表,那么还是会查出40部门,但相应的员工信息为NULL。
SELECT * FROM emp e
RIGHT OUTER JOIN dept d
ON e.deptno=d.deptno;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dhFbTbXV-1652699647534)(H:\千峰\HF-java完整版\第二阶段\第4天mysql\课件\pic\037.jpg)]
连接查询总结:
连接不限于两张表,连接查询也可以是三张、四张,甚至N张表的连接查询。通常连接查询不可能需要整个笛卡尔积,而只是需要其中一部分,那么这时就需要使用条件来去除不需要的记录。这个条件大多数情况下都是使用主外键关系去除。
5.2.3 子查询
子查询就是select语句的结果集只有一列数据时,可以将整个语句当成条件去使用,放在where后面;如果是多列数据时,那么就可以当成一张新的表去查询,此时将语句放在from后面
一个select语句中包含另一个完整的select语句。
子查询就是嵌套查询,即SELECT中包含SELECT,如果一条语句中存在两个,或两个以上SELECT,那么就是子查询语句了。里面的查询叫做子查询,外层的查询叫父查询,一般情况都是先执行子查询,再执行父查询。
l 子查询出现的位置:
a. where后,作为被查询的条件的一部分;结果集只有一列数据
b. from后,作临时表;结果可以是多列
l 当子查询出现在where后作为条件时,还可以使用如下关键字:
a. all,比最值还要最
b. any常用于不低于或者不高于这样问法,不大于最大值,不小于最小值
l 子查询结果集的常见形式:
a. 单行单列(用于条件)
b. 多行单列(用于条件)
c. 多行多列(用于表)
示例:
1. 工资高于JONES的员工。
分析:
查询条件:工资>JONES工资,其中JONES工资需要一条子查询。
第一步:查询JONES的工资
SELECT sal FROM emp WHERE ename='JONES';
第二步:查询高于JONES工资的员工
SELECT * FROM emp WHERE sal > (第一步结果);
结果:
SELECT * FROM emp WHERE sal > (SELECT sal FROM emp WHERE ename='JONES');
2. 查询与SCOTT同一个部门的员工。
l 子查询作为条件
l 子查询形式为单行单列
分析:
查询条件:部门=SCOTT的部门编号,其中SCOTT 的部门编号需要一条子查询。
第一步:查询SCOTT的部门编号
SELECT deptno FROM emp WHERE ename='SCOTT';
第二步:查询部门编号等于SCOTT的部门编号的员工
SELECT * FROM emp WHERE deptno = (SELECT deptno FROM emp WHERE ename='SCOTT');
3. 工资高于30号部门所有人的员工信息
分析:
SELECT * FROM emp WHERE sal>(SELECT MAX(sal)FROM emp WHERE deptno=30);
查询条件:工资高于30部门所有人工资,其中30部门所有人工资是子查询。高于所有需要使用all关键字。
第一步:查询30部门所有人工资
SELECT sal FROM emp WHERE deptno=30;
第二步:查询高于30部门所有人工资的员工信息
SELECT * FROM emp WHERE sal > ALL (第一步)
结果:
SELECT * FROM emp WHERE sal > ALL (SELECT sal FROM emp WHERE deptno=30)
3.1求出不低于30部门员工工资的员工信息
l 子查询作为条件
l 子查询形式为多行单列(当子查询结果集形式为多行单列时可以使用ALL或ANY关键字)
4.from后面子查询(子查询作为临时表,要为其赋予一个临时表名)
求出30这个部门的最小工资的员工
#求出30这个部门的最小工资的员工
#求出30这个部门的员工
SELECT * from emp where deptno=30
#在新表中再去查询最小工资
SELECT min(temp.sal) from (SELECT * from emp where deptno=30) AS temp
5.2.4 导出导入数据库
1 使用命令方式
导出数据库表
mysqldump -uroot -p 数据库名 > school.sql
导入数据库表
mysql -u root -p
mysql>use 数据库
然后使用source命令,后面参数为脚本文件(如这里用到的.sql)
mysql>source d:/dbname.sql
2 使用SQLyog工具完成导出导入(演示操作)
1 先备份,右击数据库选择备份---》备份数据库,转储到sql
2 导入,右击选择 "执行SQL脚本"
5.2.5 创建用户和授权(DCL)
要使用命令行登录。
创建用户
CREATE USER `zhangsan` IDENTIFIED BY '123';
CREATE USER `zhangsan`@`localhost` IDENTIFIED BY '123';
授权(数据库.表)
GRANT ALL ON school.* TO `zhangsan`;
撤销权限(重新连接客户端才会生效)
REVOKE ALL ON school.* FROM `zhangsan`;
删除用户
DROP USER `zhangsan`;
第六节 综合练习
某网上商城数据库表结构如下:
# 创建用户表
create table user(
userId int primary key auto_increment,
username varchar(20) not null,
password varchar(18) not null,
address varchar(100),
phone varchar(11)
);
#一对多的实现
#创建分类表
create table category(
cid varchar(32) PRIMARY KEY ,
cname varchar(100) not null #分类名称
);
# 商品表
CREATE TABLE `products` (
`pid` varchar(32) PRIMARY KEY,
`name` VARCHAR(40) ,
`price` DOUBLE(7,2),
category_id varchar(32),
constraint foreign key(category_id) references category(cid)
);
#多对多的实现
#订单表
create table `orders`(
`oid` varchar(32) PRIMARY KEY ,
`totalprice` double(12,2), #总计
`userId` int,
constraint foreign key(userId) references user(userId) #外键
);
# 订单项表
create table orderitem(
oid varchar(32), #订单id
pid varchar(32), #商品id
num int , #购买商品数量
primary key(oid,pid), #主键
foreign key(oid) references orders(oid),
foreign key(pid) references products(pid)
);
#初始化数据
#用户表添加数据
INSERT INTO USER(username,PASSWORD,address,phone) VALUES('张三','123','北京昌平沙河','13812345678');
INSERT INTO USER(username,PASSWORD,address,phone) VALUES('王五','5678','北京海淀','13812345141');
INSERT INTO USER(username,PASSWORD,address,phone) VALUES('赵六','123','北京朝阳','13812340987');
INSERT INTO USER(username,PASSWORD,address,phone) VALUES('田七','123','北京大兴','13812345687');
#给商品表初始化数据
insert into products(pid,name,price,category_id) values('p001','联想',5000,'c001');
insert into products(pid,name,price,category_id) values('p002','海尔',3000,'c001');
insert into products(pid,name,price,category_id) values('p003','雷神',5000,'c001');
insert into products(pid,name,price,category_id) values('p004','JACK JONES',800,'c002');
insert into products(pid,name,price,category_id) values('p005','真维斯',200,'c002');
insert into products(pid,name,price,category_id) values('p006','花花公子',440,'c002');
insert into products(pid,name,price,category_id) values('p007','劲霸',2000,'c002');
insert into products(pid,name,price,category_id) values('p008','香奈儿',800,'c003');
insert into products(pid,name,price,category_id) values('p009','相宜本草',200,'c003');
insert into products(pid,name,price,category_id) values('p010','梅明子',200,null);
#给分类表初始化数据
insert into category values('c001','电器');
insert into category values('c002','服饰');
insert into category values('c003','化妆品');
insert into category values('c004','书籍');
#添加订单
insert into orders values('o6100',18000.50,1);
insert into orders values('o6101',7200.35,1);
insert into orders values('o6102',600.00,2);
insert into orders values('o6103',1300.26,4);
#订单详情表
insert into orderitem values('o6100','p001',1),('o6100','p002',1),('o6101','p003',1)
6.1 综合练习1-【多表查询】
1>查询所有用户的订单
连接查询
SELECT
u.username,
o.oid,
o.totalprice
FROM
`user` u
INNER JOIN orders o ON u.userId = o.userId
2>查询用户id为 1 的所有订单详情
SELECT
u.username,
o.oid,
o.totalprice,
oi.pid,
oi.num
FROM
`user` u
INNER JOIN orders o ON u.userId = o.userId
INNER JOIN orderitem oi ON o.oid=oi.oid
6.2 综合练习2-【子查询】
1>查看用户为张三的订单
SELECT * FROM orders WHERE userId=(SELECT userid FROM USER WHERE username='张三');
2>查询出订单的价格大于800的所有用户信息。
SELECT * FROM USER WHERE userId IN (SELECT DISTINCT userId FROM orders WHERE totalprice>800)
6.3 综合练习3-【分页查询】
1>查询所有订单信息,每页显示5条数据
#查询第一页
SELECT * FROM orders LIMIT 0,5
总结
要求:
1.建表(约束,数据类型,行,列)
2.sql:DML,DDL(create),DQL
3.连接查询:内连接,外连接
4.子查询(对于笔试题非常的重要)
拓展:
1.视图:(将结果集进行封装)
2.索引,唯一索引,主键索引,索引他是和sql优化有关(四阶段后,面试前两周去看一看)
3.触发器(四阶段后,面试前两周去看一看)
4.存储过程和函数(mysql禁止使用存储过程和存储函数)
5.存储引擎InnerDB和myISAM(事务有关系)
mysql优化30条,触发器和索引(冲10K的,一定要看)
第七节 数据库事务
确保数据的完整性和一致性
7.1 事务概述
一组要么同时执行成功,要么同时失败的SQL语句。是数据库操作的一个不能分割执行单元。
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
7.1.1事务原理
数据库会为每一个客户端都维护一个空间独立的缓存区(回滚段),一个事务中所有的增删改语句的执行结果都会缓存在回滚段中,只有当事务中所有SQL语句均正常结束(commit),才会将回滚段的数据同步到数据库。否则无论因为哪种原因失败,整个事务将回滚(rollback)。
了解:InnerDB存储引擎(默认的),他是支持事务管理
MyISAM不支持事务管理的
事务开始于
- 连接到数据库上,并执行一条DML语句insert、update或delete
- 前一个事务结束后,又输入了另一条DML语句
事务结束于
- 执行commit或rollback语句。
- 执行一条DDL语句,例如create table语句,在这种情况下,会自动执行commit语句。
- 执行一条DDL语句,例如grant语句,在这种情况下,会自动执行commit。
- 断开与数据库的连接
- 执行了一条DML语句,该语句却失败了,在这种情况中,会为这个无效的DML语句执行rollback语句。
7.2 事务的四大特点
(ACID)
- Atomicity(原子性)
表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败
- Consistency(一致性)
表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前状态
- Isolation(隔离性)
事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。
- Durability(持久性)
持久性事务完成之后,它对于系统的影响是永久性的。
如何添加事务
1.编程式事务
2.声明式事务(spring中会去讲)
编程式事务
注意要保证四个sql用的是同一个connnetion
try{
//开启事务 START TRANSACTION;
//setAutoCommit=0; 禁止自动提交 setAutoCommit=1; 开启自动提交
//开启事务(设置为手动提交)
con.setAutoCommit(false);
业务代码
//手动提交
con.commit();
}catch(Exception e){
//回滚
con.rollback();
con.commit();//可加可不加
}
案例演示
CREATE TABLE account (
id INT PRIMARY KEY auto_increment,
username VARCHAR (10),
balance DECIMAL (10, 2)
);
INSERT INTO account (username, balance)
VALUES
('张三', '10000.00'),
('李四', '0');
package com.qf.test;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.qf.model.Account;
import com.qf.util.JDBCUtil;
/**
* 如何添加事务
* 编程式事务
* 声明式事务
* @author sks
*
*/
public class Test {
public static void main(String[] args) {
//模拟一下转账功能
Integer fromId=1;//转账人
Integer toId=2;//被转账人
BigDecimal money=new BigDecimal("1000");//转账金额
Connection con=null;
PreparedStatement pre=null;
ResultSet rs=null;
Account fromAccount=null;
Account toAccount=null;
try {
con=JDBCUtil.getCon();
//开启事务
con.setAutoCommit(false);
//1.查询转账人的相关信息
String sql="select * from account where id=?";
pre=con.prepareStatement(sql);
pre.setInt(1, fromId);
rs = pre.executeQuery();
while(rs.next()){
int id = rs.getInt("id");
String username = rs.getString("username");
BigDecimal balance = rs.getBigDecimal("balance");
fromAccount=new Account(id, username, balance);
}
//2.验证是都能发生转账
System.out.println(fromAccount);
if(fromAccount.getBalance().compareTo(money)<0){
System.out.println("余额不足");
return;
}
//3.修改转账人的余额
BigDecimal newFromBalance = fromAccount.getBalance().subtract(money);
fromAccount.setBalance(newFromBalance);
System.out.println(fromAccount);
sql="update account set balance=? where id=?";
pre=con.prepareStatement(sql);
pre.setBigDecimal(1, fromAccount.getBalance());
pre.setInt(2, fromAccount.getId());
pre.executeUpdate();
//断电了。。。
int a=10/0;
//4.查询被转账人相关信息
sql="select * from account where id=?";
pre=con.prepareStatement(sql);
pre.setInt(1, toId);
rs = pre.executeQuery();
while(rs.next()){
int id = rs.getInt("id");
String username = rs.getString("username");
BigDecimal balance = rs.getBigDecimal("balance");
toAccount=new Account(id, username, balance);
}
System.out.println(toAccount);
//5.修改被转账人金额
BigDecimal newToBalance = toAccount.getBalance().add(money);
toAccount.setBalance(newToBalance);
sql="update account set balance=? where id=?";
pre=con.prepareStatement(sql);
pre.setBigDecimal(1, toAccount.getBalance());
pre.setInt(2, toAccount.getId());
pre.executeUpdate();
//没有异常,照常执行,提交事务
con.commit();
} catch (Exception e) {
//发生异常,数据就需要回滚
System.out.println("发生回滚啦");
try {
con.rollback();
con.commit();//可加可不加
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.println("失败啦");
e.printStackTrace();
}finally{
JDBCUtil.close(con, pre, rs);
}
}
}
Mysql支持的事务语句
#开启事务
START TRANSACTION; # connection.setAutoCommit(false);
UPDATE account SET money=money-1000 WHERE id=1;
UPDATE account SET money=money+1000 WHERE id=2;
#提交事务
COMMIT;#connection.commit();
#回滚
ROLLBACK; #connection.rollback();
7.3 事务隔离级别
SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
Read Uncommitted(读取未提交内容)
读到事务没有提交的数据,第一次读是1000,但是事务被回滚了,第二次度是0,两次读的不一样,称之为脏读
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
Read Committed(读取提交内容)(字段锁)
新的问题
,第一次读,1.张三,男,18,然后这个时候出现有一个人将数据修改为1,李四。女,19;,那么稀释第二次读的时候就会和之前的数据不一样,这两次读取内容不一致,我们称之为不可重复读
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别出现不可重复读(Nonrepeatable Read)问题,因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
**Repeatable Read **可重读(行锁)
幻读
,我在做分页,第一执行select count(*) from xxx;读出来是1000,我分了10页,突然有人往表中添加了1000条数据,此时会造成两次查询记录数会不一样
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读(Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻读” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
Serializable 可串行化(表锁)
新的问题
:性能太低了
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。效率最低的。
这四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题。
最低的隔离级别 读未提交===》脏读的问题==》(加字段锁)读已提交==》不可重复读的问题==》(加行锁)可重复读===》幻读的问题==》(表锁)串行化===》性能极低的问题
例如:
脏读(Drity Read):某个事务已更新一份数据未提交前,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。但是InnoDB存储引擎通过多版本并发控制机制解决了该问题。
7.4 JDBC中事务应用
JDBC连接默认处于自动提交模式,则每个SQL语句在完成后都会提交到数据库。
事务使您能够控制是否和何时更改应用于数据库。它将单个SQL语句或一组SQL语句视为一个逻辑单元,如果任何语句失败,则整个事务将失败。
要启用手动事务支持,而不是JDBC驱动程序默认使用的自动提交模式,请使用Connection对象的**setAutoCommit()**方法。如果将boolean false传递给setAutoCommit(),则关闭自动提交。我们可以传递一个布尔值true来重新打开它。
7.4.1 事务的提交和回滚
完成更改后,我们要提交更改,然后在连接对象上调用**commit()**方法,如下所示:
conn.commit( );
否则,要使用连接名为conn的数据库回滚更新,请使用以下代码 -
conn.rollback( );
try{
//开启事务
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
String SQL = "INSERT INTO Employees " +
"VALUES (106, 20, 'Rita', 'Tez')";
stmt.executeUpdate(SQL);
//有可能出现异常
String SQL = "INSERT IN Employees " +
"VALUES (107, 22, 'Sita', 'Singh')";
stmt.executeUpdate(SQL);
// 没有错误提交
conn.commit();
}catch(SQLException se){
//出现错误回滚
conn.rollback();
conn.commit();
}
7.4.2 Savepoint
新的JDBC 3.0 Savepoint接口为您提供了额外的事务控制。
设置保存点时,可以在事务中定义逻辑回滚点。如果通过保存点发生错误,则可以使用回滚方法来撤消所有更改或仅保存在保存点之后所做的更改。
Connection对象有两种新的方法来帮助您管理保存点 -
- **setSavepoint(String savepointName):**定义新的保存点。它还返回一个Savepoint对象。
- **releaseSavepoint(Savepoint savepointName):**删除保存点。请注意,它需要一个Savepoint对象作为参数。此对象通常是由setSavepoint()方法生成的保存点。
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
Savepoint savepoint1 = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "root");
// Assume a valid connection object conn
conn.setAutoCommit(false);
stmt = conn.createStatement();
// set a Savepoint
String SQL = "INSERT INTO Employees " + "VALUES (106, 20, 'Rita', 'Tez');";
stmt.executeUpdate(SQL);
savepoint1 = conn.setSavepoint("Savepoint1");
// Submit a malformed SQL statement that breaks
SQL = "INSERT IN Employees " + "VALUES (107, 22, 'Sita', 'Tez')";
stmt.executeUpdate(SQL);
// If there is no error, commit the changes.
conn.commit();
System.out.println("执行成功");
} catch (Exception se) {
// If there is any error.
try {
se.printStackTrace();
conn.rollback(savepoint1);
conn.commit();
System.out.println("回滚");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
第八节 扩展
一.MySQL视图
1.定义:视图就是一条select语句执行后返回的结果集
2.使用场景:权限控制的时候,不希望用户访问表中某些含敏感信息的列,比如salary工资
3.语法(创建):create view 视图名称
as
select 语句
(修改):alter view 视图名称
as
select 语句
(删除):drop view if exists 视图名称; 先判断是否存在再进行删除
4.查看 show tables;
注意:视图创建成功后可以在表展示中显示,但实际不存在,相当于一个虚拟表,使用方法按照表操作进行
二.MySQL存储过程
1.定义:存储在数据库当中可以执行特定工作(查询和更新)的一组SQL代码的程序段。类似于功能函数
2.语法(创建):create procedure 存储过程名称(参数列表)
begin
存储操作的语句块;
end
注意:当语句结束;有冲突时使用delimiter定义新的结束方法
(调用):call 存储过程名称;
(有参调用):call 存储过程名称(参数1的值,参数2的值,…);
(查看所有的存储过程):show procedure status;
(删除存储过程):drop procedure if exists 存储过程名称;
3.参数
①in 给参数传入值,定义的参数就得到了值
语法:in 参数名 表中字段的类型
②out 返回值
语法:out 参数名 返回的数据类型
4.存储过程分为
①无参无返回值的存储过程
②有参无返回值的存储过程
③无参有返回值的存储过程
④有参有返回值的存储过程
三.MySQL触发器
1.定义:当一件事情发生的时候会引发其他相关的事件发生
只有当一个预定义的事件发生的时候,就会被MySQL自动调用
2.语法:(创建):create trigger 触发器名称{before|after}{inster|update|delete} on 表名
for each row
begin
触发器执行的语句块
end
注意:当语句结束;有冲突时使用delimiter定义新的结束方法
(查看所有的触发器):show triggers;
(删除存储过程):drop trigger if exists 触发器名称;
面试题
如何优化sql
1.禁止在结果集使用通配符*
2.禁止使用in语句
3.当执行查询条件时,尽量使用加了索引的字段来进行查询
主键约束和唯一约束又名索引,在查询时,根据索引字段查询,查询速度会大大提高
4.在实际开发尽量避免使用子查询,多使用连接查询
2.JDBC
JDBC概述和CRUD
第一节 关于JDBC的简介
1.1 简介
JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。
JDBC可以在各种平台上使用Java,如Windows,Mac OS和各种版本的UNIX。
JDBC库包括通常与数据库使用相关的API。
- 连接数据库。
- 创建SQL或MySQL语句。
- 在数据库中执行SQL或MySQL查询。
- 查看和修改生成的记录。
1.2 JDBC体系结构
JDBC API支持用于数据库访问的两层和三层处理模型,但通常,JDBC体系结构由两层组成:
- **JDBC:**提供了应用程序到数据库连接规范。
- JDBC驱动程序: 连接数据库的驱动程序的实现。
JDBC API使用驱动程序管理器和特定于数据库的驱动程序来提供与异构数据库的透明连接。
1.3 JDBC核心组件
**DriverManager此(类)**类管理数据库驱动程序列表。使用通信协议将来自java应用程序的连接请求与适当的数据库驱动程序匹配。(驱动管理,所有的驱动交给他进行管理,java代码只负责和DriverManager做交互)
Driver:此接口处理与数据库服务器的通信,我们很少会直接与Driver对象进行交互。而是使用DriverManager对象来管理这种类型的对象。
**Connection:**该接口具有用于连接数据库的所有方法。连接对象表示通信上下文,数据库的所有通信仅通过连接对象。
Statement:使用从此接口创建的对象将SQL语句提交到数据库。除了执行存储过程之外,一些派生接口还接受参数。
**ResultSet:**在使用Statement对象执行SQL查询后,这些对象保存从数据库检索的数据。它作为一个迭代器,允许我们移动其数据。
**SQLException:**此类处理数据库应用程序中发生的任何异常。
第二节 JDBC相关的SQL语法
2.1 CRUD语法介绍
SQL 是一种标准化的语言,它允许你在数据库上执行操作,如创建项目,查询内容,更新内容,并删除条目等操作。
Create, Read, Update, and Delete 通常称为CRUD操作。
CREATE DATABASE语句用于创建新的数据库:
SQL> CREATE DATABASE DATABASE_NAME;
DROP DATABASE语句用于删除现有数据库:
SQL> DROP DATABASE DATABASE_NAME;
CREATE TABLE语句用于创建新表。语法是 -
SQL> CREATE TABLE Employees
(
id INT NOT NULL,
age INT NOT NULL,
first VARCHAR(255),
last VARCHAR(255),
PRIMARY KEY ( id )
);
DROP TABLE语句用于删除现有表。
SQL> DROP TABLE table_name;
INSERT的语法类似于以下内容,其中column1,column2等表示要显示在相应列中的新数据
SQL> INSERT INTO table_name VALUES (column1, column2, ...);
SELECT语句用于从数据库中检索数据。SELECT的语法是 -
SQL> SELECT column_name, column_name, ...
FROM table_name
WHERE conditions;
WHERE子句可以使用比较运算符,例如=,!=,<,>,<=和> =,以及BETWEEN和LIKE运算符。
UPDATE语句用于更新数据。
SQL> UPDATE table_name
SET column_name = value, column_name = value, ...
WHERE conditions;
WHERE子句可以使用比较运算符,例如=,!=,<,>,<=和> =,以及BETWEEN和LIKE运算符。
DELETE语句用于从表中删除数据。
SQL> DELETE FROM table_name WHERE conditions;
WHERE子句可以使用比较运算符,例如=,!=,<,>,<=和> =,以及BETWEEN和LIKE运算符。
第三节 JDBC初始
3.1 使用步骤
构建JDBC应用程序涉及以下六个步骤:
- **导入JDBC驱动包:**需要下载包含数据库编程所需的JDBC的jar包。
- **注册JDBC驱动程序:**要求您初始化驱动程序,以便您可以打开与数据库的通信通道。
- **创建连接:**需要使用*DriverManager.getConnection()*方法创建一个Connection对象,该对象表示与数据库的物理连接。
- **获得sql执行器,执行sql:**需要使用类型为Statement的对象来构建和提交SQL语句到数据库。
- 处理执行结果需要使用相应的*ResultSet.getXXX()*方法从结果集中检索数据。
- **释放资源:**需要明确地关闭所有数据库资源,而不依赖于JVM的垃圾收集。
(1)导入jar包,在项目下创建lib目录,把mysql的jdbc包放入此目录,并添加到build path中(idea不同)。
(2)注册驱动
第一种方式(推荐写法):Class.forName()
注册驱动程序最常见的方法是使用Java的**Class.forName()**方法,将驱动程序的类文件动态加载到内存中,并将其自动注册
try {
Class.forName("com.mysql.jdbc.Driver");
}
catch(ClassNotFoundException ex) {
System.out.println("Error: unable to load driver class!");
System.exit(1);
}
第二种方式:是使用静态**DriverManager.registerDriver()**方法。
try {
Driver myDriver = new com.mysql.jdbc.Driver();
DriverManager.registerDriver( myDriver );
}
catch(ClassNotFoundException ex) {
System.out.println("Error: unable to load driver class!");
System.exit(1);
}
* 1>驱动程序注册两次
* 2>java程序依赖mysql驱动包
(3)获取连接
3.1数据库URL配置
加载驱动程序后,可以使用**DriverManager.getConnection()**方法建立连接。为了方便参考,让我列出三个重载的DriverManager.getConnection()方法 -
- getConnection(String url)
- getConnection(String url,Properties prop)
- getConnection(String url,String user,String password)
RDBMS | JDBC驱动程序名称 | 连接字符串格式 |
---|---|---|
MySQL的 | com.mysql.jdbc.Driver | **jdbc:mysql://**hostname:3306 / databaseName |
ORACLE | oracle.jdbc.driver.OracleDriver | jdbc:oracle:thin:@ hostname:port Number:databaseName |
DB2 | COM.ibm.db2.jdbc.net.DB2Driver | **jdbc:db2:**hostname:port Number / databaseName |
SYBASE | com.sybase.jdbc.SybDriver | jdbc:sybase:Tds: hostname:port Number / databaseName |
3.2 创建数据库连接对象
String URL = "jdbc:mysql://localhost:3306/emp";
String USER = "root";
String PASS = "root"
Connection conn = DriverManager.getConnection(URL, USER, PASS);
(4)执行查询(暂不执行,下一章内容)
(5)处理结果(暂不处理,下一章内容)
(6)关闭数据库连接
为确保连接关闭,您可以在代码中提供一个“finally”块。finally块总是执行,不管是否发生异常。
要关闭上面打开的连接,你应该调用close()方法如下 -
conn.close();
第四节 JDBC执行SQL语句
一旦获得了连接,我们可以与数据库进行交互。JDBC Statement和PreparedStatement接口定义了使您能够发送SQL命令并从数据库接收数据的方法和属性。
接口 | 使用 |
---|---|
Statement | 用于对数据库进行通用访问。在运行时使用静态SQL语句时很有用。Statement接口不能接受参数。 |
PreparedStatement(推荐使用) | 当您计划多次使用SQL语句时使用。PreparedStatement接口在运行时接受输入参数。 |
4.1 Statement
创建语句对象
在使用Statement对象执行SQL语句之前,需要使用Connection对象的createStatement()方法创建一个,如下例所示:
Statement stmt = null;
try {
stmt = conn.createStatement( );
. . .
}
catch (SQLException e) {
. . .
}
finally {
. . .
}
创建Statement对象后,您可以使用它来执行一个SQL语句,其中有三个执行方法之一。
- boolean execute(String SQL):如果可以检索到ResultSet对象,则返回一个布尔值true; 否则返回false。使用此方法执行SQL DDL语句或需要使用真正的动态SQL时。
- int executeUpdate(String SQL):返回受SQL语句执行影响的行数。使用此方法执行预期会影响多个行的SQL语句,例如INSERT,UPDATE或DELETE语句。
- ResultSet executeQuery(String SQL):返回一个ResultSet对象。当您希望获得结果集时,请使用此方法,就像使用SELECT语句一样。
关闭Statement对象
就像我们关闭一个Connection对象以保存数据库资源一样,由于同样的原因,还应该关闭Statement对象。
一个简单的调用close()方法将执行该作业。如果先关闭Connection对象,它也会关闭Statement对象。但是,应始终显式关闭Statement对象,以确保正确清理。
Statement stmt = null;
try {
stmt = conn.createStatement( );
. . .
}
catch (SQLException e) {
. . .
}
finally {
stmt.close();
}
4.2 ResultSet
SELECT语句是从数据库中选择行并在结果集中查看行的标准方法。该java.sql.ResultSet中的接口表示结果集数据库查询。
ResultSet对象维护指向结果集中当前行的游标。术语“结果集”是指包含在ResultSet对象中的行和列数据。
如果没有指定任何ResultSet类型,您将自动获得一个TYPE_FORWARD_ONLY。
类型 | 描述 |
---|---|
ResultSet.TYPE_FORWARD_ONLY | 光标只能在结果集中向前移动。 |
ResultSet.TYPE_SCROLL_INSENSITIVE | 光标可以向前和向后滚动,结果集对创建结果集后发生的数据库的其他更改不敏感。 |
ResultSet.TYPE_SCROLL_SENSITIVE。 | 光标可以向前和向后滚动,结果集对创建结果集之后发生的其他数据库所做的更改敏感。 |
演示案例1:查询数据
package com.qf.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class SelectDemo {
public static void main(String[] args) {
//1.导jar包
//2.加载驱动
Connection con=null;
Statement sta=null;
ResultSet rs=null;
List<User> list=new ArrayList<>();
try {
Class.forName("com.mysql.jdbc.Driver");
//3.获得连接
String url="jdbc:mysql://localhost:3306/2003-java";
String user="root";
String password="1234";
con=DriverManager.getConnection(url, user, password);
//4.获得sql执行器,并且执行sql
String sql="select userId as id,username,password,address,phone from user where userId=1";
sta = con.createStatement();
System.out.println(sql);
rs = sta.executeQuery(sql);
//5.处理结果
//next()是用来判断有没有下一个值,如果有,则将‘指针’指向下一个值
while(rs.next()){
int id = rs.getInt("id");
String username = rs.getString("username");
String myPassword = rs.getString("password");
String address = rs.getString("address");
String phone=rs.getString("phone");
User u=new User(id, username, myPassword, address, phone);
list.add(u);
}
//时间类型的处理
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
try {
rs.close();
sta.close();
con.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(User u:list){
System.out.println(u.toString());
}
}
}
演示案例2:添加数据
package com.qf.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import com.mysql.jdbc.Driver;
public class Demo {
public static void main(String[] args) {
String username="赵六";
String myPassword="111111";
String address="安徽合肥";
String phone="15611113222";
//需求是往mysql中2003-java库中的user表去插入一条数据
//1.导jar包(找到mysql所提供的驱动)
//在工程下新建folder,命名为lib,将mysql-connector-java.jar放进去
//所需要右击build path到我们的工程中(看到一个小奶瓶)
//2.加载驱动(由于Driver类的静态代码块中已经包含了注册驱动的代码,所以不需要再次注册)
Connection con=null;
Statement sta=null;
try {
Class.forName("com.mysql.jdbc.Driver");
//3.通过DriverManager类来获得连接
/**
* url:资源定位符,数据库的位置
* user:用户名
* password:密码
*/
//如果是连接本地库,可以省略localhost:3306
String url="jdbc:mysql:///2003-java";
String user="root";
String password="1234";
con = DriverManager.getConnection(url, user, password);
if(con!=null){
System.out.println("连接建立成功");
}
//4.获得sql语句的执行器(矿车)
sta = con.createStatement();
//5.将sql语句交给执行器,并且获得结果
String sql="insert into user(username,password,address,phone)values('"+username+"','"+myPassword+"','"+address+"','"+phone+"')";
//一定一定要切记,打印sql后要去navcat执行下
System.out.println(sql);
//DML语句使用的都是executeUpdate
int executeUpdate = sta.executeUpdate(sql);
//6.分析处理结果
if(executeUpdate==1){
System.out.println("新增成功");
}
//7.关闭资源
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
try {
sta.close();
con.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/*
* 这是一种非常过时的做法
* //2.注册驱动(需要将驱动交给DriverManager去管理)
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}*/
}
}
演示案例3:更新数据
public class JdbcDemo4 {
public static void main(String[] args) {
Connection connection=null;
Statement stat=null;
//2 注册驱动
try {
Class.forName("com.mysql.jdbc.Driver");
//3打开连接
connection=DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "root");
//4创建命令并执行
stat=connection.createStatement();
int result=stat.executeUpdate("update dept set loc='内蒙' where deptno=80");
if(result>0){
System.out.println("更新成功");
}else{
System.out.println("更新失败");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(stat!=null){
try {
stat.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
演示案例4:删除
package com.qf.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.function.Consumer;
public class DelDemo {
public static void main(String[] args) {
//1.导jar包
//2.加载驱动
int id=5;
Connection con=null;
Statement sta=null;
try {
Class.forName("com.mysql.jdbc.Driver");
//3.获得连接对象
String url="jdbc:mysql://localhost:3306/2003-java";
String user="root";
String password="1234";
con=DriverManager.getConnection(url, user, password);
//4。获得sql语句执行器,并且执行sql得到结果
String sql="delete from user where userId="+id;
System.out.println(sql);
sta = con.createStatement();
int executeUpdate = sta.executeUpdate(sql);
System.out.println(executeUpdate);
//5.处理结果
if(executeUpdate==1){
System.out.println("删除成功");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
try {
sta.close();
con.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
演示案例5:实现用户登录
数据库myschool
表 :user
create table user(
id int primary key auto_increment,
username varchar(18) not null,
password varchar(16) not null, # 密文 123456
phone varchar(11)
);
insert into user (username,password,phone) values(‘zhangsan’,‘123456’,‘13898765678’);
insert into user (username,password,phone) values(‘lisi’,‘6666’,‘17898879988’);
package com.qf.day03;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
import javax.management.remote.rmi.RMIJRMPServerImpl;
/*
* 实现用户登录
*/
public class JdbcDemo1 {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
System.out.println("请输入用户名");
String username=input.nextLine();
System.out.println("请输入密码");
String pass=input.nextLine();
//连接对象
Connection connection=null;
//命令对象
Statement stat=null;
//结果集
ResultSet rs=null;
//1 注册驱动
try {
Class.forName("com.mysql.jdbc.Driver");
//2获取连接
connection=DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "root");
//3创建命令
stat=connection.createStatement();
//4执行命命令 select * from user where username='zhangsan' and password='123456';
rs=stat.executeQuery("select * from user where username='"+username+"' and password='"+pass+"'");
if(rs.next()){ //有数据
System.out.println("登录成功");
}else{
System.out.println("账号或密码错误");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(stat!=null){
try {
stat.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
4.2.1时间处理
package com.qf.demo1;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestDate {
public static void main(String[] args) {
Connection con = null;
Statement sta = null;
ResultSet rs=null;
try {
Class.forName("com.mysql.jdbc.Driver");
// 注意不是连接名,是库名
String url = "jdbc:mysql://localhost:3306/2003-java";
String user = "root";
String password1 = "1234";
con = DriverManager.getConnection(url, user, password1);
if (con != null) {
System.out.println("连接成功");
}
sta = con.createStatement();
String sql="select brithday from user_test where id=3";
rs = sta.executeQuery(sql);
while(rs.next()){
//以下操作在做读取的时候,只能获取年月日,拿不到时分秒
/*java.sql.Date date = rs.getDate("brithday");
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String format = sdf.format(date);
System.out.println(format);*/
Timestamp timestamp = rs.getTimestamp("brithday");
java.util.Date d=new Date(timestamp.getTime());
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
System.out.println(sdf.format(d));
}
/*Date d=new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String format = sdf.format(d);
String sql="insert into user_test (username,brithday) values('张三','"+format+"')";
System.out.println(sql);
int executeUpdate = sta.executeUpdate(sql);
System.out.println(executeUpdate);*/
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (sta != null) {
sta.close();
}
if (con != null) {
con.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
4.3 SQL注入
就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击。
4.4 PreparedStatement
该PreparedStatement的接口扩展了Statement接口,它为您提供了一个通用的Statement对象有两个优点附加功能。
作用:1预编译,效率高 2 安全,避免SQL注入
此语句使您可以动态地提供参数。
PreparedStatement pstmt = null;
try {
String SQL = "Update Employees SET age = ? WHERE id = ?";
pstmt = conn.prepareStatement(SQL);
. . .
}
catch (SQLException e) {
. . .
}
finally {
. . .
}
JDBC中的所有参数都由**?**符号,这被称为参数标记。在执行SQL语句之前,必须为每个参数提供值。
所述的setXXX()方法将值绑定到所述参数,其中XXX代表要绑定到输入参数的值的Java数据类型。如果忘记提供值,将收到一个SQLException。
每个参数标记由其顺序位置引用。第一个标记表示位置1,下一个位置2等等。该方法与Java数组索引不同,从0开始。
关闭PreparedStatement对象
就像关闭Statement对象一样,由于同样的原因,还应该关闭PreparedStatement对象。
一个简单的调用close()方法将执行该作业。如果先关闭Connection对象,它也会关闭PreparedStatement对象。但是,应始终显式关闭PreparedStatement对象,以确保正确清理。
PreparedStatement pstmt = null;
try {
String SQL = "Update Employees SET age = ? WHERE id = ?";
pstmt = conn.prepareStatement(SQL);
. . .
}
catch (SQLException e) {
. . .
}
finally {
pstmt.close();
}
上机练习:使用PreparedStatement实现emp表的数据添加
package com.qf.day03;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
/**
* 使用PreparedStatement实现emp表的数据添加
* @author wgy
*
*/
public class Demo6 {
public static void main(String[] args) throws Exception{
Scanner input=new Scanner(System.in);
System.out.println("请输入员工编号");
int empno=input.nextInt();
System.out.println("请输入员工姓名");
String ename=input.next();
System.out.println("请输入工作");
String job=input.next();
System.out.println("请输入经理的编号");
int mgr=input.nextInt();
System.out.println("请输入入职日期");
String date=input.next();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
Date hiredate=sdf.parse(date);
System.out.println("请输入工资");
double salary=input.nextDouble();
System.out.println("请输入奖金");
double comm=input.nextDouble();
System.out.println("请输入部门");
int deptno=input.nextInt();
//1注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2获取连接
String url="jdbc:mysql:///school";
Connection conn=DriverManager.getConnection(url, "root", "root");
//3创建命令
PreparedStatement pstat=conn.prepareStatement("insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno) values(?,?,?,?,?,?,?,?);");
//4参数赋值
pstat.setObject(1, empno);
pstat.setObject(2, ename);
pstat.setObject(3, job);
pstat.setObject(4, mgr);
pstat.setObject(5, hiredate);
pstat.setObject(6, salary);
pstat.setObject(7, comm);
pstat.setObject(8, deptno);
//5执行
int count=pstat.executeUpdate();
if(count>0) {
System.out.println("添加成功");
}else {
System.out.println("添加失败");
}
//6释放资源
pstat.close();
conn.close();
}
}
总结:
在以后开发中,全部使用preparstatement,尽量不要使用statement
唯一能用statement的地方就是order by xxx
4.5抽取数据库工具类
DbUtils类功能:1 注册驱动 2 获取连接 3释放资源
package com.qf.util;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class JDBCUtil {
private static String driverClassName=null;
private static String url=null;
private static String user=null;
private static String password=null;
private static Properties p=new Properties();
static{
InputStream in = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
p.load(in);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
driverClassName=p.getProperty("driverClassName");
url=p.getProperty("url");
user=p.getProperty("user");
password=p.getProperty("password");
try {
Class.forName(driverClassName);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//获取连接的方法
//只封装con的原因是每一次操作数据库都拿一个新的con,他和事务有关系
public static Connection getCon() throws Exception{
return DriverManager.getConnection(url, user, password);
}
/**
* 这是给preparstatement的?赋值的方法
* @param pre 需要赋值的执行器
* @param objs 可变参数
* @throws SQLException
*/
public static void setParam(PreparedStatement pre,Object...objs) throws SQLException{
for (int i = 0; i < objs.length; i++) {
pre.setObject(i+1, objs[i]);
}
}
public static void close(Connection con,Statement sta,ResultSet rs){
try {
if(rs!=null){
rs.close();
}
if (sta != null) {
sta.close();
}
if (con != null) {
con.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//处理结果集的方法(试用,理想阶段===》mybatis)
/**
* 抽空静下心来好好看看
*
*
* @param rs 要处理的结果集
* @param className javaBean的全限路径名
* @return
* @throws Exception
*/
public static List<Object> doResultSet(ResultSet rs,String className) throws Exception{
List<Object> list=new ArrayList<Object>();
Class<?> c1 = Class.forName(className);
while(rs.next()){
//拿到无参的构造方法
Constructor<?> constructor = c1.getConstructor();
//拿到一个数值为空的对象
Object obj = constructor.newInstance();
//拿到所有的方法对象
Method[] ms = c1.getMethods();
for (int i = 0; i < ms.length; i++) {
if(ms[i].getName().startsWith("set")){
//想办法拿到setXXX方法后面的xxx
String name = ms[i].getName();//setSname
String newName = name.substring(3);//Sname
//需要将首字母转成小写,拿到sname
char[] cs = newName.toCharArray();
cs[0]=(char)(cs[0]+32);
String realName=new String(cs);//sname
ms[i].invoke(obj, rs.getObject(realName));
}
}
list.add(obj);
}
return list;
}
}
第五节 JDBC批处理
批量处理允许将相关的SQL语句分组到批处理中,并通过对数据库的一次调用提交它们。
当需要一次向数据库发送多个SQL语句时,可以减少连接数据库的开销,从而提高性能。
5.1 Statement批处理
以下是使用语句对象的批处理的典型步骤
- 1 注册驱动获取连接
- 2 使用*createStatement()*方法创建Statement对象。
- 3 使用*setAutoCommit()*将auto-commit设置为false 。(可选)
- 4 使用*addBatch()*方法在创建的语句对象上添加您喜欢的SQL语句到批处理中。
- 5 在创建的语句对象上使用*executeBatch()*方法执行所有SQL语句。
- 6 使用*commit()*方法提交所有更改。(可选)
- 7 释放资源
// Create statement object
Statement stmt = conn.createStatement();
// Set auto-commit to false
conn.setAutoCommit(false);
// Create SQL statement
String SQL = "INSERT INTO Employees (id, first, last, age) " +
"VALUES(200,'Zia', 'Ali', 30)";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);
// Create one more SQL statement
String SQL = "INSERT INTO Employees (id, first, last, age) " +
"VALUES(201,'Raj', 'Kumar', 35)";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);
// Create one more SQL statement
String SQL = "UPDATE Employees SET age = 35 " +
"WHERE id = 100";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);
// Create an int[] to hold returned values
int[] count = stmt.executeBatch();
//Explicitly commit statements to apply changes
conn.commit();
5.2 PrepareStatement批处理
- 使用占位符创建SQL语句。
- 使用prepareStatement() 方法创建PrepareStatement对象。
- 使用*setAutoCommit()*将auto-commit设置为false 。(可选)
- 使用*addBatch()*方法在创建的语句对象上添加您喜欢的SQL语句到批处理中。
- 在创建的语句对象上使用*executeBatch()*方法执行所有SQL语句。
- 最后,使用*commit()*方法提交所有更改。(可选)
// Create SQL statement
String SQL = "INSERT INTO Employees (id, first, last, age) " +
"VALUES(?, ?, ?, ?)";
// Create PrepareStatement object
PreparedStatemen pstmt = conn.prepareStatement(SQL);
//Set auto-commit to false
conn.setAutoCommit(false);
// Set the variables
pstmt.setInt( 1, 400 );
pstmt.setString( 2, "Pappu" );
pstmt.setString( 3, "Singh" );
pstmt.setInt( 4, 33 );
// Add it to the batch
pstmt.addBatch();
// Set the variables
pstmt.setInt( 1, 401 );
pstmt.setString( 2, "Pawan" );
pstmt.setString( 3, "Singh" );
pstmt.setInt( 4, 31 );
// Add it to the batch
pstmt.addBatch();
//add more batches
//Create an int[] to hold returned values
int[] count = stmt.executeBatch();
//Explicitly commit statements to apply changes
conn.commit();
第六节 JDBC操作二进制
PreparedStatement对象可以使用输入和输出流来提供参数数据。这使您可以将整个文件放入可以保存大值的数据库列,例如Text和BLOB数据类型。
有以下方法可用于流式传输数据 -
- **setAsciiStream():**此方法用于提供大的ASCII值。存字节的
- **setCharacterStream():**此方法用于提供大型UNICODE值。存字符
- **setBinaryStream():**此方法用于提供较大的二进制值。存任意文件
setXXXStream()方法除了参数占位符之外还需要额外的参数,文件大小。
考虑我们要将XML文件XML_Data.xml上传到数据库表中。这是XML文件的内容 -
<?xml version="1.0" encoding="UTF-8"?>
<Employee>
<id>100</id>
<first>张</first>
<last>无极</last>
<Salary>10000</Salary>
<Dob>18-08-1978</Dob>
</Employee>
// Import required packages
import java.sql.*;
import java.io.*;
import java.util.*;
public class JDBCExample {
// JDBC driver name and database URL
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/EMP";
// Database credentials
static final String USER = "username";
static final String PASS = "password";
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt = null;
Statement stmt = null;
ResultSet rs = null;
try{
// Register JDBC driver
Class.forName("com.mysql.jdbc.Driver");
// Open a connection
System.out.println("Connecting to database...");
conn = DriverManager.getConnection(DB_URL,USER,PASS);
//Create a Statement object and build table
stmt = conn.createStatement();
createXMLTable(stmt);
//Open a FileInputStream
File f = new File("XML_Data.xml");
long fileLength = f.length();
FileInputStream fis = new FileInputStream(f);
//Create PreparedStatement and stream data
String SQL = "INSERT INTO XML_Data VALUES (?,?)";
pstmt = conn.prepareStatement(SQL);
pstmt.setInt(1,100);
pstmt.setAsciiStream(2,fis,(int)fileLength);
pstmt.execute();
//Close input stream
fis.close();
// Do a query to get the row
SQL = "SELECT Data FROM XML_Data WHERE id=100";
rs = stmt.executeQuery (SQL);
// Get the first row
if (rs.next ()){
//Retrieve data from input stream
InputStream xmlInputStream = rs.getAsciiStream (1);
int c;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while (( c = xmlInputStream.read ()) != -1)
bos.write(c);
//Print results
System.out.println(bos.toString());
}
// Clean-up environment
rs.close();
stmt.close();
pstmt.close();
conn.close();
}catch(SQLException se){
//Handle errors for JDBC
se.printStackTrace();
}catch(Exception e){
//Handle errors for Class.forName
e.printStackTrace();
}finally{
//finally block used to close resources
try{
if(stmt!=null)
stmt.close();
}catch(SQLException se2){
}// nothing we can do
try{
if(pstmt!=null)
pstmt.close();
}catch(SQLException se2){
}// nothing we can do
try{
if(conn!=null)
conn.close();
}catch(SQLException se){
se.printStackTrace();
}//end finally try
}//end try
System.out.println("Goodbye!");
}//end main
public static void createXMLTable(Statement stmt)
throws SQLException{
System.out.println("Creating XML_Data table..." );
//Create SQL Statement
String streamingDataSql = "CREATE TABLE XML_Data " +
"(id INTEGER, Data TEXT)";
//Drop table first if it exists.
try{
stmt.executeUpdate("DROP TABLE IF EXISTS XML_Data");
//Build table.
stmt.executeUpdate(streamingDataSql);
}catch(SQLException se){
}// do nothing
}//end createXMLTable
}//end JDBCExample
案例:把图片放入数据库
package com.qf.day04;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.ProcessBuilder.Redirect;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* 把图片放入数据库
* @author wgy
*
*/
public class Demo4 {
public static void main(String[] args) throws Exception{
//write();
read();
}
public static void write() throws Exception{
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/school";
Connection conn=DriverManager.getConnection(url, "root", "root");
PreparedStatement pstat=conn.prepareStatement("insert into bigdata2(id,img) values(?,?)");
FileInputStream fis=new FileInputStream("d:\\图片\\003.jpg");
pstat.setInt(1, 1);
pstat.setBinaryStream(2, fis);
int count=pstat.executeUpdate();
System.out.println(count);
pstat.close();
conn.close();
}
public static void read() throws Exception{
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/school";
Connection conn=DriverManager.getConnection(url, "root", "root");
PreparedStatement pstat=conn.prepareStatement("select * from bigdata2 where id=1");
ResultSet rs=pstat.executeQuery();
if(rs.next()) {
int id=rs.getInt("id");
System.out.println(id);
//处理图片
InputStream is=rs.getBinaryStream("img");
FileOutputStream fos=new FileOutputStream("d:\\haha.jpg");
byte[] buf=new byte[1024];
int len=0;
while((len=is.read(buf))!=-1) {
fos.write(buf,0,len);
}
fos.close();
is.close();
}
rs.close();
pstat.close();
conn.close();
System.out.println("读取完成");
}
}