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 第一步最佳化:連接獲取和釋放問題描述:資料庫連線頻繁的開啟和關閉本身就造成了 資源的浪費,影響系統的效能 。
解決問題:資料庫連線的獲取和關閉我們 可以使用資料庫連線池來解決資源浪費的問題 。透過連線池就可以反覆利用已經建立的連線去訪問資料庫了。減少連線的開啟和關閉的時間。
問題描述:但是現在 連線池多種多樣,可能存在變化 ,有可能採用DBCP的連線池,也有可能採用容器本身的JNDI資料庫連線池。
解決問題:我們可以 透過DataSource進行隔離解耦 ,我們統一從DataSource裡面獲取資料庫連線, DataSource具體由DBCP實現還是由容器的JNDI實現都可以 ,所以我們將DataSource的具體實現透過讓使用者配置來應對變化。
3.2 第二步最佳化:SQL統一存取問題描述:我們使用JDBC進行操作資料庫時, SQL語句基本都散落在各個JAVA類中 ,這樣有三個不足之處:
第一,可讀性很差,不利於維護以及做效能調優。
第二,改動Java程式碼需要重新編譯、打包部署。
第三,不利於取出SQL在資料庫客戶端執行(取出後還得刪掉中間的Java程式碼,編寫好的SQL語句寫好後還得透過+號在Java進行拼湊)。
解決問題:我們可以考慮不把SQL語句寫到Java程式碼中,那麼把SQL語句放到哪裡呢?首先需要有一個統一存放的地方,我們可以將這些 SQL語句統一集中放到配置檔案或者資料庫裡面(以key-value的格式存放) 。然後透過SQL語句的key值去獲取對應的SQL語句。
既然我們將SQL語句都統一放在配置檔案或者資料庫中, 那麼這裡就涉及一個SQL語句的載入問題 。
3.3 第三步最佳化:傳入引數對映和動態SQL問題描述:很多情況下,我們都可以透過在SQL語句中設定佔位符來達到使用傳入引數的目的,這種方式本身就有一定侷限性,它是按照一定順序傳入引數的,要與佔位符一一匹配。但是,如果我們 傳入的引數是不確定的 (比如列表查詢,根據使用者填寫的查詢條件不同,傳入查詢的引數也是不同的,有時是一個引數、有時可能是三個引數),那麼我們就得 在後臺程式碼中自己根據請求的傳入引數去拼湊相應的SQL語句 ,這樣的話還是 避免不了在Java程式碼裡面寫SQL語句的命運 。既然我們已經把SQL語句統一存放在配置檔案或者資料庫中了, 怎麼做到能夠根據前臺傳入引數的不同,動態生成對應的SQL語句呢?
解決問題:第一,我們先解決這個動態問題, 按照我們正常的程式設計師思維是,透過if和else這類的判斷來進行是最直觀的 ,這個時候我們想到了JSTL中的這樣的標籤,那麼,能不能將這類的標籤引入到SQL語句中呢?假設可以,那麼我們這裡就需要一個專門的SQL解析器來解析這樣的SQL語句,但是,if判斷的變數來自於哪裡呢?傳入的值本身是可變的,那麼我們得為這個值定義一個不變的變數名稱,而且這個變數名稱必須和對應的值要有對應關係,可以透過這個變數名稱找到對應的值,這個時候我們想到了key-value的Map。解析的時候根據變數名的具體值來判斷。
假如前面可以判斷沒有問題,那麼假如判斷的結果是true,那麼就需要輸出的標籤裡面的SQL片段,但是怎麼解決在標籤裡面使用變數名稱的問題呢?這裡我們需要 使用一種有別於SQL的語法來嵌入變數(比如使用#變數名#) 。這樣,SQL語句經過解析後就可以動態的生成符合上下文的SQL語句。
還有,怎麼區分開佔位符變數和非佔位變數?有時候我們單單使用佔位符是滿足不了的,佔位符只能為查詢條件佔位,SQL語句其他地方使用不了。 這裡我們可以使用#變數名#表示佔位符變數,使用變數名錶示非佔位符變數 。
3.4 第四步最佳化:結果對映和結果快取問題描述:執行SQL語句、獲取執行結果、對執行結果進行轉換處理、釋放相關資源是一整套下來的。假如是執行查詢語句,那麼執行SQL語句後,返回的是一個ResultSet結果集, 這個時候我們就需要將ResultSet物件的資料取出來,不然等到釋放資源時就取不到這些結果資訊了 。我們從前面的最佳化來看,以及將獲取連線、設定傳入引數、執行SQL語句、釋放資源這些都封裝起來了,只剩下結果處理這塊還沒有進行封裝,如果能封裝起來,每個資料庫操作都不用自己寫那麼一大堆Java程式碼,直接呼叫一個封裝的方法就可以搞定了。
解決問題:我們分析一下,一般對執行結果的有哪些處理, 有可能將結果不做任何處理就直接返回,也有可能將結果轉換成一個JavaBean物件返回、一個Map返回、一個List返回等 `,結果處理可能是多種多樣的。從這裡看,我們必須告訴SQL處理器兩點: 第一,需要返回什麼型別的物件;第二,需要返回的物件的資料結構怎麼跟執行的結果對映 ,這樣才能將具體的值copy到對應的資料結構上。
接下來, 我們可以進而考慮對SQL執行結果的快取來提升效能 。快取資料都是key-value的格式,那麼這個key怎麼來呢?怎麼保證唯一呢?即使同一條SQL語句幾次訪問的過程中由於傳入引數的不同,得到的執行SQL語句也是不同的。那麼快取起來的時候是多對。 但是SQL語句和傳入引數兩部分合起來可以作為資料快取的key值 。
3.5 第五步最佳化:解決重複SQL語句問題問題描述:由於我們將所有SQL語句都放到配置檔案中, 這個時候會遇到一個SQL重複的問題 ,幾個功能的SQL語句其實都差不多,有些可能是SELECT後面那段不同、有些可能是WHERE語句不同。有時候表結構改了,那麼我們就需要改多個地方,不利於維護。
解決問題:當我們的程式碼程式出現重複程式碼時怎麼辦? 將重複的程式碼抽離出來成為獨立的一個類,然後在各個需要使用的地方進行引用 。對於SQL重複的問題,我們也可以採用這種方式,透過將SQL片段模組化, 將重複的SQL片段獨立成一個SQL塊,然後在各個SQL語句引用重複的SQL塊 ,這樣需要修改時只需要修改一處即可。
4 Mybaits有待改進之處問題描述:Mybaits所有的資料庫操作都是基於SQL語句, 導致什麼樣的資料庫操作都要寫SQL語句 。一個應用系統要寫的SQL語句實在太多了。
改進方法:我們對資料庫進行的操作大部分都是對錶資料的增刪改查,很多都是對單表的資料進行操作,由這點我們可以想到一個問題: 單表操作可不可以不寫SQL語句,透過JavaBean的預設對映器生成對應的SQL語句 ,比如:一個類UserInfo對應於USER_INFO表, userId屬性對應於USER_ID欄位。 這樣我們就可以透過反射可以獲取到對應的表結構了,拼湊成對應的SQL語句顯然不是問題 。