JDBC 架構分層
應用代碼
│
▼
JDBC API (java.sql / javax.sql)
│ ┌─ DriverManager / DataSource
│ ├─ Connection
│ ├─ Statement / PreparedStatement / CallableStatement
│ └─ ResultSet
▼
JDBC Driver (廠商實現: mysql-connector-j, postgresql, ojdbc, mssql-jdbc)
│
▼
數據庫服務器
核心 API 速查
| 接口/類 | 核心方法 | 用途 |
| DriverManager | getConnection(url, props) | 簡單獲取連接(非池化) |
| DataSource | getConnection() | 連接池標準入口 |
| Connection | createStatement()
prepareStatement(sql)
prepareCall(sql)
setAutoCommit(false)
commit()/rollback() | 會話控制、事務、創建執行器 |
| Statement | executeUpdate(sql)
executeQuery(sql) | 靜態 SQL |
| PreparedStatement | setX(index, val)
executeUpdate()
executeQuery() | 預編譯、防注入、批量 |
| CallableStatement | registerOutParameter(idx, type)
getX(idx) | 存儲過程 |
| ResultSet | next()
getX(colName/index)
getMetaData() | 結果集游標 |
標準 CRUD 模板
1. 查詢
String sql = "SELECT id, name, age FROM users WHERE age > ? AND status = ?";
try (Connection conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, 18);
ps.setString(2, "ACTIVE");
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
User u = new User();
u.setId(rs.getLong("id"));
u.setName(rs.getString("name"));
u.setAge(rs.getInt("age"));
list.add(u);
}
}
}
2. 插入/更新/刪除
String sql = "INSERT INTO users(name, age, status) VALUES (?, ?, ?)";
try (Connection conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, "Alice");
ps.setInt(2, 25);
ps.setString(3, "ACTIVE");
int affected = ps.executeUpdate();
if (affected > 0) {
try (ResultSet keys = ps.getGeneratedKeys()) {
if (keys.next()) System.out.println("新增 ID: " + keys.getLong(1));
}
}
}
3. 批量操作
String sql = "INSERT INTO logs(level, msg) VALUES (?, ?)";
try (Connection conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
conn.setAutoCommit(false);
for (LogEntry e : entries) {
ps.setString(1, e.getLevel());
ps.setString(2, e.getMsg());
ps.addBatch();
}
int[] counts = ps.executeBatch();
conn.commit();
}
4. 存儲過程
-- MySQL 示例
CREATE PROCEDURE get_user_count(IN min_age INT, OUT total BIGINT)
BEGIN SELECT COUNT(*) INTO total FROM users WHERE age >= min_age; END;
String sql = "{CALL get_user_count(?, ?)}";
try (Connection conn = ds.getConnection();
CallableStatement cs = conn.prepareCall(sql)) {
cs.setInt(1, 18);
cs.registerOutParameter(2, Types.BIGINT);
cs.execute();
System.out.println("用戶數: " + cs.getLong(2));
}
事務管理模式
// 編程式事務
public void transfer(long from, long to, BigDecimal amount) {
Connection conn = null;
try {
conn = ds.getConnection();
conn.setAutoCommit(false);
// 扣減
updateBalance(conn, from, amount.negate());
// 增加
updateBalance(conn, to, amount);
conn.commit();
} catch (Exception e) {
if (conn != null) conn.rollback();
throw new RuntimeException(e);
} finally {
if (conn != null) conn.setAutoCommit(true); // 歸還池前恢復
}
}
生產環境建議使用 @Transactional (Spring) 或程序化 TransactionTemplate。
連接池選型
| 連接池 | 特點 | 推薦場景 |
| HikariCP | 極速、輕量、預設配置優秀 | 默認首選 (Spring Boot 2+ 默認) |
| Druid | 監控強、SQL 防火墻、阿里出品 | 需要詳細監控、防注入 |
| Tomcat JDBC Pool | Tomcat 內置、穩定 | Tomcat 環境無額外依賴 |
HikariCP 基礎配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
username: root
password: 123456
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
避坑指南
- ❌ 長時間持有 Connection → 池耗盡 → 設置
max-lifetime、idle-timeout
- ❌ Statement 未關閉 → 游標洩漏 → try-with-resources 強制關閉
- ❌ 拼接 SQL 導致注入 → 全量 PreparedStatement
- ❌ 大結果集一次性載入 OOM →
setFetchSize(100) + 流式處理 / 分頁
- ❌ 事務邊界不清 → 數據不一致 → 統一用
@Transactional 宣告式
現代替代方案
- MyBatis / JPA / jOOQ:ORM/映射框架,減少樣板代碼
- Spring JDBC Template:簡化 JDBC 模板
- R2DBC:響應式非阻塞驅動
理解 JDBC 底層原理,排查連接洩漏、死鎖、慢 SQL 才能得心應手。
- 上一篇文章: Java 並發編程實戰(八):UncaughtExceptionHandler 處理線程非受檢異常
- 下一篇文章: Java 泛型與反射實作通用 CSV 匯出工具
Copyright ©
程式碼之家 All Rights Reserved