1. 程式人生 > >javaweb學習總結—Apache的DBUtils框架學習

javaweb學習總結—Apache的DBUtils框架學習

運行時 轉載 general n) create tdi 代碼 print varchar

註明: 本文轉載自http://www.cnblogs.com/xdp-gacl/p/4007225.html

一、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

技術分享圖片
 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     }

 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各種類型的處理器

技術分享圖片
 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層出現這樣的業務處理方法是完全錯誤的
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如下:

技術分享圖片
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 */
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      
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      
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)中處理事務

技術分享圖片 16 public class AccountService {
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類的使用範例如下:

技術分享圖片
 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,改造後的代碼如下:

技術分享圖片
 15 public class JdbcUtils2 {
 16     
 17     private static ComboPooledDataSource ds = null;
 18     //使用ThreadLocal存儲當前線程中的Connection對象
 19     private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
 20     
 21     //在靜態代碼塊中創建數據庫連接池
 22     static{
 23         try{
 24             //通過代碼創建C3P0數據庫連接池
 25             /*ds = new ComboPooledDataSource();
 26             ds.setDriverClass("com.mysql.jdbc.Driver");
 27             ds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbcstudy");
 28             ds.setUser("root");
 29             ds.setPassword("XDP");
 30             ds.setInitialPoolSize(10);
 31             ds.setMinPoolSize(5);
 32             ds.setMaxPoolSize(20);*/
 33             
 34             //通過讀取C3P0的xml配置文件創建數據源,C3P0的xml配置文件c3p0-config.xml必須放在src目錄下
 35             //ds = new ComboPooledDataSource();//使用C3P0的默認配置來創建數據源
 36             ds = new ComboPooledDataSource("MySQL");//使用C3P0的命名配置來創建數據源
 37             
 38         }catch (Exception e) {
 39             throw new ExceptionInInitializerError(e);
 40         }
 41     }
 42     
 50     public static Connection getConnection() throws SQLException{
 51         //從當前線程中獲取Connection
 52         Connection conn = threadLocal.get();
 53         if(conn==null){
 54             //從數據源中獲取數據庫連接
 55             conn = getDataSource().getConnection();
 56             //將conn綁定到當前線程
 57             threadLocal.set(conn);
 58         }
 59         return conn;
 60     }
 61     
 68     public static void startTransaction(){
 69         try{
 70             Connection conn =  threadLocal.get();
 71             if(conn==null){
 72                 conn = getConnection();
 73                  //把 conn綁定到當前線程上
 74                 threadLocal.set(conn);
 75             }
 76             //開啟事務
 77             conn.setAutoCommit(false);
 78         }catch (Exception e) {
 79             throw new RuntimeException(e);
 80         }
 81     }
 82     
 89     public static void rollback(){
 90         try{
 91             //從當前線程中獲取Connection
 92             Connection conn = threadLocal.get();
 93             if(conn!=null){
 94                 //回滾事務
 95                 conn.rollback();
 96             }
 97         }catch (Exception e) {
 98             throw new RuntimeException(e);
 99         }
100     }
101     
108     public static void commit(){
109         try{
110             //從當前線程中獲取Connection
111             Connection conn = threadLocal.get();
112             if(conn!=null){
113                 //提交事務
114                 conn.commit();
115             }
116         }catch (Exception e) {
117             throw new RuntimeException(e);
118         }
119     }
120      
127     public static void close(){
128         try{
129             //從當前線程中獲取Connection
130             Connection conn = threadLocal.get();
131             if(conn!=null){
132                 conn.close();
133                  //解除當前線程上綁定conn
134                 threadLocal.remove();
135             }
136         }catch (Exception e) {
137             throw new RuntimeException(e);
138         }
139     }
140     
147     public static DataSource getDataSource(){
148         //從數據源中獲取數據庫連接
149         return ds;
150     }
151 }
技術分享圖片

  對AccountDao進行改造,數據庫連接對象不再需要service層傳遞過來,而是直接從JdbcUtils2提供的getConnection方法去獲取,改造後的AccountDao如下:

技術分享圖片
11 /*
12 create table account(
13     id int primary key auto_increment,
14     name varchar(40),
15     money float
16 )character set utf8 collate utf8_general_ci;
17 
18 insert into account(name,money) values(‘A‘,1000);
19 insert into account(name,money) values(‘B‘,1000);
20 insert into account(name,money) values(‘C‘,1000);
21 
22 */
23 
31 public class AccountDao2 {
32 
33     public void update(Account account) throws SQLException{
34         
35         QueryRunner qr = new QueryRunner();
36         String sql = "update account set name=?,money=? where id=?";
37         Object params[] = {account.getName(),account.getMoney(),account.getId()};
38         //JdbcUtils2.getConnection()獲取當前線程中的Connection對象
39         qr.update(JdbcUtils2.getConnection(),sql, params);
40         
41     }
42     
43     public Account find(int id) throws SQLException{
44         QueryRunner qr = new QueryRunner();
45         String sql = "select * from account where id=?";
46         //JdbcUtils2.getConnection()獲取當前線程中的Connection對象
47         return (Account) qr.query(JdbcUtils2.getConnection(),sql, id, new BeanHandler(Account.class));
48     }
49 }
技術分享圖片

  對AccountService進行改造,service層不再需要傳遞數據庫連接Connection給Dao層,改造後的AccountService如下:

技術分享圖片
 8 public class AccountService2 {
20     public void transfer(int sourceid,int tartgetid,float money) throws SQLException{
21         try{
22             //開啟事務,在業務層處理事務,保證dao層的多個操作在同一個事務中進行
23             JdbcUtils2.startTransaction();
24             AccountDao2 dao = new AccountDao2();
25             
26             Account source = dao.find(sourceid);
27             Account target = dao.find(tartgetid);
28             source.setMoney(source.getMoney()-money);
29             target.setMoney(target.getMoney()+money);
30             
31             dao.update(source);
32             //模擬程序出現異常讓事務回滾
33             int x = 1/0;
34             dao.update(target);
35             
36             //SQL正常執行之後提交事務
37             JdbcUtils2.commit();
38         }catch (Exception e) {
39             e.printStackTrace();
40             //出現異常之後就回滾事務
41             JdbcUtils2.rollback();
42         }finally{
43             //關閉數據庫連接
44             JdbcUtils2.close();
45         }
46     }
47 }
技術分享圖片

  這樣在service層對事務的處理看起來就更加優雅了。ThreadLocal類在開發中使用得是比較多的,程序運行中產生的數據要想在一個線程範圍內共享,只需要把數據使用ThreadLocal進行存儲即可

4.4、ThreadLocal + Filter 處理事務

  上面介紹了JDBC開發中事務處理的3種方式,下面再介紹的一種使用ThreadLocal + Filter進行統一的事務處理,這種方式主要是使用過濾器進行統一的事務處理,如下圖所示:

  技術分享圖片

1、編寫一個事務過濾器TransactionFilter

代碼如下:

技術分享圖片
23 public class TransactionFilter implements Filter {
24 
25     @Override
26     public void init(FilterConfig filterConfig) throws ServletException {
27 
28     }
29 
30     @Override
31     public void doFilter(ServletRequest request, ServletResponse response,
32             FilterChain chain) throws IOException, ServletException {
33 
34         Connection connection = null;
35         try {
36             //1、獲取數據庫連接對象Connection
37             connection = JdbcUtils.getConnection();
38             //2、開啟事務
39             connection.setAutoCommit(false);
40             //3、利用ThreadLocal把獲取數據庫連接對象Connection和當前線程綁定
41             ConnectionContext.getInstance().bind(connection);
42             //4、把請求轉發給目標Servlet
43             chain.doFilter(request, response);
44             //5、提交事務
45             connection.commit();
46         } catch (Exception e) {
47             e.printStackTrace();
48             //6、回滾事務
49             try {
50                 connection.rollback();
51             } catch (SQLException e1) {
52                 e1.printStackTrace();
53             }
54             HttpServletRequest req = (HttpServletRequest) request;
55             HttpServletResponse res = (HttpServletResponse) response;
56             //req.setAttribute("errMsg", e.getMessage());
57             //req.getRequestDispatcher("/error.jsp").forward(req, res);
58             //出現異常之後跳轉到錯誤頁面
59             res.sendRedirect(req.getContextPath()+"/error.jsp");
60         }finally{
61             //7、解除綁定
62             ConnectionContext.getInstance().remove();
63             //8、關閉數據庫連接
64             try {
65                 connection.close();
66             } catch (SQLException e) {
67                 e.printStackTrace();
68             }
69         }
70     }
71 
72     @Override
73     public void destroy() {
74 
75     }
76 }
技術分享圖片

  我們在TransactionFilter中把獲取到的數據庫連接使用ThreadLocal綁定到當前線程之後,在DAO層還需要從ThreadLocal中取出數據庫連接來操作數據庫,因此需要編寫一個ConnectionContext類來存儲ThreadLocal,ConnectionContext類的代碼如下:

技術分享圖片
12 public class ConnectionContext {
13 
14     /**
15      * 構造方法私有化,將ConnectionContext設計成單例
16      */
17     private ConnectionContext(){
18         
19     }
20     //創建ConnectionContext實例對象
21     private static ConnectionContext connectionContext = new ConnectionContext();
22     
30     public static ConnectionContext getInstance(){
31         return connectionContext;
32     }
33     
34     /**
35     * @Field: connectionThreadLocal
36     *         使用ThreadLocal存儲數據庫連接對象
37     */ 
38     private ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<Connection>();
39     
47     public void bind(Connection connection){
48         connectionThreadLocal.set(connection);
49     }
50     
58     public Connection getConnection(){
59         return connectionThreadLocal.get();
60     }
61     
68     public void remove(){
69         connectionThreadLocal.remove();
70     }
71 }
技術分享圖片

  在DAO層想獲取數據庫連接時,就可以使用ConnectionContext.getInstance().getConnection()來獲取,如下所示:

技術分享圖片
10 /*
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 
30 public class AccountDao3 {
31 
32     public void update(Account account) throws SQLException{
33         
34         QueryRunner qr = new QueryRunner();
35         String sql = "update account set name=?,money=? where id=?";
36         Object params[] = {account.getName(),account.getMoney(),account.getId()};
37         //ConnectionContext.getInstance().getConnection()獲取當前線程中的Connection對象
38         qr.update(ConnectionContext.getInstance().getConnection(),sql, params);
39         
40     }
41     
42     public Account find(int id) throws SQLException{
43         QueryRunner qr = new QueryRunner();
44         String sql = "select * from account where id=?";
45         //ConnectionContext.getInstance().getConnection()獲取當前線程中的Connection對象
46         return (Account) qr.query(ConnectionContext.getInstance().getConnection(),sql, id, new BeanHandler(Account.class));
47     }
48 }
技術分享圖片

  businessService層也不用處理事務和數據庫連接問題了,這些統一在TransactionFilter中統一管理了,businessService層只需要專註業務邏輯的處理即可,如下所示:

技術分享圖片
 7 public class AccountService3 {
19     public void transfer(int sourceid, int tartgetid, float money)
20             throws SQLException {
21         AccountDao3 dao = new AccountDao3();
22         Account source = dao.find(sourceid);
23         Account target = dao.find(tartgetid);
24         source.setMoney(source.getMoney() - money);
25         target.setMoney(target.getMoney() + money);
26         dao.update(source);
27         // 模擬程序出現異常讓事務回滾
28         int x = 1 / 0;
29         dao.update(target);
30     }
31 }
技術分享圖片

  Web層的Servlet調用businessService層的業務方法處理用戶請求,需要註意的是:調用businessService層的方法出異常之後,繼續將異常拋出,這樣在TransactionFilter就能捕獲到拋出的異常,繼而執行事務回滾操作,如下所示:

技術分享圖片
11 public class AccountServlet extends HttpServlet {
12 
13     public void doGet(HttpServletRequest request, HttpServletResponse response)
14             throws ServletException, IOException {
15         AccountService3 service = new AccountService3();
16         try {
17             service.transfer(1, 2, 100);
18         } catch (SQLException e) {
19             e.printStackTrace();
20             //註意:調用service層的方法出異常之後,繼續將異常拋出,這樣在TransactionFilter就能捕獲到拋出的異常,繼而執行事務回滾操作
21             throw new RuntimeException(e);
22         }
23     }
24 
25     public void doPost(HttpServletRequest request, HttpServletResponse response)
26             throws ServletException, IOException {
27         doGet(request, response);
28     }
29 }
技術分享圖片

javaweb學習總結—Apache的DBUtils框架學習