1. 程式人生 > >JDBC演變到Mybatis過程

JDBC演變到Mybatis過程

1 引言#

本文主要講解JDBC怎麼演變到Mybatis的漸變過程,重點講解了為什麼要將JDBC封裝成Mybaits這樣一個持久層框架。再而論述Mybatis作為一個數據持久層框架本身有待改進之處。

2 JDBC實現查詢分析#

我們先看看我們最熟悉也是最基礎的通過JDBC查詢資料庫資料,一般需要以下七個步驟:

  • 載入JDBC驅動;

  • 建立並獲取資料庫連線;

  • 建立 JDBC Statements 物件;

  • 設定SQL語句的傳入引數;

  • 執行SQL語句並獲得查詢結果;

  • 對查詢結果進行轉換處理並將處理結果返回;

  • 釋放相關資源(關閉Connection,關閉Statement,關閉ResultSet);

以下是具體的實現程式碼:

public static List<Map<String,Object>> queryForList(){  
    Connection connection = null;  
    ResultSet rs = null;  
    PreparedStatement stmt = null;  
    List<Map<String,Object>> resultList = new ArrayList<Map<String,Object>>();  
          
    try {  
        // 載入JDBC驅動  
        Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();  
        String url = "jdbc:oracle:thin:@localhost:1521:ORACLEDB";  
              
        String user = "trainer";   
        String password = "trainer";   
              
        // 獲取資料庫連線  
        connection = DriverManager.getConnection(url,user,password);   
              
        String sql = "select * from userinfo where user_id = ? ";  
        // 建立Statement物件(每一個Statement為一次資料庫執行請求)  
        stmt = connection.prepareStatement(sql);  
              
        // 設定傳入引數  
        stmt.setString(1, "zhangsan");  
              
        // 執行SQL語句  
        rs = stmt.executeQuery();  
              
        // 處理查詢結果(將查詢結果轉換成List<Map>格式)  
        ResultSetMetaData rsmd = rs.getMetaData();  
        int num = rsmd.getColumnCount();  
              
        while(rs.next()){  
            Map map = new HashMap();  
            for(int i = 0;i < num;i++){  
                String columnName = rsmd.getColumnName(i+1);  
                map.put(columnName,rs.getString(columnName));  
            }  
            resultList.add(map);  
        }  
              
    } catch (Exception e) {  
        e.printStackTrace();  
    } finally {  
        try {  
            // 關閉結果集  
            if (rs != null) {  
                rs.close();  
                rs = null;  
            }  
            // 關閉執行  
            if (stmt != null) {  
                stmt.close();  
                stmt = null;  
            }  
            if (connection != null) {  
                connection.close();  
                connection = null;  
            }  
        } catch (SQLException e) {  
            e.printStackTrace();  
        }  
    }        
    return resultList;  
}

3 JDBC演變到Mybatis過程#

上面我們看到了實現JDBC有七個步驟,哪些步驟是可以進一步封裝的,減少我們開發的程式碼量。

3.1 第一步優化:連接獲取和釋放##

  1. 問題描述:

資料庫連線頻繁的開啟和關閉本身就造成了資源的浪費,影響系統的效能

解決問題:

資料庫連線的獲取和關閉我們可以使用資料庫連線池來解決資源浪費的問題。通過連線池就可以反覆利用已經建立的連線去訪問資料庫了。減少連線的開啟和關閉的時間。

  • 2.問題描述:

但是現在連線池多種多樣,可能存在變化,有可能採用DBCP的連線池,也有可能採用容器本身的JNDI資料庫連線池。

解決問題:

我們可以通過DataSource進行隔離解耦,我們統一從DataSource裡面獲取資料庫連線,DataSource具體由DBCP實現還是由容器的JNDI實現都可以,所以我們將DataSource的具體實現通過讓使用者配置來應對變化。

3.2 第二步優化:SQL統一存取##

  1. 問題描述:

我們使用JDBC進行操作資料庫時,SQL語句基本都散落在各個JAVA類中,這樣有三個不足之處:

第一,可讀性很差,不利於維護以及做效能調優。

第二,改動Java程式碼需要重新編譯、打包部署。

第三,不利於取出SQL在資料庫客戶端執行(取出後還得刪掉中間的Java程式碼,編寫好的SQL語句寫好後還得通過+號在Java進行拼湊)。

解決問題:

我們可以考慮不把SQL語句寫到Java程式碼中,那麼把SQL語句放到哪裡呢?首先需要有一個統一存放的地方,我們可以將這些SQL語句統一集中放到配置檔案或者資料庫裡面(以key-value的格式存放)。然後通過SQL語句的key值去獲取對應的SQL語句。

既然我們將SQL語句都統一放在配置檔案或者資料庫中,那麼這裡就涉及一個SQL語句的載入問題

3.3 第三步優化:傳入引數對映和動態SQL##

  1. 問題描述:

很多情況下,我們都可以通過在SQL語句中設定佔位符來達到使用傳入引數的目的,這種方式本身就有一定侷限性,它是按照一定順序傳入引數的,要與佔位符一一匹配。但是,如果我們傳入的引數是不確定的(比如列表查詢,根據使用者填寫的查詢條件不同,傳入查詢的引數也是不同的,有時是一個引數、有時可能是三個引數),那麼我們就得在後臺程式碼中自己根據請求的傳入引數去拼湊相應的SQL語句,這樣的話還是避免不了在Java程式碼裡面寫SQL語句的命運。既然我們已經把SQL語句統一存放在配置檔案或者資料庫中了,怎麼做到能夠根據前臺傳入引數的不同,動態生成對應的SQL語句呢?

解決問題:

第一,我們先解決這個動態問題,按照我們正常的程式設計師思維是,通過if和else這類的判斷來進行是最直觀的,這個時候我們想到了JSTL中的<if test=””></if>這樣的標籤,那麼,能不能將這類的標籤引入到SQL語句中呢?假設可以,那麼我們這裡就需要一個專門的SQL解析器來解析這樣的SQL語句,但是,if判斷的變數來自於哪裡呢?傳入的值本身是可變的,那麼我們得為這個值定義一個不變的變數名稱,而且這個變數名稱必須和對應的值要有對應關係,可以通過這個變數名稱找到對應的值,這個時候我們想到了key-value的Map。解析的時候根據變數名的具體值來判斷。

假如前面可以判斷沒有問題,那麼假如判斷的結果是true,那麼就需要輸出的標籤裡面的SQL片段,但是怎麼解決在標籤裡面使用變數名稱的問題呢?這裡我們需要使用一種有別於SQL的語法來嵌入變數(比如使用#變數名#)。這樣,SQL語句經過解析後就可以動態的生成符合上下文的SQL語句。

還有,怎麼區分開佔位符變數和非佔位變數?有時候我們單單使用佔位符是滿足不了的,佔位符只能為查詢條件佔位,SQL語句其他地方使用不了。這裡我們可以使用#變數名#表示佔位符變數,使用變數名錶示非佔位符變數

3.4 第四步優化:結果對映和結果快取##

  1. 問題描述:

執行SQL語句、獲取執行結果、對執行結果進行轉換處理、釋放相關資源是一整套下來的。假如是執行查詢語句,那麼執行SQL語句後,返回的是一個ResultSet結果集,這個時候我們就需要將ResultSet物件的資料取出來,不然等到釋放資源時就取不到這些結果資訊了。我們從前面的優化來看,以及將獲取連線、設定傳入引數、執行SQL語句、釋放資源這些都封裝起來了,只剩下結果處理這塊還沒有進行封裝,如果能封裝起來,每個資料庫操作都不用自己寫那麼一大堆Java程式碼,直接呼叫一個封裝的方法就可以搞定了。

解決問題:

我們分析一下,一般對執行結果的有哪些處理,有可能將結果不做任何處理就直接返回,也有可能將結果轉換成一個JavaBean物件返回、一個Map返回、一個List返回等`,結果處理可能是多種多樣的。從這裡看,我們必須告訴SQL處理器兩點:第一,需要返回什麼型別的物件;第二,需要返回的物件的資料結構怎麼跟執行的結果對映,這樣才能將具體的值copy到對應的資料結構上。

接下來,我們可以進而考慮對SQL執行結果的快取來提升效能。快取資料都是key-value的格式,那麼這個key怎麼來呢?怎麼保證唯一呢?即使同一條SQL語句幾次訪問的過程中由於傳入引數的不同,得到的執行SQL語句也是不同的。那麼快取起來的時候是多對。但是SQL語句和傳入引數兩部分合起來可以作為資料快取的key值

3.5 第五步優化:解決重複SQL語句問題##

  1. 問題描述:

由於我們將所有SQL語句都放到配置檔案中,這個時候會遇到一個SQL重複的問題,幾個功能的SQL語句其實都差不多,有些可能是SELECT後面那段不同、有些可能是WHERE語句不同。有時候表結構改了,那麼我們就需要改多個地方,不利於維護。

解決問題:

當我們的程式碼程式出現重複程式碼時怎麼辦?將重複的程式碼抽離出來成為獨立的一個類,然後在各個需要使用的地方進行引用。對於SQL重複的問題,我們也可以採用這種方式,通過將SQL片段模組化,將重複的SQL片段獨立成一個SQL塊,然後在各個SQL語句引用重複的SQL塊,這樣需要修改時只需要修改一處即可。

4 Mybaits有待改進之處#

  1. 問題描述:

Mybaits所有的資料庫操作都是基於SQL語句,導致什麼樣的資料庫操作都要寫SQL語句。一個應用系統要寫的SQL語句實在太多了。

改進方法:

我們對資料庫進行的操作大部分都是對錶資料的增刪改查,很多都是對單表的資料進行操作,由這點我們可以想到一個問題:單表操作可不可以不寫SQL語句,通過JavaBean的預設對映器生成對應的SQL語句,比如:一個類UserInfo對應於USER_INFO表, userId屬性對應於USER_ID欄位。這樣我們就可以通過反射可以獲取到對應的表結構了,拼湊成對應的SQL語句顯然不是問題