风人园

弱水三千,只取一瓢,便能解渴;佛法无边,奉行一法,便能得益。
随笔 - 99, 文章 - 181, 评论 - 56, 引用 - 0
数据加载中……

DaoZero:让它为你实现DAO接口

1. DaoZero是什么?它可以在哪方面帮助我?

*假设你具有使用Spring的iBatis支持类作为持久层实现的实际编码经验(即时没有,学习Spring和iBatis也应该不是件怎么难的事情)。

DaoZero是1个很小的Spring Java Bean。可以到http://dao-zero.sourceforge.net下载。使用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接口的实现代码了。

2. DaoZero的工作原理

DaoZero约定iBatis SQL Mapping XML文件中定义的statement的名字需要和DAO接口(抽象类)的method的名字保持一致,而且,当前版本还要求statement的parameter的数量及首次出现顺序也必须要和DAO接口(抽象类)的method的参数(形参)的数量及出现顺序保持一致,通过做出这些约定(应该不太难于遵守吧),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这部分代码也不得不重新写。)

3. 用DaoZero代替原来的iBatis DAO bean

假设我们有一个数据库表叫"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 );
   }


自然,iBatis的SQL Mapping XML文件是必不可少的:

<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>


然后,我们通常会到Spring Context XML文件中为该DAO声明1个Spring Bean?:

<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的方式用的bean的class是DaoZero库提供的1个固定的类--"daozero.ibatis.Dao"该类会在运行时自动提供AccountDao接口的实现类,所以AccountDaoImpl.java就可以扔掉了,就这么简单。

4. 使用抽象类的例子

虽然上述例子足以应付多数情况,但是DaoZero不可能为我们完成任何事情,有时候还是有不得不手工实现一些DAO方法的需要的,这时候我们如何既保留下DaoZero提供的好处, 又能够告诉DaoZero哪些方法我们已经自己手工实现了而不需要DaoZero代劳了呢?解决办法是很自然的,写好我们自己的DAO类,但是只需要填上需要手工实现的,其他的可以继续由DaoZero代劳, DaoZero会依据该实现类的某方法是否是abstract来判断是否需要替我们去做事情,不需要的话就什么也不做,直接转交给我们填好的方法代码,所以,这时候的DAO实现类是抽象类。另外需要做的事情就是把bean的targetType换成这个abstract的DAO实现类。对于上面那个例子,我们保留下AccountDaoImpl.java,手工填写updateAccount()方法:

public abstract class AccountDaoImpl 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);
}
}


最后,需要修改SQL mapped statement的名字,改成"__updateAccount",以便让DaoZero了解statement __updateAccount是和DAO方法__updateAccount联系在一起的:

<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替你 代劳不了的事情,而不是去写那些重复性很大的代码。)

5. 使用当前版本的DaoZero的一些限制条件

# 同一个类中不允许有同名方法(名字重载),除非这些同名方法的参数个数不一样(否则,DaoZero为statement寻找对应方法时会因出现歧义而失败)。
# 由DaoZero负责实现的抽象方法必须是public abstract。因为当前版本的DaoZero使用Java标准的Reflection机制(Class.getMethods())来遍历DAO类的method,所以非public abstract的方法是找不到的,这是一个可以改进的限制条件 -- DaoZero计划在后续版本中自行分析class文件来找到protected abstract方法,这样就可以不用强迫我们不得不把内部方法也public出来了;
# 目前DaoZero尚不支持iBatis API中的queryForMap()方法和Pagination机制(queryForMap在实现计划之中,而pagination我正考虑如何实现,而且既然使用iBatis就是为了获得使用native SQL 的灵活性和性能,我个人更倾向于不使用iBatis的pagination作为分页支持机制,而习惯于利用数据库SQL对分页的支持语法);
# 虽然目前只支持iBatis,但为Hibernate给出一个DaoZero实现也是在计划之中(因为我和不少人一样,也喜欢Hibernate);

6. 使用时出现问题?

# 确保参数的数量和首次出现顺序在statement和method中是保持一致的,而且必须是abstract,根据使用经验,大多数错误来自于此;
# 看看是否满足第5节说明的一些限制条件,很可能忽略的一种情况就是把method做成protected abstract了。

7. 其他

实际上还有1个小技巧:namespace -- 是的,这可以用于支持iBatis的namespace机制,但是1个Dao只能限定于1个namespace。具体使用方法请参照下载下来的sample。Sample是修改自Spring的JPetstore例子,比较一下它和原始的Spring的JPetstore的实现例子,可以找到DaoZero提倡的如何在service层使用DAO的理念,例如DaoZero更提倡不要只把service作为dao层的delegation,提倡大部分对DAO的操作的组合都放到service层中去。

8. License:

Apache License Version 2.0

posted on 2006-07-05 17:54 风人园 阅读(265) 评论(0)  编辑  收藏 所属分类: Java