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