上一篇
Java通过JDBC实现数据库查询:加载驱动→建立连接→创建Statement→执行SQL查询语句
以下是关于 Java 中实现数据库查询 的完整指南,涵盖核心原理、代码实践、最佳实践及常见问题解决方案:
前置准备与核心组件
1 依赖环境搭建
| 组件 | 说明 | 示例值 |
|---|---|---|
| JDK版本 | Java Development Kit | ≥8 (建议LTS长期支持版) |
| 数据库驱动 | 根据目标数据库选择对应厂商提供的JDBC驱动 | MySQL: mysql-connector-java-8.x.jar |
| IDE/构建工具 | IntelliJ IDEA/Eclipse + Maven/Gradle | |
| 数据库服务端 | 已启动且可访问的数据库实例 | 本地MySQL/PostgreSQL/Oracle等 |
2 关键类库解析
| 类/接口 | 功能描述 | 典型用途 |
|---|---|---|
DriverManager |
管理数据库驱动注册与连接创建 | getConnection() |
Connection |
表示与数据库的会话连接 | 设置事务隔离级别、创建Statement |
Statement |
执行静态SQL语句的基础接口 | 简单查询/更新操作 |
PreparedStatement |
支持预编译带占位符(?)的SQL语句 | 防SQL注入、批量操作 |
ResultSet |
存储查询结果的二维表结构 | 遍历数据行、获取列值 |
ResultSetMetaData |
描述结果集的结构信息 | 动态获取列名、类型、数量 |
标准JDBC查询实现流程
1 基础查询四步曲
// 1. 加载数据库驱动(新版JDBC可省略此步)
Class.forName("com.mysql.cj.jdbc.Driver"); // 显式加载驱动类
// 2. 建立数据库连接
String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, user, password);
// 3. 创建Statement并执行查询
String sql = "SELECT id, name, age FROM users WHERE status = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 1); // 设置第一个占位符参数为整型1
ResultSet rs = pstmt.executeQuery();
// 4. 处理结果集
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.printf("ID: %d, Name: %s, Age: %d%n", id, name, age);
}
// 5. 释放资源(务必按反向顺序关闭)
rs.close();
pstmt.close();
conn.close();
2 关键细节说明
| 环节 | 注意事项 | 改进方案 |
|---|---|---|
| 驱动加载 | Java SPI机制会自动发现META-INF/services/java.sql.Driver文件 |
旧版需手动调用Class.forName() |
| 连接字符串参数 | characterEncoding=UTF-8解决中文乱码;rewriteBatchedStatements=true提升批处理性能 |
根据数据库特性调整参数 |
| SQL执行方式 | executeQuery()仅用于SELECT;executeUpdate()用于INSERT/UPDATE/DELETE |
区分返回类型 |
| 结果集遍历 | rs.next()每次移动游标到下一行,初始位置在首行之前 |
使用do-while循环处理空结果集 |
| 资源关闭 | 未关闭会导致连接池耗尽;推荐使用try-with-resources自动关闭 | 见下文异常处理章节 |
高级特性与最佳实践
1 参数化查询防注入
风险对比:
| 写法类型 | 示例代码 | 安全风险 |
|——————–|——————————————|——————————|
| 字符串拼接 | "SELECT FROM users WHERE name='"+user+"'" | 易受SQL注入攻击 |
| PreparedStatement | pstmt.setString(1, userInput) | 完全避免SQL注入 |
批量操作优化:
// 批量插入示例
String batchSql = "INSERT INTO logs(content) VALUES(?)";
PreparedStatement batchPstmt = conn.prepareStatement(batchSql);
for (String log : logList) {
batchPstmt.setString(1, log);
batchPstmt.addBatch(); // 累积批处理命令
}
int[] results = batchPstmt.executeBatch(); // 一次性执行所有命令
2 事务控制
try {
conn.setAutoCommit(false); // 关闭自动提交
// 执行多个关联操作...
conn.commit(); // 全部成功则提交
} catch (SQLException e) {
conn.rollback(); // 出现异常则回滚
throw new RuntimeException(e);
} finally {
conn.setAutoCommit(true); // 恢复默认设置
}
3 try-with-resources自动关闭资源
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 执行查询逻辑...
} catch (SQLException e) {
// 异常处理...
} // 自动关闭conn和pstmt,无需显式调用close()
主流ORM框架对比
| 框架 | 特点 | 适用场景 | 学习曲线 |
|---|---|---|---|
| MyBatis | SQL映射灵活,支持动态SQL | 复杂查询较多的企业级应用 | 中等 |
| Hibernate | JPA规范实现,面向对象映射 | ORM需求强烈的领域驱动设计 | 较高 |
| JdbcTemplate | Spring封装的轻量级模板 | Spring生态项目快速开发 | 低 |
| Flyway/Liquibase | 数据库版本控制工具 | 团队协作时的数据库迁移管理 | 低 |
常见问题排查手册
Q1: java.sql.SQLException: Access denied for user ‘root’@’localhost’ (using password: YES)
原因分析:
- 用户名/密码错误
- 用户权限不足(缺少SELECT权限)
- 身份验证插件不匹配(MySQL8默认使用caching_sha2_password)
解决方案:
- 通过命令行验证账号密码:
mysql -u root -p - 授予权限:
GRANT ALL PRIVILEGES ON mydb. TO 'root'@'localhost'; FLUSH PRIVILEGES; - 修改连接字符串添加认证插件参数:
?allowPublicKeyRetrieval=true
Q2: com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
原因分析:
- 数据库服务未启动或网络不通
- 防火墙阻止了3306端口
- 连接超时时间过短(默认8秒)
解决方案:
- 检查数据库服务状态:
systemctl status mysql - 测试端口连通性:
telnet localhost 3306 - 调整连接超时参数:
jdbc:mysql://localhost:3306/mydb?connectTimeout=5000
相关问答FAQs
Q1: 如何判断查询结果是否为空?
答: 通过ResultSet的next()方法判断,当rs.next()返回false时表示没有数据,推荐使用以下模式:
if (!rs.next()) {
System.out.println("未查询到任何记录");
} else {
do {
// 处理每行数据...
} while (rs.next());
}
注意:next()方法会将指针移动到第一行,因此需要先判断是否存在数据。
Q2: 为什么推荐使用PreparedStatement而不是Statement?
答: 主要优势包括:
- 安全性:自动转义特殊字符,彻底杜绝SQL注入攻击,例如当用户输入
' OR '1'='1时,普通Statement会变成反面查询,而PreparedStatement会将其视为普通字符串。 - 性能优化:数据库服务器会对预编译的SQL进行缓存,重复执行相同结构的查询时效率更高。
- 代码可读性:使用占位符(?)使SQL语句更清晰,参数设置与业务逻辑分离。
- 类型安全:通过
setXXX()方法强制类型转换,避免类型错误
