上一篇
在Java中使用数据库需引入JDBC驱动,加载驱动类,建立连接,创建Statement对象,执行SQL语句,处理ResultSet结果集,最后
核心前置条件
1 技术栈组成
| 组件 | 作用 | 典型实现 |
|---|---|---|
| JDBC API | Java标准接口,统一访问关系型数据库 | java.sql包 |
| 驱动管理器 | 动态加载数据库厂商提供的JDBC驱动 | Class.forName() |
| 数据库驱动 | 实现JDBC接口的具体类(不同数据库需对应驱动) | MySQL Connector/J |
| 物理连接 | 通过网络协议与数据库服务器建立TCP长连接 | DriverManager.getConnection() |
| SQL引擎 | 解析并执行SQL语句,返回结果集 | Database Server内部模块 |
2 环境准备清单
开发工具: IntelliJ IDEA/Eclipse + Maven/Gradle
依赖配置 (以MySQL为例):
<!-pom.xml -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version> <!-根据实际版本调整 -->
</dependency>
️ 注意: 不同数据库驱动包名称不同(Oracle为ojdbcX.jar,PostgreSQL为postgresql-XX.jar)
标准开发流程详解
1 七步标准操作法
| 序号 | 步骤 | 关键代码示例 | 说明 |
|---|---|---|---|
| 1 | 加载数据库驱动 | Class.forName("com.mysql.cj.jdbc.Driver") |
触发静态初始化块注册驱动到DriverManager |
| 2 | 建立数据库连接 | DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "password") |
URL格式:协议://主机:端口/数据库名 |
| 3 | 创建执行器对象 | Connection con = ...;<br>Statement stmt = con.createStatement(); |
三种执行器类型:Statement/PreparedStatement/CallableStatement |
| 4 | 执行SQL语句 | ResultSet rs = stmt.executeQuery("SELECT FROM users"); |
executeQuery()用于查询,executeUpdate()用于DML/DDL |
| 5 | 处理结果集 | while(rs.next()){ System.out.println(rs.getString("username")); } |
通过游标逐行读取,列索引从1开始/列名直接获取 |
| 6 | 事务控制 | con.setAutoCommit(false);<br>// 多条SQL后<br>con.commit(); |
默认自动提交,显式事务需关闭自动提交+手动提交/回滚 |
| 7 | 资源释放 | rs.close();<br>stmt.close();<br>con.close(); |
逆向顺序关闭,建议使用try-with-resources自动管理 |
2 完整代码演示(带注释)
import java.sql.;
public class JdbcDemo {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement pstmt = null;
ResultSet resultSet = null;
try {
// 1. 加载驱动(新版MySQL可省略此步)
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 建立连接(URL含时区配置)
String url = "jdbc:mysql://localhost:3306/testdb?serverTimezone=UTC";
connection = DriverManager.getConnection(url, "root", "123456");
// 3. 使用预编译语句防止SQL注入
String sql = "SELECT id, name, email FROM users WHERE age > ?";
pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, 18); // 设置第一个占位符的值
// 4. 执行查询
resultSet = pstmt.executeQuery();
// 5. 处理结果集
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String email = resultSet.getString("email");
System.out.printf("ID:%d Name:%s Email:%s%n", id, name, email);
}
// 6. 执行更新操作示例
int affectedRows = pstmt.executeUpdate("UPDATE users SET status=1 WHERE id=100");
System.out.println("更新了" + affectedRows + "条记录");
} catch (ClassNotFoundException e) {
System.err.println("驱动加载失败:" + e.getMessage());
} catch (SQLException e) {
System.err.println("数据库操作异常:" + e.getMessage());
try {
if (connection != null) connection.rollback(); // 事务回滚
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
// 7. 资源释放(按相反顺序)
try { if(resultSet != null) resultSet.close(); } catch(SQLException e){}
try { if(pstmt != null) pstmt.close(); } catch(SQLException e){}
try { if(connection != null) connection.close(); } catch(SQLException e){}
}
}
}
关键要素深度解析
1 三种执行器对比表
| 特性 | Statement | PreparedStatement | CallableStatement |
|---|---|---|---|
| 主要用途 | 简单SQL执行 | 带参数的预编译SQL | 存储过程调用 |
| SQL注入防护 | 不安全 | 参数化查询 | |
| 执行效率 | 较低(每次编译) | 高(首次编译后缓存) | 高 |
| 批处理支持 | 有限 | batchUpdate() | |
| 适用场景 | 一次性简单查询 | 重复执行的参数化查询 | T-SQL/PL/SQL脚本执行 |
2 连接池优化方案
| 方案 | 特点 | 推荐场景 |
|---|---|---|
| HikariCP | 轻量级高性能,默认最大10个连接 | Web应用首选 |
| Druid | 监控统计功能强大,支持SQL防火墙 | 企业级应用 |
| DBCP | Tomcat自带,配置简单 | 小型项目快速集成 |
| C3P0 | 历史悠久,功能全面 | 传统项目维护 |
示例配置(HikariCP):
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("123456");
config.setMaximumPoolSize(10); // 最大连接数
HikariDataSource ds = new HikariDataSource(config);
Connection connection = ds.getConnection();
高级技巧与注意事项
1 SQL注入防御策略
错误写法:字符串拼接会导致破绽
String sql = "SELECT FROM users WHERE username='" + userInput + "'"; // 危险!
️ 正确做法:强制使用预编译语句
String sql = "SELECT FROM users WHERE username=?"; PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setString(1, userInput); // 自动转义特殊字符
2 批量操作优化
String sql = "INSERT INTO logs(content) VALUES(?)";
connection.setAutoCommit(false); // 关闭自动提交提升性能
PreparedStatement pstmt = connection.prepareStatement(sql);
for (String log : logList) {
pstmt.setString(1, log);
pstmt.addBatch(); // 加入批处理队列
}
int[] results = pstmt.executeBatch(); // 批量执行
connection.commit(); // 手动提交事务
3 BLOB/CLOB大字段处理
// 读取图片文件
Blob imageBlob = resultSet.getBlob("avatar");
InputStream binaryStream = imageBlob.getBinaryStream();
byte[] bytes = binaryStream.readAllBytes(); // Java 9+方法
// 写入文本文档
Clob documentClob = connection.createClob();
documentClob.setString(1, "大量文本内容");
pstmt.setClob(1, documentClob);
常见错误及解决方案
| 错误类型 | 典型表现 | 根本原因 | 解决方案 |
|---|---|---|---|
| CommunicationsException | java.sql.SQLException: No suitable driver found | 未加载驱动或驱动不匹配 | 检查Class.forName()和驱动版本 |
| Access denied | SQLSTATE[HY000] [28000] | 数据库权限不足 | 授予用户相应表的操作权限 |
| Latin-1 encoding error | 中文乱码 | 字符集配置不一致 | URL添加?characterEncoding=UTF-8 |
| Too many connections | Exceeded maximum pool size | 连接未及时释放 | 使用连接池+合理设置超时时间 |
| Lock wait timeout | Deadlock found when trying to get lock | 事务竞争导致死锁 | 缩小事务范围+重试机制 |
相关问答FAQs
Q1: 为什么会出现”No suitable driver found”错误?
A: 此错误通常由以下原因导致:①未正确加载数据库驱动(忘记调用Class.forName());②使用的驱动版本与数据库版本不兼容;③Maven依赖未正确导入,解决方案:检查pom.xml中的驱动依赖版本,确认已添加对应的Class.forName()调用,且驱动JAR包确实存在于classpath中。
Q2: 如何处理数据库连接时的中文乱码问题?
A: 中文乱码的根本原因是字符集编码不一致,解决方法:①在数据库连接URL中添加参数?characterEncoding=UTF-8&useUnicode=true;②确保数据库表的字符集设置为utf8mb4;③在Java代码中统一使用UTF-8编码,例如连接字符串应为:jdbc:mysql://localhost:3306/mydb?characterEncoding=UTF-8&useUnicode=true&serverTimezone=UTC
