《筆者帶你剖析Apache Commons DbUtils 1.6》
?
前言
關于Apache的DbUtils中間件或許了解的人并不多,大部分開發人員在生成環境中更多的是依靠Hibernate、Ibatis、Spring JDBC、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中等),如下所示:
/**
* 數據源信息
*
* @author gaoxianglong
*/
public class ConnectionManager {
public static 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 String SQL = "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 String SQL = "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 String SQL = "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 String SQL = "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 ComboPooledDataSource dataSource;
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();
}
}
/**
* 從連接池中獲取數據源鏈接
*
* @author gaoxianglong
*
* @return Connection 數據源鏈接
*/
public static Connection getConnection2() {
Connection conn = null;
if (null != dataSource) {
try {
conn = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
return conn;
}
?
當成功在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 String SQL = "select * from test_1 where username like ?";
try {
if (null == conn || conn.isClosed())
conn = ConnectionManager.getConnection2();
List<Map<String, Object>> values = new QueryRunner().query(conn,
SQL, new Object[] { "%JohnGao%" }, new MapListHandler());
if (null != values) {
for (int i = 0; i < values.size(); i++) {
Map<String, Object> 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 String SQL = "select * from test_1 where username like ?";
try {
if (null == conn || conn.isClosed())
conn = ConnectionManager.getConnection();
List<Test_1Bean> test1Beans = new QueryRunner().query(conn, SQL,
new Object[] { "%JohnGao%" }, new BeanListHandler(
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的鏈接。
?
小結
本章內容到此結束,由于時間倉庫,本文或許有很多不盡人意的地方,希望各位能夠理解和體諒。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

