<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Terry.Li-彬

    虛其心,可解天下之問;專其心,可治天下之學;靜其心,可悟天下之理;恒其心,可成天下之業。

      BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
      143 隨筆 :: 344 文章 :: 130 評論 :: 0 Trackbacks

    擴展 iBatis 以透明支持多種數據庫

    developerWorks
    文檔選項

    未顯示需要 JavaScript 的文檔選項

    將打印機的版面設置成橫向打印模式

    打印本頁

    將此頁作為電子郵件發送

    將此頁作為電子郵件發送

    樣例代碼


    級別: 中級

    張 琦煒 (zhangqiw@cn.ibm.com), 軟件工程師, IBM 中國軟件開發中心

    2007 年 12 月 13 日

    iBatis 是一個開源的對象關系映射框架,著重于 POJO 與 SQL 之間的映射關系。和其它 ORM 框架不同,iBatis 開發者需要自己編寫和維護 SQL 語句。為了得到更好的執行性能,在實際開發中免不了會使用一些數據庫方言。隨之而來的一個問題是,如何在增加對新的數據庫支持的同時盡可能避免對已有應用 程序代碼的修改?本文提供了一個簡單有效的方法,通過擴展 iBatis 來透明地支持多數據庫方言。

    iBatis 簡介

    iBatis 是一個開源的對象關系映射程序,著重于 POJO 與 SQL 之間的映射關系。使用時,開發者提供一個被稱為 SQL 映射的 XML 文件,定義程序對象與 SQL 語句間的映射關系, iBatis 會根據 SQL 映射文件的定義,運行時自動完成 SQL 調用參數的綁定以及 JDBC ResultSet 到 Java POJO 之間的轉換。下面是一個簡單的例子,相比其它 ORM 工具,iBatis 相對簡單,更容易上手。


    清單 1. POJO 對象
                    
    public class BlogData implements Serializable {
    protected String id;
    protected String name;
    protected int rating = 0;
    public String getId() {
    return id;
    }
    public String getName() {
    return name;
    }
    public int getRating() {
    return rating;
    }
    public void setId(String id) {
    this.id = id;
    }
    public void setName(String name) {
    this.name = name;
    }
    public void setRating(int rating) {
    this.rating = rating;
    }
    }


    清單 2. SQL 映射文件—— SQLMAP.XML
                    
    <sqlMap namespace="blog">
    <resultMap id="blog" class="BlogData">
    <result property="id" column="id" />
    <result property="name" column="name" />
    <result property="rating" column="rating" />
    </resultMap>
    <insert id="SAVEBLOG" parameterClass="BlogData">
    insert into blogs(id, name, rating) values(#id#, #name#, #rating#)
    </insert>
    <update id="UPDATEBLOG" parameterClass="BlogData">
    update blogs set name = #name#, rating = #rating# where id = #id#
    </update>
    <delete id="REMOVEBLOG" parameterClass="string">
    delete from blogs where id = #id#
    </delete>
    <select id="GETMOSTPOPULARBLOG" parameterClass="map" resultMap="blog">
    select * from blogs order by rating desc fetch first $size$ rows only
    </select>
    </sqlMap>


    清單 3. iBatis 配置文件—— SQLMAPCONFIG.XML
                    
    <sqlMapConfig>
    <settings useStatementNamespaces="true" />
    <transactionManager type="JDBC" commitRequired="true">
    <dataSource type="JNDI">
    <property name="DataSource" value="java:comp/env/jdbc/db" />
    </dataSource>
    </transactionManager>
    //SQL 映射聲明
    <sqlMap resource="SQLMAP.XML" />
    </sqlMapConfig>


    清單 4. SQL 訪問示例
                    
    String cfg = "SQLMAPCONFIG.XML";
    Reader r = Resources.getResourceAsReader(cfg);
    SqlMapClient client = new SqlMapConfigParser().parse(r);
    ...
    // 插入
    BlogData o = new BlogData();
    o.setId("id");
    o.setName("test");
    client.insert("SAVEBLOG", o);
    ...
    // 更新
    o.setRating(10);
    client.update("UPDATEBLOG", o);
    ...
    // 刪除
    client.delete("REMOVEBLOG", "id");
    ...
    // 查詢
    Map params = new HashMap();
    params.put(size, 5);
    List l = client. queryForList("GETMOSTPOPULARBLOG", params, 0, 5);
    ...





    回頁首


    iBatis 應用中的多數據庫支持

    在 iBatis 應用中,開發者仍需自己編寫具體的 SQL 語句,iBatis 只是隱藏和簡化了 JDBC 的相關調用。

    實際開發中,我們不免需要就 SQL 語句針對各種特定的數據庫進行特殊優化,以期獲取更好的執行性能,隨之而來的一個問題是,如何應對新的數據庫平臺支持的需求。

    一般的做法是,修改 SQL 映射文件,提供一些新的針對新數據庫平臺的 SQL 語句版本,然后修改程序代碼,添加相應調用。繼續上面的例子。上面的例子中,對于 SQL 語句 GETMOSTPOPULARBLOG 的定義,我們使用了 DB2 特有的 SQL 方言“FETCH FIRST n ROWS ONLY”,對于這樣的程序,如果希望增加對 MYSQL 的支持,按照一般的做法,需要:

    1.修改 SQLMAP.XML,增加語句定義“GETMOSTPOPULARBLOG_MYSQL”。


    清單 5. 增加語句定義
                    
    ......
    <select id="GETMOSTPOPULARBLOG" parameterClass="map" resultMap="blog">
    select * from blogs order by rating desc fetch first $size$ rows only
    </select>
    <select id=" GETMOSTPOPULARBLOG_MYSQL" parameterClass="map" resultMap="blog">
    select * from blogs order by rating desc limit 0, $size$
    </select>
    ......

    2.搜索程序代碼,在每一個調用 iBatis “GETMOSTPOPULARBLOG”的地方,增加檢測 MYSQL 數據庫引擎的代碼,并添加對“GETMOSTPOPULARBLOG_MYSQL”的 iBatis 調用。


    清單 6. 增加檢測數據庫引擎的代碼
                    
    ......
    SqlMapClient client = ...;
    ......
    // 查詢
    Map params = new HashMap();
    params.put(size, 5);
    List l = null;
    Connection conn = client.getCurrentConnection();
    String prodName = conn.getMetaData().getDatabaseProductName().toLowerCase();
    if (prodName.indexOf("mysql") > - 1) {
    //Microsoft SQL Server
    l = client. queryForList("GETMOSTPOPULARBLOG_MYSQL", params);
    } else {
    l = client. queryForList("GETMOSTPOPULARBLOG", params);
    }

    ......

    每增加一個新的數據庫支持,增加了一些新 的針對新數據庫平臺的 SQL 語句版本,我們就不得不搜索源代碼,找出所有受到影響的 iBatis 調用,修改并增加針對新數據庫的特殊調用。代碼維護時,每次涉及使用數據庫方言的 SQL 語句,我們也都必須記住小心謹慎地處理所有相關的數據庫特化調用。這樣的工作乏味且容易出錯。

    本文,我們試圖在分析 iBatis 源碼的基礎上,通過適當擴展 iBatis,提供一個高效方便的解決方案。





    回頁首


    擴展 SqlMapConfigParser

    在 iBatis 應用中,SqlMapConfigParser 負責解析 iBatis 配置文件,加載所有的 SQL 映射文件,生成 SqlMapClient 實例,這是持久化調用的入口。

    SqlMapConfigParser 的實現并不復雜。成員函數 parser 將傳入的配置文件 XML 輸入流交給一個 XML 解析器。XML 解析器解析 XML 輸入,并針對每一個 XML Fragment 調用合適的處理器處理。所有的處理器都在 SqlMapConfigParser 類實例初始化時預先被注冊到 XML 解析器上,其中,對于 iBatis 配置中的 SQL 映射聲明,只是簡單地調用類 SqlMapParser 中的 parser 方法,解析并加載相應的 SQL 映射定義文件。


    清單 7. SqlMapConfigParser 實現
                    
    public class SqlMapConfigParser {
    //XML 解析器
    protected final NodeletParser parser = new NodeletParser();

    public SqlMapConfigParser() {
    ......

    // 注冊 XML 處理器
    addSqlMapNodelets();
    ...... // more
    }

    public SqlMapClient parse(Reader reader) {
    ......
    // 調用 XML 解析器解析傳入的配置文件 XML 輸入流
    parser.parse(reader);
    return vars.client;
    }

    protected void addSqlMapNodelets() {
    //XML 處理器,處理 XPath:"/sqlMapConfig/sqlMap",即 SQL 映射聲明
    parser.addNodelet("/sqlMapConfig/sqlMap", new Nodelet() {
    public void process(Node node)throws Exception {
    Properties attributes = NodeletUtils.parseAttributes(node);
    String resource = attributes.getProperty("resource");
    Reader reader = Resources.getResourceAsReader(resource);
    new SqlMapParser(vars).parse(reader); // 調用 SqlMapParser.parser 方法
    // 解析并加載 SQL 映射文件
    ......
    }
    }
    );
    }
    ......
    }

    我們繼承 iBatis 原有的配置文件解析器實現 SqlMapConfigParser,重寫其中對 SQL 映射聲明的處理。

    首先,我們重寫 SqlMapConfigParser 的成員函數 addSqlMapNodelets。對于從 XML 解析器傳入的 SQL 映射聲明節點,我們并不立即進行解析處理,而只是將它們記錄下來。


    清單 8. 重寫 addSqlMapNodelets 方法
                    
    public class SqlMapConfigParserEx extends SqlMapConfigParser {
    List sqlMapNodeList = new ArrayList();
    .......
    protected void addSqlMapNodelets() {
    //XML 處理器,處理 XPath:”/sqlMapConfig/sqlMap”,即 SQL 映射聲明
    parser.addNodelet("/sqlMapConfig/sqlMap", new Nodelet() {
    public void process(Node node)throws Exception {
    sqlMapNodeList.addNode(node);
    }
    }
    );
    }
    ......
    }

    這些 SQL 映射聲明被放到最后處理,此時 SqlMapClient 實例已經基本構造完畢,至少,我們可以安全地調用它的相關方法,打開數據庫連接,查詢數據庫引擎相關信息。對于每個 SQL 映射聲明,SqlMapConfigParserEx 調用其成員函數方法 handleSqlMapNode 進行相應的 SQL 映射文件解析和加載處理,數據庫引擎支持的 SQL 方言版本信息作為參數被一并傳入。


    清單 9. 重寫 parse 方法
                    
    public interface DialectMapping {
    public String getDialect(String productName);
    // 返回數據庫平臺支持的 SQL 方言信息
    }

    public class SqlMapConfigParserEx extends SqlMapConfigParser {
    List sqlMapNodeList = new ArrayList();
    DialectMapping dialectMapping = ...;
    .......
    public SqlMapClient parse(Reader reader) {
    ......
    super.parse(reader);
    String sqlDialect = null;
    SqlMapClient client = vars.client;
    Connection conn = client.getDataSource().getConnection();
    DatabaseMetaData dbMetaData = conn.getMetaData();
    String productName = dbMetaData.getDatabaseProductName();
    sqlDialect = dialectMapping.getDialect(productName);
    conn.close();

    for (Iterator iter = sqlMapNodeList.iterator(); iter.hasNext();) {
    handleSqlMapNode((Node)iter.next(), sqlDialect);
    }

    return client;
    }
    ......
    }

    對于傳入的 SQL 映射聲明,除了解析并加載 SQL 映射聲明中指定的 SQL 映射文件,handleSqlMapNode 還根據傳入的 SQL 方言版本信息,以一定的路徑規則,尋找針對該 SQL 方言的 SQL 映射文件定制版本,將它們一并解析加載。


    清單 10. handleSqlMapNode 方法
                    
    public interface SqlMapStreamMerger {
    public void addInput(InputStream input);
    public InputStream merge();
    ......
    }

    public class SqlMapConfigParserEx extends SqlMapConfigParser {
    SqlMapStreamMerger sqlMapStreamMerger = ...;
    .......

    public void handleSqlMapNode(Node node, String sqlDialect) {
    Properties attributes = NodeletUtils.parseAttributes(node);
    String resource = attributes.getProperty("resource");
    // 讀取 SQL 映射聲明指定的 SQL 映射文件
    InputStream is = Resources.getResourceAsStream(resource);
    sqlMapStreamMerger.addInput(is);
    // 尋找并試圖讀取針對 SQL 方言 sqlDialect 的 SQL 映射文件定制版本
    int idx = resource.lastIndexOf('/');
    resource = resource.substring(0, idx) + "/" + sqlDialect +
    resource.substring(idx);
    is = Resources.getResourceAsStream(resource);
    if (is != null) {
    sqlMapStreamMerger.addInput(is);
    }
    // 將讀取到的 SQL 映射文件,包括基本的 SQL 映射文件,以及為特定數據庫的定制版本,
    // 合成一個 SQL 映射文件 XML 數據流交給 SqlMapParser 解析處理
    Reader reader = new InputStreamReader(sqlMapStreamMerger.merge());
    new SqlMapParser(vars).parse(reader);
    }

    ......
    }

    成員函數 handleSqlMapNode 將找到的 SQL 映射文件,包括 SQL 映射聲明中指定的基本的 SQL 映射文件,以及以一定路徑規則找到的針對特定數據庫的 SQL 映射文件定制版本,通過 SqlMapStreamMerge 對象整合成一個 SQL 映射文件,才遞交給 SqlMapParser 解析處理。SqlMapStreamMerge 確保:

    1. 結 果 SQL 映射文件中不存在重復 ID 的 SQL 映射配置塊(statement、insert、update、delete、sql、resultMap 等)。如果基本的 SQL 映射文件與針對特定數據庫的 SQL 映射文件定制版本之間存在重復 ID 的 SQL 映射配置塊定義,SqlMapStreamMerge 保留后者覆蓋前者;
    2. 結果 SQL 映射文件中的配置塊按引用依賴順序有序排列。即所有的 resultMap 聲明都位于引用它們的 statement 聲明之前,被繼承的 resultMap 聲明都位于繼承的 resultMap 聲明之前等。

    先 Merge 再解析,這是必要的,因為 SqlMapParser 本身并不支持 SQL 映射定義的方法重寫。





    回頁首


    使用

    使用擴展的 SqlMapConfigParser 實現 —— SqlMapConfigParserEx,可以大大簡化應用程序中多數據庫支持問題的解決。

    還是之前那個例子。

    首先,我們使用 SqlMapConfigParserEx 替換程序中的 SqlMapConfigParser 使用。


    清單 11. 在應用代碼中使用擴展的 SqlMapConfigParserEx
                    
    String cfg = "SQLMAPCONFIG.XML";
    Reader r = Resources.getResourceAsReader(cfg);

    // old code
    // SqlMapClient client = new SqlMapConfigParser().parse(r);

    // new code
    SqlMapClient client = new SqlMapConfigParserEx().parse(r);
    ...

    現在,要增加對 MySQL 的支持,只需建立一個新的 SQL 映射文件 /mysql/SQLMAP.XML,重寫 SQLMAP.XML 中 GETMOSTPOPULARBLOG 的 SQL 定義。Java 代碼可以繼續保持數據庫平臺透明性,無需作出任何修改。


    清單 12. 針對 MySQL 的配置文件 /mysql/SQLMAP.XML
                    
    <!-- /mysql/SQLMAP.XML -->
    <sqlMap namespace="blog">
    <select id="GETMOSTPOPULARBLOG" parameterClass="map" resultMap="blog">
    select * from blogs order by rating desc limit 0, $size$
    </select>
    </sqlMap>

    運行 時,SqlMapConfigParserEx 會自動檢測數據庫引擎版本信息,讀取文件 /mysql/SQLMAP.XML,使用其中的(針對 MYSQL 定制的)GETMOSTPOPULARBLOG 定義覆蓋 SQLMAP.XML 中的 DB2 方言版本,從而確保程序行為的正確性。

    我們支持,針對新的數據庫平臺,對 SQL 映射文件中的任意配置進行定制 / 重寫,甚至包括 <parameterMap>、<resultMap>、<cacheModel> 等,盡管在實際應用中,這樣的需求并不常見。





    回頁首


    關于 iBatis 2.1.5

    上 述分析只適合 iBatis 2.2.0 之后的版本。在 iBatis 2.1.5 中,addSqlMapNodelets 是 SqlMapConfigParser 的私有成員函數,無法在子類中重寫。附件中,我們給出了針對 iBatis 2.1.5 的 SqlMapConfigParserEx 實現,大致思想類似,這里就不再詳述。





    回頁首


    結束語

    iBatis 作為一個 ORM 框架,以其簡單易用,支持更為靈活的數據庫 / 系統設計,正在得到越來越多的關注。iBatis 應用中,開發者需要自己編寫具體的 SQL 語句,針對特定的數據庫進行 SQL 優化,處理跨數據庫平臺移植問題等。本文,針對 iBatis 應用中的多數據庫支持問題,通過擴展 iBatis 的現有實現,給出了一個較為簡單高效的解決方法。






    回頁首


    下載

    描述名字大小下載方法
    本文代碼下載(for iBatis 2.1.5) code_for_ibatis215.zip 14 KB HTTP
    本文代碼下載(for iBatis 2.2.0) code_for_ibatis220.zip 12 KB HTTP
    posted on 2008-12-08 11:59 禮物 閱讀(969) 評論(0)  編輯  收藏 所屬分類: ibatis
    主站蜘蛛池模板: 免费观看无遮挡www的小视频| 在线观看片免费人成视频播放| 污网站在线免费观看| 一级人做人爰a全过程免费视频| 91福利免费网站在线观看| 8x8×在线永久免费视频| 成人免费午夜无码视频| 又黄又爽的视频免费看| 亚洲av伊人久久综合密臀性色| 亚洲国产品综合人成综合网站| 亚洲国产成人久久精品大牛影视| yy一级毛片免费视频| 91青青青国产在观免费影视 | 国产精品亚洲а∨天堂2021| 好吊色永久免费视频大全| 最近的中文字幕大全免费8| a级毛片无码免费真人| 亚洲成A∨人片天堂网无码| 亚洲人成网站在线播放影院在线| 中国china体内裑精亚洲日本| 色爽黄1000部免费软件下载| 久久免费的精品国产V∧| 国产精品自在自线免费观看| 亚洲av永久无码精品网站| 亚洲人成网站在线在线观看| 两个人看的www免费视频中文| 97免费人妻无码视频| 亚洲一区精品伊人久久伊人| 亚洲日本国产精华液| 一级特黄a免费大片| 国产a视频精品免费观看| 亚洲精品人成无码中文毛片 | 免费人成再在线观看网站 | 国产成人久久AV免费| 精品剧情v国产在免费线观看| 精品国产综合成人亚洲区 | 亚洲av乱码一区二区三区| 九九久久国产精品免费热6| 国产啪精品视频网免费| 亚洲最大AV网站在线观看| 亚洲日韩精品国产一区二区三区|