使用JDBC一次插入多條記錄(以MySQL為例)
閱讀本文需要的先修知識:
- 最基本的SQL語句
- 最基本的JDBC操作(如插入單條記錄)
如急需使用請直接看最後一段程式碼。
在JDBC中,插入記錄最簡單的方法是使用executeUpdate()
方法,但該方法中的引數只能是單條SQL語句,其實對於需要INSERT或者UPDATE多條記錄的情況,JDBC也提供了批量更新的機制。
1.事務
批量更新基於事務處理,JDBC提供了兩個方法void commit()
和void rollback()
,這兩個函式的用法正如大部分SQL資料庫中提供的事務處理語句一樣,commit()
方法用來提交多條語句,rollback()
請看如下程式碼:
public static void main(String[] args) { Connection conn; Statement stmt; try { Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection(DB_URL, USER, PASS); //DB_URL,USER,PASS均為事先定義好的字串,分別代表資料庫地址,登入使用者名稱,密碼 stmt = conn.createStatement(); conn.setAutoCommit(false); stmt.executeUpdate("INSERT INTO test(id, name, tel) VALUES(1, 'Chandler', '1111111')"); stmt.executeUpdate("INSERT INTO test(id, name, tel) VALUES(2, 'Joey', '2222222')"); stmt.executeUpdate("INSERT INTO test(id, name, tel) VALUES(3, 'Rachel', '3333333')"); conn.commit(); stmt.executeUpdate("INSERT INTO test(id, name, tel) VALUES(4, 'Monica', '4444444')"); stmt.executeUpdate("INSERT INTO test(id, name, tel) VALUES(5, 'Ross', '5555555')"); stmt.executeUpdate("INSERT INTO test(id, name, tel) VALUES(6, 'Phoebe', '666666')"); stmt.close(); conn.close(); } catch (SQLException se) { se.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } }
在執行命令之前,我們首先呼叫了一個引數為false
的setAutoCommit()
方法,這個方法的作用是使得在其之後執行的命令不會立即被提交給資料庫,而是等到下一次呼叫commit()
方法時,才一次性全部提交。
在上面的程式碼中,我們首先執行了3條插入語句,然後進行了一次提交,之後又執行了3條插入語句,不同的是這3條插入語句執行後沒有進行提交。執行這個程式之後,結果是這樣的。
mysql> select * from test; +----+----------+---------+ | id | name | tel | +----+----------+---------+ | 1 | Chandler | 1111111 | | 2 | Joey | 2222222 | | 3 | Rachel | 3333333 | +----+----------+---------+ 3 rows in set (0.00 sec)
可見後面3條插入命令因為還沒有commit
,所以是沒有生效的。就這個結果來看,利用這樣的方法,我們就可以先執行多條插入語句,再進行一次commit,達到一次插入多條記錄的效果。但事實上,這樣和不使用事務沒有太大的區別,效能也沒有什麼提高,真正要實現批量插入,我們還需要藉助JDBC的批量更新機制。
2.批量更新
在這裡我們主要需要使用兩個方法,分別是void addBatch(String command)
和int[] executeBatch()
。我們通過對上面的程式碼做一些改動來探究這兩個方法的用法。
public static void main(String[] args) {
Connection conn;
Statement stmt;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(DB_URL, USER, PASS);
//DB_URL,USER,PASS均為事先定義好的字串,分別代表資料庫地址,登入使用者名稱,密碼
stmt = conn.createStatement();
conn.setAutoCommit(false);
stmt.addBatch("INSERT INTO test(id, name, tel) VALUES(1, 'Chandler', '1111111')");
stmt.addBatch("INSERT INTO test(id, name, tel) VALUES(2, 'Joey', '2222222')");
stmt.addBatch("INSERT INTO test(id, name, tel) VALUES(3, 'Rachel', '3333333')");
stmt.addBatch("INSERT INTO test(id, name, tel) VALUES(4, 'Monica', '4444444')");
stmt.addBatch("INSERT INTO test(id, name, tel) VALUES(5, 'Ross', '5555555')");
stmt.addBatch("INSERT INTO test(id, name, tel) VALUES(6, 'Phoebe', '666666')");
int[] counts = stmt.executeBatch(); //執行Batch中的全部語句
conn.commit(); //提交到資料庫
for (int i : counts) {
if (i == 0) {
conn.rollback();
}
}
conn.setAutoCommit(true); //在完成批量操作後恢復預設的自動提交方式,提高程式的可擴充套件性
stmt.close();
conn.close();
} catch (SQLException se) {
se.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
addBatch()
方法每呼叫一次,就相當於往一個假想的“批處理”中添加了一條語句,這些語句在下一次呼叫 executeBatch()
方法時一次性全部執行,在此之後,我們再次呼叫一個commit()
將所作的更改提交到資料庫。
executeBatch()
方法的返回值是一個int陣列,裡面儲存了本次執行的每條語句的返回值,即受到影響的記錄的行數,在本例中,陣列中的所有值均應為1,如果為0則說明插入失敗,我們可以選擇進行回滾或者報錯。
3.預備語句
每次呼叫addBatch()
方法時都需要輸入一長串SQL語句顯得十分繁瑣,在操作列數比較多的表時就更是如此,為了避免這樣的情況,我們可以使用預備語句。
預備語句的用法有點類似於printf()
的用法,當我們使用printf進行輸出時,往往會在字串中插入幾個像%d
、%c
這樣的佔位符,至於這些位置具體的值,我們則在字串後面再專門指定。
預備語句的佔位符沒有按型別進行區分,只有一種——?
,請看如下程式碼:
PreparedStatement pstm = conn.prepareStatement("INSERT INTO test(id, name, tel) VALUES(?, ?, ?")
pstm.setInt(1, 1);
pstm.setString(2, 'Chandler');
pstm.setString(3, '1111111');
pstm.executeUpdate();
首先我們使用帶佔位符?
的SQL語句初始化一個PreparedStatement
物件,然後分別使用setInt()
方法和setString()
方法給對應的位置填值,除了這兩種方法還有很多其他型別的賦值方法,具體可以查閱官方文件或者利用IDE的自動補全功能進行檢視,這一類方法的引數都是類似的,第一個引數指明要給第幾個?
進行賦值,第二個引數要賦的;在給所有的位置賦值之後,我們呼叫executeUpdate()
方法執行這條語句。
上面程式碼的功能和下面的等同:
Statement stmt = conn.createStatement();
stmt.executeUpdate("INSERT INTO test(id, name, tel) VALUES(1, 'Chandler', '1111111')");
表面看來下面使用普通語句的方法更簡潔,但當我們要操作的記錄數變多,乃至成千上萬條時,預備語句的優勢就會體現出來。最後,我們將一開始的程式使用預備語句+批量更改+事務重寫一遍:
public static void main(String[] args) {
Connection conn;
PreparedStatement pstm;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(DB_URL, USER, PASS);
//DB_URL,USER,PASS均為事先定義好的字串,分別代表資料庫地址,登入使用者名稱,密碼
conn.setAutoCommit(false);
pstm = conn.prepareStatement("INSERT INTO test(id, name, tel) VALUES(?, ?, ?");
pstm.setInt(1, 1);
pstm.setString(2, "Chandler");
pstm.setString(3, "1111111");
pstm.addBatch();
pstm.setInt(1, 2);
pstm.setString(2, "Joey");
pstm.setString(3, "2222222");
pstm.addBatch();
pstm.setInt(1, 3);
pstm.setString(2, "Rachel");
pstm.setString(3, "3333333");
pstm.addBatch();
pstm.setInt(1, 4);
pstm.setString(2, "Monica");
pstm.setString(3, "4444444");
pstm.addBatch();
pstm.setInt(1, 5);
pstm.setString(2, "Ross");
pstm.setString(3, "5555555");
pstm.addBatch();
pstm.setInt(1, 6);
pstm.setString(2, "Phoebe");
pstm.setString(3, "666666");
pstm.addBatch();
int[] counts = pstm.executeBatch(); //執行Batch中的全部語句
conn.commit(); //提交到資料庫
for (int i : counts) {
if (i == 0) {
conn.rollback();
}
}
conn.setAutoCommit(true); //在完成批量操作後恢復預設的自動提交方式,提高程式的可擴充套件性
pstm.close();
conn.close();
} catch (SQLException se) {
se.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
參考文獻
- Java核心技術·卷2:高階特性(原書第9版)(截止我寫這篇文章時,已經出到第10版)
- MySQL必知必會
實驗所用環境
- Windows 10(1809)
- jdk 1.8.0_101
- MySQL Sserver 8.0.13 for Win64 on x86_64
原創文章,轉載請註明出處