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

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

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

    Terry.Li-彬

    虛其心,可解天下之問;專其心,可治天下之學(xué);靜其心,可悟天下之理;恒其心,可成天下之業(yè)。

      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      143 隨筆 :: 344 文章 :: 130 評(píng)論 :: 0 Trackbacks

    擴(kuò)展 iBatis 以透明支持多種數(shù)據(jù)庫(kù)

    developerWorks
    文檔選項(xiàng)

    未顯示需要 JavaScript 的文檔選項(xiàng)

    將打印機(jī)的版面設(shè)置成橫向打印模式

    打印本頁

    將此頁作為電子郵件發(fā)送

    將此頁作為電子郵件發(fā)送

    樣例代碼


    級(jí)別: 中級(jí)

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

    2007 年 12 月 13 日

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

    iBatis 簡(jiǎn)介

    iBatis 是一個(gè)開源的對(duì)象關(guān)系映射程序,著重于 POJO 與 SQL 之間的映射關(guān)系。使用時(shí),開發(fā)者提供一個(gè)被稱為 SQL 映射的 XML 文件,定義程序?qū)ο笈c SQL 語句間的映射關(guān)系, iBatis 會(huì)根據(jù) SQL 映射文件的定義,運(yùn)行時(shí)自動(dòng)完成 SQL 調(diào)用參數(shù)的綁定以及 JDBC ResultSet 到 Java POJO 之間的轉(zhuǎn)換。下面是一個(gè)簡(jiǎn)單的例子,相比其它 ORM 工具,iBatis 相對(duì)簡(jiǎn)單,更容易上手。


    清單 1. POJO 對(duì)象
                    
    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 應(yīng)用中的多數(shù)據(jù)庫(kù)支持

    在 iBatis 應(yīng)用中,開發(fā)者仍需自己編寫具體的 SQL 語句,iBatis 只是隱藏和簡(jiǎn)化了 JDBC 的相關(guān)調(diào)用。

    實(shí)際開發(fā)中,我們不免需要就 SQL 語句針對(duì)各種特定的數(shù)據(jù)庫(kù)進(jìn)行特殊優(yōu)化,以期獲取更好的執(zhí)行性能,隨之而來的一個(gè)問題是,如何應(yīng)對(duì)新的數(shù)據(jù)庫(kù)平臺(tái)支持的需求。

    一般的做法是,修改 SQL 映射文件,提供一些新的針對(duì)新數(shù)據(jù)庫(kù)平臺(tái)的 SQL 語句版本,然后修改程序代碼,添加相應(yīng)調(diào)用。繼續(xù)上面的例子。上面的例子中,對(duì)于 SQL 語句 GETMOSTPOPULARBLOG 的定義,我們使用了 DB2 特有的 SQL 方言“FETCH FIRST n ROWS ONLY”,對(duì)于這樣的程序,如果希望增加對(duì) 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.搜索程序代碼,在每一個(gè)調(diào)用 iBatis “GETMOSTPOPULARBLOG”的地方,增加檢測(cè) MYSQL 數(shù)據(jù)庫(kù)引擎的代碼,并添加對(duì)“GETMOSTPOPULARBLOG_MYSQL”的 iBatis 調(diào)用。


    清單 6. 增加檢測(cè)數(shù)據(jù)庫(kù)引擎的代碼
                    
    ......
    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);
    }

    ......

    每增加一個(gè)新的數(shù)據(jù)庫(kù)支持,增加了一些新 的針對(duì)新數(shù)據(jù)庫(kù)平臺(tái)的 SQL 語句版本,我們就不得不搜索源代碼,找出所有受到影響的 iBatis 調(diào)用,修改并增加針對(duì)新數(shù)據(jù)庫(kù)的特殊調(diào)用。代碼維護(hù)時(shí),每次涉及使用數(shù)據(jù)庫(kù)方言的 SQL 語句,我們也都必須記住小心謹(jǐn)慎地處理所有相關(guān)的數(shù)據(jù)庫(kù)特化調(diào)用。這樣的工作乏味且容易出錯(cuò)。

    本文,我們?cè)噲D在分析 iBatis 源碼的基礎(chǔ)上,通過適當(dāng)擴(kuò)展 iBatis,提供一個(gè)高效方便的解決方案。





    回頁首


    擴(kuò)展 SqlMapConfigParser

    在 iBatis 應(yīng)用中,SqlMapConfigParser 負(fù)責(zé)解析 iBatis 配置文件,加載所有的 SQL 映射文件,生成 SqlMapClient 實(shí)例,這是持久化調(diào)用的入口。

    SqlMapConfigParser 的實(shí)現(xiàn)并不復(fù)雜。成員函數(shù) parser 將傳入的配置文件 XML 輸入流交給一個(gè) XML 解析器。XML 解析器解析 XML 輸入,并針對(duì)每一個(gè) XML Fragment 調(diào)用合適的處理器處理。所有的處理器都在 SqlMapConfigParser 類實(shí)例初始化時(shí)預(yù)先被注冊(cè)到 XML 解析器上,其中,對(duì)于 iBatis 配置中的 SQL 映射聲明,只是簡(jiǎn)單地調(diào)用類 SqlMapParser 中的 parser 方法,解析并加載相應(yīng)的 SQL 映射定義文件。


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

    public SqlMapConfigParser() {
    ......

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

    public SqlMapClient parse(Reader reader) {
    ......
    // 調(diào)用 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); // 調(diào)用 SqlMapParser.parser 方法
    // 解析并加載 SQL 映射文件
    ......
    }
    }
    );
    }
    ......
    }

    我們繼承 iBatis 原有的配置文件解析器實(shí)現(xiàn) SqlMapConfigParser,重寫其中對(duì) SQL 映射聲明的處理。

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


    清單 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 映射聲明被放到最后處理,此時(shí) SqlMapClient 實(shí)例已經(jīng)基本構(gòu)造完畢,至少,我們可以安全地調(diào)用它的相關(guān)方法,打開數(shù)據(jù)庫(kù)連接,查詢數(shù)據(jù)庫(kù)引擎相關(guān)信息。對(duì)于每個(gè) SQL 映射聲明,SqlMapConfigParserEx 調(diào)用其成員函數(shù)方法 handleSqlMapNode 進(jìn)行相應(yīng)的 SQL 映射文件解析和加載處理,數(shù)據(jù)庫(kù)引擎支持的 SQL 方言版本信息作為參數(shù)被一并傳入。


    清單 9. 重寫 parse 方法
                    
    public interface DialectMapping {
    public String getDialect(String productName);
    // 返回?cái)?shù)據(jù)庫(kù)平臺(tái)支持的 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;
    }
    ......
    }

    對(duì)于傳入的 SQL 映射聲明,除了解析并加載 SQL 映射聲明中指定的 SQL 映射文件,handleSqlMapNode 還根據(jù)傳入的 SQL 方言版本信息,以一定的路徑規(guī)則,尋找針對(duì)該 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);
    // 尋找并試圖讀取針對(duì) 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 映射文件,以及為特定數(shù)據(jù)庫(kù)的定制版本,
    // 合成一個(gè) SQL 映射文件 XML 數(shù)據(jù)流交給 SqlMapParser 解析處理
    Reader reader = new InputStreamReader(sqlMapStreamMerger.merge());
    new SqlMapParser(vars).parse(reader);
    }

    ......
    }

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

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

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





    回頁首


    使用

    使用擴(kuò)展的 SqlMapConfigParser 實(shí)現(xiàn) —— SqlMapConfigParserEx,可以大大簡(jiǎn)化應(yīng)用程序中多數(shù)據(jù)庫(kù)支持問題的解決。

    還是之前那個(gè)例子。

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


    清單 11. 在應(yīng)用代碼中使用擴(kuò)展的 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);
    ...

    現(xiàn)在,要增加對(duì) MySQL 的支持,只需建立一個(gè)新的 SQL 映射文件 /mysql/SQLMAP.XML,重寫 SQLMAP.XML 中 GETMOSTPOPULARBLOG 的 SQL 定義。Java 代碼可以繼續(xù)保持?jǐn)?shù)據(jù)庫(kù)平臺(tái)透明性,無需作出任何修改。


    清單 12. 針對(duì) 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>

    運(yùn)行 時(shí),SqlMapConfigParserEx 會(huì)自動(dòng)檢測(cè)數(shù)據(jù)庫(kù)引擎版本信息,讀取文件 /mysql/SQLMAP.XML,使用其中的(針對(duì) MYSQL 定制的)GETMOSTPOPULARBLOG 定義覆蓋 SQLMAP.XML 中的 DB2 方言版本,從而確保程序行為的正確性。

    我們支持,針對(duì)新的數(shù)據(jù)庫(kù)平臺(tái),對(duì) SQL 映射文件中的任意配置進(jìn)行定制 / 重寫,甚至包括 <parameterMap>、<resultMap>、<cacheModel> 等,盡管在實(shí)際應(yīng)用中,這樣的需求并不常見。





    回頁首


    關(guān)于 iBatis 2.1.5

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





    回頁首


    結(jié)束語

    iBatis 作為一個(gè) ORM 框架,以其簡(jiǎn)單易用,支持更為靈活的數(shù)據(jù)庫(kù) / 系統(tǒng)設(shè)計(jì),正在得到越來越多的關(guān)注。iBatis 應(yīng)用中,開發(fā)者需要自己編寫具體的 SQL 語句,針對(duì)特定的數(shù)據(jù)庫(kù)進(jìn)行 SQL 優(yōu)化,處理跨數(shù)據(jù)庫(kù)平臺(tái)移植問題等。本文,針對(duì) iBatis 應(yīng)用中的多數(shù)據(jù)庫(kù)支持問題,通過擴(kuò)展 iBatis 的現(xiàn)有實(shí)現(xiàn),給出了一個(gè)較為簡(jiǎn)單高效的解決方法。






    回頁首


    下載

    描述名字大小下載方法
    本文代碼下載(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 禮物 閱讀(970) 評(píng)論(0)  編輯  收藏 所屬分類: ibatis
    主站蜘蛛池模板: 亚洲日韩精品无码AV海量| 岛国精品一区免费视频在线观看| 久久亚洲精品成人综合| 亚洲AV无码一区东京热| 亚洲国产精品无码久久九九大片| caoporm超免费公开视频| 免费真实播放国产乱子伦| 香蕉大伊亚洲人在线观看| 18国产精品白浆在线观看免费| 西西人体44rt高清亚洲 | 亚洲精品美女网站| 免费无码又爽又刺激聊天APP| 中文日韩亚洲欧美制服| 无码日韩人妻av一区免费| 亚洲精品国产精品国自产观看 | 亚洲无线码一区二区三区| 成全视频高清免费观看电视剧| 亚洲乱码中文字幕综合| 国产午夜精品久久久久免费视| 亚洲精品自产拍在线观看动漫| 男女免费观看在线爽爽爽视频| 亚洲国产精品日韩av不卡在线| 777成影片免费观看| 一级毛片免费视频网站| 免费在线观看自拍性爱视频| 国产午夜不卡AV免费| 亚洲综合激情九月婷婷| 光棍天堂免费手机观看在线观看| 久久久亚洲精品国产| 5555在线播放免费播放| 国产精品亚洲mnbav网站| 无码精品一区二区三区免费视频 | 97视频免费观看2区| 亚洲ts人妖网站| 日韩精品视频免费观看| 亚洲另类古典武侠| 又大又黄又粗又爽的免费视频| 午夜无码A级毛片免费视频| 亚洲人成影院77777| 久久WWW免费人成一看片| 亚洲av日韩av不卡在线观看|