1. JDBC 核心概念
1.1 什么是 JDBC?
JDBC(Java Database Connectivity,Java 数据库连接)是 Java 官方提供的一套用于操作关系型数据库的标准 API,它定义了 Java 程序与数据库交互的统一接口,屏蔽了不同数据库(如 MySQL、Oracle、SQL Server)的底层实现差异,让开发者无需针对特定数据库编写不同代码。
简单来说:JDBC 是 Java 程序和数据库之间的 “桥梁”—— 通过 JDBC,Java 可以发送 SQL 语句到数据库,接收执行结果,并处理数据。
1.2 JDBC 的核心作用
- 统一接口:无论操作 MySQL 还是 Oracle,都使用相同的 JDBC 方法(如
Connection、Statement),降低跨数据库开发的成本。 - 解耦:Java 程序无需依赖具体数据库的实现,只需引入对应数据库的 “驱动”(Driver)即可。
- 原生交互:直接操作 SQL,保留数据库的原生功能,适合需要精细控制 SQL 的场景。
1.3 JDBC 依赖的核心组件
JDBC 的使用依赖 “Java 标准 API + 数据库驱动” 两部分:
- Java 标准 API:JDK 自带的
java.sql和javax.sql包下的接口 / 类(如Connection、Statement),定义了规范。 - 数据库驱动:由数据库厂商提供的实现类(如 MySQL 的
com.mysql.cj.jdbc.Driver),负责将 JDBC 接口与数据库底层协议关联,是 JDBC 能操作具体数据库的关键。
2. JDBC 核心组件详解
JDBC 的核心操作围绕以下 5 个核心接口 / 类展开,需掌握每个组件的作用和常用方法:
| 组件 | 全类名 | 核心作用 |
|---|---|---|
| DriverManager | java.sql.DriverManager | 1. 注册数据库驱动;2. 管理驱动列表;3. 获取数据库连接(Connection) |
| Connection | java.sql.Connection | 代表 Java 与数据库的 “一次连接”,是操作数据库的基础,可创建执行 SQL 的对象 |
| Statement 家族 | java.sql.Statement | 用于向数据库发送 SQL 语句并执行 |
| ResultSet | java.sql.ResultSet | 存储 SQL 查询(SELECT)的结果集,提供遍历和获取数据的方法 |
| SQLException | java.sql.SQLException | 处理 JDBC 操作中所有与数据库相关的异常(如连接失败、SQL 语法错误) |
2.1 DriverManager(驱动管理类)
核心功能:获取数据库连接
获取连接的关键是 数据库 URL、用户名、密码,不同数据库的 URL 格式不同:
- MySQL 8.0+:jdbc:mysql://localhost:3306/数据库名?useSSL=false&serverTimezone=UTC
localhost:数据库服务器地址(本地为localhost,远程为 IP)3306:MySQL 默认端口useSSL=false:禁用 SSL(开发环境常用)serverTimezone=UTC:指定时区(MySQL 8.0+ 必须配置)
- Oracle:
jdbc:oracle:thin:@localhost:1521:数据库服务名 - SQL Server:
jdbc:sqlserver://localhost:1433;databaseName=数据库名
常用方法:
// 1. 注册驱动(Java 6+ 后可省略,驱动会自动加载)
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 获取数据库连接(核心方法)
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC",
"root", // 数据库用户名
"123456" // 数据库密码
);
2.2 Connection(数据库连接接口)
核心功能:
- 创建执行 SQL 的
Statement/PreparedStatement对象; - 控制事务(提交、回滚);
- 关闭连接(释放资源)。
常用方法:
| 方法 | 作用 |
|---|---|
createStatement() | 创建普通 Statement 对象(执行静态 SQL) |
prepareStatement(String sql) | 创建 PreparedStatement 对象(执行预编译 SQL,防注入) |
setAutoCommit(boolean autoCommit) | 设置事务是否自动提交(默认 true,手动控制事务需设为 false) |
commit() | 提交事务 |
rollback() | 回滚事务(发生异常时调用) |
close() | 关闭连接(必须执行,避免资源泄露) |
2.3 Statement 家族(SQL 执行接口)
JDBC 提供 3 种执行 SQL 的接口,适用于不同场景:
| 接口 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Statement | 执行静态 SQL(SQL 语句固定不变) | 简单直接 | 1. SQL 硬编码;2. 存在 SQL 注入风险;3. 性能低(每次执行都编译) |
| PreparedStatement | 执行动态 SQL(SQL 含参数,如 ?) | 1. 预编译 SQL(性能高);2. 防 SQL 注入;3. 代码可复用 | 仅支持参数化 SQL,不支持存储过程 |
| CallableStatement | 调用数据库存储过程(如 call proc(?)) | 支持存储过程的调用和输出参数获取 | 语法复杂,依赖数据库存储过程 |
常用方法(以 PreparedStatement 为例):
// 1. 预编译 SQL(? 是参数占位符,不能加引号)
String sql = "INSERT INTO user(name, age) VALUES (?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
// 2. 给 ? 赋值(索引从 1 开始!)
pstmt.setString(1, "张三"); // 第一个 ? 赋值为字符串
pstmt.setInt(2, 20); // 第二个 ? 赋值为整数
// 3. 执行 SQL(根据 SQL 类型选择方法)
int rows = pstmt.executeUpdate(); // 执行增删改(返回受影响行数)
ResultSet rs = pstmt.executeQuery(); // 执行查询(返回结果集)
2.4 ResultSet(结果集接口)
核心功能:存储 SELECT 查询的结果,提供遍历和获取数据的方法。
常用方法:
| 方法 | 作用 |
|---|---|
next() | 游标向下移动一行,返回 true 表示有数据,false 表示无数据(遍历核心) |
getString(int columnIndex) | 获取当前行指定索引列的字符串(索引从 1 开始) |
getString(String columnName) | 获取当前行指定列名的字符串(更推荐,避免列顺序变化) |
getInt(String columnName) | 获取当前行指定列名的整数 |
close() | 关闭结果集(释放资源) |
遍历结果集示例:
// 假设查询 user 表,有 id(int)、name(varchar)、age(int) 列
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);
}
2.5 SQLException(异常类)
JDBC 操作中所有与数据库相关的异常(如连接失败、SQL 语法错误、参数类型不匹配)都会抛出 SQLException,需强制捕获或声明抛出。
常用方法(获取异常信息):
getMessage():获取异常描述信息;getSQLState():获取 SQL 状态码(如42S22表示列不存在);getErrorCode():获取数据库厂商自定义的错误码(如 MySQL 的1045表示用户名密码错误)。
3. JDBC 标准操作流程(必背)
JDBC 操作数据库的流程固定,无论增删改查,都遵循以下 6 个步骤(以查询为例):
步骤 1:准备环境(导入驱动包)
使用 JDBC 需引入对应数据库的驱动包:
-
Maven 项目(推荐):在pom.xml中添加依赖(以 MySQL 8.0 为例):
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.32</version> <!-- 版本需与数据库匹配 --> </dependency> -
非 Maven 项目:下载驱动 Jar 包(如
mysql-connector-java-8.0.32.jar),手动添加到项目依赖中。
步骤 2:加载驱动(Java 6+ 可省略)
Java 6 及以上版本会通过 SPI 机制自动加载驱动,无需手动调用 Class.forName(),但为了兼容旧代码,了解即可。
步骤 3:获取数据库连接(Connection)
通过 DriverManager.getConnection() 获取连接,需传入 URL、用户名、密码。
步骤 4:创建 Statement/PreparedStatement 对象
根据 SQL 类型选择合适的对象(查询用 PreparedStatement,静态 SQL 用 Statement)。
步骤 5:执行 SQL 并处理结果
- 增删改操作:调用
executeUpdate(),返回受影响的行数; - 查询操作:调用
executeQuery(),返回ResultSet,需遍历处理。
步骤 6:释放资源(关键)
JDBC 资源(ResultSet、Statement、Connection)属于 “非内存资源”,必须手动关闭,否则会导致数据库连接泄露,耗尽数据库连接池。
释放顺序:ResultSet → Statement → Connection(先创建的后关闭)
推荐使用 try-with-resources(Java 7+ 特性):
自动关闭实现了 AutoCloseable 接口的资源(JDBC 核心组件均实现了该接口),无需手动写 finally,代码更简洁。
4. JDBC 事务管理
JDBC 事务默认是 自动提交(autoCommit = true),即每执行一条 INSERT/UPDATE/DELETE 语句就自动提交到数据库。但实际开发中,需保证 “一组操作要么全成功,要么全失败”(如转账:扣钱和加钱必须同时成功),此时需手动控制事务。
事务控制步骤:
- 获取
Connection后,设置autoCommit = false(关闭自动提交); - 执行一组 SQL 操作;
- 若所有操作成功,调用
conn.commit()提交事务; - 若发生异常,调用
conn.rollback()回滚事务(恢复到操作前状态); - 最终在
finally中关闭资源。
事务示例(转账):
try (Connection conn = DriverManager.getConnection(URL, USER, PWD)) {
conn.setAutoCommit(false); // 关闭自动提交,开启手动事务
// 1. 扣减用户 A 的钱
String sql1 = "UPDATE account SET balance = balance - 100 WHERE name = ?";
try (PreparedStatement pstmt1 = conn.prepareStatement(sql1)) {
pstmt1.setString(1, "A");
pstmt1.executeUpdate();
}
// 2. 增加用户 B 的钱
String sql2 = "UPDATE account SET balance = balance + 100 WHERE name = ?";
try (PreparedStatement pstmt2 = conn.prepareStatement(sql2)) {
pstmt2.setString(1, "B");
pstmt2.executeUpdate();
}
conn.commit(); // 所有操作成功,提交事务
System.out.println("转账成功!");
} catch (SQLException e) {
// 若有异常,回滚事务(注意:需在 catch 中获取 Connection,需调整代码结构)
System.out.println("转账失败,回滚事务!");
e.printStackTrace();
}
事务保存点(Savepoint)
若需要 “部分回滚”(如一组操作中,前 2 步成功,第 3 步失败,仅回滚第 3 步),可使用 Savepoint:
try (Connection conn = DriverManager.getConnection(URL, USER, PWD)) {
conn.setAutoCommit(false);
PreparedStatement pstmt = null;
// 操作 1:成功
pstmt = conn.prepareStatement("UPDATE account SET balance = balance - 50 WHERE name = 'A'");
pstmt.executeUpdate();
// 设置保存点(标记操作 1 后的状态)
Savepoint sp = conn.setSavepoint();
// 操作 2:失败(假设 SQL 错误)
try {
pstmt = conn.prepareStatement("UPDATE account SET balance = balance + 50 WHERE name = 'C'"); // C 不存在
pstmt.executeUpdate();
} catch (SQLException e) {
// 仅回滚到保存点(操作 1 不回滚,操作 2 回滚)
conn.rollback(sp);
System.out.println("操作 2 失败,回滚到保存点!");
}
conn.commit(); // 提交操作 1 的结果
}
5. JDBC 优缺点
优点:
- 跨数据库兼容性:统一接口,切换数据库只需更换驱动和 URL;
- 原生 SQL 支持:直接操作 SQL,保留数据库的所有功能(如存储过程、函数);
- 轻量级:无需依赖额外框架,仅需驱动包;
- 性能较高:底层直接与数据库交互,无中间层开销。
缺点:
- 代码冗余:每次操作都需重复写 “获取连接→创建 Statement→释放资源”;
- SQL 硬编码:SQL 语句写在 Java 代码中,修改需重新编译;
- 手动管理资源:容易遗漏关闭资源,导致连接泄露;
- 结果集处理繁琐:需手动将
ResultSet映射为 Java 对象(如 User、Order)。
注意:JDBC 的缺点催生了 ORM 框架(如 MyBatis、Hibernate),这些框架基于 JDBC 封装,简化了代码编写,但 JDBC 是底层基础,必须掌握。
6. 完整示例代码(查询 + 增删改)
6.1 查询示例(获取用户列表)
import java.sql.*;
public class JdbcQueryExample {
// 数据库连接信息(建议配置在配置文件中,此处为演示)
private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PWD = "123456";
public static void main(String[] args) {
// try-with-resources 自动关闭 ResultSet、PreparedStatement、Connection
try (Connection conn = DriverManager.getConnection(URL, USER, PWD);
// 预编译查询 SQL(? 是参数占位符)
PreparedStatement pstmt = conn.prepareStatement("SELECT id, name, age FROM user WHERE age > ?");
) {
// 给 ? 赋值(索引从 1 开始)
pstmt.setInt(1, 18);
// 执行查询,获取结果集
try (ResultSet rs = pstmt.executeQuery()) {
// 遍历结果集
while (rs.next()) {
int id = rs.getInt("id"); // 通过列名获取
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.printf("id: %d, name: %s, age: %d%n", id, name, age);
}
}
} catch (SQLException e) {
System.out.println("查询失败!");
e.printStackTrace();
}
}
}
6.2 增删改示例(插入用户)
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JdbcUpdateExample {
private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PWD = "123456";
public static void main(String[] args) {
// 插入用户的 SQL
String sql = "INSERT INTO user(name, age) VALUES (?, ?)";
try (Connection conn = DriverManager.getConnection(URL, USER, PWD);
PreparedStatement pstmt = conn.prepareStatement(sql)
) {
// 赋值参数
pstmt.setString(1, "李四");
pstmt.setInt(2, 22);
// 执行增删改,返回受影响行数
int rows = pstmt.executeUpdate();
if (rows > 0) {
System.out.println("插入成功!受影响行数:" + rows);
} else {
System.out.println("插入失败!");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
7. 关键注意事项
- URL 格式正确性:不同数据库的 URL 格式不同,MySQL 8.0+ 必须配置
serverTimezone; - 参数索引从 1 开始:
PreparedStatement的?占位符索引从 1 开始,而非 0; - 资源必须关闭:优先使用
try-with-resources,避免资源泄露; - 防 SQL 注入:严禁使用
Statement拼接 SQL(如sql = "SELECT * FROM user WHERE name = '" + name + "'"),必须用PreparedStatement的参数化查询; - 事务手动控制:涉及多步操作时,必须关闭自动提交(
setAutoCommit(false)),并在异常时回滚。

3139

被折叠的 条评论
为什么被折叠?



