JDBC从入门到面试:全面掌握Java数据库连接技术
引言
Java数据库连接(JDBC)是Java语言中用来规范客户端程序如何访问数据库的应用程序接口(API),它提供了跨数据库的统一访问方式。无论是初学者还是准备面试的开发者,深入理解JDBC都是Java后端开发者的必备技能。本文将带你从JDBC基础概念开始,逐步深入到高级特性和常见面试问题。
一、JDBC基础概念
1.1 什么是JDBC?
JDBC(Java Database Connectivity)是Java提供的一套用于执行SQL语句的Java API,它由一组用Java语言编写的类和接口组成。通过JDBC,开发者可以使用纯Java代码连接各种关系型数据库,并执行数据库操作。
1.2 为什么需要JDBC?
在JDBC出现之前,Java程序访问不同数据库需要使用各自特定的API,这导致了几个问题:
- 代码与特定数据库绑定,移植困难
- 需要学习不同数据库的API
- 维护成本高
JDBC通过提供统一的接口解决了这些问题,实现了"编写一次,随处运行"的数据库访问能力。
1.3 JDBC架构
JDBC架构分为四层:
- 应用程序层:调用JDBC API的Java程序
- JDBC API层:java.sql和javax.sql包中的接口和类
- 驱动程序管理层:DriverManager类
- 数据库驱动程序层:数据库厂商提供的具体实现
二、JDBC核心组件
2.1 DriverManager
DriverManager是JDBC架构中的基本服务,用于管理数据库驱动程序。它负责加载驱动程序并根据连接请求匹配适当的驱动程序。
java
// 加载驱动(JDBC 4.0以后可自动加载)
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立连接
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb", "username", "password");
2.2 Connection
Connection对象代表与数据库的连接,是JDBC操作的起点。通过Connection可以创建Statement、PreparedStatement和CallableStatement对象。
java
// 获取连接
Connection connection = DriverManager.getConnection(url, user, password);
// 设置事务属性
connection.setAutoCommit(false); // 关闭自动提交
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); // 设置隔离级别
2.3 Statement
Statement用于执行静态SQL语句并返回结果。适用于执行不带参数的SQL语句。
java
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
2.4 PreparedStatement
PreparedStatement是Statement的子接口,用于执行预编译的SQL语句,可以有效防止SQL注入攻击,提高执行效率。
java
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, "John Doe");
pstmt.setString(2, "john@example.com");
pstmt.executeUpdate();
2.5 CallableStatement
CallableStatement用于执行数据库存储过程,是PreparedStatement的子接口。
java
CallableStatement cstmt = connection.prepareCall("{call get_user_details(?, ?)}");
cstmt.setInt(1, userId);
cstmt.registerOutParameter(2, Types.VARCHAR);
cstmt.execute();
String userName = cstmt.getString(2);
2.6 ResultSet
ResultSet表示数据库结果集的数据表,通常通过执行查询数据库的语句生成。它提供了多种方法来获取和操作数据。
java
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
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编程步骤
3.1 基本步骤
- 加载并注册数据库驱动
- 建立数据库连接
- 创建Statement对象
- 执行SQL语句
- 处理结果集
- 关闭连接释放资源
3.2 完整示例
java
import java.sql.*;
public class JdbcExample {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 1. 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 建立连接
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb", "username", "password");
// 3. 创建Statement
stmt = conn.createStatement();
// 4. 执行查询
rs = stmt.executeQuery("SELECT id, name, email FROM users");
// 5. 处理结果
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);
}
} catch (ClassNotFoundException e) {
System.out.println("找不到数据库驱动: " + e.getMessage());
} catch (SQLException e) {
System.out.println("数据库操作错误: " + e.getMessage());
} finally {
// 6. 关闭资源
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
System.out.println("关闭资源时发生错误: " + e.getMessage());
}
}
}
}
3.3 使用try-with-resources简化代码
Java 7引入了try-with-resources语句,可以自动关闭资源,使代码更简洁。
java
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
while (rs.next()) {
// 处理结果
}
} catch (SQLException e) {
e.printStackTrace();
}
四、事务处理
4.1 什么是事务?
事务是数据库操作的基本单位,是一系列要么全部成功要么全部失败的操作。事务具有ACID特性:
- 原子性(Atomicity):事务中的所有操作要么全部完成,要么全部不完成
- 一致性(Consistency):事务执行前后数据库状态保持一致
- 隔离性(Isolation):并发事务之间相互隔离
- 持久性(Durability):事务提交后对数据库的修改是永久的
4.2 JDBC事务管理
java
Connection conn = null;
try {
conn = DriverManager.getConnection(url, user, password);
// 关闭自动提交
conn.setAutoCommit(false);
// 执行多个SQL操作
Statement stmt1 = conn.createStatement();
stmt1.executeUpdate("UPDATE account SET balance = balance - 100 WHERE id = 1");
Statement stmt2 = conn.createStatement();
stmt2.executeUpdate("UPDATE account SET balance = balance + 100 WHERE id = 2");
// 提交事务
conn.commit();
} catch (SQLException e) {
// 回滚事务
if (conn != null) {
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
// 恢复自动提交并关闭连接
if (conn != null) {
try {
conn.setAutoCommit(true);
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4.3 保存点(Savepoint)
保存点允许在事务中设置中间点,可以回滚到某个保存点而不是整个事务。
java
Savepoint savepoint = null;
try {
conn.setAutoCommit(false);
// 执行一些操作
stmt.executeUpdate("INSERT INTO table1 VALUES (...)");
// 设置保存点
savepoint = conn.setSavepoint("SAVEPOINT_1");
// 执行更多操作
stmt.executeUpdate("INSERT INTO table2 VALUES (...)");
conn.commit();
} catch (SQLException e) {
if (savepoint != null) {
// 回滚到保存点
conn.rollback(savepoint);
conn.commit(); // 提交部分事务
} else {
conn.rollback(); // 回滚整个事务
}
}
五、数据库连接池
5.1 为什么需要连接池?
传统的JDBC连接方式存在以下问题:
- 每次操作都需要建立和关闭连接,开销大
- 无法控制连接数量,可能导致系统资源耗尽
- 连接缺乏管理,容易造成连接泄漏
连接池通过预先创建并管理一定数量的数据库连接,解决了这些问题。
5.2 常见连接池
- Apache DBCP:Apache基金会提供的连接池
- C3P0:老牌连接池,稳定但性能一般
- Druid:阿里巴巴开源的高性能连接池,功能全面
- HikariCP:目前性能最好的连接池,Spring Boot 2.x默认连接池
5.3 HikariCP配置示例
java
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("username");
config.setPassword("password");
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
HikariDataSource ds = new HikariDataSource(config);
// 获取连接
try (Connection conn = ds.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
// 处理结果
}
5.4 连接池关键参数
- maximumPoolSize:连接池最大连接数
- minimumIdle:连接池最小空闲连接数
- connectionTimeout:获取连接的超时时间
- idleTimeout:连接空闲超时时间
- maxLifetime:连接最大存活时间
六、JDBC进阶特性
6.1 批量处理
批量处理可以显著提高大量数据操作的性能。
java
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO users (name, email) VALUES (?, ?)")) {
conn.setAutoCommit(false);
for (int i = 1; i <= 1000; i++) {
pstmt.setString(1, "User" + i);
pstmt.setString(2, "user" + i + "@example.com");
pstmt.addBatch(); // 添加到批处理
if (i % 100 == 0) {
pstmt.executeBatch(); // 每100条执行一次
conn.commit(); // 提交事务
}
}
pstmt.executeBatch(); // 执行剩余的
conn.commit();
} catch (SQLException e) {
e.printStackTrace();
}
6.2 结果集元数据
ResultSetMetaData可以获取结果集的结构信息,如列数、列名、列类型等。
java
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String columnName = rsmd.getColumnName(i);
String columnType = rsmd.getColumnTypeName(i);
int columnSize = rsmd.getColumnDisplaySize(i);
System.out.println("Column " + i + ": " + columnName + " (" + columnType + ", " + columnSize + ")");
}
6.3 数据库元数据
DatabaseMetaData可以获取数据库的整体信息。
java
DatabaseMetaData dbmd = conn.getMetaData();
System.out.println("Database Product: " + dbmd.getDatabaseProductName());
System.out.println("Database Version: " + dbmd.getDatabaseProductVersion());
System.out.println("Driver Name: " + dbmd.getDriverName());
System.out.println("Driver Version: " + dbmd.getDriverVersion());
// 获取所有表
ResultSet tables = dbmd.getTables(null, null, "%", new String[]{"TABLE"});
while (tables.next()) {
String tableName = tables.getString("TABLE_NAME");
System.out.println("Table: " + tableName);
}
6.4 可滚动和可更新的结果集
JDBC支持可滚动和可更新的结果集,但需要数据库驱动支持。
java
// 创建可滚动、可更新的Statement
Statement stmt = conn.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE
);
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
// 滚动到第一行
rs.first();
// 更新当前行
rs.updateString("name", "New Name");
rs.updateRow();
// 插入新行
rs.moveToInsertRow();
rs.updateString("name", "Inserted Name");
rs.updateString("email", "inserted@example.com");
rs.insertRow();
七、JDBC与SQL注入
7.1 什么是SQL注入?
SQL注入是一种代码注入技术,攻击者通过在输入中插入恶意SQL代码,从而执行非预期的数据库操作。
7.2 如何防止SQL注入?
- 使用PreparedStatement:最重要的防御手段
- 输入验证:对所有用户输入进行严格验证
- 最小权限原则:数据库用户只授予必要权限
- 使用ORM框架:如Hibernate、MyBatis等
java
// 易受SQL注入的写法
String query = "SELECT * FROM users WHERE name = '" + userName + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query);
// 安全的写法
String query = "SELECT * FROM users WHERE name = ?";
PreparedStatement pstmt = conn.prepareStatement(query);
pstmt.setString(1, userName);
ResultSet rs = pstmt.executeQuery();
八、JDBC常见面试题
8.1 基础问题
-
什么是JDBC?
JDBC是Java数据库连接API,提供了一套用于执行SQL语句的Java接口,使Java程序能够与各种关系型数据库进行交互。
-
JDBC驱动有哪些类型?
- Type 1: JDBC-ODBC桥接驱动
- Type 2: 本地API驱动
- Type 3: 网络协议驱动
- Type 4: 纯Java驱动(最常用)
-
Statement和PreparedStatement的区别?
- Statement用于执行静态SQL,每次执行都需要编译
- PreparedStatement用于执行预编译SQL,性能更好,可防止SQL注入
-
execute、executeQuery和executeUpdate的区别?
- execute: 可执行任何SQL,返回boolean表示是否有ResultSet
- executeQuery: 执行查询,返回ResultSet
- executeUpdate: 执行DML语句,返回受影响的行数
8.2 进阶问题
-
什么是数据库连接池?为什么使用它?
数据库连接池是管理数据库连接的缓存池,通过复用连接减少创建和关闭连接的开销,提高性能。
-
JDBC如何处理事务?
通过Connection的setAutoCommit(false)关闭自动提交,使用commit()提交事务,rollback()回滚事务。
-
ResultSet.TYPE_SCROLL_INSENSITIVE和ResultSet.TYPE_SCROLL_SENSITIVE的区别?
- INSENSITIVE: 结果集不反映底层数据的更改
- SENSITIVE: 结果集反映底层数据的更改
-
JDBC中的批处理是什么?
批处理允许将多个SQL语句分组为一个单元一次性提交到数据库,减少网络通信开销,提高性能。
8.3 实战问题
-
如何优化JDBC性能?
- 使用连接池
- 使用PreparedStatement
- 使用批处理
- 合理设置fetch size
- 及时关闭资源
-
如何处理JDBC中的大数据类型?
使用setBinaryStream()、setCharacterStream()等方法处理BLOB和CLOB类型数据。
-
如何实现JDBC的分页查询?
不同数据库语法不同,MySQL使用LIMIT,Oracle使用ROWNUM,也可以通过ResultSet的absolute()方法实现。
java
// MySQL分页
String sql = "SELECT * FROM users LIMIT ?, ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, (pageNo - 1) * pageSize); // 起始位置
pstmt.setInt(2, pageSize); // 每页大小
九、总结
JDBC作为Java访问数据库的标准API,是每个Java开发者必须掌握的基础技能。本文从JDBC的基本概念开始,详细介绍了核心组件、编程步骤、事务处理、连接池等关键知识点,并涵盖了常见的面试问题。
随着技术的发展,虽然现在更多使用ORM框架如MyBatis、Hibernate等,但这些框架底层仍然基于JDBC。深入理解JDBC原理和最佳实践,不仅有助于编写高效的数据库访问代码,也是解决复杂数据库问题的关键。
希望本文能帮助你全面掌握JDBC,无论是日常开发还是面试准备,都能游刃有余。