上一篇
在 Java 中通过 JDBC 调用数据库:先加载驱动类,用 DriverManager 获取连接;再创建 Statement 对象,执行 SQL 语句;最后处理结果集
以下是关于 Java 调用数据库 的完整技术指南,涵盖核心步骤、最佳实践及典型场景实现:
前置准备与核心依赖
1 必备组件清单
| 类别 | 名称/描述 | 作用说明 |
|---|---|---|
| JDBC驱动 | mysql-connector-java.jar |
MySQL官方提供的Java数据库连接器 |
| 项目配置文件 | jdbc.properties |
存储数据库连接参数(如URL/账号/密码) |
| Java核心API | java.sql. |
提供标准数据库操作接口 |
| IDE工具链 | Maven/Gradle + IntelliJ IDEA | 项目管理与代码调试支持 |
2 环境配置要点
- Maven依赖声明(以MySQL为例):
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency>
- 属性文件内容示例 (
src/main/resources/jdbc.properties):db.url=jdbc:mysql://localhost:3306/mydatabase?useSSL=false&serverTimezone=UTC db.user=root db.password=123456
标准开发流程详解
1 五步法实现数据库交互
| 序号 | 阶段 | 关键技术点 | 注意事项 |
|---|---|---|---|
| 1 | 加载数据库驱动 | Class.forName("com.mysql.cj.jdbc.Driver") |
仅适用于传统方式,新版JDBC可省略此步 |
| 2 | 建立数据库连接 | DriverManager.getConnection(url, user, password) |
必传三要素:URL/用户名/密码 |
| 3 | 创建执行器对象 | Connection.createStatement() → 普通语句prepareStatement(sql) → 预编译语句 |
优先使用PreparedStatement防SQL注入 |
| 4 | 执行SQL指令 | executeUpdate()→更新操作executeQuery()→查询操作 |
根据业务类型选择对应方法 |
| 5 | 处理结果集 | ResultSet遍历+next()判断+getXXX()取值 |
严格遵循列索引/别名映射规则 |
| 6 | 资源释放 | close()顺序:ResultSet→Statement→Connection |
必须放在finally块保证执行 |
2 完整代码示例(增删改查全场景)
import java.sql.;
import java.util.Properties;
public class JdbcDemo {
// 静态常量定义
private static final String PROPERTIES_PATH = "jdbc.properties";
public static void main(String[] args) {
// 1. 读取配置文件
Properties prop = new Properties();
try {
prop.load(Thread.currentThread().getContextClassLoader().getResourceAsStream(PROPERTIES_PATH));
} catch (Exception e) {
System.err.println("配置文件加载失败: " + e.getMessage());
return;
}
// 2. 声明数据库连接变量
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 3. 建立数据库连接
conn = DriverManager.getConnection(
prop.getProperty("db.url"),
prop.getProperty("db.user"),
prop.getProperty("db.password")
);
// ========== 插入数据示例 ==========
String insertSql = "INSERT INTO users(name, email) VALUES(?, ?)";
pstmt = conn.prepareStatement(insertSql);
pstmt.setString(1, "张三");
pstmt.setString(2, "zhangsan@example.com");
int affectedRows = pstmt.executeUpdate();
System.out.println("插入成功,影响行数: " + affectedRows);
// ========== 查询数据示例 ==========
String selectSql = "SELECT id, name, email FROM users WHERE id = ?";
pstmt = conn.prepareStatement(selectSql);
pstmt.setInt(1, 1); // 假设刚插入的ID为1
rs = pstmt.executeQuery();
while(rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
System.out.printf("查询结果 ID:%d, 姓名:%s, 邮箱:%s%n", id, name, email);
}
// ========== 更新数据示例 ==========
String updateSql = "UPDATE users SET email=? WHERE id=?";
pstmt = conn.prepareStatement(updateSql);
pstmt.setString(1, "new_email@example.com");
pstmt.setInt(2, 1);
affectedRows = pstmt.executeUpdate();
System.out.println("更新成功,影响行数: " + affectedRows);
// ========== 删除数据示例 ==========
String deleteSql = "DELETE FROM users WHERE id=?";
pstmt = conn.prepareStatement(deleteSql);
pstmt.setInt(1, 1);
affectedRows = pstmt.executeUpdate();
System.out.println("删除成功,影响行数: " + affectedRows);
} catch (SQLException e) {
System.err.println("数据库操作异常: " + e.getMessage());
e.printStackTrace();
} finally {
// 4. 逆向关闭资源
try { if(rs != null) rs.close(); } catch(SQLException ignored) {}
try { if(pstmt != null) pstmt.close(); } catch(SQLException ignored) {}
try { if(conn != null) conn.close(); } catch(SQLException ignored) {}
}
}
}
3 关键机制解析
| 技术点 | 实现原理 | 优势分析 |
|---|---|---|
| 预编译语句(占位符) | SQL模板预先编译,参数化绑定 | 防止SQL注入 提升重复执行效率 |
| 事务控制 | conn.setAutoCommit(false) + commit()/rollback() |
保证原子性操作 批量操作一致性 |
| 批处理 | addBatch() + executeBatch() |
⏱️ 减少网络往返次数 ⏱️ 提升大数据量性能 |
| 连接池 | HikariCP/C3P0/DBCP等第三方库 | 复用物理连接 降低建连开销 |
进阶优化策略
1 连接池选型对比表
| 连接池名称 | 特点 | 适用场景 |
|---|---|---|
| HikariCP | 轻量级/高性能/零配置启动 | 中小型应用首选 |
| C3P0 | 功能丰富/支持缓存/JMX监控 | 复杂企业级系统 |
| DBCP | Apache Commons组件/简单易用 | 快速原型开发 |
| Druid | 阿里开源/监控功能强大/支持SQL防火墙 | 高并发生产环境 |
2 性能调优建议
- 索引优化:对高频查询字段建立复合索引
- 分页查询:使用
LIMIT startIndex, pageSize替代全表扫描 - 懒加载:仅在需要时初始化数据库连接
- 超时设置:
jdbc:mysql://...?connectTimeout=5000&socketTimeout=10000 - 二进制日志:开启
binlog用于主从复制和故障恢复
常见错误解决方案
| 错误现象 | 根本原因 | 解决方法 |
|---|---|---|
Communications link failure |
网络中断/数据库服务未启动 | 检查防火墙/重启数据库服务 |
Access denied for user |
用户名密码错误/权限不足 | 核对凭证/授予相应权限 |
Table doesn't exist |
表名大小写不匹配/未创建表 | 统一使用反引号`table_name` |
Too many connections |
连接数超过最大限制 | 增大maxPoolSize或优化业务逻辑 |
Lock wait timeout exceeded |
长事务导致锁竞争 | 缩短事务时长/优化锁粒度 |
相关问答FAQs
Q1: 如何有效防止SQL注入攻击?
A: 应始终使用PreparedStatement代替字符串拼接的Statement,通过占位符进行参数化绑定,JDBC驱动会自动对特殊字符进行转义。
// 危险做法(易受注入) String sql = "SELECT FROM users WHERE username='" + userInput + "'"; // 安全做法(推荐) String sql = "SELECT FROM users WHERE username=?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, userInput);
Q2: 为什么要按照ResultSet→Statement→Connection的顺序关闭资源?
A: 因为这三个对象存在依赖关系:ResultSet依赖于Statement,Statement又依赖于Connection,如果先关闭父级对象(如Connection),子级对象(如ResultSet)会立即失效,反向关闭可以确保所有资源都被正确释放,避免内存泄漏,典型的finally块结构如下:
finally {
try { if(rs != null) rs.close(); } catch(SQLException ignored) {}
try { if(pstmt != null) pstmt.close(); } catch(SQLException ignored) {}
try { if(conn != null) conn.close(); } catch(SQLException ignored) {}
