首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

JDBC 核心接口深度解析:DriverManager、Connection、Statement、ResultSet 實戰筆記

     編輯:Java 綜合教學

JDBC 核心接口深度解析

JDBC (Java Database Connectivity) 是 Java 操作關係型數據庫的標準 API。掌握其四大核心組件——DriverManager、Connection、Statement、ResultSet——的職責邊界與協作方式,是寫出高質量數據訪問代碼的關鍵。本文結合實戰代碼,系統梳理核心概念、工作原理、CRUD 模板與避坑指南。

四大核心組件職責鏈

🔧 DriverManager

角色:驅動管理器

職責:

  • 加載/註冊 JDBC 驅動
  • 管理驅動列表
  • 根據 URL 創建 Connection

核心方法:getConnection(url, user, pwd)

🔗 Connection

角色:數據庫連接/會話

職責:

  • 代表物理通信通道
  • 創建 Statement/PreparedStatement
  • 事務管理 (commit/rollback)
  • 元數據獲取

核心方法:createStatement(), prepareStatement()

📝 Statement

角色:SQL 執行載體

職責:

  • 發送 SQL 到數據庫
  • 執行查詢/更新
  • 批量處理

核心方法:executeQuery(), executeUpdate()

📊 ResultSet

角色:結果集游標

職責:

  • 封裝查詢結果
  • 游標導航 (next/previous)
  • 列數據提取 (getXxx)
  • 元數據訪問

核心方法:next(), getString(), getInt()

JDBC 標準工作流程 (五步法)

// 1. 加載驅動 (JDBC 4.0+ 可省略,SPI 自動加載)
Class.forName("com.mysql.cj.jdbc.Driver");

// 2. 建立連接
Connection conn = DriverManager.getConnection(
    "jdbc:mysql://localhost:3306/db?useSSL=false", "root", "123456");

// 3. 創建語句對象
Statement stmt = conn.createStatement();
// 或 PreparedStatement ps = conn.prepareStatement(sql);

// 4. 執行 SQL 處理結果
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
// int rows = stmt.executeUpdate("INSERT INTO ...");

// 5. 釋放資源 (反向順序,JDK 7+ 推薦 try-with-resources)
rs.close();
stmt.close();
conn.close();

實戰 CRUD 代碼範本

環境準備

-- 測試表
CREATE TABLE star_studyone (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    sex VARCHAR(10),
    age INT
);

-- Maven 依賴
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

1. 增 (INSERT) - 使用 PreparedStatement 防注入

public void insertUser(String name, String sex, int age) {
    String sql = "INSERT INTO star_studyone (name, sex, age) VALUES (?, ?, ?)";
    
    try (Connection conn = DriverManager.getConnection(URL, USER, PASS);
         PreparedStatement ps = conn.prepareStatement(sql)) {
        
        ps.setString(1, name);
        ps.setString(2, sex);
        ps.setInt(3, age);
        
        int rows = ps.executeUpdate();
        System.out.println(rows > 0 ? "✅ 插入成功" : "❌ 插入失敗");
    } catch (SQLException e) {
        throw new RuntimeException("插入異常", e);
    }
}

2. 查 (SELECT) - 結果集遍歷與元數據

public void queryUsers() {
    String sql = "SELECT * FROM star_studyone";
    
    try (Connection conn = DriverManager.getConnection(URL, USER, PASS);
         PreparedStatement ps = conn.prepareStatement(sql);
         ResultSet rs = ps.executeQuery()) {
        
        // 方式一:按列名獲取 (推薦,不依賴順序)
        while (rs.next()) {
            System.out.printf("ID: %d, 姓名: %s, 性別: %s, 年齡: %d%n",
                rs.getInt("id"),
                rs.getString("name"),
                rs.getString("sex"),
                rs.getInt("age"));
        }
        
        // 方式二:通用處理 (配合元數據)
        // ResultSetMetaData meta = rs.getMetaData();
        // int cols = meta.getColumnCount();
        // while (rs.next()) {
        //     for (int i = 1; i <= cols; i++) {
        //         System.out.print(rs.getObject(i) + "\t");
        //     }
        //     System.out.println();
        // }
    } catch (SQLException e) {
        throw new RuntimeException("查詢異常", e);
    }
}

3. 改 (UPDATE)

public void updateUserAge(int id, int newAge) {
    String sql = "UPDATE star_studyone SET age = ? WHERE id = ?";
    
    try (Connection conn = DriverManager.getConnection(URL, USER, PASS);
         PreparedStatement ps = conn.prepareStatement(sql)) {
        
        ps.setInt(1, newAge);
        ps.setInt(2, id);
        
        int rows = ps.executeUpdate();
        System.out.println(rows > 0 ? "✅ 更新成功" : "⚠️ 未找到記錄");
    } catch (SQLException e) {
        throw new RuntimeException("更新異常", e);
    }
}

4. 刪 (DELETE)

public void deleteUser(int id) {
    String sql = "DELETE FROM star_studyone WHERE id = ?";
    
    try (Connection conn = DriverManager.getConnection(URL, USER, PASS);
         PreparedStatement ps = conn.prepareStatement(sql)) {
        
        ps.setInt(1, id);
        int rows = ps.executeUpdate();
        System.out.println(rows > 0 ? "✅ 刪除成功" : "⚠️ 未找到記錄");
    } catch (SQLException e) {
        throw new RuntimeException("刪除異常", e);
    }
}

進階必知:PreparedStatement vs Statement

維度StatementPreparedStatement
SQL 注入防護❌ 無 (拼接字符串)✅ 參數化查詢,預編譯
性能 (重複執行)每次編譯預編譯緩存,複用執行計劃
代碼可讀性拼接繁瑣參數佔位符清晰
類型安全手動轉換setXxx 方法強類型
適用場景極簡一次性查詢✅ 所有生產代碼

SQL 注入演示 (反面教材)

// ❌ 危險代碼
String sql = "SELECT * FROM users WHERE name = '" + userInput + "'";
// 輸入: admin' OR '1'='1
// 實際 SQL: SELECT * FROM users WHERE name = 'admin' OR '1'='1' → 繞過驗證

// ✅ 安全代碼
String sql = "SELECT * FROM users WHERE name = ?";
ps.setString(1, userInput); // 參數化,輸入作為字面值處理

事務控制模式

public void transfer(String fromAccount, String toAccount, double amount) {
    Connection conn = null;
    try {
        conn = DriverManager.getConnection(URL, USER, PASS);
        conn.setAutoCommit(false); // 開啟事務
        
        // 扣款
        updateBalance(conn, fromAccount, -amount);
        // 入賬
        updateBalance(conn, toAccount, amount);
        
        conn.commit(); // 提交事務
        System.out.println("✅ 轉賬成功");
    } catch (SQLException e) {
        if (conn != null) {
            try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); }
        }
        throw new RuntimeException("❌ 轉賬失敗,已回滾", e);
    } finally {
        if (conn != null) {
            try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
    }
}

private void updateBalance(Connection conn, String account, double delta) throws SQLException {
    String sql = "UPDATE account SET balance = balance + ? WHERE id = ?";
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
        ps.setDouble(1, delta);
        ps.setString(2, account);
        ps.executeUpdate();
    }
}

常見錯誤避坑速查表

錯誤現象根本原因修正方案
ClassNotFoundException驅動包未在 classpathMaven 引入正確版本 mysql-connector-java
Communications link failureURL/主機/端口錯誤、防火牆檢查 jdbc:mysql://ip:port/db、telnet 驗證端口
Public Key Retrieval is not allowedMySQL 8.0+ 默認需 SSLURL 添加 allowPublicKeyRetrieval=true&useSSL=false
中文亂碼字符集不一致URL characterEncoding=utf8、表 charset=utf8mb4、JVM -Dfile.encoding=UTF-8
ResultSet 獲取數據報錯列索引從 1 開始、列名大小寫使用列名 rs.getString("name")、檢查別名
資源洩漏 (連接數耗盡)未關閉 Connection/Statement/ResultSet統一使用 try-with-resources、配置連接池

最佳實踐總結

  1. 驅動加載:JDBC 4.0+ 依賴 SPI 自動加載,無需 Class.forName
  2. 連接獲取:生產環境必須使用連接池 (HikariCP/Druid),禁用 DriverManager 直連
  3. SQL 執行:全量使用 PreparedStatement,參數化查詢防注入+性能優化
  4. 資源管理:JDK 7+ 強制使用 try-with-resources,自動關閉資源
  5. 事務控制:業務邏輯層控制事務邊界,DAO 層不處理事務
  6. 異常處理:捕獲 SQLException 轉換為業務異常/運行時異常,保留堆棧信息
  7. 結果集處理:優先按列名獲取,大數據量考慮流式處理/分頁

JDBC 雖然是底層 API,但其設計哲學 (接口隔離、驅動解耦、資源管理) 貫穿整個 Java 數據訪問生態。深入理解這四大接口的協作機制,不僅能寫出正確的數據庫代碼,更能看透 MyBatis、JPA、Spring JDBC 等上層框架的底層原理。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved