JDBC(Java Database Connectivity)是 Java 存取關聯式資料庫的標準 API,定義了統一介面,屏蔽底層驅動差異。核心四大物件:
com.mysql.cj.jdbc.Driver)<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId> <!-- 8.0+ 套件名變更 -->
<version>8.0.33</version>
</dependency>
或 Gradle:implementation 'com.mysql:mysql-connector-j:8.0.33'。舊版 mysql-connector-java 已停止維護。
-- 建立資料庫
CREATE DATABASE IF NOT EXISTS jdbc_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 建立測試表
USE jdbc_demo;
CREATE TABLE IF NOT EXISTS student (
sno CHAR(7) PRIMARY KEY COMMENT '學號',
sname VARCHAR(20) NOT NULL COMMENT '姓名',
sex CHAR(2) COMMENT '性別',
bdate DATE COMMENT '出生日期',
height DECIMAL(5,2) DEFAULT 0 COMMENT '身高'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 插入測試資料
INSERT INTO student VALUES
('0104421', '周遠行', '男', '1990-01-15', 175.50),
('0208123', '王義平', '女', '1991-03-22', 162.00),
('0309120', '何問起', '男', '1992-07-08', 180.00);
jdbc:mysql://localhost:3306/jdbc_demo?
useSSL=false& # 開發環境關閉 SSL
serverTimezone=Asia/Taipei& # 時區設定,避免時間偏移
allowPublicKeyRetrieval=true& # 允許公開金鑰獲取(MySQL 8 預設 caching_sha2_password)
characterEncoding=utf-8& # 字元編碼
useUnicode=true& # 使用 Unicode
autoReconnect=true& # 自動重連
failOverReadOnly=false # 讀寫分離配置
public class JdbcDemo {
private static final String URL = "jdbc:mysql://localhost:3306/jdbc_demo?useSSL=false&serverTimezone=Asia/Taipei";
private static final String USER = "root";
private static final String PASSWORD = "your_password";
public static void main(String[] args) {
String sql = "SELECT sno, sname, sex, bdate, height FROM student";
// 1. 獲取連線 2. 建立 Statement 3. 執行查詢 4. 處理結果集
// Try-with-resources 確保連線、Statement、ResultSet 自動關閉(JDK 7+)
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
System.out.println("連線成功!查詢結果:");
System.out.printf("%-10s %-10s %-4s %-12s %s%n", "學號", "姓名", "性別", "出生日期", "身高");
System.out.println("--------------------------------------------------");
while (rs.next()) { // 游標向下移動
String sno = rs.getString("sno");
String sname = rs.getString("sname");
String sex = rs.getString("sex");
Date bdate = rs.getDate("bdate"); // 直接獲取 java.sql.Date
BigDecimal height = rs.getBigDecimal("height"); // 精確數值型別
System.out.printf("%-10s %-10s %-4s %-12s %s%n", sno, sname, sex, bdate, height);
}
} catch (SQLException e) {
System.err.println("資料庫操作異常:" + e.getMessage());
e.printStackTrace();
} // 自動關閉 rs, stmt, conn
}
}
String insertSql = "INSERT INTO student (sno, sname, sex, bdate, height) VALUES (?, ?, ?, ?, ?)";
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
pstmt.setString(1, "0401001");
pstmt.setString(2, "新學生");
pstmt.setString(3, "男");
pstmt.setDate(4, Date.valueOf("2000-01-01")); // LocalDate -> sql.Date
pstmt.setBigDecimal(5, new BigDecimal("170.50"));
int affected = pstmt.executeUpdate();
System.out.println("插入 " + affected + " 筆資料");
}
? 佔位符由驅動程式處理逸出,徹底防注入;SQL 預編譯可重複執行計畫,批次操作效能更優。
conn.setAutoCommit(false); // 關閉自動提交,手動控制事務
try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
for (int i = 0; i < 10000; i++) {
pstmt.setString(1, String.format("%07d", i));
pstmt.setString(2, "學生" + i);
pstmt.setString(3, i % 2 == 0 ? "男" : "女");
pstmt.setDate(4, Date.valueOf(LocalDate.now().minusDays(i)));
pstmt.setBigDecimal(5, new BigDecimal(ThreadLocalRandom.current().nextDouble(150, 190)));
pstmt.addBatch(); // 累積批次
if (i % 1000 == 0) {
pstmt.executeBatch(); // 每 1000 筆執行一次
conn.commit();
}
}
pstmt.executeBatch(); // 執行剩餘
conn.commit();
} catch (SQLException e) {
conn.rollback(); // 回滾
throw e;
} finally {
conn.setAutoCommit(true);
}
Connection conn = null;
try {
conn = DriverManager.getConnection(URL, USER, PASSWORD);
conn.setAutoCommit(false); // 開啟事務
// 業務邏輯:轉帳扣款 + 存入
updateBalance(conn, fromAccount, -amount);
updateBalance(conn, toAccount, +amount);
recordTransaction(conn, fromAccount, toAccount, amount);
conn.commit(); // 全部成功提交
} catch (Exception e) {
if (conn != null) conn.rollback(); // 任一失敗回滾
throw new RuntimeException("轉帳失敗", e);
} finally {
if (conn != null) {
conn.setAutoCommit(true);
conn.close();
}
}
頻繁建立/關閉物理連線開銷極大。主流連線池:
# Spring Boot application.yml (HikariCP)
spring:
datasource:
url: jdbc:mysql://localhost:3306/jdbc_demo?useSSL=false&serverTimezone=Asia/Taipei
username: root
password: your_password
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
| 問題 | 原因 | 解決 |
|---|---|---|
| ClassNotFoundException | 驅動 JAR 未在 Classpath | Maven/Gradle 正確引入依賴 |
| Communications link failure | IP/Port 錯誤、防火牆、MySQL bind-address | 檢查 3306 開放、MySQL 配置 bind-address=0.0.0.0 |
| Public Key Retrieval is not allowed | MySQL 8 預設 caching_sha2_password | URL 加 allowPublicKeyRetrieval=true 或改用 mysql_native_password |
| 時區錯誤 | 伺服器/客戶端時區不一致 | URL 加 serverTimezone=Asia/Taipei |
| 中文亂碼 | 字元編碼不匹配 | URL 加 characterEncoding=utf-8&useUnicode=true,資料庫表 utf8mb4 |
| 資源洩漏 | 未關閉 Connection/Statement/ResultSet | 統一使用 Try-with-Resources 或連線池 |
JDBC 是 Java 資料存取的基石,掌握 DriverManager、Connection、PreparedStatement、ResultSet 核心 API、事務控制、批次處理與連線池配置,即可構建健壯的資料存取層。實際專案中強烈建議使用 MyBatis 或 JPA/Hibernate 等 ORM 框架進一步提升開發效率,但理解 JDBC 底層原理對於除錯、效能調優、解決框架無法覆蓋的邊界場景至關重要。