- 环境准备:安装必要的软件和库。
- JDBC 核心概念:了解几个关键的类和接口。
- 项目配置:如何在 Java 项目中引入 MySQL 驱动。
- CRUD 完整代码示例:一个完整的、可运行的 Java 类,包含所有操作。
- 最佳实践:使用
PreparedStatement和try-with-resources。 - 进阶:使用连接池。
环境准备
在开始之前,请确保你已经安装并配置好了以下环境:

- Java Development Kit (JDK): 版本 8 或更高。
- MySQL Server: 已安装并正在运行,并且创建了一个数据库。
- IDE: 如 IntelliJ IDEA, Eclipse 或 VS Code。
数据库准备
让我们创建一个简单的数据库和一张 users 表用于演示。
-- 1. 创建一个数据库
CREATE DATABASE my_jdbc_db;
-- 2. 使用该数据库
USE my_jdbc_db;
-- 3. 创建一个用户表
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
password VARCHAR(50) NOT NULL,
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 4. 插入一些初始数据
INSERT INTO users (username, password, email) VALUES ('alice', '123456', 'alice@example.com');
INSERT INTO users (username, password, email) VALUES ('bob', 'bobpass', 'bob@example.com');
JDBC 核心概念
JDBC 是 Java 访问数据库的 API,它定义了一套接口,具体的实现由数据库厂商(如 Oracle, MySQL)提供,我们称之为驱动。
你需要了解以下几个核心类/接口:

DriverManager: 管理数据库驱动程序,它的主要作用是建立与数据库的连接。Connection: 代表与数据库的连接,所有 SQL 操作都通过这个连接对象执行。Statement: 用于执行静态 SQL 语句并返回它所生成结果的对象,我们通常不直接使用它,因为它有 SQL 注入的风险。PreparedStatement:Statement的子接口,用于执行预编译的 SQL 语句。强烈推荐使用,因为它可以防止 SQL 注入,并且性能更好。ResultSet: 代表 SQL 查询的结果集,它是一个指向结果集数据行的游标。
项目配置 (引入 MySQL 驱动)
你需要将 MySQL 的 JDBC 驱动程序(一个 .jar 文件)添加到你的 Java 项目中。
使用 Maven (推荐)
如果你使用 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 会自动下载并管理这个依赖。

手动添加 JAR
如果你不使用 Maven,可以:
- 去 Maven 中央仓库 下载
mysql-connector-j-x.x.xx.jar文件。 - 在你的 IDE 中,将这个 JAR 文件添加到项目的库(Libraries)或构建路径(Build Path)中。
CRUD 完整代码示例
下面是一个完整的 Java 类,演示了如何连接数据库并进行增、删、改、查操作。
我们将使用 try-with-resources 语句,它可以自动关闭实现了 AutoCloseable 接口的对象(如 Connection, PreparedStatement, ResultSet),避免资源泄漏。
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;
import java.time.LocalDateTime;
public class MysqlCrudExample {
// --- 数据库连接信息 ---
// 注意:如果使用 MySQL 8.0+,驱动类名是 com.mysql.cj.jdbc.Driver
private static final String DB_URL = "jdbc:mysql://localhost:3306/my_jdbc_db?useSSL=false&serverTimezone=UTC";
private static final String USER = "root"; // 你的数据库用户名
private static final String PASS = "your_password"; // 你的数据库密码
public static void main(String[] args) {
// 为了演示,我们把所有操作放在 main 方法里
// 实际项目中,这些操作会分布在不同层(DAO层, Service层等)
System.out.println("===== 1. 查询所有用户 (SELECT) =====");
selectAllUsers();
System.out.println("\n===== 2. 创建新用户 (INSERT) =====");
// 假设新用户的ID是自增的,我们不需要在SQL中指定
int newUserId = insertUser("charlie", "charlie_pass", "charlie@example.com");
System.out.println("成功创建新用户,ID为: " + newUserId);
System.out.println("\n===== 3. 更新用户信息 (UPDATE) =====");
boolean updateResult = updateUserEmail(newUserId, "charlie.new@example.com");
System.out.println("更新用户 " + newUserId + " 的邮箱结果: " + (updateResult ? "成功" : "失败"));
System.out.println("\n===== 4. 查询单个用户 (SELECT by ID) =====");
User user = selectUserById(newUserId);
if (user != null) {
System.out.println("查找到用户: " + user);
}
System.out.println("\n===== 5. 删除用户 (DELETE) =====");
boolean deleteResult = deleteUserById(newUserId);
System.out.println("删除用户 " + newUserId + " 结果: " + (deleteResult ? "成功" : "失败"));
System.out.println("\n===== 6. 再次查询所有用户,确认删除 =====");
selectAllUsers();
}
// --- 增 ---
// 返回新插入用户的ID
public static int insertUser(String username, String password, String email) {
String sql = "INSERT INTO users (username, password, email) VALUES (?, ?, ?)";
// 使用 Statement.RETURN_GENERATED_KEYS 来获取自增主键
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
pstmt.setString(1, username);
pstmt.setString(2, password);
pstmt.setString(3, email);
int affectedRows = pstmt.executeUpdate();
if (affectedRows > 0) {
// 获取生成的键
try (ResultSet rs = pstmt.getGeneratedKeys()) {
if (rs.next()) {
return rs.getInt(1); // 返回新用户的ID
}
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return -1; // 插入失败
}
// --- 删 ---
public static boolean deleteUserById(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();
return affectedRows > 0; // 如果影响行数大于0,则表示删除成功
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
// --- 改 ---
public static boolean updateUserEmail(int id, String newEmail) {
String sql = "UPDATE users SET email = ? WHERE id = ?";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, newEmail);
pstmt.setInt(2, id);
int affectedRows = pstmt.executeUpdate();
return affectedRows > 0; // 如果影响行数大于0,则表示更新成功
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
// --- 查 ---
public static void selectAllUsers() {
String sql = "SELECT id, username, password, email, created_at 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 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%s\t\t%s\t%s\n", id, username, email, createdAt);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
// --- 查 (单个) ---
public static User selectUserById(int id) {
String sql = "SELECT id, username, password, email, created_at FROM users WHERE id = ?";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setEmail(rs.getString("email"));
user.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());
return user;
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return null; // 未找到用户
}
}
// 一个简单的POJO类,用于映射用户数据
class User {
private int id;
private String username;
private String password;
private String email;
private LocalDateTime createdAt;
// Getters and Setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
", createdAt=" + createdAt +
'}';
}
}
最佳实践
在上面的代码中,我们已经遵循了几个最佳实践:
-
使用
PreparedStatement而不是Statement- 防止 SQL 注入:
PreparedStatement会对输入参数进行转义,恶意输入不会被当作 SQL 代码执行。 - 性能更优:数据库可以对预编译的 SQL 语句进行缓存和优化。
- 代码更清晰:使用 作为占位符,使得 SQL 语句和参数分离,易于阅读和维护。
- 防止 SQL 注入:
-
使用
try-with-resources- 这是 Java 7 引入的一个语法糖,用于自动管理资源。
- 任何实现了
AutoCloseable接口的资源(Connection,Statement,ResultSet)都可以在try语句中声明。 - 当
try块执行完毕后,无论是否发生异常,这些资源都会被自动关闭,无需手动调用close()方法,极大地简化了代码并避免了资源泄漏。
-
使用列名访问
ResultSet- 在
rs.getString("username")中,我们使用列名而不是列索引(如rs.getString(2))。 - 优点:代码可读性更强,并且当数据库表结构(列的顺序)发生变化时,你的 Java 代码不会受影响,更加健壮。
- 在
进阶:使用连接池
每次执行 SQL 操作都创建一个新的 Connection 是非常消耗资源的,在高并发的应用中,性能会急剧下降。
解决方案是使用数据库连接池,连接池在应用启动时预先创建一定数量的 Connection 对象,并将它们存放在一个池中,当需要连接时,从池中获取一个用完后,再归还给池,而不是直接关闭。
常用的连接池库:
- HikariCP: 目前性能最好的连接池,是 Spring Boot 2.x 的默认选择。
- Apache DBCP
- C3P0
使用 HikariCP 的示例
在 pom.xml 中添加 HikariCP 依赖:
<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.SQLException;
public class DataSourceUtil {
private static HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/my_jdbc_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)
config.setIdleTimeout(600000); // 空闲连接超时时间 (ms)
config.setMaxLifetime(1800000); // 连接最大存活时间 (ms)
dataSource = new HikariDataSource(config);
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
你可以修改之前的代码,使用 DataSourceUtil.getConnection() 来获取连接,而不再是 DriverManager.getConnection(),这样可以显著提高应用的性能和稳定性。
| 操作 | SQL 关键字 | Java 核心步骤 |
|---|---|---|
| 增 | INSERT |
Connection -> PreparedStatement -> pstmt.executeUpdate() -> pstmt.getGeneratedKeys() |
| 删 | DELETE |
Connection -> PreparedStatement -> pstmt.executeUpdate() |
| 改 | UPDATE |
Connection -> PreparedStatement -> pstmt.executeUpdate() |
| 查 | SELECT |
Connection -> PreparedStatement -> pstmt.executeQuery() -> 遍历 ResultSet |
掌握这些基础后,你就可以开始在 Java 应用中与 MySQL 数据库进行交互了,对于更复杂的项目,可以考虑学习 MyBatis 或 JPA (如 Hibernate) 等 ORM 框架,它们能进一步简化数据库操作。
