1. 程式人生 > >輕量級高效能jdbc封裝工具 Apache Commons DbUtils 1.6

輕量級高效能jdbc封裝工具 Apache Commons DbUtils 1.6


因為自己做專案的時候剛好在尋找一款輕量級的資料庫操作框架,不同於hibernate,mybatis這麼大型,希望能使用的簡單一點,所以發現了這款工具,在這裡mark一下,方便自己以後使用時檢視。


前言

關於Apache的DbUtils中介軟體或許瞭解的人並不多,大部分開發人員在生成環境中更多的是依靠Hibernate、Ibatis、SpringJDBC、JPA等大廠提供的持久層技術解決方案,或者是企業內部自己研發的持久層技術。但無論如何,使用這些技術的初衷和本質都是為了能夠減少企業開發成本,提高生產效率,降低耦合。

放眼企業級專案,Hibernate等ORM產品是首選,而網際網路領域,大部分開發人員往往並不會在生產環境中上這些ORM技術,原因很簡單,要的就是效率,其次都不重要。對於剛接觸SQL和JDBC的開發人員,最引以為傲的就是希望能夠在日後編寫複雜的SQL語句,以及會使用諸如Hibernate、Ibatis等第三方持久層技術,並且極力的撇清與傳統JDBC技術的關係,但筆者不得不認為,這是一種普遍業界存在的“病態”!

如果是企業級的專案,尤其是跟金融相關的業務,SQL語句或許會非常複雜,並且關聯著事物。但網際網路專案卻並非如此,在網際網路專案中,看你牛不牛逼併不是取決於你能否寫出一條複雜的SQL語句,而是看你能否將原本一條複雜的SQL語句拆散成單條SQL,一句一句的執行;並且脫離Hibernate等ORM產品後,能否使用傳統的JDBC技術完成一條簡單的CRUD操作,這才是牛逼!是的,你沒有聽錯,網際網路確確實實就是這麼玩,還原最本質的東西,才是追求效能的不二選擇。

筆者本章不會提及垂直分庫、水平分割槽等資料庫概念,以及資料路由中介軟體等技術(請閱讀筆者博文《剖析淘寶TDDL—Matrix層分庫分表實現》),因為這些內容與本章內容無關,但間接來看,筆者之前提及的單條SQL、使用JDBC完成基本的CRUD操作就可以在最大程度上滿足一個網際網路場景的持久層操作。以Hibernate為例,簡單來說需要經歷HQL->SQL->DBMS等編譯過程,中間還冗餘著快取、物件等開銷,希望大家記住,封裝層次越高,效能越低!這個是無可爭議的事實。筆者希望大家接下來,暫時“忘記”掉你所會的持久層技術,耐心的聽筆者為你介紹Apache的DbUtils技術,或許你會有意想不到的收穫。

目錄

一、Apache Commons DbUtils簡介;

二、下載與安裝DbUtils;

三、使用DbUtils完成CRUD操作;

四、C3P0連線池整合DbUtils;

五、常用包、類講解;

六、自動封裝結果集;

七、事物管理;

一、Apache Commons DbUtils簡介;

Apache的DbUtils工具是一個輕量級的持久層解決方案,天生為效能而生,它簡單的對JDBC進行了必要的操作封裝,讓開發人員能夠以一種高階API的方式使用JDBC技術完成原本複雜的CRUD操作。換句話說,DbUtils天生就不是一個複雜的技術,它只是一個簡單的JDBC上層封裝,對開發人員而言,大概只需半小時就能夠完全掌握DbUtils技術的使用,是的,它就是這麼簡單與方便,它是網際網路專案的寵兒,選擇DbUtils技術作為持久層的解決方案,或許能夠讓你從原本複雜的Hibernate操作中解脫出來,或者是你覺得Ibatis不夠好用,DbUtils也是你選擇的理由之一。總之,使用它,你將會感到驚豔,它是如此的簡單和乾淨,如此的純粹和高效!並且DbUtils是採用商業友好的開源協議,大家甚至可以下載它的原始碼,進行二次開發,以此滿足企業自身的需要。

二、下載與安裝DbUtils;

當大家對DbUtils的專案背景有所瞭解後,接下來本節內容筆者將會告訴你它的下載和安裝。大家可以登入http://commons.apache.org/站點下載DbUtils工具的最新版本,筆者使用的版本為1.6.0,在此大家需要注意,為了避免在開發過程中出現異常,建議大家下載、使用與筆者本篇博文一致的版本。

當大家成功下載好DbUtils相關的構件後,我們可以將其新增到專案中的ClassPath目錄下,當然筆者後續小節會提及DbUtils與C3P0連線池的整合,因此,大家最好將C3P0所需的構件以及資料庫驅動(筆者使用Mysql)一起新增到專案中。使用DbUtils時關聯的構件,如下所示:

3、使用DbUtils完成CRUD操作;

廢話不多說,使用DbUtils操作資料庫之前,首先要做的事情就是獲取Connection。那麼為了方便,筆者使用硬編碼的方式將資料來源的配置資訊coding在程式碼中(生產環境中,有可能是配置在專案的配置檔案中、資料庫中、Diamond中等),如下所示:

public class ConnectionManager { 
    publicstatic Connection getConnection() { 
       Connection conn = null; 
       try { 
           Class.forName("com.mysql.jdbc.Driver"); 
           conn = DriverManager.getConnection( 
                   "jdbc:mysql://ip:port/dbName", "userName", 
                   "passWord"); 
       } catch (Exception e) { 
           e.printStackTrace(); 
       } 
       return conn; 
   } 
} 

當編寫好ConnectionManager之後,接下來要做的事情就是獲取Connection,然後就能夠使用DbUtils進行CRUD操作了。或許細心的讀者已經發現,使用DbUtils其實是非常簡單的,需要會的不多,僅僅只需要掌握JDBC操作以及簡單的CRUD操作即可(網際網路場景下同樣也是這麼要求)。

@Test 
public void testInsert() { 
    final StringSQL = "insert into test_1 values(?, ?)"; 
    try{ 
       if (null == conn || conn.isClosed()) 
           conn = ConnectionManager.getConnection2(); 
       int result = new QueryRunner().update(conn, SQL, new Object[]{ 
               "JohnGao1", "123" }); 
       if (0 < result) 
           System.out.println("資料插入成功..."); 
    } catch(Exception e) { 
       e.printStackTrace(); 
    } finally{ 
       close(conn); 
   } 
} 
 
@Test 
public void testUpdate() { 
    final StringSQL = "update test_1 set password= ? where username =?"; 
    try{ 
       if (null == conn || conn.isClosed()) 
           conn = ConnectionManager.getConnection(); 
       int result = new QueryRunner().update(conn, SQL, new Object[]{ 
               "321", "JohnGao1" }); 
       if (0 < result) 
           System.out.println("資料更新成功..."); 
    } catch(Exception e) { 
       e.printStackTrace(); 
    } finally{ 
       close(conn); 
   } 
} 
 
@Test 
public void testDelete() { 
    final StringSQL = "delete from test_1 where username like?"; 
    try{ 
       if (null == conn || conn.isClosed()) 
           conn = ConnectionManager.getConnection(); 
       int result = new QueryRunner().update(conn, SQL,"%JohnGao%"); 
       if (0 < result) 
           System.out.println("資料刪除成功..."); 
    } catch(Exception e) { 
       e.printStackTrace(); 
    } finally{ 
       close(conn); 
   } 
} 
 
@Test 
public void testQuery() { 
    final StringSQL = "select * from test_1"; 
    try{ 
       if (null == conn || conn.isClosed()) 
           conn = ConnectionManager.getConnection(); 
       Test_1Bean test1Bean = new QueryRunner().query(conn,SQL, 
               new BeanHandler(Test_1Bean.class)); 
       if (null != test1Bean) { 
           System.out.println(test1Bean.getUsername()); 
           System.out.println(test1Bean.getPassword()); 
       } 
    } catch(Exception e) { 
       e.printStackTrace(); 
    } finally{ 
       close(conn); 
   } 
} 


四、C3P0連線池整合DbUtils;

在生產環境中,開發人員在對資料庫進行CRUD操作的時候,由於資料庫的連結是有限的,因此不得不使用連線池來實現資源複用,以此降低資料庫的效能瓶頸(儘管這麼說有些不太友好,因為併發環境下,單靠連線池是不能夠解決問題的,而常用的方案更多是諸如Redis之類的記憶體資料庫抗住70%傳統DBMS資料的受訪壓力、資料庫先做垂直分庫,再做水平分割槽,當然Master/Sleave是必不可少的,經過這些步驟之後,才能夠說基本上解決了理論上可能出現的資料庫在高併發環境下的瓶頸)。

廢話不多說,在之前的ConnectionManager中新增進連線池相關的程式碼,當然為了方便,筆者同樣還是使用硬編碼的方式,如下所示:

public static ComboPooledDataSourcedataSource; 
static { 
    try{ 
       dataSource = new ComboPooledDataSource(); 
       dataSource.setUser("userName"); 
       dataSource.setPassword("passWord"); 
       dataSource.setJdbcUrl("jdbc:mysql://ip:port/dbName"); 
       dataSource.setDriverClass("com.mysql.jdbc.Driver"); 
       dataSource.setInitialPoolSize(10); 
       dataSource.setMinPoolSize(5); 
       dataSource.setMaxPoolSize(50); 
       dataSource.setMaxStatements(100); 
       dataSource.setMaxIdleTime(60); 
    } catch(Exception e) { 
       e.printStackTrace(); 
   } 
} 
 
 
public static Connection getConnection2() { 
    Connectionconn = null; 
    if (null !=dataSource) { 
       try { 
           conn = dataSource.getConnection(); 
       } catch (SQLException e) { 
           e.printStackTrace(); 
       } 
   } 
    returnconn; 
} 
 


當成功在ConnectionManager中新增好所需的C3P0連線池配置後,接下來要做的事情就是考慮如何使用C3P0與DbUtils之間的整合。其實最簡單的做法就是直接將之前獲取Connection的getConnection()方法更換為上述程式碼中的getConnection2()即可,同樣可以使用在建立QueryRunner例項時,將資料來源的DataSource傳遞過去,這樣即可避免在執行CRUD操作時,還需要在方法中指明Connection。

當然究竟應該怎麼做,完全取決於你自己,如果希望方便,那麼筆者建議你在建立QueryRunner例項時,直接將C3P0的DataSource傳遞過去,但這樣做的弊端很明顯,如果在特殊的場景下,需要手動控制事物時,那麼這種操作是極其不便的,因為Connection並不可控。那麼為了解決事物控制的問題,當然是Connection可控最好。

五、常用包、類講解;

相信大家已經從上述DbUtils的CRUD示例中發現了QueryRunner的身影,那麼筆者接下來就將會針對DbUtils中諸如QueryRunner等常用型別進行深入講解。

在DbUtils中,最常用的3個包為org.apache.commons.dbutils、org.apache.commons.dbutils.handlers以及org.apache.commons.dbutils.wrappers。

org.apache.commons.dbutils包下的常用類,如下所示:

1、DbUtils : 提供如關閉連線、裝載 JDBC 驅動程式等常規工作的工具類;
2、QueryRunner : 該類簡單化了 SQL 查詢,它常與與 ResultSetHandler 組合在一起使用;

org.apache.commons.dbutils.handlers包下的常用類,如下所示:

1、ArrayHandler :將ResultSet中第一行的資料轉化成物件陣列;
2、ArrayListHandler:將ResultSet中所有的資料轉化成List,List中存放的是Object[];
3、BeanHandler :將ResultSet中第一行的資料轉化成類物件;
4、BeanListHandler :將ResultSet中所有的資料轉化成List,List中存放的是類物件;
5、ColumnListHandler:將ResultSet中某一列的資料存成List,List中存放的是Object物件;
6、KeyedHandler :將ResultSet中存成對映,key為某一列對應為Map。Map中存放的是資料;
7、MapHandler :將ResultSet中第一行的資料存成Map對映;
8、MapListHandler :將ResultSet中所有的資料存成List。List中存放的是Map;
9、ScalarHandler :將ResultSet中一條記錄的其中某一列的資料存成Object;

org.apache.commons.dbutils.wrappers包下的常用類,如下所示:

1、SqlNullCheckedResultSet :該類是用來對sql語句執行完成之後的的數值進行null的替換;
2、StringTrimmedResultSet :去除ResultSet中中欄位的左右空格;

六、自動封裝結果集;

在org.apache.commons.dbutils.handlers包下的型別,大部分都是與查詢結果集相關的。試想一下,利用傳統的JDBC進行查詢時,返回的資料我們需要對ResultSet進行迭代,這是相當麻煩的,且不利於維護,因為我們需要手動編寫與之相關的資料封裝。但是使用DbUtils之後,我們要做的事情僅僅只是告訴DbUtils我們需要什麼樣的資料即可,關於資料封裝這種通用的控制邏輯,則無需開發人員參與,這極大的節省了開發人員的時間,提升了生產效率。

簡單來說,筆者在開發過程中使用最廣泛的就是BeanListHandler以及MapListHandler封裝的結果集。簡單來說,BeanListHandler將會查詢後的資料封裝到一個對應的POJO中(可以看做是一個無狀態的實體Bean),MapListHandler會將查詢後的資料封裝為一個List,List中儲存的就是一個個的Map集合,通過key-value的方式獲取封裝後的資料集。先來看看MapListHandler的使用,如下所示:

@Test 
public void testQuery4() { 
    final StringSQL = "select * from test_1 where username like?"; 
    try{ 
       if (null == conn || conn.isClosed()) 
           conn = ConnectionManager.getConnection2(); 
       List> values = newQueryRunner().query(conn, 
               SQL, new Object[] { "%JohnGao%" }, newMapListHandler()); 
       if (null != values) { 
           for (int i = 0; i < values.size(); i++){ 
               Map map = values.get(i); 
               System.out.println(map.get("username")); 
               System.out.println(map.get("password")); 
           } 
       } 
    } catch(Exception e) { 
       e.printStackTrace(); 
    } finally{ 
       close(conn); 
   } 
} 


如果你喜歡類似於實體Bean的操作方式,那麼BeanListHandler無疑使最好的選擇。一旦我們使用BeanListHandler作為資料返回後的結果集封裝,那麼DbUtils便會將查詢後的結果集一個欄位一個欄位的對映到指定的POJO中,當然前提就是欄位名稱是必須一致的,否則DbUtils將無法完成資料封裝。BeanListHandler的使用示例,如下所示:

@Test 
public void testQuery3() { 
    final StringSQL = "select * from test_1 where username like?"; 
    try{ 
       if (null == conn || conn.isClosed()) 
           conn = ConnectionManager.getConnection(); 
       List test1Beans = new QueryRunner().query(conn,SQL, 
               new Object[] { "%JohnGao%" }, newBeanListHandler( 
                       Test_1Bean.class)); 
       if (null != test1Beans) { 
           for (Test_1Bean test1Bean : test1Beans) { 
               System.out.println(test1Bean.getUsername()); 
               System.out.println(test1Bean.getPassword()); 
           } 
       } 
    } catch(Exception e) { 
       e.printStackTrace(); 
    } finally{ 
       close(conn); 
   } 
} 


在此大家需要注意,為了方便演示,筆者在此並沒有提供對應的POJO。如果有需要,大家可以編寫一個與資料庫表字段相同的POJO來完成查詢結果集的欄位對映封裝操作。

七、事物管理;

說起事物管理,這其實是一個非常複雜與繁瑣,且是最容易出錯的場景,尤其是在手動管理事物操作上。當然本節所提及的事物管理仍然是建立在基於手動管理的事物操作上。對於JDBC操作,如果希望事物不要手動提交,那麼在獲取Connection的時候,一定需要將設定conn.setAutoCommit(false);這樣一來事物就不會自動進行提交,當我們手動執行conn.commit()方法的時候,事物才會進行提交。這種方式對於DbUtils其實是一樣的,之前也說過,DbUtils僅僅只是對JDBC做了一個輕量級的上層封裝,那麼必然可以和JDBC進行混用,一旦我們在程式中設定了事物後,接下來的事物管理操作就依賴與開發人員自身了,DbUtils將不會再參與事物的管理。

對於大多數開發人員而言,事物控制的不好,將會導致業務出現問題,髒資料等情況是非常常見的,但從另一個層面來說,手動的事物管理其實是最靈活和方便的。在此需要提醒大家,如果是使用Mysql資料庫,只有將資料庫引擎設定為InnoDB後,才會支援事物!

最後筆者在囉嗦一下,使用完資源後,我們一定要記得及時釋放掉資源,以此避免無用資源長時間掛起。那麼在DbUtils中,你將有2種方式結束掉Connection,第一個是使用DbUtils.close()方法。其次,你將可以直接使用close()方法關閉Connection的連結。