雖然現代開發多使用 ORM 框架,但 JDBC 作為 Java 資料訪問的基石,掌握其核心機制對理解上層框架原理、排查性能問題至關重要。本文系統梳理 JDBC 完整操作流程,涵蓋連接管理、CRUD、存儲過程、元數據、ResultSet 遊標操作、預處理/批處理優化、事務與 Savepoint 機制。
核心接口:DriverManager、Connection、Statement、PreparedStatement、CallableStatement、ResultSet、Savepoint。
String driver = "org.apache.derby.jdbc.EmbeddedDriver";
String url = "jdbc:derby:EmbeddedDB;create=true";
Class.forName(driver);
Connection con = DriverManager.getConnection(url);
Statement st = con.createStatement();
st.execute("create table foo(ID INT, NAME VARCHAR(30))");
st.executeUpdate("insert into foo(ID,NAME) values(1,'Zhang San')");
ResultSet rs = st.executeQuery("select ID,NAME from foo");
while(rs.next()) System.out.println("ID=" + rs.getInt("ID") + "; NAME=" + rs.getString("NAME"));
在 META-INF/services/java.sql.Driver 文件中寫入驅動全限定名(如 com.mysql.jdbc.Driver),JDK 6+ 會自動發現,無需 Class.forName()。注意仍需將驅動 JAR 放入 classpath。
String url = "jdbc:mysql://localhost/test";
Connection con = DriverManager.getConnection(url, "root", "123");
Statement st = con.createStatement();
// Insert
st.execute("insert into user(ID,NAME) values(1,'Zhang San')");
// Query
ResultSet rs = st.executeQuery("select ID,NAME from user");
while(rs.next()) System.out.println("ID:" + rs.getInt("ID") + "; NAME=" + rs.getString("NAME"));
// Update
st.executeUpdate("update user set NAME='TEST' where ID=2");
// Delete
st.execute("delete from user where ID=3");
CallableStatement cst = con.prepareCall("call GetUser()");
ResultSet rs = cst.executeQuery();
// MySQL 過程:CREATE PROCEDURE GetUserByID(in id int) ...
CallableStatement cst = con.prepareCall("call GetUserByID(?)");
cst.setInt(1, id);
ResultSet rs = cst.executeQuery();
使用 cst.registerOutParameter(index, Types.VARCHAR) 註冊,執行後 cst.getString(index) 獲取。
DatabaseMetaData dbmd = con.getMetaData();
System.out.println(dbmd.getDatabaseProductName() + " " + dbmd.getDatabaseProductVersion());
ResultSet rs = dbmd.getTables(null, null, null, null);
while(rs.next()) System.out.println(rs.getString("TABLE_NAME") + " " + rs.getString("TABLE_TYPE"));
ResultSetMetaData rsmd = rs.getMetaData();
for(int i=1; i<=rsmd.getColumnCount(); i++)
System.out.println(rsmd.getColumnName(i) + " " + rsmd.getColumnTypeName(i));
創建 Statement 時指定類型與併發性:
Statement st = con.createStatement(
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE,
ResultSet.CLOSE_CURSORS_AT_COMMIT);
關鍵方法:
rs.last() / rs.first() / rs.absolute(n):定位rs.moveToInsertRow() / rs.updateXxx() / rs.insertRow():插入rs.absolute(n) / rs.updateXxx() / rs.updateRow():更新rs.absolute(n) / rs.deleteRow():刪除示例流程:統計行數 → 插入 ID=4 → 更新最後一行 → 刪除最後一行,輸出驗證每步結果。
PreparedStatement pst = con.prepareStatement("insert into user(id,name) values(?,?)");
pst.setInt(1, 5); pst.setString(2, "Lei Feng"); pst.executeUpdate();
Statement st = con.createStatement();
st.addBatch("insert into user(id,name) values(6,'Xiao Zhang')");
st.addBatch("insert into user(id,name) values(7,'Xiao Liu')");
st.executeBatch();
PreparedStatement pst = con.prepareStatement("insert into user(id,name) values(?,?)");
pst.setInt(1, 9); pst.setString(2, "Xiao Zhang"); pst.addBatch();
pst.setInt(1, 10); pst.setString(2, "Xiao Liu"); pst.addBatch();
pst.executeBatch();
con.setAutoCommit(false);
try {
st.executeUpdate("insert into user(id,name) values(12,'Xiao Li')");
con.commit();
} catch(Exception e) { con.rollback(); }
finally { con.setAutoCommit(true); }
連續執行兩次:首次成功,二次主鍵衝突拋異常並回滾。
Savepoint svpt = null;
con.setAutoCommit(false);
try {
st.executeUpdate("insert into user(id,name) values(13,'Xiao Li')");
st.executeUpdate("insert into user(id,name) values(14,'Xiao Wang')");
svpt = con.setSavepoint("roll back to here");
st.executeUpdate("insert into user(id,name) values(15,'Xiao Zhao')");
st.executeUpdate("insert into user(id,name) values(13,'Xiao Li')"); // 主鍵衝突
con.commit();
} catch(Exception e) { con.rollback(svpt); }
finally { con.setAutoCommit(true); }
結果:ID 13、14 保留;ID 15 回滾;異常拋出後事務部分提交。
PreparedStatement.addBatch(),適當調大 rewriteBatchedStatements=true(MySQL)。