12.5. iBATIS SQL Maps

Spring通过 org.springframework.orm.ibatis 包来支持iBATIS SQL Maps 1.x和2.x (http://www.ibatis.com)。 与JDBC/Hibernate支持非常类似,Spring对于iBATIS的支持也采用了Template的风格,同样遵循Spring的异常体系,这些会让你喜欢上Spring的所有IoC特性。

事务管理可以由Spring标准机制进行处理。对于iBATIS来说没有特别的事务策略,除了JDBC Connection 以外,也没有特别的事务资源。 因此,Spring标准的JDBC DataSourceTransactionManager 或者 JtaTransactionManager 已经能够完全足够了。

12.5.1. iBATIS 1.x和2.x的概览与区别

Spring同时支持iBATIS SQL Maps 1.x和2.x。首先让我们先来看一下两者的区别。

两者XML配置文件有一点区别,节点和属性名有了些改动。你所要继承的Spring类和方法名也有一些区别。

表 12.1. iBATIS SQL Maps 1.x和2.x的支持类

特性 1.x 2.x
SqlMap(Client)的创建 SqlMapFactoryBean SqlMapClientFactoryBean
Template风格的帮助类 SqlMapTemplate SqlMapClientTemplate
使用MappedStatement的回调 SqlMapCallback SqlMapClientCallback
DAO基类 SqlMapDaoSupport SqlMapClientDaoSupport


12.5.2. iBATIS SQL Maps 1.x

12.5.2.1. 创建SqlMap

使用iBATIS SQL Maps包括创建一个SqlMap配置文件来定义sql语句和结果映射。Spring会通过 SqlMapFactoryBean 来加载并处理这些配置。

public class Account {

    private String name;
    private String email;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return this.email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

假设我们要映射这个类,我们需要创建如下的 SqlMap。通过使用查询,稍后我们可以用email地址来查找对应的用户。Account.xml 如下:

<sql-map name="Account">

  <result-map name="result" class="examples.Account">
    <property name="name" column="NAME" columnIndex="1"/>
    <property name="email" column="EMAIL" columnIndex="2"/>
  </result-map>

  <mapped-statement name="getAccountByEmail" result-map="result">
    select ACCOUNT.NAME, ACCOUNT.EMAIL
    from ACCOUNT
    where ACCOUNT.EMAIL = #value#
  </mapped-statement>

  <mapped-statement name="insertAccount">
    insert into ACCOUNT (NAME, EMAIL) values (#name#, #email#)
  </mapped-statement>

</sql-map>

定义完Sql Map之后,我们需要创建一个iBATIS的配置文件(sqlmap-config.xml):

<sql-map-config>

  <sql-map resource="example/Account.xml"/>

</sql-map-config>

iBATIS会从CLASSPATH加载资源,所以要确保 Account.xml 在CLASSPATH下。

通过Spring,我们可以非常容易的使用 SqlMapFactoryBean 来创建SqlMap:

<beans>

  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
  </bean>

  <bean id="sqlMap" class="org.springframework.orm.ibatis.SqlMapFactoryBean">
    <property name="configLocation" value="WEB-INF/sqlmap-config.xml"/>
  </bean>

  ...
</beans>

12.5.2.2. 使用 SqlMapTemplateSqlMapDaoSupport

SqlMapDaoSupport 类是一个类似于 HibernateDaoSupportJdoDaoSupport 的支持类。我们来实现一个DAO:

public class SqlMapAccountDao extends SqlMapDaoSupport implements AccountDao {

    public Account getAccount(String email) throws DataAccessException {
        return (Account) getSqlMapTemplate().executeQueryForObject("getAccountByEmail", email);
    }

    public void insertAccount(Account account) throws DataAccessException {
        getSqlMapTemplate().executeUpdate("insertAccount", account);
    }
}

正如你所看到的,我们使用预先配置好的 SqlMapTemplate 来执行查询。 Spring在创建 SqlMapAccountDao 的时候已经使用 SqlMapFactoryBean 为我们初始化了 SqlMap,如下所示一切都准备就绪了。 注意在iBATIS SQL Maps 1.x里面,JDBC DataSource 通常都是DAO中指定的。

<beans>
  ...

  <bean id="accountDao" class="example.SqlMapAccountDao">
    <property name="dataSource" ref="dataSource"/>
    <property name="sqlMap" ref="sqlMap"/>
  </bean>

</beans>

注意 SqlMapTemplate 实例是可以手工创建的,通过传入 DataSource,并把 SqlMap 作为构造函数参数进行创建。 SqlMapDaoSupport 的基类已经预先替我们初始化了一个 SqlMapTemplate 实例了。

12.5.3. iBATIS SQL Maps 2.x

12.5.3.1. 创建SqlMapClient

如果我们希望使用iBATIS 2.x来映射刚才的那个Account类,则需要创建这样一个SQL map Account.xml

<sqlMap namespace="Account">

  <resultMap id="result" class="examples.Account">
    <result property="name" column="NAME" columnIndex="1"/>
    <result property="email" column="EMAIL" columnIndex="2"/>
  </resultMap>

  <select id="getAccountByEmail" resultMap="result">
    select ACCOUNT.NAME, ACCOUNT.EMAIL
    from ACCOUNT
    where ACCOUNT.EMAIL = #value#
  </select>

  <insert id="insertAccount">
    insert into ACCOUNT (NAME, EMAIL) values (#name#, #email#)
  </insert>

</sqlMap>

iBATIS 2的配置文件有了一些改变(sqlmap-config.xml):

<sqlMapConfig>

  <sqlMap resource="example/Account.xml"/>

</sqlMapConfig>

记住iBATIS从CLASSPATH下加载资源,所以必须确保 Account.xml 在CLASSPATH下。

我们可以使用Spring application context中的 SqlMapClientFactoryBean。 注意iBATIS SQL Map 2.x中,JDBC DataSource 通常由 SqlMapClientFactoryBean 指定,并开启了延迟加载。

<beans>

  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
  </bean>

  <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
    <property name="configLocation" value="WEB-INF/sqlmap-config.xml"/>
    <property name="dataSource" ref="dataSource"/>
  </bean>

  ...
</beans>

12.5.3.2. 使用 SqlMapClientTemplateSqlMapClientDaoSupport

SqlMapClientDaoSupport 提供了类似 SqlMapDaoSupport 的功能。我们可以继承它来实现我们自己的DAO:

public class SqlMapAccountDao extends SqlMapClientDaoSupport implements AccountDao {

    public Account getAccount(String email) throws DataAccessException {
        return (Account) getSqlMapClientTemplate().queryForObject("getAccountByEmail", email);
    }

    public void insertAccount(Account account) throws DataAccessException {
        getSqlMapClientTemplate().update("insertAccount", account);
    }
}

我们可以在application context中创建了 SqlMapAccountDao 并且注入 SqlMapClient 实例,这样我们就可以在DAO中使用预先配置的 SqlMapClientTemplate 来执行查询了:

<beans>
  ...

  <bean id="accountDao" class="example.SqlMapAccountDao">
    <property name="sqlMapClient" ref="sqlMapClient"/>
  </bean>

</beans>

注意 SqlMapTemplate 实例也可以手工创建,使用 SqlMapClient 作为构造函数参数。 SqlMapClientDaoSupport 基类为我们预先初始化了一个 SqlMapClientTemplate 实例。

SqlMapClientTemplate 还提供了一个通用的 execute 方法,将用户自定义的 SqlMapClientCallback 的实现作为参数。 举例来说,这可以实现批量操作:

public class SqlMapAccountDao extends SqlMapClientDaoSupport implements AccountDao {
    ...

    public void insertAccount(Account account) throws DataAccessException {
        getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
            public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                executor.startBatch();
                executor.update("insertAccount", account);
                executor.update("insertAddress", account.getAddress());
                executor.executeBatch();
            }
        });
    }
}

一般来说,任何由 SqlMapExecutor API提供的操作组合都以这样的回调形式被使用。 而在这个过程中产生的任何 SQLException 都将被自动地转化为Spring的通用的 DataAccessException 异常体系。

12.5.3.3. 基于原生的iBATIS API的DAO实现

你也可以基于原生的iBATIS API来编程,而无需对Spring产生任何依赖。直接使用注入的 SqlMapClient。 一个相应的DAO实现类看上去就像下面这样:

public class SqlMapAccountDao implements AccountDao {

    private SqlMapClient sqlMapClient;

    public void setSqlMapClient(SqlMapClient sqlMapClient) {
        this.sqlMapClient = sqlMapClient;
    }

    public Account getAccount(String email) {
        try {
            return (Account) this.sqlMapClient.queryForObject("getAccountByEmail", email);
        }
        catch (SQLException ex) {
            throw new MyDaoException(ex);
        }
    }

    public void insertAccount(Account account) throws DataAccessException {
        try {
            this.sqlMapClient.update("insertAccount", account);
        }
        catch (SQLException ex) {
            throw new MyDaoException(ex);
        }
    }
}

在这种情况下,由iBATIS API抛出的 SQLException 异常需要以用户自定义的方式进行处理:通常封装成为你的应用程序自身的DAO异常。 在application context中进行的整合看上去依然像以前一样,这是由于基于原生的iBATIS的DAO依然遵循IoC的模式:

<beans>
  ...

  <bean id="accountDao" class="example.SqlMapAccountDao">
    <property name="sqlMapClient" ref="sqlMapClient"/>
  </bean>

</beans>