数据库概述

  • 数据库是“按照数据结构来组织、存储和管理数据的仓库”。是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。

为什么要使用数据库?

  • 数据持久性: 数据库是设计用来持久存储数据的,这意味着数据在断电或系统关闭后不会丢失。这对于长期存储和恢复数据至关重要。
  • 数据共享: 多个用户和应用程序可以同时访问数据库,这允许多人协作,并确保数据的一致性和可靠性。
  • 数据组织: 数据库允许您以结构化的方式组织数据,这使得数据容易管理和查询。您可以使用表格、索引和关系来存储和检索数据,而不是将其散落在不同的文件中。
  • 数据完整性: 数据库系统提供了数据完整性的机制,可以确保数据的准确性和一致性。这包括主键、外键和约束等功能,可以帮助防止错误的数据输入或修改。
  • 数据安全: 数据库可以实施访问控制和权限管理,以确保只有授权用户可以访问和修改数据。这对于保护敏感信息非常重要。
  • 数据备份和恢复: 数据库管理系统通常提供备份和恢复功能,以防止数据丢失或损坏。这有助于应对硬件故障、人为错误或灾难性事件。
  • 高性能: 数据库系统经过优化,可以处理大量数据和复杂查询。这对于需要高度性能的应用程序(如电子商务、社交媒体和金融系统)至关重要。
  • 数据分析: 数据库可以存储大量数据,这些数据可以用于分析和报告。通过执行查询和分析,您可以获得有关业务绩效、趋势和模式的重要见解。

数据库相关概念

DB:数据库(Database)
即存储数据的“仓库”,其本质是一个文件系统。它保存了一系列有组织的数据。
DBMS:数据库管理系统(Database Management System)
是一种操纵和管理数据库的大型软件,用于建立、使用和维护数据库,对数据库进行统一管理和控 制。用户通过数据库管理系统访问数据库中表内的数据。
SQL:结构化查询语言(Structured Query Language)
专门用来与数据库通信的语言。
  • 关系

数据库管理系统(DBMS)可以管理多个数据库,一般开发人员会针对每一个应用创建一个数据库。为保存应用中实体的数据,一般会在数据库创建多个表,以保存程序中实体用户的数据。

常见数据库介绍

Oracle

  • 1979 年,Oracle 2 诞生,它是第一个商用的 RDBMS(关系型数据库管理系统)。随着 Oracle 软件的名气 越来越大,公司也改名叫 Oracle 公司。 2007年,总计85亿美金收购BEA Systems。
  • 2009年,总计74亿美金收购SUN。此前的2008年,SUN以10亿美金收购MySQL。意味着Oracle 同时拥有了 MySQL 的管理权,至此 Oracle 在数据库领域中成为绝对的领导者。
  • 2013年,甲骨文超越IBM,成为继Microsoft后全球第二大软件公司。
  • 如今 Oracle 的年收入达到了 400 亿美金,足以证明商用(收费)数据库软件的价值。

SQL Server

  • SQL Server是微软开发的大型商业数据库,诞生于 1989 年。C#、.net等语言常使用,与WinNT完全集 成,也可以很好地与Microsoft BackOffice产品集成。

DB2

  • IBM公司的数据库产品,收费的。常应用在银行系统中。

PostgreSQL

  • PostgreSQL 的稳定性极强,最符合SQL标准,开放源码,具备商业级DBMS质量。PG对数据量大的文本以 及SQL处理较快。

SQLite

  • 嵌入式的小型数据库,应用在手机端。 零配置,SQlite3不用安装,不用配置,不用启动,关闭或者配置 数据库实例。当系统崩溃后不用做任何恢复操作,再下次使用数据库的时候自动恢复。

informix

  • IBM公司出品,取自Information 和Unix的结合,它是第一个被移植到Linux上的商业数据库产品。仅运行 于unix/linux平台,命令行操作。性能较高,支持集群,适应于安全性要求极高的系统,尤其是银行,证券系统的应用。

MySQL介绍

概述

  • MySQL是一个开放源代码的关系型数据库管理系统**,由瑞典MySQL AB(创始人Michael Widenius)公 司1995年开发,迅速成为开源数据库的 No.1。
  • 2008被Sun收购(10亿美金),2009年Sun被Oracle收购。MariaDB 应运而生。(MySQL 的创造者担心 MySQL 有闭源的风险,因此创建了 MySQL 的分支项目 MariaDB)
  • MySQL6.x 版本之后分为社区版 和 商业版
  • MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而不是将所有数据放在一个大仓库 内,这样就增加了速度并提高了灵活性。 MySQL是开源的,所以你不需要支付额外的费用。
  • MySQL是可以定制的,采用了GPL(GNU General Public License) 协议,你可以修改源码来 开发自己的MySQL系统。
  • MySQL支持大型的数据库。可以处理拥有上千万条记录的大型数据库。 支持5000万条记录的数据仓库,32位系统表文件最大可支持 4GB ,64位系 统支持最大的表文件为 8TB 。
  • MySQL使用标准的SQL数据语言形式。
  • MySQL可以允许运行于多个系统上,并且支持多种语言。这些编程语言包括C、C++、Python、 Java、Perl、PHP和Ruby等。

RDBMS 与 非RDBMS

  • 关系型数据库(RDBMS)

  • 这种类型的数据库是最古老的数据库类型,关系型数据库模型是把复杂的数据结构归结为简单的二元关系(即二维表格形式)。

  • 关系型数据库以**行(row)和列(column)**的形式存储数据,以便于用户理解。这一系列的行和列被称为表(table) ,一组表组成了一个库(database)。

  • 表与表之间的数据记录有关系(relationship)。现实世界中的各种实体以及实体之间的各种联系均用关系模型来表示。

  • 关系型数据库,就是建立在关系模型基础上的数据库。SQL 就是关系型数据库的查询语言。

  • 优势

    • 复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。
    • 事务支持使得对于安全性能很高的数据访问要求得以实现。
  • 非关系型数据库(非RDBMS)

  • 非关系型数据库,可看成传统关系型数据库的功能的阉割版本基于键值对存储数据,不需要经过SQL层 的解析,性能非常高 。同时,通过减少不常用的功能,进一步提高性能。目前基本上大部分主流的非关系型数据库都是免费的。

  • 键值型数据库通过 Key-Value 键值的方式来存储数据,其中Key和Value可以是简单的对象,也可以是复 杂的对象。Key 作为唯一的标识符,

    • 优点是查找速度快,在这方面明显优于关系型数据库,
    • 缺点是无法像关系型数据库一样使用条件过滤(比如 WHERE),如果你不知道去哪里找数据,就要遍历所有的键, 这就会消耗大量的计算。 键值型数据库典型的使用场景是作为 内存缓存 。 Redis 是最流行的键值型数据库。
  • 文档型数据库可存放并获取文档,可以是XML、JSON等格式。在数据库中文档作为处理信息的基本单位, 一个文档就相当于一条记录。文档数据库所存放的文档,就相当于键值数据库所存放的“值”。MongoDB 是最流行的文档型数据库。此外,还有CouchDB等。

  • 列式数据库是相对于行式存储的数据库,Oracle、MySQL、SQL Server 等数据库都是采用的行式存储 (Row-based),而列式数据库是将数据按照列存储到数据库中,这样做的好处是可以大量降低系统的 I/O,适合于分布式文件系统,不足在于功能相对有限。典型产品:HBase等。

  • 图形数据库顾名思义,就是一种存储图形关系的数据库。它利用了图这种数据结构存储了实体(对象) 之间的关系。关系型数据用于存储明确关系的数据,但对于复杂关系的数据存储却有些力不从心。如社 交网络中人物之间的关系,如果用关系型数据库则非常复杂,用图形数据库将非常简单。典型产品: Neo4J、InfoGrid等。

关系型数据库设计规则

表、记录、字段

  • E-R(entity-relationship,实体-联系)模型中有三个主要概念是:实体集 、属性 、联系集 。
    • 一个实体集(class)对应于数据库中的一个表(table),
    • 一个实体(instance)则对应于数据库表 中的一行(row),也称为一条记录(record)。
    • 一个属性(attribute)对应于数据库表中的一列 (column),也称为一个字段(field)。

表的关联关系

一对一关系

举例:设计学生表 :学号、姓名、手机号码、班级、系别、身份证号码、家庭住址、籍贯、紧急 联系人、

  • 拆为两个表:两个表的记录是一一对应关系。
  • 基础信息表 (常用信息):学号、姓名、手机号码、班级、
  • 别 档案信息表 (不常用信息):学号、身份证号码、家庭住址、籍贯、紧急联系人、

两种建表原则:

外键唯一:主表的主键和从表的外键(唯一),形成主外键关系,外键唯一。

外键是主键:主表的主键和从表的主键,形成主外键关系。

一对多关系

实例场景: 客户表和订单表 , 分类表和商品表 , 部门表和员工表 。

举例:

  • 员工表:编号、姓名、…、所属部门

  • 部门表:编号、名称、简介

一对多建表原则:在从表(多方)创建一个字段,字段作为外键指向主表(一方)的主键

多对多关系

要表示多对多关系,必须创建第三个表,该表通常称为联接表 ,它将多对多关系划分为两个一对多关系。将这两个表的主键都插入到第三个表中。

举例:用户-角色

SQL概述

  • SQL(Structured Query Language,结构化查询语言)是使用关系模型的数据库应用语言, 与数据直接打交道 ,由 IBM 上世纪70年代开发出来。后由美国国家标准局(ANSI)开始着手制定SQL标准, 先后有 SQL-86 , SQL-89 , SQL-92 , SQL-99 等标准。
    • SQL 有两个重要的标准,分别是 SQL92 和 SQL99,它们分别代表了 92 年和 99 年颁布的 SQL 标 准,我们今天使用的 SQL 语言依然遵循这些标准。
    • 不同的数据库生产厂商都支持SQL语句,但都有特有内容。

SQL分类

SQL语言在功能上主要分为如下3大类:

  • DDL(Data Definition Languages、数据定义语言),这些语句定义了不同的数据库、表、视图、索 引等数据库对象,还可以用来创建、删除、修改数据库和数据表的结构。 主要的语句关键字包括 CREATE 、 DROP 、 ALTER 等。
  • DML(Data Manipulation Language、数据操作语言),用于添加、删除、更新和查询数据库记 录,并检查数据完整性。 主要的语句关键字包括 INSERT 、 DELETE 、 UPDATE 、 SELECT 等。 SELECT是SQL语言的基础,最为重要。
  • DCL(Data Control Language、数据控制语言),用于定义数据库、表、字段、用户的访问权限和 安全级别。

数据库连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mysql -u root -p --连接数据库

flush privileges;--刷新权限

---------------------所有语句以;结尾
show databases; ------显示所有数据库
use school;----切换数据库
show tables; --查看数据库中所有的表
describe student; --显示数据库中所有表的信息

create database name;---创建数据库
exit --退出
-- 注释 单行
/*
多行
注释
*/

语言分类(CRUD)

DDL–数据库定义语言

DML–数据库操作管理语言

DQL–数据库查询语言

DCL–数据库控制语言

操作数据库

mysql关键字不区分大小写

1.创建数据库

1
2
3
create database [if not exists] name; --[]为可选参数  此处为判定是否存在
create database shop character set utf8 collate utf8_general_ci
---设置字符集 --设置校验

2.删除数据库

1
drop database [if exists] name;

3.使用数据库

1
use name;--如果表或字段为特殊字符 需要带飘号``

4.查看数据库

1
show databases;--查看所有的数据库

数据库的列类型

数值

  • tinyint 十分小的数据 1个字节
  • smallint 较小的数据 2个字节
  • mediumint 中等大学的数据 3个字节
  • int 标准的整数 4个字节
  • bigint 较大的数字 8个字节
  • float 浮点数 4个字节
  • double 浮点数 8个字节(精度问题!)
  • decimal 字符串形浮点数 (金融计算时使用)

字符串

  • char 字符串固定大小 0~255
  • varchar 可变字符串 0-65535
  • tinytext 微型文本 2^8-1
  • text 文本串 2^16-1(保存大文本)

时间日期

  • data YYYY-MM-DD 日期格式
  • time HH:mm:ss 时间格式
  • datetime YYYY-MM-DD HH:mm:ss 最常用时间格式
  • timestamp 时间戳 1970.1.1到现在的毫秒数
  • year 年份

null

  • 没有,未知
  • 不要使用null进行运算!

数据库的字段属性

unsigned

  • 无符号的整数
  • 声明了该列不能为负数

zerofill

  • 0填充的
  • 1->0000001 不足的位数用0填充

自增

  • 自动在上一条基础上加一
  • 通常用于设置唯一的主键
  • 可以自定义主键自增的起始值

非空 NULL not null

  • 假设为 not null 如果不赋值 就回报错
  • NULL如果不填默认为null

default

  • 默认的值

创建数据库表

1
2
3
4
5
6
7
8
9
10
11
create table [if not exists] `表名`(
`字段` 列类型 [属性] [索引] [注释],
`字段` 列类型 [属性] [索引] [注释],
`字段` 列类型 [属性] [索引] [注释]
)[表类型] [表字符集设置] [注释]

create table shop(
`id` int(3) not null auto_increment,
`name` varchar not null,
`money` varchar not null,
)engine=innodb default charset=utf8
1
2
3
show create database名 --显示创建数据库的语句
show create table 名· --显示创建表的语句
desc--显示表的结构

数据表的类型

引擎 MYISAM INNODB
事务支持 不支持 支持
数据行锁定 不支持 支持
外键约束 不支持 支持
全文索引 支持 不支持
表空间大小 较小 较大,约为2倍
  • MYISAM 解约空间,速度快
  • INNODB 安全性高,事务的处理,多表多用户操作

在物理空间存储的位置

  • MySQL
  • 所有的数据库都存在data文件夹下
  • 本质还是文件的存储

字符集编码

修改删除表

1
2
3
4
5
6
7
8
9
10
11
12
13
ALTER TABLE 旧名 RENAME AS 新名--修改表名

alter table 表名 add 字段 属性--添加表字段

alter table 表名 modify 旧属性 新属性 --修改字段约束

alter table 表名 change 旧字段 新字段 +属性(及时不改也要给) --修改字段名

alter table 表名 drop 字段 --删除表的字段

drop table [if exists] 表名

--所有创建和删除尽量加上判断

MySQL数据管理

外键

DML语言

  • insert
  • delete
  • update

增删改

1
2
3
4
5
6
7
8
9
insert into 表名 (字段1,字段2,字段3) values(值1,值2,值3)
--
insert into 表名 (字段1) values(值1),(值2),(值3)--可以插入多个值到同一字段一个()表示一行数据

update 表名 set name=value,eg=value where 条件---where条件一定要要!!!不然会修改所有数据多个字段用逗号隔开

delete from `表名` where 条件=

truncate '清空表'
  • name为数据库的列尽量带上``
  • where为筛选条件
  • value为具体的值也可以是个变量

条件:操作符会返回布尔值

操作符 含义 范围 结果
= 等于
<> 或!= 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于
between…and… 闭合区间
and &&
or ||

在使用等号运算符时,遵循如下规则:

  • 如果等号两边的值、字符串或表达式都为字符串,则MySQL会按照字符串进行比较,其比较的是每个字符串中字符的ANSI编码是否相等。
  • 如果等号两边的值都是整数,则MySQL会按照整数来比较两个值的大小。
  • 如果等号两边的值一个是整数,另一个是字符串,则MySQL会将字符串转化为数字进行比较。
  • 如果等号两边的值、字符串或表达式中有一个为NULL,则比较结果为NULL。

DQL查询

1
2
3
4
5
6
select 字段 from
select 字段,字段,字段 from--可多查询字段
select 字段 as 姓名,字段 as 班级,字段 as 学号 from--- as可以取别名在数据显示列
--也可以给表起别名
select concat(a,b) --字符串拼接
select distinct 字段 from--去重

where子句

作用:检索数据中符合条件的值

运算符 语法 描述
and && a and b
or || a or b
not ! a not b

可以加在后边

模糊查询

运算符 语法 描述
IS NULL a is null 如果a为null返回真
IS NOT NULL a is not null 如果a不为null返回真
between and a between b and c b<a<c
like ? like %??? 模糊查询
in xxx in (value1,value2,value3,…) in内为范围
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
select `name` from `school`
where `name` like '刘%'
--查询名字内含有刘的
select `name` from `school`
where `name` like '刘_'
--查询刘后有一个字符的
select `id` from `school`
where `id` in(1,2,3)
--查询id在1 2 3 之间的
select `name` from `school`
where `name` is not null
--查询名字不为空的
select `name` from `school`
where `name` is null
--查询名字为空的

like详解

匹配串中可包含如下四种通配符:
_ :匹配任意一个字符;
%:匹配0个或多个字符;
[ ]:匹配[ ]中的任意一个字符(若要比较的字符是连续的,则可以用连字符“-”表 达 );
[^ ]:不匹配[ ]中的任意一个字符。

例23.查询学生表中姓‘张’的学生的详细信息。

1
SELECT` `* ``FROM` `学生表 ``WHERE` `姓名 ``LIKE` `‘张%

例24.查询姓“张”且名字是3个字的学生姓名。

1
SELECT` `* ``FROM` `学生表 ``WHERE` `姓名 ``LIKE` `'张__’

如果把姓名列的类型改为nchar(20),在SQL Server 2012中执行没有结果。原因是姓名列的类型是char(20),当姓名少于20个汉字时,系统在存储这些数据时自动在后边补空格,空格作为一个字符,也参加LIKE的比较。可以用rtrim()去掉右空格。

1
SELECT` `* ``FROM` `学生表 ``WHERE` `rtrim(姓名) ``LIKE` `'张__'

例25.查询学生表中姓‘张’、姓‘李’和姓‘刘’的学生的情况。

1
SELECT` `* ``FROM` `学生表 ``WHERE` `姓名 ``LIKE` `'[张李刘]%’

例26.查询学生表表中名字的第2个字为“小”或“大”的学生的姓名和学号。

1
SELECT` `姓名,学号 ``FROM` `学生表 ``WHERE` `姓名 ``LIKE` `'_[小大]%'

例27.查询学生表中所有不姓“刘”的学生。

1
SELECT` `姓名 ``FROM` `学生 ``WHERE` `姓名 ``NOT` `LIKE` `'刘%’

例28.从学生表表中查询学号的最后一位不是2、3、5的学生信息。

1
SELECT` `* ``FROM` `学生表 ``WHERE` `学号 ``LIKE` `'%[^235]'

联表查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
--一般多表查询
select ?
from biao1,biao2
where biao1.key=biao2.key ---匹配不到的不会显示

--joinon查询
select ?
from biao1 left join biao2
on biao1.id=biao2.id;--左查询 --无论如何 左表会全部显示 及时查询不到

select ?
from biao1 right join biao2
on biao1.id=biao2.id;--右查询 --无论如何 右表会全部显示 及时查询不到

–自连接

  • 本表内存在key与另一个字段产生联系,可以将表看作两张表

分页和排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--排序
select ?
from
where
order by 字段 [asc/升序] [desc/降序]

--分页,缓解数据库压力
select ?
from
where
limit 起始位置[与数字类似从0开始] 显示多少个
limit 0 5
--第一页 0 5
--第二页 5 5
--第三页 10 5
--起始位置=(每页显示显示多少)*(n-1)

子查询

1
2
3
4
5
6
7
8
select ?
from
where name in (
select
from
where 1.id=2.id
)
--执行顺序由内到往外

分组

1
2
group by 字段 --分组
having --分组后过滤用having

Select小结

1
2
3
4
5
6
7
8
9
select [all/distinct]	--是否去重 默认为全部
字段? --表和字段都可以取别名
from table_name --表可以多写
[left|rigth join]+on] --左右查询--用on就不用where了
[where...]--where可以使具体的值 也可以是子查询
[group by]--分组
[having]--分组后的过滤与where相同
[order by...]--通过字段排序 升序(asc),降序(desc)
[limit offset pagesite] --分页

一般情况下,除非需要使用表中所有的字段数据,最好不要使用通配符‘’。使用通配符虽然可以节 省输入查询语句的时间,但是获取不需要的列数据通常会降低查询和所使用的应用程序的效率。通 配符的优势是,当不知道所需要的列的名称时,可以通过它获取它们。 在生产环境下,*不推荐你直接使用 SELECT * 进行查询。

MySQL函数

数学函数

1
2
3
4
5
abs()--绝对值
ceiling()--向上取整
floor()--向下取整
rand()--随机数
sign()--判断数的符号 -1 0 1

字符串函数

1
2
3
4
5
6
7
8
char_length()--字符串长度
concat("a","b")--拼接字符串
lower()--转小写
upper()--转大写
inster("helllo","l") 2--返回第一次出现字符串的索引
replace("abc","b","c")->acc--字符串替换
subster("abcd",1,3)->bcd--返回子串
reverse("")--字符串反转

时间和日期

1
2
3
4
5
6
7
current_date()--获取当前日期
now()---------------------
localtime()--本地时间
sysdate()--系统时间
user()--用户
version()--版本

聚合函数

count()–计数

1
2
3
4
5
6
7
8
9
count(name) from --count(字段) 会忽略null值
count(*) from --count(字段) 不会忽略null值
count(1) from --count(字段) 不会忽略null值

sum(字段)--求和
avg(字段)--平均
max(字段)--最大
min(字段)--最小

事务

原则

  • 原子性—–要么都成功 要么都失败
  • 一致性—–事务前后的数据保证一致
  • 隔离性—–针对多个用户同时操作,主要是排除其他事务对本次事务的影响
  • 持久性—–事务一旦提交则不可逆,被持久到数据库中

数据库的并发问题

  • 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:

    • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
    • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
    • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
  • 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。

  • 一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。

1
2
3
4
5
6
7
8
--模拟转账
set autocommit=0;--关闭自动提交
start transactin --开启一个事务
update `people` set money=money-500 where id=1 --A-500
update `people` set money=money+500 where id=2 --B+500
commit--提交事务 --一旦提交就持久化了
rollback--回滚
set autocommit=1--回复默认自动提交

索引

  • 主键索引
  • 唯一索引
  • 普通索引
  • 全文索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
--显示索引
show index from table;

--创建唯一索引
create unique index name on tablename (id)

--创建普通索引
create index name on tablename (id)

--添加主键索引
alter table tablename add primary key (id)

--删除索引
drop index index_name on table name

--删除主键索引
alter table table_name drop primary key

查询tips

  1. 使用SELECT +(所有列名) from 表名 然后结尾 ; 这样读取效率最高

    SELECT* from 表名 , 只适合自己看一眼,实际开发中效率低(因为需要将* 转化为每一个列名)

数据库规范

糟糕的数据库设计:

  • 数据冗余,浪费空间
  • 数据库插入和删除都会麻烦、异常【屏蔽使用物理外键】
  • 程序的性能差

良好的数据库设计:

  • 节省内存空间
  • 保证数据库的完整性
  • 方便我们开发系统

软件开发中,关于数据库的设计

  • 分析需求:分析业务和需要处理的数据库的需求
  • 概要设计:设计关系图E-R图

三大范式

第一范式(1NF)

  • 原子性:保证每一列不可再分

第二范式(2NF)

  • 前提:满足第一范式每张表只描述─件事情

第三范式(3NF)

  • 前提:满足第一范式和第二范式
  • 第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。

规范性和性能的问题

  • 关联查询的表不得超过三张表

  • 考虑商业化的需求和目标,(成本,用户体验! )数据库的性能更加重要

  • 在规范性能的问题的时候,需要适当的考虑一下规范性!

  • 故意给某些表增加一些冗余的字段。(从多表查询中变为单表查询)

  • 故意增加一些计算列(从大数据量降低为小数据量的查询:索引)

JDBC

1.JDBC概述

1.1数据的持久化

  • 持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现过程大多通过各种关系数据库来完成
  • 持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。

1.2 Java中的数据存储技术

  • 在Java中,数据库存取技术可分为如下几类:

    • JDBC直接访问数据库

    • JDO (Java Data Object )技术

    • 第三方O/R工具,如Hibernate, Mybatis 等

  • JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封装了JDBC。

1.3 JDBC介绍

  • JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用这些类库可以以一种标准的方法、方便地访问数据库资源。
  • JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
  • JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。
  • 如果没有JDBC,那么Java程序访问数据库时是这样的:


  • 有了JDBC,Java程序访问数据库时是这样的:


  • 总结如下:

1.4 JDBC体系结构

  • JDBC接口(API)包括两个层次:
    • 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。
    • 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。

JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。

不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。 ————面向接口编程

1.5 JDBC程序编写步骤

补充:ODBC(Open Database Connectivity,开放式数据库连接),是微软在Windows平台下推出的。使用者在程序中只需要调用ODBC API,由 ODBC 驱动程序将调用转换成为对特定的数据库的调用请求。

2.获取数据库连接

2.1 Driver接口实现类

2.1.1 Driver接口介绍
  • java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。

  • 在程序中不需要直接去访问实现了 Driver 接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现。

    • Oracle的驱动:oracle.jdbc.driver.OracleDriver
    • mySql的驱动: com.mysql.jdbc.Driver
2.1.2 加载与注册JDBC驱动
  • 加载驱动:加载 JDBC 驱动需调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名

    • Class.forName(“com.mysql.jdbc.Driver”);
  • 注册驱动:DriverManager 类是驱动程序管理器类,负责管理驱动程序

    • 使用DriverManager.registerDriver(com.mysql.jdbc.Driver)来注册驱动

    • 通常不用显式调用 DriverManager 类的 registerDriver() 方法来注册驱动程序类的实例,因为 Driver 接口的驱动程序类包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例。

2.2 URL

  • JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。
  • JDBC URL的标准由三部分组成,各部分间用冒号分隔。
    • jdbc:子协议:子名称
    • 协议:JDBC URL中的协议总是jdbc
    • 子协议:子协议用于标识一个数据库驱动程序
    • 子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名
  • 举例:

几种常用数据库的 JDBC URL

  • MySQL的连接URL编写方式:
1
2
3
4
- jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值
- jdbc:mysql://localhost:3306/atguigu
- jdbc:mysql://localhost:3306/atguigu?useUnicode=true&characterEncoding=utf8(如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集)
- jdbc:mysql://localhost:3306/atguigu?user=root&password=123456

2.3 用户名和密码

  • user,password可以用“属性名=属性值”方式告诉数据库
  • 可以调用 DriverManager 类的 getConnection() 方法建立到数据库的连接

2.4 数据库连接方式举例

2.4.1 连接方式一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void testConnection1() {
try {
//1.提供java.sql.Driver接口实现类的对象
Driver driver = null;
driver = new com.mysql.jdbc.Driver();

//2.提供url,指明具体操作的数据
String url = "jdbc:mysql://localhost:3306/test";

//3.提供Properties的对象,指明用户名和密码
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "abc123");

//4.调用driver的connect(),获取连接
Connection conn = driver.connect(url, info);
System.out.println(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}

说明:上述代码中显式出现了第三方数据库的API

2.4.2 连接方式二
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void testConnection2() {
try {
//1.实例化Driver
String className = "com.mysql.jdbc.Driver";
Class clazz = Class.forName(className);
Driver driver = (Driver) clazz.newInstance();

//2.提供url,指明具体操作的数据
String url = "jdbc:mysql://localhost:3306/test";

//3.提供Properties的对象,指明用户名和密码
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "abc123");

//4.调用driver的connect(),获取连接
Connection conn = driver.connect(url, info);
System.out.println(conn);

} catch (Exception e) {
e.printStackTrace();
}
}

说明:相较于方式一,这里使用反射实例化Driver,不在代码中体现第三方数据库的API。体现了面向接口编程思想。

2.4.3 连接方式三
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void testConnection3() {
try {
//1.数据库连接的4个基本要素:
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "abc123";
String driverName = "com.mysql.jdbc.Driver";

//2.实例化Driver
Class clazz = Class.forName(driverName);
Driver driver = (Driver) clazz.newInstance();
//3.注册驱动
DriverManager.registerDriver(driver);
//4.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
} catch (Exception e) {
e.printStackTrace();
}

}

说明:使用DriverManager实现数据库的连接。体会获取连接必要的4个基本要素。

2.4.4 连接方式四
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Test
public void testConnection4() {
try {
//1.数据库连接的4个基本要素:
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "abc123";
String driverName = "com.mysql.jdbc.Driver";

//2.加载驱动 (①实例化Driver ②注册驱动)
Class.forName(driverName);


//Driver driver = (Driver) clazz.newInstance();
//3.注册驱动
//DriverManager.registerDriver(driver);
/*
可以注释掉上述代码的原因,是因为在mysql的Driver类中声明有:
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}

*/


//3.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
} catch (Exception e) {
e.printStackTrace();
}

}

说明:不必显式的注册驱动了。因为在DriverManager的源码中已经存在静态代码块,实现了驱动的注册。

2.4.5 连接方式五(最终版)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void testConnection5() throws Exception {
//1.加载配置文件
InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);

//2.读取配置信息
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");

//3.加载驱动
Class.forName(driverClass);

//4.获取连接
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);

}

其中,配置文件声明在工程的src目录下:【jdbc.properties】

1
2
3
4
user=root
password=abc123
url=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.jdbc.Driver

说明:使用配置文件的方式保存配置信息,在代码中加载配置文件

使用配置文件的好处:

①实现了代码和数据的分离,如果需要修改配置信息,直接在配置文件中修改,不需要深入代码

②如果修改了配置信息,省去重新编译的过程。

3.使用PreparedStatement实现CRUD操作

3.1 操作和访问数据库

  • 数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。

  • 在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:

    • Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
    • PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
    • CallableStatement:用于执行 SQL 存储过程

3.2 使用Statement操作数据表的弊端

  • 通过调用 Connection 对象的 createStatement() 方法创建该对象。该对象用于执行静态的 SQL 语句,并且返回执行结果。

  • Statement 接口中定义了下列方法用于执行 SQL 语句:

    1
    2
    int excuteUpdate(String sql):执行更新操作INSERTUPDATEDELETE
    ResultSet executeQuery(String sql):执行查询操作SELECT
  • 但是使用Statement操作数据表存在弊端:

    • 问题一:存在拼串操作,繁琐
    • 问题二:存在SQL注入问题
  • SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password FROM user_table WHERE user=’a’ OR 1 = ‘ AND password = ‘ OR ‘1’ = ‘1’) ,从而利用系统的 SQL 引擎完成恶意行为的做法。

  • 对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了。

3.3 PreparedStatement的使用

3.3.1 PreparedStatement介绍
  • 可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取 PreparedStatement 对象

  • PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句

  • PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值

3.3.2 PreparedStatement vs Statement
  • 代码的可读性和可维护性。

  • PreparedStatement 能最大可能提高性能:

    • DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
    • 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
    • (语法检查,语义检查,翻译成二进制命令,缓存)
  • PreparedStatement 可以防止 SQL 注入

3.3.3 Java与SQL对应数据类型转换表
Java类型 SQL类型
boolean BIT
byte TINYINT
short SMALLINT
int INTEGER
long BIGINT
String CHAR,VARCHAR,LONGVARCHAR
byte array BINARY , VAR BINARY
java.sql.Date DATE
java.sql.Time TIME
java.sql.Timestamp TIMESTAMP
3.3.4 使用PreparedStatement实现增、删、改操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//通用的增、删、改操作(体现一:增、删、改 ; 体现二:针对于不同的表)
public void update(String sql,Object ... args){
Connection conn = null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
conn = JDBCUtils.getConnection();

//2.获取PreparedStatement的实例 (或:预编译sql语句)
ps = conn.prepareStatement(sql);
//3.填充占位符
for(int i = 0;i < args.length;i++){
ps.setObject(i + 1, args[i]);
}

//4.执行sql语句
ps.execute();
} catch (Exception e) {

e.printStackTrace();
}finally{
//5.关闭资源
JDBCUtils.closeResource(conn, ps);

}
}
3.3.5 使用PreparedStatement实现查询操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 通用的针对于不同表的查询:返回一个对象 (version 1.0)
public <T> T getInstance(Class<T> clazz, String sql, Object... args) {

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1.获取数据库连接
conn = JDBCUtils.getConnection();

// 2.预编译sql语句,得到PreparedStatement对象
ps = conn.prepareStatement(sql);

// 3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}

// 4.执行executeQuery(),得到结果集:ResultSet
rs = ps.executeQuery();

// 5.得到结果集的元数据:ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();

// 6.1通过ResultSetMetaData得到columnCount,columnLabel;通过ResultSet得到列值
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {// 遍历每一个列

// 获取列值
Object columnVal = rs.getObject(i + 1);
// 获取列的别名:列的别名,使用类的属性名充当
String columnLabel = rsmd.getColumnLabel(i + 1);
// 6.2使用反射,给对象的相应属性赋值
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnVal);

}

return t;

}
} catch (Exception e) {

e.printStackTrace();
} finally {
// 7.关闭资源
JDBCUtils.closeResource(conn, ps, rs);
}

return null;

}

说明:使用PreparedStatement实现的查询操作可以替换Statement实现的查询操作,解决Statement拼串和SQL注入问题。

3.4 ResultSet与ResultSetMetaData

3.4.1 ResultSet
  • 查询需要调用PreparedStatement 的 executeQuery() 方法,查询结果是一个ResultSet 对象

  • ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商提供实现

  • ResultSet 返回的实际上就是一张数据表。有一个指针指向数据表的第一条记录的前面。

  • ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象的 next() 方法移动到下一行。调用 next()方法检测下一行是否有效。若有效,该方法返回 true,且指针下移。相当于Iterator对象的 hasNext() 和 next() 方法的结合体。

  • 当指针指向一行时, 可以通过调用 getXxx(int index) 或 getXxx(int columnName) 获取每一列的值。

    • 例如: getInt(1), getString(“name”)
    • 注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始。
  • ResultSet 接口的常用方法:

    • boolean next()

    • getString()

3.4.2 ResultSetMetaData
  • 可用于获取关于 ResultSet 对象中列的类型和属性信息的对象

  • ResultSetMetaData meta = rs.getMetaData();

    • getColumnName(int column):获取指定列的名称

    • getColumnLabel(int column):获取指定列的别名

    • getColumnCount():返回当前 ResultSet 对象中的列数。

    • getColumnTypeName(int column):检索指定列的数据库特定的类型名称。

    • getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。

    • isNullable(int column):指示指定列中的值是否可以为 null。

    • isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。

问题1:得到结果集后, 如何知道该结果集中有哪些列 ? 列名是什么?

​ 需要使用一个描述 ResultSet 的对象, 即 ResultSetMetaData

问题2:关于ResultSetMetaData

  1. 如何获取 ResultSetMetaData: 调用 ResultSet 的 getMetaData() 方法即可
  2. 获取 ResultSet 中有多少列:调用 ResultSetMetaData 的 getColumnCount() 方法
  3. 获取 ResultSet 每一列的列的别名是什么:调用 ResultSetMetaData 的getColumnLabel() 方法

3.5 资源的释放

  • 释放ResultSet, Statement,Connection。
  • 数据库连接(Connection)是非常稀有的资源,用完后必须马上释放,如果Connection不能及时正确的关闭将导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
  • 可以在finally中关闭,保证及时其他代码出现异常,资源也一定能被关闭。

4.DAO及相关实现类

  • DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、Delete),而不包含任何业务相关的信息。有时也称作:BaseDAO
  • 作用:为了实现功能的模块化,更有利于代码的维护和升级。

层次结构:

5.数据库连接池技术

  • 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。

  • 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。

  • 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个

  • 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

  • 工作原理:

  • 数据库连接池技术的优点

    1. 资源重用

    由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。

    2. 更快的系统反应速度

    数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间

    3. 新的资源分配手段

    对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源

    4. 统一的连接管理,避免数据库连接泄漏

    在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露

5.1 多种开源的数据库连接池

  • JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
    • DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
    • C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。hibernate官方推荐使用
    • Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
    • BoneCP 是一个开源组织提供的数据库连接池,速度快
    • Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快
  • DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
  • DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
  • 特别注意:
    • 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
    • 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。

5.2 Druid(德鲁伊)数据库连接池

Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.atguigu.druid;

import java.sql.Connection;
import java.util.Properties;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSourceFactory;

public class TestDruid {
public static void main(String[] args) throws Exception {
Properties pro = new Properties(); pro.load(TestDruid.class.getClassLoader().getResourceAsStream("druid.properties"));
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
Connection conn = ds.getConnection();
System.out.println(conn);
}
}

其中,src下的配置文件为:【druid.properties】

1
2
3
4
5
6
7
8
9
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
username=root
password=123456
driverClassName=com.mysql.jdbc.Driver

initialSize=10
maxActive=20
maxWait=1000
filters=wall

6.Apache-DBUtils实现CRUD操作

6.1 Apache-DBUtils简介

  • commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。

  • API介绍:

    • 工具类:org.apache.commons.dbutils.DbUtils

    • org.apache.commons.dbutils.QueryRunner

    • org.apache.commons.dbutils.ResultSetHandler

6.2 DbUtils

  • DbUtils :提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法如下:
    • public static void close(…) throws java.sql.SQLException: DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet。
    • public static void closeQuietly(…): 这一类方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLEeception。
    • public static void commitAndClose(Connection conn)throws SQLException: 用来提交连接的事务,然后关闭连接
    • public static void commitAndCloseQuietly(Connection conn): 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。
    • public static void rollback(Connection conn)throws SQLException:允许conn为null,因为方法内部做了判断
    • public static void rollbackAndClose(Connection conn)throws SQLException
    • rollbackAndCloseQuietly(Connection)
    • public static boolean loadDriver(java.lang.String driverClassName):这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。

6.3 QueryRunner类

  • 该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。

  • QueryRunner类提供了两个构造器:

    • 默认的构造器
    • 需要一个 javax.sql.DataSource 来作参数的构造器
  • QueryRunner类的主要方法:

    • 更新
      • public int update(Connection conn, String sql, Object… params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。
    • 插入
      • public T insert(Connection conn,String sql,ResultSetHandler rsh, Object… params) throws SQLException:只支持INSERT语句,其中 rsh - The handler used to create the result object from the ResultSet of auto-generated keys. 返回值: An object generated by the handler.即自动生成的键值
    • 批处理
      • public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT, UPDATE, or DELETE语句
      • public T insertBatch(Connection conn,String sql,ResultSetHandler rsh,Object[][] params)throws SQLException:只支持INSERT语句
    • 查询
      • public Object query(Connection conn, String sql, ResultSetHandler rsh,Object… params) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。
  • 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 测试添加
@Test
public void testInsert() throws Exception {
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnection3();
String sql = "insert into customers(name,email,birth)values(?,?,?)";

int count = runner.update(conn, sql, "何成飞", "he@qq.com", "1992-09-08");

System.out.println("添加了" + count + "条记录");

JDBCUtils.closeResource(conn, null);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
// 测试删除
@Test
public void testDelete() throws Exception {
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnection3();
String sql = "delete from customers where id < ?";
int count = runner.update(conn, sql,3);

System.out.println("删除了" + count + "条记录");

JDBCUtils.closeResource(conn, null);

}

6.4 ResultSetHandler接口及实现类

  • 该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。

  • ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。

  • 接口的主要实现类:

    • ArrayHandler:把结果集中的第一行数据转成对象数组。
    • ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
    • BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
    • BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
    • ColumnListHandler:将结果集中某一列的数据存放到List中。
    • KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
    • MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
    • MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
    • ScalarHandler:查询单个值对象
  • 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* 测试查询:查询一条记录
*
* 使用ResultSetHandler的实现类:BeanHandler
*/
@Test
public void testQueryInstance() throws Exception{
QueryRunner runner = new QueryRunner();

Connection conn = JDBCUtils.getConnection3();

String sql = "select id,name,email,birth from customers where id = ?";

//
BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
Customer customer = runner.query(conn, sql, handler, 23);
System.out.println(customer);
JDBCUtils.closeResource(conn, null);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* 测试查询:查询多条记录构成的集合
*
* 使用ResultSetHandler的实现类:BeanListHandler
*/
@Test
public void testQueryList() throws Exception{
QueryRunner runner = new QueryRunner();

Connection conn = JDBCUtils.getConnection3();

String sql = "select id,name,email,birth from customers where id < ?";

//
BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
List<Customer> list = runner.query(conn, sql, handler, 23);
list.forEach(System.out::println);

JDBCUtils.closeResource(conn, null);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
* 如何查询类似于最大的,最小的,平均的,总和,个数相关的数据,
* 使用ScalarHandler
*
*/
@Test
public void testQueryValue() throws Exception{
QueryRunner runner = new QueryRunner();

Connection conn = JDBCUtils.getConnection3();

//测试一:
// String sql = "select count(*) from customers where id < ?";
// ScalarHandler handler = new ScalarHandler();
// long count = (long) runner.query(conn, sql, handler, 20);
// System.out.println(count);

//测试二:
String sql = "select max(birth) from customers";
ScalarHandler handler = new ScalarHandler();
Date birth = (Date) runner.query(conn, sql, handler);
System.out.println(birth);

JDBCUtils.closeResource(conn, null);
}

7.JDBC总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
总结
@Test
public void testUpdateWithTx() {

Connection conn = null;
try {
//1.获取连接的操作(
//① 手写的连接:JDBCUtils.getConnection();
//② 使用数据库连接池:C3P0;DBCP;Druid
//2.对数据表进行一系列CRUD操作
//① 使用PreparedStatement实现通用的增删改、查询操作(version 1.0 \ version 2.0)
//version2.0的增删改public void update(Connection conn,String sql,Object ... args){}
//version2.0的查询 public <T> T getInstance(Connection conn,Class<T> clazz,String sql,Object ... args){}
//② 使用dbutils提供的jar包中提供的QueryRunner类

//提交数据
conn.commit();


} catch (Exception e) {
e.printStackTrace();


try {
//回滚数据
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}

}finally{
//3.关闭连接等操作
//① JDBCUtils.closeResource();
//② 使用dbutils提供的jar包中提供的DbUtils类提供了关闭的相关操作

}
}