Main   |   Project Page   |   Download  

                                 Grateful to
SourceForge.net Logo
for hosting this project

用15分钟了解daozero


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

2. Daozero的工作原理

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

4. 使用抽象类的例子

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

6. 想使用当前版本的daozero不支持的iBatis特性时怎么办?

7. 使用时出现问题?

8. 其他特性

9. 为什么推荐daozero? 和源代码生成工具的比较

10. License


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

*假设你具有使用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接口的 实现代码了。

 

2. Daozero的工作原理

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这部分代码也不得不重新写。)

 

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
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);
}
}

最后,需要修改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()方法;
  • 虽然目前只支持iBatis,但为Hibernate给出一个daozero实现也是在计划之中(因为我和不少人一样,也喜欢Hibernate);
  • 6. 想使用当前版本的daozero不支持的iBatis特性时怎么办?

  • 对于queryForMap等iBatis提供的而目前版本的daozero不支持的特性,可以先自己实现1个abstract的Dao实现类,手工填上那些不被支持的特性代码,而由daozero为我们自动完成其他部分。Daozero会利用CGLIB生成abstract的Dao实现类的子类,自动实现剩下的abstract方法。例子请参见4. 使用抽象类的例子
  • 与我联系,我非常乐意把你要求的特性加入某个版本中,并在该版本可用之前给出我个人的对过渡方法的建议
  • 7. 使用时出现问题?

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

    实际上还有几个小技巧,包括:
    (1) 支持namespace:,但是1个Dao只能限定于1个namespace。
    (2) 通过1个特别的属性(targetProperties),daozero也允许为abstract implementation的dao bean定义属性。 具体使用方法可以参照下载下来的sample。Sample是修改自Spring的JPetstore例子,在\sample\jpetstore\war\WEB-INF\dao-context.xml(jpetstore的DAO层的spring配置文件) 中你可以发现几乎所有使用daozero需要知道的事情的例子, 比较一下它和原始的Spring的JPetstore的实现例子,还可以找到daozero提倡的 如何在service层使用DAO的观点,例如daozero更提倡不要只把service作为dao层的delegation,提倡大部分对DAO的操作的组合都放到service层中去。
    (3) 从0.3版开始,daozero支持使用iBatis的RowHandler,不需要知道什么使用方法,使用时和以前一样,在接口方法中传进1个RowHandler即可,因为daozero会根据 传进来的参数的类型知道哪个是RowHandler,把RowHanlder转交给iBatis。
    (4) 从0.4版开始,daozero支持dao方法返回iBatis的PaginatedList,和返回分页后的java.util.List。为此我们需要遵守两个约定:对于需要返回PaginatedList的dao方法, 约定方法的最后一个参数的类型必须是int,用于传递分页大小;对于返回分页后的java.util.List的dao方法,约定方法的末尾两个参数的类型必须是int,用于传递分页起始行 位置和分页大小。
    (5) 从0.4版开始,daozero还提供了一个额外的特性:可以自动地把iBatis返回的java.util.List转换为其他类型,例如java.lang.String[]数组,java bean的数组, java.util.Iterator, java.util.Set,等等。
    (6) Daozero在source中提供了两个例子程序,它们覆盖了daozero的使用方法:一个是HelloWorld命令行例子程序, 演示了基本的使用方法,另一个是修改自springframework带的jpetstore例子的web应用例子,它给出了一个更接近实际应用的例子。

    9. 为什么推荐Daozero? 和源代码生成工具的比较:

    源代码生成工具(例如Abator)是我们的很好的帮手,特别是用于在项目初期启动时生成初期源代码时。 Daozero并不准备取代源代码生成工具,而是希望能从程序员们和源代码生成工具手上接管那些固定的重复的代码,希望能让这些代码从我们的视野中消失。 计算机通常在做固定的重复的事情方面比人做得更出色,更少犯错误。让源代码生成工具去负责生成那些需要留给程序员去修改的代码这样更好,而不是不必要地 生成那些固定的重复的代码而留给程序员们犯一些低级可笑的错误的机会。当然,我们将不断改进daozero,让它接管尽可能多的iBatis代码,任何新特性的引入都会 以不付出带进了很多使用复杂性的代价为前提。即使在这些新特性被引入之前,我们也完全可以以原来的方式使用相应的iBatis API,因为daozero支持abstract的 DAO实现类(详情请参照4. 使用抽象类的例子)。简洁实用,允许在abstract的DAO实现类中以原来的方式自由地使用iBatis API, 把其他工作扔给daozero去做。这是daozero遵从的准则。

    10. License:

    Apache License Version 2.0