7

MySQL个人学习笔记

 3 years ago
source link: https://segmentfault.com/a/1190000039668319
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

MySQL

数据库概述

什么是数据库?

所谓的数据库就是指存储和管理数据的仓库

扩展内容1:数据库有哪些分类?(了解)

//早期: 层次式数据库、网络型数据库
//现在:关系型数据库、非关系型数据库

什么是关系型数据库?

底层以二维表的形式保存数据的库就是关系型数据库

image.png

扩展内容2:常见的关系型数据库有哪些?(了解)

  • Sql Server:微软提供,收费,适用于一些中型或大型的项目中,在java中的使用占比不高(.NET中使用的较多)
  • Oracle:甲骨文公司提供,收费,适用于一些大型或者超大型的项目中,在java中的使用占比非常高
  • mysql:瑞典MySQLAB公司提供,免费开源,适用于一些小型或者中型的项目中,在Java中的使用占比较高(小巧轻量)mariadb其实就是MySQL的一个分支,用法和MySQL完全一样。
  • DB2:IBM公司提供,收费,在一些银行、金融等行业中使用较多。在java中的使用占比也不高。
  • Sqlite:迷你数据库,嵌入式设备中(安卓、苹果手机、pad)

数据库相关概念

1、什么是数据库服务器

数据库服务器就是一个软件(比如mysql软件)将数据库软件安装在电脑上,当前电脑就是一个数据库服务器。就可以对外提供存取数据的服务

在一个数据库服务器中可以创建多个数据库(dataBases),每一个数据库都是一个单独的仓库。

2、什么是数据库

数据库就是存储和管理数据的仓库,通常情况下,一个网站的中的所有数据会存放在一个数据库中。例如:

image.png

3、什么是表

一个数据库中可以创建多张表,每张表用于存储一类信息(数据库),例如:

jd.com中的用户数据 tb_user(表)

jd.com中的商品数据 tb_product(表)

jd.com中的订单数据 tb_order(表)

4、什么表记录

一张表中可以包含多行表记录,每一行表记录用于存储某一个具体的数据

image.png

什么是SQL语言?

SQL是一门用于操作关系型数据库的通用的语言(使用SQL可以操作所有的关系型数据库)

使用SQL可以操作数据库、表、表记录

(1)创建数据库、删除数据库、修改数据库、查询数据库

(2)创建表、删除表、修改表、查询表

(3)新增表记录、删除表记录、修改表记录、查询表记录

使用SQL也可以操作存储过程/视图/索引等。

提示:SQL是一个标准通用的操作关系型数据库的语言(普通话),每个数据库厂商为了增强自己数据库的功能,都提供了支持自己数据库的语言,称之为数据库的方言。方言不通用!

连接mysql服务器

通过命令行工具可以登录MySQL客户端,连接MySQL服务器,从而访问服务器中的数据。

1、连接mysql服务器:

 mysql -uroot -p密码

-u:后面的root是用户名,这里使用的是超级管理员root;

-p:(小写的p)后面的root是密码,这是在安装MySQL时就已经指定的密码;

2、连接mysql服务器并指定IP和端口:

mysql -uroot -proot -h127.0.0.1 -P3306

-h:后面给出的127.0.0.1是服务器主机名或ip地址,可以省略的,默认连接本机;

-P:(大写的P)后面的3306是连接端口,可以省略,默认连接3306端口;

3、退出客户端命令:quit或exit或 q

4、FAQ:常见问题:
image.png

解决方法:复制mysql安装目录下的bin目录的路径,将bin目录的路径添加到path环境变量中!!

可以在cmd中通过 echo %path% 检查path环境变量的值。

扩展内容3:

(1)在cmd中连接mysql服务器之后,可以使用 #、/**/、-- 等符号添加注释,例如:

image.png

(2)在cmd中连接mysql服务器之后,在书写SQL语句时,可以通过 c 取消当前语句的执行。例如:

image.png

数据库及表操作

创建、删除、查看数据库

提示: (1)SQL语句对大小写不敏感。推荐关键字使用大写,自定义的名称(库名,表名,列名、别名等)使用小写。

 SHOW DATABASES; -- 查看当前数据库服务器中的所有库
 CREATE DATABASE mydb1; -- 创建mydb1库

(2)并且在自定义名称时,针对多个单词不要使用驼峰命名,而是使用下划线连接。(例如:tab_name,而不是 tabName )

-- 01.查看mysql服务器中所有数据库

show databases; -- 查看所有库
  ​
show datebases; -- 错误写法,是data,不是date

-- 02.进入某一数据库(进入数据库后,才能操作库中的表和表记录)

-- 语法:USE 库名;

  use test;    -- 进入test数据库
  show tables; -- 查看test库中的所有表

-- 查看已进入的库(了解)

select database();

-- 03.查看当前数据库中的所有表

show tables; -- 查看test库中的所有表show tables; -- 查看test库中的所有表

-- 04.删除mydb1库

-- 语法:DROP DATABASE 库名;

drop database mydb1; -- 删除不存在的库,这种写法会报错!

-- 思考:当删除的库不存在时,如何避免错误产生?

drop database if exists mydb1; -- 如果存在mydb1,则删除;

-- 05.重新创建mydb1库,指定编码为utf8

-- 语法:CREATE DATABASE 库名 CHARSET 编码;

-- 需要注意的是,mysql中不支持横杠(-),所以utf-8要写成utf8;

 create database mydb1 charset utf8;
 -- 建库时,最好指定编码,如果不指定,这个库的编码有可能是latin1

-- 如果不存在则创建mydb1;

 create database if not exists mydb1 charset utf8;

-- 06.查看建库时的语句(了解, 并验证数据库库使用的编码)

-- 语法:SHOW CREATE DATABASE 库名;

 show create database mydb1;

创建、删除、查看表

-- 07.进入mydb1库,删除stu学生表(如果存在)

-- 语法:DROP TABLE 表名;

  use mydb1;
  drop table if exists stu; -- 如果存在则删除

-- 08.创建stu学生表(编号[数值类型]、姓名、性别、出生年月、考试成绩[浮点型]),建表的语法:

  CREATE TABLE 表名(
   列名 数据类型,
   列名 数据类型,
   ...
    列名 数据类型
  );

SQL语句:

 
  create database if not exists mydb1;
  use mydb1;
  drop table if exists stu;
  create table stu(
    id int primary key auto_increment,
    name varchar(50), -- 50表示name这一列,最多能存50个字符
    gender varchar(10),
    birthday date,
    score double
  );

-- 09.查看stu学生表结构

-- 语法:desc 表名

desc stu;
image.png

新增、修改、删除表记录

-- 10.往学生表(stu)中插入记录(数据)

-- 语法:INSERT INTO 表名(列名1,列名2,列名3...) VALUES(值1,值2,值3...);

insert into stu(id,name,gender,birthday,score) value(null,'tom','male','2000-1-1',85);
  -- 如果插入的有中文数据,在cmd中,先 set names gbk; 再插入,可以防止中文乱码(或者中文数据插入失败)
  insert into stu value(null,'孙尚香','female','2001-2-3',90);
  insert into stu value(null,'后裔','male','2002-3-4',78);
(1)当为所有列插入值时,可以省写列名,但值的个数和顺序必须和声明时列的个数和顺序保持一致!
  (2)SQL语句中的值为字符串或日期时,值的两边要加上单引号(有的版本的数据库双引号也可以,但推荐使用单引号)。
  (3)(针对cmd窗口)在插入数据之前,先设置编码:set names gbk;
  或者用以下命令连接mysql服务器:
   mysql --default-character-set=gbk -uroot -proot
  等价于:
   mysql -uroot -proot
   set names gbk;

-- 11.查询stu表所有学生的信息

-- 语法:SELECT 列名 | * FROM 表名

 select * from stu; -- *号是通配符,表示查询所有列

-- 12.修改stu表中所有学生的成绩,加10分特长分

-- 修改语法: UPDATE 表名 SET 列=值,列=值,列=值...[WHERE子句];

 update stu set score=score+10;
  -- score+=10 错误写法,mysql不支持+=

-- 13.修改stu表中编号为1的学生成绩,将成绩改为83分。

update stu set score=83 where id=1;

提示:where子句用于对记录进行筛选过滤,保留符合条件的记录,将不符合条件的记录剔除。

-- 14.删除stu表中所有的记录

-- 删除记录语法: DELETE FROM 表名 [where子句]

delete from stu; -- 删除所有记录

-- 仅删除符合条件的(例如,删除id大于1的记录)

 delete from stu where id>1;

查询表记录

-- 准备数据: 以下练习将使用db10库中的表及表记录,请先进入db10数据库!!!

SELECT 语句用于从表中选取数据。结果被存储在一个结果表中(称为结果集)。

语法:SELECT 列名称 | * FROM 表名

提示:(1) *(星号)为通配符,表示查询所有列。

(2)但使用 *(星号)有时会把不必要的列也查出来了,并且效率不如直接指定列名

-- 15.查询emp表中的所有员工,显示姓名,薪资,奖金

 select * from emp; -- *表示查询emp表中的所有列
 select name,sal,bonus from emp; -- 查询指定的列

-- 16.查询emp表中的所有部门和职位

 select dept,job from emp;

思考:如果查询的结果中,存在大量重复的记录,如何剔除重复记录,只保留一条?

-- 在select之后、列名之前,使用DISTINCT 剔除重复的记录

select distinct dept,job from emp;

WHERE子句查询

WHERE子句查询语法:SELECT 列名称 | * FROM 表名称 WHERE 列 运算符 值

WHERE子句后面跟的是条件,条件可以有多个,多个条件之间用连接词(or | and)进行连接

下面的运算符可在 WHERE 子句中使用:

image.png

-- 17.查询emp表中【薪资大于3000】的所有员工,显示员工姓名、薪资

  select name, sal from emp where sal>3000;

-- 18.查询emp表中【总薪资(薪资+奖金)大于3500】的所有员工,显示员工姓名、总薪资

 select name, sal+bonus from emp
  where sal+bonus > 3500;

-- ifnull(列名, 值)函数: 判断指定的列是否包含null值,如果有null值,用第二个值替换null值

 select name, sal+ifnull(bonus,0) from emp
  where sal+ifnull(bonus,0) > 3500;

-- 注意查看上面查询结果中的表头,如何将表头中的 sal+bonus 修改为 "总薪资"

-- 使用as可以为表头指定别名(格式:列名 as 别名)

select name as 姓名, sal+ifnull(bonus,0) as 总薪资 from emp
  where sal+ifnull(bonus,0) > 3500;

-- 另外 as可以省略

 select name 姓名, sal+ifnull(bonus,0) 总薪资 from emp
  where sal+ifnull(bonus,0) > 3500;

-- 19.查询emp表中【薪资在3000和4500之间】的员工,显示员工姓名和薪资

 select name,sal from emp
  where sal>=3000 and sal<=4500;
  -- and:必须同时满足and两边的条件

-- 提示: between...and... 在...和...之间

 select name,sal from emp
  where sal between 3000 and 4500;

-- 20.查询emp表中【薪资为 1400、1600、1800】的员工,显示员工姓名和薪资

 select name,sal from emp
  where sal=1400 or sal=1600 or sal=1800;
  -- or: 或,只要满足or两边中的任何一个条件即可!

-- 或者使用 in

 select name,sal from emp
  where sal in(1400,1600,1800);
  -- in:只要sal是in后面括号中的任何一个值,就算符合条件!

-- 21.查询薪资不为1400、1600、1800的员工,显示员工姓名和薪资

 select name,sal from emp
  where not(sal=1400 or sal=1600 or sal=1800);
  -- ---------------------------------------
  select name,sal from emp
  where sal not in(1400,1600,1800);
  -- not表示对条件进行取反

-- 22.(自己完成) 查询emp表中【薪资大于4000和薪资小于2000】的员工,显示员工姓名、薪资。

 select name,sal from emp
  where sal>4000 or sal<2000;

-- 23.(自己完成) 查询emp表中薪资大于3000并且奖金小于600的员工,显示员工姓名、薪资、奖金。

select name,sal,bonus from emp
  where sal>3000 and bonus<600;

-- 处理null值

 
  select name,sal,bonus from emp
  where sal>3000 and ifnull(bonus,0)<600;

-- 24.查询没有部门的员工(即部门列为null值)

select * from emp
where dept is null; -- 查询dept列为null的

-- 思考:如何查询有部门的员工(即部门列不为null值)

select * from emp
  where not(dept is null); -- 查询dept列不为null的
  -- 或
  select * from emp
  where dept is not null; -- 查询dept列不为null的

LIKE 操作符用于在 WHERE 子句中搜索列中的指定模式。

可以和通配符(%、_)配合使用,其中"%"表示0或多个任意的字符,"_"表示一个任意的字符

语法:SELECT 列 | * FROM 表名 WHERE 列名 LIKE 值

-- 25.查询emp表中姓名中以"刘"字开头的员工,显示员工姓名。

select name from emp
where name like '刘%';
-- %表示匹配0个、1个或多个任意字符

-- 26.查询emp表中姓名中包含"涛"字的员工,显示员工姓名。

select name from emp
where name like '%涛%';
-- %涛%, 表示匹配某一列包含涛的记录,'涛'可能在开头、结尾或中间某一个位置

-- 27.查询emp表中姓名以"刘"开头,并且姓名为两个字的员工,显示员工姓名。

-- 以'刘'开头,姓名为两个字的员工
select name from emp
where name like '刘_'; -- _(下划线)表示任意一个字符
-- 以'刘'开头,姓名为三个字的员工
select name from emp
where name like '刘__';

多行函数查询

多行函数也叫做聚合(聚集)函数,根据某一列或所有列进行统计。

常见的多行函数有:

COUNT( 列名 | * )

统计结果集中指定列的记录的行数。

MAX( 列名 )

统计结果集中某一列值中的最大值

MIN( 列名 )

统计结果集中某一列值中的最小值

SUM( 列名 )

统计结果集中某一列所有值的和

AVG( 列名 )

统计结果集中某一列值的平均值

提示:(1)多行函数不能用在where子句中

(2)多行函数和是否分组有关,分组与否会直接影响多行函数的执行结果。

(3)多行函数在统计时会对null值进行过滤,直接将null值丢弃,不参与统计。

-- 28.统计emp表中薪资大于3000的员工个数

-- 查询薪资大于3000的员工有哪些?
select * from emp where sal>3000;
-- count:用于统计行数
select count(*) from emp where sal>3000; -- 7
select count(id) from emp where sal>3000; -- 7
select count(bonus) from emp where sal>3000; -- 6
-- 由于bonus中有null值,在统计时,null值直接别丢弃,不参与统计

-- 29.求emp表中的最高薪资

-- emp表中的最高薪资(max函数)
select max(sal) from emp;
-- emp表中的最低薪资(min函数)
select min(sal) from emp;

-- 求emp表中薪资最高的员工
select name,max(sal) from emp;
-- 上面的结果,max(sal)是求薪资中的最大值,而name只是name列中的第一个姓名,因此name和max(sal)很可能是不对应的。因此结果是错的!

-- 30.统计emp表中所有员工的薪资总和(不包含奖金)

select sum(sal) from emp; -- 求薪资这一列所有值的和
select sum(bonus) from emp; -- 求奖金这一列所有值的和

-- 31.统计emp表员工的平均薪资(不包含奖金)

select avg(sal) from emp; -- 薪资平均值(总薪资/12)
select avg(bonus) from emp; -- 奖金平均值(总奖金/11),有null值

多行函数需要注意的问题:

  • 多行函数和是否分组有关,如果查询结果中的数据没有经过分组,默认整个查询结果是一个组,多行函数就会默认统计当前这一个组的数据。产生的结果只有一个。
  • 如果查询结果中的数据经过分组(分的组不止一个),多行函数会根据分的组进行统计,有多少个组,就会统计出多少个结果。

例如:统计emp表中的人数

-- 如果没有分组,默认整个查询结果是一个组
select count(*) from emp; -- 12

结果返回的就是emp表中的所有人数

再例如:根据性别对所有员工进行分组,再统计每组的人数,显示性别和对应人数

-- 根据性别分组,最终分为两个组,统计两个组的人数(10、2)
select gender,count(*) from emp group by gender;

GROUP BY 语句根据一个或多个列对结果集进行分组。

在分组的列上我们可以使用 COUNT,SUM,AVG,MAX,MIN等函数。

语法:SELECT 列 | * FROM 表名 [WHERE子句] GROUP BY 列;

-- 32.对emp表,按照部门对员工进行分组,查看分组后效果。

-- 按照部门分组(3个组)
select * from emp group by dept;
-- 对上面3个组的人数进行统计
select dept,count(*) from emp group by dept;

-- 33.对emp表按照职位进行分组,并统计每个职位的人数,显示职位和对应人数

-- 按照职位分组(3个组)
select * from emp group by job;
-- 对上面3个组的人数进行统计
select job,count(*) from emp group by job;

-- 34.对emp表按照部门进行分组,求每个部门的最高薪资(不包含奖金),显示部门名称和最高薪资

-- 根据部门进行分组(3个组)
select * from emp group by dept;
-- 求每个部门(每个组)的最高薪资
select dept,max(sal) from emp group by dept;

使用 ORDER BY 子句将结果集中记录根据指定的列排序后再返回

语法:SELECT 列名 FROM 表名 ORDER BY 列名 [ASC|DESC]

ASC(默认)升序,即从低到高;DESC 降序,即从高到低。

-- 35.对所有员工的薪资进行升序(从低到高)排序,显示员工姓名、薪资。

-- 按照薪资进行排序,默认是升序(asc),即从低到高
select name,sal from emp order by sal;
select name,sal from emp order by sal asc;

-- 36.对emp表中所有员工的奖金进行降序(从高到低)排序,显示员工姓名、奖金。

-- 按照奖金进行降序排序
select name,bonus from emp order by bonus desc;

在mysql中,通过limit进行分页查询,查询公式为:

limit (页码-1)*每页显示记录数, 每页显示记录数

-- 37.查询emp表中的所有记录,分页显示:每页显示3条记录,返回所有页的数据。

-- 分页查询:每页显示3条,返回第1页数据:
select * from emp limit 0, 3;
-- 分页查询:每页显示3条,返回第2页数据:
select * from emp limit 3, 3;
-- 分页查询:每页显示3条,返回第3页数据:
select * from emp limit 6, 3;
-- 分页查询:每页显示3条,返回第4页数据:
select * from emp limit 9, 3;

-- 38.求emp表中薪资最高的前3名员工的信息,显示姓名和薪资

-- 按照薪资对员工信息进行排序,降序排序
select name,sal from emp order by sal desc;
-- 在上面查询的基础上,分页查询,每页显示3条,查询第一页
select name,sal from emp order by sal desc limit 0, 3;

curdate()

获取当前日期,格式是:年月日

curtime()

获取当前时间 ,格式是:时分秒

sysdate()/now()

获取当前日期+时间,格式是:年月日 时分秒

year(date)

返回date中的年份

month(date)

返回date中的月份

day(date)

返回date中的天数

hour(date)

返回date中的小时

minute(date)

返回date中的分钟

second(date)

返回date中的秒

CONCAT(s1,s2..)

将s1,s2 等多个字符串合并为一个字符串

CONCAT_WS(x,s1,s2..)

同CONCAT(s1,s2,..)函数,但是每个字符串之间要加上x,x是分隔符

-- 39.查询emp表中所有【在1993和1995年之间出生】的员工,显示姓名、出生日期。

select name,birthday from emp
where birthday between 1993 and 1995;
-- birthday(日期) 和 1993、1995不能比较,因为类型不同

-- 解决方案1:将1993、1995转换成日期后,再和birthday进行比较
select name,birthday from emp
where birthday between '1993-1-1' and '1995-12-31';

-- 解决方案2:将birthday中的年份取出,和1993、1995进行比较
select name,birthday from emp
where year(birthday) between 1993 and 1995;

-- 40.查询emp表中本月过生日的所有员工

-- 求本月过生日的员工
select * from emp
where month(now())=month(birthday);
-- 求下个月过生日的员工
select * from emp
where month(now())+1=month(birthday); -- 问题:如果当前是12月份

select * from emp
where ( month(now())+1 )%12=month(birthday); -- 问题:如果当前是11月份
-- 最终版本:求下个月过生日的员工
select * from emp
where ( month(now())+1 )%12=month(birthday)%12;

-- 41.查询emp表中员工的姓名和薪资(薪资格式为: xxx(元) 、 xxx/元)

select name,concat(sal,'(元)') from emp;
select name,concat(sal,'/元') from emp;
select name,concat_ws('/',sal,'元') from emp;

mysql的数据类型

MySQL支持多种类型,大致可以分为三类:数值、字符串(字符)类型和日期/时间。

MySQL中支持多种整型,其实很大程度上是相同的,只是存储值的大小范围不同而已,下面的表显示了常用的数值类型的存储和范围。

tinyint

1 byte(相对于java中的byte)

smallint

2 bytes(相对于java中的short)

int

4 bytes(相对于java中的int)

bigint

8 bytes(相对于java中的long)

极大整数值

float

4 bytes(相对于java中的float)

单精度浮点数值

double

8 bytes(相对于java中的double)

双精度浮点数值

decimal

存储精确的小数值

decimal数据类型用于在数据库中存储精确的数值

decimal用法:column_name  decimal(P,D);
1)P是表示有效数的精度。 P范围为1〜65。
2)D是表示小数点后的位数。 D的范围是0~30。MySQL要求D小于或等于(<=)P
例如:amount DECIMAL(6,2); 表示amount列最多可以存储6位数字,小数位数为2位,因此,amount列的范围是从-9999.99到9999.99

字符串类型

mysql中的字符串类型有很多,例如:CHAR、VARCHAR、TEXT、BLOB、MEDIUMTEXT、MEDIUMBLOB、LONGTEXT、LONGBLOB等

下面说几个较为常见的字符串类型:

0~255(字符)

定长字符串

varchar

0~65535(字节)

变长字符串

mediumtext

0~16 777 215(字节,约16M)

大文本/长文本

mediumblob

0~16 777 215(字节,约16M)

longtext

0-4 294 967 295(字节,约4G)

longblob

0-4 294 967 295(字节,约4G)

极大二进制

1、char(n) 定长字符串,最长255个字符。n表示字符数,例如:

-- 创建user表,指定用户名为char类型,字符长度不超过10 个字符

create table user(
  username char(10),
  ...
);

所谓的定长,是当插入的数据的长度小于指定的长度时,剩余的空间会用空格填充。(这样会浪费空间)

char类型往往用于存储长度固定的数据,这样不会浪费空间,并且在存储数据的效率上比varchar类型略高一些。

2、varchar(n) 变长字符串,最长不超过65535个字节,n表示字符数,一般超过255个字符,会使用text类型,例如:

iso8859-1码表:一个字符占用1个字节,1*n < 65535, n最多等于 65535
utf8码表:一个中文汉字占用3个字节,3*n < 65535,n最多等于 65535/3
GBK码表:一个中文汉字占用2个字节,2*n < 65535,n最多等于 65535/2

-- 创建user表,指定用户名为varchar类型,长度不超过10个字符

create table user(
username varchar(10)
);

所谓的不定长,是当插入的数据的长度小于指定的长度时,剩余的空间可以留给别的数据使用。(节省空间)

总结:长度固定的数据,用char类型,这样既不会浪费空间,效率也比较高

如果长度不固定,使用varchar类型,这样不会浪费空间。

表示时间值的日期和时间类型为date、time、datetime、timestamp和year。

大小(bytes)

1000-01-01/9999-12-31

YYYY-MM-DD

'-838:59:59'/'838:59:59'

HH:MM:SS

datetime

1000-01-01 00:00:00/9999-12-31 23:59:59

YYYY-MM-DD HH:MM:SS

日期和时间

timestamp

1970-01-01 00:00:00/2038

YYYYMMDD HHMMSS

日期和时间

1901/2155

timestamp:时间戳,实际存储的是一个从1970-1-1到该 日期时间值 的时间毫秒值

mysql的字段约束

字段约束/列约束 --> 约束: 限制

主键约束:如果为一个列添加了主键约束,那么这个列就是主键,主键的特点是唯一且不能为空。

主键的作用: 作为一个唯一标识,唯一的表示一条表记录(作用类似于人的身份证号,可以唯一的表示一个人一样。)

添加主键约束,例如将id设置为主键:

create table stu(
id int primary key,
...
);

如果主键是数值类型,为了方便插入主键(并且保证插入数据时,主键不会因为重复而报错),可以设置一个主键自增策略。

create table stu(
id int primary key auto_increment,
...
);

主键自增策略是指:设置了自增策略的主键,可以在插入记录时,不给id赋值,只需要设置一个null值,数据库会自动为id分配一个值(AUTO_INCREMENT变量,默认从1开始,后面依次+1),这样既可以保证id是唯一的,也省去了设置id的麻烦。

将id主键设置为自增:

create table stu(
id int primary key auto_increment,
...
);

非空约束:如果为一个列添加了非空约束,那么这个列的值就不能为空,但可以重复。

添加非空约束,例如为password添加非空约束:

create table user(
password varchar(50) not null,
...
);

唯一约束:如果为一个列添加了唯一约束,那么这个列的值就必须是唯一的(即不能重复),但可以为空。

添加唯一约束,例如为username添加唯一约束及非空约束:

create table user(
username varchar(50) unique not null,
...
);

外键其实就是用于通知数据库两张表数据之间对应关系的这样一个列。

这样数据库就会帮我们维护两张表中数据之间的关系。

(1) 创建表的同时添加外键

create table emp(
id int,
name varchar(50),
dept_id int,
foreign key(dept_id) references dept(id)
);

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assets6b56c23ba5cf6a63d9f9e8a8b5fd8467.png?lastModify=1616081589)

(1)如果是要表示两张表的数据之间存在对应关系,只需要在其中的一张表中添加一个列,保存另外一张表的主键,就可以保存两张表数据之间的关系。

但是添加的这个列(dept_id)对于数据库来说就是一个普通列,数据库不会知道两张表存在任何关系,因此数据库也不会帮我们维护这层关系。

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assetsimage-20200530160646152.png?lastModify=1616081589)

(2)如果将dept_id列设置为外键,等同于通知数据库,部门表和员工表之间存在对应关系,dept_id列中的数据要参考部门的主键,数据库一旦知道部门和员工表之间存在关系,就会帮我们维护这层关系。

思考:如果在创建表时没有指定外键,那么后期该如何指定外键?以及如何删除外键?

常见的表关系分为以下三种:

一对多(多对一)·、一对一、多对多

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assets1e4a36daf517c88d356cbc3c8dbd3ea8.png?lastModify=1616081589)

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assets392ee4d4c0f8fdd86adc4b9abc3ad6f5.png?lastModify=1616081589)

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assetsimage-20201030000709386.png?lastModify=1616081589)

-- 准备数据: 以下练习将使用db30库中的表及表记录,请先进入db30数据库!!!

-- 42.查询部门和部门对应的员工信息

select * from dept,emp;

上面的查询中存在大量错误的数据,一般我们不会直接使用这种查询。

笛卡尔积查询:所谓笛卡尔积查询就是指,查询两张表,其中一张表有m条记录,另一张表有n条记录,查询的结果是m*n条。

虽然笛卡尔积查询中包含大量错误数据,但我们可以通过where子句将错误数据剔除,保留下来的就是正确数据。

-- 通过where子句将笛卡尔积查询结果中错误的数据剔除,保留正确数据!
select * from dept,emp
where emp.dept_id = dept.id;
-- 因为id在两张表中都存在,所以为了区分,必须在列名前面加上[表名.]

通过where子句将笛卡尔积查询中的错误数据剔除,保留正确的数据,这就是连接查询!

上面的查询可以换成下面的查询:

select * from dept inner join emp
on emp.dept_id=dept.id;
-- 内连接查询,结果和上面的连接查询相同!

左外连接查询

-- 43.查询【所有部门】及部门对应的员工,如果某个部门下没有员工,员工显示为null

-- 这是连接查询,结果中包含的都是[有员工的部门]和[有部门的员工]
select * from dept, emp
where dept_id=dept.id;
-- 如果要查询其中一张表(比如部门表)的所有记录,另一张表只显示对应的记录
select * from dept left join emp
on dept_id=dept.id;
-- ------------------------------
select * from emp right join dept
on dept_id=dept.id;

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assetsimage-20201030000731608.png?lastModify=1616081589)

左外连接查询:可以将左边表中的所有记录都查询出来,右边表只显示和左边相对应的数据,如果左边表中某些记录在右边没有对应的数据,右边显示为null即可。

右外连接查询

-- 44.查询【所有员工】及员工所属的部门,如果某个员工没有所属部门,部门显示为null即可

-- 查询的结果中都是有员工的部门和有部门的员工
select * from dept, emp
where emp.dept_id=dept.id;
-- 如果要查询所有员工及员工对应的部门
select * from dept right join emp
on emp.dept_id=dept.id;
-- 或者使用左外连接查询
select * from emp left join dept
on emp.dept_id=dept.id;

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assetsimage-20201030000757358.png?lastModify=1616081589)

右外连接查询:可以将右边表中的所有记录都查询出来,左边表只显示和右边相对应的数据,如果右边表中某些记录在左边没有对应的数据,可以显示为null。

扩展:如果想将两张表中的所有数据都查询出来(左外+右外并去除重复记录),可以使用全外连接查询,但是mysql又不支持全外连接查询。

select * from dept left join emp on emp.dept_id=dept.id
union
select * from dept right join emp on emp.dept_id=dept.id;

可以使用union将左外连接查询的结果和右外连接查询的结果合并在一起,并去除重复的记录。例如:

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assetsd0c3a3d4ad4f48d4779d1d40cac31755.png?lastModify=1616081589)

需要注意的是:union可以将两条SQL语句执行的结果合并,但是有前提:

(1)两条SQL语句查询的结果列数必须一致

(2)两条SQL语句查询的结果列名、顺序也必须一致

并且union默认就会将两个查询中重复的记录去除(如果不希望去除重复记录,可以使用union all)

子查询练习

-- 准备数据:以下练习将使用db40库中的表及表记录,请先进入db40数据库!!!

-- 45.列出薪资比'王海涛'的薪资高的所有员工,显示姓名、薪资

-- 1)查询出'王海涛'的薪资是多少
select sal from emp where name='王海涛'; -- 2450
-- 2)查询比'王海涛'薪资高的员工有哪些?
select name,sal from emp where sal > (select sal from emp where name='王海涛');

-- 46.列出与'刘沛霞'从事相同职位的所有员工,显示姓名、职位。

-- 1)查询出'刘沛霞'从事的职位
select job from emp where name='刘沛霞'; -- 推销员
-- 2)和'刘沛霞'从事相同职位的员工有哪些?
select name,job from emp
where job=(select job from emp where name='刘沛霞');

多表查询练习

-- 47.列出在'培优部'任职的员工,假定不知道'培优部'的部门编号,显示部门名称,员工名称。

-- 1)连接查询部门表和员工表, 显示部门和部门对应的员工
select dept.name,emp.name from dept, emp
where emp.dept_id = dept.id;
-- 表别名: 可以为表名起一个别名,一旦起了表别名,就一定要用别名代替表名
select d.name,e.name from dept d, emp e
where e.dept_id = d.id;
-- 2)只查询部门名称为'培优部'的员工
select d.name,e.name from dept d, emp e
where e.dept_id = d.id and d.name='培优部';

-- 48.(自查询)列出所有员工及其直接上级,显示员工姓名、上级编号,上级姓名

/* emp e1(员工表), emp e2(上级表)
查询的表: emp e1, emp e2
显示的列: e1.name, e1.topid, e2.name
连接条件: e1.topid = e2.id;
*/
select e1.name, e1.topid, e2.name
from emp e1, emp e2
where e1.topid = e2.id;

-- 49.列出最低薪资大于1500的各种职位,显示职位和该职位的最低薪资

-- 1)求出每个职位的最低薪资(按照职位进行分组,求每个组/职位的最低薪资)
select job,min(sal) from emp group by job;
-- 2)求出最低薪资大于1500的职位有哪些?
select job,min(sal) from emp
/ where xxx /
group by job
having min(sal)>1500;

补充内容:where和having子句的区别:

相同点: 都是对查询的结果进行筛选过滤
不同点:
(1)where是在分组之前对数据进行筛选过滤; 而having是在分组之后对数据进行筛选过滤
(2)where子句中不能使用多行数据, 也不能使用列别名, 但可以使用表别名; 而having中可以使用多行函数, 也可以使用列别名和表别名;

-- 50.列出在每个部门就职的员工数量、平均工资。显示部门编号、员工数量,平均薪资。

-- 根据部门编号(dept_id)进行分组
select dept_id,count(*),avg(sal) from emp group by dept_id;

-- 51.列出受雇日期早于直接上级的所有员工,显示员工编号、员工姓名、部门名称。

/* emp e1(员工表), emp e2(上级表), dept d
查询的表:emp e1,emp e2,dept d
显示的列:e1.id,e1.name,d.name
连接条件:e1.topid=e2.id e1.dept_id=d.id
筛选条件: e1.hdate<e2.hdate
*/
select e1.id,e1.name,d.name
from emp e1,emp e2,dept d
where e1.topid=e2.id and e1.dept_id=d.id
and e1.hdate<e2.hdate;

-- 补充52:查询员工表中薪资最高的员工信息

-- 查询emp表中的最高薪资
select max(sal) from emp;
-- 查询emp表中的最高薪资及对应的员工姓名
select name,max(sal) from emp; -- 错误,name和最高薪资不对应

-- 方式1:排序+分页,根据薪资降序排序,分页查询,取第一条
select * from emp order by sal desc limit 0,1;
-- 方式2:子查询,求emp表中的最高薪资,再根据这个薪资找对应的员工
-- 求emp表中的最高薪资是多少
select max(sal) from emp; -- 5000
-- 求emp表中最高薪资对应的员工
select * from emp where sal=(select max(sal) from emp);

数据库备份与恢复

B站视频链接:https://www.bilibili.com/video/BV1ez4y19776

备份数据库

在cmd窗口中(未登录的状态下),可以通过如下命令对指定的数据库进行备份:

mysqldump -u用户名 -p 数据库的名字 > 备份文件的位置

示例1: 对db40库中的数据(表,表记录)进行备份,备份到 d:/db40.sql文件中

mysqldump -uroot -p db40 > d:/db40.sql

键入密码,如果没有提示,即表示备份成功!

也可以一次性备份所有库,例如:

对mysql服务器中所有的数据库进行备份,备份到 d:/all.sql文件中

mysqldump -uroot -p --all-database > d:/all.sql

键入密码,如果没有提示错误(警告信息不是错误,可以忽略),即表示备份成功!

恢复数据库

1、恢复数据库方式一:

在cmd窗口中(未登录的状态下),可以通过如下命令对指定的数据库进行恢复:

mysql -u用户名 -p 数据库的名字 < 备份文件的位置

示例:将d:/db40.sql文件中的数据恢复到db60库中

-- 在cmd窗口中(已登录的状态下),先创建db60库:

create database db60 charset utf8;

-- 在cmd窗口中(未登录的状态下)

mysql -uroot -p db60 < d:/db40.sql

2、恢复数据库方式二:

在cmd窗口中(已登录的状态下),可以通过source执行指定位置的SQL文件:

source sql文件的位置

示例:将d:/db40.sql文件中的数据恢复到db80库中

-- 在cmd窗口中(已登录的状态下),先创建db80库,进入db80库:

create database db80 charset utf8;
use db80;

-- 再通过source执行指定位置下的sql文件:

source d:/db40.sql

Navicat软件的使用

B站视频链接:https://www.bilibili.com/video/BV1yA41147Vi/

Navicat Premium是一套带图形用户界面的数据库管理工具,让你从单一应用程序中同时连接MySQL、MariaDB、MongoDB、SQL Server、Oracle、PostgreSQL 和 SQLite数据库。使用Navicat可以快速、轻松地创建、管理和维护数据库。

1、使用navicat连接mysql服务器(使用cmd连接mysql服务器)
2、查看所有库、进入数据库、创建数据库、删除数据库、修改数据库
3、创建表、查看表、修改表、删除表
4、新增表记录、查询表记录、修改表记录、删除表记录
5、使用navicat书写SQL语句操作数据库、表和表记录
...


现创建学生表:

use test; -- 进入test库
drop table if exists stu; -- 删除学生表(如果存在)
create table stu( -- 创建学生表

id int, -- 学生id
name varchar(20), -- 学生姓名
gender char(1), -- 学生性别
birthday date -- 出生年月

修改表—新增列

语法:ALTER TABLE tabname ADD col_name datatype DEFAULT expr;

1、往stu表中添加score列,double类型

alter table stu add score double;

修改表—修改列

语法:ALTER TABLE tabname MODIFY (col_name datatype DEFAULT expr...);

1、修改id列,将id设置为主键

alter table stu modify id int primary key;

2、修改id列,将id主键设置为自动增长

alter table stu modify id int auto_increment;

修改表—删除列

语法:ALTER TABLE tabname DROP [COLUMN] col_name;

1、删除stu表中的score列

alter table stu drop score;

添加或删除主键及自增

思考:a) 在建表时,如何为id指定主键约束和自增?

b) 建好的表,如何通过修改添加主键约束和自增?

c) 如何删除表中的主键约束和自增?

1、创建stu学生表,不添加主键自增, 查看表结果

use mydb1; -- 切换到mydb1库
drop table if exists stu; -- 删除stu学生表(如果存在)
create table stu( -- 重建stu学生表,没有主键自增
  id int,
  name varchar(20),
  gender char(1),
  birthday date
);
desc stu; -- 查看表结构

表结构如下: 没有主键约束和自增。

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assets4f7cf015ff7829f26d326b6f443628cc-1595871301917.png?lastModify=1616081589)

2、如果表没有创建,或者要删除重建,在创建时可以指定主键或主键自增

drop table if exists stu; -- 删除stu表
create table stu( -- 重新创建stu表时,指定主键自增
  id int primary key auto_increment,
  name varchar(20),
  gender char(1),
  birthday date
);
desc stu; -- 查看表结构

表结构如下: 已经添加了主键约束和自增。

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assets1606d49b391f6e0d23b526912dbb22cf-1595871300159.png?lastModify=1616081589)

3、如果不想删除重建表,也可以通过修改表添加主键或主键自增

再次执行第1步,创建stu学生表,不添加主键自增,查看表结果

-- 例如: 将stu学生表中的id设置为主键和自动增长

alter table stu modify id int primary key auto_increment;
desc stu; -- 查看表结构

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assets1606d49b391f6e0d23b526912dbb22cf.png?lastModify=1616081589)

如果只添加主键约束,不设置自增

alter table stu modify id int primary key;

如果已经添加主键约束,仅仅设置自增,但需注意:

(1)如果没有设置主键,不可添加自增

(2)只有当主键是数值时,才可以添加自增

alter table stu modify id int auto_increment;

4、如果想删除主键自增

-- 删除主键自增时,要先删除自增

alter table stu modify id int;

-- 再删除主键约束

alter table stu drop primary key;
desc stu; -- 查看表结构

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assets4f7cf015ff7829f26d326b6f443628cc.png?lastModify=1616081589)

添加外键约束

1、添加外键方式一:建表时添加外键

现有部门表如下:

-- 创建部门表

create table dept(
id int primary key auto_increment, -- 部门编号
name varchar(20) -- 部门名称
);

要求创建员工表,并在员工表中添加外键关联部门主键

-- 创建员工表

create table emp(
  id int primary key auto_increment, -- 员工编号
  name varchar(20), -- 员工姓名
  dept_id int, -- 部门编号
  foreign key(dept_id) references dept(id) -- 指定dept_id为外键
);

2、添加外键方式二:建表后添加外键

现有部门表和员工表:

-- 创建部门表

create table dept(
  id int primary key auto_increment, -- 部门编号
  name varchar(20) -- 部门名称
);

-- 创建员工表

create table emp(
  id int primary key auto_increment, -- 员工编号
  name varchar(20), -- 员工姓名
  dept_id int -- 部门编号
);

-- 如果表已存在,可以使用下面这种方式:

alter table emp add constraint fk_dept_id foreign key(dept_id) references dept(id);

其中 fk_dept_id (名字由自己定义),是指外键约束名称,也可以将【constraint fk_dept_id】省略,MySQL会自动分配一个外键名称,将来可以通过该名称删除外键。

foreign key(dept_id)中的dept_id为外键

删除外键约束

1、首先通过 “show create table 表名”语法,查询含有外键表的建表语句,例如:

show create table emp;

显示结果如下:

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assetse5ad1306d0985dadf8ffbbd3eff61174.png?lastModify=1616081589)

其中,emp_ibfk_1是在创建表时,数据库为外键约束指定的一个名字,删除这个名字即可删除外键关系,例如:

alter table emp drop foreign key emp_ibfk_1;

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assetsimage-20201030000837137.png?lastModify=1616081589)

外键删除成功!

添加外键约束(多对多)

-- 现有学生(stu)表和教师(tea)表:

-- 创建学生表

create table stu(
  stu_id int primary key auto_increment, -- 学生编号
  name varchar(20) -- 学生姓名
);

-- 创建教师表

create table tea(
tea_id int primary key auto_increment, -- 教师编号
name varchar(20) -- 教师姓名
);

-- 添加第三方表(stu_tea)表示学生表和教师表关系

-- 创建学生和教师关系表

create table stu_tea(
  stu_id int, -- 学生编号
  tea_id int, -- 教师编号
  primary key(stu_id,tea_id), -- 设置联合主键
  foreign key(stu_id) references stu(stu_id), -- 添加外键
  foreign key(tea_id) references tea(tea_id) -- 添加外键
);

其中为了防止重复数据,将stu_id和tea_id设置为联合主键。

将stu_id设置为外键,参考stu表中的stu_id列

并将tea_id设置为外键,参考tea表中的tea_id列

级联更新、级联删除

-- 创建db20库、dept表、emp表并插入记录

-- 删除db20库(如果存在),并重新创建db20库

drop database if exists db20;
create database db20 charset utf8;
use db20;

-- 创建部门表, 要求id, name字段

create table dept(
id int primary key auto_increment, -- 部门编号
name varchar(20) -- 部门名称
);

-- 往部门表中插入记录

insert into dept values(null, '财务部');
insert into dept values(null, '人事部');
insert into dept values(null, '科技部');
insert into dept values(null, '销售部');

-- 创建员工表, 要求id, name, dept_id

create table emp(
  id int primary key auto_increment, -- 员工编号
  name varchar(20), -- 员工姓名
  dept_id int, -- 部门编号
  foreign key(dept_id) references dept(id) -- 指定外键
   on update cascade -- 级联更新
   on delete cascade -- 级联删除
);
insert into emp values(null, '张三', 1);
insert into emp values(null, '李四', 2);
insert into emp values(null, '老王', 3);
insert into emp values(null, '赵六', 4);
insert into emp values(null, '刘能', 4);

级联更新:主表(dept表)中的主键发生更新时(例如将销售部的id改为40),从表(emp表)中的记录的外键数据也会跟着该表(即赵六和刘能的部门编号也会更新为40)

级联删除:如果不添加级联删除,当删除部门表中的某一个部门时(例如删除4号部门),若该部门在员工表中有对应的员工(赵六和刘能),删除会失败!

若果添加了级联删除,当删除部门表中的某一个部门时,若该部门在员工表中有对应的员工,会在删除部门的同时,将员工表中对应的员工也删除!

where中不能使用列别名

SQL语句的书写顺序:

select * | 列名 -- 确定要查询的列有哪些
from 表名 -- 确定查询哪张表
where 条件 -- 通过筛选过滤,剔除不符合条件的记录
group by 分组的列 -- 指定根据哪一列进行分组
having 条件 -- 通过条件对分组后的数据进行筛选过滤
order by 排序的列 -- 指定根据哪一列进行排序
limit (countPage-1)*rowCount, rowCount -- 指定返回第几页记录以及每页显示多少条

SQL语句的执行顺序:

from 表名 -- 确定查询哪张表
where 条件 -- 通过筛选过滤,剔除不符合条件的记录
select * | 列名 列别名 -- 确定要查询的列有哪些,
group by 分组的列 -- 指定根据哪一列进行分组
having 条件 -- 通过条件对分组后的数据进行筛选过滤
order by 排序的列 -- 指定根据哪一列进行排序
limit (countPage-1)*rowCount, rowCount

关于where中不能使用列别名但是可以使用表别名?**

是因为,表别名是声明在from中,from先于where执行,先声明再使用没有问题,但是列别名是声明在select中,where先于select执行,如果先使用列别名,再声明,这样执行会报错!!

unit07-JDBC

学习目标:

  • 了解什么是JDBC?为什么要学习这门技术?
  • 掌握通过JDBC连接并访问数据库
  • 掌握PreparedStatement传输器的使用(SQL注入攻击)
  • 掌握什么是连接池?为什么要使用连接池?
  • 掌握C3P0连接池的用法
  • 数据库事务(放在后面讲,框架之前)

JDBC概述

什么是JDBC?为什么要学习JDBC?

JDBC(Java DataBase Connectivity) Java数据库连接

其实就是利用Java语言/程序连接并访问数据库的一门技术

之前我们可以通过CMD或者navicat等工具连接数据库

但在企业开发中,更多的是通过程序(Java程序)连接并访问数据库,通过Java程序访问数据库,就需要用到JDBC这门技术。

如何通过JDBC程序访问数据库?

1、提出需求:

创建一个 jt_db 数据库,在库中创建一个account表,并插入三条记录,然后利用Java程序查询出account表中所有的记录,并将查询的结果打印在控制台上。

2、开发步骤:

(1)准备数据, 创建jt_db库, 创建account表

drop database if exists jt_db;
create database jt_db charset utf8;
use jt_db;
create table account(

id int primary key auto_increment,
name varchar(50),
money double

);
insert into account values(null, 'tom', 1000);
insert into account values(null, 'andy', 1000);
insert into account values(null, 'tony', 1000);

如果已经执行过课前资料中的"SQL脚本文件",此步骤可以跳过。

(2)创建JAVA工程:

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assetsimage-20200318153229937.png?lastModify=1616081589)

(3)导入jar包——mysql驱动包:

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assetsimage-20201030000404489.png?lastModify=1616081589)

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assets6ba3042ef2947f314836128a5c19e2c2.png?lastModify=1616081589)

(4)创建类并实现JDBC程序(六个步骤)

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assetsdccd4a8163b4835b74e77f945d106683.png?lastModify=1616081589)

代码实现:

public static void main(String[] args) throws Exception {
   //1.注册数据库驱动
   Class.forName("com.mysql.jdbc.Driver");
   //2.获取数据库连接
   Connection conn = DriverManager.getConnection(
       "jdbc:mysql://localhost:3306/jt_db?characterEncoding=utf-8",
       "root", "root");
   //3.获取传输器
   Statement stat = conn.createStatement();
   //4.发送SQL到服务器执行并返回执行结果
   String sql = "select * from account";
   ResultSet rs = stat.executeQuery( sql );
   //5.处理结果
   while( rs.next() ) {
       int id = rs.getInt("id");
       String name = rs.getString("name");
       double money = rs.getDouble("money");
       System.out.println(id+" : "+name+" : "+money);
  }
   //6.释放资源
   rs.close();
   stat.close();
   conn.close();
   System.out.println("TestJdbc.main()....");
}

3、执行结果:

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assetsa37ab7234bb1beb988f4b9c01c8cee2f.png?lastModify=1616081589)

JDBC API总结

1、注册数据库驱动

Class.forName("com.mysql.jdbc.Driver");

所谓的注册驱动,就是让JDBC程序加载mysql驱动程序,并管理驱动

驱动程序实现了JDBC API定义的接口以及和数据库服务器交互的功能,加载驱动是为了方便使用这些功能。

2、获取连接之数据库URL

Connection conn = DriverManager.getConnection(
   "jdbc:mysql://localhost:3306/jt_db?characterEncoding=utf-8",
   "root", "root" );

DriverManager.getConnection() 用于获取数据连接,返回的Connection连接对象是JDBC程序连接数据库至关重要的一个对象。

参数2参数3分别是所连接数据库的用户名和密码。

参数1:"jdbc:mysql://localhost:3306/jt_db" 是连接数据库的URL,用于指定访问哪一个位置上的数据库服务器及服务器中的哪一个数据库,其写法为:

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assets7edb0faa5fac5f29cd19411909c708cb.png?lastModify=1616081589)

当连接本地数据库,并且端口为3306,可以简写为如下形式:

jdbc:mysql:///jt_db

3、Statement传输器对象

Statement stat = conn.createStatement();
该方法返回用于向数据库服务器发送sql语句的Statement传输器对象

该对象上提供了发送sql的方法:

executeQuery(String sql) --
用于向数据库发送查询类型的sql语句,返回一个ResultSet对象中
executeUpdate(String sql) --
用于向数据库发送更新(增加、删除、修改)类型的sql语句,返回一个int值,表示影响的记录行数

4、ResultSet结果集对象

ResultSet对象用于封装sql语句查询的结果,也是一个非常重要的对象。该对象上提供了遍历数据及获取数据的方法。

(1)遍历数据行的方法

next() – 使指向数据行的箭头向下移动一行,并返回一个布尔类型的结果,true表示箭头指向了一行数据,false表示箭头没有指向任何数据(后面也没有数据了)

(2)获取数据的方法

getInt(int columnIndex)
getInt(String columnLable)
getString(int columnIndex)
getString(String columnLable)
getDouble(int columnIndex)
getDouble(String columnLable)
getObject(int columnIndex)
getObject(String columnLable)
...

5、释放资源

rs.close();
stat.close();
conn.close();

此处释放资源必须按照一定的顺序释放,越晚获取的越先关闭。所以先关闭rs对象,再关闭stat对象,最后关闭conn对象。

另,为了避免上面的程序抛出异常,释放资源的代码不会执行,应该把释放资源的代码放在finally块中.

try{
...
}catch(Exception e){
...
}finally{
   if (rs != null) {
       try {
      rs.close();
      } catch (SQLException e) {
           e.printStackTrace();
      } finally {
           rs = null;
      }
  }
   if (stat != null) {
       try {
      stat.close();
      } catch (SQLException e) {
           e.printStackTrace();
      } finally {
           stat = null;
      }
  }
   if (conn != null) {
       try {
      conn.close();
      } catch (SQLException e) {
           e.printStackTrace();
      } finally {
           conn = null;
      }
  }
}

JDBC增删改查

1、新增:往account表中添加一个名称为john、money为3500的记录

/ 1、新增:往account表中添加一个名称为john、money为3500的记录 /
@Test
public void testInsert() {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
//注册驱动并获取连接
conn = JdbcUtil.getConn();
//获取传输器
stat = conn.createStatement();
//发送sql语句到服务器执行,并返回执行结果
String sql = "insert into account values(null, 'john', 3500)";
int rows = stat.executeUpdate( sql );
//处理结果
System.out.println( "影响行数: "+rows );
} catch (Exception e) {
e.printStackTrace();
} finally {
//通过JdbcUtil工具类中的close方法释放资源
JdbcUtil.close(conn, stat, rs);
}
}

2、修改:将account表中名称为john的记录,money修改为1500

/ 2、修改:将account表中名称为john的记录,money修改为1500 /
@Test
public void testUpdate() {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
//注册驱动并获取连接
conn = JdbcUtil.getConn();
//获取传输器
stat = conn.createStatement();
//发送sql语句到服务器执行,并返回执行结果
String sql = "update account set money=1500 where name='john'";
int rows = stat.executeUpdate( sql );
//处理结果
System.out.println( "影响行数: "+rows );
} catch (Exception e) {
e.printStackTrace();
} finally {
//通过JdbcUtil工具类中的close方法释放资源
JdbcUtil.close(conn, stat, rs);
}
}

3、查询(自己完成):查询account表中名称为john的记录

/ 3、查询:查询account表中id为1的记录 /
@Test
public void testFindById() {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
//注册驱动并获取连接
conn = JdbcUtil.getConn();
//获取传输器
stat = conn.createStatement();
//执行sql语句,返回执行结果
String sql = "select * from account where id=1";
rs = stat.executeQuery( sql );
//处理结果
if( rs.next() ) {
int id = rs.getInt("id");
String name = rs.getString("name");
double money = rs.getDouble("money");
System.out.println( id+" : "+name+" : "+money);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.close(conn, stat, rs);
}
}

4、删除(自己完成):删除account表中名称为john的记录

/ 4、删除:删除account表中名称为john的记录 /
@Test
public void testDelete() {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
//注册驱动并获取连接
conn = JdbcUtil.getConn();
//获取传输器
stat = conn.createStatement();
//发送sql语句到服务器执行,并返回执行结果
String sql = "delete from account where name='john'";
int rows = stat.executeUpdate( sql );
//处理结果
System.out.println( "影响行数: "+rows );
} catch (Exception e) {
e.printStackTrace();
} finally {
//通过JdbcUtil工具类中的close方法释放资源
JdbcUtil.close(conn, stat, rs);
}
}

单元测试补充

单元测试:不用创建新的类,也不用提供main函数,也不用创建类的实例,就可以直接执行一个方法

加了@Test注解的方法,可以通过单元测试(junit)框架测试该方法。底层会创建该方法所在类的实例,通过实例调用该方法。

@Test
public void testInsert() {
System.out.println("TestPreparedStatement.testInsert()");
}

能够使用@Test单元测试测试的方法必须满足如下几个条件:

(1)方法必须是公共的
(2)方法必须是非静态的
(3)方法必须是无返回值的
(4)方法必须是无参数的
(5)进行单元测试的方法或类,命名时不要命名为 Test/test

PreparedStatement

在上面的增删改查的操作中,使用的是Statement传输器对象,而在开发中我们用的更多的传输器对象是PreparedStatement对象,PreparedStatement是Statement的子接口,比Statement更加安全,并且能够提高程序执行的效率。

Statement 父对象

PreparedStatement 子对象

模拟用户登录案例

(1)准备数据

use jt_db;
create table user(
  id int primary key auto_increment,
  username varchar(50),
  password varchar(50)
);
insert into user values(null,'张三','123');
insert into user values(null,'李四','234');

(2)创建LoginUser 类,提供 main 方法 和 login 方法。

public static void main(String[] args) {
/* 1、提示用户登录,提示用户输入用户名并接收用户名

*  2、提示用户输入密码并接收密码
*  3、根据用户名和密码查询用户信息
*/

// 1、提示用户登录,提示用户输入用户名并接收用户名
Scanner sc = new Scanner(System.in);
System.out.println( "请登录:" );
System.out.println( "请输入用户名:" );
String user = sc.nextLine();

// 2、提示用户输入密码并接收密码
System.out.println( "请输入密码:" );
String pwd = sc.nextLine();

// 3、根据用户名和密码查询用户信息
login( user, pwd );
}
/**

  • 根据用户名和密码查询用户信息
  • @param user 用户名
  • @param pwd 密码

*/
private static void login(String user, String pwd) {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
//1.注册驱动并获取连接
conn = JdbcUtil.getConn();
//2.获取传输器,执行sql并返回执行结果
stat = conn.createStatement();
String sql = "select * from user where username='"+user+"' and password='"+pwd+"'";
rs = stat.executeQuery(sql);
System.out.println( sql );
//3.处理结果
if( rs.next() ) { //有数据 -- 用户名密码都正确
System.out.println("恭喜您登录成功!");
}else { //没数据 -- 用户名或密码不正确
System.out.println("登录失败, 用户名或密码不正确!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//4.释放资源
JdbcUtil.close(conn, stat, rs);
}
}

执行时,输入:

请登录:
请输入用户名:
张飞'#'
请输入密码:

select * from user where username='张飞'#'' and password=''
恭喜您登录成功了!

或输入

请登录:
请输入用户名:
张飞' or '1=1
请输入密码:

select * from user where username='张飞' or '1=1' and password=''
恭喜您登录成功了!

或输入

请登录:
请输入用户名:

请输入密码:
' or '2=2
select * from user where username='' and password='' or '2=2'
恭喜您登录成功了!

SQL注入攻击

通过上面的案例,我们发现在执行时,不输入密码只输入用户名也可以登陆成功。这就是SQL注入攻击。

SQL注入攻击产生的原因: 由于后台执行的SQL语句是拼接而来的:

select * from user where username='"+user+"' and password='"+pwd+"'

其中的参数是用户提交过来的,如果用户在提交参数时,在参数中掺杂了一些SQL关键字(比如or)或者特殊符号(#、-- 、' 等),就可能会导致SQL语句语义的变化,从而执行一些意外的操作(用户名或密码不正确也能登录成功)!

防止SQL注入攻击

如何防止SQL注入攻击?

(1)使用正则表达式对用户提交的参数进行校验。如果参数中有(# -- ' or等)这些符号就直接结束程序,通知用户输入的参数不合法

(2)使用PreparedStatement对象来替代Statement对象。

下面通过第二种方式解决SQL注入攻击:添加loginByPreparedSatement方法,在方法中,使用PreparedStatement来代替Statement作为传输器对象使用,代码示例:

  • 根据用户名和密码查询用户信息
  • @param user 用户名
  • @param pwd 密码

*/
private static void login(String user, String pwd) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1.注册驱动并获取连接
conn = JdbcUtil.getConn();
//2.获取传输器,执行sql并返回执行结果
String sql = "select * from user where username=? and password=?";
ps = conn.prepareStatement( sql );
//设置SQL语句中的参数
ps.setString( 1 , user );
ps.setString( 2 , pwd );
//执行SQL语句
rs = ps.executeQuery();//这里不要再传输SQL语句

//3.处理结果
if( rs.next() ) { //有数据 -- 用户名密码都正确
System.out.println("恭喜您登录成功!");
}else { //没数据 -- 用户名或密码不正确
System.out.println("登录失败, 用户名或密码不正确!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//4.释放资源
JdbcUtil.close(conn, ps, rs);
}
}

再次执行程序,按照上面的操作登录。此时,已经成功的防止了SQL注入攻击问题了。

PreparedStatement对象是如何防止SQL注入攻击的:

使用PreparedStatement对象是先将SQL语句的骨架发送给服务器编译并确定下来,编译之后,SQL语句的骨架和语义就不会再被改变了,再将SQL语句中的参数发送给服务器,即使参数中再包含SQL关键字或者特殊符号,也不会导致SQL语句的骨架或语义被改变,只会被当作普通的文本来处理!


使用PreparedStatement对象可以防止SQL注入攻击

而且通过方法设置参数更加的方便且不易出错!

还可以从某些方面提高程序执行的效率!

数据库连接池

什么是连接池

常量池/线程池/连接池

池:指内存中的一片空间(容器,比如数组、集合)

连接池:就是将连接存放在容器中,供整个程序共享,可以实现连接的复用,减少连接创建和关闭的次数,从而提高程序执行的效率!

为什么要使用连接池

1、传统方式操作数据库

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assetsed6ceb2908a9c2a66c3336a651056ce0.png?lastModify=1616081589)

Connection conn = DriverManager.getConnection( url, user, pwd ); //创建连接对象
......
conn.close(); //关闭连接, 销毁连接

在传统方式中,每次用户需要连接访问数据库时,都是创建一个连接对象,基于这个连接对象访问数据库,用完连接后,会将连接关闭(conn.close())。

由于每次创建连接和关闭连接非常的耗时间而且耗资源,因此会导致程序执行的效率低下。

2、使用连接池操作数据库

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assetsb7566731966b48e3d918bbd205d30030.jpg?lastModify=1616081589)

可以在程序一启动时,就创建一批连接放在一个连接池中(容器),当用户需要连接时,就从连接池中获取一个连接对象,用完连接后,不要关闭,而是将连接再还回连接池中,这样一来,用来用去都是池中的这一批连接,实现了连接的复用,减少了连接创建和关闭的次数,从而提高了程序执行的效率!

如何使用C3P0连接池

dbcp/c3p0/druid

所有的连接池都要实现一个接口——DataSource(数据源),因此连接池也被叫做数据源!

使用C3P0连接池开发步骤:

1、导入开发包

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assetsd9837a8227847f3e22aa28a9e5bf6aa5.png?lastModify=1616081589)

2、创建数据库连接池(对象)

ComboPooledDataSource cpds = new ComboPooledDataSource();

3、设置连接数据库的基本信息

1)方式一:(不推荐) 直接将参数通过 pool.setXxx方法设置给c3p0程序

这种方式直接将参数写死在了程序中,后期一旦参数发生变化,就要修改程序,要重新编译项目、重新发布项目,非常麻烦。

//设置连接数据库的基本信息
pool.setDriverClass( "com.mysql.jdbc.Driver" );
pool.setJdbcUrl( "jdbc:mysql:///jt_db?characterEncoding=utf-8" );
pool.setUser( "root" );
pool.setPassword( "root" );

2)方式二:将连接参数提取到properties文件中(推荐)

文件必须放在src(源码根目录)目录下 !

文件名必须叫做 c3p0.properties !

在类目录下(开发时可以放在src或者类似的源码目录下),添加一个c3p0.properties文件,配置内容如下:

c3p0.driverClass=com.mysql.jdbc.Driver
c3p0.jdbcUrl=jdbc:mysql:///jt_db?characterEncoding=utf-8
c3p0.user=root
c3p0.password=root

这种方式由于是c3p0到指定的位置下寻找指定名称的properties文件,所以文件的位置必须是放在src或其他源码根目录下,文件名必须是c3p0.properties。

3)方式三:将连接参数提取到xml文件中(推荐)

文件必须放在src(源码根目录)目录下 !

文件名必须叫做 c3p0-config.xml

在类目录下(开发时可以放在src或者类似的源码目录下),添加一个c3p0-config.xml文件,配置内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
   <default-config>
       <property name="driverClass">com.mysql.jdbc.Driver</property>
       <property name="jdbcUrl">jdbc:mysql:///jt_db?characterEncoding=utf-8</property>
       <property name="user">root</property>
       <property name="password">root</property>
   </default-config>
</c3p0-config>

这种方式由于是c3p0到指定的位置下寻找指定名称的xml文件,所以文件的位置必须是放在src或其他源码根目录下,文件名必须是c3p0-config.xml。

4、从连接池中获取一个连接对象并进行使用

Connection conn = pool.getConnection();

5、用完连接后将连接还回连接池中

conn.close()
/* 如果是自己创建的连接对象,这个连接对象没有经过任何的改动,调用

  • conn.close方法,是将连接对象关闭
  • 如果是从连接池中获取的连接对象,该连接对象在返回时就已经被连接池
  • 改造了,将连接对象的close方法改为了还连接到连接池中

扩展:学生信息管理系统

通过JDBC实现学生信息管理系统

B站视频链接:https://www.bilibili.com/video/BV1ka4y1x7M6

建库建表语句如下:

-- 1、创建数据库jt_db数据库(如果不存在才创建)
create database if not exists jt_db charset utf8;
use jt_db; -- 选择jt_db数据库
-- 2、在 jt_db 库中创建 stu 表(学生表)
drop table if exists stu;
create table stu(
  id int,
  name varchar(50),
  gender char(2),
  addr varchar(50),
  score double
);
-- 3、往 stu 表中, 插入记录
insert into stu values(1001,'张三','男', '北京', 86);

运行程序控制台提示如下:

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assets4b9ad722dca6065b01a5202f85e2d89c.png?lastModify=1616081589)

输入a:查询所有学生信息

输入b:添加学生信息

输入c:根据id修改学生信息

输入d:根据id删除学生信息

查询所有学生信息

在控制台中输入操作代码"a",效果如下:

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assets55a1ab054846de0d92525b6292f7b930.png?lastModify=1616081589)

添加学生信息

在控制台中输入操作代码"b",效果如下:

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assetsc9f2e18f2a41668cd39ddaccd712290c.png?lastModify=1616081589)

根据id修改学生信息

在控制台中输入操作代码"c",效果如下:

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assets40de75c8a4f08fc4a4b581b358be6a87.png?lastModify=1616081589)

根据id删除学生信息

在控制台中输入操作代码"d",效果如下:

%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E8%AE%B2%E4%B9%8903.assets9eda8bae1797eced35a1e4907009db83.png?lastModify=1616081589)

unit08-transaction

今日目标:

  • 了解事务的作用
  • 掌握事务的四大特性(面试)
  • 了解事务的三个并发读问题
  • 掌握mysql开启和结束事务
  • 了解事物的四个隔离级别

事务及四大特性

什么是事务

数据库事务(Database Transaction),是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。

简单的说:事务就是将一堆的SQL语句(通常是增删改操作)绑定在一起执行,要么都执行成功,要么都执行失败,即都执行成功才算成功,否则就会恢复到这堆SQL执行之前的状态。

下面以银行转账为例,张三转100块到李四的账户,这至少需要两条SQL语句:

  • 给张三的账户减去100元;

    update 账户表 set money=money-100 where name='张三';

  • 给李四的账户加上100元。

    update 账户表 set money=money+100 where name='李四';

如果在第一条SQL语句执行成功后,在执行第二条SQL语句之前,程序被中断了(可能是抛出了某个异常,也可能是其他什么原因),那么李四的账户没有加上100元,而张三却减去了100元,在现实生活中这肯定是不允许的。

如果在转账过程中加入事务,则整个转账过程中执行的所有SQL语句会在一个事务中,而事务中的所有操作,要么全都成功,要么全都失败,不可能存在成功一半的情况。

也就是说给张三的账户减去100元如果成功了,那么给李四的账户加上100元的操作也必须是成功的;否则,给张三减去100元以及给李四加上100元都是失败的。

事务的四大特性

事务的四大特性(ACID)是:

1)原子性(Atomicity):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。即使事务在执行过程中发生错误,也会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

即:事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。

2)一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

例如:转账业务,无论事务执行成功与否,参与转账的两个账户金额之和在事务前后应该是保持不变的。

张三:1000 1000-500=500 1000
李四:1000 1000+500=1500 1000

3)隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。即:隔离性可以在事务并发时,让不同的事务隔离开来,一个事务看不到另一个事务正在进行中的状态。

例如:在A事务中,查看另一B事务(正在修改张三的账户金额)中张三的账户金额,要查看到B事务之前的张三的账户金额,要么查看到B事务之后张三的账户金额。

事务1: 查询A、B账户金额之和
事务2: A转账给B 500元

    A - 500 = 500
    B + 500 = 1500

4)持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。因为事务一旦提交,事务中所有的数据操作都必须被持久化到数据库中,即使事务提交后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。

开启事务---A给B转账500元
A: 1000 - 500 = 500 (成功了) -- 在日志中记录,事务成功,A账户金额更新为500
B: 1000 + 500 = 1500 (成功了) -- 在日志中记录,事务成功,B账户金额更新为1500
结束事务---回滚/提交

MySQL中的事务

mysql中操作事务

在默认情况下,MySQL每执行一条SQL语句,都是一个单独的事务。因为底层在执行SQL语句之前会自动开启事务,在SQL语句执行完后,会自动提交事务!

如果需要在一个事务中包含多条SQL语句,那么需要手动开启事务和结束事务。

  • 开启事务:begin / start transaction;

    begin 或 start transaction 显式地开启一个事务;

  • 提交事务:commit;

    commit会提交事务,并使已对数据库进行的所有修改成为永久性的;

  • 回滚事务:rollback;

    rollback会回滚事务,即撤销正在进行的所有未提交的修改。数据也会恢复到事务开始前的状态,就像这个事务从来没有执行过一样。

下面演示事务在转账例子中的应用:

准备数据:

-- 1、创建数据库jt_db数据库(如果不存在才创建)
create database if not exists jt_db charset utf8;
use jt_db; -- 选择jt_db数据库
-- 2、在 jt_db 库中创建 acc 表(银行账户表),要求有id(主键),name(姓名),money(账户金额)
drop table if exists acc;
create table acc(
  id int primary key auto_increment,
  name varchar(50),
  money double
);
-- 3、往 acc 表中, 插入2条记录
insert into acc values(null,'A',1000);
insert into acc values(null,'B',1000);
-- 查询acc表中的所有记录
select * from acc;

下面分别演示事务开启及执行一系列SQL之后,回滚事务、提交事务及中断操作的效果。

-- rollback(回滚事务)

-- 查询acc账户表中A和B的金额
select * from acc;
-- 开启事务
start transaction;
-- 开始转账,A账户减去100元
update acc set money=money-100 where name='A';
-- 查询acc账户表中A和B的金额
select * from acc;
-- B账户增加100元
update acc set money=money+100 where name='B';
-- 查询acc账户表中A和B的金额
select * from acc;
-- 回滚事务
rollback;
-- 再次查询acc账户表中A和B的金额
select * from acc;

-- commit(提交事务):将上面的操作再做一次,最后将rollback替换为commit,即提交事务

commit;

-- 中断操作:将上面的操作再做一次,最后将rollback替换为quit,即中断操作

quit;

jdbc中操作事务

在JDBC中实现转账例子

提示:JDBC中默认是自动提交事务,所以需要关闭自动提交,改为手动提交事务

也就是说, 关闭了自动提交后, 事务就自动开启, 但是执行完后需要手动提交或者回滚!!

(1)执行下面的程序,程序执行没有异常,转账成功!A账户减去100元,B账户增加100元。

(2)将第4步、5步中间的代码放开,再次执行程序,在转账过程中抛异常,转账失败!由于事务回滚,所以A和B账户金额不变。

public static void main(String[] args) throws SQLException {
  Connection conn = null;
  Statement stat = null;
  ResultSet rs = null;
  try {
      //1.获取连接
      Class.forName("com.mysql.jdbc.Driver");
  //2.获取数据库连接
  Connection conn = DriverManager.getConnection(
      "jdbc:mysql://localhost:3306/jt_db?characterEncoding=utf-8",
      "root", "root");
      //2.关闭JDBC自动提交事务(默认开启事务)
      conn.setAutoCommit(false);
      //3.获取传输器
      stat = conn.createStatement();
       / A给B转账100元 /
      //4.A账户减去100元
      String sql = "update acc set money=money-100 where name='A'";
      stat.executeUpdate(sql);
      //int i = 1/0; // 让程序抛出异常,中断转账操作
      //5.B账户加上100元
      sql = "update acc set money=money+100 where name='B'";
      stat.executeUpdate(sql);
      //6.手动提交事务
      conn.commit();
      System.out.println("执行成功!提交事务...");
  } catch (Exception e) {
  e.printStackTrace();
  //一旦其中一个操作出错都将回滚,使两个操作都不成功
  conn.rollback();
  System.out.println("执行失败!回滚事务...");
  } finally{
  rs.close();
  stat.close();
  conn.close();
  }
}


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK