对于JDBC核心API的理解
对于JDBC核心API的理解
Re-xyJDBC核心API理解
数据库代码实现
1 | CREATE DATABASE xy; |
Java代码实现
1 | package com.xy.base; |
步骤总结
- 注册驱动【依赖的驱动类,进行安装】
- 获取连接【Connection建立连接】
- 创建发送SQL语句对象【Connection创建发送SQL语句的Statement】
- 发送SQL语句,并获取返回结果【Statement 发送sql语句到数据库并且取得返回结果】
- 结果集解析【结果集解析,将查询结果解析出来】
- 资源关闭【释放ResultSet、Statement 、Connection】
核心API理解
注册驱动
1 | Class.forName("com.mysql.cj.jdbc.Driver"); |
- 在 Java 中,当使用 JDBC(Java Database Connectivity)连接数据库时,需要加载数据库特定的驱动程序,以便与数据库进行通信。加载驱动程序的目的是为了注册驱动程序,使得 JDBC API 能够识别并与特定的数据库进行交互。
- 从JDK6开始,不再需要显式地调用
Class.forName()来加载 JDBC 驱动程序,只要在类路径中集成了对应的jar文件,会自动在初始化时注册驱动程序。
Connection
Connection接口是JDBC API的重要接口,用于建立与数据库的通信通道。换而言之,Connection对象不为空,则代表一次数据库连接。
在建立连接时,需要指定数据库URL、用户名、密码参数。
- URL:jdbc:mysql://localhost:3306/xy
- jdbc:mysql://IP地址:端口号/数据库名称?参数键值对1&参数键值对2
这里补充一点当你在自己的电脑的本地端口时,URL可以为:
URL:jdbc:mysql:///xy
- URL:jdbc:mysql://localhost:3306/xy
Connection接口还负责管理事务,Connection接口提供了commit和rollback方法,用于提交事务和回滚事务。可以创建
Statement对象,用于执行 SQL 语句并与数据库进行交互。在使用JDBC技术时,必须要先获取Connection对象,在使用完毕后,要释放资源,避免资源占用浪费及泄漏。
获取 Connection 对象就是连接数据库
- 当你调用
DriverManager.getConnection(url, username, password)时,JDBC 会与数据库建立一次连接。- 这个连接是通过
Connection对象表示的。如果Connection对象不为空(即connection != null),说明连接成功。- 每次获取
Connection对象,都会占用数据库的一个连接资源。
为什么要释放 Connection 资源?
数据库连接是一种有限的资源,如果不及时释放,可能会导致以下问题:
- 资源浪费:
- 每个
Connection对象都会占用数据库的连接资源。- 如果连接不释放,数据库的连接池可能会被耗尽,导致新的连接请求失败。
- 资源泄漏:
- 如果程序中没有正确关闭
Connection,这些连接会一直保持打开状态,最终可能导致数据库服务器崩溃。
- 性能问题:
- 未释放的连接会占用内存和网络资源,影响应用程序的性能。
Statement
Statement接口用于执行 SQL 语句并与数据库进行交互。它是 JDBC API 中的一个重要接口。通过Statement对象,可以向数据库发送 SQL 语句并获取执行结果。- 结果可以是一个或多个结果。
- 增删改:受影响行数单个结果。
- 查询:单行单列、多行多列、单行多列等结果。
- 但是
Statement接口在执行SQL语句时,会产生SQL注入攻击问题:- 当使用
Statement执行动态构建的 SQL 查询时,往往需要将查询条件与 SQL 语句拼接在一起,直接将参数和SQL语句一并生成,让SQL的查询条件始终为true得到结果。
- 当使用
关于SQL注入攻击问题:
当你输入
abc' OR '1' = '1时,由于数据库中没有abc' OR '1' = '1没有这个字段,因此输出应该为空才对,那为什么输出却是整个数据库呢?很简单,这是字符串的拼接问题,如图所示:
总结:
代码分析
代码功能
- 这段代码的功能是根据用户输入的员工姓名,查询
t_emp表中的员工信息。- 用户输入的姓名会被拼接到 SQL 语句中,然后通过
Statement执行查询。关键代码
1
2
3 String name = scanner.nextLine();
String s = "select emp_id,emp_name,emp_salary,emp_age from t_emp where emp_name='" + name + "'";
ResultSet rs = stmt.executeQuery(s);
用户输入的
name被直接拼接到 SQL 语句中。如果用户输入
abc' OR '1' = '1,最终的 SQL 语句会变成:
1 SELECT emp_id, emp_name, emp_salary, emp_age FROM t_emp WHERE emp_name='abc' OR '1' = '1';由于
'1' = '1'永远为真,这条 SQL 语句会返回t_emp表中的所有数据。
SQL 注入的原理
什么是 SQL 注入?
SQL 注入是一种攻击技术,攻击者通过在用户输入中插入恶意 SQL 代码,破坏原始 SQL 语句的结构,从而执行非预期的操作。
攻击过程
攻击者输入
abc' OR '1' = '1。这段输入被拼接到 SQL 语句中,导致 SQL 语句的逻辑被改变。
最终的 SQL 语句:
1 SELECT emp_id, emp_name, emp_salary, emp_age FROM t_emp WHERE emp_name='abc' OR '1' = '1';由于
OR '1' = '1'永远为真,查询条件被绕过,返回所有数据。危害
- 攻击者可以获取数据库中的所有数据,包括敏感信息。
- 攻击者可以执行任意 SQL 语句,如删除表、修改数据等。
PreparedStatement
PreparedStatement是Statement接口的子接口,用于执行预编译的 SQL 查询,作用如下:- 预编译SQL语句:在创建PreparedStatement时,就会预编译SQL语句,也就是SQL语句已经固定。
- 防止SQL注入:
PreparedStatement支持参数化查询,将数据作为参数传递到SQL语句中,采用?占位符的方式,将传入的参数用一对单引号包裹起来’’,无论传递什么都作为值。有效防止传入关键字或值导致SQL注入问题。 - 性能提升:PreparedStatement是预编译SQL语句,同一SQL语句多次执行的情况下,可以复用,不必每次重新编译和解析。
- 后续的学习我们都是基于PreparedStatement进行实现,更安全、效率更高!
1 | package com.xy.base; |
- 什么是 PreparedStatement?
PreparedStatement是 Java JDBC 中用于执行预编译 SQL 语句的接口。与Statement不同,PreparedStatement允许你在 SQL 语句中使用占位符?,然后将具体的参数值动态地绑定到这些占位符上。
- ? 的作用
?是 SQL 语句中的占位符,表示一个参数的位置。在执行 SQL 语句之前,你需要通过PreparedStatement的方法(如setString、setInt等)将具体的值绑定到这些占位符上。例如:
1
2
3
4 String sql = "SELECT * FROM t_emp WHERE emp_name = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "Andy"); // 将 "Andy" 绑定到第一个占位符
//值得一提的是,这个?排第几个位置就写数字几个,而不是像数组下表一样从0开始
- ? 的机制详解
(1) SQL 语句的预编译
- 当你调用
connection.prepareStatement(sql)时,JDBC 会将 SQL 语句发送到数据库进行预编译。- 预编译后的 SQL 语句会被数据库缓存,后续执行时可以直接使用,无需重新解析和编译,从而提高性能。
(2) 占位符的绑定
- 在预编译阶段,
?只是一个占位符,不会被解析为具体的值。- 在执行 SQL 语句之前,你需要通过
PreparedStatement的方法(如setString、setInt等)将具体的值绑定到占位符上。- 绑定值时,JDBC 会确保值被正确处理,避免 SQL 注入。
(3) 参数的类型安全
PreparedStatement提供了多种setXxx方法(如setString、setInt、setDouble等),用于绑定不同类型的参数。- 这些方法会确保参数值被正确地转换为 SQL 语句中的数据类型。
(4) 防止 SQL 注入
- 当使用
PreparedStatement时,JDBC 会自动对绑定的值进行转义处理,确保它们不会被解释为 SQL 代码。- 例如,如果用户输入的字符串中包含单引号
',JDBC 会将其转义为\',从而避免破坏 SQL 语句的结构。
- PreparedStatement 的工作流程
以下是一个完整的
PreparedStatement使用流程:(1) 定义 SQL 语句
1 String sql = "SELECT * FROM t_emp WHERE emp_name = ?";(2) 创建 PreparedStatement 对象
1 PreparedStatement preparedStatement = connection.prepareStatement(sql);(3) 绑定参数值
1 preparedStatement.setString(1, "Andy");
setString(1, "Andy"):将"Andy"绑定到第一个占位符(?)。如果有多个占位符,可以依次绑定:
1
2 preparedStatement.setString(1, "Andy");
preparedStatement.setInt(2, 30);(4) 执行 SQL 语句
1 ResultSet resultSet = preparedStatement.executeQuery();(5) 处理结果
1
2
3
4
5
6
7 while (resultSet.next()) {
int emp_id = resultSet.getInt("emp_id");
String emp_name = resultSet.getString("emp_name");
double emp_salary = resultSet.getDouble("emp_salary");
int emp_age = resultSet.getInt("emp_age");
System.out.println(emp_id + "\t" + emp_name + "\t" + emp_salary + "\t" + emp_age);
}(6) 释放资源
1
2 resultSet.close();
preparedStatement.close();
- 为什么 PreparedStatement 能防止 SQL 注入?
(1) SQL 注入的原理
SQL 注入是通过在用户输入中插入恶意 SQL 代码,破坏原始 SQL 语句的结构,从而执行非预期的操作。例如:
1 SELECT * FROM t_emp WHERE emp_name = 'abc' OR '1' = '1';
如果用户输入
abc' OR '1' = '1,原始 SQL 语句会被修改为:
1 SELECT * FROM t_emp WHERE emp_name = 'abc' OR '1' = '1';由于
'1' = '1'永远为真,这条 SQL 语句会返回所有员工的数据。(2) PreparedStatement 的防护机制
当使用
PreparedStatement时,JDBC 会将用户输入的值作为参数处理,而不是直接拼接到 SQL 语句中。例如:
1
2
3 String sql = "SELECT * FROM t_emp WHERE emp_name = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "abc' OR '1' = '1");
JDBC 会将
"abc' OR '1' = '1"转义为"abc\' OR \'1\' = \'1",然后将其作为字符串值绑定到 SQL 语句中。最终执行的 SQL 语句是:
1 SELECT * FROM t_emp WHERE emp_name = 'abc\' OR \'1\' = \'1';这样,恶意代码就不会被解释为 SQL 语法,从而避免了 SQL 注入。
PreparedStatement 的优势
防止 SQL 注入:
- 通过参数绑定机制,确保用户输入不会被解释为 SQL 代码。
性能优化:
- SQL 语句在数据库中被预编译,后续执行时可以直接使用缓存,减少解析和编译的开销。
类型安全:
- 通过
setXxx方法绑定参数,确保参数值与 SQL 语句中的数据类型一致。代码可读性:
- 使用占位符
?可以使 SQL 语句更清晰,避免字符串拼接的混乱。
ResultSet
ResultSet是 JDBC API 中的一个接口,用于表示从数据库中执行查询语句所返回的结果集。它提供了一种用于遍历和访问查询结果的方式。- 遍历结果:ResultSet可以使用
next()方法将游标移动到结果集的下一行,逐行遍历数据库查询的结果,返回值为boolean类型,true代表有下一行结果,false则代表没有。 - 获取单列结果:可以通过getXxx的方法获取单列的数据,该方法为重载方法,支持索引和列名进行获取。



