JDBC
目标:
- 掌握 JDBC 的 CRUD
- 理解 JDBC 中各个对象的作用
# JDBC 概述
在开始 JavaWeb 课程学习的时候,我们就介绍过,JavaWeb 技术体系包含三部分:网页、JavaWeb、数据库。
- 网页:用来展现数据;
- JavaWeb 程序:用来从数据库获取数据,并处理获取到的数据,然后将数据提供给网页展示;
- 数据库:用来存储和管理数据。
前面我们已经学习 MySQL 的安装和使用:
- MySQL 基础
- DML:对表中的数据进行增、删、改;
- DQL:按照不同的需求对表中的数据进行查询;
- MySQL 高级
- 对数据表施加非空、唯一、主键、默认等约束,用于保证表中数据的正确性、有效性和完整性;
- 学些了如何进行数据库设计,通过外键、约束实现一对一、一对多、多对多等表关系;
- 在单表基础查询的基础上,我们又扩展实现了內连、外联、子查询等多表关系查询;
- 同时,数据的正确、有效和完整,我们介绍了事务机制,并学习了开启、提交、回滚事务等操作。
那接下来,我们就要学习下如何使用 Java 来操作数据库了,实际上就是我们本节要学习的 JDBC。在介绍 JDBC 前,我们先提两个问题:
- 如何用 Java 操作数据库?
- 如何用 Java 操作不同类型的关系型数据库?比如:Oracle、DB2、PostgreSQL 等等
那带着这两个问题我们来了解一下 JDBC。
# JDBC 概念
JDBC:就是使用 Java 语言操作关系型数据库的一套 API,全程 Java DataBase Connectivity,翻译过来就是 Java 数据库连接。
从字面上看,是不是很难理解 JDBC 的概念,什么是 API?API 又能给我们提供什么便利?实际上,我们生活中有很多场景都有借鉴 API 接口的思维。 比如我们经常使用的插排也是由国家定义好标准的,不同电器厂商生产设备的插头如果不满足这个标准就没办法用电,那这个标准就有点类似咱们所说 的 API。
# JDBC 本质
JDBC 与上面的举例类似,它实际上就是由 Sun 公司指定的一套操作数据库的标准接口,在这套标准接口中定义了操作所有关系型数据库的规则,然后由 各个数据库厂商去实现这些接口,这些实现就是各个厂商自己数据库的驱动程序。
# JDBC 优点
介绍完 JDBC,那使用 JDBC 编程有哪些好处就显而易见了:
- 各数据库厂商使用相同的接口,Java 代码不需要针对不同的数据库分别开发;
- 可随时替换底层数据库,访问数据库的 Java 代码基本不变。
使用 JDBC,我们编写操作数据库的代码只需要面向 JDBC 接口,操作关系型数据库只需要导入该数据库的驱动包即可。
上面我们提出了两个问题,第二个问题:如何使用 Java 操作不同类型的数据库我们已经给出了答案,也即是使用 JDBC 接口编程。那接下来我们就通过 一个入门案例来介绍一下:如何使用 Java 通过 JDBC 操作数据库?
# JDBC 快速入门
# 编写代码步骤
首先,我们来试着猜一下使用 Java 代码操作 MySQL 数据库需要几个步骤:
上面我们已经介绍了 JDBC 是 Sun 公司定义的一套操作数据库的标准接口,那我们编写 Java 代码操作数据库也即是使用这套标准接口来插座数据库。不过 大家都清楚,接口是需要有具体实现的,因 Java 的流行以及 Sun 公司的权威,所以 JDBC 接口的实现就由各数据库厂商来实现,并封装打包成他们各自 数据库产品的驱动程序,那么:
- 第一步:引入 MySQL 驱动程序,并通过 Java 代码加载驱动程序;
在驱动程序加载完成后,我们回忆下之前做 SQL 语句练习时,是不是要通过 MySQL 字符命令客户端或者 Navicat 图形化客户端与 MySQL 服务器建立连接? 那通过 Java 代码操作数据库也是一样,因此:
- 第二步:通过驱动获取与 MySQL 服务器的连接;
接下来,连接建立成功了,那就需要我们依据需求编写 SQL 语句了,所以:
- 第三步:编写 SQL 语句;
至此,大家应该都能理解吧?那接下来该怎么办呢?命令客户端或图形化客户端都可以很方便的进行操作,写好 SQL 回车或点击运行即可输出结果, 那 JDBC 应该也得给我一个工具或者入口来执行 SQL 语句吧?大家到现在应该对 Java 面向对象的思想有一定的了解了,实际上 JDBC 也是通过对象来执行 SQL 语句,获取执行结果的。那么:
- 第四步:获取执行 SQl 的对象;
接着:
- 第五步:通过执行 SQL 对象的方法来执行 SQL;
然后:
- 第六步:处理 SQL 执行的结果;
现在我们使用 Java 代码与 MySQL 服务器建立了连接,SQL 成功执行了,接下来是不是整个用 Java 操作数据库的流程就结束了呢?其实,还差一步。 之前在学习了网络编程,那 Java 与 MySQL 服务器建立的连接是不是就是网络连接,既然是网络连接我们是不是就应该进行资源的释放。那最后我们就 应该:
- 第七步:释放资源。
# 实际操作
接下来,我们就按照上面梳理的步骤来一步一步完成 Java 操作数据库的编码实现。
首先,我们在 Idea 中创建一个空项目,通过菜单栏File -> New -> Project...打开创建项目对话框,选择Empty Project;
定义项目的名称,并指定项目位置
对项目进行设置,设置 JDK 版本、编译版本等
创建模块,并指定模块名称以及模块保存位置
项目、模块创建成功,那接下来我们就要进行第一步操作了,引入 MySQL 驱动程序,引入的方式为在我们模块下创建
lib
目录,该目录通常用来存放 从外部引入的 jar 包,然后将 MySQL 驱动程序包放到我们lib
目录下,并将其添加为库文件。下面,我们就开始进行实际的代码编写,首先我们创建一个类,遵循 Javadoc 注释规范完善代码注释,并设定好操作数据库实现步骤,如下图:
- 第一步:加载 MySQL 驱动类
Class.forName("com.mysql.jdbc.Driver');
- 第二步:获取 MySQL 数据库连接
String url = "jdbc:mysql://127.0.0.1:3306/db1"; String username = "root"; String password = "1234"; Connection conn = DriverManager.getConnection(url, username, password);
1
2
3
4第三步:定义 SQL 语句
String sql = "update account set money = 2000 where id = 1";
第四步:获取 SQL 执行对象
Statement stmt = conn.createStatement();
第五步:通过 SQL 执行对象来执行 SQL
int count = stmt.executeUpdate(sql);
第六步:处理 SQL 执行结果
System.out.println(count);
第七步:释放资源
stmt.close(); conn.close();
1
2- 第一步:加载 MySQL 驱动类
最后,所有步骤的代码编写完成后如下,我们运行来看下效果
package com.itheima.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; /** * Jdbc入门案例 * * @author sunyy * @version 0.0.1 * @since 2022.10.18 */ public class JdbcDemo { public static void main(String[] args) throws Exception { // 1. 第一步:加载MySQL驱动类 Class.forName("com.mysql.jdbc.Driver"); // 2. 第二步:获取MySQL数据库连接 String url = "jdbc:mysql://127.0.0.1:3306/db1"; String username = "root"; String password = "1234"; Connection conn = DriverManager.getConnection(url, username, password); // 3. 第三步:定义SQL语句 String sql = "update account set money = 2000 where id = 1"; // 4. 第四步:获取SQL执行对象 Statement stmt = conn.createStatement(); // 5. 第五步:通过SQL执行对象来执行SQL int count = stmt.executeUpdate(sql); // 6. 第六步:处理SQL执行结果 System.out.println(count); // 7. 第七步:释放资源 stmt.close(); conn.close(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# JDBC API 详解
上面我们演示了一个 JDBC 的入门案例,看过这个案例,我们就会产生疑问?
- 代码为什么要这样写?
- 案例代码过于简单,如果要实现更复杂一些的需求怎么办?
下面我们就重点学习下 JDBC API,通过 JDBC API 的学习,我们就可以明白为什么入门案例为何那么实现,并且学完以后我们还可以扩展入门案例
实现更复杂的功能。在入门案例里,我们使用了DriverManager
、Connection
、Statement
等 Sun 公司
定义的 JDBC 接口,接下来,我们就来逐个学习下它们。
# DriverManager
# DriverManager 详解
从上面的案例中我们看到,第一、第二部分别完成了注册驱动以及获取数据库连接的操作,在获取连接时我们使用了DriverManager
的一个静态方法
Connection conn = DriverManager.getConnection(url, username, password);
,那么DriverManager
在其中起到了什么样的作用?
我们来看一下 JDK 的 API 文档,JDK8、11 在 JDBC 接口这部分没什么变化,那咱们在 JDK11 API 文档里面来看下DriverManager
的描述。
从上面的两张图中,我们总结一下,DriverManager
大体上有两个作用:
注册驱动
但是在案例中我们并没有看到DriverManager
注册驱动的实现,只有第一步通过Class.forName("com.mysql.jdbc.Driver");
来 加载 MySQL 驱动。那我们来看下com.mysql.jdbc.Driver
的源码,大家可以看到在该类的静态代码块中执行了DriverManager
的registerDriver(new Driver())
方法,这个方法注册了驱动。因此只要我们加载Driver
类,那么Driver
类中的静态代码块就会执行, 进而注册数据库驱动,而Class.forName("com.mysql.jdbc.Driver");
就是加载Driver
类的。友情提示
MySQL 5 之后的驱动包,可以省略注册驱动的步骤,也即是可以不写
Class.forName("com.mysql.jdbc.Driver");
这一行代码了, 因为项目会自动加载 jar 包中META-INF/services/java.sql.Driver
文件中的驱动类。获取数据库连接
在案例中我们通过Connection conn = DriverManager.getConnection(url, username, password);
获取了 MySQL 数据库连接,在 JDK11 API 文档中,我们也能找到对应的方法说明:尝试建立与给定数据库 url 的连接。建立连接需要传入三个参数:- url:连接路径,格式:
jdbc:mysql://ip:port/数据库名称?参数键1=参数值1&参数键2=参数值2...
, 示例:jdbc:mysql://127.0.0.1:3306/db1
细节
- 如果连接的是本机 mysql 服务器,并且 mysql 服务使用默认端口 3306,那么 URL 可以简写为:
jdbc:mysql://数据库名称?参数键值对
; - 配置
useSSL=false
参数,禁用安全连接方式,可以解决控制台输出警告提示的问题。
- user:数据库用户名
- password:数据库密码
- url:连接路径,格式:
# DriverManager 练习
下面我们来编码演示一下刚才讲解的内容,为了方便演示,下面我们的代码全部通过之前学的单元测试进行演示:
创建
DriverManagerDemo
类编写省略注册驱动步骤测试代码,并运行查看结果
/** * 测试省略注册驱动步骤 * @throws Exception */ @Test public void testDriverLoad() throws Exception { // 1. 第一步:加载MySQL驱动类,MySQL 5 之后的驱动包可以省略注册驱动这一步 // Class.forName("com.mysql.jdbc.Driver"); // 2. 第二步:获取MySQL数据库连接 String url = "jdbc:mysql://127.0.0.1:3306/db1"; String username = "root"; String password = "1234"; Connection conn = DriverManager.getConnection(url, username, password); // 3. 第三步:定义SQL语句 String sql = "update account set money = 2000 where id = 1"; // 4. 第四步:获取SQL执行对象 Statement stmt = conn.createStatement(); // 5. 第五步:通过SQL执行对象来执行SQL int count = stmt.executeUpdate(sql); // 6. 第六步:处理SQL执行结果 System.out.println(count); // 7. 第七步:释放资源 stmt.close(); conn.close(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25运行结果如下:
编写解决警告提示测试代码,并运行查看结果
/** * 解决控制台输出警告测试代码 * @throws Exception */ @Test public void testSolveWarning() throws Exception { // 1. 第一步:获取MySQL数据库连接 String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false"; String username = "root"; String password = "1234"; Connection conn = DriverManager.getConnection(url, username, password); // 2. 第二步:定义SQL语句 String sql = "update account set money = 2000 where id = 1"; // 3. 第三步:获取SQL执行对象 Statement stmt = conn.createStatement(); // 4. 第四步:通过SQL执行对象来执行SQL int count = stmt.executeUpdate(sql); // 5. 第五步:处理SQL执行结果 System.out.println(count); // 6. 第六步:释放资源 stmt.close(); conn.close(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23运行结果如下:
# Connection
# Connection 详解
上面我们从 JDK API 文档里面找到了DriverManager
的描述,下面我们依旧先从文档中查看Connection
的信息。
文档中翻译过来的语句虽说不太通顺,但大体上我们可以理解其中的意思,Connection
与数据库建立连接,获取执行 SQL 语句的上下文也即是获取
执行 SQL 语句的对象,以及设置提交模式来管理事务。
获取 SQL 语句执行对象
那我们来从 JDK API 文档中来看下Connection
的方法列表,重点介绍后续我们经常使用的几种 SQL 语句执行对象。- 普通执行 SQL 对象:
Statement createStatement();
,我们入门案例中使用的就是这种方式
- 预编译 SQL 的执行 SQL 对象:
PreparedStatement prepareStatement(sql);
通过这种方式获取的
PreparedStatement
SQL 语句对象是我们要重点讲解的,它可以防止 SQL 注入。 - 执行存储过程的对象:
CallableStatement prepareCall(sql);
通过这种方式获取的
CallableStatement
执行对象是用来执行存储过程的,而存储过程在 MySQL 中并不常用,这个我们就进行讲解了。
- 普通执行 SQL 对象:
管理事务
我们继续在 JDK API 文档中查找,如何通过Connection
来管理事务- 开启事务:
void setAutoCommit(boolean autoCommit);
参数autoCommit
表示是否自动提交事务,true
表示自动提交,false
表示手动提交,因此开启事务需要将该参数设置为false
。 - 提交事务:
void commit();
- 回滚事务:
void rollback();
- 开启事务:
# Connection 练习
Statement
和PreparedStatement
的练习我们在详细介绍它们的时候在进行,当前我们只针对Connection
如何管理事务进行演示。假设有这样
一个场景:
第一步:创建表并初始化数据
-- 创建账户表 create table account ( id int primary key auto_increment, -- 账户主键 name varchar(20), -- 账户名 money double(7, 2) -- 账户余额 ); -- 初始化账户表数据 insert into account(name, money) values('张三', 1000), ('李四', 1000);
1
2
3
4
5
6
7
8第二步:创建
ConnectionDemo
演示类,编写testWithoutTransaction
方法,不开启事务,当执行两条更新 SQL 语句之间出现异常后, 事务不回滚,转账业务出现问题,张三账户减 500 成功,李四账户并没有加 500/** * 不开启事务,模拟因异常导致转账业务出现问题 * @throws Exception */ @Test public void testWithoutTransaction() throws Exception { // 1. 第一步:获取MySQL数据库连接 String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false"; String username = "root"; String password = "1234"; Connection conn = DriverManager.getConnection(url, username, password); // 2. 第二步:定义SQL语句 String reduceSql = "update account set money = money - 500 where name = '张三'"; String plusSql = "update account set money = money + 500 where name = '李四'"; // 3. 第三步:获取SQL执行对象 Statement stmt = conn.createStatement(); // 4. 第四步:执行张三余额减500 SQL语句 int reduceResult = stmt.executeUpdate(reduceSql); System.out.println(reduceResult > 0 ? "减500成功" : "减500失败"); int e = 3 / 0; // 在此模拟发生异常 // 5. 第五步:执行李四余额加500 SQL语句 int plusResult = stmt.executeUpdate(plusSql); System.out.println(plusResult > 0 ? "加500成功" : "加500失败"); // 6. 第六步:释放资源 stmt.close(); conn.close(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29运行结果如下:
第三步:编写
testWithTransaction
方法,开启事务,当出现异常后,事务回滚,转账业务数据正确,张三、李四余额不发生变化。 大家思考一下,无异常提交事务,出现异常后回滚事务,我们应该使用什么样的代码结构?是不是应该使用异常处理try{...}catch(...){}
/** * 开启事务,模拟出现异常不回导致转账业务出现问题 * @throws Exception */ @Test public void testWithTransaction() throws Exception { // 1. 第一步:获取MySQL数据库连接 String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false"; String username = "root"; String password = "1234"; Connection conn = DriverManager.getConnection(url, username, password); // 2. 第二步:定义SQL语句 String reduceSql = "update account set money = money - 500 where name = '张三'"; String plusSql = "update account set money = money + 500 where name = '李四'"; // 3. 第三步:获取SQL执行对象 Statement stmt = conn.createStatement(); try { // 开启事务 conn.setAutoCommit(false); // 4. 第四步:执行张三余额减500 SQL语句 int reduceResult = stmt.executeUpdate(reduceSql); System.out.println(reduceResult > 0 ? "减500成功" : "减500失败"); int e = 3 / 0; // 在此模拟发生异常 // 5. 第五步:执行李四余额加500 SQL语句 int plusResult = stmt.executeUpdate(plusSql); System.out.println(plusResult > 0 ? "加500成功" : "加500失败"); // 转账代码运行完成,无异常,提交事务 conn.commit(); } catch (Exception e) { // 转账业务出现异常,此时应该回滚事务 conn.rollback(); e.printStackTrace(); } // 6. 第六步:释放资源 stmt.close(); conn.close(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39代码编写完成,不要忘了将
account
表中张三
、李四
的账户余额修改为初始化金额,也就是 1000。然后我们运行代码看下结果:
# Statement
# Statement 详解
我们依旧先从 JDK API 文档中查看有关Statement
的介绍:
文档中的介绍相当简单,用于执行静态 SQL 语句并返回其生成的结果的对象。当前我们重点理解一下静态 SQL 语句即可,静态 SQL 语句 也即是我们通过下面方式定义的 SQL 语句:
// 通过字符串定义的完整的SQL语句
String sql1 = "update account set money = 1000 where name = '张三'";
// 通过字符串拼接定义的SQL语句
double money = 1000;
String name = "张三";
String sql2 = "update account set money = " + money + " where name = '" + name + "'";
2
3
4
5
6
Statement
对象用来执行 SQL 语句,我们之前已经学习了 SQL 的分类,那接下来我们就从三方面来演示其如何执行不同类型的 SQL 语句:
- 执行 DML 语句
- 执行 DDL 语句
- 执行 DQL 语句
# Statement 练习
执行 DML 语句:DML 类型 SQL 也即是对表中的数据进行增(
insert
)、删(delete
)、改(update
)
创建StatementDemo
类,前面我们已经演示了转账的操作,通过update
更新账户的余额,那么我们接下来开发一个插入 数据的方法testDml
,代码如下:/** * 通过Statement执行DML语句,插入账户数据 * @throws Exception */ @Test public void testDml() throws Exception { // 1. 第一步:获取MySQL数据库连接 String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false"; String username = "root"; String password = "1234"; Connection conn = DriverManager.getConnection(url, username, password); // 2. 第二步:定义SQL语句 String sql = "insert into account(name, money) values('王五', 3000)"; // 3. 第三步:获取SQL执行对象 Statement stmt = conn.createStatement(); // 4. 第四步:通过SQL执行对象来执行SQL int count = stmt.executeUpdate(sql); // 5. 第五步:处理SQL执行结果 System.out.println(count > 0 ? "插入成功" : "插入失败"); // 6. 第六步:释放资源 stmt.close(); conn.close(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23执行结果如下:
执行 DDL 语句:DDL 类型 SQL 可创建(
create
)、修改(只针对表alter
)、删除(drop
)数据库及数据表。
实际工作中,我们并不会通过Statement
来执行 DDL 类型 SQL 语句,大家知道可以通过Statement
执行 DDL 类型的 SQL 语句即可。 接下来我们先通过客户端create database db2;
语句来创建数据库,然后通过Statement
来删除db2
数据库
创建数据库后,我们来编写 Java 代码:/** * 通过Statement执行DDL类型SQL语句,删除数据库 * @throws Exception */ @Test public void testDdl() throws Exception { // 1. 第一步:获取MySQL数据库连接 String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false"; String username = "root"; String password = "1234"; Connection conn = DriverManager.getConnection(url, username, password); // 2. 第二步:定义SQL语句 String sql = "drop database db2"; // 3. 第三步:获取SQL执行对象 Statement stmt = conn.createStatement(); // 4. 第四步:通过SQL执行对象来执行SQL int count = stmt.executeUpdate(sql); // 5. 第五步:处理SQL执行结果 System.out.println(count); // 执行完DDL,返回结果可能是0 // 6. 第六步:释放资源 stmt.close(); conn.close(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23执行 DQL 语句:在学习通过
Statement
执行 DQL 类型 SQL 语句前,我们需要了解下ResultSet,因为ResultSet
封装了 SQL 查询语句的结果,大家可以回头看一下,咱们前面通过Statement
执行 SQL 的返回结果基本上都是int
,我们执行insert
、update
语句 时,只给我们返回 SQL 语句执行影响的数据条数,那如果是select
语句呢,其返回结果可能是多行多列的一个数据集,这种情况 JDBC 也定义了 对应的接口,就是ResultSet
# ResultSet
# ResultSet 详解
首先,来看下 JDK API 文档对ResultSet
的介绍:
文档中的表述已经比较明确了,总结一下:ResultSet
封装了 SQL 查询语句的执行结果,我们可以通过ResultSet
提供的 boolean next();
、xxx getXxx(参数);
方法与while
循环配合对 SQL 查询语句结果集进行遍历。
接下来,我们就重点介绍一下boolean next();
方法,因为ResultSet
中有很多xxx getXxx(参数);
类型的方法,我们选取几种常用的介绍下,
剩下的大家就可以举一反三了,它们的使用方式都是一样的。
boolean next();
:将光标从当前位置向前移动一行;判断移动后指向的行是否为有效数据行。- 返回值说明:
true
指向的数据行为有效数据行;false
指向的数据行为无效数据行。
- 返回值说明:
- **
xxx getXxx(参数);
:获取列数据- xxx:数据类型,如:
int getInt(参数);
;String getString(参数);
; - 参数:
int
类型的参数:列的编号,从 1 开始;String
类型的参数:列的名称
- xxx:数据类型,如:
下面通过代码来实现一下上面讲解的操作,创建ResultSetDemo
类:
/**
* 遍历DQL类SQL语句结果
* @throws Exception
*/
@Test
public void testResultSet() throws Exception {
// 1. 第一步:获取MySQL数据库连接
String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
// 2. 第二步:定义SQL语句
String dql = "select * from account";
// 3. 第三步:获取SQL执行对象
Statement stmt = conn.createStatement();
// 4. 第四步:通过SQL执行对象来执行SQL
ResultSet rs = stmt.executeQuery(dql);
// 5. 第五步:处理SQL执行结果,遍历ResultSet中的数据
// 光标向下移动一行,并判断移动后指向的行是否有数据
while (rs.next()) {
// 5.1 通过xxx getXxx(int columnIndex)获取列数据
// int id = rs.getInt(1);
// String name = rs.getString(2);
// double money = rs.getDouble(3);
// 5.2 通过xxx getXxx(String columnLabel)获取列数据
int id = rs.getInt("id");
String name = rs.getString("name");
double money = rs.getDouble("money");
System.out.println(id);
System.out.println(name);
System.out.println(money);
}
// 6. 第六步:释放资源
stmt.close();
conn.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
执行结果如下:
# ResultSet 扩展案例
需求:查询
account
账户表,将结果封装为Account
对象,并存储到ArrayList
集合中。
代码实现:
package com.itheima.pojo; /** * 账户信息 * @author sunyy * @version 0.0.1 * @since 2022.10.19 */ public class Account { /** * 账户表主键 * * 不用int类型 * 原因:int类型默认值为0,当new Account()时,int类型的默认值可能会对业务产生影响 */ private Integer id; /** * 账户名 */ private String name; /** * 账户余额 * * 不用double类型 * 原因:double类型默认值为0.0, 当new Account()时,double类型的默认值肯能会对业务产生影响 * 因此,后面针对实体类定义,属性建议使用对应基础类型的包装类型 */ private Double money; public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65/** * 需求:查询account账户表数据,封装为Account对象中,并且存储到ArrayList集合中 * 1. 定义实体类Account * 2. 查询account表数据,封装到Account对象 * 3. 将Account对象存入ArrayList集合中 * @throws Exception */ @Test public void testResultSet2() throws Exception { // 1. 第一步:获取MySQL数据库连接 String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false"; String username = "root"; String password = "1234"; Connection conn = DriverManager.getConnection(url, username, password); // 2. 第二步:定义SQL语句 String dql = "select * from account"; // 3. 第三步:获取SQL执行对象 Statement stmt = conn.createStatement(); // 4. 第四步:通过SQL执行对象来执行SQL ResultSet rs = stmt.executeQuery(dql); // 5. 第五步:处理SQL执行结果,遍历ResultSet中的数据 // 循环体外创建集合ArrayList,用于保存Account对象 List<Account> accounts = new ArrayList<>(); // 光标向下移动一行,并判断移动后指向的行是否有数据 while (rs.next()) { // 5.1 通过xxx getXxx(int columnIndex)获取列数据 // int id = rs.getInt(1); // String name = rs.getString(2); // double money = rs.getDouble(3); // 5.2 通过xxx getXxx(String columnLabel)获取列数据 int id = rs.getInt("id"); String name = rs.getString("name"); double money = rs.getDouble("money"); // 每次循环遍历一行数据,创建Account对象,并给属性赋值 Account account = new Account(); System.out.println(account); account.setId(id); account.setName(name); account.setMoney(money); // 将对象存入ArrayList集合 accounts.add(account); } System.out.println(accounts); // 6. 第六步:释放资源 stmt.close(); conn.close(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48运行结果如下:
那么,如果我们将
Account
类中的id
、money
属性替换为基础类型运行结果如下:
# PreparedStatement
# PreparedStatement 详解
咱们来看下 JDK API 文档有关PreparedStatement
的介绍:
PreparedStatement
:表示预编译 SQL 语句的对象,其保存已预编译的 SQL 语句,可以使用该对象多次有效地执行此语句。除此之外,我们还会
介绍还会介绍PreparedStatement
的一个特性--预防 SQL 注入。接下来,我们先回顾下之前通过Statement
执行DML
、DDL
代码有没有问题:
`Statement`执行`DML`、`DDL`问题
DML
、DDL
语句简单,没有设置where
限定条件,比如:只更新张三的余额,或者查询余额小于 1000 的账户信息等等?
那接下来我们先通过
Statement
来解决下上面提出的问题,不如更新某一个账户的余额为给定的值,那这个 SQL 的需求应该怎么来写?-- 因为账户以及要更新的余额我们都不确定,因此我们先用?代替 update account set money = ? where name = ?;
1
2代码实现:
/** * 完善DML操作,实现更新某一账户余额为指定的值 * @throws Exception */ @Test public void testStatement() throws Exception { // 1. 第一步:获取MySQL数据库连接 String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false"; String username = "root"; String password = "1234"; Connection conn = DriverManager.getConnection(url, username, password); // 模拟网页传参,我们在此定义两个变量 String name = "张三"; double money = 10000.00; // 2. 第二步:定义SQL语句,我们要考虑如何将参数拼接到SQL语句中,字符串拼接 String sql = "update account set money = " + money + " where name = '" + name + "'"; System.out.println(sql); // 3. 第三步:获取SQL执行对象 Statement stmt = conn.createStatement(); // 4. 第四步:通过SQL执行对象来执行SQL int count = stmt.executeUpdate(sql); // 5. 第五步:处理SQL执行结果 System.out.println(count > 0 ? "修改成功" : "修改失败"); // 6. 第六步:释放资源 stmt.close(); conn.close(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27执行前,我们先确认下当前账户表中张三的余额是多少
执行代码看下结果:
那下面我们就来看下
PreparedStatement
如何使用?- 获取
PreparedStatement
对象,那和Statement
普通执行 SQL 语句对象相比,它是预编译 SQL 语句,那么我们首先就得定义好 SQL,然后将 SQL 语句作为参数来获得PreparedStatement
对象。
// SQL语句中的参数值,使用?占位符代替 String sql = "update account set money = ? where name = ?"; // 通过Connection对象获取,并传入对应的SQL语句 PreparedStatement pstmt = conn.prepareStatement(sql);
1
2
3
4设置参数值:
Statement
是通过字符串拼接来设置参数值,那么PreparedStatement
如何设置参数?PreparedStatement
对象提供了一个方法:setXxx(arg1, arg2)
来给?
占位符进行赋值- Xxx:代表数据类型;如,setInt(arg1, arg2),setString(arg1, arg2)。
- 参数
- arg1:
?
从左至右的位置编号,编号从1开始 - arg2:
?
的值,也就是我们需要传入的参数值
- arg1:
执行 SQL 语句:与
Statement
类似- executeUpdate():执行 DDL 和 DML 语句
- executeQuery():执行 DQL 语句
不过需要注意的是,与
Statement
不同的是,调用这两个方法不再需要传递 SQL 语句,因为获取PreparedStatement
对象时已经对 SQL > 语句进行了预编译。
代码实现
/** * PreparedStatement简单示例 * * @throws Exception */ @Test public void testPreparedStatement() throws Exception { // 1. 第一步:获取MySQL数据库连接 String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false"; String username = "root"; String password = "1234"; Connection conn = DriverManager.getConnection(url, username, password); // 模拟网页传参,我们在此定义两个变量 String name = "张三"; // 注意双引号里面的数据为张三 double money = 500.00; // 2. 第二步:定义SQL语句,我们要考虑如何将参数拼接到SQL语句中,字符串拼接 String sql = "update account set money = ? where name = ?"; // 3. 第三步:获取预编译SQL执行对象 PreparedStatement pstmt = conn.prepareStatement(sql); // 设置?占位符的值 pstmt.setDouble(1, money); pstmt.setString(2, name); // 4. 第四步:通过预编译SQL执行对象来执行SQL,注意这个地方不在传入SQL语句 int count = pstmt.executeUpdate(); // 5. 第五步:处理SQL执行结果 System.out.println(count > 0 ? "修改成功" : "修改失败"); // 6. 第六步:释放资源 pstmt.close(); conn.close(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30执行结果:
- 获取
# SQL 注入问题
为了更直观的展示 SQL 注入问题,我这边做了一个简单的演示项目,下面通过下面的一个流程来简单给大家演示一下。
环境准备:
- 在 MySQL 服务器创建演示数据库
test
,这个库是我们演示项目要使用的,如果不创建,演示项目会启动失败那我们通过图形化客户端创建下
test
库
- 修改 MySQL 安装目录下
my.ini
文件,开启 MySQL 服务器执行日志
log-output=FILE general-log=1 general_log_file="D:\itcast\Tool\MySQL\mysql-5.7.24-winx64\logs\mysql.log" slow-query-log=1 slow_query_log_file="D:\itcast\Tool\MySQL\mysql-5.7.24-winx64\logs\mysql_slow.log" long_query_time=2
1
2
3
4
5
6重启 MySQL 服务
- 在 MySQL 服务器创建演示数据库
配置环境:将
sql.jar
和application.properties
拷贝到非中文路径下,修改配置演示项目连接的数据库信息
项目演示:
- 启动项目:在
sql.jar
目录下运行cmd
命令,输入java -jar ./sql.jar
启动服务,默认使用8080
端口。如果出现下面的问题, 说明8080
端口已被占用,需要停止占用8080
端口上启动的服务,或者在application.properties
文件中通过server.port=8081
指定 演示项目使用的端口。
启动服务,如果出现下面的提示,说明启动成功。
服务启动成功后,项目会在
test
数据库下创建 2 张表user
、product
,并初始化表里面的数据。
- 启动项目:在
项目演示:访问登录页
- 正常登录:用户名/密码为 zhangsan/123,登录成功;用户名/密码为 zhangsan/789,登录失败。SQL 执行日志输出
- 注入登录:用户名随便,密码填写
' or '1' = '1
,登录成功,SQL 执行日志输出
从日志当中我们可以看到,我们登录功能执行的 SQL 语句原意是匹配用户名、密码一致的用户,而经过页面精心构造参数,导致我们的 SQL 语句不再 按照预先设定的逻辑执行。
select count(1) from test.user where username='dfqwer1adf' and password='' or '1' = '1'
。
- 正常登录:用户名/密码为 zhangsan/123,登录成功;用户名/密码为 zhangsan/789,登录失败。SQL 执行日志输出
小结:通过上面的演示,我们对 SQL 注入就有一个比较直观的认识了。它其实 是通过操作输入来修改事先定义好的 SQL 语句,用以达到对服务器进行攻击的目的。
上面通过 SQL 注入演示了获取登录权限的过程,当然还可以通过注入漏洞来恶意修改数据等等,因此有 SQL 注入漏洞的系统是很危险的。 不过现在市面上的系统都不会存在这种问题了:一是研发人员重点关注了;二是国家单位定期或者举办大型活动前都会进行护网活动, 由专业的渗透测试人员针对重点网站进行测试和攻防演练,发现问题要立即整改。
既然 SQL 注入漏洞及其危险,我们该如何解决呢?接下来我们就来看下PreparedStatement
是如何解决 SQL 注入问题的。为了加深对 SQL 注入的理解,
我们通过代码来实现一个例子:
# PreparedStatement 解决 SQL 注入
代码模拟 SQL 注入漏洞恶意修改数据:通过
Statement
执行拼接的 SQL 语句,模拟 SQL 注入恶意修改数据。场景:通过 SQL 注入,修改 account 表中所有账户的余额。/** * 模拟SQL注入恶意修改数据 * 场景:通过SQL注入修改account表中所有账户的余额 * @throws Exception */ @Test public void testWithSqlInject() throws Exception { // 1. 第一步:获取MySQL数据库连接 String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false"; String username = "root"; String password = "1234"; Connection conn = DriverManager.getConnection(url, username, password); // 模拟网页传参,我们在此定义两个变量 String name = "张三 ' or '1' = '1"; // 本意是只修改张三的余额为10000,通过恶意构造参数,将所有账户的余额都修改为了10000。 double money = 10000.00; // 2. 第二步:定义SQL语句,我们要考虑如何将参数拼接到SQL语句中,字符串拼接 String sql = "update account set money = " + money + " where name = '" + name + "'"; System.out.println(sql); // 3. 第三步:获取SQL执行对象 Statement stmt = conn.createStatement(); // 4. 第四步:通过SQL执行对象来执行SQL int count = stmt.executeUpdate(sql); // 5. 第五步:处理SQL执行结果 System.out.println(count > 0 ? "修改成功" : "修改失败"); // 6. 第六步:释放资源 stmt.close(); conn.close(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28演示效果:
使用
PreparedStatement
解决 SQL 注入问题:代码实现如下/** * 使用PreparedStatement解决SQL注入问题 * @throws Exception */ @Test public void testWithoutSqlInject() throws Exception { // 1. 第一步:获取MySQL数据库连接 String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false"; String username = "root"; String password = "1234"; Connection conn = DriverManager.getConnection(url, username, password); // 模拟网页传参,我们在此定义两个变量 String name = "张三 ' or '1' = '1"; // 依旧恶意构造参数 double money = 10000.00; // 2. 第二步:定义SQL语句,我们要考虑如何将参数拼接到SQL语句中,字符串拼接 String sql = "update account set money = ? where name = ?"; // 3. 第三步:获取PreparedStatement PreparedStatement pstmt = conn.prepareStatement(sql); // 设置参数 pstmt.setDouble(1, money); pstmt.setString(2, name); // 4. 第四步:通过SQL执行对象来执行SQL int count = pstmt.executeUpdate(); // 5. 第五步:处理SQL执行结果 System.out.println(count > 0 ? "修改成功" : "修改失败"); // 6. 第六步:释放资源 pstmt.close(); conn.close(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29执行结果提示修改失败,分析代码发现通过恶意构造参数无法匹配到任何数据,因此
update
执行后受影响的数据为0行,输出修改失败。 那为什么匹配不到数据,我们来看下 SQL 执行日志不难发现,PreparedStatement
在执行 SQL 语句时将参数中的特殊字符进行了转义。
# PreparedStatement 预编译原理
在介绍PreparedStatement
预编译原理前,我们首先来看下 Java 代码操作数据库流程:
Java 代码操作数据库流程如上图所示:
- 将 SQL 语句发送到 MySQL 服务器端
- MySQL 服务端会对 sql 语句进行如下操作
- 检查 SQL 语句 检查 SQL 语句的语法是否正确。
- 编译 SQL 语句。将 SQL 语句编译成可执行的函数。 检查 SQL 和编译 SQL 花费的时间比执行 SQL 的时间还要长。如果我们只是重新设置参数,那么检查 SQL 语句和编译 SQL 语句将不需要重复执行。 这样就提高了性能。
- 执行 SQL 语句
接下来我们通过代码以及 SQL 执行日志来演示PreparedStatement
的预编译效果。首先,需要开启预编译功能:在代码中编写 url 时需要加上
useServerPrepStmts=true
参数,否则预编译功能默认关闭,我们是看不到效果的。
代码实现:
/**
* 演示PreparedStatement的预编译效果
* 场景:根据指定条件查询account表中账户信息。
*
* @throws Exception
*/
@Test
public void testPreCompile() throws Exception {
// 1. 第一步:获取MySQL数据库连接,开启预编译功能
String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false&useServerPrepStmts=true";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
// 模拟网页传参,我们在此定义两个变量
String param1 = "张三"; //
// 2. 第二步:定义SQL语句,我们要考虑如何将参数拼接到SQL语句中,字符串拼接
String sql = "select id, name, money from account where name = ?";
// 3. 第三步:获取预编译SQL执行对象
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置?占位符的值
pstmt.setString(1, param1);
// 4. 第四步:通过预编译SQL执行对象来执行查询SQL,注意这个地方不在传入SQL语句
// 第一次执行查询
ResultSet rs = pstmt.executeQuery();
// 5. 第五步:处理SQL执行结果
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
double money = rs.getDouble("money");
Account account = new Account();
account.setId(id);
account.setName(name);
account.setMoney(money);
System.out.println(account);
}
// 第二次执行查询,为更直观的看到效果,该处首先sleep10秒
Thread.sleep(10000);
String param2 = "李四";
pstmt.setString(1, param2);
ResultSet rs2 = pstmt.executeQuery();
while (rs2.next()) {
int id = rs2.getInt("id");
String name = rs2.getString("name");
double money = rs2.getDouble("money");
Account account = new Account();
account.setId(id);
account.setName(name);
account.setMoney(money);
System.out.println(account);
}
// 6. 第六步:释放资源
rs.close();
rs2.close();
pstmt.close();
conn.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
SQL 执行日志:
目标:
- 掌握 Druid 的使用
- 能够使用 Maven 进行项目的管理
# 数据库连接池
# 数据库连接池简介
之前我们代码中使用数据库连接每次都会创建一个 Connection
对象,使用完毕再将其销毁。实际上,这种重复创建、销毁数据库连接对象是非常耗费资源和时间,会导致程序执行效率低下。
那是不是能够优化这一部分内容呢?
我们可以在程序启动时,就创建一批连接放在一个容器中,当用户需要连接时,就从容器中获取一个连接对象,用完连接后,不需要关闭,而是将连接再返回连接池中,这样一来,就实现了连接的复用,减少了连接创建和关闭的次数,提高了程序执行的效率。
上面存放数据库连接的容器就是我们所说的数据库连接池:
- 数据库连接池是一个容器,负责分配、管理数据库连接(
Connection
)- 它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个连接
- 释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放连接而引起数据库连接遗漏
使用数据库连接池,可以使我们:
- 资源重用
- 提升系统响应速度
- 避免数据库连接遗漏
# 数据库连接池实现
数据库连接池的实现,Sun 公司同样给我们提供了一套标准的接口,然后由第三方组织实现此接口。该接口提供了获取数据库连接的功能:
Connection getConnection();
那么,以后我们就不需要通过 DriverManager
对象来获取 Connection
对象了,可以通过连接池 DataSource
来获取。
与数据库连接驱动类似,不同类型的数据库有自己实现的连接驱动,接下来我们看下有哪些常用的数据库连接池实现:
- DBCP
- C3P0
- Druid
我们现在使用更多的是 Druid,它的性能要比其他两个好一些。
# Druid 使用
Druid 连接池是阿里巴巴开源的数据库连接池项目,功能强大、性能优秀,是 Java 语言最好的数据库连接池之一。
首先,咱们来看一下使用 Druid 连接池的步骤:
接下来,咱们来按步进行编码实现:
第一步:将 druid 的 jar 包放到项目的 lib 目录下并添加为库文件
第二步:定义 druid 配置文件
druid.properties
,项目目录结构如下第三步:完善
druid.properties
文件内容driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true username=root password=1234 # 初始化连接数量 initialSize=5 # 最大连接数 maxActive=10 # 最大等待时间 maxWait=3000
1
2
3
4
5
6
7
8
9
10第四步:加载配置文件,获取数据库连接池对象
Properties prop = new Properties(); prop.load(new FileInputStream("jdbc-demo/src/druid.properties")); // 获取数据库连接池对象 DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
1
2
3
4第五步:获取数据库连接
Connection
Connection connect = dataSource.getConnection();
1
完整代码如下:
package com.itheima.druid;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
/**
* Druid 数据库连接池示例
* @author sunyy
* @version 0.0.1
* @since 2022-10-23
*/
public class DruidDemo {
public static void main(String[] args) throws Exception {
/*
加载配置文件,注意加载配置文件时可以通过
System.getProperty("user.dir");
获取项目根目录,然后根据根目录找到配置文件的相对路径
*/
Properties prop = new Properties();
prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));
// 获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
// 获取连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Druid 练习
**第一步:分析需求:**完成商品品牌数据的增、删、改、查等操作
- 查询:查询所有品牌数据
- 添加:添加品牌
- 修改:根据 id 修改
- 删除:根据 id 删除
第二步:建表
-- 删除tb_brand表 drop table if exists tb_brand; -- 创建tb_brand表 create table tb_brand ( -- id 主键 id int not null auto_increment, -- 品牌名称 brand_name varchar(20), -- 企业名称 company_name varchar(20), -- 排序字段 ordered int, -- 描述信息 description varchar(100), -- 状态 0:禁用 1:启用 status int, -- 主键约束 constraint pk_tb_brand primary key(id) ); -- 初始化数据 insert into tb_brand (brand_name, company_name, ordered, description, status) values ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0), ('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1), ('小米', '小米科技有限公司', 50, 'are you ok', 1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28第三步:在
pojo
包下创建品牌实体类Brand
package com.itheima.pojo;
/**
* 品牌实体
* @author sunyy
* @version 0.0.1
* @since 2022.10.23
*/
public class Brand {
/**
* 主键
*/
private Integer id;
/**
* 品牌名称
*/
private String brandName;
/**
* 企业名称
*/
private String companyName;
/**
* 排序序号
*/
private Integer ordered;
/**
* 描述信息
*/
private String description;
/**
* 状态
* 0:禁用
* 1:启用
*/
private Integer status;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
this.brandName = brandName;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public Integer getOrdered() {
return ordered;
}
public void setOrdered(Integer ordered) {
this.ordered = ordered;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
@Override
public String toString() {
return "Brand{" +
"id=" + id +
", brandName='" + brandName + '\'' +
", companyName='" + companyName + '\'' +
", ordered=" + ordered +
", description='" + description + '\'' +
", status=" + status +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# 练习:查询所有
/**
* 查询所有品牌
* 需求分析:
* 1. SQL: select * from tb_brand;
* 2. 参数:不需要
* 3. 结果:List<Brand>
* @throws Exception
*/
@Test
public void testSelectAll() throws Exception {
// 1. 加载Druid配置文件
Properties properties = new Properties();
// System.out.println(System.getProperty("user.dir"));
properties.load(new FileInputStream("src/druid.properties"));;
// 2. 获取数据库连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 3. 获取数据库连接
Connection connection = dataSource.getConnection();
// 4. 定义SQL语句
String sql = "select * from tb_brand";
// 5. 获取预编译SQL执行对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 6. 设置参数,当前练习不用
// 7. 执行SQL
ResultSet resultSet = preparedStatement.executeQuery();
// 8. 处理结果,将结果封装为 Brand 对象,并放到集合当中去
Brand brand = null;
List<Brand> brands = new ArrayList<>();
while (resultSet.next()) {
// 获取数据
Integer id = resultSet.getInt("id");
String brandName = resultSet.getString("brand_name");
String companyName = resultSet.getString("company_name");
int ordered = resultSet.getInt("ordered");
String description = resultSet.getString("description");
int status = resultSet.getInt("status");
// 封装Brand对象
brand = new Brand();
brand.setId(id);
brand.setBrandName(brandName);
brand.setCompanyName(companyName);
brand.setOrdered(ordered);
brand.setDescription(description);
brand.setStatus(status);
// 装载集合
brands.add(brand);
}
System.out.println(brands);
// 7. 释放资源
resultSet.close();
preparedStatement.close();
connection.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 练习:添加品牌数据
/**
* 添加品牌数据
* 需求分析:
* 1. SQL: inert into tb_brand(brand_name, company_name, ordered, description, status) values (?,?,?,?,?);
* 2. 参数:除id之外的所有参数
* 3. 结果:boolean
* @throws Exception
*/
@Test
public void testAdd() throws Exception {
// 模拟接收到页面提交的参数
String brandName = "香飘飘";
String companyName = "香飘飘";
int ordered = 1;
String description = "绕地球一圈";
int status = 1;
// 1. 加载配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src/druid.properties"));
// 2. 获取数据库连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 3. 获取数据库连接对象
Connection connection = dataSource.getConnection();
// 4. 定义SQL
String sql = "insert into tb_brand(brand_name, company_name, ordered, description, status) values(?, ?, ?, ?, ?)";
// 5. 获取预编译SQL执行对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 6. 设置参数
preparedStatement.setString(1, brandName);
preparedStatement.setString(2, companyName);
preparedStatement.setInt(3, ordered);
preparedStatement.setString(4, description);
preparedStatement.setInt(5, status);
// 7. 执行SQL
int i = preparedStatement.executeUpdate();
// 8. 处理结果
System.out.println(i > 0 ? true : false);
// 9. 释放资源
preparedStatement.close();
connection.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 练习:修改品牌数据
/**
* 修改品牌数据
* 需求分析:
* 1. SQL:
* update tb_brand
* set brand_name = ?,
* company_name = ?,
* ordered = ?,
* description = ?,
* status = ?
* where id = ?
* 2. 参数:所有数据
* 3. 结果:boolean
* @throws Exception
*/
@Test
public void testUpdate() throws Exception {
// 模拟接收到页面提交的参数
String brandName = "香飘飘";
String companyName = "香飘飘";
int ordered = 1000;
String description = "绕地球三圈";
int status = 1;
int id = 4;
// 1. 加载配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src/druid.properties"));
// 2. 获取数据库连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 3. 获取数据库连接对象
Connection connection = dataSource.getConnection();
// 4. 定义SQL
String sql = "update tb_brand " +
"set brand_name = ?, " +
"company_name = ?, " +
"ordered = ?, " +
"description = ?, " +
"status = ? " +
"where id = ?";
// 5. 获取预编译SQL执行对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 6. 设置参数
preparedStatement.setString(1, brandName);
preparedStatement.setString(2, companyName);
preparedStatement.setInt(3, ordered);
preparedStatement.setString(4, description);
preparedStatement.setInt(5, status);
preparedStatement.setInt(6, id);
// 7. 执行SQL
int i = preparedStatement.executeUpdate();
// 8. 处理结果
System.out.println(i > 0 ? true : false);
// 9. 释放资源
preparedStatement.close();
connection.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# 练习:删除品牌数据
/**
* 删除品牌数据
* 需求分析
* 1. SQL:delete from tb_brand where id = ?
* 2. 参数:需要,id
* 3. 结果:boolean
* @throws Exception
*/
@Test
public void testDelete() throws Exception {
// 模拟接收页面参数
int id = 4;
// 1. 加载配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src/druid.properties"));
// 2. 获取数据库连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 3. 获取数据库连接对象
Connection connection = dataSource.getConnection();
// 4. 定义SQL
String sql = "delete from tb_brand where id = ?";
// 5. 获取预编译SQL执行对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 6. 设置参数
preparedStatement.setInt(1, id);
// 7. 执行SQL
int i = preparedStatement.executeUpdate();
// 8. 处理结果
System.out.println(i > 0 ? true : false);
// 9. 释放资源
preparedStatement.close();
connection.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41