在Java应用程序开发中,与数据库进行交互是核心功能之一,无论是构建一个简单的后台管理系统,还是一个复杂的分布式服务,都离不开对数据的增、删、改、查操作,这四个操作统称为CRUD(Create, Read, Update, Delete),本文将详细介绍如何使用Java的核心技术——JDBC(Java Database Connectivity)来实现数据库的CRUD操作,并探讨相关的最佳实践。

准备工作:驱动与连接
在编写任何数据库操作代码之前,我们需要完成两项准备工作:
-
添加数据库驱动依赖:Java程序通过特定数据库的驱动程序与数据库通信,若使用MySQL数据库,需在项目(如Maven项目)的
pom.xml文件中添加相应依赖:<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> -
配置数据库连接信息:你需要知道数据库的连接URL、用户名和密码,一个典型的MySQL连接URL格式如下:
jdbc:mysql://主机名:端口号/数据库名?serverTimezone=UTC
JDBC操作的核心流程
无论执行哪种CRUD操作,JDBC都遵循一个标准化的流程:
- 加载驱动:通过
Class.forName()加载数据库驱动类。 - 建立连接:使用
DriverManager.getConnection()获取一个Connection对象,它代表与数据库的会话。 - 创建语句对象:通过
Connection对象创建Statement或PreparedStatement对象,用于执行SQL语句。 - 执行SQL:调用语句对象的
executeQuery()(用于查询)或executeUpdate()(用于增、删、改)方法。 - 处理结果:
- 对于查询操作,遍历返回的
ResultSet对象,提取数据。 - 对于更新操作,获取受影响的行数。
- 对于查询操作,遍历返回的
- 关闭资源:按相反的顺序关闭
ResultSet、Statement和Connection,释放数据库资源。强烈推荐使用try-with-resources语句来自动管理资源关闭,避免资源泄露。
实现CRUD操作
假设我们有一个名为users的表,包含id, username, email三个字段,下面我们将围绕这个表进行CRUD操作。
创建 - 插入数据
创建操作对应SQL的INSERT语句,为了安全和性能,我们优先使用PreparedStatement,它可以有效防止SQL注入,并且数据库可以预编译SQL,提高执行效率。
public void addUser(String username, String email) {
String sql = "INSERT INTO users (username, email) VALUES (?, ?)";
// try-with-resources 自动关闭连接和语句对象
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, username); // 为第一个占位符?赋值
pstmt.setString(2, email); // 为第二个占位符?赋值
int affectedRows = pstmt.executeUpdate();
if (affectedRows > 0) {
System.out.println("用户添加成功!");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
读取 - 查询数据
读取操作对应SQL的SELECT语句,查询会返回一个ResultSet对象,我们可以像遍历迭代器一样从中读取数据。

public void getAllUsers() {
String sql = "SELECT id, username, email FROM users";
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
System.out.println("用户列表:");
while (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
String email = rs.getString("email");
System.out.printf("ID: %d, 用户名: %s, 邮箱: %s\n", id, username, email);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
更新 - 修改数据
更新操作对应SQL的UPDATE语句,其实现方式与插入类似,使用executeUpdate()方法,并返回受影响的行数。
public void updateUserEmail(int userId, String newEmail) {
String sql = "UPDATE users SET email = ? WHERE id = ?";
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, newEmail);
pstmt.setInt(2, userId);
int affectedRows = pstmt.executeUpdate();
if (affectedRows > 0) {
System.out.println("用户邮箱更新成功!");
} else {
System.out.println("未找到指定ID的用户。");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
删除 - 移除数据
删除操作对应SQL的DELETE语句,同样使用executeUpdate()方法。
public void deleteUser(int userId) {
String sql = "DELETE FROM users WHERE id = ?";
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, userId);
int affectedRows = pstmt.executeUpdate();
if (affectedRows > 0) {
System.out.println("用户删除成功!");
} else {
System.out.println("未找到指定ID的用户。");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
代码封装与最佳实践
在真实项目中,直接在业务逻辑中散布JDBC代码是不明智的,我们会采用DAO(Data Access Object)设计模式,将所有数据库访问逻辑封装在专门的DAO类中(如UserDAO),使业务逻辑与数据访问逻辑解耦。
频繁地创建和销毁数据库连接会严重影响性能。使用连接池(如HikariCP、C3P0)是生产环境中的标准实践,连接池预先创建并维护一批数据库连接,应用程序需要时从池中借用,用完归还,极大地提升了系统响应速度和资源利用率。
对于更现代的Java开发,开发者往往会选择JPA(Java Persistence API)或Spring Data JPA等ORM(Object-Relational Mapping)框架,这些框架在JDBC基础上进行了高度封装,让开发者能以面向对象的方式操作数据库,而无需编写繁琐的SQL语句,进一步提高了开发效率和代码的可维护性。
相关问答FAQs
问题1:Statement和PreparedStatement有什么区别,为什么更推荐使用PreparedStatement?
解答: PreparedStatement是Statement的子接口,主要区别和优势在于:

- 安全性:
PreparedStatement使用参数化查询(通过占位符),可以有效防止SQL注入攻击,而Statement通过字符串拼接构建SQL,极易被恶意利用。 - 性能:
PreparedStatement的SQL语句会被数据库预编译并缓存执行计划,当多次执行相同结构的SQL(只是参数不同)时,数据库可以直接重用执行计划,速度更快。Statement每次执行都需要完整编译。 - 可读性:对于复杂的SQL语句,使用
PreparedStatement的setXxx()方法设置参数比用字符串拼接更清晰、更易于维护。
在几乎所有的场景下,都应该优先选择PreparedStatement。
问题2:什么是数据库连接池,为什么需要它?
解答: 数据库连接池是一种创建和管理数据库连接的缓冲池技术,传统的JDBC操作是“按需连接,用完即关”,但建立数据库连接是一个非常耗时的操作,涉及网络通信和协议握手。
在高并发场景下,如果每个请求都创建新连接,会导致应用性能急剧下降,甚至可能耗尽数据库的连接资源。
连接池解决了这个问题:
- 资源复用:它在应用启动时预先创建一定数量的连接,放入池中,当应用需要连接时,直接从池中获取一个空闲连接,使用完毕后归还,而不是物理关闭。
- 更快的响应:避免了频繁创建和销毁连接的开销,应用能更快地获取到连接并执行数据库操作。
- 统一的连接管理:连接池可以监控连接状态,有效管理连接数量,防止连接泄露,提高了系统的稳定性和健壮性。
常见的连接池实现有HikariCP(目前性能最好)、Druid、C3P0等。