Java數據庫連接-集成第三方ORM框架-iBatis – JAVA編程語言程序開發技術文章

9. 集成第三方ORM框架 – iBatis
總體來說 iBATIS 的系統結構還是比較簡單的,它主要完成兩件事情:
<!–[if !supportLists]–>_        <!–[endif]–>根據 JDBC 規范建立與數據庫的連接;
<!–[if !supportLists]–>_        <!–[endif]–>通過反射打通 Java 對象與數據庫參數交互之間相互轉化關系。
 
 
以下內容摘自: 百度百科
詞條:iBatis
 
一站式
  iBATIS提供的持久層框架包括SQL Maps和Data Access Objects(DAO),同時還提供一個利用這個框架開發的JPetStore實例。
  相對Hibernate和Apache OJB等“一站式”ORM解決方案而言,ibatis 是一種“半自動化”的ORM實現。
 
縱觀目前主流
  所謂“半自動”,可能理解上有點生澀。縱觀目前主流的 ORM,無論 Hibernate 還是Apache OJB,都對數據庫結構提供瞭較為完整的封裝,提供瞭從POJO 到數據庫表的全套映射機制。程序員往往隻需定義好瞭POJO 到數據庫表的映射關系,即可通過 Hibernate或者OJB 提供的方法完成持久層操作。程序員甚至不需要對 SQL 的熟練掌握,Hibernate/OJB 會根據制定的存儲邏輯,自動生成對應的 SQL 並調用 JDBC 接口加以執行。
 
新系統的開發
  大多數情況下(特別是對新項目,新系統的開發而言),這樣的機制無往不利,大有一統天下的勢頭。但是,在一些特定的環境下,這種一站式的解決方案卻未必靈光。
  在筆者的系統咨詢工作過程中,常常遇到以下情況:
  1. 系統的部分或全部數據來自現有數據庫,處於安全考慮,隻對開發團隊提供幾條Select SQL(或存儲過程)以獲取所需數據,具體的表結構不予公開。
  2. 開發規范中要求,所有牽涉到業務邏輯部分的數據庫操作,必須在數據庫層由存儲過程實現(就筆者工作所面向的金融行業而言,工商銀行、中國銀行、交通銀行,都在開發規范中嚴格指定)
  3. 系統數據處理量巨大,性能要求極為苛刻,這往往意味著我們必須通過經過高度優化的SQL語句(或存儲過程)才能達到系統性能設計指標。
  面對這樣的需求,再次舉起 Hibernate 大刀,卻發現刀鋒不再銳利,甚至無法使用,奈何?恍惚之際,隻好再摸出JDBC 準備拼死一搏……,說得未免有些淒涼,直接使用 JDBC進行數據庫操作實際上也是不錯的選擇,隻是拖沓的數據庫訪問代碼,乏味的字段讀取操作令人厭煩。
 
半自動化
剛好解決上述問題
  “半自動化”的ibatis,卻剛好解決瞭這個問題。
  這裡的“半自動化”,是相對Hibernate等提供瞭全面的數據庫封裝機制的“全自動化”
  ORM 實現而言,“全自動”ORM 實現瞭 POJO 和數據庫表之間的映射,以及 SQL 的自動
  生成和執行。而ibatis 的著力點,則在於POJO 與 SQL之間的映射關系。也就是說,ibatis
  並不會為程序員在運行期自動生成 SQL 執行。具體的 SQL 需要程序員編寫,然後通過映
  射配置文件,將SQL所需的參數,以及返回的結果字段映射到指定 POJO。
 
全自動
  使用ibatis 提供的ORM機制,對業務邏輯實現人員而言,面對的是純粹的 Java對象,
  這一層與通過 Hibernate 實現 ORM 而言基本一致,而對於具體的數據操作,Hibernate
  會自動生成SQL 語句,而ibatis 則要求開發者編寫具體的 SQL 語句。相對Hibernate等
  “全自動”ORM機制而言,ibatis 以 SQL開發的工作量和數據庫移植性上的讓步,為系統
  設計提供瞭更大的自由空間。作為“全自動”ORM實現的一種有益補充,ibatis 的出現顯
  得別具意義。


 
 
一般步驟:
9.1 全局配置文件
該全局配置文件用來配置數據庫連接,數據庫事務,定義SQL映射配置文件
說明:
a) <properties>標簽定義全局配置屬性文件(.properties),隻能出現0次或1次
用於填充com.ibatis.sqlmap.engine.builder.xml.XmlParseState類globalProps屬性
另外,這個全局.properties可以動態配置.
在後面說到的數據庫操作時,通過com.ibatis.sqlmap.client.SqlMapClientBuilder類中靜態方法buildSqlMapClient(InputStream,Properties)和buildSqlMapClient(Reader,Properties)獲取com.ibatis.sqlmap.client.SqlMapClient對象時動態配置.如此,可以動態使用數據源,動態更改成不同的數據庫連接等等.
 
如:jdbc.properties設置瞭一些數據庫連接的參數,其中鍵(e.g. driverClassName)可以通過如${driverClassName}在sql-map-config.xml文件中使用.
b) <sqlMap>標簽用來配置SQL映射文件.e.g sql-map-user.xml是SQL映射文件
c) <properties>標簽、<sqlMap>標簽的resource屬性是相對於classpath路徑.所以,以 / 開頭是錯誤的.這二個標簽還有一個url屬性,可以配置絕對路徑.
d) <transactionManager>標簽用於定義數據庫事務.type屬性定義數據庫事務類型,有三個選項: JDBC,JTA,EXTERNAL
e) <dataSource>標簽用來定義數據庫連接的一些屬性.事務類型為JDBC時,type屬性定義數據庫數據源
用戶可以使用ibatis內置的三類數據源:
SIMPLE
將<dataSource>標簽內定義的數據源參數包裝成一個Map對象,通過SimpleDataSourceFactory類initialize()方法傳入到com.ibatis.common.jdbc.SimpleDataSource,初始化SimpleDataSource數據源屬性.
相關類 com.ibatis.sqlmap.engine.datasource.SimpleDataSourceFactory
/**
 * DataSourceFactory implementation for the iBATIS SimpleDataSource
 */
public class SimpleDataSourceFactory implements DataSourceFactory {
 
  private DataSource dataSource;
 
  public void initialize(Map map) {
    dataSource = new SimpleDataSource(map);
  }
 
  public DataSource getDataSource() {
    return dataSource;
  }
 
}
 
相關類 com.ibatis.common.jdbc.SimpleDataSource
/**
 * This is a simple, synchronous, thread-safe database connection pool.
 * <p/>
 * REQUIRED PROPERTIES
 * ——————-
 * JDBC.Driver
 * JDBC.ConnectionURL
 * JDBC.Username
 * JDBC.Password
 * <p/>
 * Pool.MaximumActiveConnections
 * Pool.MaximumIdleConnections
 * Pool.MaximumCheckoutTime
 * Pool.TimeToWait
 * Pool.PingQuery
 * Pool.PingEnabled
 * Pool.PingConnectionsOlderThan
 * Pool.PingConnectionsNotUsedFor
 * Pool.QuietMode
 */
private void initialize(Map props) {
    …
if (!(props.containsKey(PROP_JDBC_DRIVER)
          && props.containsKey(PROP_JDBC_URL)
          && props.containsKey(PROP_JDBC_USERNAME)
          && props.containsKey(PROP_JDBC_PASSWORD))) {
        throw new RuntimeException("SimpleDataSource: Some properties were not set.");
      } else {
 
        jdbcDriver = (String) props.get(PROP_JDBC_DRIVER);
        jdbcUrl = (String) props.get(PROP_JDBC_URL);
        jdbcUsername = (String) props.get(PROP_JDBC_USERNAME);
        jdbcPassword = (String) props.get(PROP_JDBC_PASSWORD);
 
        poolMaximumActiveConnections =
            props.containsKey(PROP_POOL_MAX_ACTIVE_CONN)
            ? Integer.parseInt((String) props.get(PROP_POOL_MAX_ACTIVE_CONN))
            : 10;
 
        poolMaximumIdleConnections =
            props.containsKey(PROP_POOL_MAX_IDLE_CONN)
            ? Integer.parseInt((String) props.get(PROP_POOL_MAX_IDLE_CONN))
            : 5;
 
        poolMaximumCheckoutTime =
            props.containsKey(PROP_POOL_MAX_CHECKOUT_TIME)
            ? Integer.parseInt((String) props.get(PROP_POOL_MAX_CHECKOUT_TIME))
            : 20000;
 
        poolTimeToWait =
            props.containsKey(PROP_POOL_TIME_TO_WAIT)
            ? Integer.parseInt((String) props.get(PROP_POOL_TIME_TO_WAIT))
            : 20000;
 
        poolPingEnabled =
            props.containsKey(PROP_POOL_PING_ENABLED)
                && Boolean.valueOf((String) props.get(PROP_POOL_PING_ENABLED)).booleanValue();
 
        prop_pool_ping_query = (String) props.get(PROP_POOL_PING_QUERY);
        poolPingQuery =
            props.containsKey(PROP_POOL_PING_QUERY)
            ? prop_pool_ping_query
            : "NO PING QUERY SET";
 
        poolPingConnectionsOlderThan =
            props.containsKey(PROP_POOL_PING_CONN_OLDER_THAN)
            ? Integer.parseInt((String) props.get(PROP_POOL_PING_CONN_OLDER_THAN))
            : 0;
 
        poolPingConnectionsNotUsedFor =
            props.containsKey(PROP_POOL_PING_CONN_NOT_USED_FOR)
            ? Integer.parseInt((String) props.get(PROP_POOL_PING_CONN_NOT_USED_FOR))
            : 0;
 
        jdbcDefaultAutoCommit =
            props.containsKey(PROP_JDBC_DEFAULT_AUTOCOMMIT)
                && Boolean.valueOf((String) props.get(PROP_JDBC_DEFAULT_AUTOCOMMIT)).booleanValue();
    …
}
    
DBCP
相關類 com.ibatis.sqlmap.engine.datasource.DbcpDataSourceFactory
/**
 * DataSourceFactory implementation for DBCP
 */
public class DbcpDataSourceFactory implements DataSourceFactory {
 
  private DataSource dataSource;
 
  public void initialize(Map map) {
    DbcpConfiguration dbcp = new DbcpConfiguration(map);
    dataSource = dbcp.getDataSource();
  }
 
  public DataSource getDataSource() {
    return dataSource;
  }
 
}
 
相關類 com.ibatis.common.jdbc.DbcpConfigurationDataSource
/* REQUIRED PROPERTIES
 * ——————-
 * JDBC.Driver
 * JDBC.ConnectionURL
 * JDBC.Username
 * JDBC.Password
 * <p/>
 * Pool.ValidationQuery
 * Pool.MaximumActiveConnections
 * Pool.MaximumIdleConnections
 * Pool.MaximumWait
 */
private BasicDataSource legacyDbcpConfiguration(Map map) {
    BasicDataSource basicDataSource = null;
    if (map.containsKey("JDBC.Driver")) {
      basicDataSource = new BasicDataSource();
      String driver = (String) map.get("JDBC.Driver");
      String url = (String) map.get("JDBC.ConnectionURL");
      String username = (String) map.get("JDBC.Username");
      String password = (String) map.get("JDBC.Password");
      String validationQuery = (String) map.get("Pool.ValidationQuery");
      String maxActive = (String) map.get("Pool.MaximumActiveConnections");
      String maxIdle = (String) map.get("Pool.MaximumIdleConnections");
      String maxWait = (String) map.get("Pool.MaximumWait");
 
      basicDataSource.setUrl(url);
      basicDataSource.setDriverClassName(driver);
      basicDataSource.setUsername(username);
      basicDataSource.setPassword(password);
    …
}
 
 
JNDI
/* REQUIRED PROPERTIES
 * ——————-
 * DataSource
 * or
 * DBJndiContext
 * or
 * DBFullJndiContext
 * or
 * DBInitialContext and DBLookup
 */
相關類 com.ibatis.sqlmap.engine.datasource.JndiDataSourceFactory
/**
 * DataSourceFactory implementation for JNDI
 */
public class JndiDataSourceFactory implements DataSourceFactory {
  public void initialize(Map properties) {
    …
    if (properties.containsKey("DataSource")) {
        dataSource = (DataSource) initCtx.lookup((String) properties.get("DataSource"));
      } else if (properties.containsKey("DBJndiContext")) { // LEGACY –Backward compatibility      
        dataSource = (DataSource) initCtx.lookup((String) properties.get("DBJndiContext"));
      } else if (properties.containsKey("DBFullJndiContext")) { // LEGACY –Backward compatibility
        dataSource = (DataSource) initCtx.lookup((String) properties.get("DBFullJndiContext"));
      } else if (properties.containsKey("DBInitialContext")
          && properties.containsKey("DBLookup")) { // LEGACY –Backward compatibility
        Context ctx = (Context) initCtx.lookup((String) properties.get("DBInitialContext"));
        dataSource = (DataSource) ctx.lookup((String) properties.get("DBLookup"));
      }
    …
  }
}
 
sql-map-config.xml數據庫事務片段:
<transactionManager type="JDBC" >
    <dataSource type="JNDI">
       <property name="DataSource" value="java:comp/env/jdbc/mydb"/>
    </dataSource>
</transactionManager>
 
java:comp/env/jdbc/mydb是通過JNDI方式獲取數據源
簡述:建立一個context.xml文件放置在WebRoot/META-INF目錄下,詳見 7.1 配置JNDI數據源.
 
sql-map-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-config-2.dtd" >
 
<sqlMapConfig>
 
    <properties resource="com/iteye/jarg/resources/jdbc.properties" />
  
    <transactionManager type="JDBC">
       <dataSource type="SIMPLE">
           <property name="JDBC.Driver" value="${driverClassName}" />
           <property name="JDBC.ConnectionURL" value="${url}" />
           <property name="JDBC.Username" value="${username}" />
           <property name="JDBC.Password" value="${password}" />
           <property name="Pool.MaximumActiveConnections" value="${maxActive}" />
           <property name="Pool.MaximumIdleConnections" value="${maxIdle}" />
           <property name="Pool.TimeToWait" value="${maxWait}" />
       </dataSource>
    </transactionManager>
 
  <sqlMap resource="com/iteye/jarg/resources/sql-map-user.xml" />
 
</sqlMapConfig>
 
 
9.2 SQL映射文件
該SQL映射文件用來配置數據庫表字段與POJO對象的映射
sql-map-user.xml用於將數據庫中User表字段映射到com.iteye.jarg.bean.User屬性
說明:
<typeAlias>配置別名,將一長串,用一個別名代替
<resultMap>、<parameterMap>用來設置生成的結果集中每條記錄的類型及包含的數據
<insert>、<delete>、<update>、<select>對應SQL的增刪改查操作語句
<statement>可以用來代替增刪改查的四個標簽,也即其中的內容可以是任意的數據庫增刪改查操作
<procedure>對應數據庫調用存儲過程
註意:
a) 花括號是必須的
b) 通過call關鍵字來調用存儲過程
c) 問號用來占位,代表一個輸入輸出參數
如:
    <procedure id="swapEmailAddresses" parameterMap="swapParameters" >
       { call swap_email_address (?, ?) }
    </procedure>
  
<insert>、<delete>、<update>、<select>、<statement>、<procedure>這些標簽必不可少的是id屬性,供數據庫操作函數調用對應的SQL語句,也是區別於其他的標簽SQL內容的依據.該id屬性必須是在全局配置文件(e.g. sql-map-config.xml)中定義的所有SQL映射文件(e.g. sql-map-user.xml)中獨一無二的,即:所有SQL映射文件中的所有id屬性不可出現重復情況.
另外,常用的屬性有
設置傳入參數類型  parameterClass,parameterMap
設置傳出參數類型  resultClass,resultMap
設置調整緩存類型  cacheModel
parameterClass,resultClass用於指定已經存在的數據對象類型
parameterMap,resultMap用於指定自定義的數據對象類型
如:
    <resultMap class="User" id="UserMap">
       <result property="id"  column="id" />
       <result property="username"  column="username" />
       <result property="password"  column="password" />
    </resultMap>
  
 
<cacheModel>標簽
用於設置數據庫預編譯SQL語句,緩存SQL語句的緩存模式
A cacheModel is used to describe a cache for use with a query mapped statement. Each query mapped statement can use a different cacheModel, or the same one.
如:
    <cacheModel id="product-cache" type="LRU">
       <flushInterval hours="24"/>
       <flushOnExecute statement="insertProduct"/>
       <flushOnExecute statement="updateProduct"/>
       <flushOnExecute statement="deleteProduct"/>
       <property name=”size” value=”1000” />
    </cacheModel>
  
 
sql-map-user.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN " "http://ibatis.apache.org/dtd/sql-map-2.dtd" >
 
<sqlMap>
  
    <typeAlias alias="User" type="com.iteye.jarg.bean.User" />
  
    <resultMap class="User" id="UserMap">
       <result property="id"  column="id" />
       <result property="username"  column="username" />
       <result property="password"  column="password" />
    </resultMap>
  
    <select id="user.queryUserById" parameterClass="java.lang.Integer" resultMap="UserMap">
       select *
       from user
       where id = #id#
    </select>
  
    <select id="user.queryUser" parameterClass="User" resultMap="UserMap">
       select *
       from user
       where username=#username# and password=#password#
    </select>
  
    <select id="user.queryUsersById" parameterClass="java.lang.Integer" resultMap="UserMap">
       select *
       from user
       where id != #id#
    </select>
  
    <select id="user.queryAllUser" resultMap="UserMap">
       select *
       from user
    </select>
  
</sqlMap>
 
 
9.3 數據庫操作
9.3.1 獲取com.ibatis.sqlmap.client.SqlMapClient對象
 
摘自com.ibatis.sqlmap.client.SqlMapClientBuilder類描述:
Builds SqlMapClient instances from a supplied resource (e.g. XML configuration file)
 
The SqlMapClientBuilder class is responsible for parsing configuration documents and building the SqlMapClient instance. Its current implementation works with XML configuration files (e.g. sql-map-config.xml).
 
Example:
 
 Reader reader = Resources.getResourceAsReader("properties/sql-map-config.xml");
 SqlMapClient client = SqlMapClientBuilder.buildSqlMapClient (reader);
 
 
通過com.ibatis.sqlmap.client.SqlMapClientBuilder類中靜態方法
public static SqlMapClient buildSqlMapClient(Reader reader)
public static SqlMapClient buildSqlMapClient(Reader reader, Properties props)
public static SqlMapClient buildSqlMapClient(InputStream inputStream)
public static SqlMapClient buildSqlMapClient(InputStream inputStream, Properties props)
獲取com.ibatis.sqlmap.client.SqlMapClient對象
 
 
 
9.3.2 利用com.ibatis.sqlmap.client.SqlMapClient接口中內置的數據庫操作方法完成數據庫操作
com.ibatis.sqlmap.client.SqlMapClient是一個接口,繼承瞭SqlMapExecutor,SqlMapTransactionManager接口.
public interface SqlMapClient extends SqlMapExecutor, SqlMapTransactionManager
 
SqlMapExecutor接口中定義瞭所有數據庫操作的方法(This interface declares all methods involved with executing statements and batches for an SQL Map.)
e.g. queryForList(),queryForObject(),querForMap()
queryForList可以用來獲取包含多條記錄的結果集
queryForObject可以用來獲取包含0條或1條記錄的結果集
 
以上二種方式最多可以包含三個參數,最少一個參數.
id對應於SQL映射文件(e.g. sql-map-user.xml)中包含數據庫操作SQL語句的標簽編號,是必不可少的.
parameterObject指定SQL語句參數
resultObject指定需要的結果數據(當結果集中包含數據所需要的數據量時,可以通過這個參數過濾掉一些不需要的結果數據)
Parameters:
    id:        The name of the statement to execute.
    parameterObject:  The parameter object (e.g. JavaBean, Map, XML etc.).
    resultObject: The result object instance that should be populated with result data.
 
Map queryForMap(String id, Object parameterObject, String keyProp) throws SQLException;
用來獲取結果集中鍵為keyProp的結果
 
Map queryForMap(String id, Object parameterObject, String keyProp, String valueProp) throws SQLException;
用來獲取結果集中鍵為keyProp,值為valueProp的結果
 
 
SqlMapTransactionManager接口中定義瞭數據庫事務操作的方法(This interface declares methods for demarcating SQL Map transactions.)
e.g. startTransaction(),commintTransaction(),endTransaction()
 
默認情況,當完成一次數據庫操作(select,insert,update,delete)時,數據庫事務總是自動提交,即使使用瞭startTransaction(),commitTransaction(),endTransaction().
 
在全局配置文件(e.g. sql-map-config.xml)中,<transactionManager>標簽的commitRequired屬性設置為true時,通過commitTransaction()來手動提交數據庫事務.
這種方式的優點:
a) 一次執行多條數據庫操作,出錯時可以回滾,將數據庫還原到出錯前.
b) 一次提交效率高,節省多次提交的多次數據庫建立,連接資源
 
 
摘自com.ibatis.sqlmap.client.SqlMapClient類描述:
A thread safe client for working with your SQL Maps (Start Here). This interface inherits transaction control and execution methods from the SqlMapTransactionManager and SqlMapExecutor interfaces.
 
The SqlMapClient is the central class for working with SQL Maps. This class will allow you to run mapped statements (select, insert, update, delete etc.), and also demarcate transactions and work with batches. Once you have an SqlMapClient instance, everything you need to work with SQL Maps is easily available.
 
The SqlMapClient can either be worked with directly as a multi-threaded client (internal session management), or you can get a single threaded session and work with that. There may be a slight performance increase if you explicitly get a session (using the openSession() method), as it saves the SqlMapClient from having to manage threads contexts. But for most cases it won't make much of a difference, so choose whichever paradigm suits your needs or preferences.
 
An SqlMapClient instance can be safely made static or applied as a Singleton. Generally it's a good idea to make a simple configuration class that will configure the instance (using SqlMapClientBuilder) and provide access to it.
 
9.3.3 數據庫操作示例
a)讀取數據全局配置文件
b) 獲取數據庫操作句柄
c) 查詢操作並返回結果
 
    String filePath = "com/iteye/jarg/resources/sql-map-config.xml";
    InputStream inputStream = IbatisDbUtil.class.getClassLoader().getResourceAsStream(filePath);
  
    sqlMapper = SqlMapClientBuilder.buildSqlMapClient(inputStream);
 
    int id = 2;
    User user = (User)sqlMapper.queryForObject("user.queryUserById", id);
  
    System.out.println("id:\t\t" + user.getId());
    System.out.println("username:\t" + user.getUsername());
    System.out.println("password:\t" + user.getPassword());

發佈留言