在Java开发中,与数据库交互是不可避免的核心环节——无论是构建管理系统、开发后端接口还是进行数据分析,都需要程序能够读写数据库中的数据。而JDBC正是连接Java与数据库的官方“桥梁”,是一套标准化的操作数据库API。许多初学者觉得JDBC复杂,有一堆接口和异常需要处理,但只要理清其核心逻辑,就会发现其模式相当固定。本文将从基础到进阶,系统地讲解JDBC,每个步骤都配有以MySQL 8.0为实例的实操代码,确保你学完即可上手实践。
一、理解基石:什么是JDBC及其价值
1、JDBC的本质:Java连接数据库的“通用协议”
JDBC,全称为Java Database Connectivity,是Java官方定义的一套接口和类,位于 java.sql 和 javax.sql 包下。它的核心作用在于“统一标准”——不同数据库(如MySQL、Oracle、PostgreSQL)的底层实现和语法细节存在差异。如果没有JDBC,操作不同的数据库就需要编写不同的代码,极其繁琐。
有了JDBC,情况就大为不同:Java程序只需调用JDBC的标准接口,具体的数据库通信、SQL执行等底层操作,则交由各数据库厂商提供的“驱动包”去实现。这相当于JDBC为所有数据库制定了一套“通用语言”,开发者学会这一套,就能以相似的思路操作任何数据库,仅需更换对应的驱动包和少量配置即可。
2、为何选择JDBC而非图形化工具?
有人可能会问:使用Navicat等图形化工具手动就能进行增删改查,为何还要编写代码使用JDBC?答案关键在于自动化。Navicat是“人工操作”,而JDBC实现的是“程序自动化操作”。例如,在一个用户注册功能中,用户在网页提交信息后,后台Java程序需要自动将数据存入数据库,此时显然不可能依赖人工手动插入。JDBC正是实现此类“程序自动化操作数据库”场景的核心技术。
二、JDBC核心组件:五大接口与类详解
JDBC的核心围绕5个关键接口和1个管理类展开。我们无需深究其底层实现,只需明确每个组件的作用和常用方法即可。
1. Driver(驱动接口)
这是数据库驱动的核心接口,负责与数据库建立物理连接。开发者无需自己实现,数据库厂商已经提供了实现类。例如,MySQL的驱动包中就包含了实现此接口的 com.mysql.cj.jdbc.Driver 类。我们需要做的只是将对应的驱动包导入项目。
2. DriverManager(驱动管理类)
这是一个工具类,负责管理所有已注册的驱动,并帮助我们创建数据库连接。它就像一个“中介”:你告知它数据库地址、账号和密码,它便找到对应的驱动,建立连接并将连接对象返回。常用方法有两个:
registerDriver(Driver driver):注册驱动(现代驱动通常自动注册,很少需要手动调用)。
getConnection(String url, String user, String password):核心方法,用于获取数据库连接对象。
3. Connection(连接接口)
此接口代表Java程序与数据库之间的一条活动“会话”通道。只有获得此连接对象,才能执行后续的SQL操作。常用方法包括:
createStatement():创建Statement对象,用于执行静态SQL语句。
prepareStatement(String sql):创建PreparedStatement对象,用于执行预编译SQL(推荐使用,可防SQL注入)。
setAutoCommit(boolean autoCommit):设置事务是否自动提交(默认为true,手动控制事务时需设为false)。
commit():提交事务。
rollback():回滚事务。
close():关闭连接(至关重要,必须在使用后关闭以释放数据库资源)。
4. Statement / PreparedStatement(SQL执行器)
这两个接口都用于执行SQL语句,其中PreparedStatement是Statement的子接口,功能更强,强烈推荐优先使用。
(1)Statement(普通执行器)
用于执行静态SQL语句(如固定的查询或删除)。常用方法:
executeQuery(String sql):执行查询语句(SELECT),返回ResultSet结果集。
executeUpdate(String sql):执行更新语句(INSERT、UPDATE、DELETE),返回受影响的行数。
close():关闭执行器。
缺点:存在SQL注入风险。例如在用户登录场景,若使用Statement拼接用户输入的账号密码,恶意用户可能通过输入特殊字符绕过验证,安全性较差。
(2)PreparedStatement(预编译执行器)
核心优势在于预编译、防止SQL注入、提升性能(相同结构的SQL只需编译一次)。它使用 ? 作为占位符,通过 setXxx(int index, Xxx value) 方法为占位符赋值(索引从1开始)。例如:setString(1, "zhangsan")。
结论:在开发中应一律使用PreparedStatement,避免使用Statement。
5. ResultSet(结果集接口)
执行查询语句(SELECT)后,返回的数据被封装在ResultSet对象中,它就像一个“虚拟表格”。我们需要通过其方法遍历数据行并获取每列的值。常用方法:
next():将光标移动到下一行,返回boolean值(true表示有数据,false表示已无数据)。
getXxx(String columnName):根据列名获取对应类型的值,如 getString("username")、getInt("id")。
getXxx(int columnIndex):根据列索引获取值(索引从1开始)。
close():关闭结果集。
三、JDBC完整实战:基于MySQL 8.0实现CRUD
理论结合实践,下面我们以MySQL 8.0为例,从环境准备到编码,一步步实现完整的增删改查操作。
1、环境准备
(1)启动MySQL 8.0并创建测试表
确保MySQL服务正在运行,并创建用于测试的数据库和表。例如,创建一个 test_db 数据库和 t_user 表:
CREATE DATABASE IF NOT EXISTS test_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE test_db;
drop table IF EXISTS t_user;
CREATE TABLE IF NOT EXISTS t_user (
`id` INT NOT NULL AUTO_INCREMENT, -- 自增
`username` VARCHAR(255) NOT NULL, -- 用户名
`password` VARCHAR(255) NOT NULL, -- 密码
`age` INT, -- 年龄
`balance` decimal(20,4) DEFAULT '0.0000', -- 余额
PRIMARY KEY(`id`) -- 主键
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户信息表';
(2)导入MySQL驱动包
推荐使用Maven管理依赖。在项目的 pom.xml 文件中添加以下依赖(版本号请与你的MySQL版本对应):
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version> <!--请与自己的MySQL版本适配-->
</dependency>
</dependencies>
对于不使用Maven的项目,可前往MySQL官网下载对应版本的Connector/J驱动包,并手动将其JAR文件添加到项目的类路径中。
(3)MySQL 8.0连接参数
MySQL 8.0的连接URL格式与5.x版本有所不同,需特别注意:
2、编写JDBC工具类
将获取连接和关闭资源等重复性操作封装成工具类,能极大提高代码复用率和可维护性。
import java.sql.*;
/**
* JDBC工具类:封装获取连接、关闭资源的方法
*/
public class JDBCUtils {
// 数据库连接参数(请根据实际情况修改)
private static final String URL = "jdbc:mysql://localhost:3306/test_db?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true";
private static final String USER = "root";
private static final String PASSWORD = "123456"; // 替换成你自己的MySQL密码
// 静态代码块:加载驱动(MySQL 8.0+)
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver"); // MySQL 8.0的驱动类名
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("驱动加载失败!");
}
}
// 获取数据库连接
public static Connection getConnection() {
try {
return DriverManager.getConnection(URL, USER, PASSWORD);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException("获取连接失败!");
}
}
// 关闭资源(查询操作需要关闭ResultSet、PreparedStatement、Connection)
public static void close(ResultSet rs, PreparedStatement pstmt, Connection conn) {
// 关闭顺序:ResultSet → PreparedStatement → Connection(先开后关)
try {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 重载关闭方法(增删改操作不需要ResultSet)
public static void close(PreparedStatement pstmt, Connection conn) {
close(null, pstmt, conn);
}
}
注意:静态代码块在类加载时执行一次,用于驱动加载。
3、实现增删改查(CRUD)
核心流程统一为:获取连接 → 创建PreparedStatement → 设置参数并执行SQL → 处理结果 → 关闭资源。
(1)新增数据(INSERT)
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCDemo {
public static void main(String[] args) {
insertUser("zhangsan", "123456", 20);
}
// 新增用户
public static void insertUser(String username, String password, int age) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
// 1. 获取连接
conn = JDBCUtils.getConnection();
// 2. 编写SQL模板,用?占位符
String sql = "INSERT INTO t_user (username, password, age) VALUES (?, ?, ?)";
// 3. 创建PreparedStatement
pstmt = conn.prepareStatement(sql);
// 4. 给占位符赋值(index从1开始)
pstmt.setString(1, username);
pstmt.setString(2, password);
pstmt.setInt(3, age);
// 5. 执行SQL(增删改用executeUpdate,返回受影响行数)
int rows = pstmt.executeUpdate();
// 6. 处理结果
System.out.println("新增成功,受影响行数:" + rows);
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 7. 关闭资源
JDBCUtils.close(pstmt, conn);
}
}
}
执行后,可在数据库工具中查看 t_user 表,确认数据已成功插入。
(2)修改数据(UPDATE)
// 修改用户年龄
public static void updateUserAge(int id, int newAge) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = JDBCUtils.getConnection();
String sql = "UPDATE t_user SET age = ? WHERE id = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, newAge); // 第一个占位符:新年龄
pstmt.setInt(2, id); // 第二个占位符:用户ID
int rows = pstmt.executeUpdate();
System.out.println("修改成功,受影响行数:" + rows);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(pstmt, conn);
}
}
调用 updateUserAge(1, 25),即可将id为1的用户的年龄更新为25。
(3)删除数据(DELETE)
// 删除用户
public static void deleteUser(int id) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = JDBCUtils.getConnection();
String sql = "DELETE FROM t_user WHERE id = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, id);
int rows = pstmt.executeUpdate();
System.out.println("删除成功,受影响行数:" + rows);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(pstmt, conn);
}
}
调用 deleteUser(1),即可删除id为1的用户记录。
(4)查询数据(SELECT)
查询操作需要处理ResultSet结果集,遍历并取出数据。
// 根据ID查询单个用户
public static void selectUserById(int id) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
String sql = "SELECT id, username, password, age FROM t_user WHERE id = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, id);
// 执行查询,返回ResultSet
rs = pstmt.executeQuery();
// 遍历结果集
if (rs.next()) { // 移动到下一行,有数据则进入
int userId = rs.getInt("id");
String username = rs.getString("username");
String password = rs.getString("password");
int age = rs.getInt("age");
System.out.println("查询到用户:");
System.out.println("ID:" + userId + ",用户名:" + username + ",年龄:" + age);
} else {
System.out.println("没有查询到该用户");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源(包含ResultSet)
JDBCUtils.close(rs, pstmt, conn);
}
}
若要查询所有用户,可将SQL改为 SELECT * FROM t_user,并使用 while 循环遍历ResultSet。
public static void selectAllUsers() {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
// 假设已定义User实体类
List<User> userList = new ArrayList<>();
try {
conn = JDBCUtils.getConnection();
String sql = "SELECT id, username, password, age FROM t_user";
pstmt = conn.prepareStatement(sql);
// 执行查询,返回ResultSet
rs = pstmt.executeQuery();
// 遍历结果集
while (rs.next()) { // 移动到下一行,有数据则进入
int userId = rs.getInt("id");
String username = rs.getString("username");
String password = rs.getString("password");
int age = rs.getInt("age");
userList.add(new User(userId, username, password, age));
}
System.out.println("用户列表:" + userList.toString());
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源(包含ResultSet)
JDBCUtils.close(rs, pstmt, conn);
}
}
通过以上步骤,你已经掌握了使用JDBC进行数据库操作的核心流程与最佳实践。理解并熟练运用这些组件和模式,是构建健壮Java后端应用的基础。如果在实践中遇到更复杂的需求,如事务管理、连接池优化等,可以前往云栈社区的技术论坛与更多开发者交流探讨。