javaweb學習總結(四十一)——Apache的DBUtils框架學習
一、commons-dbutils簡介
commons-dbutils 是 Apache 組織提供的一個開源 JDBC工具類庫,它是對JDBC的簡單封裝,學習成本極低,並且使用dbutils能極大簡化jdbc編碼的工作量,同時也不會影響程式的效能。因此dbutils成為很多不喜歡hibernate的公司的首選。
commons-dbutilsAPI介紹:
- org.apache.commons.dbutils.QueryRunner
- org.apache.commons.dbutils.ResultSetHandler
工具類
- org.apache.commons.dbutils.DbUtils
二、QueryRunner類使用講解
該類簡單化了SQL查詢,它與ResultSetHandler組合在一起使用可以完成大部分的資料庫操作,能夠大大減少編碼量。
QueryRunner類提供了兩個構造方法:
- 預設的構造方法
- 需要一個 javax.sql.DataSource 來作引數的構造方法。
2.1、QueryRunner類的主要方法
public Object query(Connection conn, String sql, Object[] params, ResultSetHandler rsh) throws SQLException:執行一個查詢操作,在這個查詢中,物件陣列中的每個元素值被用來作為查詢語句的置換引數。該方法會自行處理 PreparedStatement 和 ResultSet 的建立和關閉。
public Object query(String sql, Object[] params, ResultSetHandler rsh) throws SQLException: 幾乎與第一種方法一樣;唯一的不同在於它不將資料庫連線提供給方法,並且它是從提供給構造方法的資料來源(DataSource) 或使用的setDataSource 方法中重新獲得 Connection。
public Object query(Connection conn, String sql, ResultSetHandler rsh) throws SQLException : 執行一個不需要置換引數的查詢操作。
public int update(Connection conn, String sql, Object[] params) throws SQLException:用來執行一個更新(插入、更新或刪除)操作。
public int update(Connection conn, String sql) throws SQLException:用來執行一個不需要置換引數的更新操作。
2.2、使用QueryRunner類實現CRUD
1 package me.gacl.test; 2 3 import java.util.Date; 4 import java.util.List; 5 import java.io.File; 6 import java.io.FileReader; 7 import java.io.IOException; 8 import java.sql.SQLException; 9 import javax.sql.rowset.serial.SerialClob; 10 importme.gacl.domain.User; 11 import me.gacl.util.JdbcUtils; 12 import org.apache.commons.dbutils.QueryRunner; 13 import org.apache.commons.dbutils.handlers.BeanHandler; 14 import org.apache.commons.dbutils.handlers.BeanListHandler; 15 import org.junit.Test; 16 17 /** 18 * @ClassName: DBUtilsCRUDTest 19 * @Description:使用dbutils框架的QueryRunner類完成CRUD,以及批處理 20 * @author: 孤傲蒼狼 21 * @date: 2014-10-5 下午4:56:44 22 * 23 */ 24 public class QueryRunnerCRUDTest { 25 26 /* 27 *測試表 28 create table users( 29 id int primary key auto_increment, 30 name varchar(40), 31 password varchar(40), 32 email varchar(60), 33 birthday date 34 ); 35 */ 36 37 @Test 38 public void add() throws SQLException { 39 //將資料來源傳遞給QueryRunner,QueryRunner內部通過資料來源獲取資料庫連線 40 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 41 String sql = "insert into users(name,password,email,birthday) values(?,?,?,?)"; 42 Object params[] = {"孤傲蒼狼","123", "[email protected]", new Date()}; 43 //Object params[] = {"白虎神皇","123", "[email protected]", "1988-05-07"}; 44 qr.update(sql, params); 45 } 46 47 @Test 48 public void delete() throws SQLException { 49 50 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 51 String sql = "delete from users where id=?"; 52 qr.update(sql, 1); 53 54 } 55 56 @Test 57 public void update() throws SQLException { 58 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 59 String sql = "update users set name=? where id=?"; 60 Object params[] = { "ddd", 5}; 61 qr.update(sql, params); 62 } 63 64 @Test 65 public void find() throws SQLException { 66 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 67 String sql = "select * from users where id=?"; 68 Object params[] = {2}; 69 User user = (User) qr.query(sql, params, new BeanHandler(User.class)); 70 System.out.println(user.getBirthday()); 71 } 72 73 @Test 74 public void getAll() throws SQLException { 75 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 76 String sql = "select * from users"; 77 List list = (List) qr.query(sql, new BeanListHandler(User.class)); 78 System.out.println(list.size()); 79 } 80 81 /** 82 * @Method: testBatch 83 * @Description:批處理 84 * @Anthor:孤傲蒼狼 85 * 86 * @throws SQLException 87 */ 88 @Test 89 public void testBatch() throws SQLException { 90 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 91 String sql = "insert into users(name,password,email,birthday) values(?,?,?,?)"; 92 Object params[][] = new Object[10][]; 93 for (int i = 0; i < 10; i++) { 94 params[i] = new Object[] { "aa" + i, "123", "[email protected]", 95 new Date() }; 96 } 97 qr.batch(sql, params); 98 } 99 100 //用dbutils完成大資料(不建議用) 101 /*************************************************************************** 102 create table testclob 103 ( 104 id int primary key auto_increment, 105 resume text 106 ); 107 **************************************************************************/ 108 @Test 109 public void testclob() throws SQLException, IOException{ 110 QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); 111 String sql = "insert into testclob(resume) values(?)"; //clob 112 //這種方式獲取的路徑,其中的空格會被使用“%20”代替 113 String path = QueryRunnerCRUDTest.class.getClassLoader().getResource("data.txt").getPath(); 114 //將“%20”替換回空格 115 path = path.replaceAll("%20", " "); 116 FileReader in = new FileReader(path); 117 char[] buffer = new char[(int) new File(path).length()]; 118 in.read(buffer); 119 SerialClob clob = new SerialClob(buffer); 120 Object params[] = {clob}; 121 runner.update(sql, params); 122 } 123 }
三、ResultSetHandler介面使用講解
該介面用於處理java.sql.ResultSet,將資料按要求轉換為另一種形式。
ResultSetHandler介面提供了一個單獨的方法:Object handle (java.sql.ResultSet .rs)
3.1、ResultSetHandler介面的實現類
- ArrayHandler:把結果集中的第一行資料轉成物件陣列。
- ArrayListHandler:把結果集中的每一行資料都轉成一個數組,再存放到List中。
- BeanHandler:將結果集中的第一行資料封裝到一個對應的JavaBean例項中。
- BeanListHandler:將結果集中的每一行資料都封裝到一個對應的JavaBean例項中,存放到List裡。
- ColumnListHandler:將結果集中某一列的資料存放到List中。
- KeyedHandler(name):將結果集中的每一行資料都封裝到一個Map裡,再把這些map再存到一個map裡,其key為指定的key。
- MapHandler:將結果集中的第一行資料封裝到一個Map裡,key是列名,value就是對應的值。
- MapListHandler:將結果集中的每一行資料都封裝到一個Map裡,然後再存放到List
3.2、測試dbutils各種型別的處理器
1 package me.gacl.test; 2 3 import java.sql.SQLException; 4 import java.util.Arrays; 5 import java.util.List; 6 import java.util.Map; 7 import me.gacl.util.JdbcUtils; 8 import org.apache.commons.dbutils.QueryRunner; 9 import org.apache.commons.dbutils.handlers.ArrayHandler; 10 import org.apache.commons.dbutils.handlers.ArrayListHandler; 11 import org.apache.commons.dbutils.handlers.ColumnListHandler; 12 import org.apache.commons.dbutils.handlers.KeyedHandler; 13 import org.apache.commons.dbutils.handlers.MapHandler; 14 import org.apache.commons.dbutils.handlers.MapListHandler; 15 import org.apache.commons.dbutils.handlers.ScalarHandler; 16 import org.junit.Test; 17 18 /** 19 * @ClassName: ResultSetHandlerTest 20 * @Description:測試dbutils各種型別的處理器 21 * @author: 孤傲蒼狼 22 * @date: 2014-10-6 上午8:39:14 23 * 24 */ 25 public class ResultSetHandlerTest { 26 27 @Test 28 public void testArrayHandler() throws SQLException{ 29 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 30 String sql = "select * from users"; 31 Object result[] = (Object[]) qr.query(sql, new ArrayHandler()); 32 System.out.println(Arrays.asList(result)); //list toString() 33 } 34 35 @Test 36 public void testArrayListHandler() throws SQLException{ 37 38 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 39 String sql = "select * from users"; 40 List<Object[]> list = (List) qr.query(sql, new ArrayListHandler()); 41 for(Object[] o : list){ 42 System.out.println(Arrays.asList(o)); 43 } 44 } 45 46 @Test 47 public void testColumnListHandler() throws SQLException{ 48 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 49 String sql = "select * from users"; 50 List list = (List) qr.query(sql, new ColumnListHandler("id")); 51 System.out.println(list); 52 } 53 54 @Test 55 public void testKeyedHandler() throws Exception{ 56 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 57 String sql = "select * from users"; 58 59 Map<Integer,Map> map = (Map) qr.query(sql, new KeyedHandler("id")); 60 for(Map.Entry<Integer, Map> me : map.entrySet()){ 61 int id = me.getKey(); 62 Map<String,Object> innermap = me.getValue(); 63 for(Map.Entry<String, Object> innerme : innermap.entrySet()){ 64 String columnName = innerme.getKey(); 65 Object value = innerme.getValue(); 66 System.out.println(columnName + "=" + value); 67 } 68 System.out.println("----------------"); 69 } 70 } 71 72 @Test 73 public void testMapHandler() throws SQLException{ 74 75 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 76 String sql = "select * from users"; 77 78 Map<String,Object> map = (Map) qr.query(sql, new MapHandler()); 79 for(Map.Entry<String, Object> me : map.entrySet()) 80 { 81 System.out.println(me.getKey() + "=" + me.getValue()); 82 } 83 } 84 85 86 @Test 87 public void testMapListHandler() throws SQLException{ 88 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 89 String sql = "select * from users"; 90 List<Map> list = (List) qr.query(sql, new MapListHandler()); 91 for(Map<String,Object> map :list){ 92 for(Map.Entry<String, Object> me : map.entrySet()) 93 { 94 System.out.println(me.getKey() + "=" + me.getValue()); 95 } 96 } 97 } 98 99 @Test 100 public void testScalarHandler() throws SQLException{ 101 QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()); 102 String sql = "select count(*) from users"; //[13] list[13] 103 int count = ((Long)qr.query(sql, new ScalarHandler(1))).intValue(); 104 System.out.println(count); 105 } 106 }
三、DbUtils類使用講解
DbUtils :提供如關閉連線、裝載JDBC驅動程式等常規工作的工具類,裡面的所有方法都是靜態的。主要方法如下:
public static void close(…) throws java.sql.SQLException: DbUtils類提供了三個過載的關閉方法。這些方法檢查所提供的引數是不是NULL,如果不是的話,它們就關閉Connection、Statement和ResultSet。
public static void closeQuietly(…): 這一類方法不僅能在Connection、Statement和ResultSet為NULL情況下避免關閉,還能隱藏一些在程式中丟擲的SQLEeception。
public static void commitAndCloseQuietly(Connection conn): 用來提交連線,然後關閉連線,並且在關閉連線時不丟擲SQL異常。
public static boolean loadDriver(java.lang.String driverClassName):這一方裝載並註冊JDBC驅動程式,如果成功就返回true。使用該方法,你不需要捕捉這個異常ClassNotFoundException。
四、JDBC開發中的事務處理
在開發中,對資料庫的多個表或者對一個表中的多條資料執行更新操作時要保證對多個更新操作要麼同時成功,要麼都不成功,這就涉及到對多個更新操作的事務管理問題了。比如銀行業務中的轉賬問題,A使用者向B使用者轉賬100元,假設A使用者和B使用者的錢都儲存在Account表,那麼A使用者向B使用者轉賬時就涉及到同時更新Account表中的A使用者的錢和B使用者的錢,用SQL來表示就是:
1 update account set money=money-100 where name='A' 2 update account set money=money+100 where name='B'
4.1、在資料訪問層(Dao)中處理事務
對於這樣的同時更新一個表中的多條資料的操作,那麼必須保證要麼同時成功,要麼都不成功,所以需要保證這兩個update操作在同一個事務中進行。在開發中,我們可能會在AccountDao寫一個轉賬處理方法,如下:
1 /** 2 * @Method: transfer 3 * @Description:這個方法是用來處理兩個使用者之間的轉賬業務 4 * 在開發中,DAO層的職責應該只涉及到CRUD, 5 * 而這個transfer方法是處理兩個使用者之間的轉賬業務的,已經涉及到具體的業務操作,應該在業務層中做,不應該出現在DAO層的 6 * 所以在開發中DAO層出現這樣的業務處理方法是完全錯誤的 7 * @Anthor:孤傲蒼狼 8 * 9 * @param sourceName 10 * @param targetName 11 * @param money 12 * @throws SQLException 13 */ 14 public void transfer(String sourceName,String targetName,float money) throws SQLException{ 15 Connection conn = null; 16 try{ 17 conn = JdbcUtils.getConnection(); 18 //開啟事務 19 conn.setAutoCommit(false); 20 /** 21 * 在建立QueryRunner物件時,不傳遞資料來源給它,是為了保證這兩條SQL在同一個事務中進行, 22 * 我們手動獲取資料庫連線,然後讓這兩條SQL使用同一個資料庫連線執行 23 */ 24 QueryRunner runner = new QueryRunner(); 25 String sql1 = "update account set money=money-100 where name=?"; 26 String sql2 = "update account set money=money+100 where name=?"; 27 Object[] paramArr1 = {sourceName}; 28 Object[] paramArr2 = {targetName}; 29 runner.update(conn,sql1,paramArr1); 30 //模擬程式出現異常讓事務回滾 31 int x = 1/0; 32 runner.update(conn,sql2,paramArr2); 33 //sql正常執行之後就提交事務 34 conn.commit(); 35 }catch (Exception e) { 36 e.printStackTrace(); 37 if(conn!=null){ 38 //出現異常之後就回滾事務 39 conn.rollback(); 40 } 41 }finally{ 42 //關閉資料庫連線 43 conn.close(); 44 } 45 }
然後我們在AccountService中再寫一個同名方法,在方法內部呼叫AccountDao的transfer方法處理轉賬業務,如下:
1 public void transfer(String sourceName,String targetName,float money) throws SQLException{ 2 AccountDao dao = new AccountDao(); 3 dao.transfer(sourceName, targetName, money); 4 }
上面AccountDao的這個transfer方法可以處理轉賬業務,並且保證了在同一個事務中進行,但是AccountDao的這個transfer方法是處理兩個使用者之間的轉賬業務的,已經涉及到具體的業務操作,應該在業務層中做,不應該出現在DAO層的,在開發中,DAO層的職責應該只涉及到基本的CRUD,不涉及具體的業務操作,所以在開發中DAO層出現這樣的業務處理方法是一種不好的設計。
4.2、在業務層(BusinessService)處理事務
由於上述AccountDao存在具體的業務處理方法,導致AccountDao的職責不夠單一,下面我們對AccountDao進行改造,讓AccountDao的職責只是做CRUD操作,將事務的處理挪到業務層(BusinessService),改造後的AccountDao如下:
1 package me.gacl.dao; 2 3 import java.sql.Connection; 4 import java.sql.SQLException; 5 import org.apache.commons.dbutils.QueryRunner; 6 import org.apache.commons.dbutils.handlers.BeanHandler; 7 import me.gacl.domain.Account; 8 import me.gacl.util.JdbcUtils; 9 10 /*account測試表 11 create table account( 12 id int primary key auto_increment, 13 name varchar(40), 14 money float 15 )character set utf8 collate utf8_general_ci; 16 17 insert into account(name,money) values('A',1000); 18 insert into account(name,money) values('B',1000); 19 insert into account(name,money) values('C',1000); 20 21 */ 22 23 /** 24 * @ClassName: AccountDao 25 * @Description: 針對Account物件的CRUD 26 * @author: 孤傲蒼狼 27 * @date: 2014-10-6 下午4:00:42 28 * 29 */ 30 public class AccountDao { 31 32 //接收service層傳遞過來的Connection物件 33 private Connection conn = null; 34 35 public AccountDao(Connection conn){ 36 this.conn = conn; 37 } 38 39 public AccountDao(){ 40 41 } 42 43 /** 44 * @Method: update 45 * @Description:更新 46 * @Anthor:孤傲蒼狼 47 * 48 * @param account 49 * @throws SQLException 50 */ 51 public void update(Account account) throws SQLException{ 52 53 QueryRunner qr = new QueryRunner(); 54 String sql = "update account set name=?,money=? where id=?"; 55 Object params[] = {account.getName(),account.getMoney(),account.getId()}; 56 //使用service層傳遞過來的Connection物件操作資料庫 57 qr.update(conn,sql, params); 58 59 } 60 61 /** 62 * @Method: find 63 * @Description:查詢 64 * @Anthor:孤傲蒼狼 65 * 66 * @param id 67 * @return 68 * @throws SQLException 69 */ 70 public Account find(int id) throws SQLException{ 71 QueryRunner qr = new QueryRunner(); 72 String sql = "select * from account where id=?"; 73 //使用service層傳遞過來的Connection物件操作資料庫 74 return (Account) qr.query(conn,sql, id, new BeanHandler(Account.class)); 75 } 76 }
接著對AccountService(業務層)中的transfer方法的改造,在業務層(BusinessService)中處理事務
1 package me.gacl.service; 2 3 import java.sql.Connection; 4 import java.sql.SQLException; 5 import me.gacl.dao.AccountDao; 6 import me.gacl.domain.Account; 7 import me.gacl.util.JdbcUtils; 8 9 /** 10 * @ClassName: AccountService 11 * @Description: 業務邏輯處理層 12 * @author: 孤傲蒼狼 13 * @date: 2014-10-6 下午5:30:15 14 * 15 */ 16 public class AccountService { 17 18 /** 19 * @Method: transfer 20 * @Description:這個方法是用來處理兩個使用者之間的轉賬業務 21 * @Anthor:孤傲蒼狼 22 * 23 * @param sourceid 24 * @param tartgetid 25 * @param money 26 * @throws SQLException 27 */ 28 public void transfer(int sourceid,int tartgetid,float money) throws SQLException{ 29 Connection conn = null; 30 try{ 31 //獲取資料庫連線 32 conn = JdbcUtils.getConnection(); 33 //開啟事務 34 conn.setAutoCommit(false); 35 //將獲取到的Connection傳遞給AccountDao,保證dao層使用的是同一個Connection物件操作資料庫 36 AccountDao dao = new AccountDao(conn); 37 Account source = dao.find(sourceid); 38 Account target = dao.find(tartgetid); 39 40 source.setMoney(source.getMoney()-money); 41 target.setMoney(target.getMoney()+money); 42 43 dao.update(source); 44 //模擬程式出現異常讓事務回滾 45 int x = 1/0; 46 dao.update(target); 47 //提交事務 48 conn.commit(); 49 }catch (Exception e) { 50 e.printStackTrace(); 51 //出現異常之後就回滾事務 52 conn.rollback(); 53 }finally{ 54 conn.close(); 55 } 56 } 57 }
程式經過這樣改造之後就比剛才好多了,AccountDao只負責CRUD,裡面沒有具體的業務處理方法了,職責就單一了,而AccountService則負責具體的業務邏輯和事務的處理,需要操作資料庫時,就呼叫AccountDao層提供的CRUD方法操作資料庫。
4.3、使用ThreadLocal進行更加優雅的事務處理
上面的在businessService層這種處理事務的方式依然不夠優雅,為了能夠讓事務處理更加優雅,我們使用ThreadLocal類進行改造,ThreadLocal一個容器,向這個容器儲存的物件,在當前執行緒範圍內都可以取得出來,向ThreadLocal裡面存東西就是向它裡面的Map存東西的,然後ThreadLocal把這個Map掛到當前的執行緒底下,這樣Map就只屬於這個執行緒了
ThreadLocal類的使用範例如下:
1 package me.gacl.test; 2 3 public class ThreadLocalTest { 4 5 public static void main(String[] args) { 6 //得到程式執行時的當前執行緒 7 Thread currentThread = Thread.currentThread(); 8 System.out.println(currentThread); 9 //ThreadLocal一個容器,向這個容器儲存的物件,在當前執行緒範圍內都可以取得出來 10 ThreadLocal<String> t = new ThreadLocal<String>(); 11 //把某個物件繫結到當前執行緒上 物件以鍵值對的形式儲存到一個Map集合中,物件的的key是當前的執行緒,如: map(currentThread,"aaa") 12 t.set("aaa"); 13 //獲取繫結到當前執行緒中的物件 14 String value = t.get(); 15 //輸出value的值是aaa 16 System.out.println(value); 17 } 18 }
使用使用ThreadLocal類進行改造資料庫連線工具類JdbcUtils,改造後的程式碼如下:
1<