| Main | Project Page | Download | Grateful to for hosting this project | 
3. 用daozero代替原来的iBatis DAO bean
6. 想使用当前版本的daozero不支持的iBatis特性时怎么办?
*假设你具有使用Spring的iBatis支持类作为持久层实现的实际编码经验(即时没有,学习Spring和iBatis也应该不是件怎么难的事情)。 Daozero是1个很小的Spring Java Bean。使用daozero可以减少基于 iBatis+Spring的持久层代码数量, 因为daozero会动态地替我们实现持久层接口。它不是1个Spring中iBatis支持类的包装,而是用来直接替换掉我们手工编写的持久层 实现代码的。使用daozero时,一旦我们完成了DAO接口的定义(Java Interface),通常情况下,我们只需要再在Spring Context定义 文件中声明类型(class)为daozero.ibatis.Dao的bean,并且设置这些bean的targetType属性为已定义好的DAO接口,然后这些daozero bean 就会在运行时为我们动态地生成实现了targetType的DAO实现类,由这些实现类去调用iBatis API访问数据库。所以,不需要DAO接口的 实现代码了。
Daozero约定iBatis SQL Mapping XML文件中定义的statement的名字需要和DAO接口(抽象类)的method的名字保持一致, 而且,当前版本还要求statement的parameter的数量及首次出现顺序也必须要和DAO接口(抽象类)的method的参数(形参)的数量及出现顺序保持一致(当然,如果statement的参数是单个的Java Bean,就可以不受这个约定的限制)。 通过做出这些约定(应该不太难于遵守吧),daozero就可以确定如何把method被调用时传入的参数(实参)和statement的parameter对应起来,于是就 可以用这些传入的参数组成statement需要的parameter map,去调用iBatis API访问数据库。Daozero是一个实现了org.springframework.beans.factory.FactoryBean 的bean,就是说它是可以产生bean的factory bean,而daozero factory bean产生的bean就是实现了DAO接口的DAO对象,这些DAO对象负责调用Spring的iBatis template的方法, 例如queryForObject()、queryForList()和update()等。这些DAO对象也有足够“智能”,它们会依据method的返回类型推断出该调用queryForObject()还是queryForList()(当前版本尚不支持queryForMap)。 daozero factory bean是如何产生DAO对象的呢?这要视其属性targetType是接口还是抽象类来定:如果targetType是接口,那么使用JDK标准的proxy(java.lang.reflect.Proxy)机制,由该proxy负责拦截下 对该接口方法的调用;如果是抽象类,那么就使用CGLIB的enhancer(net.sf.cglib.proxy.Enhancer)莱拦截下对该抽象类中抽象方法的调用,而将方法调用 拦截下来后的处理则基本上一致。使用JDK Proxy或CGLIB enhancer对性能的影响在数百纳秒(ns)这个数量级,因此对于大多数Web应用来说相对于数据库SQL执行是可以忽略不计的。
事实上daozero不得不hack了一些iBatis的代码,不得不把iBatis提供的一些接口强行转型(Cast)到iBatis的内部实现类,原因在于iBatis似乎没有提供检索其statement元数据的方法, 使得daozero不得不在代码中留下了一些坏味道。(所以,如果iBatis出现了大的版本改变,那么daozero这部分代码也不得不重新写。)
假设我们有一个数据库表叫"account",表结构如下所示,
create 
  table account (
  userid varchar(80) not null,
  email varchar(80) not null,
  constraint pk_account primary key (userid)
  );
我们使用了一个叫Account的domain class来代表该表,Account是标准的Java Bean(POJO),具有属性:"userId"和"email",
public class Account implements Serializable {
  private String userid;
  private String email;
  public String getUserId() { return this.userid; }
  public void setUserId(String s) { this.userid=s; }
  public String getEmail() { return this.email; }
  public void setEmail(String s) { this.email=s; }
  } 
操作Account的DAO接口是这样的,
public 
  interface AccountDao {
  Account getAccountByUserId(String userId) throws DataAccessException;
  void updateAccount(Account account) throws DataAccessException;
  List getUsernameList() throws DataAccessException;
  }
使用daozero之前,一般情况下,我们会用iBatis做一个DAO实现类,该类继承自Spring的SqlMapClientDaoSupport,该基类封装了iBatis SqlMapClient的主要操作以和Spring集成起来, 该实现类可能是这样的:
  import org.springframework.org.ibatis.support.SqlMapClientDaoSupport;
  
  public class AccountDaoImpl
  extends SqlMapClientDaoSupport
 
  implements AccountDao {
      public Account getAccountByUserIdAndEmail(String userId, String email) throws 
  DataAccessException {
      Map params = new HashMap();
      params.put( "userId", userId );
      params.put( "email", email );
      return getTemplate().queryForObject( "getAccountByUserIdAndEmail", 
  params );
        }
  
     public int updateAccount(Account account) throws DataAccessException {
      return getTemplate().update( "updateAccount", account 
  );
        }
        public List getUsernameList() throws DataAccessException;
      return getTemplate().queryForList( "getUsernameList", 
  null );
        }
  <select id="getAccountByUserIdAndEmail" resultClass="Account">
    select * from account where userid=#userId# AND email=#email#
  </select>
  <select id="getUsernameList" resultClass="java.lang.String">
    select userid from account
  </select>
  <update id="updateAccount">
    update account set email = #email# where userid=#userId#
  </update>
 
  <bean id="accountDao" class="AccountDaoImpl">
       <property name="sqlMapClient" ref="sqlMapClient"/>
  </bean> 
现在,我们来试试看使用了daozero后daozero怎样来去掉AccountDaoImpl那些繁复无聊的代码: 很简单,第一步是删掉那个AccountDaoImpl.java! 然后,修改上面那段Spring Context XML文件中bean的定义,改成:
 
  <bean id="accountDao" class="daozero.ibatis.Dao">
       <property name="sqlMapClient" ref="sqlMapClient"/>
      <property name="targetType" value="AccountDao" 
  /> <!-- interface -->
  </bean> 
虽然上述例子足以应付多数情况,但是daozero不可能为我们完成任何事情,有时候还是有不得不手工实现一些DAO方法的需要的,这时候我们如何既保留下daozero提供的好处, 又能够告诉daozero哪些方法我们已经自己手工实现了而不需要daozero代劳了呢?解决办法是很自然的,写好我们自己的DAO类,但是只需要填上需要手工实现的,其他的可以继续由daozero代劳, daozero会依据该实现类的某方法是否是abstract来判断是否需要替我们去做事情,不需要的话就什么也不做,直接转交给我们填好的方法代码,所以,这时候的DAO实现类是抽象类。 另外需要做的事情就是把bean的targetType换成这个abstract的DAO实现类。对于上面那个例子,我们保留下AccountDaoImpl.java,手工填写updateAccount()方法:
public abstract 
  class AccountDaoImpl 
  extends SqlMapClientDaoSupport 
  implements AccountDao {
  public abstract int __updateAccount(Account account) 
  throws DataAccessException;
  public int updateAccount(Account account) throws 
  DataAccessException {
      // Insert additional operation code here 
      return 
  __updateAccount(account);
  }
  }
<select id="getAccountByUserIdAndEmail" 
  resultClass="Account">
      select * from account where userid=#userId# AND email=#email#
  </select>
  
  <select id="getUsernameList" resultClass="java.lang.String">
      select userid from account
  </select>
  
  <update id="__updateAccount">
      update account set email = #email# where userid=#userId#
  </update>
  当然,daozero并不反对你不修改statement名字,不增加一个__updateAccount方法,而是在updateAccount()方法中按照从前的写法自己调用iBatis API(不过,既然改改statement的名字
  就可以让daozero继续为我们服务,而且能保持代码的一致性,我想通常还是加上个__updateAccount方法更合适一些,这也是daozero推荐的一个实践 -- daozero希望你关注于完成daozero替你
  代劳不了的事情,而不是去写那些重复性很大的代码。)
  
|  |  |  |