JDBC 从入门到实战:Java 操作数据库的核心技术详解
JDBC 从入门到实战:Java 操作数据库的核心技术详解
JDBC(Java Database Connectivity)是 Java 访问关系型数据库的标准 API,它定义了一套统一的接口,让 Java 程序能够独立于具体的数据库产品(如 MySQL、Oracle、PostgreSQL)进行数据操作。无论是简单的增删改查,还是复杂的事务处理,JDBC 都是 Java 后端开发的必备技能。本文将从“核心概念→环境搭建→基础操作→实战案例→高级特性”五个维度,带大家系统掌握 JDBC,所有示例基于 MySQL 数据库,可直接复制运行。
一、JDBC 核心概念:先搞懂“是什么”和“为什么”
1. 什么是 JDBC?
- 技术定义:JDBC 是 Sun 公司定义的一套用于访问数据库的接口(
java.sql和javax.sql包下),数据库厂商(如 MySQL、Oracle)提供接口的实现类(即 JDBC 驱动),Java 程序通过调用接口方法,间接操作数据库。 - 核心作用:屏蔽不同数据库的底层差异,实现“一次编码,多库兼容”(理论上,更换数据库只需替换驱动和连接信息,无需修改业务代码)。
- 核心组件:JDBC 接口主要包括 5 个核心类/接口,协同完成数据库操作:
组件 作用 DriverManager管理 JDBC 驱动,负责创建数据库连接 Connection代表数据库连接,是所有操作的基础 Statement执行静态 SQL 语句(存在 SQL 注入风险) PreparedStatement执行预编译 SQL 语句(推荐,防 SQL 注入) ResultSet存储 SQL 查询的结果集
2. JDBC 工作原理
JDBC 采用“接口-实现”的设计模式,工作流程如下:
- 加载数据库驱动(JDBC 4.0+ 无需手动加载,自动识别);
- 通过
DriverManager获取数据库连接(Connection); - 通过
Connection创建Statement或PreparedStatement对象; - 执行 SQL 语句,查询操作返回
ResultSet结果集; - 处理结果集(查询)或获取影响行数(增删改);
- 关闭资源(
ResultSet、Statement、Connection),避免内存泄漏。
3. JDBC 与 ORM 框架的关系
很多开发者会混淆 JDBC 和 MyBatis、Hibernate 等 ORM 框架,核心关系如下:
- JDBC:底层 API,操作数据库的“基础工具”,需要手动编写 SQL 和处理结果集,代码繁琐但灵活;
- ORM 框架:基于 JDBC 封装的高级工具(如 MyBatis、Hibernate),通过注解或 XML 映射数据,简化 SQL 编写和结果集映射,提升开发效率。
类比:JDBC 是“手动挡汽车”,需要手动控制离合、换挡;ORM 框架是“自动挡汽车”,自动处理底层细节,更易操作,但底层仍依赖 JDBC。
二、JDBC 开发环境搭建
要使用 JDBC 操作 MySQL,需准备三个核心组件:JDK、MySQL 数据库、JDBC 驱动(MySQL Connector/J)。
1. 环境准备
- JDK:推荐 JDK 8(兼容性最好);
- MySQL:推荐 MySQL 8.0(需提前创建数据库和表,后续示例用);
- JDBC 驱动:MySQL 官方提供的驱动包(
mysql-connector-java),获取方式:- 方式 1:Maven 依赖(推荐,自动管理版本):
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> <!-- 与 MySQL 版本匹配,8.0+ 对应此版本 --> </dependency> - 方式 2:手动下载:从 MySQL 官网(https://dev.mysql.com/downloads/connector/j/)下载 JAR 包,添加到项目
lib目录并添加依赖。
- 方式 1:Maven 依赖(推荐,自动管理版本):
2. 准备测试环境
提前创建测试数据库和表,后续所有示例基于此结构:
-- 1. 创建数据库
CREATE DATABASE IF NOT EXISTS jdbc_demo
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
-- 2. 切换数据库
USE jdbc_demo;
-- 3. 创建学生表
CREATE TABLE IF NOT EXISTS student (
id INT PRIMARY KEY AUTO_INCREMENT, -- 自增主键
name VARCHAR(50) NOT NULL, -- 姓名(非空)
age INT CHECK (age > 0 AND age < 100), -- 年龄范围
gender ENUM('男', '女', '其他') DEFAULT '男', -- 性别
create_time DATETIME DEFAULT CURRENT_TIMESTAMP -- 创建时间
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 4. 插入测试数据
INSERT INTO student (name, age, gender)
VALUES ('张三', 20, '男'), ('李四', 19, '女'), ('王五', 21, '男');
三、JDBC 基础操作:CRUD 核心实现
JDBC 操作数据库的核心是“CRUD”(新增 Create、查询 Read、修改 Update、删除 Delete),以下是完整实现示例,包含代码注释和注意事项。
1. 核心常量定义(避免硬编码)
首先定义数据库连接信息常量,方便后续维护:
public class JDBCConstants {
// 数据库连接 URL(MySQL 8.0+ 需指定 serverTimezone)
public static final String URL = "jdbc:mysql://localhost:3306/jdbc_demo?serverTimezone=UTC&useSSL=false";
public static final String USERNAME = "root"; // 数据库用户名(替换为自己的)
public static final String PASSWORD = "123456"; // 数据库密码(替换为自己的)
}
2. 新增数据(INSERT)
使用 PreparedStatement 执行预编译 SQL,避免 SQL 注入:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCInsertDemo {
public static void main(String[] args) {
// 声明资源(Connection、PreparedStatement 需关闭,用 try-with-resources 自动关闭)
try (
// 1. 获取数据库连接
Connection conn = DriverManager.getConnection(JDBCConstants.URL, JDBCConstants.USERNAME, JDBCConstants.PASSWORD);
// 2. 创建 PreparedStatement,预编译 SQL(? 是占位符,后续赋值)
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO student (name, age, gender) VALUES (?, ?, ?)")
) {
// 3. 给占位符赋值(索引从 1 开始,而非 0)
pstmt.setString(1, "赵六"); // 第一个 ? 赋值为字符串
pstmt.setInt(2, 18); // 第二个 ? 赋值为整数
pstmt.setString(3, "女"); // 第三个 ? 赋值为字符串
// 4. 执行 SQL(INSERT/UPDATE/DELETE 用 executeUpdate(),返回影响行数)
int affectedRows = pstmt.executeUpdate();
System.out.println("新增成功,影响行数:" + affectedRows);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3. 查询数据(SELECT)
查询操作需处理 ResultSet 结果集,提取查询数据:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCSelectDemo {
public static void main(String[] args) {
try (
Connection conn = DriverManager.getConnection(JDBCConstants.URL, JDBCConstants.USERNAME, JDBCConstants.PASSWORD);
// 预编译查询 SQL
PreparedStatement pstmt = conn.prepareStatement("SELECT id, name, age, gender, create_time FROM student WHERE age > ?");
) {
// 给占位符赋值:查询年龄大于 19 的学生
pstmt.setInt(1, 19);
// 执行查询(SELECT 用 executeQuery(),返回 ResultSet 结果集)
try (ResultSet rs = pstmt.executeQuery()) {
// 遍历结果集(rs.next() 移动到下一行,返回 true 表示有数据)
while (rs.next()) {
// 通过列名或索引获取数据(推荐列名,更稳定)
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
String gender = rs.getString("gender");
String createTime = rs.getString("create_time");
// 打印结果
System.out.printf("ID:%d,姓名:%s,年龄:%d,性别:%s,创建时间:%s%n",
id, name, age, gender, createTime);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4. 修改数据(UPDATE)
与新增操作类似,使用 executeUpdate() 执行更新 SQL:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCUpdateDemo {
public static void main(String[] args) {
try (
Connection conn = DriverManager.getConnection(JDBCConstants.URL, JDBCConstants.USERNAME, JDBCConstants.PASSWORD);
// 预编译更新 SQL:修改姓名为“张三”的年龄
PreparedStatement pstmt = conn.prepareStatement("UPDATE student SET age = ? WHERE name = ?")
) {
// 赋值占位符(注意:占位符顺序与 SQL 中一致)
pstmt.setInt(1, 22); // 第一个 ?:新年龄
pstmt.setString(2, "张三"); // 第二个 ?:查询条件(姓名)
int affectedRows = pstmt.executeUpdate();
System.out.println("修改成功,影响行数:" + affectedRows);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
5. 删除数据(DELETE)
注意:删除操作需谨慎,建议添加明确的 WHERE 条件:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCDeleteDemo {
public static void main(String[] args) {
try (
Connection conn = DriverManager.getConnection(JDBCConstants.URL, JDBCConstants.USERNAME, JDBCConstants.PASSWORD);
// 预编译删除 SQL:删除姓名为“赵六”的学生
PreparedStatement pstmt = conn.prepareStatement("DELETE FROM student WHERE name = ?")
) {
pstmt.setString(1, "赵六");
int affectedRows = pstmt.executeUpdate();
System.out.println("删除成功,影响行数:" + affectedRows);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
6. 核心注意事项
- 资源关闭:
Connection、PreparedStatement、ResultSet都是稀缺资源,必须关闭。推荐使用try-with-resources语法(JDK 7+ 支持),自动关闭资源,无需手动写close(); - SQL 注入风险:严禁使用
Statement执行动态 SQL(如拼接用户输入的参数),必须使用PreparedStatement预编译 SQL,通过占位符赋值; - 占位符索引:
PreparedStatement的占位符?索引从 1 开始,而非 0,避免索引越界; - 连接信息配置:避免硬编码 URL、用户名、密码,建议放在配置文件(如
jdbc.properties)中,后续实战会优化。
四、JDBC 实战优化:工具类封装 + 配置文件
前面的示例存在代码冗余(如重复获取连接、关闭资源),实际开发中会封装 JDBC 工具类,同时将配置信息写入配置文件,提升代码复用性和可维护性。
1. 步骤 1:创建配置文件(jdbc.properties)
在 src/main/resources 目录下创建配置文件,存储数据库连接信息:
# jdbc.properties
jdbc.url=jdbc:mysql://localhost:3306/jdbc_demo?serverTimezone=UTC&useSSL=false
jdbc.username=root
jdbc.password=123456
jdbc.driver=com.mysql.cj.jdbc.Driver # MySQL 8.0+ 驱动类名(可选,JDBC 4.0+ 自动加载)
2. 步骤 2:封装 JDBC 工具类(JDBCUtils.java)
封装连接获取、资源关闭等通用操作:
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class JDBCUtils {
// 加载配置文件
private static final Properties props = new Properties();
// 静态代码块:加载配置文件(只执行一次)
static {
try (
// 读取配置文件(类路径下的资源)
InputStream in = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties")
) {
props.load(in); // 加载配置到 Properties 对象
// 加载驱动(MySQL 8.0+ 可省略,JDBC 4.0+ 自动识别)
Class.forName(props.getProperty("jdbc.driver"));
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("JDBC 配置文件加载失败!", e);
}
}
// 1. 获取数据库连接
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(
props.getProperty("jdbc.url"),
props.getProperty("jdbc.username"),
props.getProperty("jdbc.password")
);
}
// 2. 关闭资源(查询操作:Connection + Statement + ResultSet)
public static void close(Connection conn, Statement stmt, ResultSet rs) {
// 关闭 ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 关闭 Statement(PreparedStatement 是其子类)
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 关闭 Connection
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 3. 重载:关闭资源(增删改操作:Connection + Statement,无 ResultSet)
public static void close(Connection conn, Statement stmt) {
close(conn, stmt, null);
}
}
3. 步骤 3:使用工具类简化 CRUD 操作
以查询操作为例,简化后的代码:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCUtilsDemo {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 1. 获取连接(通过工具类)
conn = JDBCUtils.getConnection();
// 2. 创建 PreparedStatement
pstmt = conn.prepareStatement("SELECT * FROM student");
// 3. 执行查询
rs = pstmt.executeQuery();
// 4. 处理结果集
while (rs.next()) {
System.out.printf("ID:%d,姓名:%s,年龄:%d%n",
rs.getInt("id"), rs.getString("name"), rs.getInt("age"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 5. 关闭资源(通过工具类)
JDBCUtils.close(conn, pstmt, rs);
}
}
}
五、JDBC 高级特性:事务、批量操作与存储过程
1. 事务管理(ACID 特性)
JDBC 事务默认自动提交(执行一条 SQL 自动提交一次),实际开发中需手动控制事务(如转账操作:扣款和到账必须同时成功或失败)。
事务核心 API
conn.setAutoCommit(false):关闭自动提交,开启手动事务;conn.commit():提交事务(所有操作成功后执行);conn.rollback():回滚事务(出现异常时执行,撤销所有操作);conn.setSavepoint():设置保存点(回滚到指定节点,而非全部)。
实战示例:模拟转账操作
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
// 假设存在账户表 account:id(主键)、username(用户名)、balance(余额)
public class JDBC事务Demo {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt1 = null; // 扣款操作
PreparedStatement pstmt2 = null; // 到账操作
try {
// 1. 获取连接
conn = JDBCUtils.getConnection();
// 2. 关闭自动提交,开启事务
conn.setAutoCommit(false);
// 3. 执行扣款(用户 A 减少 100 元)
String sql1 = "UPDATE account SET balance = balance - 100 WHERE username = 'A'";
pstmt1 = conn.prepareStatement(sql1);
pstmt1.executeUpdate();
// 模拟异常(测试回滚,实际开发中无需写)
// int i = 1 / 0;
// 4. 执行到账(用户 B 增加 100 元)
String sql2 = "UPDATE account SET balance = balance + 100 WHERE username = 'B'";
pstmt2 = conn.prepareStatement(sql2);
pstmt2.executeUpdate();
// 5. 所有操作成功,提交事务
conn.commit();
System.out.println("转账成功!");
} catch (Exception e) {
// 6. 出现异常,回滚事务
try {
if (conn != null) {
conn.rollback();
System.out.println("转账失败,事务回滚!");
}
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
// 7. 关闭资源
JDBCUtils.close(conn, pstmt1);
JDBCUtils.close(null, pstmt2); // conn 已在上面关闭,此处传 null
}
}
}
2. 批量操作(Batch)
当需要执行大量相同结构的 SQL 语句(如批量插入 1000 条数据)时,使用批量操作可大幅提升性能(减少数据库连接次数)。
批量操作核心 API
pstmt.addBatch():将 SQL 语句添加到批处理队列;pstmt.executeBatch():执行批处理队列中的所有 SQL;pstmt.clearBatch():清空批处理队列。
实战示例:批量插入 1000 条学生数据
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCBatchDemo {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = JDBCUtils.getConnection();
// 关闭自动提交(批量操作推荐关闭,提升性能)
conn.setAutoCommit(false);
String sql = "INSERT INTO student (name, age, gender) VALUES (?, ?, ?)";
pstmt = conn.prepareStatement(sql);
// 批量添加 1000 条数据
for (int i = 1; i <= 1000; i++) {
pstmt.setString(1, "学生" + i);
pstmt.setInt(2, 18 + (i % 5)); // 年龄 18-22 随机
pstmt.setString(3, i % 2 == 0 ? "女" : "男"); // 性别交替
pstmt.addBatch(); // 添加到批处理队列
// 每 200 条执行一次(避免队列过大)
if (i % 200 == 0) {
pstmt.executeBatch(); // 执行批处理
pstmt.clearBatch(); // 清空队列
}
}
// 执行剩余的 SQL
pstmt.executeBatch();
conn.commit(); // 提交事务
System.out.println("批量插入 1000 条数据成功!");
} catch (SQLException e) {
try {
if (conn != null) {
conn.rollback();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
JDBCUtils.close(conn, pstmt);
}
}
}
3. 调用存储过程
存储过程是预编译存储在数据库中的 SQL 语句集合,JDBC 可通过 CallableStatement 调用存储过程。
步骤 1:创建 MySQL 存储过程
-- 创建存储过程:根据姓名查询学生年龄
DELIMITER // -- 修改语句结束符为 //(避免与存储过程中的 ; 冲突)
CREATE PROCEDURE getStudentAge(IN studentName VARCHAR(50), OUT studentAge INT)
BEGIN
SELECT age INTO studentAge FROM student WHERE name = studentName;
END //
DELIMITER ; -- 恢复语句结束符为 ;
步骤 2:JDBC 调用存储过程
import java.sql.Connection;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.sql.Types;
public class JDBCStoredProcedureDemo {
public static void main(String[] args) {
Connection conn = null;
CallableStatement cstmt = null;
try {
conn = JDBCUtils.getConnection();
// 调用存储过程的 SQL 格式:{call 存储过程名(?, ?)}
String sql = "{call getStudentAge(?, ?)}";
cstmt = conn.prepareCall(sql);
// 1. 设置输入参数(IN 类型:学生姓名)
cstmt.setString(1, "张三");
// 2. 注册输出参数(OUT 类型:学生年龄,指定类型为 INT)
cstmt.registerOutParameter(2, Types.INTEGER);
// 3. 执行存储过程
cstmt.execute();
// 4. 获取输出参数结果
int age = cstmt.getInt(2);
System.out.println("张三的年龄:" + (age == 0 ? "未找到该学生" : age));
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(conn, cstmt);
}
}
}
六、JDBC 常见问题与避坑指南
1. 连接数据库失败(Communications link failure)
- 常见原因:
- MySQL 服务未启动;
- URL 错误(如端口号错误、数据库名拼写错误);
- MySQL 8.0+ 未指定
serverTimezone(需添加serverTimezone=UTC); - 防火墙拦截 3306 端口。
- 解决方案:
- 启动 MySQL 服务(
net start mysql命令); - 检查 URL 格式:
jdbc:mysql://localhost:3306/数据库名?serverTimezone=UTC&useSSL=false; - 关闭防火墙或开放 3306 端口。
- 启动 MySQL 服务(
2. 中文乱码问题
- 原因:数据库/表/字段字符集不是
utf8mb4,或 JDBC 连接未指定编码; - 解决方案:
- 创建数据库时指定字符集(
CHARACTER SET utf8mb4); - URL 中添加编码参数:
useUnicode=true&characterEncoding=utf8; - 确保 Java 程序编码为 UTF-8(IDE 中设置)。
- 创建数据库时指定字符集(
3. SQL 注入风险
- 问题场景:使用
Statement拼接 SQL 语句(如String sql = "SELECT * FROM user WHERE username = '" + username + "'"),若用户输入' OR '1'='1,会导致查询所有用户数据; - 解决方案:
- 严禁使用
Statement,必须使用PreparedStatement预编译 SQL; - 对用户输入的参数进行过滤(如过滤特殊字符
'、;)。
- 严禁使用
4. 资源泄漏(Connection 未关闭)
- 问题:未关闭
Connection会导致数据库连接池耗尽,后续无法获取连接; - 解决方案:
- 强制使用
try-with-resources语法,自动关闭资源; - 若使用传统
try-catch-finally,必须在finally中关闭资源(先关ResultSet,再关Statement,最后关Connection)。
- 强制使用
5. 批量操作性能不佳
- 原因:未关闭自动提交(默认每次
addBatch()都提交一次); - 解决方案:
- 批量操作前执行
conn.setAutoCommit(false); - 分批执行
executeBatch()(如每 200 条执行一次),避免队列过大。
- 批量操作前执行
七、总结:JDBC 学习路径与发展
1. 学习路径
- 基础阶段:掌握 JDBC 核心组件(
Connection、PreparedStatement、ResultSet),实现 CRUD 操作; - 进阶阶段:封装 JDBC 工具类、配置文件,理解事务、批量操作、存储过程;
- 实战阶段:结合 Servlet/JSP 实现 Web 应用(如用户登录、商品管理);
- 框架阶段:学习 MyBatis、Spring JDBC 等框架,理解框架如何简化 JDBC 开发。
2. JDBC 的核心价值
- JDBC 是 Java 操作数据库的基础,所有 ORM 框架(MyBatis、Hibernate)、数据访问框架(Spring JDBC)都基于 JDBC 实现;
- 掌握 JDBC 原理,能更好地理解框架的底层逻辑,排查框架使用中的问题;
- 小型项目或性能要求高的场景,JDBC 仍是最优选择(无框架 overhead)。
更多推荐



所有评论(0)