系統梳理 JDBC 標準流程、驅動註冊機制、Connection 管理、Statement 三大類型、ResultSet/RowSet 操作、事務隔離級別、批處理優化及 Apache DbUtils 簡化封裝,附完整可運行代碼片段。
// 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();
Class.forName() 觸發類加載 → 執行靜態塊 → 向 DriverManager 註冊// 兩種重載 getConnection(url, user, password); getConnection(url, Properties info);
URL 參數示例:jdbc:mysql://host:3306/db?useUnicode=true&characterEncoding=UTF8&useServerPrepStmts=true&cachePrepStmts=true
| 方法 | 用途 |
|---|---|
| createStatement() | 普通 Statement |
| prepareStatement(sql) | 預編譯 PreparedStatement |
| prepareCall(sql) | 存儲過程 CallableStatement |
| setAutoCommit(false) | 開啟事務 |
| commit()/rollback() | 提交/回滾 |
| setTransactionIsolation(level) | 設置隔離級別 |
| setSavepoint(name) | 保存點 |
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") ... ...
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);
// 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();
}
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%+(預編譯複用)。
jdbc:mysql://host:3306/db? useServerPrepStmts=true& -- 開啟服務端預編譯 cachePrepStmts=true& -- 客戶端緩存 PreparedStatement prepStmtCacheSize=50& -- 緩存數量 prepStmtCacheSqlLimit=300 -- 單條 SQL 最大長度
-- 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));
rs.next(); rs.previous(); rs.first(); rs.last(); rs.beforeFirst(); rs.afterLast(); rs.absolute(10); rs.relative(-2);
列索引從 1 開始,`getObject(i)` 通用取值。
前提:單表查詢、包含主鍵列。
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()) { ... }
| 級別 | 常量 | 解決問題 | MySQL 默認 |
|---|---|---|---|
| READ UNCOMMITTED | TRANSACTION_READ_UNCOMMITTED | 無 | |
| READ COMMITTED | TRANSACTION_READ_COMMITTED | 髒讀 | Oracle 默認 |
| REPEATABLE READ | TRANSACTION_REPEATABLE_READ | 不可重複讀、幻讀 | ✅ MySQL 默認 |
| SERIALIZABLE | TRANSACTION_SERIALIZABLE | 所有並發問題 |
-- MySQL 設置 SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; SELECT @@tx_isolation; -- JDBC 設置 conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
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();
rewriteBatchedStatements=true 啟用多值合併優化Statement 而非 PreparedStatement(預編譯緩存有限)commons-dbutils commons-dbutils 1.6
QueryRunner:執行 update()、query()、batch()ResultSetHandler:結果集轉換策略DbUtils:靜態工具 close() 等| Handler | 返回類型 | 場景 |
|---|---|---|
| BeanHandler | T | 單行 → JavaBean |
| BeanListHandler | List<T> | 多行 → List |
| MapHandler | Map<String,Object> | 單行 → Map |
| MapListHandler | List<Map> | 多行 → List<Map> |
| ScalarHandler | Object | 單值(COUNT 等) |
| ColumnListHandler | List<Object> | 單列多行 |
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();
rollbackrewriteBatchedStatements=true + 批處理try-with-resources (Java 7+)