在Java生态系统中,与数据库进行交互是一项基础且核心的任务,JDBC(Java Database Connectivity,Java数据库连接)正是为此而设计的一套标准的API(应用程序编程接口),它提供了一组统一的接口和类,使得Java程序能够执行SQL语句,与各种关系型数据库(如MySQL, PostgreSQL, Oracle, SQL Server等)进行通信,理解JDBC的工作原理,是掌握Java后端开发的必经之路,本文将系统性地阐述如何通过JDBC访问数据库,从核心步骤到代码实践,深入浅出地解析这一过程。

JDBC访问数据库的核心步骤
使用JDBC连接并操作数据库,通常遵循一个固定且逻辑清晰的流程,可以概括为以下六个核心步骤。
第一步:加载并注册数据库驱动
这是JDBC与特定数据库“握手”的第一步,每个数据库厂商都会提供实现JDBC接口的驱动程序(通常是一个JAR包),加载驱动的目的是告诉Java虚拟机(JVM)我们即将使用的是哪种数据库。
在早期的JDBC版本中,我们通常使用Class.forName()方法显式地加载驱动类,加载MySQL驱动:
Class.forName("com.mysql.cj.jdbc.Driver");
这行代码会触发驱动类的静态代码块,从而向DriverManager注册一个驱动实例,自JDBC 4.0起,引入了自动注册机制,只要驱动的JAR包在项目的类路径(classpath)中,应用程序启动时,服务提供者接口(SPI)会自动扫描并加载驱动,无需再手动调用Class.forName(),尽管如此,了解这一历史机制对于理解JDBC的演进和维护旧项目依然有帮助。
第二步:建立数据库连接
驱动注册成功后,就可以通过DriverManager类来获取与数据库的物理连接了。DriverManager是JDBC的管理层,负责处理驱动程序的加载和建立连接,核心方法是getConnection(),它需要三个关键参数:
-
数据库URL:一个字符串,用于唯一标识一个数据库,其标准格式为
jdbc:子协议:子名称。jdbc:协议,这是固定的。子协议:数据库的名称,如mysql,postgresql,oracle。子名称:数据库的地址、端口和数据库名称。//localhost:3306/mydatabase。
一个完整的MySQL URL示例:
jdbc:mysql://localhost:3306/mydatabase?useSSL=false&serverTimezone=UTC -
用户名:访问数据库的合法用户名。
-
密码:对应用户名的密码。
示例代码如下:

String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "root"; String password = "your_password"; Connection connection = DriverManager.getConnection(url, user, password);
如果所有参数正确,此方法将返回一个Connection对象,它代表了与数据库的一个会话连接。
第三步:创建Statement对象
获取到Connection对象后,我们需要通过它来创建一个Statement对象。Statement是执行SQL语句的载体,JDBC提供了三种类型的Statement:
Statement:用于执行静态的SQL语句,它在每次执行时都会将SQL语句发送给数据库进行编译,简单易用,但存在SQL注入的风险,且执行效率相对较低。PreparedStatement:预编译的SQL语句,它是Statement的子接口,性能更好,最重要的是能够有效防止SQL注入攻击。在实际开发中,强烈推荐使用PreparedStatement,SQL语句中的参数值用问号作为占位符。CallableStatement:用于执行数据库存储过程。
创建PreparedStatement的示例:
String sql = "SELECT id, name, email FROM users WHERE id = ?"; PreparedStatement pstmt = connection.prepareStatement(sql);
第四步:执行SQL语句
通过Statement或PreparedStatement对象,我们可以执行SQL语句,根据SQL类型的不同,调用不同的方法:
ResultSet executeQuery(String sql):用于执行SELECT查询语句,返回一个ResultSet对象,其中包含了查询结果集。int executeUpdate(String sql):用于执行INSERT,UPDATE,DELETE等修改数据的语句,以及DDL语句(如CREATE TABLE),返回一个整数,表示受影响的行数。boolean execute(String sql):可以执行任何类型的SQL语句,如果执行后第一个结果是ResultSet,则返回true;否则返回false。
对于PreparedStatement,执行时无需再传入SQL字符串,只需设置参数后调用相应方法即可:
pstmt.setInt(1, 101); // 设置第一个占位符(?)的值为101 ResultSet rs = pstmt.executeQuery();
第五步:处理结果集
如果执行的是查询操作,executeQuery()会返回一个ResultSet对象,这个对象就像一个指向查询结果行的游标,初始位置在第一行之前,我们可以使用while(rs.next())循环来遍历结果集。rs.next()方法会将游标移动到下一行,如果存在下一行则返回true。
在循环内部,可以通过getXXX()方法(如getString(), getInt(), getDate()等)来获取当前行中指定列的数据,可以通过列名(推荐,更易维护)或列索引(从1开始)来获取。
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
System.out.println("ID: " + id + ", Name: " + name + ", Email: " + email);
}
第六步:释放资源
资源释放是至关重要的一步,但常常被初学者忽略,JDBC对象(Connection, Statement, ResultSet)都占用了数据库和系统的宝贵资源,必须在使用完毕后显式关闭,关闭的顺序与创建的顺序相反:先关闭ResultSet,再关闭Statement(或PreparedStatement),最后关闭Connection。
为了确保资源在任何情况下(包括发生异常时)都能被释放,最佳实践是将关闭操作放在finally块中,从Java 7开始,更推荐使用try-with-resources语句,它能自动管理资源的关闭,代码更简洁、安全。
// 使用 try-with-resources 自动关闭资源
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, 101);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
// ...处理结果集
}
}
} catch (SQLException e) {
e.printStackTrace();
}
核心接口小编总结
为了更直观地理解JDBC的核心组件,下表小编总结了几个关键接口及其作用:

| 接口/类 | 主要作用 | 常用方法 |
|---|---|---|
DriverManager |
管理数据库驱动,建立数据库连接 | getConnection(String url, String user, String password) |
Connection |
代表与数据库的连接会话 | createStatement(), prepareStatement(String sql), close() |
Statement |
执行静态SQL语句 | executeQuery(String sql), executeUpdate(String sql) |
PreparedStatement |
执行预编译的SQL语句,防注入,性能更佳 | setXXX(int parameterIndex, XXX value), executeQuery(), executeUpdate() |
ResultSet |
封装SQL查询的结果集 | next(), getXXX(int columnIndex), getXXX(String columnLabel) |
JDBC作为Java数据库编程的基石,虽然其原生API使用起来略显繁琐,但它提供了一套强大而灵活的底层机制,掌握了上述六个步骤,就意味着掌握了与任何关系型数据库交互的基本能力,在现代Java开发中,虽然我们更多地使用MyBatis、Hibernate、JPA等框架,它们极大地简化了数据库操作,但这些框架本质上都是对JDBC的封装和优化,深刻理解JDBC的工作原理,不仅能帮助我们更好地使用这些高级框架,还能在遇到复杂问题或需要进行性能调优时,追溯到问题的根源,从而游刃有余地解决挑战。
相关问答FAQs
问1:JDBC和数据库连接池(如HikariCP, Druid)有什么区别和联系?
答: JDBC是一套API规范,它定义了Java程序如何与数据库交互的接口和类,是“做什么”的标准,而数据库连接池是一种基于JDBC API实现的技术模式,它关注的是“如何做得更好”。
它们的联系在于:连接池内部管理着多个数据库连接(这些连接就是通过JDBC的DriverManager.getConnection()创建的Connection对象),当应用程序需要连接时,连接池会直接“借”出一个已创建好的空闲连接,而不是让应用程序每次都去物理创建一个新的连接,当应用程序使用完毕,连接池会将连接“收回”,而不是将其物理关闭。
区别在于目的和效果:
- JDBC:提供了建立连接和执行SQL的能力,但每次创建和销毁连接的开销很大。
- 连接池:通过复用连接,显著减少了创建和销毁连接带来的性能损耗和系统资源消耗,从而提高了应用的响应速度和吞吐量,可以说,连接池是JDBC在生产环境中高性能应用的必备优化技术。
问2:为什么在实际开发中强烈推荐使用PreparedStatement而不是Statement?
答: 推荐使用PreparedStatement主要基于两个核心优势:安全性和性能。
-
安全性(防止SQL注入):
Statement通过简单的字符串拼接来构建SQL,如果用户输入的数据中包含恶意的SQL片段,就可能被拼接并执行,导致SQL注入攻击。"SELECT * FROM users WHERE name = '" + userName + "'",如果userName被输入为' OR '1'='1,SQL就会变成SELECT * FROM users WHERE name = '' OR '1'='1',从而绕过验证,而PreparedStatement使用预编译和参数占位符,它会先将SQL语句结构发送给数据库进行编译,然后再将用户输入的参数作为纯数据填入,这样,即使用户输入了恶意代码,数据库也只会将其视为普通字符串,而不会作为SQL命令执行,从而从根本上杜绝了SQL注入的风险。 -
性能(预编译):数据库对于SQL语句的执行,包括了解析、编译、优化等步骤,这些是比较耗时的。
Statement每次执行时,数据库都需要重新处理完整的SQL语句,而PreparedStatement的SQL语句在创建时(或首次执行时)就会被预编译,后续再执行相同的SQL语句(只是参数不同),数据库可以直接复用已编译的执行计划,只需替换参数即可,大大提高了执行效率,尤其是在需要批量执行或重复执行相似SQL的场景下,性能优势尤为明显。