文章目录
- 一.JDBC概述
- [1. 基本介绍](#1. 基本介绍)
- [1.1 JDBC的目标](#1.1 JDBC的目标)
- [1.2 JDBC架构](#1.2 JDBC架构)
- [1.3 JDBC驱动类型](#1.3 JDBC驱动类型)
- [1.4 JDBC基本使用步骤](#1.4 JDBC基本使用步骤)
- [2. 示例代码](#2. 示例代码)
- [3. JDBC 带来的好处](#3. JDBC 带来的好处)
- [二.获取数据库连接 5 种方式](#二.获取数据库连接 5 种方式)
- 三.ResultSet
- [3.1 ResultSet 的作用](#3.1 ResultSet 的作用)
- [3.2 ResultSet 的类型](#3.2 ResultSet 的类型)
- [3.3 ResultSet 的并发性](#3.3 ResultSet 的并发性)
- [3.4 常用的 ResultSet 方法](#3.4 常用的 ResultSet 方法)
- [3.5 示例代码](#3.5 示例代码)
- 四.Sql注入
- [4.1 SQL注入的原理](#4.1 SQL注入的原理)
- [4.2 SQL注入的类型](#4.2 SQL注入的类型)
- [4.3 防御SQL注入的方法](#4.3 防御SQL注入的方法)
- 五.Statement
- [5.1 Statement 接口的主要方法](#5.1 Statement 接口的主要方法)
- [5.2 创建 Statement 对象](#5.2 创建 Statement 对象)
- [5.3 使用 Statement 执行SQL语句](#5.3 使用 Statement 执行SQL语句)
- [5.4 安全性和性能问题](#5.4 安全性和性能问题)
- [5.5 资源管理](#5.5 资源管理)
- [六. PreparedStatement](#六. PreparedStatement)
- [6.1 PreparedStatement 的优势](#6.1 PreparedStatement 的优势)
- [6.2 创建 PreparedStatement 对象](#6.2 创建 PreparedStatement 对象)
- [6.3 设置参数](#6.3 设置参数)
- [6.4 执行 PreparedStatement](#6.4 执行 PreparedStatement)
- [6.5 使用示例](#6.5 使用示例)
- [6.6 资源管理](#6.6 资源管理)
- [6.7 总结](#6.7 总结)
🙋♂️ 作者:@whisperrr. 🙋♂️
👀 专栏:JDBC 👀
💥 标题:探索JDBC:Java数据库连接的艺术与魅力💥
❣️ 寄语:比较是偷走幸福的小偷❣️
一.JDBC概述
1. 基本介绍
JDBC(Java Database Connectivity)是Java语言中用于数据库连接和操作的一组标准API,它定义了一套标准的数据库访问接口,为Java程序提供了与数据库连接和执行SQL语句的能力。以下是JDBC的基本介绍:
1.1 JDBC的目标
- 提供一种标准的数据库访问接口,使Java应用程序可以方便地访问各种数据库。
- 实现数据库访问的跨平台性。
- 简化数据库编程。
1.2 JDBC架构
JDBC架构包括以下四个层次:
- JDBC API:这是提供给Java程序员的接口,他们可以使用这些接口来连接数据库,执行SQL语句,处理结果集等。
- JDBC Driver Manager:负责管理不同数据库的JDBC驱动程序,根据请求为应用程序加载合适的驱动程序。
- JDBC Driver API:由数据库厂商提供,实现了JDBC API中定义的接口,用于与特定的数据库进行交互。
- 数据库:存储数据的系统,JDBC API通过驱动程序与之通信。
1.3 JDBC驱动类型
根据实现方式和功能,JDBC驱动程序可以分为以下四类:
- Type 1:JDBC-ODBC Bridge Driver
- 将JDBC调用转换为ODBC调用。
- 需要本地ODBC驱动程序。
- 适用于实验和小型应用。
- Type 2:Native API Driver
- 将JDBC调用转换为特定数据库的本地API调用。
- 依赖于特定数据库的本地库。
- 性能较好,但跨平台性较差。
- Type 3:Network Protocol Driver
- 将JDBC调用转换为网络协议,通常由中间服务器转换为特定数据库的命令。
- 跨平台性好,但可能存在性能瓶颈。
- Type 4:Thin Driver(纯Java驱动)
- 直接将JDBC调用转换为数据库的网络协议。
- 不需要额外的中间件或客户端软件。
- 性能好,跨平台性好。
1.4 JDBC基本使用步骤
- 注册驱动 :通过
Class.forName()
加载驱动类。 - 建立连接 :通过
DriverManager.getConnection()
获取Connection
对象。 - 创建语句 :通过
Connection
对象创建Statement
或PreparedStatement
。 - 执行查询 :通过
Statement
或PreparedStatement
执行SQL语句。 - 处理结果 :处理
ResultSet
对象中的查询结果。 - 关闭连接 :操作完成后,关闭
ResultSet
、Statement
和Connection
对象。
2. 示例代码
java
import java.sql.*;
public class JDBCDemo {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
// 1. 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 建立连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb", "username", "password");
// 3. 创建语句
stmt = conn.createStatement();
// 4. 执行查询
ResultSet rs = stmt.executeQuery("SELECT * FROM example_table");
// 5. 处理结果
while (rs.next()) {
System.out.println(rs.getString("column1") + ", " + rs.getString("column2"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6. 关闭连接
try {
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException se) {
se.printStackTrace();
}
}
}
}
通过以上步骤,Java程序就可以使用JDBC与数据库进行交互了。在实际开发中,通常会使用更高级的数据库访问框架,如Hibernate、MyBatis等,来简化数据库操作和提高开发效率。
3. JDBC 带来的好处
JDBC带来的好处(示意图)
说明:JDBC是Java提供一套用于数据库操作的接口API,Java程序员只需要面向这套接口编程即可。不同的数据库厂商,需要针对这套接口,提供不同实现。
二.获取数据库连接 5 种方式
1.方式1
获取 Driver 实现类对象,属于静态加载,灵活性不够高,依赖性强。
java
public void connect01() throws SQLException {
Driver driver = new Driver();
String url = "jdbc:mysql://localhost:3306/hsp_db02";
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","lrx");
Connection connect = driver.connect(url, properties);
System.out.println(connect);
}
2.方式2
使用反射机制 Class<?> aClass = Class.forName("com.mysql.jdbc.Driver"),动态加载,可以更加灵活
java
public void connect02() throws Exception {
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();
String url = "jdbc:mysql://localhost:3306/hsp_db02";
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","lrx");
Connection connect = driver.connect(url, properties);
System.out.println(connect);
}
3.方式3
使用 DriverManager 替换 Driver,DriverManager 用于管理一组 JDBC 驱动程序的基本服务。
java
public void connect03() throws Exception {
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();
String url = "jdbc:mysql://localhost:3306/hsp_db02";
String user = "root";
String password = "lrx";
DriverManager.registerDriver(driver);
Connection connection
= DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
4.方式4
使用反射加载 Driver 类,使用 DriverManager 替换 Driver ,注册加载的工作在加载 Driver 类时,底层已经完成,相比较方式3,更加简洁
java
public void connect04() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/hsp_db02";
String user = "root";
String password = "lrx";
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
5.方式5
使用配置文件,连接数据库
java
public void connect05() throws Exception {
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
String sql = "insert into news1 values (1,'111'),(2,'222')";
Statement statement = connection.createStatement();
int rows = statement.executeUpdate(sql);
System.out.println(rows > 0 ? "成功" : "失败");
statement.close();
connection.close();
}
三.ResultSet
1.ResultSet 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成。
2.ResultSet 对象保持一个光标指向其当前的数据行。 最初,光标位于第一行之前。
- next 方法将光标移动到下一行,并且由于在 ResultSet 对象中没有更多行时返回 false, 因此可以在while循环中使用循环来遍历结果集。
以下是对 ResultSet
的详细介绍:
3.1 ResultSet 的作用
- 存储 SQL 查询返回的数据。
- 允许应用程序通过移动光标来遍历结果集中的行。
- 提供了访问当前行中不同列的数据的方法。
3.2 ResultSet 的类型
ResultSet
有几种类型,这些类型定义了结果集的滚动能力和更新能力:
TYPE_FORWARD_ONLY
:光标只能向前移动。TYPE_SCROLL_INSENSITIVE
:光标可以向前和向后移动,但不反映对数据库的更改。TYPE_SCROLL_SENSITIVE
:光标可以向前和向后移动,并反映对数据库的更改。
3.3 ResultSet 的并发性
ResultSet
的并发性定义了其他线程或进程对结果集所做的更改是否可见:
CONCUR_READ_ONLY
:结果集是只读的,不能更新。CONCUR_UPDATABLE
:结果集是可更新的。
3.4 常用的 ResultSet 方法
以下是一些常用的 ResultSet
方法:
next()
:将光标从当前位置向前移动一行。previous()
:将光标从当前位置向后移动一行(仅在可滚动的结果集中可用)。absolute(int row)
:将光标移动到指定的行号。relative(int rows)
:相对于当前位置移动光标。getRow()
:返回当前行的行号。getString(String columnLabel)
:以 String 形式获取当前行指定列的值。getInt(int columnIndex)
:以 int 形式获取当前行指定列的值。getObject(int columnIndex)
:以 Object 形式获取当前行指定列的值。updateString(int columnIndex, String x)
:更新当前行指定列的值为 String。updateRow()
:更新结果集中的当前行。insertRow()
:在结果集中插入一行。deleteRow()
:从结果集中删除当前行。close()
:关闭ResultSet
对象并释放与之关联的资源。
3.5 示例代码
以下是如何使用 ResultSet
的一个简单示例:
java
import java.sql.*;
public class ResultSetExample {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 加载驱动、建立连接、创建语句等步骤省略
// 执行查询
stmt = conn.createStatement();
rs = stmt.executeQuery("SELECT id, name, age FROM users");
// 遍历结果集
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.println("ID: " + id + ", Name: " + name + ", Age: " + age);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException se) {
se.printStackTrace();
}
}
}
}
底层存储:
在处理 ResultSet
时,需要注意资源的正确关闭,以避免潜在的资源泄漏问题。在实际应用中,通常会使用 try-with-resources 语句来自动关闭实现了 AutoCloseable
接口的资源。
四.Sql注入
SQL注入(SQL Injection)是一种常见的网络攻击技术,它主要针对基于SQL语言的数据库系统。攻击者通过在Web应用的输入字段中插入恶意的SQL代码,从而欺骗服务器执行非预期的SQL命令。以下是关于SQL注入的详细介绍:
4.1 SQL注入的原理
当应用程序直接将用户输入的数据拼接到SQL查询语句中,而没有进行适当的验证或转义时,就可能出现SQL注入漏洞。例如:
java
String username = request.getParameter("username");
String password = request.getParameter("password");
String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
如果用户输入的数据包含SQL关键字或特殊字符,如单引号('),它们可能会改变原始SQL语句的结构,导致安全漏洞。例如,如果用户输入的username
是' OR '1'='1
,那么拼接后的SQL语句将变成:
sql
SELECT * FROM users WHERE username = '' OR '1'='1' --' AND password = '...'
这里的--
是SQL中的注释符号,它将忽略后面的内容,从而使攻击者绕过身份验证。
4.2 SQL注入的类型
- 基于错误的SQL注入:攻击者故意输入错误的SQL语法,通过错误信息了解数据库结构。
- 基于布尔的SQL注入:攻击者通过真或假的布尔表达式来推断数据库信息。
- 基于时间的SQL注入:攻击者利用SQL语句的执行时间来推断数据库信息。
- 盲注:攻击者无法直接从服务器获取信息,只能通过服务器响应的差异来推断信息。
4.3 防御SQL注入的方法
-
使用预编译的SQL语句(PreparedStatement):这是预防SQL注入最有效的方法。预编译的语句会先对SQL模板进行编译,然后再将用户输入作为参数绑定到模板中,从而避免了直接拼接SQL语句。
javaString sql = "SELECT * FROM users WHERE username = ? AND password = ?"; PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setString(1, username); pstmt.setString(2, password); ResultSet rs = pstmt.executeQuery();
-
输入验证:对所有用户输入进行严格的验证,只接受符合预期格式的数据。
-
使用存储过程:在数据库层面使用存储过程可以减少SQL注入的风险,因为存储过程可以限制输入类型和执行权限。
-
最小权限原则:数据库连接应该使用最小权限的账户,以减少攻击者成功注入SQL后的潜在损害。
-
错误处理:不要在应用程序中显示数据库错误信息给用户,以免泄露数据库结构信息。
-
使用ORM框架 :对象关系映射(ORM)框架通常会处理SQL语句的构造,减少SQL注入的风险。
通过采取上述措施,可以有效地减少应用程序遭受SQL注入攻击的风险。
五.Statement
在Java的JDBC API中,Statement
是一个接口,它用于执行不带参数的SQL语句并返回执行结果。以下是对Statement
的详细介绍:
5.1 Statement 接口的主要方法
execute(String sql)
: 执行给定的SQL语句,该语句可能返回多个结果集,也可能不返回结果集,例如DDL语句。executeQuery(String sql)
: 执行查询数据库的SQL语句,并返回单个ResultSet
对象。executeUpdate(String sql)
: 执行更新数据库的SQL语句(INSERT、UPDATE、DELETE等),并返回一个整数,表示受影响的行数。addBatch(String sql)
: 将给定的SQL命令添加到当前的命令批次中。executeBatch()
: 执行当前批次中的所有命令,并返回一个整数数组,表示每条命令受影响的行数。close()
: 立即释放此Statement
对象的数据库和JDBC资源,而不是等待它们自动释放。
5.2 创建 Statement 对象
要使用Statement
,首先需要通过Connection
对象创建它:
java
Connection conn = DriverManager.getConnection(url, username, password);
Statement stmt = conn.createStatement();
5.3 使用 Statement 执行SQL语句
以下是如何使用Statement
执行不同类型的SQL语句的示例:
java
// 执行查询
String query = "SELECT * FROM employees";
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
// 处理结果集
}
// 执行更新
String update = "UPDATE employees SET salary = salary * 1.1 WHERE department = 'Engineering'";
int rowsUpdated = stmt.executeUpdate(update);
// 执行多个更新
stmt.addBatch("INSERT INTO employees (name, department) VALUES ('Alice', 'HR')");
stmt.addBatch("INSERT INTO employees (name, department) VALUES ('Bob', 'Finance')");
int[] rowsAffected = stmt.executeBatch();
5.4 安全性和性能问题
虽然Statement
接口使用方便,但它存在一些安全和性能问题:
- SQL注入风险 :由于
Statement
直接将用户输入拼接到SQL语句中,因此容易受到SQL注入攻击。 - 性能问题 :每次执行SQL语句时,
Statement
都需要重新编译SQL语句,这可能会降低性能。
为了解决这些问题,推荐使用PreparedStatement
,它是Statement
的子接口,提供了更好的安全性和性能: - 防止SQL注入 :
PreparedStatement
使用参数化查询,可以有效地防止SQL注入。 - 性能提升 :
PreparedStatement
可以预编译SQL语句,多次执行时不需要重新编译。
5.5 资源管理
在使用完Statement
对象后,应该及时关闭它以释放数据库资源:
java
stmt.close();
在实际应用中,通常使用try-with-resources语句来自动关闭实现了AutoCloseable
接口的资源:
java
try (Statement stmt = conn.createStatement()) {
// 使用stmt执行SQL语句
}
// stmt会在try块结束时自动关闭
六. PreparedStatement
PreparedStatement
是 Java JDBC API 中的一个接口,它是 Statement
的子接口,提供了比 Statement
更强大的功能,尤其是在安全性、性能和灵活性方面。以下是 PreparedStatement
的详细介绍:
6.1 PreparedStatement 的优势
- 参数化查询 :
PreparedStatement
允许你使用占位符(?
)来代替直接在 SQL 语句中插入值,这样可以有效地防止 SQL 注入攻击。 - 性能提升 :使用
PreparedStatement
可以预编译 SQL 语句,多次执行相同的 SQL 语句时,不需要再次编译,从而提高性能。 - 类型安全 :
PreparedStatement
提供了设置参数类型的方法,确保了数据类型的一致性。
6.2 创建 PreparedStatement 对象
要创建 PreparedStatement
对象,需要通过 Connection
对象的 prepareStatement
方法:
java
Connection conn = DriverManager.getConnection(url, username, password);
String sql = "SELECT * FROM employees WHERE department = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
6.3 设置参数
在执行 SQL 语句之前,需要为 PreparedStatement
中的占位符设置值:
java
pstmt.setString(1, "Engineering"); // 设置第一个占位符的值为 "Engineering"
参数的索引从 1 开始。
6.4 执行 PreparedStatement
PreparedStatement
提供了与 Statement
类似的方法来执行 SQL 语句:
execute()
: 执行任何类型的 SQL 语句。executeQuery()
: 执行查询语句并返回ResultSet
。executeUpdate()
: 执行 INSERT、UPDATE 或 DELETE 语句,并返回受影响的行数。
java
ResultSet rs = pstmt.executeQuery(); // 执行查询
int rowsUpdated = pstmt.executeUpdate(); // 执行更新
6.5 使用示例
以下是一个使用 PreparedStatement
的完整示例:
java
try (Connection conn = DriverManager.getConnection(url, username, password);
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM employees WHERE department = ?")) {
pstmt.setString(1, "Engineering");
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
// 处理结果集
}
} catch (SQLException e) {
e.printStackTrace();
}
6.6 资源管理
与 Statement
一样,使用 PreparedStatement
后,应该关闭它以释放数据库资源。使用 try-with-resources 语句可以自动关闭资源:
java
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 使用 pstmt 执行 SQL 语句
}
// pstmt 在 try 块结束时自动关闭
6.7 总结
PreparedStatement
是处理数据库操作时推荐使用的接口,因为它提供了更好的安全性、性能和易用性。在处理用户输入和执行多次相同的 SQL 语句时,它尤其有用。