A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 李迪13524 初级黑马   /  2018-1-11 15:22  /  1214 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

# MySQL SQL语句


## 数据库概念

* 数据库:
        * 存储数据的仓库. 本质是一个文件系统, 数据按照指定的格式将数据存储起来, 用户可以对数据库中的数据进行增删改查
* DBMS:
        * Database Management System, 数据库管理系统. 是操作和管理数据库的大型软件, 用于建立, 使用和维护数据库, 对数据库进行统一管理和控制, 以保证数据库的安全性和完整性. 用户通过数据库管理系统访问数据库中表内的数据


## 常见的数据库

* 常见关系型数据库(应用程序)
        * MySQL: **常用**. Oracle开源免费小型数据库, 普通企业都在用
        * Oracle: **常用**, Oracle收费大型数据库, 一般大型国企, 银行, 政府部门用
        * DB2: IBM出品收费数据库. 一般大型国企, 银行, 政府部门用
        * SQLServer: 微软出品收费数据库. C#, .NET使用
        * SQLite: 嵌入式数据库, 用于移动设备, 机顶盒等, 安卓手机就是使用的SQLite数据库
        * SyBase: 没了
* 扩展: 目前的数据库分类:
        * 关系型数据库: 表示表与表之间的关系
                * MySQL, Oracle都是关系型数据库
        * 非关系型数据库: (NoSQL, Not Only SQL)
                * MongoDB
                * 用途: 文档信息的存储, 日志, 记录, 博客文章


## 数据库和管理系统

* 作用
        * 数据库: 存储**数据表**, 索引, 视图等
        * 数据表: 存储数据
                * 行: 数据
                * 列: 描述数据信息
        * 数据: 也叫**记录**, 也就是我们实际要存储的信息
        * 数据库管理系统: 操作数据库, 表, 记录
* 记住关系:
        * 数据库程序有数据库
        * 数据库中有表
        * 表中有记录
        * 数据库程序 > 库 > 表 > 记录


* 便于理解:

```
# 数据库和仓库的关系

* 京东(数据库程序MySQL)
        * 京东的工人(DBMS)                    --- mysqld.exe
        * 建立了多个仓库                       --- 数据库
        * 每个仓库中有多个货架                 --- 数据表
        * 每个货架上可以存放商品               --- 数据
        * 数据库包含数据表, 数据表包含数据
* 京东的工人(DBMS)
        * 工人建立了石家庄仓库, 北京仓库        --- 创建了2个数据库
        * 工人往石家庄仓库中放入了一个手机货架   --- 在名为"石家庄"的数据库中创建名为"手机"的数据表
        * 工人往手机货架上放入了500个大米手机    --- 在名为"石家庄"数据表中插入了500条记录



# 数据库和Excel表格的关系

* 电脑上有一个目录专门存入Excel文件
* 我们使用Excel编辑单元格, 就相当于DBMS在操作数据
```


## 数据表和Java中的类的对应关系

* 面向对象思想可以让我们把现实事物映射为Java的类, 那数据表能不能映射为Java的类呢?
* 数据表和Java类的关系
        * 表: Java类
        * 字段名: 类的成员属性
        * 记录: 类的对象
* 对应关系有什么用
        * 有了这种映射关系, 以后我们会直接通过特殊的方式将JavaBean的各个属性一一对应存入数据库
        * 我们在设计表时, 也会参考这种面向对象的方式, 设计表的字段(也就是事物的属性)

```
+---------------------+
|      Person表       |    表名   --- Java类名
+------+-----+--------+
| name | age | gender |    列名   --- 类中的属性
+------+-----+--------+            
| 张三  |  13 |   女   |    记录1   --- 类的对象1
| 李四  |  14 |   女   |    记录2   --- 类的对象2
| 王五  |  15 |   女   |   
| 赵六  |  16 |   女   |   
| 田七  |  17 |   女   |   
+------+-----+--------+


// 将表转换为Java类
public class Person {

        private String name;
    private int age;
        private String gender;

        public Person(String name, int age, String gender) {
                // this.xxx = xxx;
        }
}

// 创建对象
Person person1 = new Person("张三", 13, "女");
```


## 数据表和Java类对应关系举例: 用户表

(明白关系即可)






---







## MySQL数据库安装

* 对于MySQL程序的简单介绍
        * MySQL分为两个部分
                * 服务端: 主要是负责数据的存储
                        * mysqld.exe
                * 客户端: 连接服务端发出操作指令
                        * 命令行界面: 安装完自带的
                                * mysql.exe
                        * GUI界面: SQLyog, Navicat, Sequel pro
        * 我们安装的MySQL程序实际安装的是两个端(服务端, 客户端)
* 查看doc安装说明


## 查看MySQL运行在系统服务中

* `Win + R`, 输入`services.msc`, 回车, 打开服务管理器, 找到MySQL服务, 查看打开状态
* 进程管理程序查看`mysqld.exe`, (MySQL deamon)该程序就是MySQL的系统服务
* 服务程序的启动和停止(必须以管理员权限运行cmd):
        * 启动: `net start mysql`
        * 停止: `net stop mysql`


## MySQL登录和退出

* 打开命令行:

```
# 登录方式1: 先输入用户名再输入密码
mysql -u 用户名 -p
Enter password: 输入密码

# 登录方式2: 输入用户名后直接输入密码
mysql -u用户名 -p密码


# 退出mysql客户端
exit
```


## SQLyog图形界面客户端



## SQL语句介绍和分类

* 什么是SQL: Structured Query Language, 结构化查询语言.
        * 为什么叫`结构化查询`: 因为数据表是有结构的, 有行和列, 对行和列进行查询
* SQL语言作用: 和DBMS通信, 操作数据库, 表, 记录
* SQL语言按照作用可以划分为4类:
        * DDL: Data Defination Language, 数据定义语言, 用来定义数据库对象(库, 表等)
                * `CREATE`
                * `ALTER`
                * `DROP`
        * DML: Data Manipulation Language, 数据操作语言, 对表中的记录进行增, 删, 改
                * `INSERT`
                * `DELETE`
                * `UPDATE`
        * DQL: Data Query Language, 数据查询语言, 对表中的记录进行查
                * `SELECT`
                * `FROM`
                * `WHERE`
        * DCL: Data Control Language, 数据控制语言, 创建修改用户, 权限
* SQL语法特点
        * SQL语句不区分大小写. (默认情况数据也不区分大小写, 但可以设置)
                * 但按照惯例, SQL关键字用大写; 数据库名, 表名, 字段名等用小写, 这样便于阅读
        * 一条SQL语句可以单行或多行书写, 一条语句的结尾必须以`;`分号结尾
        * SQL语句的单词之间通过空格分隔, 参数之间使用`,`逗号分隔
        * 字符串和日期要使用`''`单引号或`""`双引号包裹, 推荐单引号
* SQL注释的2+1种
               
```sql
#单行注释
-- 单行注释, 减号后必须有一个空格

/*
多行注释
*/
```


## 数据表中的数据类型

* 常用数据类型:
        * `INT`: 整数
        * `DOUBLE`: 浮点数(小数)
        * `VARCHAR`: 存储空间是可变的, 可变长度字符
        * `CHAR`: 存储空间是固定的, 固定长度字符
        * `DATE`: 日期类型, `yyyy-MM-dd`格式的字符串
                * 扩展: 除此之外常用的日期时间类型还有如下:
                        * `TIME`: 时间类型, `HH:mm:ss`格式的字符串
                        * `DATETIME`: 日期时间类型, `yyyy-MM-dd HH:mm:ss`格式的字符串. 如果没有指定值则默认值是null
                        * `TIMESTAMP`: 时间戳类型, `yyyy-MM-dd HH:mm:ss`格式的字符串. 如果没有指定值则默认值是当前系统时间
* 扩展:
        * 数据类型的作用: 用于开辟内存空间
        * CHAR和VARCHAR的区别
                * VARCHAR(200), 可变长度字符, 存储占用的内存空间是可变的, 如果实际数据容量小于指定的字符长度大小, 那么就会按照实际的字符数来开辟内存大小
                        * 优点: 节省内存
                        * 缺点: 因为每次存入数据时都会判断数据的实际大小, 来动态修改内存空间, 所以需要占用资源, 效率低
                * CHAR(200), 固定长度字符, 存储占用的内存空间是不可变的. 无论实际存入的数据容量是多大, 都占用这么大的空间
                        * 优点: 插入数据时不需要额外操作, 效率高
                        * 缺点: 可能会浪费空间
               
               
## SQL语句创建数据库操作

* 创建数据库:
        * 直接创建数据库:
                * `CREATE DATABASE 库名;`
                        * 默认使用安装MySQL时设置的UTF-8编码
        * 创建数据库同时指定字符集:
                * `CREATE DATABASE 库名 CHARACTER SET '字符集';`
        * 创建数据库同时指定字符集和字符校对规则:
                * `CREATE DATABASE 库名 CHARACTER SET '字符集' COLLATE '校对规则';`
                        * 校对规则可以指定是否区分大小写字母, `SHOW COLLATION LIKE 'utf8%';`可以查看支持的校对规则, 其中`-ci`表示CASE INSENSITIVE, 即大小写不敏感
                        * 默认是`utf8_general_ci`, 若要大小写敏感可以修改为`utf8_bin`
* 删除指定数据库
        * 语句: `DROP DATABASE 库名;`
        * 删了能反悔吗? 不能
* 查看所有数据库
        * 语句: `SHOW DATABASES;`
        * MySQL自带的数据库不要擅自修改和删除
* 使用指定数据库(相当于进入数据库内, 然后就可以操作该数据库中的数据表)
        * 语句: `USE 库名;`


* 扩展
        * 查看当前正在使用的数据库
                * 语句: `SELECT DATABASE();`
        * 查看指定数据库的建库信息:
                * 语句: `SHOW CREATE DATABASE 库名;`
        * 修改数据库
                * 修改字符集: `ALTER DATABASE 库名 CHARACTER SET '新字符集';`
                * 修改校对规则: `ALTER DATABASE 库名 COLLATE '新校对规则';`


* 5分钟练习:
        * 使用CREATE DATABASE 库名;创建一个数据库db_drop
        * 使用SHOW DATABASES;查看是否创建成功
        * 使用DROP DATABASE 库名;删除自己创建的数据库
        * 使用SHOW DATABASES;再次查看是否已经删除

```sql
mysql> CREATE DATABASE db_drop;
Query OK, 1 row affected (0.01 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| db_drop            |
| mysql              |
| performance_schema |
| test               |
+--------------------+
5 rows in set (0.00 sec)

mysql> DROP DATABASE db_drop;
Query OK, 0 rows affected (0.02 sec)

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+
4 rows in set (0.00 sec)
```





---






## 在数据库中创建数据表

* 注意: 创建表的操作必须先要使用一个数据库: `USE 库名;`
* 建表语句格式:

```sql
CREATE TABLE 表名 (
  列名 数据类型(长度) 约束,
  列名2 数据类型(长度) 约束,
  ...
  列名n 数据类型(长度) 约束
);

// VARCHAR和CHAR一般需要手动指定长度, 日期数字等其他类型有默认长度一般不需要指定
```

* 建表语句的注意事项:
        * 语句可以写在一行内, 但为了阅读性, 一般分行写
        * 不同字段定义之间必须以`,`逗号分隔, 最后一个定义不要逗号




## 约束

* 约束:
        * constraint
        * 作用: 就是对列中记录的值进行限制, 如果插入的数据不符合约束规则, 则该操作会被立刻终止
* 扩展:
        * 约束的种类:
                * **主键约束**: `PRIMARY KEY`, 限制该列的记录值不能为空且唯一(不能重复), 相当于同时设定了`NOT NULL`和`UNIQUE`
                * **自增约束**: `AUTO_INCREMENT`, 设置该列的记录值可以自动增加
                        * 默认从几开始? 1
                        * 能用于什么类型? 只能用于数字类型(如INT, DOUBLE, 小数1.23自增后是2)
                * 外键约束: `FOREIGN KEY`, 主要是连接另一个表的主键
                * 非空约束: `NOT NULL`, 限制该列的记录值不能为空
                * 唯一约束: `UNIQUE`, 唯一, 限制该列的记录值不能重复
                * 检查约束: `CHECK (条件)`, 限制该列插入的记录值是否符合要求
                * 默认值约束: `DEFAULT 值`, 限制该列记录如果没有插入值, 则使用默认值




## SQLyog中SQL代码的保存

* 在SQLyog的Query选项卡中
        * `Ctrl + s`, 保存SQL语句到文件
        * `Ctrl + o`, 打开.sql文件, 导入SQL语句



## 创建用户表

* 代码演示

```sql
# 创建数据库mybase
CREATE DATABASE mybase;

# 使用库
USE mybase;

# 建表
CREATE TABLE users (
  uid INT,
  uname VARCHAR(20),
  uaddress VARCHAR(200)
);
```


## 主键约束

* 主键约束
        * 格式: `PRIMARY KEY`
        * 作用: 限制该列非空且唯一
* 自增约束
        * 格式: `AUTO_INCREMENT`
        * 作用: 使该列的值按照数字规律自动增长


* 5分钟练习:
        * 建库mybase
        * 使用该库
        * 创建用户表users
                * 字段
                        * 用户编号: uid INT PRIMARY KEY AUTO_INCREMENT
                        * 姓名: uname VARCHAR(20)
                        * 用户地址: uaddress VARCHAR(200)
  

```sql
# 建库
CREATE DATABASE mybase;

# 使用库
USE mybase;

# 创建用户表
CREATE TABLE users (
  uid INT PRIMARY KEY AUTO_INCREMENT,
  uname VARCHAR(20),
  uaddress VARCHAR(200)
);

# 执行语句: 选中SQL语句, 点击左上角的一个三角的蓝色圆形按钮, 或按快捷键F9
# 多条SQL语句可以全部选中批量执行, 但注意: 每条语句必须有;分号结尾, 否则无法识别出是几条语句
# 执行完语句后, 在左侧空白处右键, 选择刷新对象浏览器, 即可看到变更
```






---=







## 常见表操作

* 列出当前数据库的所有表
        * `SHOW TABLES;`
* 查看表的结构
        * `DESC 表名;`: desc=description描述
* 删除表
        * `DROP TABLE 表名;`



## 修改表的结构

* 修改表的结构
        * 格式: `ALTER TABLE 表名 操作关键字 列名 数据类型 约束;`
                * 操作关键字
                        * `ADD`: 增加列.
                                * `ALTER TABLE 表名 ADD 新列名 数据类型 约束;`
                        * `MODIFY`: 修改指定列的数据类型和约束(不能修改列名).
                                * `ALTER TABLE 表名 MODIFY 要修改的列名 新数据类型 新约束;`
                        * `CHANGE`: 修改指定列的列名+数据类型+约束.
                                * `ALTER TABLE 表名 CHANGE 旧列名 新列名 新数据类型 新约束;`
                        * `DROP`: 删除列.
                                * `ALTER TABLE 表名 DROP 列名;`
* 修改表名
        * 格式: `RENAME TABLE 旧表名 TO 新表名;`



## 数据表添加记录: 格式1

* 插入记录的格式1: 手动指定主键列的值
        * 注意: 字段数量必须和值数量一一对应
        * 该方法适用于给指定列插入值

```sql
# 格式1

INSERT INTO 表名
(列名1, 列名2,..., 列名n)
VALUES
(值1, 值2, ..., 值n);
```


* 5分钟练习:
        * 使用库mybase
        * 创建产品表product并插入数据
        * 字段
                * 序号: pid INT PRIMARY KEY AUTO_INCREMENT
                * 产品名称: pname VARCHAR(100) NOT NULL
                * 产品价格: price DOUBLE
        * 插入数据
                * 1, '笔记本', 5555.99
                * 2, '智能手机', 9999
        * 2个数据指定相同的pid有什么错误?
                * Duplicate entry '1' for key 'PRIMARY': 该列是主键(不能重复且非空)


```sql
# 使用库mybase
USE mybase;

# 建表
CREATE TABLE product (
  pid INT PRIMARY KEY AUTO_INCREMENT,
  pname VARCHAR(100) NOT NULL,
  price DOUBLE
);

# 插入记录
INSERT INTO product
(pid, pname, price)
VALUES
(1, '笔记本', 5555.99);

INSERT INTO product
(pid, pname, price)
VALUES
(2, '智能手机', 9999);
```




## 数据表添加记录: 其他格式

* 插入记录格式2: 不指定主键, 让其自增
        * 当设置了主键自增后, 插入记录时可以不考虑主键字段的值. 即字段名和值都没有主键这一列. 同时可以避免产生主键重复的错误
        * 该方式适用于设置了主键自增(AUTO_INCREMENT)的情况

```sql
INSERT INTO 表名
(非自增列名1, 非自增列名2, ..., 非自增列名n)
VALUES
(值1, 值2, ..., 值n);
```


* 插入记录格式3: 不指定列名, 给所有列添加值
        * 不指定列名, 为所有列添加值
        * 该方式适用于为所有列添加值, 可以省去写列名. 但**不能省略主键**

```sql
INSERT INTO 表名
VALUES
(值1, 值2,...,值n);
```

* 批量插入记录:
        * 适用于批量插入记录
       
```sql
INSERT INTO 表名
(列1, 列2)
VALUES
(记录1值1, 记录1值2),
(记录2值1, 记录2值2);

INSERT INTO 表名
VALUES
(记录1值1, 记录1值2),
(记录2值1, 记录2值2);
```

* 注意:
        * 插入记录能从中间插入吗? 都是从最后插入的, 只是排序不同
        * 如果删除一条记录, 导致自增的id中间断号, 有问题吗? 没问题, 如果想补可以补上









---









## 更新记录(修改记录)

* 更新记录语句(修改记录中的值)

```sql
# 语法
UPDATE 表名 SET
列名1 = 新值,
列名2 = 新值;

# 修改指定条件的记录
UPDATE 表名 SET
列名1 = 新值, 列名2 = 新值
WHERE 条件;
```

* 更新(修改)指定的记录
        * 如果不加`WHERE`条件, 则所有数据都会被修改
* 扩展:
        * WHERE: 条件子句
                * 作用: 筛选符合条件的结果, 然后对这些结果进行操作

                       

## 删除记录

* 删除指定的记录

```sql
# 删除指定条件的记录
DELETE FROM 表名 WHERE 条件;

# 删除所有记录
DELETE FROM 表名;
```

* 清空表

```sql
# 语句
TRUNCATE TABLE 表名;
```

* 注意: DELETE和TRUNCATE清空表有什么区别?
        * `DELETE FROM 表名`: 逐条删除记录, 如果有自增, 自增计数器不会重置. 如果继续添加记录, 则自增的序号会继续自增
        * `TRUNCATE TABLE 表名;`: 删除表然后重新建表(DROP TABLE/CREATE TABLE), 自增计数器也被删除了, 而创建后会是新的计数器, 所以相当于计数器重置(1)


## 命令行乱码问题

* 乱码原因: 存入记录的字符编码和数据库编码不一致, windows的cmd是GBK编码, 而数据库是utf-8编码
* 临时方法:

```sql
# 将数据库数据修改为Windows的GBK
SET NAMES 'GBK';  
```

* 扩展:
        * `SET NAMES`实际上对MySQL做了以下3个配置
        * [查看原理](http://mysql.taobao.org/monthly/2015/05/07/)

```
SET character_set_client = charset_name;
SET character_set_results = charset_name;
SET character_set_connection = charset_name;
```


## 数据表和测试数据准备

* 8分钟练习:
        * 使用库mybase
        * 创建新表zhangwu
                * 账务id: id 整数 主键 自增
                * 账务名称: zname 字符(200)
                * 金额: zmoney 浮点数
        * 添加测试数据
                * 1, 吃饭支出, 247
                * 2, 工资收入, 12345
                * 3, 服装支出, 1000
                * 4, 吃饭支出, 325
                * 5, 股票收入, 8000
                * 6, 打麻将支出, 8000
                * 7, NULL, 5000

```sql
# 使用库
USE mybase;

# 建表
CREATE TABLE zhangwu (
  id INT PRIMARY KEY AUTO_INCREMENT,
  zname VARCHAR(200),
  zmoney DOUBLE
);

# 批量插入数据
INSERT INTO zhangwu
VALUES
(1, '吃饭支出', 247),
(2, '工资收入', 12345),
(3, '服装支出', 1000),
(4, '吃饭支出', 325),
(5, '股票收入', 8000),
(6, '打麻将支出', 8000),
(7, NULL, 5000);
```






---






## 数据的基本查询

* 语法

```sql
# 查询指定字段
SELECT 列名1, 列名2 FROM 表名;

# 查询所有字段
SELECT * FROM 表名;

# DISTINCT去重
SELECT DISTINCT 列名1, 列名2 FROM 表名;

# AS给字段取别名
SELECT 列名 AS 别名 FROM 表名;

# 对查询结果进行数学计算
SELECT 列名+值 FROM 表名;
```

* `DISTINCT`关键字的作用:
        * 按照指定列的值将查询出的记录去重
                * 如果`DISTINCT`后面有多个列, 怎么去重?
                        * 按照多个列的记录值的组合来判断是否重复, 如以下2条记录
                                * 张三, 16
                                * 张三, 18
                        * 如果按姓名列去重, 则只会留下一个张三; 如果按姓名和年龄2列去重, 则2个都会留下
* `AS`关键字的作用:
        * 给列取别名: `列名 AS '别名'`
        * 给表取别名: `表名 AS '别名'`
        * 注意: 别名会修改真正的名称吗? 不会, 在当前执行的SQL语句中有效
* 在查询中对记录的查询结果进行数学计算
        * 格式: 在字段名上直接使用数学运算符
        * 注意: 数学计算结果仅对查询出的结果生效, 不会影响表内的实际数据



* 5分钟练习:
        * 继续对zhangwu表进行以下操作:
                * 查询zname和zmoney两个列的记录
                * 去重查询zname列的记录
                * 查询zname字段并取别名为name(观察查询结果的字段名是否变化)
                * 查询zname, zmoney字段, 同时将zmoney字段增加1000(观察zmoney记录的值是否改变, 是否会影响真实的表中数据)

```sql
# 查询zname和zmoney两个列的记录
SELECT zname, zmoney FROM zhangwu;

# 去重查询zname列的记录
SELECT DISTINCT zname FROM zhangwu;

# 查询zname字段并取别名为name(观察查询结果的字段名是否变化)
SELECT zname AS 'name' FROM zhangwu;

# 查询zname, zmoney字段, 同时将zmoney字段增加1000(观察zmoney记录的值是否改变, 是否会影响真实的表中数据)
SELECT zname, zmoney+1000 FROM zhangwu;
```



## 数据的条件查询1:  比较运算符和逻辑运算符

* 条件查询:
        * 即按指定条件查询记录返回符合条件的结果集, 使用`SELECT`和`WHERE`组合
* `WHERE`:
        * 作用: 条件子句, 过滤符合条件的结果
        * 运算符
                * 比较运算符
                        * `=`: 相等
                        * `<>`或`!=`: 不等
                        * `>`: 大于
                        * `<`: 小于
                        * `>=`: 大于等于
                        * `<=`: 小于等于
                        * `BETWEEN...AND...`: 在一个范围内(包含头和尾), `BETWEEN 0 AND 10;` 0~10
                                * 如:
                        * `IN ()`: 在列表中, 满足列表中一个即可
                                * 如: `IN (1, 3, 5)`
                        * `IS NULL`: 是空
                        * `LIKE 通配符`: 模糊查询
                                * 通配符种类
                                        * `%`: 一个百分号可以表示任意个字符. 比如`王%`, `王大锤`, `王五`
                                        * `_`: 一个下划线可以表示一个字符. 比如`王_`, 只能匹配`王五`
                * 逻辑运算符
                        * `AND`: 与. 两边条件同时成立才成立
                        * `OR`: 或. 两边条件只要有一个成立就成立
                        * `NOT`: 非, 取相反结果
                                * `NOT BETWEEN ... AND ...`: 不在范围内
                                * `NOT IN`: 不在列表中
                                * `NOT LIKE`: 不匹配
                                * `IS NOT NULL`: 非空
                * 日期也是可以比较的


* 10分钟练习:
        * 继续对zhangwu表进行以下操作:
                * 查询所有的吃饭支出
                * 查询金额大于1000的记录
                * 查询金额在2000到5000之间的记录, 使用>=, <=运算符和AND
                * 查询金额在2000到5000之间的记录, 使用BETWEEN ... AND ...
                * 查询金额符合指定列表(1000, 2000, 5000)的数据记录, 使用=运算符和OR
                * 查询金额符合指定列表(1000, 2000, 5000)的数据记录, 使用IN()

```sql
# 查询所有的吃饭支出
SELECT * FROM zhangwu WHERE zname = '吃饭支出';

# 查询金额大于1000的记录
SELECT * FROM zhangwu WHERE zmoney > 1000;

# 查询金额在2000到5000之间的记录, 使用>=, <=运算符和AND
SELECT * FROM zhangwu WHERE zmoney >= 2000 AND zmoney <= 5000;

# 查询金额在2000到5000之间的记录, 使用BETWEEN ... AND ...
SELECT * FROM zhangwu WHERE zmoney BETWEEN 2000 AND 5000;

# 查询金额符合指定列表(1000, 2000, 5000)的数据记录, 使用=运算符和OR
SELECT * FROM zhangwu WHERE zmoney = 1000 OR zmoney = 2000 OR zmoney = 5000;

# 查询金额符合指定列表(1000, 2000, 5000)的数据记录, 使用IN()
SELECT * FROM zhangwu WHERE zmoney IN (1000, 2000, 5000);
```


## 数据的条件查询2: 通配符

* `... WHERE zname IS NOT NULL;`
        * name不是null
* `... WHERE NOT (zname IS NULL);`
        * 将name是null的结果取反


* 5分钟练习:
        * 继续对zhangwu表进行以下操作:
                * 查询所有支出: zname含有"支出", 使用LIKE
                * 查询5个字符的zname, 通配符用5个下划线
                * 查询zname不为空的记录

```sql
# 查询所有支出: zname含有"支出", 使用LIKE
SELECT * FROM zhangwu WHERE zname LIKE '%支出%';

# 查询5个字符的zname, 通配符用5个下划线
SELECT * FROM zhangwu WHERE zname LIKE '_____';

# 查询zname不为空的记录
SELECT * FROM zhangwu WHERE zname IS NOT NULL;
SELECT * FROM zhangwu WHERE NOT (zname IS NULL);
```





----------------------

## 今日扩展


### CRUD

* CRUD是`Create`, `Retrieve`, `Update`, `Delete`的首字母组合, 表示增加, 查询, 修改, 删除4种操作, 用中文一般叫做`增删改查`


### 数据类型

* 大致可分为三类:
        * 数值
        * 日期/时间
        * 字符串


#### 数值类型

|类型         |大小 |范围(有符号)                                                                    |范围(无符号)                             |用途        |
|------------|----|--------------------------------------------------------------------------------|---------------------------------------|------------|
|TINYINT     |1字节|`[-128, 127]`                                                                   |`[0,255]`                                |小整数值     |
|SMALLINT    |2字节|`[-32768, 32767]`                                                               |`[0, 65535]`                             |大整数值     |
|MEDIUMINT   |3字符|`[-8388608, 8388607]`                                                           |`[0, 16777215]`                          |大整数值     |
|INT或INTEGER|4字节|`[-2147483648, 2147483647]`                                                     |`[0, 4294967295]`                        |大整数值     |
|BIGINT      |8字节|`[-9233372036854775808, 9223372036854775807]`                                   |`[0, 18446744073709551615]`              |极大整数值   |
|FLOAT       |4字节|`[-3.402823466E+38, -1.175494351E-38]`, `0`, `[1.175494351E-38, 3.402823466351E+38]`|`[0, [1.175494351E-38, 3.402823466E+38]`|单精度浮点数值|
|DOUBLE      |8字节|`[-1.7976931348623157E+308, -2.2250738585072014E-308]`, `0`, `[2.2250738585072014E-308, 1.7976931348623157E+308]`|`0`, `[2.2250738585072014E-308, 1.7976931348623157E+308]`|双精度浮点数值|
|DECIMAL     |对于DECIMAL(M, D), 如果M>D, 是M+2; 否则是D+2|依赖于M和D的值|依赖于M和D的值|小数值|


#### 日期和时间类型

当时间不合法时, 值为零值

|类型      |大小(字节)|范围                                    |格式               |用途                 |
|---------|---------|-----------------------------------------|-------------------|--------------------|
|DATE     |3        |`1000-01-01/9999-12-31`                  |`YYYY-MM-DD`         |日期值               |
|TIME     |3        |`-838:59:59/838:59:59`                   |`HH:MM:SS`           |时间值或持续时间       |
|YEAR     |1        |`1901/2155`                              |`YYYY`               |年份值               |
|DATETIME |8        |`1000-01-01 00:00:00/9999-12-31 23:59:59`|`YYYY-MM-DD HH:MM:SS`|混合日期和时间值       |
|TIMESTAMP|4        |`1970-01-01 00:00:00/2037 年某时`         |`YYYYMMDD HHMMSS`    |混合日期和时间值, 时间戳|


#### 字符串类型

|类型       |大小(字节)   |用途                     |
|----------|------------|------------------------|
|CHAR      |0-255       |定长字符串                |
|VARCHAR   |0-65535     |变长字符串                |
|TINYBLOB  |0-255       |不超过255个字符的二进制字符串|
|TINYTEXT  |0-255       |短文本字符串               |
|BLOB      |0-65535     |二进制形式的长文本数据      |
|TEXT      |0-65535     |长文本数据                |
|MEDIUMBLOG|0-16777215  |二进制形式的中等长度文本数据 |
|MEDIUMTEXT|0-16777215  |中等长度文本数据           |
|LONGBLOB  |0-4294967295|二进制形式的极大文本数据    |
|LONGTEXT  |0-4294967295|极大文本数据              |



--------------------

# 今日总结

* 数据库
        * 概念: 存储数据的仓库. 本质是一个文件系统, 数据按照指定的格式将数据存储起来, 用户可以对数据库中的数据进行增删改查
        * DBMS: Database Management System, 数据库管理系统. 是操作和管理数据库的大型软件, 用于建立, 使用和维护数据库, 对数据库进行统一管理和控制, 以保证数据库的安全性和完整性. 用户通过数据库管理系统访问数据库中表内的数据
        * 常见数据库
                * `MySQL`
                * `Oracle`
        * 数据库: 存储数据表
        * 数据表: 存储记录
                * 由行和列组成的
        * DBMS: 操作数据库
        * 表和Java类的对应关系
                * 表名: Java类名
                * 列名: 成员属性名
                * 记录: 类对象
* MySQL
        * 安装
        * 配置和遇到的问题
        * 命令行的登录和退出
                * 登录: `mysql -u 用户名 -p`
                * 退出: `exit`
* SQL基本语言
        * SQL的概念: Structured Query Language, 结构化查询语言
                * 结构: 数据表是由行和列组成的, 是有结构的, 从这种结构中查询
        * SQL的4中分类
                * DDL: Data Defination Language, 数据定义语言, 用来定义数据库对象(库, 表等)
                        * CREATE
                        * ALTER
                        * DROP
                * DML: Data Manipulation Language, 数据操作语言, 对表中的记录进行增, 删, 改
                        * INSERT
                        * UPDATE
                        * DELETE
                * DQL: Data Query Language, 数据查询语言, 对表中的记录进行查
                        * SELECT
                        * FROM
                        * WHERE
                * DCL: Data Control Language, 数据控制语言, 创建修改用户, 权限
        * SQL特点
                * SQL不区分大小写
                * 一条SQL语句以`;`分号结尾, 一条语句可以多行书写
                * 单词之间通过空格分隔, 参数之间通过`,`逗号分隔
                * 字符串和日期要用`''`或`""`引起来, 建议单引号
        * SQL注释(2+1种)
                * 2种单行注释
                        * `#单行注释`
                        * `-- 单行注释`: 减号后必须有一个空格
                * 1种多行注释: `/* 注释内容 */`
        * SQL的数据类型
                * `INT`: 整数
                * `DOUBLE`: 小数
                * `VARCHAR`: 可变长度字符
                * `CHAR`: 固定长度字符
                * 其他看文档
                * VARCHAR和CHAR的区别:
                        * VARCHAR(200), 可变长度字符, 存储占用的内存空间是可变的, 如果你的数据容量小于我们指定的空间大小, 那么就会按照实际的数据空间来开辟内存大小
                                * 优点: 节省内存
                                * 缺点: 因为每次存入数据时都会判断数据的实际大小, 来动态修改内存空间, 所以需要占用资源, 效率低
                        * CHAR(200), 固定长度字符, 存储占用的内存空间是不可变的. 无论实际存入的数据容量是多大, 都占用这么大的空间
                                * 优点: 插入数据时不需要额外操作, 效率高
                                * 缺点: 可能会浪费空间
        * SQL对库的操作
                * 创建库
                        * 使用默认字符集创建库: `CREATE DATABASE 库名;`
                                * 默认是UTF-8编码
                        * 指定字符集: `CREATE DATABASE 库名 CHARACTER SET '字符集';`
                * 删除库
                        * `DROP DATABASE 库名;`
                * 查看所有库
                        * `SHOW DATABASES;`
                * 使用库
                        * `USE 库名;`
                * 查看当前使用的库
                        * `SELECT DATABASE();`
        * SQL对表的操作
                * 创建表: `CREATE TABLE 表名 (列名1 数据类型 约束, 列名2 数据类型 约束);`
                * 删除表: `DROP TABLE 表名;`
                * 查看当前库中的所有表: `SHOW TABLES;`
                * 修改表的结构: `ALTER TABLE 表名 操作关键字 列名 数据类型 约束;`
                        * 增加列: `ALTER TABLE 表名 ADD 新列名 新数据类型 新约束;`
                        * 删除列: `ALTER TABLE 表名 DROP 列名;`
                        * 修改列的数据类型和约束(不能修改列名): `ALTER TABLE 表名 MODIFY 列名 新数据类型 新约束;`
                        * 修改列的列名, 数据类型, 约束: `ALTER TABLE 表名 CHANGE 旧列名 新列名 新数据类型 新约束;`
                * 修改表名: `RENAME TABLE 旧表名 TO 新表名;`
        * SQL对记录的操作
                * 增
                        * 一次插入一条记录: `INSERT INTO 表名 (列1, 列2) VALUES (值1, 值2);`
                        * 一次性插入一条记录(省略自增主键): `INSERT INTO 表名 (非主键自增列1, 非主键自增列2) VALUES (值1, 值2);`
                        * 一次插入所有列的记录(省略列名): `INSERT INTO 表名 VALUES (值1, 值2);`
                                * 值的数量和顺序必须和表的列一致
                        * 一次插入多个记录(指定列): `INSERT INTO 表名 (列1, 列2) VALUES (记录1值1, 记录1值2), (记录2值1, 记录2值2);`
                        * 一次插入多个记录(省略列): `INSERT INTO 表名 VALUES (记录1值1, 记录1值2), (记录2值1, 记录2值2);`
                * 删
                        * 删除符合条件的记录: `DELETE FROM 表名 WHERE 条件;`
                        * 删除所有记录: `DELETE FROM 表名;`
                        * 清空表: `TRUNCATE TABLE 表名;`
                        * `DELETE FROM 表名;`和`TRUNCATE TABLE 表名;`的区别
                                * DELETE, 逐条删除记录, 不会重置自增计数器
                                * TRUNCATE, 删除表再重建, 会重置自增计数器
                * 改
                        * 修改符合条件的记录: `UPDATE 表名 SET 列名1=新值, 列名2=新值 WHERE 条件;`
                        * 修改所有记录: `UPDATE 表名 SET 列名1=新值, 列名2=新值;`
                * 查
                        * 查询指定列: `SELECT 列名1, 列名2 FROM 表名;`
                        * 查询所有列: `SELECT * FROM 表名;`
                        * 条件查询: `SELECT 列名1, 列名2 FROM 表名 WHERE 条件;`
                                * 运算符
                                        * 比较运算符
                                                * `=`: 相等
                                                * `<>`或`!=`: 不等
                                                * `>`: 大于
                                                * `<`: 小于
                                                * `>=`: 大于等于
                                                * `<=`: 小于等于
                                                * `BETWEEN...AND...`: 在一个范围内(包含头和尾)
                                                        * 如: BETWEEN 0 AND 10
                                                * `IN ()`: 在列表中, 满足列表中一个即可
                                                        * 如: IN (1, 3, 5)
                                                * `IS NULL`: 是空
                                                * `LIKE '通配符'`: 模糊查询
                                                        * `%`: 一个百分号可以表示任意个字符. 比如'王%', 王大锤, 王五
                                                        * `_`: 一个下划线可以表示一个字符. 比如'王_', 只能匹配王五
                                        * 逻辑运算符
                                                * `AND`: 与. 两边条件同时成立才成立
                                                * `OR`: 或. 两边条件只要有一个成立就成立
                                                * `NOT`: 非, 取相反结果
                                                        * `NOT BETWEEN ... AND ...`: 不在范围内
                                                        * `NOT IN`: 不在列表中
                                                        * `NOT LIKE`: 不匹配
                                                        * `IS NOT NULL`: 非空
                        * 去重:         `SELECT DICTINCT 列名1, 列名2 ... FROM 表名 WHERE 条件;`
                        * 别名:
                                * `字段名 AS 别名`
                                * `表名 AS 别名`




# day02 高级SQL, JDBC, SQL注入


## 排序查询

* 排序查询
        * 是`SELECT`的子句, 将查询后的结果集进行排序
        * 格式:
                * `ORDER BY 列名 排序选项`
                * `SELECT ... FROM ... WHERE ... ORDER BY 列名 排序选项`
                * 排序选项: 适用于数字, 英文字母, 日期
                        * `ASC`: 默认, 升序
                        * `DESC`: 降序
        * 注意: `WHERE`和`ORDER BY`的顺序
                * `WHERE`在前, `ORDER BY`在后, 否则报错
                * 因为是排序是对SELECT查询出的结果集再次进行排序, 而不是对表中的数据进行排序. 所以要先把SELECT WHERE的查询结果拿到, 才能使用ORDER BY排序
        * 扩展:
                * 排序可以按照多列, 如先按a列降序, 如果a列中有相同的值, 则按b列降序
                        * `SELECT * FROM users ORDER BY age DESC, salary DESC;`
* `ORDER BY`是SQL语句的末尾吗?
        * 不一定是SQL语句的末尾, 后面还可以写LIMIT子句



```sql
/* 排序示例 */

# 查询用户表所有列, 按照年龄升序排序
SELECT * FROM users ORDER BY age;
SELECT * FROM users ORDER BY age ASC;

# 查询用户表所有列, 按照年龄降序排序
SELECT * FROM users ORDER BY age DESC;

# 查询用户表中年龄在20~30岁的用户, 并按年龄降序排序
SELECT * FROM users WHERE age BETWEEN 20 AND 30 ORDER BY age DESC;

# 查询用户表中年龄在20~30岁的用户, 先按年龄降序排序, 年龄相同的按工资降序排序
SELECT * FROM users WHERE age BETWEEN 20 AND 30 ORDER BY age DESC, salary DESC;
```



* 5分钟练习
        * 继续使用mybase库, 对zhangwu表做以下操作:
                * 查询所有列的记录, 按金额排序, 使用默认排序
                * 查询所有列的记录, 按金额排序, 使用升序关键字ASC
                * 查询所有列的记录, 按金额排序, 使用降序关键字DESC
                * 查询所有支出, 按金额降序

```sql
# 查询所有列的记录, 按金额排序, 使用默认排序
SELECT * FROM zhangwu ORDER BY zmoney;

# 查询所有列的记录, 按金额排序, 使用升序关键字ASC
SELECT * FROM zhangwu ORDER BY zmoney ASC;

# 查询所有列的记录, 按金额排序, 使用降序关键字DESC
SELECT * FROM zhangwu ORDER BY zmoney DESC;

# 查询所有支出, 按金额降序
SELECT * FROM zhangwu WHERE zname LIKE '%支出%' ORDER BY zmoney DESC;
```


## 聚合函数

* 聚合函数
        * SQL内置了一些函数(类似于API方法), 可以对查询的结果进行相关计算
        * 作用:
                * 竖向对某一列的值进行计算, 然后返回一个计算结果.
        * 格式:
                * `SELECT 函数名(列名) FROM 表名 ...;`
* 常用的5个聚合函数
        * `COUNT(列名)`: 计算指定列的记录行数.
                * 注意: 值为NULL的记录不会被统计
        * `SUM(列名)`: 计算指定列的数值总和
                * 如果数据类型不是数值, 则结果为: 0
        * `MAX(列名)`: 获取指定列的数值中的最大值
        * `MIN(列名)`: 获取指定列的数值中的最小值
        * `AVG(列名)`: 计算指定列的数值中的平均值
                * 如果数据类型不是数值, 则结果为: 0
* 注意:
        * 聚合函数的函数名和小括号之间要连着写, 不要有空格:
                * 正确: `SUM(zname)`
                * 错误: `SUM (zname)`
        * NULL与任何数的运算结果都是NULL: `SELECT (NULL + 10)`结果为NULL


* 5分钟练习
        * 继续使用mybase库, 对zhangwu表做以下操作:
                * 查询表中一共有多少条记录: COUNT
                * 计算所有金额的总和: SUM
                * 计算总支出: SUM
                * 计算总收入: SUM
                * 获取最高金额: MAX
                * 获取最低金额: MIN
                * 获取最高收入: MAX
                * 获取最低收入: MIN
                * 计算金额平均值: AVG
       

```sql
# 查询表中一共有多少条记录: COUNT  7条
SELECT COUNT(*) FROM zhangwu;

# 计算所有金额的总和: SUM  34917
SELECT SUM(zmoney) AS '金额总和' FROM zhangwu;

# 计算总支出: SUM   9572
SELECT SUM(zmoney) AS '总支出' FROM zhangwu WHERE zname LIKE '%支出%';

# 计算总收入: SUM  20345
SELECT SUM(zmoney) AS '总收入' FROM zhangwu WHERE zname LIKE '%收入%';

# 获取最高金额: MAX  12345
SELECT MAX(zmoney) AS '最高金额' FROM zhangwu;

# 获取最低金额: MIN  247
SELECT MIN(zmoney) AS '最低金额' FROM zhangwu;

# 获取最高收入: MAX  12345
SELECT MAX(zmoney) AS '最高收入' FROM zhangwu WHERE zname LIKE '%收入%';

# 获取最低收入: MIN  8000
SELECT MIN(zmoney) AS '最低收入' FROM zhangwu WHERE zname LIKE '%收入%';

# 计算金额平均值: AVG  4988.1428...
SELECT AVG(zmoney) AS '平均金额' FROM zhangwu;
```




---





## 分组查询

* 分组查询
        * 是`SELECT`的子句, 用于将查询出的结果集再次按照某列分组合并显示, 记录值相同的归为一组. 同时还可以对分组后的结果再次使用条件进行过滤
        * 格式:
                * `GROUP BY 列名 HAVING 条件`,
                * `SELECT ... FROM ... WHERE ... GROUP BY 列名 HAVING 条件;`
                * `HAVING`的作用: 可选, 对查询结果进行分组显示后, 再次按条件过滤
* 注意:
        * 聚合函数与分组的使用效果
                * 使用聚合函数后再进行分组, 才相当于汇总统计
                        * `SELECT zname, SUM(zmoney) FROM zhangwu GROUP BY zname;`
                * 不使用聚合函数的分组, 只有第一条记录的值
                        * `SELECT zname, zmoney FROM zhangwu GROUP BY zname;`
        * `HAVING`和`WHERE`的区别
                * 过滤时机不同
                        * WHERE是在分组前先对查询结果进行过滤
                        * HAVING是在查询结果分组后再次过滤
                * 是否可跟随聚合函数不同
                        * WHERE条件中不可使用聚合函数
                        * HAVING条件中可以使用聚合函数
                * 条件可用列名不同
                        * HAVING条件中的列名必须是SELECT或GROUP BY中使用过的列名
                        * WHERE条件中可以使用表的所有列名

```sql
/* 最终一个完整的SELECT语句格式 */

SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... DESC;
```


* 10分钟练习
        * 继续使用mybase库, 对zhangwu表做以下操作:
                * 按账务名称分组统计各组金额总和(查询zname, zmoney求和列, 并按照zname分组)
                * 按账务名称分组统计各组支出金额总和(查询zname, zmoney求和列, 过滤出支出, 并按zname分组)
                * 按账务名称分组统计各组支出金额总和, 并按金额总和的降序排列(查询zname, zmoney求和列, 过滤出支出, 并按zname分组, 按zmoney求和列降序排序)
                * 按账务名称分组统计各组支出金额总和大于5000的记录, 并按金额总和的降序排列(查询zname, zmoney求和列, 过滤出支出, 并按zname分组, 将分组后的结果再次过滤出大于5000的, 最后按zmoney求和列降序排序)
                        * 注意: 这个需求中支出应使用where过滤, 分组后大于5000的应使用having过滤


```sql
# 按账务名称分组统计各组金额总和(查询zname, zmoney求和列, 并按照zname分组)
SELECT zname, SUM(zmoney) FROM zhangwu
GROUP BY zname;

# 按账务名称分组统计各组支出金额总和(查询zname, zmoney求和列, 过滤出支出, 并按zname分组)
SELECT zname, SUM(zmoney) FROM zhangwu
WHERE zname LIKE '%支出%'
GROUP BY zname;

# 按账务名称分组统计各组支出金额总和, 并按金额总和的降序排列(查询zname, zmoney求和列, 过滤出支出, 并按zname分组, 按zmoney求和列降序排序)
SELECT zname, SUM(zmoney) AS '金额总和' FROM zhangwu
WHERE zname LIKE '%支出%'
GROUP BY zname
ORDER BY 金额总和 DESC;

# 按账务名称分组统计各组支出金额总和大于5000的记录, 并按金额总和的降序排列(查询zname, zmoney求和列, 过滤出支出, 并按zname分组, 将分组后的结果再次过滤出大于5000的, 最后按zmoney求和列降序排序)
SELECT zname, SUM(zmoney) AS '金额总和' FROM zhangwu
WHERE zname LIKE '%支出%'
GROUP BY zname
HAVING 金额总和 > 5000
ORDER BY 金额总和 DESC;
```


## JDBC概念和数据库驱动程序

* JDBC:
        * Java DataBase Connectivity, Java数据库连接. 是一种技术的名称
        * 是SUN公司提供的用于执行SQL语句的Java API, 用为多种关系型数据库提供统一的访问. 它由一组用Java语言编写的类和接口组成, 是Java访问数据库的标准规范
* 数据库驱动:
        * 两个不同的设备要通信, 需要满足一定的通信数据格式, 数据格式由设备提供商规定, 设备提供商为设备提供驱动软件, 通过驱动软件就可以让两个设备通信.
        * Java和MySQL数据库就可以看作2个不同的设备, JDBC需要使用数据库驱动来让Java代码与数据库服务器通信, 将Java中的SQL字符串发送给数据库来执行
        * 数据库驱动有多种, 用于不同开发语言, 对于Java也有专门的一个驱动
* Java用于MySQL的驱动从哪里得到: [MySQL官网](https://dev.mysql.com/downloads/connector/j/)



## JDBC原理

* JDBC和数据库驱动的关系
        * JDBC是一套API, 可以通过Java代码来使用JDBC, 同时提供了一套接口, 用于让数据库厂商根据自家数据库特点去实现JDBC的功能
        * 数据库驱动是数据库厂商根据自家数据库特点去实现JDBC接口而制作的, 用于让JDBC能够调用数据库的相关功能
        * Java程序通过使用JDBC的API, 通过多态的方式调用数据库驱动中实现类的方法, 对数据库进行操作

```
           +----------+
           | Java程序 |
           +-----+----+
                 |
           +-----+----+
           |   JDBC   |
           |    接口   |
           +-----+----+
                 |
       +---------+---------+
       |                   |
+------+------+     +------+------+
|  MySQL驱动   |     |  Oracle驱动 |
|    实现类    |     |    实现类   |
+------+------+     +------+------+
       |                   |
+------+------+     +------+------+
| MySQL数据库  |     | Oracle数据库 |
+-------------+     +-------------+
```



## 准备测试数据: 分类表

```sql
# day02 高级SQL, JDBC, SQL注入

-- 创建JDBC测试数据----------------------------------

# 使用mybase库
USE mybase;

# 创建产品分类表
CREATE TABLE sort (
  sid INT PRIMARY KEY AUTO_INCREMENT,
  sname VARCHAR(100),
  sprice DOUBLE,
  sdesc VARCHAR(5000)
);

# 初始化数据
INSERT INTO sort
(sname, sprice, sdesc)
VALUES
('家电', 2000, '优惠的促销'),
('家具', 8900, '家具价格上调, 原材料涨价'),
('儿童玩具', 300, '赚家长钱'),
('生鲜', 500.99, '生鲜商品'),
('服装', 24000, '换季销售'),
('洗涤', 50, '洗发水促销');

# 查看数据
SELECT * FROM sort;
```





---






## JDBC开发步骤


* JDBC开发步骤
        * 前提: 导入数据库驱动jar包
        * 代码编写步骤:
                1. 注册驱动
                        * 告知JVM使用哪一个数据库驱动
                2. 获取连接
                        * 使用JDBC中的类, 创建对MySQL数据库的连接
                3. 获取语句执行平台
                        * 通过连接对象获取对SQL语句的执行者对象
                4. 执行SQL语句
                        * 使用执行者对象, 向数据库执行SQL语句, 并获取执行结果
                5. 处理结果
                        * 根据项目的业务需求对结果进行操作
                6. 释放资源
                        * 释放数据库连接, 必须



## JDBC开发: 前提.导入数据库驱动jar包

* 步骤
        * 在项目根目录下创建`lib`目录
        * 将jar包复制到lib目录下
        * 右键点击jar文件, `Build Path...`, `Add to Build Path`, 变成奶瓶导入成功


* 3分钟练习:
        * 新建一个项目s3-day02
        * 在lib目录中导入jar包


## JDBC开发: 1.注册数据库驱动

* 注册数据库驱动的源代码
        * `java.sql.DriverManager`类: 管理JDBC驱动
                * `static void registerDriver(Driver driver)`: 注册数据库驱动
        * `java.sql.Driver`接口: 数据库驱动必须实现的接口. 在数据库驱动jar包中已经实现了
* 注册驱动的2种方式
        * 方式1: `DriverManager.registerDriver(new Driver());`
                * 其中Driver类是jar包驱动中的实现类, 如`com.mysql.jdbc.Driver`
        * 方式2: `Class.forName("com.mysql.jdbc.Driver");`
                * 这是MySQL推荐的, 内部也是使用方式1实现的
* 注意:
        * MySQL驱动`com.mysql.jdbc.Driver`类中中已经编写了注册驱动的方法. 我们只需要通过反射去加载MySQL的驱动类, 让其中的静态代码块执行, 从而注册驱动即可. 不要手动注册驱动导致资源浪费


* 3分钟练习:
        * 继续使用项目s3-day02, 建包com.itheima.practice_01
        * 建类Test
                * 使用Class类的静态方法注册驱动

```java
// 1.注册驱动, 使用反射的方式
Class.forName("com.mysql.jdbc.Driver");
```


## JDBC开发: 2.获取数据库连接对象

* 这一步相当于SQLyog点击连接按钮
* `java.sql.DriverManager`类
        * `static Connection getConnection(String url, String user, String password)`: 获取Connection接口的实现类, 实现类是jar包中实现的
                * `url`: 数据库地址, `jdbc:mysql://主机IP:端口号/数据库名`
                        * 主机IP或端口号可以省略, 默认使用localhost和3306: `jdbc:mysql:///day02_04`
                * `user`: 用户名
                * `password`: 密码


* 5分钟练习:
        * 继续编写代码
        * 通过DriverManager的静态方法获取Connection对象, 打印对象查看地址值

```java
// 2.获取连接对象
String url = "jdbc:mysql://localhost:3306/mybase";
String user = "root";
String password = "123456";
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection); // com.mysql.jdbc.JDBC4Connection@10d1f30
```


## JDBC开发: 3. 获取SQL语句的执行对象

* 相当于获取SQLyog中的Query窗口, 用来写SQL语句
* `Connection`接口
        * `Statement createStatement()`: 获取Statement对象, 该对象用于将SQL语句发送到数据库


* 3分钟练习:
        * 继续编写
        * 通过connection对象创建sql执行对象Statement

```java
// 3.通过Connection对象获取Statement对象
Statement statement = connection.createStatement();
System.out.println(statement);  // com.mysql.jdbc.StatementImpl@137bc9
```


## JDBC开发: 4.执行INSERT语句, 获取结果集, 释放资源

* `Statement`接口
        * `int executeUpdate(String sql)`: 执行SQL语句, 仅限于INSERT, DELETE, UPDATE, 返回生效的行数
        * `void close()`: 释放Statement资源
* `Connection`接口
        * `void close()`: 释放连接资源


* 3分钟练习:
        * 继续编写
        * 编写sql
                * 向sort表中增加一条记录: '汽车用品', 50000, '疯狂涨价'
        * 通过sql执行平台对象执行插入语句
        * 释放相关资源

```java
public class Test {

        public static void main(String[] args) throws ClassNotFoundException, SQLException {
                // 1.注册驱动, 使用反射的方式
                Class.forName("com.mysql.jdbc.Driver");
                // 2.获取连接对象
                String url = "jdbc:mysql://localhost:3306/mybase";
                String user = "root";
                String password = "123456";
                Connection connection = DriverManager.getConnection(url, user, password);
//                System.out.println(connection); // com.mysql.jdbc.JDBC4Connection@10d1f30
                // 3.通过Connection对象获取Statement对象
                Statement statement = connection.createStatement();
//                System.out.println(statement);  // com.mysql.jdbc.StatementImpl@137bc9
                // 4. 执行SQL语句 向sort表中增加一条记录: '汽车用品', 50000, '疯狂涨价'
                String sql = "INSERT INTO sort (sname, sprice, sdesc) VALUES ('汽车用品', 50000, '疯狂涨价');";
                int rows = statement.executeUpdate(sql);
                // 5. 处理结果
                System.out.println(rows);
                // 6. 释放资源
                statement.close();
                connection.close();
        }
}
```




---=






## JDBC开发: 5.6.执行SELECT语句, 获取结果集, 从结果集中获取数据

* `Statement`类
        * `ResultSet executeQuery(String sql)`: 执行SELECT查询, 返回查询结果到`ResultSet`对象中.
* `ResultSet`接口
        * `boolean next()`: 将结果集的指针向下移动一行, 并返回是否还有下一条记录
                * 注意: 没有调用该方法之前, ResultSet的光标在第一条记录之前, 只有调用一次next方法后, 指针才指向第一条记录
        * `int getInt(int column)`: 获取第column列的int类型的数据
        * `int getInt(String columnName)`: 获取指定列名的int类型的数据
        * `String getString(int column)`: 获取第column列的String类型数据
        * `String getString(String columnName)`: 获取指定列名的String类型的数据
        * `Object getObject(String columnName)`: 获取指定列名的任何类型数据
        * `void close()`: 释放结果集资源


* 5分钟练习:
        * 将插入语句注释掉, 继续编写代码
                * 编写sql查询sort表中所有记录
                * 并从结果集中获取数据打印到控制台
                * 释放资源

```java
public class Test {

        public static void main(String[] args) throws ClassNotFoundException, SQLException {
                // 1.注册驱动, 使用反射的方式
                Class.forName("com.mysql.jdbc.Driver");
                // 2.获取连接对象
                String url = "jdbc:mysql://localhost:3306/mybase";
                String user = "root";
                String password = "123456";
                Connection connection = DriverManager.getConnection(url, user, password);
//                System.out.println(connection); // com.mysql.jdbc.JDBC4Connection@10d1f30
                // 3.通过Connection对象获取Statement对象
                Statement statement = connection.createStatement();
//                System.out.println(statement);  // com.mysql.jdbc.StatementImpl@137bc9
               
                /*// 4. 执行SQL语句 向sort表中增加一条记录: '汽车用品', 50000, '疯狂涨价'
                String sql = "INSERT INTO sort (sname, sprice, sdesc) VALUES ('汽车用品', 50000, '疯狂涨价');";
                int rows = statement.executeUpdate(sql);
                // 5. 处理结果
                System.out.println(rows);*/
               
                // 执行查询
                String sql = "SELECT * FROM sort;";
                ResultSet resultSet = statement.executeQuery(sql);
                while (resultSet.next()) {
                        String sid = resultSet.getString("sid");
                        String sname = resultSet.getString("sname");
                        String sprice = resultSet.getString("sprice");
                        String sdesc = resultSet.getString("sdesc");
                        System.out.println(sid + "\t" + sname + "\t" + sprice + "\t" + sdesc);
                }
               
                // 6. 释放资源
                resultSet.close();
                statement.close();
                connection.close();
        }
}
```





## SQL注入攻击

* 用户注册和登录实际就是对数据库进行操作
        * 注册: 将用户名和密码INSERT一条记录
                * `INSERT INTO users (username, password) VALUES ('zhangsan', '123456');`
        * 登录: 按照用户名和密码查询是否有符合条件的记录
                * `SELECT * FROM users WHERE username='zhangsan' AND password='123456';`
                * 如果查到了, 则说明用户已经注册过, 且用户名密码都匹配, 则允许登录
                * 如果查不到, 则说明用户没有注册, 或用户名密码不匹配, 则提示重新输入
* 什么是SQL注入攻击?


```
users表
------------------------------
id  |  username  |  password
------------------------------
1   |  a         |  1
------------------------------

-- 正常登陆
用户名: ____a____
密  码: ____1____

# 服务器端的Java程序获取到用户名和密码后, 最简单的方式是将其使用字符串拼接的方式, 组成一个完整的SQL语句
String sql = "SELECT * FROM user WHERE username = '" + username + "' AND password = '" + password + "';";

SELECT * FROM user WHERE username = 'a' AND password = '1';
                                     -                  -

# 查询到1条记录, 说明用户名存在, 且密码正确, 允许登陆
登录成功!



-- 利用SQL注入来登录
用户名:__随便怎么写____
密  码:__1' OR '1=1___

# 来看看字符串拼接以后的SQL语句变成了什么?
SELECT * FROM user WHERE username = '随便怎么写' AND password = '1' OR '1=1';
                                     ---------                  ----------
# where条件变成了:
username = '随便怎么写' AND password = '1'
或者
'1=1'
因为'1=1'是永远成立的, 所以这个where条件永远成立, 则相当于没有过滤任何条件, 最终的执行效果等于SELECT * FROM user

# 既然能查到用户, 则允许登录
登录成功!
```


## SQL注入攻击Java代码演示

* 能被注入的原因:
        * SQL语句使用了字符串拼接方式
        * 使用Statement执行的语句
        * 密码: 1' OR '1=1



## 使用PreparedStatement接口预编译SQL语句, 避免SQL注入

* 避免SQL注入的解决方案
        * 不使用字符串拼接SQL
        * 使用PreparedStatement替代Statement
* 相关API
        * `Connection`接口
                * `PreparedStatement prepareStatement(String sql)`: 创建预编译对象
        * `PreparedStatement`接口
                * `setObject(int paramIndex, Object value)`: 设置指定占位符的值
                * 注意: 索引从1开始
        * SQL语句中的参数使用`?`问号占位符代替, 且不要写单引号
* 步骤:
        * 通过Connection对象预编译SQL语句, 获取PreparedStatement对象
        * 然后通过PreparedStatement对象调用方法执行语句




---






## PreparedStatement修改记录

* `PreparedStatement`接口
        * `int executeUpdate()`: 执行INSERT, DELETE, UPDATE语句, 返回生效的行数
                * 注意该方法不要传递参数


* 5分钟练习:
        * 继续使用项目s3-day02, 建包com.itheima.practice_02
        * 建类Test
                * 使用JDBC, PreparedStatement, 将分类表中sid为6的数据修改为汽车用品, 49988
                        * `String sql = "UPDATE sort SET sname = ?, sprice = ? WHERE sid = ?;";`
                        * 参数为: 汽车美容, 49988, 7

```java
public class Test {

        public static void main(String[] args) throws ClassNotFoundException, SQLException {
                // 1.注册驱动, 使用反射的方式
                Class.forName("com.mysql.jdbc.Driver");
                // 2.获取连接对象
                String url = "jdbc:mysql://localhost:3306/mybase";
                String user = "root";
                String password = "123456";
                Connection connection = DriverManager.getConnection(url, user, password);
               
                // 使用PreparedStatement修改记录
                String sql = "UPDATE sort SET sname=?, sprice=? WHERE sid=?;";
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
                // 设置参数
                preparedStatement.setObject(1, "汽车美容");
                preparedStatement.setObject(2, "49988");
                preparedStatement.setObject(3, 7);
                // 执行语句
                preparedStatement.executeUpdate();
               
                // 6. 释放资源
                preparedStatement.close();
                connection.close();
        }
}
```



## PreparedStatement执行查询

* `PreparedStatement`
        * `ResultSet executeQuery()`: 执行SELECT查询语句, 返回结果集
                * 注意该方法不要传递参数


* 5分钟练习:
        * 继续编写代码
        * 将修改语句相关代码注释掉, 使用PreparedStatement查询sort表中所有记录并打印到控制台

```java
public class Test {

        public static void main(String[] args) throws ClassNotFoundException, SQLException {
                // 1.注册驱动, 使用反射的方式
                Class.forName("com.mysql.jdbc.Driver");
                // 2.获取连接对象
                String url = "jdbc:mysql://localhost:3306/mybase";
                String user = "root";
                String password = "123456";
                Connection connection = DriverManager.getConnection(url, user, password);
               
                /*// 使用PreparedStatement修改记录
                String sql = "UPDATE sort SET sname=?, sprice=? WHERE sid=?;";
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
                // 设置参数
                preparedStatement.setObject(1, "汽车美容");
                preparedStatement.setObject(2, "49988");
                preparedStatement.setObject(3, 7);
                // 执行语句
                preparedStatement.executeUpdate();*/
               
                // 查询所有记录
                String sql = "SELECT * FROM sort;";
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
                ResultSet resultSet = preparedStatement.executeQuery();
                while (resultSet.next()) {
                        String sid = resultSet.getString("sid");
                        String sname = resultSet.getString("sname");
                        String sprice = resultSet.getString("sprice");
                        String sdesc = resultSet.getString("sdesc");
                        System.out.println(sid + "\t" + sname + "\t" + sprice + "\t" + sdesc);
                }
               
                // 6. 释放资源
                resultSet.close();
                preparedStatement.close();
                connection.close();
        }
}
```


## 封装JDBC工具类

* JDBCUtils第一版:
        * 当类加载时根据4项配置, 创建数据库连接对象
        * 定义静态方法获取数据库连接对象
        * 定义静态方法释放资源


* 10分钟练习:
        * 继续使用项目s3-day02, 建包com.itheima.practice_03
        * 复制上一个练习的代码
        * 新建JDBCUtils工具类, 完成工具类的编写
        * 将测试类中代码修改为使用工具类方法获取连接对象, 使用工具类方法释放资源
        * 测试是否可用

```java
/*
JDBC工具类: v1.0
*/
public class JDBCUtils {

        // 私有构造方法
        private JDBCUtils() {}
       
        // 定义静态的连接对象作为成员变量
        private static Connection connection;
       
        // 使用静态代码块执行注册驱动和获取连接
        static {
                try {
                        // 注册驱动
                        Class.forName("com.mysql.jdbc.Driver");
                        // 获取连接
                        String url = "jdbc:mysql://localhost:3306/mybase";
                        String user = "root";
                        String password = "123456";
                        connection = DriverManager.getConnection(url, user, password);
                } catch (Exception e) {
                        e.printStackTrace();
                        throw new RuntimeException("数据库连接失败");
                }
        }
       
        // 获取连接
        public static Connection getConnection() {
                return connection;
        }
       
        // 释放资源
        public static void close(Connection connection, Statement statement, ResultSet resultSet) {
                if (resultSet != null) {
                        try {
                                resultSet.close();
                        } catch (SQLException e) {}
                }
                if (statement != null) {
                        try {
                                statement.close();
                        } catch (SQLException e) {}
                }
                if (connection != null) {
                        try {
                                connection.close();
                        } catch (SQLException e) {}
                }
        }
}

public class Test {

        public static void main(String[] args) throws ClassNotFoundException, SQLException {
                // 使用工具类获取连接
                Connection connection = JDBCUtils.getConnection();
               
                // 查询所有记录
                String sql = "SELECT * FROM sort;";
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
                ResultSet resultSet = preparedStatement.executeQuery();
                while (resultSet.next()) {
                        String sid = resultSet.getString("sid");
                        String sname = resultSet.getString("sname");
                        String sprice = resultSet.getString("sprice");
                        String sdesc = resultSet.getString("sdesc");
                        System.out.println(sid + "\t" + sname + "\t" + sprice + "\t" + sdesc);
                }
               
                // 6. 释放资源
                JDBCUtils.close(connection, preparedStatement, resultSet);
        }
}
```




----------------------------





## 扩展

### `LIMIT ... OFFSET ...`语句
        * 在`SELECT`最后使用
        * 翻页公式: `条数 * (页数 - 1)`
        * 作用: 从总的查询结果中取出一部分作为查询结果
        * 应用场景: 常用于分页
        * 2种格式
                * 格式1: `LIMIT 结果数 OFFSET 偏移量;`
                * 格式2: `LIMIT 偏移量, 结果数;`
                * 注意: 以上两种格式的参数位置是相反的, 不要写错!

```
USE mybase;

# 分页(1页2条), 第1页
SELECT * FROM sort LIMIT 2 OFFSET 0;   条数 * (页数 - 1)

# 分页(1页2条), 第2页
SELECT * FROM sort LIMIT 2 OFFSET 2;   条数 * (页数 - 1)

# 分页(1页2条), 第3页
SELECT * FROM sort LIMIT 2 OFFSET 4;   条数 * (页数 - 1)

# 分页(1页2条), 第4页
SELECT * FROM sort LIMIT 2 OFFSET 6;   
```


### `UNION`合并操作符
        * 作用: 用于将两个或多个SELECT语句的查询结果合并为一个结果集
        * 必要条件:
                * 所有合并的SELECT结果集必须拥有**相同的列**(列的数量相同, 列名相同, 列的顺序相同)
        * 默认情况下, **UNION会对结果集进行去重**, 如果不想去重, 可以使用`UNION ALL`

```sql
# 格式
SELECT column_name, ... FROM table_name1
[WHERE condition]
UNION [ALL|DISTINCT]
SELECT column_name, ... FROM table_name2
[WHERE condition];

# 示例
SELECT username FROM user_table1
UNION
SELECT username FROM user_table2;



SELECT * FROM sort WHERE sid = 1 OR sid = 7;

(SELECT * FROM sort WHERE sid = 1)
UNION
(SELECT * FROM sort WHERE sid = 7)

(SELECT * FROM sort)
UNION
(SELECT * FROM sort)
```

* 演示

```
# 第一个SELECT
SELECT username FROM user_table1;

# 输出
|username|
|--------|
|zhangsan|

# 第二个SELECT
SELECT username FROM user_table2;

# 输出
|username|
|--------|
|    lisi|

# 使用UNION合并
(SELECT username FROM user_table1)
UNION
(SELECT username FROM user_table2);

# 输出
|username|
|--------|
|zhangsan|
|    lisi|
```

### 子查询(也叫嵌套查询)

* 子查询: 使用SELECT语句查询出来的结果集, 实际上也可以看做一个表. 既然是一个表, 我们就可以用他作为其他查询语句的表
* 子查询语句也是一个SELECT语句, 使用小括号`()`括起来
* 注意:
        * 在SELECT查询语句中使用子查询, 相当于把结果集看做一个表
                * 如果作为WHERE子句的条件, 则子查询可以不起别名
                        * 如: `SELECT * FROM user_table1 WHERE age < (SELECT AVG(age) FROM user_table1);`
                * 如果作为FROM后被查询的表, **子查询必须起别名**
                        * 如: `SELECT * FROM (SELECT username FROM user_table1) AS user WHERE username = 'zhangsan';`
                        * 注意:
                                * 如果不起别名会报错: Every derived table must have its own alias
                                * 虽然起了别名, 但结果集实际还是结果集, 并不会变成真的表
        * 使用UPDATE, DELETE语句时, 被更新的表不能用在set或where的子查询
                * 否则会报错: `You can't specify target table 'xxx' for update in FROM clause`
                * 解决办法: 在操作被更新的表的子查询外再套一层SELECT * FROM(是否取别名取决于是用在FROM还是WHERE子句), 然后外层再执行UPDATE或DELETE
                * 如: `DELETE FROM stutb WHERE score < (SELECT * FROM (SELECT AVG(score) FROM stutb WHERE department LIKE 'javaEE基础班') AS avgscore);`

```sql
# 一个SELECT查询
SELECT username FROM user_table1;

# 输出
|username|
|--------|
|zhangsan|
|    lisi|
|  wangwu|

# 如果把以上看做一个表, 假设这个表名为user, 则我们会这样写
SELECT * FROM user WHERE username = 'zhangsan';

# 可以得到
|username|
|--------|
|zhangsan|

# 所以, 我们把user替换为刚才的SELECT语句, 并用小括号将SELECT语句括起来
# 替换结果类似于下面这样
# SELECT * FROM user                                       WHERE username = 'zhangsan';
# SELECT * FROM (SELECT username FROM user_table1) AS user WHERE username = 'zhangsan';
# 执行一下
SELECT * FROM (SELECT username FROM user_table1) WHERE username = 'zhangsan';

# 也可以得到
|username|
|--------|
|zhangsan|

# 这就是子查询, 括号内的查询相当于父查询语句的一个孩子, 使用子查询的结果作为表继续查询



SELECT sname FROM sort;

sheet = SELECT sname FROM sort;

SELECT * FROM (SELECT sname FROM sort) AS z WHERE sname = '家电';
```



---------------------------------


## 今日总结

* 高级SQL
        * 排序
                * `ORDER BY 列名 ASC|DESC;`
                * 注意:
                        * 默认顺序为升序ASC
                        * WHERE在前, ORDER BY在后
        * 聚合函数
                * 用在`SELECT`后或`HAVING`后
                * `COUNT(列名)`: 计算记录数量
                        * NULL值不会被算入数量
                * `SUM(列名)`: 将该列的值求总和
                        * 如果不是数字类型, 则结果为0
                * `MAX(列名)`: 求该列值中的最大值
                * `MIN(列名)`: 求该列值中的最小值
                * `AVG(列名)`: 求该列所有值的平均值
                        * 如果不是数字类型, 则结果为0
        * 分组
                * `GROUP BY 列名 HAVING 条件;`: 对查询结果集分组, 然后再按条件过滤
                * 注意:
                        * 聚合函数与分组的使用效果
                                * 使用聚合函数后再进行分组, 才相当于汇总统计
                                        * `SELECT zname, SUM(zmoney) FROM zhangwu GROUP BY zname;`
                                * 不使用聚合函数的分组, 只有第一条记录的值
                                        * `SELECT zname, zmoney FROM zhangwu GROUP BY zname;`
                        * `HAVING`和`WHERE`的区别
                                * 过滤时机不同
                                        * WHERE是在分组前先对查询结果进行过滤
                                        * HAVING是在查询结果分组后再次过滤
                                * 是否可跟随聚合函数不同
                                        * WHERE条件中不可使用聚合函数
                                        * HAVING条件中可以使用聚合函数
                                * 条件可用列名不同
                                        * HAVING条件中的列名必须是SELECT或GROUP BY中使用过的列名
                                        * WHERE条件中可以使用表的所有列名
* JDBC
        * JDBC: Java数据库连接. SUN公司提供的接口, 用来让数据库厂商实现数据库操作的功能
        * 数据库驱动: 就是JDBC的实现
        * JDBC开发步骤:
                * 前提: 导包(数据库驱动)
                * 步骤:
                        1. 注册驱动
                                * `DriverManager.registerDriver()`: 是真实的注册驱动的方法
                                * 因为驱动中实现了这个方法, 推荐我们使用:
                                        * `Class.forName("数据库驱动的Driver实现类全类名")`
                        2. 获取连接:
                                * `Connection conn = DriverManager.getConnection(url, username, password)`
                        3. 获取执行平台:
                                * `Statement s = conn.createStatement();`: 不安全的
                                * `PreparedStatement ps = conn.prepareStatement(String sql);`: 可以防范SQL注入
                                        * 设置参数:
                                                * 占位符: `?`, 即使是字符串, 也不要给问号加引号
                                                * `void setObject(int index, Object obj)`
                                                        * index从1开始
                        4. 执行SQL语句
                                * Statement
                                        * 增删改: `int s.executeUpdate(String sql)`
                                                * 判断操作是否成功: 判断返回值是否`>0`则生效, 否则不成功
                                        * 查: `ResultSet rs = s.executeQuery(String sql)`
                                                * 思考: 判断操作是否成功: 是返回null呢还是调用`.next()`用是否为true判断?
                                * PreparedStatement
                                        * 增删改: `int ps.executeUpdate();`
                                                * 判断操作是否成功: 判断返回值是否`>0`则生效, 否则不成功
                                        * 查: `ResultSet rs = ps.executeQuery();`
                        5. 处理结果
                                * `ResultSet`
                                        * `boolean next();`: 将指针向后移动, 如果有下一个记录则true, 否则false
                                                * 默认不执行的之后, 指针在第一条记录之前; 只有调用一次才能指向第一条: `while (rs.next()) {}`
                                        * `String getString(String 列名)`: 获取指定列的值, 以String类型
                                        * `Object getObject(String 列名)`: 获取指定列的值, 以Object类型
                        6. 释放资源
                                * `resultSet.close()`
                                * `preparedStatement.close()`
                                * `statement.close()`
                                * `connection.close()`
        * SQL注入
                * 原理: 拼接SQL导致用户的输入可以改变SQL的意义, Statement无法避免注入
                * 超级密码: `1' OR '1=1`
                * 如何避免: 使用PreparedStatement替换Statement
        * JDBCUtils工具类的自定义
                * 在静态代码块中初始化连接对象
                * 定义方法获取连接对象, 释放资源



* 今日重点
        * 高级SQL语句
                * 排序ORDER BY
                * 聚合函数
                        * SUM()
                        * COUNT()
                        * MIN()
                        * MAX()
                        * AVG()
                * 分组: GROUP BY 列名 HAVING 条件
        * JDBC
                * 数据库驱动和JDBC原理: 多态的调用
                * 代码:
                        * JDBC操作的6个步骤:
                                1. (先导入jar包) 注册驱动
                                2. 获取连接
                                3. 获取执行平台
                                4. 执行SQL语句
                                5. 处理结果
                                6. 释放资源
                        * 执行平台
                                * Statement(不能用)
                                * PreparedStatement(重点)


```
// Statement
// 1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
Connection conn = DriverManager.getConnection(url, user, password);
// 3. 获取执行平台
Statement statement = conn.createStatement();
// 4. 执行SQL
int rows = statement.executeUpdate(sql);    // INSERT UPDATE DELETE   DML
ResultSet rs = statement.executeQuery(sql);  // SELECT               DQL
// 5. 操作数据
...
// 6. 释放资源
rs.close();
statement.close();
conn.close()
```

```
// PreparedStatement
// 1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
Connection conn = DriverManager.getConnection(url, user, password);
// 3. 获取执行平台
String sql = ".... ? ...";
PreparedStatement pst = conn.prepareStatement(sql);
// 如果有参数, 设置参数
pst.setObject(占位符的序号, 参数值);
// 4. 执行sql语句
int rows = pst.executeUpdate();  // INSERT UPDATE DELETE
ResultSet rs = pst.executeQuery();              // SELECT
// 5. 处理结果
while (rs.next()) {
        //rs.getXxx();
}
// 6. 释放资源
rs.close();
pst.close();
conn.close();
```

```
// JDBCUtils
public class JDBCUtils {

        // 私有构造方法
        private JDBCUtils() {}

        // 定义静态变量Connection
        private static Connection conn;

        // 使用静态代码块初始化
        static {
                try {
                        // 注册驱动
                        Class.forName("com.mysql.jdbc.Driver");
                        // 获取连接
                        String url = "";
                        String user = "";
                        String password = "";
                        conn = DriverManager.getConnection(url, user, password);
                } catch(Exception e) {
                        throw new RuntimeException("数据库连接失败");
                }
        }

        // 获取连接对象
        public static Connection getConnection() {
                return conn;
        }

        // 释放资源
        public static void close(Connection conn, Statement statement, ResultSet rs) {  // null
                if (resultSet != null) {
                        try {
                                resultSet.close();
                        } catch (SQLException e) {}
                }
                if (statement != null) {
                        try {
                                statement.close();
                        } catch (SQLException e) {}
                }
                if (connection != null) {
                        try {
                                connection.close();
                        } catch (SQLException e) {}
                }
        }




# day03 加载本地配置, DBUtils


## 数据表与Java类的转换关系案例演示

* 案例需求:
        * 使用JDBC查询sort表的所有记录, 将查询出的结果集中的每条记录封装为一个Sort类对象, 再将这些Sort对象存入List
* 步骤:
        1. 按照表和Java类的对应关系, 定义一个以表名为名的类
        2. 类中属性就是表的字段
        3. 创建一个对象来封装查询出的记录的各个列的值
        4. 结果集中的多条记录, 就是多个对象, 将这些对象存入一个集合中, 用集合来表示查询的结果集
* 有什么用?
        * 这种方式完成了数据库表向Java对象的转化, 叫做ORM, Object Relation Mapping, 对象关系映射. 就是指Java对象和关系型数据表的相互转换过程
        * 如果我们能将数据表转换为Java的类, 那我们操作数据库的表就相当于操作集合和对象, 这样会变得简单很多


## Properties配置文件: 将JDBCUtils的连接参数保存在配置文件中

* JDBC中连接数据库的配置:
        * 会改变的配置项:
                * 驱动类类名
                * url
                * 用户名
                * 密码
        * 我们希望修改更方便, 而不是每次都要修改Java代码后重新编译, 所以想到可以用读取文件的方式动态的获取这些信息
* Properties文件
        * 以`.properties`后缀结尾的文件, 以`key=value`方式编写配置
        * 配置文件创建位置: 在项目的`src`目录下创建该配置文件, 在编译时会将该配置文件自动复制到`bin`目录中



## Properties配置文件: 创建和编写文件

* 注意:
        * properties文件放在src目录下就可以, 不要放在包中


* 3分钟练习: 创建配置文件
        * 创建项目s3-day03
        * 在src目录下创建`database.properties`文件
        * 在其中写入键值对信息
                * driverClass=com.mysql.jdbc.Driver
                * url=jdbc:mysql://localhost:3306/mybase
                * username=root
                * password=123456
        * 查看在bin目录下是否已经自动复制了配置文件
        * 注意: 不要直接在bin目录下创建


## Properties配置文件: 使用类加载器加载文件

* 回忆Java的编译原理和内存机制:
        * src下存放java文件, 编译后生成class文件到bin目录下.
                * src下的其他文件, 编译后也会自动复制到bin目录下
        * 程序运行时, 要使用一个类, 必须先加载类到内存中, 所以就会使用`类加载器`将要用的类的字节码加载到方法区中
                * 所以使用类加载器, 同样可以找到bin目录下的其他文件
* **注意**:
        * 在src目录下修改配置文件, 内容会自动同步到bin目录中
        * 但是不能直接删除bin目录下的配置文件, 如删除则需要重新在src目录中创建配置文件
* 加载bin目录下文件的方式:

```java
// 1.获取类加载器: 获取某个类的字节码对象, 通过字节码对象获取类加载器对象
ClassLoader classLoader = 类名.class.getClassLoader();
// 2.使用类加载器对象加载配置文件, 返回字节输入流  
InputStream in = classLoader.getResourceAsStream("相对于bin目录的相对路径");
// 3.使用Properties加载输入流, 获得键值对信息
properties.load(in);
```



## 读取配置文件, 连接数据库

* 注意键名不要写错, 会导致读取配置文件时找不到对应的值
* 回忆Properties
        * `String getProperty(String key)`: 通过键获取值

```java
// 最终实现方式:

InputStream in = 某个类名.class.getClassLoader().getResourceAsStream("相对于bin目录的相对路径");
Properties properties = new Properties();
properties.load(in);

String driverClass = properties.getProperty("driverClass");
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");

Class.forName(driverClass);
connection = DriverManager.getConnection(url, username, password);
```


## 打造能读取配置文件的JDBC工具类

* 10分钟练习:
        * 继续使用s3-day03项目, 建包com.itheima.utils
        * 建类JDBCUtilsConfig, 并优化
                * 在工具类中定义5个静态变量:
                        * private static Connection conn;
                        * private static String driverClass;
                        * private static String url;
                        * private static String username;
                        * private static String password;
                * 定义读取配置文件的方法:
                        * private static void readConfig()
                        * 其中使用ClassLoader加载配置文件, 读取配置, 对静态变量driverClass, url, username, password进行初始化
                * 定义静态代码块, 在其中
                        * 调用readConfig()方法读取配置
                        * 调用Class.forName(driverClass)加载驱动
                        * 调用conn = DriverManager.getConnection(url, username, password)对连接对象进行初始化
                * 定义public static Connection getConnection()方法
                * close()释放资源方法不用再定义, 后面会使用DbUtils里面的释放资源的方法

```
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybase
username=root
password=123456
```

```java
/*
JDBC工具类: v2.0
可以读取配置文件
*/
public class JDBCUtilsConfig {

        // 声明静态变量
        private static Connection connection;
        private static String driverClass;
        private static String url;
        private static String username;
        private static String password;
       
        // 静态代码块进行初始化
        static {
                try {
                        // 读取配置文件
                        readConfig();
                        // 注册驱动
                        Class.forName(driverClass);
                        // 获取连接
                        connection = DriverManager.getConnection(url, username, password);
                } catch (Exception e) {
                        e.printStackTrace();
                        throw new RuntimeException("数据库连接失败");
                }
        }
       
        // 私有构造方法
        private JDBCUtilsConfig() {}
       
        // 读取配置文件方法
        private static void readConfig() throws Exception {
                // 使用类加载器加载bin目录下的配置文件, 获取输入流
                InputStream in = JDBCUtilsConfig.class.getClassLoader().getResourceAsStream("database.properties");
                // 创建Properties对象
                Properties properties = new Properties();
                // 加载数据
                properties.load(in);
                // 通过键获取值
                driverClass = properties.getProperty("driverClass");
                url = properties.getProperty("url");
                username = properties.getProperty("username");
                password = properties.getProperty("password");
        }
       
        // 获取连接对象
        public static Connection getConnection() {
                return connection;
        }
       
}
```


## 测试工具类

* 3分钟练习: 测试工具类
        * 继续使用包com.itheima.utils
        * 新建Test类, 编写main方法
                * 使用优化后的JDBCUtilsConfig获取连接
                * 打印连接查看连接对象是否存在

```java
public class Test {

        public static void main(String[] args) {
                Connection connection = JDBCUtilsConfig.getConnection();
                System.out.println(connection);  // com.mysql.jdbc.JDBC4Connection@1fcc7a9
        }
}
```







---








## DBUtils工具类介绍, 三个核心类: 一个更好的JDBC工具类

* `DBUtils`
        * 是Apache的Commons项目中的组件, 用于简化JDBC的开发. 和我们自定义的JDBCUtils的目的一样, 简化开发
        * 三个核心类/接口
                * `QueryRunner`类: 提供操作SQL语句的API
                        * `QueryRunner()`: 创建对象
                        * `int update(Connection conn, String sql, Object... param)`: 执行INSERT, DELETE, UPDATE语句, 同时传入占位符的参数值, 是可变参数
                        * `<T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params)`: 执行SELECT查询, 并根据传入的结果集处理器来处理结果
                * `ResultSetHandler`接口: 定义SELECT操作后对结果集的操作方法, 可以将结果集转换为Java类
                        * `ArrayHandler`类: 将结果集的第一条记录封装到一个`Object[]`数组中, 数组中的每一个元素就是该条记录中的每一列的值
                        * `ArrayListHandler`类: 将结果集中的每一条记录都封装到一个`Object[]`数组中, 再将这些数组封装到`List<Object[]>`集合中
                        * `BeanHandler`类: 将结果集中的第一条记录封装到一个指定的JavaBean中
                        * `BeanListHandler`类: 将结果集中的每一条记录都封装到一个指定的JavaBean中, 再将这些JavaBean封装到`List<T>`集合中
                        * `ColumnListHandler`类: 将结果集中指定的列的字段值, 封装到一个List集合. 用于竖向查一个列
                        * `ScalarHandler`类: 获取单一结果, 用于聚合函数如`SELECT COUNT(*) FROM 表`的操作
                        * `MapHandler`类: 将结果集的第一条记录封装到一个Map中. key是列名, value是第一条记录的每个字段的值
                        * `MapListHandler`类: 将结果集的每一条记录封装到一个Map中. key是列名, value是第一条记录的每个字段的值. 然后再将这些Map封装到一个List中
                * `DbUtils`类: 用于关闭资源和事务处理
                        * `static void closeQuietly(Connection conn)`: 关闭连接, 内部处理了异常


## 事务的简单介绍

* 事务:
        * transaction, 是一组操作, 这组操作要么全都成功, 要么全都失败
        * 举例: 银行转账, 一方扣钱, 另一方加钱, 必须同时成功, 否则要同时失败. 不能一方扣了钱, 另一方还没有加钱


## QueryRunner类: update方法: 介绍

* `QueryRunner`类
        * `int update(Connection conn, String sql, Object... params)`: 执行INSERT, DELETE, UPDATE语句, 同时传入占位符的参数值, 是可变参数
       

## QueryRunner类: update方法: 执行INSERT语句

* `QueryRunner`类
        * `QueryRunner()`: 创建对象
        * `int update(Connection conn, String sql, Object... params)`: 执行INSERT, UPDATE, DELETE操作, 返回成功行数
* `DbUtils`类
        * `static void closeQuietly(Connection conn)`: 关闭连接, 内部处理了异常


* 5分钟练习: QueryRunner执行INSERT语句
        * 继续使用项目s3-day03, 建包com.itheima.practice_01
        * 建Test类
                * 定义静态成员变量connection, 使用自定义的JDBCUtilsConfig工具类获取connection对象
                * 在类中定义public static void insert()方法, 在其中测试QueryRunner类的操作
                        * 创建QueryRunner对象
                        * 编写插入记录的sql语句, 使用占位符?
                        * 设置占位符参数: "体育用品", 289.32, "购买体育用品"
                        * 调用update()方法执行sql, 返回行数
                        * 打印行数
                        * 使用DbUtils类的静态方法closeQuietly(Connection conn), 关闭连接对象
                * 在main方法中调用insert()方法测试, 在SQLYog中查看是否成功

```java
public class Test {
       
        private static Connection connection = JDBCUtilsConfig.getConnection();

        public static void main(String[] args) throws SQLException {
                insert();
        }
       
        // 测试DbUtils插入数据
        private static void insert() throws SQLException {
                // 创建QueryRunner对象
                QueryRunner queryRunner = new QueryRunner();
                // 定义SQL
                String sql = "INSERT INTO sort (sname, sprice, sdesc) VALUES (?, ?, ?);";
                // 定义参数数组
                Object[] params = {"体育用品", 289.32, "购买体育用品"};
                // 执行SQL语句
                int rows = queryRunner.update(connection, sql, params);
                System.out.println(rows);
                // 释放资源
                DbUtils.closeQuietly(connection);
        }
}
```



## QueryRunner类: 使用update方法执行UPDATE语句

* 5分钟练习: 执行UPDATE语句
        * 仍然在刚才的Test类中, 定义public static void update(), 在其中测试执行UPDATE语句
                * 创建QueryRunner对象
                * 编写sql语句: 将sid为4的记录修改为: "花卉", 100.89, "情人节玫瑰"
                * 参数使用占位符, 传入相关参数
                * 调用执行sql语句, 打印行数
                * 使用DbUtils的方法释放资源
        * 在main方法中调用update(), 在SQLyog中查看是否成功

```java
// 测试DbUtils更新数据
private static void update() throws SQLException {
        // 创建QueryRunner对象
        QueryRunner queryRunner = new QueryRunner();
        // 定义SQL
        String sql = "UPDATE sort SET sname=?, sprice=?, sdesc=? WHERE sid=?;";
        // 定义参数数组
        Object[] params = {"花卉", 100.89, "情人节玫瑰", 4};
        // 执行SQL语句
        int rows = queryRunner.update(connection, sql, params);
        System.out.println(rows);
        // 释放资源
        DbUtils.closeQuietly(connection);
}
```



## QueryRunner类: 使用update方法执行DELETE语句

* 5分钟练习: 执行DELETE语句
        * 仍然在刚才的Test类中, 定义另一个静态方法delete(), 在其中测试执行DELETE语句
                * 创建QueryRunner对象
                * 编写sql语句: 将sid为8的体育用品的记录删除
                * 参数使用占位符, 传入相关参数
                * 调用执行sql语句, 打印行数
                * 使用DbUtils的方法释放资源
        * 在main方法中调用delete(), 在SQLyog中查看是否成功

```java
// 测试DbUtils删除数据
private static void delete() throws SQLException {
        // 创建QueryRunner对象
        QueryRunner queryRunner = new QueryRunner();
        // 定义SQL
        String sql = "DELETE FROM sort WHERE sid=?;";
        // 执行SQL语句
        int rows = queryRunner.update(connection, sql, 8);
        if (rows > 0) {
                System.out.println("删除成功");
        } else {
                System.out.println("删除失败");
        }
        // 释放资源
        DbUtils.closeQuietly(connection);
}
```






---=








## 根据账务表定义JavaBean类: 表和Java类的转换实现

* JavaBean的要求
        * 实现`java.io.Serializable`接口, 定义`serialVersionId`
        * 成员属性使用`private`修饰
        * 提供`public`的get/set方法
        * 至少提供一个无参的构造


## DBUtils工具类处理结果集

* `ResultSetHandler`接口
        * 有很多类型的实现类, 常用实现类如下
                * `ArrayHandler`: 将结果集的第一条记录封装到一个`Object[]`数组中, 数组中的一个元素就是该条记录中的一个字段值
                * `ArrayListHandler`: 将结果集中的每一条记录都封装到一个`Object[]`数组中, 再讲这些数组封装到`List`集合中
                * `BeanHandler`: 将结果集中的第一条记录封装到一个指定的JavaBean中
                * `BeanListHandler`: 将结果集中的每一条记录都封装到一个指定的JavaBean中, 再将这些JavaBean封装到`List`集合中
                * `ColumnListHandler`: 将结果集中指定的列的字段值, 封装到一个List集合. 查一个列
                * `ScalarHandler`: 用于单数据, 如`SELECT COUNT(*) FROM 表`的操作
                * `MapHandler`: 将结果集的第一条记录封装到一个Map中. key是列名, value是第一条记录的每个字段的值
                * `MapListHandler`: 将结果集的每一条记录封装到一个Map中. key是列名, value是第一条记录的每个字段的值. 然后再将这些Map封装到一个List中
* `QueryRunner`对象执行SELECT查询的方法:
                * `<T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params)`: 执行SELECT查询, 并根据传入的结果集处理器来处理结果
                * 注意返回值是泛型


## QueryRunner类: 使用query方法执行查询

(继续看下一个知识点)


## 结果集处理: ArrayHandler

* `ArrayHandler`:
        * 将结果集的第一条记录封装到一个`Object[]`数组中, 数组中的一个元素就是该条记录中的一个字段值
* 如何判断是否有结果?
        * 判断返回数组的长度是否大于0


## 结果集处理: ArrayListHandler

* `ArrayListHandler`:
        * 将结果集中的每一条记录都封装到一个`Object[]`数组中, 再将这些数组封装到`List`集合中
* 如何判断查询到结果?
        * 判断list.size()是否大于0


* 5分钟练习:
        * 仍然在com.itheima.practice_01包中的Test类中, 定义另一个静态方法arrayListHandler()
                * 创建QueryRunner对象
                * 定义查询sort表中所有列的记录的SQL语句
                * 使用QueryRunner调用query()方法执行sql语句, 使用创建匿名对象的方式传入ArrayListHandler对象
                * 注意返回值是`List<Object[]>`集合
                        * 先增强for遍历List集合, 得到每个Object[]
                        * 然后增强for遍历每个Object[], 获取记录值, 不换行打印记录值
                        * 注意每打印完一条记录要记得换行
                * 使用DbUtils关闭连接
        * 在main方法中调用测试

```java
// 测试DbUtils查询数据: ArrayListHandler
private static void arrayListHandler() throws SQLException {
        // 创建QueryRunner对象
        QueryRunner queryRunner = new QueryRunner();
        // 定义SQL
        String sql = "SELECT * FROM sort";
        // 执行语句
        List<Object[]> result = queryRunner.query(connection, sql, new ArrayListHandler());
        // 遍历集合
        for (Object[] objects : result) {
                // 继续遍历每条记录的object数组
                for (Object value : objects) {
                        // 打印该条记录的每列的值, 不换行
                        System.out.print(value + "\t");
                }
                // 打印完一条记录后换行
                System.out.println();
        }
        // 释放资源
        DbUtils.close(connection);
}
```





---






## 结果集处理: BeanHandler

* `BeanHandler<T>(Class<T> type)`:
        * **注意**: T泛型写JavaBean的类名, 参数写JavaBean类的字节码对象
        * 将结果集中的第一条记录封装到一个指定的JavaBean中
* 使用该处理器的前提
        * JavaBean类必须有**空参构造**. 这是一个惯例, 以后大部分ORM框架都有这个要求
* 如何判断是否有结果?
        * 判断返回的对象是否为`null`


## 结果集处理: BeanListHandler

* `BeanListHandler<T>(Class<T> type)`:
        * **注意**: T泛型写JavaBean的类名, 参数写JavaBean类的字节码对象
        * 将结果集中的每一条记录都封装到一个指定的JavaBean中, 再将这些JavaBean封装到`List`集合中



* 5分钟练习:
        * 仍然在com.itheima.practice_01包中
        * 建Sort类
                * 在其中将表转换为Java类的定义, 注意几点
                        * 私有成员变量
                        * 空参构造, 有参构造
                        * get/set方法
                        * 自动生成toString方法
        * 在Test类中定义另一个静态方法beanListHandler()
                * 创建QueryRunner对象
                * 定义查询sort表中所有列的记录的SQL语句
                * 使用QueryRunner调用query()方法执行sql语句, 使用创建匿名对象的方式传入new BeanListHandler<Sort>(Sort.class)对象, 注意传入Sort的字节码对象
                * 返回值是`List<Sort>`对象, 遍历集合获取对象, 打印对象
                * 使用DbUtils关闭连接
        * 在main方法中调用查看结果

```java
// 测试DbUtils查询数据: BeanListHandler
private static void beanListHandler() throws SQLException {
        // 创建QueryRunner对象
        QueryRunner queryRunner = new QueryRunner();
        // 定义SQL
        String sql = "SELECT * FROM sort";
        // 执行语句
        List<Sort> result = queryRunner.query(connection, sql, new BeanListHandler<Sort>(Sort.class));
        // 遍历集合
        for (Sort sort : result) {
                System.out.println(sort);
        }
        // 释放资源
        DbUtils.close(connection);
}
```


## 结果集处理: ColumnListHandler

* `ColumnListHandler`:
        * 将结果集中指定的列的字段值, 封装到一个List集合. 查一个列
* 注意:
        * 如果创建ColumnListHandler对象时传入的列名不在查询语句中被查询的列中, 则会抛出异常


## 结果集处理: ScalarHandler

* `ScalarHandler`:
        * 用于单一值的结果集, 如`SELECT COUNT(*) FROM 表`的操作
        * 返回结果的类型:
                * 整数是Long
                * 小数是Double



* 5分钟练习: 查询`COUNT(*)`
        * 仍然在com.itheima.practice_01包中的Test类中, 定义另一个静态方法scalarHandler()
                * 创建QueryRunner对象
                * 定义SQL语句: SELECT COUNT(*) FROM sort;
                * 使用QueryRunner调用query()方法执行sql语句, 使用创建匿名对象的方式传入new ScalarHandler<Long>()对象
                * 注意返回值是Long对象, 打印对象
                * 使用DbUtils关闭连接
        * 在main方法中调用查看结果

```java
// 测试DbUtils查询数据: ScalarHandler
private static void scalarHandler() throws SQLException {
        // 创建QueryRunner对象
        QueryRunner queryRunner = new QueryRunner();
        // 定义SQL
        String sql = "SELECT COUNT(*) FROM sort";
//                String sql = "SELECT AVG(sprice) FROM sort";
        // 执行语句
        long count = queryRunner.query(connection, sql, new ScalarHandler<Long>());
//                double avgPrice = queryRunner.query(connection, sql, new ScalarHandler<Double>());
        // 打印结果
        System.out.println(count);
        // 释放资源
        DbUtils.close(connection);
}
```




---






## 结果集处理: MapHandler

* `MapHandler`: 将结果集的第一条记录封装到一个Map中. key是列名, value是第一条记录的每个字段的值
               

## 结果集处理: MapListHandler

* `MapListHandler`: 将结果集的每一条记录封装到一个Map中. key是列名, value是第一条记录的每个字段的值. 然后再将这些Map封装到一个List中





--------------------------






## 扩展

* 上网看看
        * 怎么从mysql官网找mysql程序, 以及mysql的jdbc驱动
        * Apache是什么?
        * Commons项目是什么?
        * 开源又是什么?




## 今日总结

* 使用Properties配置文件
        * 作用: 将配置信息从源代码中隔离, 达到可以直接修改配置文件, 而不用修改源代码的目的
        * 配置文件的创建
                * 在`src`目录下创建, 会自动在`bin`目录下生成
                * 以`.properties`后缀结尾
                * 配置文件的格式: 键值对, 一行一个键值对
        * 读取配置文件
                * 通过类加载器, 去加载`bin`目录下的配置文件
                * 类加载器的获取:
                        1. 获取类的字节码对象: `类名.class`
                        2. 通过类的字节码对象获取类加载器: `ClassLoader 字节码对象.getClassLoader()`
                        3. 通过类加载器对象读取资源文件: `InputStream getResourceAsStream(String 配置文件路径)`
                        4. 创建Properties对象: `Properties p = new Properties();`
                        5. 通过该对象加载流(此时Properties对象才有数据): `p.load(inputStream)`
                        6. 通过Properties对象获取属性值: `String p.getProperty(String key)`
* DbUtils
        * 是什么: 是Apache的Commons项目中的一个组件, 开源的工具类(第三方库)
        * 三个核心类/接口
                * `QueryRunner`类: 提供操作SQL语句的API
                        * `QueryRunner()`: 创建对象
                        * `int update(Connection conn, String sql, Object... param)`: 执行INSERT, DELETE, UPDATE语句, 同时传入占位符的参数值, 是可变参数
                        * `<T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params)`: 执行SELECT查询, 并根据传入的结果集处理器来处理结果
                * `ResultSetHandler`接口: 定义SELECT操作后对结果集的操作方法, 可以将结果集转换为Java类
                        * `ArrayHandler`类: 将结果集的第一条记录封装到一个`Object[]`数组中, 数组中的每一个元素就是该条记录中的每一列的值
                        * `ArrayListHandler`类: 将结果集中的每一条记录都封装到一个`Object[]`数组中, 再将这些数组封装到`List<Object[]>`集合中
                        * `BeanHandler`类: 将结果集中的第一条记录封装到一个指定的JavaBean中
                        * `BeanListHandler`类: 将结果集中的每一条记录都封装到一个指定的JavaBean中, 再将这些JavaBean封装到`List<T>`集合中
                        * `ColumnListHandler`类: 将结果集中指定的列的字段值, 封装到一个List集合. 用于竖向查一个列
                        * `ScalarHandler`类: 获取单一结果, 用于聚合函数如`SELECT COUNT(*) FROM 表`的操作
                        * `MapHandler`类: 将结果集的第一条记录封装到一个Map中. key是列名, value是第一条记录的每个字段的值
                        * `MapListHandler`类: 将结果集的每一条记录封装到一个Map中. key是列名, value是第一条记录的每个字段的值. 然后再将这些Map封装到一个List中
                * `DbUtils`类: 用于关闭资源和事务处理
                        * `static void closeQuietly(Connection conn)`: 关闭连接, 内部处理了异常


```java
/*
JDBC工具类: v2.0
可以读取配置文件
*/
public class JDBCUtilsConfig {

        // 声明静态变量
        private static Connection connection;
        private static String driverClass;
        private static String url;
        private static String username;
        private static String password;
       
        // 静态代码块进行初始化
        static {
                try {
                        // 读取配置文件
                        readConfig();
                        // 注册驱动
                        Class.forName(driverClass);
                        // 获取连接
                        connection = DriverManager.getConnection(url, username, password);
                } catch (Exception e) {
                        e.printStackTrace();
                        throw new RuntimeException("数据库连接失败");
                }
        }
       
        // 私有构造方法
        private JDBCUtilsConfig() {}
       
        // 读取配置文件方法
        private static void readConfig() throws Exception {
                // 使用类加载器加载bin目录下的配置文件, 获取输入流
                InputStream in = JDBCUtilsConfig.class.getClassLoader().getResourceAsStream("database.properties");
                // 创建Properties对象
                Properties properties = new Properties();
                // 加载数据
                properties.load(in);
                // 通过键获取值
                driverClass = properties.getProperty("driverClass");
                url = properties.getProperty("url");
                username = properties.getProperty("username");
                password = properties.getProperty("password");
        }
       
        // 获取连接对象
        public static Connection getConnection() {
                return connection;
        }
}
```


# day04 DBCP连接池


## 连接池介绍

* 为什么需要多个连接?
        * 因为实际项目运行起来会有很多用户同时操作, 比如很多用户可能同时在查看商品列表, 而商品列表的获取就需要连接数据库执行SELECT语句
        * 普通方式连接数据库的弊端
                * 每次操作都新建一个连接, 然后执行SQL语句, 执行完毕后关闭连接
                * 由于获取连接和释放资源都是既消耗时间, 又消耗系统资源的两个过程, 所以如果每个用户在点击查看商品列表时都去建立一次TCP连接到数据库然后再查询数据, 那么页面显示速度是很慢的
        * 解决办法:
                * 使用数据库连接池技术
                * 什么是连接池?
                        * 一次性创建多个连接对象, 存放在一个集合中, 好像把这些连接放在一个池子里. 当需要连接数据库时, 不用再创建新的, 而是直接从连接池中拿出来用, 用完再放回连接池内待用, 而不是销毁.
                        * 通常情况我们采用连接池技术, 来共享Connection. 这样就不需要每次都创建和释放连接了, 而是将这些操作都交给连接池操作
                        * 当要获取Connection对象时, 从连接池中拿出一个, 当该Connection使用完毕后, 再放回连接池


```
     连接池(存放Connection对象的池子)
+-------------------------------------+
|                                     |
|  连接1  o---------------o            |
|  连接2  o---------------o            |
|  连接3  o---------------o            |
|  连接4  o---------------o            |
|  连接5  o---------------o            |
|                                     |
+-------------------------------------+



有3个用户使用数据库连接, 从连接池中获取连接
+-------------------------------------+
|                                     |
|                                     |      用户1使用 连接1  o---------------o
|                                     |      用户2使用 连接2  o---------------o
|                                     |      用户3使用 连接3  o---------------o
|  连接4  o---------------o            |
|  连接5  o---------------o            |
|                                     |
+-------------------------------------+


用完之后再将Connection放回连接池
+-------------------------------------+
|                                     |
|  连接1  o---------------o            |
|  连接2  o---------------o            |
|  连接3  o---------------o            |
|  连接4  o---------------o            |
|  连接5  o---------------o            |
|                                     |
+-------------------------------------+
```


## 连接池概念规范, DataSource接口

* `javax.sql.DataSource`接口: 数据源. SUN公司提供的用于让不同厂商实现各自的数据库连接池
        * 作用: 通过切换不同的实现类, 可以切换不同的连接池
* 常见连接池
        * `DBCP`: Apache公司Commons项目的一个组件, 是开源项目, 是Tomcat内置连接池
        * `C3P0`: 开源连接池, Hibernate和Spring都在使用
        * `Druid`: 阿里巴巴开发的开源连接池


## DBCP连接池介绍

* `DBCP`: DataBase Connection Pool, 数据库连接池


## 导入jar包

* 所需jar包
        * DBCP连接池所需jar包:
                * `commons-dbcp-x.x.jar`: DBCP核心
                * `commons-pool-x.x.x.jar`: DBCP依赖的连接池功能
        * DbUtils工具类
                * `commons-dbutils-x.x.jar`: DbUtils工具类
        * MySQL数据库驱动
                * `mysql-connecter-java-x.x.x-bin.jar`: 数据库驱动


* 3分钟练习
        * 创建项目s3-day04
        * 导入4个相关jar包, 添加到Build Path






---







## BasicDataSource类的使用

* DBCP的`BasicDataSource`类: 实现了`javax.sql.DataSource`接口
        * 构造方法:
                * `BasicDataSource()`: 创建对象
        * 成员方法:
                * `void setDriverClassName(String name)`: 设置驱动类名
                * `void setUrl(String url)`: 设置连接地址
                * `void setUsername(String username)`: 设置用户名
                * `void setPassword(String password)`: 设置密码
                * `Connection getConnection()`: 获取连接对象



## BasicDataSource类的常见配置

* `BasicDataSource`类的配置方法
        * 成员方法:
                * `void setInitialSize(int size)`: 初始连接数. 连接池中最初的连接数
                * `void setMaxActive(int n)`: 最大连接数量. 程序能够连接数据库的最大连接数量
                * `void setMinIdle(int n)`: 最小空闲连接. 连接池中允许存在的最小空闲数量, 如果小于该数量, 则会创建新的连接, 直到满足该最小空闲连接数
                * `void setMaxIdle(int n)`: 最大空闲连接. 连接池中允许存在的最大空闲数量, 如果超出该数量, 则超出的空闲连接会被真实关闭
               

```
空闲: 在连接池中, 未被真正使用的连接, 称为空闲连接
活跃: 正在被使用的连接, 称为活跃连接

初始连接数: 10
最大连接数: 8
最大空闲数: 5
最小空闲数: 1

创建连接池, 初始连接数: 10
+---------------+
|  ||||| |||||  |
+---------------+

如果长时间没有使用连接, 则因为最大空闲数要求是5个连接, 所以池中会真正关闭多余的连接.
目前池中有10个, 需要关闭5个
+---------------+
|  |||||        |
+---------------+

现在有5个用户要执行查询, 需要使用连接对象, 池中的5个都会被使用.
+---------------+
|               |  |||||
+---------------+

由于最小空闲数要求为1, 而目前池中没有连接了, 所以连接池会创建1个新的连接放入池中
+---------------+
|  |            |  |||||
+---------------+

又有4个用户需要执行查询, 所以从空闲连接中拿出使用.
但因为最大活跃连接数是8, 而目前已经有5个连接在活跃, 所以只能满足3个用户使用连接.
剩下1个用户, 虽然连接池中已经为了满足最小空闲数又补充了1个连接, 但仍然不能使用
+---------------+
|  |            |  ||||| |||
+---------------+

当8个用户使用完毕8个连接后, 这8个连接不会销毁, 而是放回连接池等待再次使用.
此时池中总共有9个连接
+---------------+
|  ||||| ||||   |
+---------------+

而最大空闲数要求为5个, 那么多余的4个连接会被连接池真正关闭销毁, 释放资源. 从而最后剩下5个空闲连接
+---------------+
|  |||||        |
+---------------+

连接池的好处:
        如果连接池中的连接数满足使用需求, 则可以减少建立TCP连接的时间, 提高效率
        如果某些情况下, 连接池中的连接数不够使用, 连接池也会自动补充连接.
        如果极端情况下, 对与连接的需求数量特别大, 也可以靠最大活跃连接数来限制创建的连接数量, 从而避免过度消耗内存
        当连接使用高峰度过后, 连接池还会自动销毁多余的连接, 释放内存
```


## 优化JDBCUtils: 修改为获取DataSource

* 8分钟练习: 优化工具类
        * 在s3-day04项目中, 建包com.itheima.utils
        * 将昨天的JDBCUtils工具类复制进来, 修改工具类
                * 定义静态BasicDataSource对象并初始化
                * 在静态代码块中使用BasicDataSource对象进行配置
                        * 删除原有注册驱动和获取连接的代码
                        * 使用BasicDataSource对象设置连接信息(驱动类, url, 用户名, 密码)
                        * 使用BasicDataSource对象配置连接池
                                * 初始连接10
                                * 最大连接8
                                * 最大空闲5
                                * 最小空闲1
                * 删除`getConnection()`方法, 添加`DataSource getDataSource()`方法.
                * 目前在工具类中我们不再需要获取单个连接对象, 只需要获取数据源对象

```java
/*
JDBC工具类: v3.0
可以读取配置文件
使用DBCP连接池技术
*/
public class JDBCUtils {

        // 声明静态变量
        private static BasicDataSource dataSource = new BasicDataSource();
        private static String driverClass;
        private static String url;
        private static String username;
        private static String password;
       
        // 静态代码块进行初始化
        static {
                try {
                        // 读取配置文件
                        readConfig();
                        // 设置连接池必选4个配置
                        dataSource.setDriverClassName(driverClass);
                        dataSource.setUrl(url);
                        dataSource.setUsername(username);
                        dataSource.setPassword(password);
                        // 设置连接池可选4个配置
                        dataSource.setInitialSize(10);  // 初始连接数
                        dataSource.setMaxActive(8);     // 最大活跃连接数
                        dataSource.setMaxIdle(5);       // 最大空闲连接数
                        dataSource.setMinIdle(1);       // 最小空闲连接数
                } catch (Exception e) {
                        e.printStackTrace();
                        throw new RuntimeException("数据库连接失败");
                }
        }
       
        // 私有构造方法
        private JDBCUtils() {}
       
        // 读取配置文件方法
        private static void readConfig() throws Exception {
                // 使用类加载器加载bin目录下的配置文件, 获取输入流
                InputStream in = JDBCUtils.class.getClassLoader().getResourceAsStream("database.properties");
                // 创建Properties对象
                Properties properties = new Properties();
                // 加载数据
                properties.load(in);
                // 通过键获取值
                driverClass = properties.getProperty("driverClass");
                url = properties.getProperty("url");
                username = properties.getProperty("username");
                password = properties.getProperty("password");
        }
       
        // 获取连接池数据源对象
        public static DataSource getDataSource() {
                return dataSource;
        }
}
```


## 测试JDBCUtils工具类

* `QueryRunner`
        * `QueryRunner QueryRunner(DataSource ds)`: 在创建对象时传入DataSource, 免去创建连接


* 5分钟练习:
        * 使用SQLyog插入测试数据
        * 建包com.itheima.practice_01
        * 建类Test
                * 定义静态的QueryRunner对象, 在初始化时使用QueryRunner(DataSource ds)构造方法, 并利用JDBCUtils获取DataSource对象传入
                * 定义`insert()`方法
                        * 在其中使用queryRunner对象插入数据: "水果", 100.12, "刚上市的核桃"
                        * 在main方法中测试
                * 定义`select()`方法
                        * 在其中使用queryRunner对象查询所有列的记录: 使用ArrayListHandler
                        * 在main方法中测试

```java
public class Test {

        // 定义静态变量
        private static QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource());
       
        public static void main(String[] args) {
//                insert();
                select();
        }
       
        // 查询测试
        private static void select() {
                try {
                        // 定义sql
                        String sql = "select * from sort";
                        // 执行语句
                        List<Object[]> list = queryRunner.query(sql, new ArrayListHandler());
                        for (Object[] objects : list) {
                                for (Object object : objects) {
                                        System.out.print(object + "\t");
                                }
                                System.out.println();
                        }
                } catch (SQLException e) {
                        e.printStackTrace();
                        throw new RuntimeException("查询数据失败");
                }
        }
       
        // 插入测试
        private static void insert() {
                try {
                        // 定义sql语句
                        String sql = "insert into sort (sname, sprice, sdesc) values (?, ?, ?);";
                        // 定义参数
                        Object[] params = {"水果", 100.12, "刚上市的核桃"};
                        // 执行语句
                        int rows = queryRunner.update(sql, params);
                        System.out.println(rows);
                } catch (Exception e) {
                        e.printStackTrace();
                        throw new RuntimeException("插入数据失败");
                }
        }
}
```






---







## 登录注册案例: 需求分析

* 注册:
        * 将用户要注册的用户名先查询库里是否存在(SELECT)
                * 如果存在: 用户名已存在, 请更换
                * 如果不存在: 创建新用户(INSERT INTO)
* 登录
        * 将用户的用户名密码到库里查询是否存在(SELECT)
                * 用户名和密码存在且匹配: 登录成功
                * 用户名不存在或密码不匹配: 登录失败




## 登录注册案例: 创建数据库和数据表

* 假数据: 这个词在工作中经常说, 一般用于测试功能, 向数据库中写入一些自己模拟的数据


```sql
# day04 DBCP连接池, 登录案例

-- 创建登录注册测试数据----------------------------------

# 创建数据库
CREATE DATABASE day04_user;

# 使用新数据库
USE day04_user;

# 创建用户表
CREATE TABLE users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50) NOT NULL,
  password VARCHAR(50) NOT NULL
);

# 初始化数据
INSERT INTO users
(username, password)
VALUES
('a', '1'),
('b', '2');
```


## 登录注册案例: 开发过程分析

* 本案例的开发顺序:
        1. 先编写数据库操作
        2. 然后写界面



## 登录注册案例: 创建包, 类

* DAO:
        * Data Access Object, 数据访问对象, 专门用于操作数据库



* 5分钟练习:
        * 创建新项目s3-day04-login
        * 创建lib目录, 放入4个jar包
        * 建包建类:
                * com.itheima.login.dao    用于存放操作数据库的类
                        * UsersDao.java
                * com.itheima.login.ui     用于存放界面相关的类
                        * UserOperator.java
                * com.itheima.login.utils  用于存放工具类
                        * JDBCUtils: 复制今天编写的使用连接池的工具类
        * 复制配置文件, 修改其中连接的库名






---=









## 登录注册案例: 登录功能实现


* 10分钟练习: 编写dao
        * 在UserDao中创建QueryRunner成员变量, 使用JDBCUtils获取数据源来初始化
        * 定义成员方法`public boolean login(String username, String password)`, 用于执行登录操作
                * 在其中执行判断登录的sql语句: SELECT * FROM users WHERE username = ? AND password = ?;
                * 传入参数, 使用queryRunner的查询方法, 传入ArrayHandler
                * 通过返回的数组长度判断是否有值
                        * 有则返回true, 表示登录成功
                        * 无则返回false, 表示登录失败

```java
/*
操作User表的dao类
*/
public class UsersDao {

        // 定义QueryRunner对象
        private QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource());
       
        // 登录方法
        public boolean login(String username, String password) {
                try {
                        // 定义SQL语句
                        String sql = "SELECT * FROM users WHERE username=? AND password=?;";
                        // 定义参数
                        Object[] params = {username, password};
                        // 执行语句
                        Object[] result = queryRunner.query(sql, new ArrayHandler(), params);
                        // 判断是否有结果
                        if (result.length == 0)
                                // 没有查到用户信息, 不能登录
                                return false;
                        // 如果能查到用户信息, 则允许登录
                        return true;
                } catch (SQLException e) {
                        e.printStackTrace();
                        throw new RuntimeException("登录dao操作失败");
                }
        }
}
```


## 登录注册案例: 注册功能实现

* 10分钟练习: 编写注册方法
        * 继续在UserDao中定义一个方法: `public boolean register(String username, String password)`, 用于执行注册操作
                * 在其中先执行判断用户是否存在的sql语句:
                        * SELECT username FROM users WHERE username = ?;
                        * 使用ScalarHandler获取String类型的username返回值
                * 通过判断username返回值是否为null判断是否有用户
                        * 如果不为null, 返回false, 表示用户名已被占用
                        * 如果为null, 则可以注册
                                * 执行插入sql将用户信息录入数据库: INSERT INTO users (username, password) VALUES (?, ?);
                                * 直接返回true

```java
// 注册方法
public boolean register(String username, String password) {
        try {
                // 定义查询用户名是否被占用的SQL语句
                String sql = "SELECT username FROM users WHERE username=?";
                // 执行语句
                String user = queryRunner.query(sql, new ScalarHandler<String>(), username);
                // 判断是否能够查到用户名
                if (user != null)
                        // 能查到相同的用户名, 说明已被占用, 不能注册
                        return false;
                // 如果能执行到这里, 说明用户名没有被占用, 直接插入数据库
                // 定义插入用户名和密码的sql
                sql = "INSERT INTO users (username, password) VALUES (?, ?);";
                // 定义参数
                Object[] params = {username, password};
                // 执行语句
                queryRunner.update(sql, params);
                // 直接返回注册成功的结果
                return true;
        } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException("注册dao操作失败");
        }
}
```






---








## 登录注册案例: 登录功能测试

* 10分钟练习: 完成登录界面, 并测试
        * 在UserOperator类中
                * 定义静态成员变量Scanner sc
                * 定义一个静态的String chooser()方法
                        * 在其中使用Scanner对象获取用户输入
                        * 使用syso打印提示:
                                * 欢迎光临, 请选择功能
                                * 1.登录  2.注册  3.退出
                        * 获取输入后返回输入的字符串
                * 在main方法中
                        * 调用chooser()方法获取输入
                        * 通过switch判断输入的数字
                                * 1为登录
                                        * syso提示当前为登录功能
                                        * 提示请输入用户名, 获取输入
                                        * 提示请输入密码, 获取输入
                                        * 创建UserDao对象, 调用login方法执行登录, 根据方法返回值判断是否登录成功, syso提示
                                * 2为注册
                                * 3为退出
                                        * 提示再见
                                        * System.exit(0)
                                * 默认提示没有此功能

```java
/*
用户操作界面
*/
public class UserOperater {

        // 定义静态变量Scanner
        private static Scanner scanner = new Scanner(System.in);
       
        public static void main(String[] args) {
                // 调用输入选项的方法
                String number = chooser();
                // 根据输入选项调用不同的功能
                switch (number) {
                case "1":  // 登录
                        // 提示当前功能
                        System.out.println("当前是[登录]功能, 请输入用户名");
                        String username = scanner.next();
                        System.out.println("请输入密码:");
                        String password = scanner.next();
                        // 创建UsersDao对象, 调用登录方法
                        UsersDao dao = new UsersDao();
                        boolean canLogin = dao.login(username, password);
                        // 判断结果, 给出提示
                        if (canLogin) {
                                System.out.println("登录成功");
                        } else {
                                System.out.println("登录失败");
                        }
                        break;
                case "2":  // 注册
                       
                        break;
                case "3":  // 退出
                        System.out.println("再见");
                        System.exit(0);
                        break;
                default:
                        // 提示选项错误
                        System.out.println("抱歉, 没有该选项");
                        break;
                }
        }
       
        // 获取键盘输入选项
        private static String chooser() {
                // 打印菜单
                System.out.println("---===欢迎光临===---");
                System.out.println("请输入您的选项:");
                System.out.println("1.登录 2.注册 3.退出");
                // 获取键盘录入的选项, 并返回
                return scanner.next();
        }
}
```

## 登录注册案例: 注册功能测试

* 10分钟练习: 完成注册界面, 测试
        * 将UserDao对象提取为静态成员变量
        * 在main方法中
                * 继续编写注册功能的调用
                        * 2为注册
                                * syso提示当前为登录功能
                                * 提示请输入用户名, 获取输入
                                * 提示请输入密码, 获取输入
                                * 使用UserDao对象, 调用signup方法执行注册, 根据方法返回值判断是否注册成功
                                        * 如果注册成功, 提示注册成功
                                        * 如果注册失败, 提示用户名已被占用, 请更换用户名
                * 将获取用户选项和switch判断的代码都放入while(true)循环中
        * 测试程序


```java
/*
用户操作界面
*/
public class UserOperater {

        // 定义静态变量Scanner
        private static Scanner scanner = new Scanner(System.in);
       
        public static void main(String[] args) {
                // 创建dao对象
                UsersDao dao = new UsersDao();
               
                while (true) {
                        // 调用输入选项的方法
                        String number = chooser();
                        // 根据输入选项调用不同的功能
                        switch (number) {
                        case "1":  // 登录
                                // 提示当前功能
                                System.out.println("当前是[登录]功能, 请输入用户名");
                                String username = scanner.next();
                                System.out.println("请输入密码:");
                                String password = scanner.next();
                                //调用登录方法
                                boolean canLogin = dao.login(username, password);
                                // 判断结果, 给出提示
                                if (canLogin) {
                                        System.out.println("登录成功");
                                } else {
                                        System.out.println("登录失败");
                                }
                                break;
                        case "2":  // 注册
                                // 提示功能
                                System.out.println("当前是注册功能, 请输入用户名:");
                                username = scanner.next();
                                System.out.println("请输入密码:");
                                password = scanner.next();
                                // 调用dao的注册方法
                                boolean success = dao.register(username, password);
                                // 根据结果给出提示
                                if (success) {
                                        System.out.println("注册成功");
                                } else {
                                        System.out.println("当前用户名已被占用, 请尝试" + username + "_123");
                                }
                                break;
                        case "3":  // 退出
                                System.out.println("再见");
                                System.exit(0);
                                break;
                        default:
                                // 提示选项错误
                                System.out.println("抱歉, 没有该选项");
                                break;
                        }
                }
        }
       
        // 获取键盘输入选项
        private static String chooser() {
                // 打印菜单
                System.out.println("---===欢迎光临===---");
                System.out.println("请输入您的选项:");
                System.out.println("1.登录 2.注册 3.退出");
                // 获取键盘录入的选项, 并返回
                return scanner.next();
        }
}
```


-------------------------

## 今日总结

* DBCP连接池
        * Apache commons项目中的一个组件
        * 作用: 提供连接池, 替代连接. 连接池可以存放多个连接, 反复使用, 而不用每次创建销毁, 提高效率, 节省资源
        * 普通连接的弊端: 获取连接, 释放资源太消耗资源.
        * Sun公司提供了接口: `javax.sql.DataSource`接口: 连接池规范
        * 连接池的实现:
                * DBCP
                * C3P0
                * Druid
        * DBCP
                * 导包
                        * `commons-dbcp-x.x.jar`
                        * `commons-pool-x.x.x.jar`
                * `BasicDataSource`: 是DataSource接口的实现类
                        * 必选
                                * `BasicDataSource BasicDataSource()`: 创建对象
                                * `void setDriverClassName(String name)`: 设置驱动类名
                                * `void setUrl(String url)`: 设置连接地址
                                * `void setUsername(String username)`: 设置用户名
                                * `void setPassword(String password)`: 设置密码
                                * `Connection getConnection()`: 获取连接对象
                        * 可选
                                * `void setMaxActive(int n)`: 最大连接数量. 程序能够连接数据库的最大连接数量
                                * `void setMinIdle(int n)`: 最小空闲连接. 连接池中允许存在的最小空闲数量, 如果小于该数量, 则会创建新的连接, 直到满足该最小空闲连接数
                                * `void setMaxIdle(int n)`: 最大空闲连接. 连接池中允许存在的最大空闲数量, 如果超出该数量, 则超出的空闲连接会被真实关闭
                                * `void setInitialSize(int size)`: 初始化连接数. 连接池中最初的连接数
                * 改写JDBCUtils
                        * 将注册驱动, 获取连接的代码修改为必选4项
                        * 将getConnection()方法替换为getDataSource()方法
* 用户登录注册案例
        * 理解如何分析需求, 将其转换为不同的步骤
        * 注册:
                * 先检查用户名是否存在(SELECT)
                        * 如果存在: 注册失败, 提示更换用户名
                        * 如果不存在: 直接注册(INSERT INTO)
        * 登录
                * 查询用户表中是否有匹配的用户名和密码(SELECT)
                        * 如果存在: 用户已经注册且密码匹配, 登录成功
                        * 如果用户名不存在: 用户没有注册或, 登录失败
                        * 如果用户名存在, 密码不匹配: 登录失败
        * dao: 操作数据库的类
                * 类名: `XxxDao.java`



# day05 管家婆项目A


## 项目训练目标介绍

* 训练目标
        * 熟练使用三层设计思想和相互调用的实现
                * View层: 视图层, 可视界面
                * Service层: 业务层, 各种功能
                * Dao层: 持久层, 操作数据库
        * 熟练使用DBUtils操作数据库进行增删改查
        * 了解公司项目开发流程, 掌握需求分析, 设计, 功能代码实现
* 扩展: 分层思想
        * 软件架构分层模型(即Java代码这部分可以再按照如下分层)
                * MVC架构: Model, View, Controller



## 项目中的功能模块

```
+-------------------------------+
|        管家婆家庭记账软件       |
+---------------+---------------+
                |
  +------+------+------+------+
  |      |      |      |      |
+-+-+  +-+-+  +-+-+  +-+-+  +-+-+
|查 |  |多 |  |添 |   |编 |  |删 |
|询 |  |条 |  |加 |   |辑 |  |除 |
|账 |  |件 |  |账 |   |账 |  |账 |
|务 |  |查 |  |务 |   |务 |  |务 |
|   |  |询 |  |   |  |   |  |   |
+---+  +---+  +---+  +---+  +---+
```


## 技术的选择和jar包

* 管家婆项目所用jar包
        * DbUtils工具类
                * `commons-dbutils-1.4.jar`: JDBC工具类
        * DBCP连接池
                * `commons-dbcp-1.4.jar`: DBCP连接池
                * `commons-pool-1.3.jar`: DBCP连接池依赖
        * MySQL驱动
                * `mysql-connnector-java-5.1.28-bin.jar`: MySQL数据库驱动
* 技术选型:
        * 当需要实现一种功能时, 有很多技术可以实现, 我们要考虑各种因素(开发成本, 开发速度, 可维护性, 该技术的适用场景等), 或是做一些demo, 最终确定一种技术作为实现手段. 这一过程就是技术选型
        * 比如用Java操作数据库, 有多种方式可以实现, 根据各种因素来选择:
                * JDBC原生方式
                        * 开发成本: Java原生支持, 不必依赖于第三方, 免费, 且已经会用, 无成本
                        * 开发速度: 代码繁琐, 冗余性高, 开发速度慢
                        * 可维护性: 代码繁琐, 不易于维护
                        * 适用场景: 简单的需求
                * 自己封装工具类方式
                        * 开发成本: Java原生支持, 不必依赖于第三方, 免费, 且已经会用, 无成本
                        * 开发速度: 减少了原生方式的冗余代码, 但执行SQL语句的部分和查询结果的处理封装仍然繁琐
                        * 可维护性: 代码较为繁琐, 不易于维护
                        * 适用场景: 简单的需求
                * 使用第三方类库, 如DbUtils, DBCP等
                        * 开发成本: 第三方提供的开源jar包, 可随时下载使用, 免费, 且已经会用, 成本较小
                        * 开发速度: 数据库连接释放, 查询结果封装都已经简化, 开发速度快
                        * 可维护性: 代码简洁, 易于维护
                        * 适用场景: 简单的需求
                * 使用第三方框架, 如Hibernate, MyBatis等
                        * 开发成本: 目前不会用, 需要时间学习, 且不知道会遇到什么问题, 学习成本较高
                        * 开发速度: 先要学习才能开发, 肯定很慢
                        * 可维护性: 未知
                        * 适用场景: 未知
        * 根据老板提出的需求, 管家婆项目"明天中午就要上线", 所以最终我们技术选型的结果为: DbUtils + DBCP连接池
* 第三方开源项目
        * [Github](www.github.com)
        * 开源是Open Source, 开放源代码的意思, 表示源代码是公开的, 谁都可以查看其实现原理, 并且根据自己的需要去修改完善, 但一般都有License(使用许可)来约束使用者的行为(是否能用于商业目的, 用于商业目的的要求等)
* 实际开发中常有的3种环境
        * 开发环境: 开发人员用于开发项目的环境
                * 一般在开发人员的本机电脑上
                * 可能会有一些假数据用于开发自测
                * 开发完成的功能会先在开发环境中, 由开发人员自测(一般是单元测试, 接口测试, 集成测试), 自测通过后, 发布到测试环境, 由测试人员进行测试
        * 测试环境: 测试人员用于测试项目功能的环境. 与生产环境一致的一套单独的环境. 测试人员会以真实使用方式来测试项目是否有bug. 测试通过的功能反馈给开发人员, 开发人员才能发布到生产环境(俗称: 上线)
        * 生产环境: 真实用户使用的实际运行环境
                * 一般不会停止运行
                * 一般在凌晨上线, 因为新项目部署到服务器后需要重启服务器, 中断对客户的服务, 凌晨时用户量少, 不影响业务
* 测试的分类
        * 按测试规模分
                * 单元测试: 测试方法
                * 接口测试: 测试API接口
                * 集成测试: 将多个功能组合起来, 看看组合后是否有问题
        * 从测试方式分
                * 黑盒测试: 也叫功能测试, 把程序看成一个打不开的盒子, 里面黑的看不见. 在不了解源代码逻辑的情况下, 通过用户界面测试功能是否有异常(一般是测试人员)
                * 白盒测试: 检查源代码的实现逻辑, 测试代码是否会导致功能有异常(一般是开发人员)
        * 产品上线后, 新功能小部分试用查看反馈
                * 灰度测试: 70%用户使用1.0.0版本, 30%用户使用1.1.0版本
* 常用术语:
        * 联调: 系统各部分开发人员(前端, 后端)一起调试功能, 查看功能是否可用, 是否有bug



## 项目中的工具类

* JDBCUtils
        * 加载配置文件
        * 使用连接池



## 数据表的设计

* 表设计
        * 设计有多少张表
        * 表有哪些字段
        * 表与表之间有什么关系
* 主键和外键
        * 主键PRIMARY KEY, 一般使用不可重复的列, 如id
        * 外键FOREIGN KEY, 用于关联另外一个表的主键
        * 主键所在的表称为`主表`
        * 外键所在的表称为`从表`
* 主表和从表的数据要求
        * 主表有的数据, 从表可以有, 可以没有
        * 主表没有的数据, 从表肯定没有
* 级联操作
        * 主表中的记录更新或删除, 从表中对应的也应该更新或删除
* 表与表的3种关系
        * 一对一: 一个学生只能坐一个座位, 一个座位也只能坐一个学生
        * 一对多: 一个学生只能属于一个班, 但一个班可以有多个学生
        * 多对多: 一个校区可以有多个老师, 一个老师也可以在多个校区教学





---






## 创建数据库, 表, 测试数据

* 库名: gjp: 管家婆
        * 表名: gjp_zhangwu: 管家婆账务
                * zwid: 账务id, 主键, 自增
                * flname: 分类名称, 字符串
                * money: 金额, 小数
                * zhanghu: 账户消费方式, 字符串
                * createtime: 发生日期, 日期
                * description: 账务描述, 字符串
* 注意保留SQL文件


```sql
# day05 管家婆项目

-- 创建管家婆项目测试数据----------------------------------

# 创建数据库
CREATE DATABASE gjp;

# 使用新数据库
USE gjp;

# 创建表
CREATE TABLE gjp_zhangwu (
  zwid INT PRIMARY KEY AUTO_INCREMENT,    # 账务id, 主键
  flname VARCHAR(200),                    # 分类名称
  money DOUBLE,                           # 金额
  zhanghu VARCHAR(100),                   # 消费方式
  createtime DATE,                        # 消费日期
  description VARCHAR(1000)               # 费用描述
);

# 初始化数据(这就是开发用的假数据)
INSERT INTO gjp_zhangwu
(zwid, flname, money, zhanghu, createtime, description)
VALUES
(1,'吃饭支出',247,'交通银行','2016-03-02','家庭聚餐'),
(2,'工资收入',12345,'现金','2016-03-15','开工资了'),
(3,'服装支出',1998,'现金','2016-04-02','买衣服'),
(4,'吃饭支出',325,'现金','2016-06-18','朋友聚餐'),
(5,'股票收入',8000,'工商银行','2016-10-28','股票大涨'),
(6,'股票收入',5000,'工商银行','2016-10-28','股票又大涨'),
(7,'工资收入',5000,'交通银行','2016-10-28','又开工资了'),
(8,'礼金支出',5000,'现金','2016-10-28','朋友结婚'),
(9,'其他支出',1560,'现金','2016-10-29','丢钱了'),
(10,'交通支出',2300,'交通银行','2016-10-29','油价还在涨啊'),
(11,'吃饭支出',1000,'工商银行','2016-10-29','又吃饭'),
(12,'工资收入',1000,'现金','2016-10-30','开资'),
(13,'交通支出',2000,'现金','2016-10-30','机票好贵'),
(14,'工资收入',5000,'现金','2016-10-30','又开资');

# 查看数据
SELECT * FROM gjp_zhangwu;
```



## 项目中的分层设计

* 为什么要分层
        * 解耦
        * 便于维护
* 用包来实现分层

```
分层交互流程图

o------o  Input  +------+  data  +------------+   call   +---------+   call   +-----+  SQL   +----+
|      | ------> |      | -----> |            | -------> |         | -------> |     | -----> |    |
| User |         | View |        | Controller |          | Service |          | Dao |        | DB |
|      | <------ |      | <----- |            | <------- |         | <------- |     | <----- |    |
o------o   show  +------+  data  +------------+   data   +---------+   data   +-----+  data  +----+
```

* 项目包名设计
        * `view`: 视图层, 存放界面相关代码
        * `controller`: 控制层, 用于获取界面层的数据, 或向界面设置数据. 功能实现要交给业务层
        * `service`: 业务层, 实现功能, 与controller控制层和数据访问层dao交互
        * `dao`: 数据访问层, 用于操作数据库表的数据
        * `db`: 数据库
        * `domain`: 也有用entity, bean命名的, 存放所有的JavaBean
        * `tools`: (一般包名用util)工具包, 存放各种工具类
        * `test`: (一般不把测试代码放在包中, 而是项目中与src平级的test目录中)测试包, 用于存放单元测试代码
* 注意: 不能跨层调用, 否则分层就没有意义了


## 创建项目: 分层, 导入jar包

* 项目名称: gjp

```
项目包结构图

gjp                             # 项目名称
    |_ src
    |    |_ com.itheima.gjp     # 包名
    |        |_ app             # 程序入口
    |        |_ controller      # 控制层
    |        |_ dao             # 数据访问层
    |        |_ domain          # 实体类
    |        |_ service         # 业务层
    |        |_ tools           # 工具类
    |        |_ view            # 视图层
    |_ lib
        |_ commons-dbcp-1.4.jar
        |_ commons-dbutils-1.6.jar
        |_ commons-pool-1.5.6.jar
        |_ mysql-connector-java-5.1.28-bin.jar
```


* 10分钟练习:
        * 创建项目gjp
        * 创建包结构, 导入4个jar包






---







## 创建domain中的JavaBean

* JavaBean写什么类名? 写什么属性?
        * 类名和表名一致
        * 属性名和列名一致


* 10分钟:
        * 在`domain`包下创建`Zhangwu`实体类
                * 根据gjp_zhangwu表结构定义Java类
                * 提供一个无参构造和一个全参构造
                * 重写toString方法

```java
public class Zhangwu {

        private int zwid;    // 账务id, 主键
        private String flname;    // 分类名称
        private double money;    // 金额
        private String zhanghu;    // 消费方式
        private String createtime;    // 消费日期
        private String description;    //费用描述
       
        public Zhangwu() {
                super();
        }

        public Zhangwu(int zwid, String flname, double money, String zhanghu, String createtime, String description) {
                super();
                this.zwid = zwid;
                this.flname = flname;
                this.money = money;
                this.zhanghu = zhanghu;
                this.createtime = createtime;
                this.description = description;
        }

        public int getZwid() {
                return zwid;
        }

        public void setZwid(int zwid) {
                this.zwid = zwid;
        }

        public String getFlname() {
                return flname;
        }

        public void setFlname(String flname) {
                this.flname = flname;
        }

        public double getMoney() {
                return money;
        }

        public void setMoney(double money) {
                this.money = money;
        }

        public String getZhanghu() {
                return zhanghu;
        }

        public void setZhanghu(String zhanghu) {
                this.zhanghu = zhanghu;
        }

        public String getCreatetime() {
                return createtime;
        }

        public void setCreatetime(String createtime) {
                this.createtime = createtime;
        }

        public String getDescription() {
                return description;
        }

        public void setDescription(String description) {
                this.description = description;
        }

        @Override
        public String toString() {
                return "Zhangwu [zwid=" + zwid + ", flname=" + flname + ", money=" + money + ", zhanghu=" + zhanghu
                                + ", createtime=" + createtime + ", description=" + description + "]";
        }
}
```


## 创建JDBCUtils工具类

* 5分钟: 在`tools`包下创建工具类`JDBCUtils`

```java

```




# 创建其他包中的类

* 10分钟:
        * 在`app`包中创建`MainApp.java`类, 并定义`main()`方法: 用于程序入口
        * 在`dao`包下创建`ZhangwuDao.java`类: 用于实现对账务表的增删改查
                * 创建一个`QueryRunner`类的成员变量(非静态)
        * 在`service`包下创建`ZhangwuService.java`类: 用于接收controller层的计算, 并传递给dao层
                * 创建一个`ZhangwuDao`类的成员变量(非静态)
        * 在`controller`包下创建`ZhangwuController.java`类: 用于接收视图层数据, 传递给service
                * 创建一个`ZhangwuService`类的成员变量(非静态)
        * 在`view`包下创建`MainView.java`类: 用于显示界面
                * 创建一个`ZhangwuController`类的成员变量(非静态)


* MainApp.java

```java
/*
程序运行入口
*/
public class MainApp {

        public static void main(String[] args) {
                // 启动界面
               
        }
}
```

* ZhangwuDao.java

```java
/*
操作Zhangwu表的dao
*/
public class ZhangwuDao {

        // 定义queryrunner
        private QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource());
}
```

* ZhangwuService.java

```java
/*
业务层
调用dao操作数据
向controller返回数据
*/
public class ZhangwuService {

        // 定义dao对象
        private ZhangwuDao dao = new ZhangwuDao();
}
```

* ZhangwuController.java

```java
/*
控制层
接收view传递的数据, 向service传递数据
向view返回结果
*/
public class ZhangwuController {

        // 定义Service对象
        private ZhangwuService service = new ZhangwuService();
}
```

* MainView.java

```java
/*
视图层
获取用户输入和显示信息
*/
public class MainView {

        // 定义Controller对象
        private ZhangwuController controller = new ZhangwuController();
}
```





---=






## 实现用户界面菜单

* 界面在View层写
* 在`MainView`类中定义`run()`方法, 该方法需要
        * 显示功能菜单界面
        * 接收键盘输入的功能选项
        * 根据选项值, 调用对应的功能方法
* 在`MainApp`类的`main()`方法中, 调用`run()`方法, 让程序执行, 显示功能界面菜单


* 10分钟:
        * 在`MainView`类中定义`run()`方法
                * 创建Scanner对象
                * 在while死循环中编写界面
                        * 首先输出标题
                                * "----------------管家婆家庭记账软件----------------"
                                * "1.添加账务  2.删除账务  3.编辑账务  4.查询账务  5.退出系统"
                                * "请输入要操作的功能序号[1-5]:"
                        * 使用scanner对象获取用户输入
                        * 使用switch根据用户输入分别写case
                                * 实现退出系统, 提示再见, 并`System.exit(0)`
                                * default提示"输入错误"
        * 在`MainApp.java`的main方法中, 创建MainView对象, 并调用run方法测试


* MainView.java

```java
/*
视图层
获取用户输入和显示信息
*/
public class MainView {

        // 定义Controller对象
        private ZhangwuController controller = new ZhangwuController();
       
        // 启动界面显示
        public void run() {
                // 创建键盘录入
                Scanner scanner = new Scanner(System.in);
                // 循环菜单
                while (true) {
                        // 打印菜单
                        System.out.println("----------------管家婆家庭记账软件----------------");
                        System.out.println("1.添加账务  2.删除账务  3.编辑账务  4.查询账务  5.退出系统");
                        System.out.println("请输入要操作的功能序号[1-5]:");
                        // 获取输入选项
                        int choice = scanner.nextInt();
                        // 根据选项判断调用方法
                        switch (choice) {
                        case 1:  // 添加账务
                               
                                break;
                        case 2:  // 删除账务
                               
                                break;
                        case 3:  // 编辑账务
                               
                                break;
                        case 4:  // 查询账务
                               
                                break;
                        case 5:  // 退出
                                System.out.println("再见");
                                System.exit(0);
                                break;
                        default:
                                System.out.println("您输入的选项有误");
                                break;
                        }
                }
        }
}
```

* MainApp.java

```java
/*
程序运行入口
*/
public class MainApp {

        public static void main(String[] args) {
                // 启动界面
                new MainView().run();
        }
}
```



## 实现查询的界面菜单

* 5分钟
        * 在`MainView`类中:
                * 定义`selectZhangwu()`方法
                        * 打印要查询账务的方式: "1.查询所有  2.条件查询"
                        * 创建Scanner对象, 接收键盘输入, 调用对应方法
                                1. 查询所有
                                2. 条件查询
                        * default提示输入有误
                * 定义`selectAll()`方法, 表示查询所有
                * 定义`select()`方法, 表示条件查询


* MainView.java

```java
// 查询所有账务
private void selectZhangwu() {
        // 显示子菜单
        Scanner scanner = new Scanner(System.in);
        System.out.println("1.查询所有  2.条件查询");
        int choice = scanner.nextInt();
        // 判断选项
        switch (choice) {
        case 1:  // 查询所有
                selectAll();
                break;
        case 2:  // 条件查询
                select();
                break;
        default:
                System.out.println("输入有误");
                break;
        }
}

// 查询所有
private void selectAll() {
        // 调用controller对象的方法查询所有
}

// 条件查询
private void select() {
       
}
```





---






## 查询所有账务: controller, service

* 10分钟
        * 在`ZhangwuController`类中定义方法`void selectAll()`
                * 并在其中调用service对象的`selectAll()`方法
        * 在`ZhangwuService`类中定义方法`List<Zhangwu> selectAll()`
                * 并在其中调用dao对象的`selectAll()`方法
        * 在`ZhangwuDao`类中定义方法`List<Zhangwu> selectAll()`


* MainView.java

```java
// 查询所有
private void selectAll() {
        // 调用controller对象的方法查询所有
        List<Zhangwu> list = controller.selectAll();
}
```

* ZhangwuController.java

```java
// 查询所有账务
public List<Zhangwu> selectAll() {
        return service.selectAll();
}
```

* ZhangwuService.java

```java
// 查询所有账务
public List<Zhangwu> selectAll() {
        return dao.selectAll();
}
```

* ZhangwuDao.java

```java
// 查询所有账务
public List<Zhangwu> selectAll() {
        return null;
}
```


## 查询所有账务: dao

* 10分钟:
        * 编写ZhangwuDao的selectAll方法
                * 查询所有: "SELECT * FROM gjp_zhangwu;"
                * 使用queryRunner结合BeanListHandler查询出List集合, 直接返回该集合
                * 捕获异常, 抛出运行时异常

```java
// 查询所有账务
public List<Zhangwu> selectAll() {
        try {
                // 定义SQL语句
                String sql = "SELECT * FROM zhangwu;";
                // 执行语句
                List<Zhangwu> list = queryRunner.query(sql, new BeanListHandler<>(Zhangwu.class));
                // 返回查询的结果
                return list;
        } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException("查询所有失败");
        }
}
```


## 查询所有账务: view

* 5分钟
        * 在MainView类中定义`void selectAll()`方法
                * 调用ZhangwuController对象的selectAll方法获取List结果
                * 打印表头: "ID\t类别\t账户\t金额\t时间\t\t说明"
                * 遍历集合并打印
                        * 注意对于集合对象是否为null的判断

```java
// 查询所有
private void selectAll() {
        // 调用controller对象的方法查询所有
        List<Zhangwu> list = controller.selectAll();
        // 打印表头
        System.out.println("ID\t类别\t账户\t金额\t时间\t\t说明");
        // 遍历
        for (Zhangwu zhangwu : list) {
                System.out.println(
                                zhangwu.getZwid() + "\t"
                                + zhangwu.getFlname() + "\t"
                                + zhangwu.getMoney() + "\t"
                                + zhangwu.getZhanghu() + "\t"
                                + zhangwu.getCreatetime() + "\t"
                                + zhangwu.getDescription()
                                );
        }
}
```





---







## 条件查询账务实现: View层

* 条件查询主要让用户输入时间范围, 然后按时间查询出过滤后的结果


* 5分钟
        * 在`MainView`中定义`void select()`方法
                * 提示: "> 条件查询: 输入格式为: XXXX-XX-XX"
                * 提示: "请输入开始日期:"
                * 创建Scanner对象, 接收用户开始日期
                * 提示: "请输入结束日期"
                * 使用Scanner对象获取结束日期
                * (下一步准备调用controller的方法)

```java
// 条件查询
private void select() {
        // 创建键盘录入
        Scanner scanner = new Scanner(System.in);
        System.out.println("当前是条件查询, 请输入日期, 格式为YYYY-MM-DD");
        System.out.print("请输入开始日期:");
        String startDate = scanner.next();
        System.out.print("请输入结束日期:");
        String endDate = scanner.next();
        // 调用controller的方法, 传递开始和结束日期
       
}
```


## 条件查询账务实现: controller, service

* 10分钟
        * 在`ZhangwuController`中定义`List<Zhangwu> select(String beginDate, String endDate)`方法
                * 内部调用service对象的同名方法
        * 在`ZhangwuService`中定义`List<Zhangwu> select(String beginDate, String endDate)`方法
                * 内部调用dao对象的同名方法
        * 在`ZhangwuDao`中定义`List<Zhangwu> select(String beginDate, String endDate)`方法
                * 等待实现


* 10分钟:

* ZhangwuController.java

```java
// 条件查询账务
public List<Zhangwu> select(String startDate, String endDate) {
        return service.select(startDate, endDate);
}
```

* ZhangwuService.java

```java
// 条件查询账务
public List<Zhangwu> select(String startDate, String endDate) {
        return dao.select(startDate, endDate);
}
```

* ZhangwuDao.java

```java
// 条件查询账务
public List<Zhangwu> select(String startDate, String endDate) {
        return null;
}
```


## 条件查询账务实现: dao

* 10分钟
        * 编写ZhangwuDao下的`select(String beginDate, String endDate)`方法
                * 按条件查询: "SELECT * FROM gjp_zhangwu WHERE createtime BETWEEN ? AND ?;"
                * 调用queryRunner对象结合BeanListHandler执行查询, 返回List集合
                * 捕获异常, 抛出运行时异常

```java
// 条件查询账务
public List<Zhangwu> select(String startDate, String endDate) {
        try {
                // 定义SQL
                String sql = "SELECT * FROM gjp_zhangwu WHERE createtime BETWEEN ? AND ?;";
                // 定义参数
                Object[] params = {startDate, endDate};
                // 执行语句
                return queryRunner.query(sql, new BeanListHandler<>(Zhangwu.class), params);
        } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException("条件查询失败");
        }
}
```


## 条件查询账务实现: view

* 抽取打印列表方法, 简化代码


* 10分钟:
        * 在`MainView`中定义`void print(List<Zhangwu> list)`方法
                * 先判断list有无数据, 如果没有则提示: 暂无数据, 并结束方法
                * 如果有数据, 则打印表头: "ID\t类别\t账户\t金额\t时间\t\t说明"
                * 遍历集合并打印记录
        * 仍然在MainView中, 补充`select()`方法中的调用controller的代码
                * 调用controller对象的`select(beginDate, endDate)`方法得到List
                * 调用刚才定义的print方法

* MainView.java

```java
// 查询所有
private void selectAll() {
        // 调用controller对象的方法查询所有
        List<Zhangwu> list = controller.selectAll();
        // 打印
        print(list);
}

// 条件查询
private void select() {
        // 创建键盘录入
        Scanner scanner = new Scanner(System.in);
        System.out.println("当前是条件查询, 请输入日期, 格式为YYYY-MM-DD");
        System.out.print("请输入开始日期:");
        String startDate = scanner.next();
        System.out.print("请输入结束日期:");
        String endDate = scanner.next();
        // 调用controller的方法, 传递开始和结束日期
        List<Zhangwu> list = controller.select(startDate, endDate);
        // 打印
        print(list);
}

// 打印数据
private void print(List<Zhangwu> list) {
        if (list.size() == 0) {
                // 提示没有记录
                System.out.println("没有账务信息");
        } else {
                // 打印表头
                System.out.println("ID\t类别\t账户\t金额\t时间\t\t说明");
                // 遍历
                for (Zhangwu zhangwu : list) {
                        System.out.println(
                                        zhangwu.getZwid() + "\t"
                                        + zhangwu.getFlname() + "\t"
                                        + zhangwu.getMoney() + "\t"
                                        + zhangwu.getZhanghu() + "\t"
                                        + zhangwu.getCreatetime() + "\t"
                                        + zhangwu.getDescription()
                                        );
                }
        }
}
```



}
```
                       

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马