1. 先说说 JDBC 是什么
如果你写过 Java,大概率听过 JDBC 这个词。它的全称是 Java Database Connectivity,中文一般叫「Java 数据库连接」。
用一句大白话概括:
JDBC 就是 Java 官方提供的一套「和数据库打交道」的标准接口。
你可以把它理解成一个万能插座。Java 程序是插头,各种数据库(MySQL、Oracle、PostgreSQL、SQL Server……)是不同规格的电器,而 JDBC 就是那个让它们能互相插上的统一标准。
这里有个关键点要划重点:JDBC 本身不是数据库,也不是某个具体的驱动,它只是一套「规范」。真正干活的是各家数据库厂商按照这套规范写出来的 JDBC 驱动(Driver)。
2. 为什么需要 JDBC
在 JDBC 出现之前,Java 程序员想连数据库,得针对每种数据库写一套代码。换个数据库,代码几乎要重写一遍,非常痛苦。
JDBC 解决的核心问题就是 「解耦」:
- 对 Java 程序员:只需要学一套 API,就能操作几乎所有主流数据库;
- 对数据库厂商:只要按 JDBC 规范提供驱动,就能被所有 Java 程序使用。
所以 JDBC 的价值可以总结成一句话:一套接口,连接所有数据库。
3. JDBC 的核心角色
在动手写代码前,先认识几个「演员」,后面写代码时你会反复见到它们:
| 角色 | 作用 |
|---|---|
| Driver | 数据库驱动,负责真正和数据库通信 |
| DriverManager | 驱动管理器,帮我们获取数据库连接 |
| Connection | 一个数据库连接,相当于「打通的一根电话线」 |
| Statement / PreparedStatement | 用来执行 SQL 语句 |
| ResultSet | 查询结果集,用来一行行读取数据 |
记住这张表,JDBC 的代码基本就是围绕它们转的。
4. JDBC 操作的五个核心步骤
这是本文最重要的部分,几乎所有 JDBC 代码都逃不出这五步:
- 加载并注册驱动
- 获取数据库连接(Connection)
- 创建执行 SQL 的对象(Statement / PreparedStatement)
- 执行 SQL 并处理结果(ResultSet)
- 释放资源(关闭连接)
下面用一张图串起来:
5. 完整代码示例
假设我们有一张 student 表,字段是 id、name、age。下面用 MySQL 8 演示一次完整查询。
5.1 准备依赖
如果用 Maven,先引入 MySQL 驱动:
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
5.2 编写代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class JdbcDemo {
// 数据库连接信息
private static final String URL =
"jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=Asia/Shanghai";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) {
// 1. 加载驱动(JDBC 4.0 之后可省略,驱动会自动注册)
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 2~5 步:连接、执行、处理、关闭
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement ps = conn.prepareStatement(
"SELECT id, name, age FROM student WHERE age > ?")) {
// 3. 设置参数
ps.setInt(1, 18);
// 4. 执行查询
try (ResultSet rs = ps.executeQuery()) {
// 5. 处理结果集
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.println(id + " - " + name + " - " + age);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
几个值得注意的点:
try-with-resources会自动关闭Connection、PreparedStatement、ResultSet,比手动finally里 close 更省心;PreparedStatement用?占位,能有效防止 SQL 注入,实际开发中优先用它,而不是Statement;- URL 里的参数(如
serverTimezone)不同数据库、不同版本可能略有差异,报错时先检查这里。
6. 用 HikariCP 替换 DriverManager 获取连接
上面的示例里,每次调用 DriverManager.getConnection() 都会新建一条物理连接,用完再关掉。真实项目里这样写会非常慢,所以实际开发几乎都会用连接池。这里用目前最主流的 HikariCP 演示怎么改造。
6.1 引入依赖
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
</dependency>
6.2 配置连接池
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
public class DataSourceFactory {
private static final HikariDataSource DATA_SOURCE;
static {
HikariConfig config = new HikariConfig();
// 注意:连接池用的是 jdbcUrl,不是 url
config.setJdbcUrl("jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=Asia/Shanghai");
config.setUsername("root");
config.setPassword("123456");
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
// 池中保持的最小空闲连接数
config.setMinimumIdle(5);
// 池中最大连接数
config.setMaximumPoolSize(20);
// 获取连接的超时时间(毫秒)
config.setConnectionTimeout(3000);
// 空闲连接存活时间(毫秒)
config.setIdleTimeout(600000);
// 连接最大存活时间(毫秒),建议比数据库的 wait_timeout 小一点
config.setMaxLifetime(1800000);
DATA_SOURCE = new HikariDataSource(config);
}
public static DataSource getDataSource() {
return DATA_SOURCE;
}
}
6.3 改造后的查询示例
改造的关键只有一处:把 DriverManager.getConnection(...) 换成 dataSource.getConnection(),其余代码几乎不用动。
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class HikariJdbcDemo {
public static void main(String[] args) {
DataSource dataSource = DataSourceFactory.getDataSource();
String sql = "SELECT id, name, age FROM student WHERE age > ?";
// 这里的 conn 是从池里借出来的,close() 时是「归还」而不是真正断开
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, 18);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.println(id + " - " + name + " - " + age);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意: conn.close() 在连接池里不是真的关闭连接,而是把连接还给池子,所以 try-with-resources 的写法依然要保留,否则连接会被一直占用、池子很快耗尽。
6.4 连接池相比每次新建连接的优势
- 省掉反复建连的开销:建立一条数据库连接要经历 TCP 握手、身份认证等步骤,耗时往往是毫秒级甚至更高,连接池把连接复用起来,请求响应更快;
- 控制并发连接数:
maximumPoolSize能防止程序在高并发下把数据库连接数打满,保护数据库不被拖垮; - 连接可复用、可管理:池子会自动检测失效连接、定期回收空闲连接,比手写
DriverManager更稳; - 统一配置:账号、超时、池大小集中在一处,改配置不用翻遍代码。
一句话总结:DriverManager 适合学习和写小 Demo,真实项目请用连接池,HikariCP 是当下的首选。
7. 新手最容易踩的坑
- 驱动没引入:报
ClassNotFoundException或No suitable driver,先检查依赖是否加对; - URL 写错:
jdbc:mysql://主机:端口/数据库名,少一个斜杠都连不上; - 忘记关闭资源:连接池被占满,程序越跑越慢,务必用
try-with-resources; - 用
Statement拼接 SQL:既容易出错又有注入风险,养成用PreparedStatement的习惯。
8. 原生 JDBC、MyBatis、JPA/Hibernate 怎么选
学完 JDBC,你很快会听到 MyBatis 和 JPA/Hibernate 这两个名字。它们本质上都是在 JDBC 之上做的封装,区别在于「封装到什么程度」。下面从五个维度做个对比:
| 维度 | 原生 JDBC | MyBatis | JPA / Hibernate |
|---|---|---|---|
| 代码量 | 最多,连接、参数、结果集都要手写 | 中等,SQL 写在 XML 或注解里,映射自动完成 | 最少,多数 CRUD 一行方法即可 |
| SQL 可控性 | 完全可控,SQL 由你亲手写 | 完全可控,SQL 仍由你写,只是交给框架执行 | 较弱,默认自动生成 SQL,复杂查询需 JPQL 或原生 SQL 兜底 |
| 映射方式 | 手动 rs.getXxx() 逐列取值 | 半自动,结果列与对象属性做映射配置 | 全自动,实体类与表通过注解/约定映射 |
| 学习成本 | 低,API 少但要自己处理细节 | 中,需掌握 SQL、映射配置与动态 SQL | 高,涉及实体生命周期、缓存、事务、懒加载等概念 |
| 适用场景 | 教学、小工具、对 SQL 有极致要求的场景 | 业务 SQL 复杂、需要精细调优的项目 | 领域模型清晰、以增删改查为主的中大型项目 |
选型建议:
- 先把原生 JDBC 跑通,它是理解后面所有框架的地基,遇到框架报错时你才知道底层发生了什么;
- 业务里 SQL 复杂、需要精细优化,优先考虑 MyBatis,SQL 掌握在自己手里最踏实;
- 项目以标准 CRUD 为主、追求开发效率,可以选 JPA/Hibernate,但要做好学习曲线较陡的心理准备。
9. 小结
最后用三句话收个尾:
- JDBC 是 Java 访问数据库的标准接口,一套 API 通吃各种数据库;
- 核心就是五步:加载驱动 → 获取连接 → 创建执行对象 → 执行并处理结果 → 关闭资源;
- 实际开发优先用
PreparedStatement,安全又高效。
把上面的示例跑通一遍,你就已经跨过了 JDBC 的第一道门槛。接下来可以继续了解 连接池(如 Druid、HikariCP) 和 ORM 框架(如 MyBatis),它们都是在 JDBC 之上做的封装。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/cats_eat_fish_/article/details/170780616




