JDBC(Java Database Connectivity)
详情
一、JDBC 是什么?
JDBC(Java Database Connectivity) 是 Java 提供的一套访问数据库的标准 API 规范,它允许 Java 应用程序与各种关系型数据库进行交互。JDBC 是 Java 标准库(java.sql 和 javax.sql 包)的一部分,属于 Java SE 的核心组件。
核心作用:提供统一的接口,屏蔽不同数据库之间的差异,使开发者能以一致的方式操作 MySQL、PostgreSQL、Oracle、SQL Server 等数据库。
二、没有 JDBC 前
在 JDBC 没有出现之前,怎么访问各个数据库厂商提供的数据库呢?
1. 直接调用本地 C/C++ 库(通过 JNI)
很多早期数据库(如 Oracle、Sybase、Informix)只提供了 C 语言的客户端库(例如 Oracle 的 OCI - Oracle Call Interface)。Java 程序若想连接这些数据库,必须:
- 编写 JNI(Java Native Interface)代码;
- 调用本地动态链接库(.dll 或 .so);
- 处理复杂的内存管理和平台兼容性问题。
// 假设有一个 Oracle 的 native wrapper
public class OracleNativeAccess {
static {
System.loadLibrary("oracle_oci_wrapper"); // 加载本地库
}
public native ResultSet query(String sql); // 声明 native 方法
}这种方式不仅开发复杂,而且丧失了 Java “一次编写,到处运行”的优势。
2. 使用数据库厂商私有的 Java API
一些数据库厂商(如 Sybase、Informix)在 JDBC 出现前就提供了自己的 Java 类库,但这些 API 各不相同,互不兼容。
- Sybase jConnect(早期版本) 可能这样用:
SybDriver driver = new SybDriver(); Connection conn = driver.connect("sybase:Tds:localhost:5000", props); - Oracle 的早期 Java 接口(非 JDBC):
OracleConnection conn = new OracleConnection("user", "password"); OracleStatement stmt = conn.createStatement(); OracleResultSet rs = stmt.executeQuery("SELECT * FROM emp");
每换一个数据库,就要重写整个数据访问层。
3. 通过中间件或网关协议(如 ODBC 桥接)
有些系统通过 ODBC(Open Database Connectivity,微软的 Windows 数据库接口标准)间接访问数据库。Java 程序需借助 JDBC-ODBC Bridge(虽然它本身是 JDBC 的一部分,但在 JDBC 成熟前常被当作“变通方案”)。
但在纯 Java 环境中,这种桥接依赖操作系统安装的 ODBC 驱动,且性能差、跨平台性弱。
// 伪代码:调用系统命令启动 ODBC 查询工具,解析输出
Process p = Runtime.getRuntime().exec("odbc_query_tool -d mydb -q 'SELECT ...'");
BufferedReader output = new BufferedReader(new InputStreamReader(p.getInputStream()));
// 手动解析文本结果...4. 基于 Socket 的自定义协议
某些轻量级或嵌入式数据库(如早期的 dBASE、Paradox)甚至没有标准网络接口,开发者需:
- 直接通过 TCP/UDP 与数据库服务器通信;
- 手动序列化 SQL 请求;
- 解析二进制或文本响应格式。
Socket socket = new Socket("dbserver", 12345);
OutputStream out = socket.getOutputStream();
out.write("SELECT name FROM users\n".getBytes());
// 然后按特定格式读取返回的数据流...这种方式极易出错,且难以维护。
5. 文件系统模拟数据库
在更早期,有些“数据库”其实就是结构化文件(如 CSV、固定宽度文本、dBASE .dbf 文件)。Java 程序直接读写文件,自行实现查询逻辑。
BufferedReader reader = new BufferedReader(new FileReader("users.dbf"));
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("John")) { // 手动“查询”
System.out.println(line);
}
}通过以上的示例,可以看见如果没有JDBC:
- 每个数据库都要写一套专属代码,切换数据库几乎等于重写数据层,代码无法复用,维护成本爆炸。
- Java 失去“一次编写,到处运行”的优势,数据库访问逻辑被绑定到具体厂商实现上。
- 性能差,实现麻烦。
三、为什么使用 JDBC?
JDBC 的核心价值是:抽象 + 标准化
- 定义了
Connection,Statement,ResultSet等接口。 - 各数据库厂商提供自己的 JDBC 驱动(实现这些接口)。
- 你的代码只依赖标准接口,不依赖具体数据库
JDBC 做到了:
- 标准化访问:无论底层是哪种数据库,只要提供对应的 JDBC 驱动,就能用相同的方式操作。
- 跨平台性:基于 Java 的“一次编写,到处运行”特性。
- 轻量级:无需额外依赖(除了数据库驱动),适合小型项目或学习用途。
- 可控性强:直接操作 SQL,对性能调优、事务控制等有完全掌控权。
- JDBC = 标准规范(接口)
- JDBC 驱动 = 数据库厂商对这些接口的具体实现 典型的 “面向接口编程” + “SPI(Service Provider Interface)” 设计思想。
四、JDBC 的核心组件
| 组件 | 说明 |
|---|---|
DriverManager | 管理 JDBC 驱动,用于获取数据库连接 |
Connection | 表示与数据库的连接 |
Statement / PreparedStatement | 用于执行 SQL 语句 |
ResultSet | 查询结果集,可遍历获取数据 |
SQLException | 所有 JDBC 异常的父类 |
五、JDBC 使用步骤(标准流程)
- 加载数据库驱动(JDBC 4.0+ 可省略)
- 建立数据库连接(
DriverManager.getConnection()) - 创建 Statement 或 PreparedStatement
- 执行 SQL 语句
- 处理结果集(如果是查询)
- 关闭资源(Connection, Statement, ResultSet)
最佳实践:使用 try-with-resources 自动关闭资源,防止内存泄漏。
六、JDBC 示例代码
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class JdbcExample {
public void queryArticles(int authorId, String date) {
String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "password";
// 注意:JDBC 4.0+ 会自动加载驱动,无需 Class.forName()
String sql = "SELECT id, title, content, create_time FROM article " +
"WHERE author_id = ? AND create_time > ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 使用 PreparedStatement 防止 SQL 注入
pstmt.setInt(1, authorId);
pstmt.setDate(2, Date.valueOf(date)); // 假设 date 格式为 "yyyy-MM-dd"
try (ResultSet rs = pstmt.executeQuery()) {
List<Article> articles = new ArrayList<>();
while (rs.next()) {
Article article = new Article();
article.setId(rs.getInt("id"));
article.setTitle(rs.getString("title"));
article.setContent(rs.getString("content"));
article.setCreateTime(rs.getDate("create_time"));
articles.add(article);
}
System.out.println("Query SQL ==> " + sql);
System.out.println("Query Result:");
articles.forEach(System.out::println);
}
} catch (SQLException e) {
System.err.println("Database error: " + e.getMessage());
e.printStackTrace();
}
}
}七、JDBC 的局限性
| 问题 | 说明 |
|---|---|
| 无连接池 | 每次都新建连接,性能差 |
| 代码冗余 | 大量样板代码(打开/关闭连接、异常处理) |
| 易出错 | 手写 SQL 容易拼错、漏关资源 |
| 无对象映射 | 需手动将 ResultSet 转为 Java 对象 |
| 不支持高级特性 | 如缓存、懒加载、关联查询等 |
解释:
JDBC是Java 访问数据库的标准 API 规范(一套接口),不包含连接池。
- 原生 JDBC 每次调用
DriverManager.getConnection()都会:- 创建新 TCP 连接
- 进行身份认证
- 初始化会话
- 这个过程非常耗时(可能几十毫秒),而执行 SQL 可能只要 1ms。
- 在高并发下,频繁创建/销毁连接会导致:
- 性能瓶颈
- 数据库连接数耗尽(报错 “Too many connections”)
因此实际开发中,JDBC + HikariCP/Druid 是常见组合;更高层则用 MyBatis 或 Spring Data JPA。
- HikariCP/Druid/C3P0:这些工具解决 “连接效率” 问题 → 连接池
- MyBatis(SQL 映射框架)/Hibernate(ORM 框架):解决 “开发效率” 问题
关联网络
演化日志
- v0.1 (2026-01-31):初始版本
- v0.2 (2026-02-01):补充没有 JDBC 的情况说明
待办事项
- 使用HikariCP(高卡利)、Druid、C3P0
- 关掉隐性事务,合并成一个事务解决
- 学习
DataSource接口(比DriverManager更现代的连接获取方式) - 了解事务管理(
conn.setAutoCommit(false)+commit()/rollback()) - 尝试整合 HikariCP
- “面向接口编程” + “SPI(Service Provider Interface)” 设计思想。
- ORM / SQL 映射框架?
- TCP 连接,身份认证,初始化会话
- 编写 JNI(Java Native Interface)代码;
- 调用本地动态链接库(.dll 或 .so);
- ODBC
- JDBC-ODBC Bridge
- TCP/UDP