JDBC (Java Database Connectivity) 是 Java 语言用于规范客户端程序如何访问数据库的应用程序接口,它提供了诸如查询和更新数据库中数据的方法。
本文将以 MySQL 数据库为例,因为它是目前最流行的开源关系型数据库之一,但核心概念和代码结构同样适用于其他数据库(如 Oracle, PostgreSQL, SQL Server 等),只需更换相应的驱动类和连接字符串即可。
第一步:准备工作
在开始编写代码之前,请确保你已经完成了以下准备工作:
-
安装 MySQL 数据库:并确保服务正在运行。
-
创建一个测试数据库:创建一个名为
test_db的数据库。 -
创建一张测试表:在
test_db中创建一张users表。-- 进入 test_db 数据库 USE test_db; -- 创建 users 表 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, age INT ); -
添加 JDBC 驱动:你需要将 MySQL 的 JDBC 驱动(JAR 文件)添加到你的项目中。
- Maven 项目:在
pom.xml中添加依赖。<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> <!-- 使用与你的MySQL版本匹配的最新版本 --> </dependency> - 普通 Java 项目:从 Maven 中央仓库 下载 JAR 文件,并将其添加到项目的类路径(Classpath)中。
- Maven 项目:在
第二步:建立数据库连接
所有 JDBC 操作都始于一个 Connection 对象,这个对象代表与数据库的物理连接,为了管理资源(如连接、语句、结果集),我们强烈推荐使用 try-with-resources 语句,它能自动关闭实现了 AutoCloseable 接口的对象。
数据库连接信息:
- URL:
jdbc:mysql://localhost:3306/test_db - 用户名:
root - 密码:
your_password(替换成你自己的密码)
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBUtil {
// 数据库URL, 格式: jdbc:mysql://主机名:端口号/数据库名
private static final String URL = "jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC";
// 用户名
private static final String USER = "root";
// 密码
private static final String PASSWORD = "your_password";
/**
* 获取数据库连接
* @return Connection 对象
* @throws SQLException 如果连接失败
*/
public static Connection getConnection() throws SQLException {
try {
// 1. 加载JDBC驱动 (对于较新版本的JDBC驱动,这步通常是可选的)
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 获取连接
return DriverManager.getConnection(URL, USER, PASSWORD);
} catch (ClassNotFoundException e) {
throw new SQLException("MySQL JDBC Driver not found.", e);
}
}
}
注意:?useSSL=false&serverTimezone=UTC 是为了避免 SSL 警告和时区问题,在生产环境中请根据实际情况配置。
第三步:核心 CRUD 操作
我们将分别实现增、删、改、查。
查询 - SELECT
查询操作通常涉及 Connection, PreparedStatement, 和 ResultSet。
Connection: 建立连接。PreparedStatement: 预编译SQL语句,可以防止SQL注入,并提高性能。ResultSet: 查询结果集,它像一个指向数据行的游标。
示例:查询所有用户
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class SelectExample {
public static List<User> getAllUsers() {
String sql = "SELECT id, name, email, age FROM users";
List<User> userList = new ArrayList<>();
// 使用 try-with-resources 自动关闭资源
try (Connection conn = DBUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()) {
// 遍历结果集
while (rs.next()) {
// 通过列名获取数据,更健壮
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
int age = rs.getInt("age");
User user = new User(id, name, email, age);
userList.add(user);
}
} catch (SQLException e) {
e.printStackTrace();
}
return userList;
}
// 用于存储用户数据的简单POJO类
static class User {
private int id;
private String name;
private String email;
private int age;
public User(int id, String name, String email, int age) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) {
List<User> users = getAllUsers();
for (User user : users) {
System.out.println(user);
}
}
}
增加 - INSERT
增加操作使用 executeUpdate() 方法,它会返回一个整数,表示受影响的行数。
示例:添加一个新用户
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class InsertExample {
public static boolean addUser(String name, String email, int age) {
String sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
try (Connection conn = DBUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 使用 setXxx() 方法为占位符赋值
pstmt.setString(1, name);
pstmt.setString(2, email);
pstmt.setInt(3, age);
// executeUpdate() 返回受影响的行数
int affectedRows = pstmt.executeUpdate();
return affectedRows > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
public static void main(String[] args) {
// 添加一个新用户
boolean success = addUser("张三", "zhangsan@example.com", 30);
if (success) {
System.out.println("用户添加成功!");
} else {
System.out.println("用户添加失败!");
}
}
}
修改 - UPDATE
修改操作与增加操作非常相似,同样使用 executeUpdate() 方法,只是SQL语句不同。
示例:根据ID更新用户信息
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class UpdateExample {
public static boolean updateUser(int id, String newEmail, int newAge) {
String sql = "UPDATE users SET email = ?, age = ? WHERE id = ?";
try (Connection conn = DBUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, newEmail);
pstmt.setInt(2, newAge);
pstmt.setInt(3, id);
int affectedRows = pstmt.executeUpdate();
return affectedRows > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
public static void main(String[] args) {
// 假设要更新ID为1的用户
boolean success = updateUser(1, "zhangsan_new@example.com", 31);
if (success) {
System.out.println("用户信息更新成功!");
} else {
System.out.println("用户信息更新失败!(可能用户不存在)");
}
}
}
删除 - DELETE
删除操作也使用 executeUpdate() 方法。
示例:根据ID删除用户
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class DeleteExample {
public static boolean deleteUser(int id) {
String sql = "DELETE FROM users WHERE id = ?";
try (Connection conn = DBUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
int affectedRows = pstmt.executeUpdate();
return affectedRows > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
public static void main(String[] args) {
// 假设要删除ID为1的用户
boolean success = deleteUser(1);
if (success) {
System.out.println("用户删除成功!");
} else {
System.out.println("用户删除失败!(可能用户不存在)");
}
}
}
第四步:事务管理
事务是一组操作的集合,它们要么全部成功,要么全部失败,这在保证数据一致性方面至关重要。
ACID 特性:
- 原子性:事务中的所有操作,要么全部完成,要么全部不完成。
- 一致性:事务必须使数据库从一个一致性状态变换到另一个一致性状态。
- 隔离性:一个事务的执行不能被其他事务干扰。
- 持久性:一个事务一旦提交,它对数据库中数据的改变就是永久性的。
示例:转账操作(需要从两个账户中扣款和存款)
import java.sql.Connection;
import java.sql.SQLException;
public class TransactionExample {
public static void transferMoney(int fromUserId, int toUserId, double amount) {
String withdrawSql = "UPDATE users SET balance = balance - ? WHERE id = ?";
String depositSql = "UPDATE users SET balance = balance + ? WHERE id = ?";
Connection conn = null;
try {
// 1. 获取连接
conn = DBUtil.getConnection();
// 2. 开启事务 (关闭自动提交)
conn.setAutoCommit(false);
// 3. 执行第一个操作:扣款
try (PreparedStatement pstmt1 = conn.prepareStatement(withdrawSql)) {
pstmt1.setDouble(1, amount);
pstmt1.setInt(2, fromUserId);
pstmt1.executeUpdate();
}
// 模拟一个可能发生的错误
// if (true) {
// throw new RuntimeException("模拟一个系统错误!");
// }
// 4. 执行第二个操作:存款
try (PreparedStatement pstmt2 = conn.prepareStatement(depositSql)) {
pstmt2.setDouble(1, amount);
pstmt2.setInt(2, toUserId);
pstmt2.executeUpdate();
}
// 5. 如果所有操作都成功,则提交事务
conn.commit();
System.out.println("转账成功!");
} catch (SQLException | RuntimeException e) {
// 6. 如果发生任何异常,则回滚事务
if (conn != null) {
try {
conn.rollback();
System.out.println("转账失败,已回滚!");
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
// 7. 恢复自动提交,并关闭连接
if (conn != null) {
try {
conn.setAutoCommit(true); // 重要!
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
// 假设 users 表中有 balance 列
transferMoney(1, 2, 100.0);
}
}
总结与最佳实践
- 使用
try-with-resources:这是管理 JDBC 资源(Connection,Statement,ResultSet)的最佳方式,能有效防止资源泄漏。 - 优先使用
PreparedStatement:- 防止 SQL 注入:它对输入参数进行转义,是安全编程的基石。
- 提高性能:对于需要多次执行的 SQL,数据库可以预编译它,后续执行更快。
- 使用列名访问
ResultSet:rs.getString("name")比rs.getString(2)更清晰、更健壮,尤其是在 SQL 查询顺序改变时。 - 处理异常:
SQLException是受检异常,必须处理,不要简单地printStackTrace(),在实际应用中应该记录日志。 - 管理事务:对于涉及多个步骤的业务逻辑(如转账、下单),务必使用事务来保证数据的一致性。
- 连接池:在高并发应用中,频繁地创建和销毁连接是非常消耗资源的,应该使用数据库连接池(如 HikariCP, Druid, C3P0)来管理和复用连接,现代框架(如 Spring Boot)通常会自动配置连接池。
掌握了以上这些内容,你就可以熟练地使用 Java JDBC 进行大部分数据库操作了。
