1.引言
本文主要講解JDBC怎么演變到Mybatis的漸變過程,重點講解了為什么要將JDBC封裝成Mybaits這樣一個持久層框架。再而論述Mybatis作為一個數據持久層框架本身有待改進之處。
2.JDBC實現查詢分析
我們先看看我們最熟悉也是最基礎的通過JDBC查詢數據庫數據,一般需要以下七個步驟:
(1) 加載JDBC驅動
(2) 建立並獲取數據庫連接
(3) 創建 JDBC Statements 對象
(4) 設置SQL語句的傳入參數
(5) 執行SQL語句並獲得查詢結果
(6) 對查詢結果進行轉換處理並將處理結果返回
(7) 釋放相關資源(關閉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有七個步驟,哪些步驟是可以進一步封裝的,減少我們開發的代碼量。
第一步優化:連接獲取和釋放
問題描述:
數據庫連接頻繁的開啟和關閉本身就造成了資源的浪費,影響系統的性能。
解決問題:
數據庫連接的獲取和關閉我們可以使用數據庫連接池來解決資源浪費的問題。通過連接池就可以反復利用已經建立的連接去訪問數據庫了。減少連接的開啟和關閉的時間。
問題描述:
但是現在連接池多種多樣,可能存在變化,有可能采用DBCP的連接池,也有可能采用容器本身的JNDI數據庫連接池。
解決問題:
我們可以通過DataSource進行隔離解耦,我們統一從DataSource里面獲取數據庫連接,DataSource具體由DBCP實現還是由容器的JNDI實現都可以,所以我們將DataSource的具體實現通過讓用戶配置來應對變化。
第二步優化:SQL統一存取
問題描述:
我們使用JDBC進行操作數據庫時,SQL語句基本都散落在各個JAVA類中,這樣有三個不足之處:
第一,可讀性很差,不利於維護以及做性能調優。
第二,改動Java代碼需要重新編譯、打包部署。
第三,不利於取出SQL在數據庫客戶端執行(取出后還得刪掉中間的Java代碼,編寫好的SQL語句寫好后還得通過+號在Java進行拼湊)。
解決問題:
我們可以考慮不把SQL語句寫到Java代碼中,那么把SQL語句放到哪里呢?首先需要有一個統一存放的地方,我們可以將這些SQL語句統一集中放到配置文件或者數據庫里面(以key-value的格式存放)。然后通過SQL語句的key值去獲取對應的SQL語句。
既然我們將SQL語句都統一放在配置文件或者數據庫中,那么這里就涉及一個SQL語句的加載問題。
第三步優化:傳入參數映射和動態SQL
問題描述:
很多情況下,我們都可以通過在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語句其他地方使用不了。這里我們可以使用#變量名#表示占位符變量,使用$變量名$表示非占位符變量。
第四步優化:結果映射和結果緩存
問題描述:
執行SQL語句、獲取執行結果、對執行結果進行轉換處理、釋放相關資源是一整套下來的。假如是執行查詢語句,那么執行SQL語句后,返回的是一個ResultSet結果集,這個時候我們就需要將ResultSet對象的數據取出來,不然等到釋放資源時就取不到這些結果信息了。我們從前面的優化來看,以及將獲取連接、設置傳入參數、執行SQL語句、釋放資源這些都封裝起來了,只剩下結果處理這塊還沒有進行封裝,如果能封裝起來,每個數據庫操作都不用自己寫那么一大堆Java代碼,直接調用一個封裝的方法就可以搞定了。
解決問題:
我們分析一下,一般對執行結果的有哪些處理,有可能將結果不做任何處理就直接返回,也有可能將結果轉換成一個JavaBean對象返回、一個Map返回、一個List返回等等,結果處理可能是多種多樣的。從這里看,我們必須告訴SQL處理器兩點:第一,需要返回什么類型的對象;第二,需要返回的對象的數據結構怎么跟執行的結果映射,這樣才能將具體的值copy到對應的數據結構上。
接下來,我們可以進而考慮對SQL執行結果的緩存來提升性能。緩存數據都是key-value的格式,那么這個key怎么來呢?怎么保證唯一呢?即使同一條SQL語句幾次訪問的過程中由於傳入參數的不同,得到的執行SQL語句也是不同的。那么緩存起來的時候是多對。但是SQL語句和傳入參數兩部分合起來可以作為數據緩存的key值。
第五步優化:解決重復SQL語句問題
問題描述:
由於我們將所有SQL語句都放到配置文件中,這個時候會遇到一個SQL重復的問題,幾個功能的SQL語句其實都差不多,有些可能是SELECT后面那段不同、有些可能是WHERE語句不同。有時候表結構改了,那么我們就需要改多個地方,不利於維護。
解決問題:
當我們的代碼程序出現重復代碼時怎么辦?將重復的代碼抽離出來成為獨立的一個類,然后在各個需要使用的地方進行引用。對於SQL重復的問題,我們也可以采用這種方式,通過將SQL片段模塊化,將重復的SQL片段獨立成一個SQL塊,然后在各個SQL語句引用重復的SQL塊,這樣需要修改時只需要修改一處即可。
4. 優化總結:
我們總結一下上面對JDBC的優化和封裝:
(1) 使用數據庫連接池對連接進行管理
(2) SQL語句統一存放到配置文件
(3) SQL語句變量和傳入參數的映射以及動態SQL
(4) 動態SQL語句的處理
(5) 對數據庫操作結果的映射和結果緩存
(6) SQL語句的重復
5. Mybaits有待改進之處
問題描述:
Mybaits所有的數據庫操作都是基於SQL語句,導致什么樣的數據庫操作都要寫SQL語句。一個應用系統要寫的SQL語句實在太多了。
改進方法:
我們對數據庫進行的操作大部分都是對表數據的增刪改查,很多都是對單表的數據進行操作,由這點我們可以想到一個問題:單表操作可不可以不寫SQL語句,通過JavaBean的默認映射器生成對應的SQL語句,比如:一個類UserInfo對應於USER_INFO表, userId屬性對應於USER_ID字段。這樣我們就可以通過反射可以獲取到對應的表結構了,拼湊成對應的SQL語句顯然不是問題。