![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuYjYjhTNzQ2MmZGOy0SYlRWYtMDZmNTLmVzYy0iMykDZygTM58CXxITMyMTMvw1YpB3LcVmc1R3YpB3LcRWYvxGc19CXt92YuUWelRXauwGZvw1LcpDc0RHaiojIsJye.png)
《筆者帶你剖析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的連結。
小結
本章内容到此結束,由于時間倉庫,本文或許有很多不盡人意的地方,希望各位能夠了解和體諒。