MySQL JDBC 从入门到精通实践指南
JDBC 与 MySQL 的深度解析与实战指南
1. JDBC 基础概念
JDBC(Java Database Connectivity)是 Java 提供的一套用于操作关系型数据库的标准 API。它通过统一的接口定义,让 Java 程序可以独立于具体数据库实现数据操作。核心思想是“面向接口编程”,即开发者只依赖 JDBC API,而不关心底层数据库的差异,数据库厂商提供相应的 JDBC 驱动即可。
JDBC 的核心组件包括:
- DriverManager / DataSource:用于加载和管理数据库驱动以及创建数据库连接。
- Connection:表示与数据库的会话,负责事务控制、SQL 执行和资源管理。
- Statement / PreparedStatement / CallableStatement:
Statement:执行静态 SQL,不带参数。PreparedStatement:预编译 SQL,支持参数化查询,防止 SQL 注入,性能优于 Statement。CallableStatement:用于调用存储过程。
- ResultSet:用于封装查询结果,提供按列访问数据的能力。
- SQLException:数据库操作异常的统一处理类。
2. MySQL JDBC 驱动配置
在 Java 项目中,连接 MySQL 需要依赖官方 JDBC 驱动。以 Maven 项目为例:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.1.0</version>
</dependency>
JDBC URL 格式如下:
jdbc:mysql://<host>:<port>/<database>?useSSL=false&serverTimezone=UTC
其中常用参数解释:
useSSL=false:关闭 SSL 连接,简化开发环境配置。serverTimezone=UTC:指定服务器时区,避免时间字段偏移问题。characterEncoding=UTF-8:指定字符编码,避免中文乱码。
3. 数据库建表与示例数据
为了便于 JDBC 操作演示,先创建一张用户表:
CREATE DATABASE IF NOT EXISTS demo_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE demo_db;
CREATE TABLE IF NOT EXISTS user (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
password VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO user (username, password, email) VALUES
('alice', 'pass123', 'alice@example.com'),
('bob', 'pass456', 'bob@example.com');
4. JDBC 基本操作
4.1 建立数据库连接
String url = "jdbc:mysql://localhost:3306/demo_db?useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "root123";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
System.out.println("数据库连接成功!");
} catch (SQLException e) {
e.printStackTrace();
}
这里使用了 Java 7 的 try-with-resources 语法,保证 Connection 自动关闭,防止资源泄露。
4.2 执行查询操作
String sql = "SELECT id, username, email, created_at FROM user";
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
String email = rs.getString("email");
Timestamp createdAt = rs.getTimestamp("created_at");
System.out.printf("ID: %d, 用户名: %s, 邮箱: %s, 创建时间: %s%n",
id, username, email, createdAt);
}
} catch (SQLException e) {
e.printStackTrace();
}
4.3 使用 PreparedStatement 执行参数化查询
String sql = "SELECT id, username, email FROM user WHERE username = ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, "alice");
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
System.out.printf("ID: %d, 用户名: %s, 邮箱: %s%n",
rs.getInt("id"), rs.getString("username"), rs.getString("email"));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
5. 数据更新操作
5.1 插入数据
String insertSql = "INSERT INTO user(username, password, email) VALUES(?, ?, ?)";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement ps = conn.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, "charlie");
ps.setString(2, "pass789");
ps.setString(3, "charlie@example.com");
int rows = ps.executeUpdate();
if (rows > 0) {
try (ResultSet keys = ps.getGeneratedKeys()) {
if (keys.next()) {
System.out.println("插入成功,ID: " + keys.getInt(1));
}
}
}
} catch (SQLException e) {
e.printStackTrace();
}
5.2 更新数据
String updateSql = "UPDATE user SET email = ? WHERE username = ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement ps = conn.prepareStatement(updateSql)) {
ps.setString(1, "alice_new@example.com");
ps.setString(2, "alice");
int rows = ps.executeUpdate();
System.out.println("更新行数: " + rows);
} catch (SQLException e) {
e.printStackTrace();
}
5.3 删除数据
String deleteSql = "DELETE FROM user WHERE username = ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement ps = conn.prepareStatement(deleteSql)) {
ps.setString(1, "bob");
int rows = ps.executeUpdate();
System.out.println("删除行数: " + rows);
} catch (SQLException e) {
e.printStackTrace();
}
6. 事务管理
在 JDBC 中,事务默认是自动提交(auto-commit)模式,可以通过关闭自动提交来进行显式事务管理:
try (Connection conn = DriverManager.getConnection(url, user, password)) {
conn.setAutoCommit(false);
try (PreparedStatement ps1 = conn.prepareStatement("UPDATE user SET email=? WHERE username=?");
PreparedStatement ps2 = conn.prepareStatement("UPDATE user SET password=? WHERE username=?")) {
ps1.setString(1, "alice_tx@example.com");
ps1.setString(2, "alice");
ps1.executeUpdate();
ps2.setString(1, "newpass123");
ps2.setString(2, "alice");
ps2.executeUpdate();
conn.commit();
System.out.println("事务提交成功!");
} catch (SQLException e) {
conn.rollback();
System.out.println("事务回滚!");
e.printStackTrace();
}
} catch (SQLException e) {
e.printStackTrace();
}
7. 批量操作与性能优化
7.1 批量插入
String batchSql = "INSERT INTO user(username, password, email) VALUES(?, ?, ?)";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement ps = conn.prepareStatement(batchSql)) {
conn.setAutoCommit(false);
for (int i = 1; i <= 1000; i++) {
ps.setString(1, "user" + i);
ps.setString(2, "pass" + i);
ps.setString(3, "user" + i + "@example.com");
ps.addBatch();
}
ps.executeBatch();
conn.commit();
System.out.println("批量插入完成!");
} catch (SQLException e) {
e.printStackTrace();
}
7.2 PreparedStatement 缓存
在高频 SQL 执行场景下,使用 PreparedStatement 可以充分利用数据库的 SQL 预编译功能,减少解析和优化开销。
8. JDBC 与连接池
在生产环境中,直接使用 DriverManager 获取连接存在性能瓶颈,推荐使用连接池,例如 HikariCP 或 DBCP。
HikariCP 示例
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/demo_db?useSSL=false&serverTimezone=UTC");
config.setUsername("root");
config.setPassword("root123");
config.setMaximumPoolSize(10);
HikariDataSource dataSource = new HikariDataSource(config);
try (Connection conn = dataSource.getConnection()) {
System.out.println("使用连接池成功获取连接!");
}
使用连接池的好处:
- 避免频繁创建和销毁连接带来的性能损耗
- 支持多线程并发访问
- 支持连接健康检查和自动重连
9. 高级特性
9.1 存储过程调用
String callSql = "{call getUserByUsername(?)}";
try (Connection conn = dataSource.getConnection();
CallableStatement cs = conn.prepareCall(callSql)) {
cs.setString(1, "alice");
try (ResultSet rs = cs.executeQuery()) {
if (rs.next()) {
System.out.printf("ID: %d, 用户名: %s, 邮箱: %s%n",
rs.getInt("id"), rs.getString("username"), rs.getString("email"));
}
}
}
9.2 事务隔离级别
MySQL 支持多种事务隔离级别:
READ UNCOMMITTED:允许脏读READ COMMITTED:禁止脏读REPEATABLE READ:保证可重复读(MySQL 默认)SERIALIZABLE:串行化读,最严格
在 JDBC 中可通过 Connection.setTransactionIsolation() 设置:
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
9.3 大数据量处理
对于查询大量数据,可以使用流式处理(ResultSet.TYPE_FORWARD_ONLY + fetchSize):
Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(Integer.MIN_VALUE); // MySQL 特殊处理,逐行读取
ResultSet rs = stmt.executeQuery("SELECT * FROM user");
这种方式可以避免一次性加载大量数据到内存。
10. JDBC 与 ORM 对比
JDBC 是最基础的数据库访问方式,ORM 框架如 MyBatis、Hibernate 在其基础上提供了:
- SQL 映射和对象封装
- 动态 SQL 生成
- 缓存机制
- 更方便的事务管理
尽管如此,理解 JDBC 依然是掌握 ORM 的基础,有助于排查性能问题和理解底层 SQL 执行逻辑。
11. 实战经验与优化建议
- 参数化 SQL:使用
PreparedStatement避免 SQL 注入。 - 事务控制:复杂操作应关闭自动提交,确保原子性。
- 批量操作:对于大规模插入/更新,应使用批处理减少网络和解析开销。
- 连接池:生产环境必备,提升性能并简化资源管理。
- 异常处理:使用 try-with-resources 保证资源自动释放。
- SQL 调优:通过 EXPLAIN 分析查询计划,确保索引使用合理。
- 监控与日志:记录慢查询,排查性能瓶颈。
通过系统掌握 JDBC 与 MySQL 的各类操作与优化策略,可以在 Java 项目中构建高性能、可维护的数据库访问层,为进一步学习 ORM 和分布式数据库打下坚实基础。