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

JDBC 核心編程指南:驅動加載、連接池、事務控制、批處理與 DbUtils 封裝

     編輯:MySQL 綜合教學

JDBC 核心編程指南:從基礎 API 到生產級封裝

系統梳理 JDBC 標準流程、驅動註冊機制、Connection 管理、Statement 三大類型、ResultSet/RowSet 操作、事務隔離級別、批處理優化及 Apache DbUtils 簡化封裝,附完整可運行代碼片段。

一、JDBC 標準流程與驅動機制

1.1 六步標準流程

// 1. 加載驅動
Class.forName("com.mysql.jdbc.Driver");

// 2. 獲取連接
Connection conn = DriverManager.getConnection(
    "jdbc:mysql://host:port/db?useUnicode=true&characterEncoding=UTF8",
    "user", "password");

// 3. 創建 Statement
Statement stmt = conn.createStatement();

// 4. 執行 SQL
ResultSet rs = stmt.executeQuery("SELECT * FROM user");

// 5. 處理結果集
while (rs.next()) {
    System.out.println(rs.getString("name"));
}

// 6. 釋放資源(Java 7+ try-with-resources 自動關閉)
rs.close(); stmt.close(); conn.close();

1.2 驅動自註冊原理

  • Class.forName() 觸發類加載 → 執行靜態塊 → 向 DriverManager 註冊
  • JDBC 4.0+ (Java 6+) 支援 SPI 自動發現,可省略 Class.forName()

二、連接管理:從 DriverManager 到連接池

2.1 DriverManager 獲取連接

// 兩種重載
getConnection(url, user, password);
getConnection(url, Properties info);

URL 參數示例:jdbc:mysql://host:3306/db?useUnicode=true&characterEncoding=UTF8&useServerPrepStmts=true&cachePrepStmts=true

2.2 Connection 核心能力

方法用途
createStatement()普通 Statement
prepareStatement(sql)預編譯 PreparedStatement
prepareCall(sql)存儲過程 CallableStatement
setAutoCommit(false)開啟事務
commit()/rollback()提交/回滾
setTransactionIsolation(level)設置隔離級別
setSavepoint(name)保存點

2.3 連接池必要性

  • 物理連接創建/銷毀開銷大
  • 池化:啟動預創建、借用歸還、空閒檢測、動態擴縮容

2.4 C3P0 配置與代碼封裝


  com.mchange
  c3p0
  0.9.5.2


  com.mchange
  mchange-commons-java
  0.2.11
// 雙重檢測鎖單例 DataSource
private static DataSource dataSource;
public static DataSource getDataSourceC3P0(String configFile) {
    if (dataSource == null) {
        synchronized (ConnectionManager.class) {
            if (dataSource == null) {
                Properties cfg = SQLUtil.loadConfig(configFile);
                ComboPooledDataSource ds = new ComboPooledDataSource();
                ds.setDriverClass(cfg.getProperty("mysql.driver.class"));
                ds.setJdbcUrl(cfg.getProperty("mysql.url"));
                ds.setUser(cfg.getProperty("mysql.user"));
                ds.setPassword(cfg.getProperty("mysql.password"));
                ds.setMaxPoolSize(Integer.parseInt(cfg.getProperty("pool.max.size")));
                ds.setMinPoolSize(Integer.parseInt(cfg.getProperty("pool.min.size")));
                ds.setInitialPoolSize(Integer.parseInt(cfg.getProperty("pool.init.size")));
                ds.setAcquireIncrement(Integer.parseInt(cfg.getProperty("pool.acquire.increment")));
                ds.setMaxStatements(Integer.parseInt(cfg.getProperty("pool.max.statements")));
                ds.setMaxIdleTime(Integer.parseInt(cfg.getProperty("pool.max.idle_time")));
                dataSource = ds;
            }
        }
    }
    return dataSource;
}

支援 c3p0-config.xml 多環境切換:


  ...
  ...

// 代碼中:new ComboPooledDataSource("mysql-config")

2.5 HikariCP:高性能「零開銷」連接池


  com.zaxxer
  HikariCP
  2.4.0
HikariConfig cfg = new HikariConfig();
cfg.setDriverClassName(props.getProperty("mysql.driver.class"));
cfg.setJdbcUrl(props.getProperty("mysql.url"));
cfg.setUsername(props.getProperty("mysql.user"));
cfg.setPassword(props.getProperty("mysql.password"));
cfg.setMaximumPoolSize(20);
cfg.setMinimumIdle(3);
cfg.setIdleTimeout(60000);
cfg.setMaxLifetime(1800000);
DataSource ds = new HikariDataSource(cfg);
  • 基準測試性能優於 C3P0/DBCP
  • 項目主頁:HikariCP

三、SQL 執行:Statement vs PreparedStatement vs CallableStatement

3.1 Statement:靜態 SQL

// DDL 返回 0,DML 返回影響行數
stmt.executeUpdate("CREATE TABLE t (id INT PRIMARY KEY)");
stmt.executeUpdate("INSERT INTO t VALUES (1)");

// execute() 可執行任意 SQL,需手動獲取結果
boolean hasRs = stmt.execute(sql);
if (hasRs) {
    ResultSet rs = stmt.getResultSet();
} else {
    int count = stmt.getUpdateCount();
}

3.2 PreparedStatement:預編譯 + 參數化(防注入、性能優)

PreparedStatement ps = conn.prepareStatement(
    "INSERT INTO t(name, pwd) VALUES (?, ?)");
ps.setString(1, "user1");
ps.setObject(2, "pwd1");  // 通用 setObject 自動類型轉換
ps.executeUpdate();

性能對比測試:1000 次插入,PreparedStatement 比 Statement 快 30%+(預編譯複用)。

3.3 開啟服務端預編譯緩存(關鍵 URL 參數)

jdbc:mysql://host:3306/db?
  useServerPrepStmts=true&          -- 開啟服務端預編譯
  cachePrepStmts=true&              -- 客戶端緩存 PreparedStatement
  prepStmtCacheSize=50&             -- 緩存數量
  prepStmtCacheSqlLimit=300         -- 單條 SQL 最大長度

3.4 CallableStatement:調用存儲過程

-- MySQL 創建存儲過程
DELIMITER //
CREATE PROCEDURE add_pro(IN a INT, IN b INT, OUT sum INT)
BEGIN SET sum = a + b; END //
DELIMITER ;
CallableStatement cs = conn.prepareCall("{CALL add_pro(?, ?, ?)}");
cs.setInt(1, 11);
cs.setInt(2, 22);
cs.registerOutParameter(3, Types.INTEGER);
cs.execute();
System.out.println("Sum = " + cs.getInt(3));

四、結果集操作:ResultSet、可滾動/可更新、RowSet

4.1 ResultSet 游標導航

rs.next(); rs.previous(); rs.first(); rs.last();
rs.beforeFirst(); rs.afterLast();
rs.absolute(10); rs.relative(-2);

列索引從 1 開始,`getObject(i)` 通用取值。

4.2 可滾動/可更新 ResultSet

前提:單表查詢、包含主鍵列。

4.3 ResultSetMetaData:元數據分析

ResultSetMetaData meta = rs.getMetaData();
int cols = meta.getColumnCount();
for (int i=1; i<=cols; i++) {
    System.out.println(meta.getColumnLabel(i) + " " + meta.getColumnTypeName(i));
}

4.4 RowSet:離線結果集(可序列化、跨網絡傳輸)

  • JdbcRowSet:需保持連接,可滾動更新
  • CachedRowSet:離線,數據在內存,populate(rs) 填充,acceptChanges(conn) 同步回庫
  • WebRowSet:支援 XML 序列化
// Java 7 RowSetFactory
RowSetFactory factory = RowSetProvider.newFactory();
CachedRowSet crs = factory.createCachedRowSet();
crs.setUrl(url); crs.setUsername(u); crs.setPassword(p);
crs.setCommand("SELECT * FROM t");
crs.execute();  // 此時已離線

// 分頁功能
crs.setPageSize(10);
crs.populate(rs, 11);  // 從第 11 行開始
while (crs.nextPage()) { ... }

五、事務控制:ACID 與隔離級別

5.1 ACID 核心特性

  • 原子性:不可分割,全成全敗
  • 一致性:事務前後數據庫一致性狀態
  • 隔離性:並發事務互不干擾
  • 持久性:提交後永久生效

5.2 四大隔離級別(由弱到強)

級別常量解決問題MySQL 默認
READ UNCOMMITTEDTRANSACTION_READ_UNCOMMITTED無
READ COMMITTEDTRANSACTION_READ_COMMITTED髒讀Oracle 默認
REPEATABLE READTRANSACTION_REPEATABLE_READ不可重複讀、幻讀✅ MySQL 默認
SERIALIZABLETRANSACTION_SERIALIZABLE所有並發問題
-- MySQL 設置
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT @@tx_isolation;

-- JDBC 設置
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);

5.3 事務模板代碼

boolean autoCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
try {
    // 業務操作
    ps1.executeUpdate();
    ps2.executeUpdate();
    conn.commit();
} catch (Exception e) {
    conn.rollback();
    throw new RuntimeException(e);
} finally {
    conn.setAutoCommit(autoCommit);
}

注意:同一事務必須共用同一 Connection;捕獲異常後必須顯式 rollback()。

六、批處理:大量數據高效入庫

conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
for (int i=0; i<10000; i++) {
    stmt.addBatch("INSERT INTO t VALUES (" + i + ")");
}
int[] results = stmt.executeBatch();  // 返回每條影響行數
conn.commit();
  • 視為單一事務,失敗整體回滾
  • MySQL 需 URL 參數 rewriteBatchedStatements=true 啟用多值合併優化
  • 大批次建議用 Statement 而非 PreparedStatement(預編譯緩存有限)

七、Apache DbUtils:JDBC 輕量封裝


  commons-dbutils
  commons-dbutils
  1.6

7.1 核心類

  • QueryRunner:執行 update()、query()、batch()
  • ResultSetHandler:結果集轉換策略
  • DbUtils:靜態工具 close() 等

7.2 內建 Handler(免寫映射代碼)

Handler返回類型場景
BeanHandlerT單行 → JavaBean
BeanListHandlerList<T>多行 → List
MapHandlerMap<String,Object>單行 → Map
MapListHandlerList<Map>多行 → List<Map>
ScalarHandlerObject單值(COUNT 等)
ColumnListHandlerList<Object>單列多行

7.3 使用示例

QueryRunner runner = new QueryRunner(dataSource);

// 單行查詢
User user = runner.query("SELECT * FROM user WHERE id=?", new BeanHandler<>(User.class), 1);

// 多行查詢
List list = runner.query("SELECT * FROM user", new BeanListHandler<>(User.class));

// 更新
runner.update("INSERT INTO user(name) VALUES(?)", "new_user");

// 事務支援(共用 Connection)
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false);
runner.update(conn, "INSERT INTO a VALUES(1)");
runner.update(conn, "INSERT INTO b VALUES(2)");
conn.commit();

總結:生產級 JDBC 最佳實踐清單

  1. 必須使用連接池(HikariCP 首選)
  2. 所有參數化查詢用 PreparedStatement,開啟服務端預編譯緩存
  3. 事務邊界清晰,同一 Connection,異常必 rollback
  4. 隔離級別根據業務選擇,MySQL 默認 RR 通常足夠
  5. 大批量寫入用 rewriteBatchedStatements=true + 批處理
  6. 簡單 CRUD 直接用 DbUtils 減少樣板代碼
  7. 資源釋放統一用 try-with-resources (Java 7+)
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved