- JDBC 简介
- 准备工作(获取驱动、配置环境)
- 核心步骤(完整的代码示例)
- 代码详解
- 使用
try-with-resources优化资源管理 - 连接池(Connection Pool)
- 完整示例(增删改查)
- 总结与最佳实践
JDBC 简介
JDBC (Java Database Connectivity) 是 Java API 的一部分,它定义了客户端如何访问数据库的标准,你可以把它想象成一座“桥梁”,连接 Java 应用程序和各种不同的数据库(如 MySQL, Oracle, SQL Server 等)。

JDBC 的核心思想是接口与实现分离:
- Java (Sun/Oracle) 提供一套标准的接口(
java.sql,javax.sql包)。 - 数据库厂商(如 MySQL, Oracle) 提供这些接口的具体实现,这个实现就是 JDBC 驱动。
我们的 Java 代码只需要调用标准的 JDBC 接口,而真正与数据库通信的工作则由对应的 JDBC 驱动来完成。
准备工作
在开始编码之前,你需要准备好两样东西:
a. 获取 JDBC 驱动
你需要为你所使用的数据库下载对应的 JDBC 驱动(通常是 .jar 文件),这里以 MySQL 为例。

- 访问 MySQL 官方驱动下载页面。
- 选择 "Platform Independent" (平台独立) 版本。
- 下载
.zip文件并解压。 - 在你的 Java 项目中,将解压后的
mysql-connector-j-8.x.x.jar文件添加到项目的 Classpath 中。
在 Maven 项目中(推荐),你只需在 pom.xml 中添加依赖:
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version> <!-- 请使用最新的稳定版本 -->
</dependency>
b. 准备数据库
确保你有一个可用的数据库服务器,并且已经创建了一个数据库和一张用于测试的表。
-- 创建一个名为 my_test_db 的数据库
CREATE DATABASE my_test_db;
-- 使用该数据库
USE my_test_db;
-- 创建一张名为 users 的表
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入一些测试数据
INSERT INTO users (username, email) VALUES ('Alice', 'alice@example.com');
INSERT INTO users (username, email) VALUES ('Bob', 'bob@example.com');
核心步骤(完整的代码示例)
下面是一个最基础的 Java 程序,它连接到 MySQL 数据库,并查询 users 表中的所有数据。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcBasicExample {
// 1. 定义数据库连接信息
private static final String DB_URL = "jdbc:mysql://localhost:3306/my_test_db?useSSL=false&serverTimezone=UTC";
private static final String USER = "root"; // 你的数据库用户名
private static final String PASS = "your_password"; // 你的数据库密码
public static void main(String[] args) {
// 2. 加载 JDBC 驱动 (对于较新版本的JDBC驱动,这步通常是可选的)
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
System.err.println("找不到 MySQL JDBC 驱动,请确保你的 Classpath 中有驱动文件。");
e.printStackTrace();
return;
}
// 3. 获取数据库连接
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection(DB_URL, USER, PASS);
// 4. 创建 Statement 对象
stmt = conn.createStatement();
// 5. 执行 SQL 查询
String sql = "SELECT id, username, email FROM users";
rs = stmt.executeQuery(sql);
// 6. 处理结果集
System.out.println("ID\tUsername\tEmail");
System.out.println("---------------------------------");
while (rs.next()) {
// 通过列名获取数据,更具可读性
int id = rs.getInt("id");
String username = rs.getString("username");
String email = rs.getString("email");
// 输出数据
System.out.println(id + "\t" + username + "\t\t" + email);
}
} catch (SQLException e) {
System.err.println("数据库操作出错!");
e.printStackTrace();
} finally {
// 7. 关闭资源 (非常重要!)
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
代码详解
-
定义连接信息:将数据库的 URL、用户名和密码定义为常量,方便管理和修改。
(图片来源网络,侵删)DB_URL:jdbc:mysql://: 协议和子协议,表示使用 MySQL 的 JDBC 连接。localhost:3306: 数据库服务器的地址和端口。/my_test_db: 要连接的数据库。?useSSL=false&serverTimezone=UTC: URL 参数。useSSL=false用于禁用 SSL(在开发环境中常见),serverTimezone=UTC指定时区,避免警告。
-
加载驱动:
Class.forName("com.mysql.cj.jdbc.Driver")。这行代码的作用是让 JVM 加载 MySQL 驱动类,并执行其静态代码块,完成驱动的注册,在 JDBC 4.0 之后,只要驱动 jar 在 classpath 中,这步通常是自动完成的,但显式写出仍然是好习惯。
-
获取连接:
DriverManager.getConnection(url, user, password)。- 这是建立与数据库实际连接的核心方法,它会尝试使用已注册的驱动来解析 URL 并建立连接,成功后,返回一个
Connection对象,这是所有数据库操作的基础。
- 这是建立与数据库实际连接的核心方法,它会尝试使用已注册的驱动来解析 URL 并建立连接,成功后,返回一个
-
创建 Statement:
conn.createStatement()。Connection对象用于创建Statement对象。Statement用于将 SQL 语句发送到数据库。
-
执行 SQL:
stmt.executeQuery(sql): 用于执行 查询 操作,返回一个ResultSet对象,该对象包含了查询结果。stmt.executeUpdate(sql): 用于执行 更新 操作(如INSERT,UPDATE,DELETE),返回一个整数,表示受影响的行数。
-
处理结果集:
rs.next(): 将光标从当前位置向下移动一行,如果下一行存在,则返回true,否则返回false,通常用while循环遍历所有行。rs.getXXX("column_name"): 从当前行中获取指定列的数据。XXX是数据类型,如getString,getInt,getDate等,推荐使用列名,因为即使 SQL 查询的列顺序改变,代码也不需要修改。
-
关闭资源:
- 非常重要! 数据库连接、
Statement和ResultSet都是有限资源(尤其是在高并发下),必须在使用后关闭。 - 关闭顺序:先关闭
ResultSet,再关闭Statement,最后关闭Connection,后创建的先关闭。
- 非常重要! 数据库连接、
使用 try-with-resources 优化资源管理
手动关闭资源容易出错(例如忘记关闭或关闭时抛出异常),从 Java 7 开始,推荐使用 try-with-resources 语句,它可以自动关闭实现了 AutoCloseable 接口的对象。
// try-with-resources 会自动在 try 块结束时关闭 conn, stmt, rs
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT id, username, email FROM users")) {
// 处理结果集
System.out.println("ID\tUsername\tEmail");
System.out.println("---------------------------------");
while (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
String email = rs.getString("email");
System.out.println(id + "\t" + username + "\t\t" + email);
}
} catch (SQLException e) {
System.err.println("数据库操作出错!");
e.printStackTrace();
}
这种方式更简洁、更安全,是现代 Java 编程的最佳实践。
连接池(Connection Pool)
每次操作数据库都创建和销毁连接是非常消耗资源的。连接池应运而生。
连接池在程序启动时预先创建一定数量的数据库连接,并将它们存放在一个“池”中,当需要访问数据库时,从池中获取一个连接,使用完毕后再放回池中,而不是销毁,这样可以极大地提高性能。
常用的连接池库:
- HikariCP: 目前性能最好的连接池,是 Spring Boot 2.x 的默认选择。
- Apache DBCP: Apache 旗下的一个老牌连接池。
- C3P0: 另一个流行的连接池。
使用 HikariCP 的示例:
-
添加 Maven 依赖:
<dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> <!-- 使用最新版本 --> </dependency> -
配置并使用连接池:
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class JdbcWithPoolExample { public static void main(String[] args) { // 1. 配置连接池 HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/my_test_db?useSSL=false&serverTimezone=UTC"); config.setUsername("root"); config.setPassword("your_password"); config.setDriverClassName("com.mysql.cj.jdbc.Driver"); // 连接池参数 config.setMaximumPoolSize(10); // 最大连接数 config.setMinimumIdle(5); // 最小空闲连接数 config.setConnectionTimeout(30000); // 连接超时时间 (ms) // 2. 创建数据源 HikariDataSource dataSource = new HikariDataSource(config); // 3. 从连接池获取连接 try (Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT id, username, email FROM users")) { // 处理结果集 (代码与之前相同) System.out.println("ID\tUsername\tEmail"); System.out.println("---------------------------------"); while (rs.next()) { int id = rs.getInt("id"); String username = rs.getString("username"); String email = rs.getString("email"); System.out.println(id + "\t" + username + "\t\t" + email); } } catch (SQLException e) { e.printStackTrace(); } } }在实际项目中,你通常会将
HikariDataSource的创建和管理交给 Spring 或 Spring Boot 等框架,它们会自动为你配置好连接池。
完整示例(增删改查)
下面是一个结合了 try-with-resources 和 PreparedStatement(防止 SQL 注入)的完整 CRUD 示例。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
public class JdbcCrudExample {
private static final String DB_URL = "jdbc:mysql://localhost:3306/my_test_db?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASS = "your_password";
public static void main(String[] args) {
// 创建一个新用户
createUser("Charlie", "charlie@example.com");
// 查询所有用户
readAllUsers();
// 更新一个用户
updateUser(1, "Alice Updated", "alice.updated@example.com");
// 再次查询所有用户,查看更新结果
readAllUsers();
// 删除一个用户
deleteUser(2);
// 最后再查询一次
readAllUsers();
}
// CREATE - 创建用户
public static void createUser(String username, String email) {
String sql = "INSERT INTO users (username, email) VALUES (?, ?)";
// try-with-resources 会自动关闭 PreparedStatement
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, username);
pstmt.setString(2, email);
int affectedRows = pstmt.executeUpdate();
System.out.println(affectedRows + " 行已插入。");
} catch (SQLException e) {
e.printStackTrace();
}
}
// READ - 查询所有用户
public static void readAllUsers() {
String sql = "SELECT id, username, email, created_at FROM users";
System.out.println("\n--- 当前所有用户 ---");
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
System.out.println("ID\tUsername\t\tEmail\t\t\tCreated At");
System.out.println("-----------------------------------------------------------------");
while (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
String email = rs.getString("email");
Timestamp createdAt = rs.getTimestamp("created_at");
System.out.printf("%d\t%-15s\t%-20s\t%s%n", id, username, email, createdAt);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
// UPDATE - 更新用户
public static void updateUser(int id, String newUsername, String newEmail) {
String sql = "UPDATE users SET username = ?, email = ? WHERE id = ?";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, newUsername);
pstmt.setString(2, newEmail);
pstmt.setInt(3, id);
int affectedRows = pstmt.executeUpdate();
System.out.println("\n更新操作完成,影响了 " + affectedRows + " 行。");
} catch (SQLException e) {
e.printStackTrace();
}
}
// DELETE - 删除用户
public static void deleteUser(int id) {
String sql = "DELETE FROM users WHERE id = ?";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
int affectedRows = pstmt.executeUpdate();
System.out.println("\n删除操作完成,影响了 " + affectedRows + " 行。");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
总结与最佳实践
- 总是使用
try-with-resources:它能确保资源被正确关闭,避免内存泄漏。 - 优先使用
PreparedStatement:- 防止 SQL 注入:它对输入参数进行转义,是安全的。
- 性能更优:对于重复执行的 SQL,数据库可以预编译并缓存执行计划。
- 使用连接池:在生产环境中,绝对不要直接使用
DriverManager获取连接,务必使用 HikariCP 等高性能连接池。 - 不要硬编码连接信息:将数据库 URL、用户名、密码等配置放在外部的配置文件(如
application.properties)中。 - 使用 ORM 框架:对于复杂的项目,直接写 JDBC 代码会很繁琐,可以考虑使用 MyBatis 或 JPA (Hibernate) 等框架,它们能将对象映射到数据库表,大大简化数据访问层的代码。
