核心概念
- JDBC (Java Database Connectivity): Java 提供的一套标准 API,用于在 Java 程序中执行 SQL 语句,它定义了一组接口和类,让 Java 代码可以与各种数据库进行交互。
- 驱动程序: 每个数据库厂商(如 MySQL, Oracle)都会提供自己的 JDBC 实现,称为“驱动程序”,我们需要将这个驱动程序的 JAR 包引入到我们的 Java 项目中。
- 连接: 在 Java 程序和数据库之间建立一个通信通道。
- Statement / PreparedStatement: 用于向数据库发送 SQL 语句的对象。
Statement: 用于执行静态的 SQL 语句。PreparedStatement: 强烈推荐使用,它用于执行预编译的 SQL 语句,可以防止 SQL 注入攻击,并且对于重复执行的 SQL 效率更高。
- ResultSet: 当执行查询语句时,数据库返回的结果集,它就像一个指向查询结果的指针。
准备工作
在开始编码之前,请确保你已经完成了以下准备工作:

创建 MySQL 数据库和表
打开你的 MySQL 客户端(如 MySQL Workbench, Navicat 或命令行),执行以下 SQL 语句来创建一个数据库和一张用于演示的 users 表。
-- 创建一个名为 `java_test` 的数据库
CREATE DATABASE IF NOT EXISTS java_test;
-- 使用该数据库
USE java_test;
-- 创建一个 `users` 表
CREATE TABLE IF NOT EXISTS users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
password VARCHAR(50) NOT NULL,
email VARCHAR(100),
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入一些初始数据
INSERT INTO users (username, password, email) VALUES
('alice', '123456', 'alice@example.com'),
('bob', 'bobpass', 'bob@example.com');
添加 MySQL JDBC 驱动到你的项目
你需要下载 MySQL 的 JDBC 驱动程序 JAR 包,最简单的方式是使用构建工具(Maven 或 Gradle)。
使用 Maven (推荐)
在你的 pom.xml 文件中添加以下依赖:

<dependencies>
<!-- MySQL Connector/J -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version> <!-- 建议使用最新稳定版 -->
</dependency>
</dependencies>
手动下载
如果你不使用 Maven,可以访问 MySQL Connector/J 下载页面 下载 JAR 包,并将其添加到你的项目的类路径中。
完整代码示例
下面是一个完整的、结构化的 Java 类,它封装了所有增删改查操作,我们将使用 PreparedStatement 来确保代码的安全性和可维护性。
JDBCDemo.java

import java.sql.*;
public class JDBCDemo {
// --- 数据库连接信息 ---
// 注意:如果你的 MySQL 版本 >= 8.0,驱动类名和 URL 可能需要调整
// 驱动类名: com.mysql.cj.jdbc.Driver
// URL: jdbc:mysql://localhost:3306/java_test?useSSL=false&serverTimezone=UTC
private static final String DB_URL = "jdbc:mysql://localhost:3306/java_test";
private static final String USER = "root"; // 你的数据库用户名
private static final String PASS = "your_password"; // 你的数据库密码
public static void main(String[] args) {
// 1. 查询所有用户
System.out.println("--- 查询所有用户 ---");
selectAllUsers();
// 2. 添加一个新用户
System.out.println("\n--- 添加一个新用户 'charlie' ---");
addUser("charlie", "charliepass", "charlie@example.com");
selectAllUsers(); // 再次查询以验证
// 3. 更新一个用户的信息
System.out.println("\n--- 更新用户 'alice' 的邮箱 ---");
updateUserEmail("alice", "alice_new@example.com");
selectAllUsers(); // 再次查询以验证
// 4. 删除一个用户
System.out.println("\n--- 删除用户 'bob' ---");
deleteUser("bob");
selectAllUsers(); // 再次查询以验证
}
/**
* 查询所有用户
*/
public static void selectAllUsers() {
String sql = "SELECT id, username, password, email, created_time FROM users";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
System.out.println("ID\tUsername\tEmail\t\tCreated Time");
System.out.println("--------------------------------------------------");
while (rs.next()) {
// 通过列名获取数据,更安全且可读性更好
int id = rs.getInt("id");
String username = rs.getString("username");
String email = rs.getString("email");
Timestamp createdTime = rs.getTimestamp("created_time");
System.out.printf("%d\t%s\t\t%s\t%s\n", id, username, email, createdTime);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 添加一个新用户
*/
public static void addUser(String username, String password, String email) {
// 使用 ? 作为占位符,防止 SQL 注入
String sql = "INSERT INTO users (username, password, email) VALUES (?, ?, ?)";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
// 使用 PreparedStatement
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 设置参数,索引从 1 开始
pstmt.setString(1, username);
pstmt.setString(2, password);
pstmt.setString(3, email);
// executeUpdate() 用于执行 INSERT, UPDATE, DELETE 等语句
// 返回受影响的行数
int affectedRows = pstmt.executeUpdate();
if (affectedRows > 0) {
System.out.println("用户 '" + username + "' 添加成功!");
} else {
System.out.println("用户添加失败。");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 根据用户名更新用户的邮箱
*/
public static void updateUserEmail(String username, String newEmail) {
String sql = "UPDATE users SET email = ? WHERE username = ?";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, newEmail);
pstmt.setString(2, username);
int affectedRows = pstmt.executeUpdate();
if (affectedRows > 0) {
System.out.println("用户 '" + username + "' 的邮箱已更新为 '" + newEmail + "'。");
} else {
System.out.println("未找到用户 '" + username + "',更新失败。");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 根据用户名删除用户
*/
public static void deleteUser(String username) {
String sql = "DELETE FROM users WHERE username = ?";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, username);
int affectedRows = pstmt.executeUpdate();
if (affectedRows > 0) {
System.out.println("用户 '" + username + "' 已被删除。");
} else {
System.out.println("未找到用户 '" + username + "',删除失败。");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
代码解析
连接数据库 (DriverManager.getConnection)
这是所有操作的第一步,它使用数据库 URL、用户名和密码来建立与数据库的连接。
Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
注意: 连接是有限的资源,用完后必须关闭,我们使用 try-with-resources 语句(try (Connection conn = ...))可以自动关闭连接,非常方便。
增、删、改 (PreparedStatement.executeUpdate)
这三种操作的结构非常相似,都使用 PreparedStatement。
- SQL 语句: 使用 作为参数占位符。
String sql = "INSERT INTO users (username, password, email) VALUES (?, ?, ?)";
- 创建
PreparedStatement:PreparedStatement pstmt = conn.prepareStatement(sql);
- 设置参数:
pstmt.setXxx(index, value)方法用于将 替换为实际的值。index: 参数的位置,从1开始。value: 要设置的值。setString,setInt,setDate等:根据值的类型选择对应的方法。pstmt.setString(1, username); pstmt.setString(2, password); pstmt.setString(3, email);
- 执行更新:
pstmt.executeUpdate()会执行 SQL 语句,并返回一个整数,表示受影响的行数。int affectedRows = pstmt.executeUpdate();
查 (PreparedStatement.executeQuery)
查询操作与增删改略有不同,因为它需要返回一个结果集。
-
SQL 语句: 可以是静态的,也可以使用 作为条件。
String sql = "SELECT id, username, email FROM users WHERE username = ?";
-
创建
PreparedStatement并设置参数: 这一步和增删改一样。 -
执行查询:
pstmt.executeQuery()会执行 SQL 语句,并返回一个ResultSet对象。ResultSet rs = pstmt.executeQuery();
-
遍历结果集:
rs.next()将光标从当前位置向下移动一行,如果下一行存在,则返回true,否则返回false,通常使用while循环来遍历所有行。while (rs.next()) { // 通过列名获取数据,推荐使用这种方式,因为它不依赖列的顺序 int id = rs.getInt("id"); String username = rs.getString("username"); String email = rs.getString("email"); // 也可以通过列的索引获取数据(从1开始) // int id = rs.getInt(1); }
最佳实践
- 使用
PreparedStatement: 始终优先使用PreparedStatement,而不是Statement,它可以有效防止 SQL 注入,并且对于需要多次执行的 SQL,性能更好。 - 使用
try-with-resources: 所有实现了AutoCloseable接口的资源(如Connection,Statement,ResultSet)都应该在try-with-resources语句中声明,以确保它们在使用完毕后能被自动关闭,避免资源泄漏。 - 使用连接池: 在实际应用中,频繁地创建和销毁数据库连接是非常消耗资源的,应该使用数据库连接池(如 HikariCP, Druid, C3P0)来管理连接,连接池可以复用连接,大大提高性能。
- 将配置信息外化: 不要将数据库的用户名、密码等敏感信息硬编码在代码中,应该放在配置文件(如
config.properties,application.yml)中,然后在运行时读取。 - 处理异常: 捕获
SQLException并进行适当的处理(如打印日志、返回错误信息给用户),而不是简单地忽略它。
针对 MySQL 8.0+ 的注意事项
如果你使用的是 MySQL 8.0 或更高版本,驱动类名和连接 URL 可能需要调整:
- 驱动类名:
com.mysql.jdbc.Driver已被弃用,应使用com.mysql.cj.jdbc.Driver。 - 连接 URL: 必须指定时区,建议添加
serverTimezone=UTC,如果不需要 SSL,可以添加useSSL=false。
修改后的连接信息如下:
// 驱动类名
// Class.forName("com.mysql.cj.jdbc.Driver"); // 在旧版JDBC中需要手动加载,新版JDBC驱动会自动加载
// 连接 URL
private static final String DB_URL = "jdbc:mysql://localhost:3306/java_test?useSSL=false&serverTimezone=UTC"; 