目录
约束类型:
数据库介绍
什么是数据库?
数据库的发明是为了解决海量数据的存储和增删查改等问题,虽然数据的存储同样可以使用文件,但使用文件存储有诸多不便之处,例如:
- 文件的安全性无法保证
- 文件是静态存储的,不利于增删查改等操作
- 文件的占用空间大,不利于存储海量数据
- 文件不方便在程序中控制
而数据库的发明,解决了上述问题,数据库的存储介质一般为:磁盘、内存。且数据库还有许多优点
- 数据库是动态存储的,对需要经常变动的数据存储非常友好
- 数据库可以远程连接,无需本地存放
- 数据库可以使用代码在程序中控制
数据库分类
关系型数据库
关系型数据库是一种基于关系模型的数据管理系统,它使用结构化的方式来存储数据,并通过SQL语言来操作数据,在关系型数据库中,数据被组织成一个或多个表,每个表拥有唯一的名称,表中的行和列用于存储数据,并且每个表有一个主键,主键中存储的是表的索引,这里在后面为大家详细介绍,表和表之间的关联我们称为外键,总结以下,关系型数据库有如下特点
- 使用表来存储数据,每个表有自己的名称和列
- 数据被存储在行中,每一行都有唯一标识符
- 表之间可以通过外键连接
常见的关系型数据库有:
- Oracle:大名鼎鼎的甲骨文公司所开发的数据库,不开源不免费,适用于大型项目
- MySQL:本文着重介绍的数据库,免费开源,目前属于甲骨文公司的产品,但早期不是甲骨文公司的产品,在国内经历过阿里巴巴牵头的去Oracle化行动后逐渐流行,后被甲骨文收购。适用于一般不复杂的项目。
- SQL Server:微软的产品,部署在windows server上,免费。
非关系型数据库
非关系型数据库:是指在数据存储时不使用传统的关系型数据库的表格及其关系建立方式,而是通过键值对存储、文档存储、列存储或图形数据库等非关系型模型来存储数据的一种数据库系统。它们被设计用来处理大量的分散数据
- 不需要使用表的方式存储,数据是可以以任何类型存储的。
- 存储数据类型的格式可以是键值(Key-Values模型),文档,列表等。
- 可以处理例如图像,视频,音频等非结构化数据
- 不支持事务处理,但可以通过一致性模型来确保数据的一致性。
常见的关系型数据库有:
- MongDB:MongDB(10gen)公司的产品,开源免费,适用于大型复杂的程序项目,
- Redis:Salvatore Sanfilippo开发,Redis Labs公司(原名Garantia Data)维护,开源免费,适合处理实时数据,例如游戏、广告、社交软件。
Mysql的安装与使用
安装在这里不做过多叙述,安装是一个非常简单的事情,基本就是一路next即可,网络上有无数篇Mysql的安装教程,大家可以自行查阅。
打开Mysql数据库、输入密码后,可以得到如下界面
这里的Server version是数据库的版本,后续根据版本进行JDBC开发时需要下载对应版本的API,可以先记住它。
常用数据类型
在讲解数据库的操作前,我们先来了解一些数据库中的基本数据
数据类型 | 大小 | 说明 | 对应java类型 |
bit(m) | m为指定位数,默认为1 | 二进制数,m的取值为【1,64】,存储数值范围为0-2^m-1 | 常用Boolean对应bit,默认为1,即只能存0-1 |
tinyint | 1 | byte | |
smallint | 2 | short | |
int | 4 | interger | |
bigint | 8 | long | |
float(m,d) | 4 | 单精度浮点数,m为指定长度,d为指定小数位数 | float |
double(m,d) | 8 | 前面为长度后面为小数位数 | double |
decimal(m,d) | m/d最大值+2 | 双精度,M指定长度,D表示 小数点位数。精确数值 | BigDecima |
NUMERIC(M, D) | M/D最大 值+2 | 和DECIMAL一样 | BigDecimal |
数据类型 | 大小 | 说明 | 对应java类型 |
---|---|---|---|
varchar(size) | 0-65535 | 可变长度字符串 | String |
text | 0-65535 | 长文本数据 | String |
mediumtext | 0-16777215 | 中等长度文本数据 | String |
BLOB | 0-65,535字节 | 二进制形式的长文本数据 | byte[] |
数据类型 | 大小 | 说明 | 对应java类型 |
---|---|---|---|
datetime | 8 | 1000-9999年,不会进行时区的检索和转换 | java.util.Date、 java.sql.Timestamp |
timestamp | 4 | 1970-2038,自动检索时区并转换 | java.util.Date、 java.sql.Timestamp |
数据库的操作
显示当前数据库
show databases;
输入这行命令后在控制台会得到这样一张表,这里的java01是我自己先前创建过的数据库,其余是系统中自带的数据库。通过这行命令可以列出当前数据库中的数据库名称
下面我将带领大家从头开始一步一步创建数据库和表,并了解熟悉数据库的基本命令
创建数据库
create database if not exists study01 character set utf8mb4;
这里的意思是创建一个如果名为study01的数据库不存在则创建一个名为study01的数据库,并且设置它的字符集为uft8mb4
create database [约束] 名称 设定字符集;
约束会在后面详细讲到,这里我们将这个数据库的字符集设置为uft8mb4,我们可以了解一些基本的字符集
- ASCII是基本的英文字符集,只包含128个字符,包括26个英文字母、数字和一些符号等。
- GB2312:中国的基本汉字字符集,包含了6763个汉字和682个非汉字字符,但无法表示所有的汉字。
- GB6:GB2312的扩展字符集,包含了全部汉字和大部分的符号
- UTF-8 / UTF8MB4:一种可变长度的Unicode字符编码,可以表示全世界所有字符,并且兼容ASCII码,是目前应用最为广泛的字符集之一,在数据库中utf8不包含一些特殊的中文符号,所以建议大家全部使用uft8mb4。
这里成功创建一个名为study01的utf8mb4字符集的数据库。
选中数据库
use study01;
创建完数据库之后,要想对这个数据库进行操作,我们要选中这个数据库,use 数据库名 就是这个操作的语法
删除数据库
drop study01 study01;
删库之后库中的所有表会一并删除,且无法撤销
这里的操作是删除名为study01的数据库,注意:在实际工作中,删库操作造成的后果是非常非常非常非常严重的!!!(划重点),就算你要提桶跑路,你也不能删库,不然很有可能下半辈子你就衣食无忧了(懂得都懂)
但是玩归玩闹归闹,这个命令还是要记一下的,就是别真删就好了。
创建表结构
下面进行实战操作,首先我们创建这样一个表
转化为SQL的语法就是:
create table student (name varchar(20),age int,gender varchar(10));
create table 表名(变量名 变量类型,......);
这是创建表的语法,注意:这里括号内是先写变量名,在写变量类型,其实这也是一种常见的语法格式,但和C/JAVA这些语言还是有些区别的,大家注意区分
现在我们已经在数据库中创建了这样一张表,那我们如何来查看这张表的结构呢
desc student;
这个命令:desc 表名 就可以查看表的结构,desc是describe的缩写
这就是我们刚刚创建的student表的结构,Field是字段对应变量名,Type是类型,Null对应的是这一列是否为可以空,key是这个表的索引,由于建表时没有设置索引所以这里为空,Default是默认值,这里也没有设置所以为NULL,Extra表示这些字段没有额外的属性。这里涉及到索引主键等知识会在后面补充。
此时表中是没有数据的,如果要对这张表进行操作,就需要用到其他的命令
数据库的增删查改(CRUD)
这是数据库的核心内容,也是今后日常工作70%的工作内容,掌握了数据库的增删查改,你就已经成为了一名基本的码农(大概?)。
在上面我们创建了一个名为student的表并查看了它的结构,现在有如下数据,我们应该如何插入进表当中
-
数据库表的插入
-
全列插入
基本语法:insert into 表名 values (数据);
insert into student values('张三',18,'男');
这样我们就将张三这个学生的数据插入到了表中
如果有很多个数据,我们是不是要每一次都写这么长的一个语法来插入呢,其实不然,SQL是支持同时插入多条数据的,且这样的效率更为高效
语法:insert into 表名 values (数据),(数据);
insert into student values('李四',12,'男'),('小红',16,'女');
这样就把剩下的两个数据也插入到了表中,刚才提到,同时插入多条数据比一条条插入数据更加高效,这是因为,数据库是一种客户端-服务端的结构
每一次插入数据,客户端就会将请求发送给服务端进行响应,响应完成后,在返回给服务端,一条一条插入就要客户端多次发送请求,服务端也会多次发送结构,而一次性插入,则服务端只需要响应一次即可。所以一次插入比多次插入更加高效。
多列插入
语法:insert into student (变量名) values (数据);
数据的插入也并非一定要一一匹配才能插入,只插入某一个数据也是可以的
例如插入这样一行数据:喜羊羊,18岁,性别未知
insert into student (name,age) values('喜羊羊',18);
这样就只插入了喜羊羊的姓名和年龄而没有插入它的性别
-
数据库表的查询
在上面的示例中,我们插入了四行数据,那么我们如何查询我们插入的数据呢
1.全列查询
语法:select * from 表名;
select * from student;
这样就查询到了我们刚刚插入的数据。
注意:全列查询是一个非常危险的操作,如果表中的数据过多,使用全列查询可能会导致服务器所需资源过多导致宕机!
2.单列查询
语法:select 列名 from 表名;
除了全列查询,也可以单独指定一列查询数据,例如在这个表中我们只需要查询他们的名字,就可以这么写
select name from student;
3.查询列为表达式
语法:select 表达式 from 表名
在查询时,查询字段部分也可以为表达式,例如我们需要查询这个表中所有人的年龄并且在他们的年龄基础上加上10
select name,age+10 from student;
注意:这里的表中的结果只是服务端返回回来的临时结果,实际表中存储的数据并没有发生任何变动。
4.别名
语法:select 字段 as 别名 from 表名;
如果我们想修改得到的表中的标题单元格中的数据,我们可以用到别名这个语法,例如我们查询name这个数据,但是我们要让name的标题变为‘姓名’。
select name as 姓名 from student;
5.去重
语法:select distinct * from student
如果表中存在重复元素,且在查询时想要这些查询元素只出现一次,就可以使用去重,具体效果大家可以自行实验;
6.排序
语法:select 字段 from 表名 order by desc/asc;
注意:排序的默认序列为asc(升序,从小到大),也可以在后面加desc关键字使其变为降序排序。如果不加关键字,则默认是asc。
如果我们要对表中的元素进行排序,可以使用排序来进行数据的排序,例如在这个表中我们依据他们的年龄进行排序
select * from student order by age;
在排序中,NULL视为一个最小值,且没有order by的子句的查询,返回的顺序永远是未定义的,不可依赖它的顺序
排序也可以依据表达式查询
为了更好的演示结果,我们重新定义一张表
create table student02 (
name varchar(20),
age int,gender varchar(5),
math decimal(3,1),
english decimal(3,1),
chinese decimal(3,1) );
INSERT INTO student02 VALUES
('小明', 13, '男', 59, 54, 89),
('小红', 14, '女', 67, 68, 54),
('小兰', 15, '女', 58, 24, 52),
('喜羊羊', 16, '男', 97, 29, 87),
('沸羊羊', 17, '男', 86, 47, 23),
('暖羊羊', 18, '女', 79, 57, 87),
('暖羊羊', 19, '男', 81, 89, 89);
接着说回上面的表达式排序,我们要对这张表中math+english+chinese 进行排序
select * from student02 order by math+english+chinese;
除了表达式排序,排序还可以支持多个列同时进行排序,例如对语文进行升序排序,对数学进行降序排序,结果就会首先对语文进行升序排序,再将对语文进行升序排序过的表依据数学进行降序排序。
语法:select 列名 from 表名 order by 列名 desc/asc , 列名 desc/asc ...... ;
select * from student02 order by chinese, math desc;
7.条件查询:where
语法:select 列名 from 表名 where(条件 括号可省略);
条件运算符:
运算符 | 说明 | 示例 | 示例说明 | 结果 |
---|---|---|---|---|
> >= < <= | 大于,大于等于,小于,小于等于 | select * from student02 where math < 60; | 在student02这个表中查询math小于60的数据 | ![]() |
= | 等于,NULL不安全,NULL = NULL 结果为NULL | select * from student02 where math = 59; | 在student02这个表中查询math等于59的数据 | ![]() |
<=> | 等于,NULL安全 | |||
!= <> | 不等于 | select * from student02 where math != 59; | 在student02这个表中查询math不等于59的数据 | ![]() |
between a and b | 范围匹配,【a,b】 | select * from student02 where math between 60 and 100; | 在student02这个表中查询math在60到100之间的数据 | ![]() |
in (a,b,c.....) | 如果值为a,b,c或是括号内的别的值则返回true(1) | select * from student02 where math in (67,86); | 在student02这个表中查询math等于67、86的数据 | ![]() |
is null | 是NULL | select * from student02 where math is null; | 在student02这个表中查询math等于空的数据 | ![]() |
is not null | 不是NULL | select * from student02 where math is not null; | 在student02这个表中查询math不等于空的数据 | ![]() |
like | 模糊匹配。%表示任意多个,_表示一个。 | select * from student02 where name like '小%'; | 在student02这个表中查询第一个为小的名字的数据 | ![]() |
运算符 | 说明 |
---|---|
AND | 类似于JAVA中的 && |
OR | 类似于JAVA中的 || |
NOT | 类似于JAV中的 ! |
注意:where条件可以使用表达式但不能使用别名,and的优先级大于or
8.分页查询:limit
语法:select 列名 from 表名 where() order by limit 0,n; 从0开始,查询n条数据
例如查询这个表中数学成绩大于60的同学以降序排序并取前两名:
select * from student02 where math >60 order by math desc limit 2;
也可以跳过数据,使用关键字offset
例如查询math > 0 的数据,按照降序排序,显示两条数据并且跳过最上面两条数据
select * from student02 where math > 0 limit 2 offset 2;
数据库表的修改
语法:update 表名 set 列名 = 要修改的值 where() order by;
例如:将小明的数学成绩更改为99分
update student02 set math = 99 where name = '小明';
查询一下修改后的结果:
数据库表的删除Delect
语法:delete from 表名 where() order by ;
例如:删除小明同学的成绩:
delete from student02 where name = '小明';
这里就把小明的全部数据删除了,注意:删除只能把一张表或者一行全部删除,如果要修改这一行的某一个具体数据。请使用update进行更新
数据库约束
约束表示在创建表时对表的结构的约束
约束类型:
约束类型 | 说明 |
---|---|
NOT NULL | 指示某列不能存储 NULL 值。 CHECK - 保证列中的值符合指定的条件。对于MySQL数据库,对CHECK子句进行分析,但是忽略 CHECK子 |
UNIQUE | 保证某列的每行必须有唯一的值。 |
DEFAULT | 规定没有给列赋值时的默认值。 |
PRIMARY KEY | NOT NULL 和 UNIQUE 的结合。确保某列(或两个列多个列的结合)有唯一标 识,有助于更容易更快速地找到表中的一个特定的记录。 |
FOREIGN KEY | 保证一个表中的数据匹配另一个表中的值的参照完整性。 |
CHECK | 保证列中的值符合指定的条件。对于MySQL数据库,对CHECK子句进行分析,但是忽略 CHECK子句。 |
下面将这些约束类型进行一个一个讲解
NULL类型
即在创建表时约束表的结构不能为空,例如创建一个表A A中含有name这一个属性,我们可以使用not null来约束name这一列不能为空。例如
create table A (name varchar(20) not null);
然后可以查询这个表的结构,我们发现NULL这一行的值为NO,意思就是这一列的数据是不能为空的。
UNIQUE类型:唯一值约束
即标识这一列的数据为唯一的,不可重复的,使用这个约束之后,每一列的每一个数据有且只有一个,不能重复。例如我国的身份证号,每个人的身份证号都是独一无二的,如果以中国人民为数据创建一张表,那么身份证的约束就是UNIQUE;
DEFAULT:默认值约束
即约束在插入数据时如果对某一列没有插入任何数据(也不能是NULL),那么它就会默认为DEFAULT约束的值
例如设置一张学生表,name列的默认值约束为unkown
CREATE TABLE student03 (
id INT NOT NULL,
sn INT UNIQUE,
name VARCHAR(20) DEFAULT 'unkown',
qq_mail VARCHAR(20)
);
我们可以试一试往表中插入一个id为1 sn为5 name不设置 qq_mail为123@123.com的值;
结果如下,可以看到name一列的数据为unkowm。
PRIMARY KEY:主键约束
语法:create table 表名(列名 primary key auto_increment);
主键在一张表中有且只有一个,且主键一列的内容是不可以发生重复的,一张表也可以有其他的唯一键,但主键只有一个,表被创建完毕后,数据库会依据自身存储数据的结构来构建一个用于存储索引的结构,而这里的索引就相当于主键。
auto_increment:这个参数的意思是自增主键,如果插入数据时主键一列没有插入数据,则会默认插入自增主键中的数据,自增主键在一开始默认为1,且插入完成后,这个主键自增,在后面在插入数据时,如果没有插入主键一列的数据,主键会根据前一行的主键进行自增并插入自增主键当中的数据。
例如创建一张学生表,并且设置它的id为主键
drop table if exists student04;
create table student04 (
id int primary key auto_increment ,
name varchar(20) not null
);
演示一下自增主键,例如插入数据:(1,'小明'),('小红'),(5,'小兰'),(‘小强’);
可以看到,插入小明时指定主键为1,插入小红时没有设置,主键自增,插入小强时指定主键为5,插入小强时没有设置主键,主键自增后为6
FOREIGN KEY:外键约束
语法:foreign key (字段名) references 主表(列)
外键的作用是将两张表关联起来,例如
现在我们创建两张新的表:学生表student05,班级表class;
可以看到,学生表中有一列:所属班级,是关联于班级表的:在学生表中的class_id中的数据与班级表中的id数据是一一对应的,这样两张表就实现了互相约束,即学生表中class_id的数据在班级表中的id列中必须存在,而要删除班级表中id一列的数据也必须保证学生表中class_id一列没有相关联数据。
创建语句是:
CREATE TABLE class (
id INT PRIMARY KEY (id) AUTO_INCREMENT,
name VARCHAR(50),
`desc` VARCHAR(100),
);
CREATE TABLE student (
id INT PRIMARY KEY (id) AUTO_INCREMENT,
name VARCHAR(50),
class_id INT FOREIGN KEY (class_id) REFERENCES class(id)
);
注意:这里的desc要加反引号 是因为desc是mysql的一个关键字,以关键字作为表名都需要加反引号。
这样就实现了两张表的关联
表的设计
表的设计在实际开发中需要根据需求具体设计,在这里只讲解一些最基本的设计思路。
一对一:
常见的有:一个人对应一个身份证。一个身份证对应一个人
一对多:
常见的有:一个学生所属一个班级,一个班级中有许许多多的学生
多对多:
常见的有:多个学生要上多个课程,课程拥有一张描述课程信息的表,还有一张课程表将课程与学生关联起来
聚合查询
聚合函数
在select查询中,除了表达式以外,也可以使用聚合函数来进行查询,下面是一些常用的聚合函数
函数 | 说明 | 示例 | 示例说明 | 结果 |
---|---|---|---|---|
count() | 返回查询数据的数量 | select count(*) from student02; | 查询student2里一共有多少数据 | ![]() |
sum() | 返回查询数据的总和 | select sum(math) as 数学成绩总和 from student02; | 查询student2里数学成绩总和 | ![]() |
avg() | 返回查询数据的平均值 | mysql> select avg(math) as 数学平均分 from student02; | 查询student2里数学平均分 | ![]() |
max() | 返回查询数据的最大值 | select max(math) as 数学最高分 from student02; | 查询student2里数学最高分 | ![]() |
min() | 返回查询数据的最小值 | select min(math) as 数学最低分 from student02; | 查询student2里数学最低分 | |
子查询:
子查询简单来说就是套娃,对于一个数据如果需要进行多次查询才能查询出来,那么就可以使用子查询来实现在一个SQL语句中完成查询,注意:子查询的代码阅读性的非常差的,所以使用子查询时不可套太多层
语法:
Group by子句
语法:select 列名 from 表名 group by 列名;
在查询时还可以对指定列进行分组查询,例如在student02这张表里对
例如在这样一张表中
查询每个角色的最高工资,最低工资,平均工资
create table emp(
id int primary key auto_increment,
name varchar(20) not null,
role varchar(20) not null,
salary numeric(11,2)
);
insert into emp(name, role, salary) values
('马云','服务员', 1000.20),
('马化腾','游戏陪玩', 2000.99),
('孙悟空','游戏角色', 999.11),
('猪无能','游戏角色', 333.5),
('沙和尚','游戏角色', 700.33),
('隔壁老王','董事长', 12000.66);
select role,max(salary),min(salary),avg(salary) from emp group by role;
这就是以每个人的职业来分组,并将组中最大值最小值平均值输出出来
HAVING子句
在上面的group by分组完毕后,如果还要对这些组进行进一步筛选,就需要用到HAVING子句,例如对上面已经分完组的数据进一步筛选:显示平均工资低于1500的角色和它的平均工资
select role,max(salary),min(salary),avg(salary) from emp group by role
having avg(salary)<1500;
联合查询
在实际开发中,数据往往来自于不同的表,为了从多个表中查询到数据,需要用到联合查询
笛卡尔积
笛卡尔积就是将两张表每一行分别相乘,得出一张大表
内连接:
内连接就是将两张表联合起来进行查询
语法:
select 列名 from 表1,表2 或
select 列名 from 表 join 表2 on 连接条件
在将两张表笛卡尔积后,会得到一张大表,在大表中是存在许多无用数据的,因此要得到想要查询的数据,还需要对这张大表进行条件约束,例如在这样一张表中
我们需要查询学生的名字以及学生所属班级的名字
首先对两张表进行笛卡尔积可以得到一张大表
对这张表进行分析我们可以看到,小红属于六班,但在这张表中,有一些行的数据是错误的,例如小红属于六班,但在第四行中小红属于7班,为了筛选掉错误数据,我们可以将条件设为:这两张表中的关联元素需要一一匹配。 在这里就是让class和class_id匹配
select * from a,b where (a.class = b.class_id);
或者
select * from a join b on a.class = b.class_id;
这样就成功的将两张表联合起来,得到了所需数据
外连接
外连接又分为左外连接与右外连接
内连接是将两张表的结果全部查询出来,那么外连接就是查询两张表的单张表和其交集部分。如下图
左外连接:
语法:select 列名 from 表2 left join 表1 on 约束条件
右外连接:
语法:select 列名 from 表2 right join 表1 on 约束条件
自连接:
自连接是一种特殊的连接方法,一张表就可以实现自连接,它将自身和自身进行笛卡尔积来进行一些特殊的查询,这部分无需做过多了解,因为实际开发中使用到的地方很少,大家可以自行查阅。
合并查询:
在实际应用中,为了合并多个select的执行结果,可以使用集合操作符 union,union all。使用UNION 和UNION ALL时,前后查询的结果集中,字段需要一致0
- union
这个操作符用于合并两个结果集的并集,并且会自动去重
例如:查询id小于3,或者名字为“英文”的课程。
select * from course where id<3
union
select * from course where name = '英文';
或者
select * from course where id<3 or name = '英文';
- union all
这个操作符用于取得两个结果集的并集,但不会去掉重复行
例如:查询id小于3,或者名字为“Java”的课程
-- 可以看到结果集中出现重复数据Java
select * from course where id<3
union all
select * from course where name='java';
索引
索引是一种特殊的文件,其中包含的数据是数据表中所有记录的引用指针,可以对表中的一列或多列创建索引,各类索引都有不同的数据结构实现。
为了更好的理解索引,首先我们需要了解一个数据结构:B-/B+树
可以看我写的这篇博客:【数据结构】B树与B+树的区别 – 可乐的博客
数据库中的表、数据、索引之间的关系,就相当于书柜上的图书,书的内容,书的目录
索引的作用类似于哈希表的索引,可以帮助快速检索数据定位,索引对于数据库性能的提高有巨大帮助
事务
在互联网尚不发达的年代,大家可以都经历过这种事情,那时没有微信支付、支付宝。涉及到网络转账只能通过银行进行。如果你的朋友给你汇款1000元,那么在他在银行汇款完毕后,你一定会第一时间到银行查询余额是否到账。因为当时是存在汇款不到账的情况的。
这是因为,当时的数据库并不发达,如果把你朋友的账户视为A 自己的账户视为B
朋友给你转账时就涉及这些操作:
1.A的账户减少1000元
2.B的账户增加1000元
而在当时的数据库中,数据是一条一条执行的。有可能当A执行完毕后,服务器宕机了,导致B的操作没有执行,这样就发生了朋友给你转账,钱没了,你也没接收到的情况。
为了防止这种情况的发生,数据库就有了“事务”这一特性。
事务的存在,将操作1和操作2打包成一组操作。执行操作时,要么1和2全部执行,要么全部失败。
执行操作:
1.开启事务:start transaction;
2.执行SQL语句;
3.回滚或提交:rollback/commit;
事务的四大特性(八股文):
1.原子性(最核心的特性):事务是不可再分的,保持一个整体。
2.一致性:事务在执行前后,数据是靠谱的。例如A减少了1000,B就得增加1000。
3.持久性:事务修改的内容是写到硬盘的,是持久存在的,重启也不会丢失。
4.隔离性:隔离性是为了解决”并发“执行事务时引起的问题。
并发:如果把服务器比作餐馆,事务比作来吃饭的客人,那么一个餐馆为了提高运作效率,就需要同时解决多个客人的需求,此时这种操作类比到数据库中就是并发。
并发执行的问题又分为四类:
脏读:
例子:小明正在考试,坐在它旁边的小红看见小明第一题的答案为A。于是小红就抄下了这个答案,在考试结束时,小明将这个答案改为了B。
一个事务A在对数据进行修改的过程中,事务B读取了事务A,而在事务B读取完毕后,事务A又修改了数据,这样B最终读到的数据是错误的,也将B的读取操作叫做脏读,读到的数据也成为脏数据。
解决思路:
写操作加锁:read committed,即A在进行数据修改时加锁,不允许任何事务来访问A,在A提交完毕之后,在解锁放开权限。
此时脏读问题被解决,但降低了事务的并发处理能力。
不可重复读:
不可重复读是在解决脏读情况下才会出现的问题
例子:A和B都是程序员,在A将程序员提交到网站之后,B正在读取A的代码,但此时A修改了代码,此时B读到的代码唰的一下就变了。这个问题就叫做不可重复读
一个事务A已经提交数据,此时事务B去读取数据,但读取过程中,事务C又提交了新的数据,此时同一个事务B里,多次读取数据,读出来的结果不相同。即读取的结果是不可复现的。
解决思路:给读加锁(repetable read),即B在阅读时,任何人不可来修改这个数据
此时不可重复读的问题被解决,事务的并发处理能力进一步降低。
幻读:
幻读是在解决不可重复度的前提下才会出现的问题
例子:解决了不可重复读问题后,B在读A写的代码时A不能对代码进行操作了,但是为了提高效率,他又另开了一个页面来写代码,此时B就发现了:虽然他读的代码没有改动,但又突然冒出了一个新的页面,这个页面是A新开的页面,也就是结果集不一致了。
在读加锁和写加锁的前提下,一个事务两次读取一个数据,数据值是一样的,但结果集不一样,这就是幻读
解决思路:彻底”串行化“(serializable),即彻底放弃并发处理事务,一个接一个的串行处理事务,此时并发程度最低,但数据的准确性最高。
JDBC编程
在数据库中,很多复杂的逻辑是无法实现的,为了实现这些逻辑,我们就需要使用JAVA来进行数据库的连接,并且可以使用JAVA语言来操作数据库。
各种数据库,MySQL,Oracle,SQL Server ...... 在开发时,都会提供一组编程接口(API)
API:在一个程序中,对于程序员而言,是一堆代码,但对于用户来说,是一个程序,程序员可以在代码层面上修改程序,但用户不能,所以程序员会将这个程序的多个功能写成多个接口,这些接口就叫做API,用户可以通过API来操作程序。
多个数据库的API是不同的,这就导致程序员的学习成本极高,JAVA为了解决这个问题,就给了一个接口标准,让多个数据库的API符合这个标准这个标准就叫做JDBC。
数据库厂商原生的API是不需要改动的,他们只需要提供一个程序,来完成对原生API的封装转换,通过这个程序,API就变成了JDBC的形状,这个程序就叫做:数据库驱动包
这样,JAVA程序员想要进行数据库开发,就只需要在项目中导入对应数据库的驱动包,就可以编写代码了。
这里给出maven的中央仓库,可以从中找到mysql的数据驱动包:www.mvnrepository.com
此时点进去,发现有很多版本,还记得文章一开始让你记住的数据库版本吗,不记得也没关系,打开mysql,输入密码后就可以直接看到。
数据库版本的选择原则是:大版本必须对应,小版本随意。例如我的数据库版本是:Server version: 5.7.27-log MySQL Community Server (GPL) 那我就需要选择5开头的版本。
下载好对应版本后,我们会得到一个.jar文件。此时我们打开idea编译器-创建项目
创建完毕后,右键项目-new-Directory,创建一个文件夹,名字可以随意取,我叫lib
然后我们将这个包复制粘贴进lib文件夹里。
然后把这个文件夹标记为库
此时可以看到lib下有许多的类,就代表已经导入成功了。
下面就可以开始进行JDBC开发了
JDBC的开发固定分为以下操作:
这些代码刚接触时会觉得复杂,但写多了会发现,这些套路是写死的,都是固定套路。写多几次就很熟悉了。
1.创建数据源
固定套路,注意向上转型和向下转型即可,不转也可以,但程序员流行这么写。
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
public class test {
public static void main(String[] args) {
//1.创建并初始化数据源
DataSource dataSource = new MysqlDataSource();//向上转型
//向下转型
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java01?characterEncoding=utf8&useSSL=false");//设置url
((MysqlDataSource)dataSource).setUser("root");//数据库用户名
((MysqlDataSource)dataSource).setPassword("1111");//数据库密码
//2.和数据库服务器建立连接
//3.构造SQL语句
//4.执行SQL语句
//5.释放资源
}
}
这里的url:jdbc:mysql://127.0.0.1:3306/java01?characterEncoding=utf8&useSSL=false
jdbc是固定的,mysql是数据库类型 127.0.0.1:3006是IP地址:端口号,characterEncoding=utf8&useSSL=false是字符集类型和是否加密。
2.和数据库服务器建立连接
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class test {
public static void main(String[] args) throws SQLException {
//1.创建并初始化数据源
DataSource dataSource = new MysqlDataSource();//向上转型
//向下转型
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java01?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("1111");
//2.和数据库服务器建立连接
Connection Connection = dataSource.getConnection();
//3.构造SQL语句
//4.执行SQL语句
//5.释放资源
}
}
注意,这里用Connection来结束了.getConnection返回的可连接类型,这里的Connection是java.sql这个包里的
建立连接完毕后,这里就和数据库连上了,接下来就可以对数据库进行操作了
3.构建SQL语句
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class test {
public static void main(String[] args) throws SQLException {
//1.创建并初始化数据源
DataSource dataSource = new MysqlDataSource();//向上转型
//向下转型
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java01?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("1111");
//2.和数据库服务器建立连接
Connection Connection = dataSource.getConnection();
//3.构造SQL语句
Scanner scanner = new Scanner(System.in);
int id = scanner.nextInt();
String name = scanner.next();
String sql = "insert into student values(?,?)";
PreparedStatement statement = Connection.prepareStatement(sql);//预编译
statement.setInt(1,id);
statement.setString(2,name);
//4.执行SQL语句
//5.释放资源
}
}
首先我们创建两个变量接收要输入的数据,然后在使用一个字符串将要执行的SQL语句放入。
这里的 String sql = "insert into student values(?,?)";中的”?“是占位符,我们构建完SQL语句后,对SQL语句进行预编译
PreparedStatement statement = Connection.prepareStatement(sql)
然后在将占位符替换成我们要插入的数据,这样就可以防止数据库被进行sql注入。
4.执行SQL语句
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class test {
public static void main(String[] args) throws SQLException {
//1.创建并初始化数据源
DataSource dataSource = new MysqlDataSource();//向上转型
//向下转型
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java01?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("1111");
//2.和数据库服务器建立连接
Connection Connection = dataSource.getConnection();
//3.构造SQL语句
Scanner scanner = new Scanner(System.in);
int id = scanner.nextInt();
String name = scanner.next();
String sql = "insert into student values(?,?)";
PreparedStatement statement = Connection.prepareStatement(sql);
statement.setInt(1,id);
statement.setString(2,name);
//4.执行SQL语句
int ret = statement.executeUpdate();
System.out.println(ret);
//5.释放资源
}
}
注意:如果是select操作,就需要调用statement.executeQuery方法来执行代码,如果是update、insert、delect等操作,就调用statement.executeUpdate。执行完毕后会返回一个数字,用ret接收,ret表示执行成功了几行代码。
5.释放资源
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class test {
public static void main(String[] args) throws SQLException {
//1.创建并初始化数据源
DataSource dataSource = new MysqlDataSource();//向上转型
//向下转型
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java01?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("1111");
//2.和数据库服务器建立连接
Connection Connection = dataSource.getConnection();
//3.构造SQL语句
Scanner scanner = new Scanner(System.in);
int id = scanner.nextInt();
String name = scanner.next();
String sql = "insert into student values(?,?)";
PreparedStatement statement = Connection.prepareStatement(sql);
statement.setInt(1,id);
statement.setString(2,name);
//4.执行SQL语句
int ret = statement.executeUpdate();
System.out.println(ret);
//5.释放资源
statement.close();
Connection.close();
}
}
这里遵循的原则是:后创建,先释放。即statment是后创建的,就最先释放,Connection是先创建的,就后释放,
这就是JDBC编程。
无需做过多理解,记住即可,因为在实际开发中不可能经常创建数据库,套路也是固定写死的,更何况市面上有很多框架会将这个套路优化省略掉,所以只需要熟悉即可。