本篇將會詳細地介紹Apache公司的JDBC幫助工具類DbUtils以及如何使用。在上一篇中我們已經通過將以前對dao層使用JDBC操作數據庫的冗余代碼進行了簡易封裝形成自己的簡單工具類JdbcUtils,而在這過程中很多都是借鑒和參考了DbUtils的代碼,因此通過上一篇的學習,會讓我們在對DbUtils進行更快速簡單的認識。
俗話說學習一個開源的工具最好的方法就是看其官方文檔,是的,在Apache官網中對DbUtils進行了詳細的介紹:http://commons.apache.org/proper/commons-dbutils/ 。(或者下載了DbUtils的jar中也有相應文檔)
DbUtils是一些類組成的用於簡化JDBC開發的庫,解決我們以前根據“模板”代碼寫出的大部分的冗余部分,將我們的代碼進行最大程度的簡化,使我們只關心於查詢和更新(包括添加、修改和刪除)數據。就好像上一篇博客中我們重點寫的query和update方法。
DbUtils能做些什么?雖然JDBC代碼不難,但是根據我們以前所寫的“模板”代碼費時又費力,非常容易丟失鏈接並且難以追尋,而使用DbUtils基本不會發生資源泄露(比如鏈接的丟失)。當然和上一篇博文相同,DbUtils也提供了一個對結果集處理的接口ResultSetHandler,我們可以實現這個接口來對結果集進行處理,也可以使用DbUtils提供的實現類,例如BeanHandler或者BeanListHandler等等,有很多實用的結果集處理器類。如果有一些公司不使用hibernate的話一般會選用DbUtils。
DbUtils最主要的就兩個,QueryRunner類和ResultSetHandler接口。
QueryRunner類這個類主要用於提供各種重載形式的batch方法、query方法和update 方法,其中update 方法包含添加、刪除和修改。
QueryRunner並不算一個靜態工具類,因此我們要使用必須先創建一個QueryRunner類的對象,而這就有兩種構造器,分別是無參的和有參的,其中有參的構造器接收一個連接池對象DataSource。


可以從手冊中看到,如果在創建QueryRunner對象時給定了一個連接池對象,那么在一些不需要Connection對象為參數的方法被調用時會從連接池中自動獲取連接,調用完后會將連接重新返回到連接池中;而對於無參的構造器來說,在調用方法時必須指定Connection對象,同時調用完方法后Connection對象的處理要自己負責。
下面就看看對於無參和有參的QueryRunner對象的各種重載形式的query方法和update方法:


可以看到這些方法中主要就分為兩種,一種是有Connection對象參數的,一種是無Connection對象參數的。上面已經說過了,無Connection對象參數的方法是從有參QueryRunner的連接池中獲取鏈接,同時會自動將連接返還給連接池。而有Connection參數的方法由調用者負責關閉連接,這種方法看似麻煩,但是由多條SQL組成的事務進行整體執行卻是必須的,因為我們不需要只執行一條SQL就還給連接池。
除了query方法和update方法,QueryRunner類還有一個batch方法,用於執行SQL批處理:

這個方法用於對某一條SQL執行多次,而我們提供給這個方法的參數中是一個二維數組,那么怎么用呢?
比如“insert into user(id,name,age) values(?,?,?)”這樣的SQL語句,batch方法中的二維數組參數就可以是 [ [1,”Ding”,25],[2,”LRR”,24] ] 這樣的形式,也就是批處理執行兩次,而每次的參數都不同。
上面的介紹基本就是QueryRunner的全部,當然QueryRunner只是DbUtils的一部分,而另一部分在於QueryRunner中query方法參數中的ResultSetHandler接口,用於處理結果集,這一點上一篇博客已經講述的很清楚了。而在DbUtils中為我們已經寫好了更多的這個接口的實現類:

除了我們上一篇博客自己動手實現的BeanHandler和BeanListHandler之外,在DbUtils中可以使用便捷的結果集處理器還有
ArrayHandler:把結果集中的第一行數據封裝進對象數組中。
ArrayListHandler:把結果集中的每一行數據封裝進數組,再將這些數組存進List集合中。
ColumnListHandler:將結果集中的某一列所有的數據存進List集合中。
KeyedHandler:將結果集中的每一行數據封裝進一個Map中,再把這些Map存到一個Map里,其Key為指定的Key。
MapHandler:將結果集中的第一行數據封裝進Map里,Key為列名,value就是對應的值。
MapListHandler:將結果集中的每一行數據封裝到Map中,再將這些Map存放進List中。
記得上一篇博客中我們在JDBC工具類JdbcUtils中重點創建的自定義的update方法和query方法嗎(上一篇博客的第四點和第五點),那只是為了我們將冗余代碼進行封裝,而在本篇中我們可以在JdbcUtils中刪了這里兩個方法,而直接在dao層中使用DbUtils進行低層的增刪改查。
例1:
那么在下面的代碼中就對上一篇博客最后的UserDao中的增刪改查進行重構:
1 public class UserDao { 2 // 添加用戶
3 public void add(User user) throws SQLException { 4 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 5 String sql = "insert into user(id,name,age) values(?,?,?)"; 6 Object[] params = { user.getId(), user.getName(), user.getAge() }; 7 qr.update(sql, params); 8 } 9
10 // 刪除用戶
11 public void delete(int id) throws SQLException { 12 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 13 String sql = "delete from user where id=?"; 14 Object params = id; 15 qr.update(sql, params); 16 } 17
18 // 修改用戶
19 public void update(User user) throws SQLException { 20 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 21 String sql = "update user set name=?,age=? where id=?"; 22 Object[] params = { user.getName(), user.getAge(), user.getId() }; 23 qr.update(sql, params); 24 } 25
26 // 查找某個用戶
27 public User find(int id) throws SQLException { 28 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 29 String sql = "select * from user where id=?"; 30 Object params = id; 31 User user = qr.query(sql, new BeanHandler<>(User.class), params); 32 return user; 33 } 34
35 // 列出所有用戶
36 public List<User> getAllUser() throws SQLException { 37 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 38 String sql = "select * from user"; 39 List<User> list = qr.query(sql, new BeanListHandler<>(User.class)); 40 return list; 41 } 42 }
可以看到使用QueryRunner就將我們的增刪改查語句變得非常簡單,同時我們在工具類JdbcUtils中也不用另外再寫update方法和query方法,在JdbcUtils中主要是創建連接池。由於我們在創建QueryRunner對象時使用了DataSource作為參數,這樣我們在使用無Connection對象的update方法和query方法都可以不用指定連接,同時這些方法會將我們的連接自動放回連接池,因此我們在JdbcUtils甚至都可以不用寫釋放資源的代碼(當然具體情況具體分析)。
例2:
再補充一個使用DbUtils的批處理batch方法的demo:
public void batchInsertTest() throws SQLException { QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); String sql = "insert into user(id,name,age) values(?,?,?)"; Object[][] params = new Object[5][3]; for(int i=0;i<params.length;i++) { params[i] = new Object[]{i+1,"aa"+i,25+i}; } qr.batch(sql, params); }
結果如下:

關於DbUtils中的QueryRunner類就差不多介紹完了,主要就是batch、query和update 方法。
而對於結果集處理器類,DbUtils給我們提供了很多針對ResultSetHandler接口的實現類,如果這些實現類都不能滿足我們的需要,我們也可以實現ResultSetHandler接口來制作我們所需要功能的實現類。
例3:
下面使用DbUtils中的結果集處理器類之一的ArrayHandler類來簡單演示下demo(以上面批處理batch方法調用后的數據庫中數據為例):
1 public void arrayHandlerTest() throws SQLException { 2 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 3 String sql = "select * from user"; 4 Object[] firstUser = qr.query(sql, new ArrayHandler()); 5 for(Object o:firstUser) { 6 System.out.println(o); 7 } 8 }
ArrayHandler類只將結果集的第一行數據封裝進一個對象數組中,在控制台觀察:

例4:
有時候我們需要計算一張表中的總記錄數,例如在使用數據庫進行分頁的時候就需要知道有多少數據,那么將查詢結果以結果集返回也可以使用ArrayHandler簡單處理,因為數組的第一個元素就是查詢結果,但下面的代碼看似沒有問題,卻在運行時會拋出異常:
1 public void countTest() throws SQLException { 2 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 3 String sql = "select count(*) from user"; 4 Object[] result = qr.query(sql, new ArrayHandler()); 5 int count = (int) result[0]; 6 System.out.println(count); 7 }
異常原因:

也就是說在MySQL中以count方法在JDBC中是以Long類型返回查詢的數值,因此我們不能只用Integer類型。這點要注意,但是總記錄數不大時可以將Long類型強制轉換為Integer類型:
1 public void countTest() throws SQLException { 2 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 3 String sql = "select count(*) from user"; 4 Object[] result = qr.query(sql, new ArrayHandler()); 5 int count = ((Long)result[0]).intValue(); 6 System.out.println(count); 7 }
當然還有其他的很實用的結果集處理器類,這里就不一一介紹了。本篇對DbUtils的講解就結束了,對於DbUtils的使用是很簡單的,只要掌握了QueryRunner類和ResultSetHandler接口及其實現類,就能很好的掌握DbUtils。

