Topic: 让DaoZero替我们实现DAO接口 |
Print this page |
1.让DaoZero替我们实现DAO接口 | Copy to clipboard |
Posted by: suntoe Posted on: 2006-05-05 16:05 DaoZero是个Open Source的Spring Bean。(http://dao-zero.sourceforge.net) 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的参数(形参)的数量及出现顺序保持一致,通过做出这些约定(应该不太难于遵守吧),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 |
2.Re:让DaoZero替我们实现DAO接口 [Re: suntoe] | Copy to clipboard |
Posted by: jigsaw Posted on: 2006-05-05 18:32 哇 作者 支持! |
3.Re:让DaoZero替我们实现DAO接口 [Re: jigsaw] | Copy to clipboard |
Posted by: suntoe Posted on: 2006-05-06 14:32 谢谢.急盼大家的反馈以便改进。 |
4.Re:让DaoZero替我们实现DAO接口 [Re: suntoe] | Copy to clipboard |
Posted by: floater Posted on: 2006-05-08 00:56 Here is what I think(I could be wildly wrong, please forgive that):
I think with Spring's support, all the methods in dao class are just one line method, they serve the purpose to hook the sql code to the java objects. In your example, you pass in user id and email in order to get the Account object, and then use a hashmap to hold user id and email. In fact, we should design the DAO interface to work with Account directly:
The way to use it is that we initiate an Account object, fill in the user id and email, pass it to dao, then dao fills in the rest field for us. Since iBATIS ca work with object fields pretty well, DAO impls based on Spring should be just one line. Another approach with this design is that the database keys are hidden in the account objects, so that if the keys are changed, we can remap the keys in the DAO impl without touching the caller's code. I've had several cases where we have to remap the keys. In normal cases, the data persistence tends to live longer than code, however, when there is a company merge, we want to merge the data as well, so I experienced from an int user id, an 8-char IBM racf id, 3 or 4 user code(this is really stupid ids from IBM's suggestion back in the old days), a plain String, a 36 alphanumeric id, all the way to the DN name in the digital certificates. So the key mappings and validations can be crazy. Secondly, if I am right, you are trying to map the method names to the sql statement tags in iBATS xmls, using conventions. So we have to have keep the names in sync, one is in iBATIS, the other is in Spring DAO support class. While I don't know what you are doing underlying, my intuition is that if both names are set to the method name(which is an excellent idea), the problem now is boiled down to how to get the current method name. For example, in iBATIS we have
Then plain Spring class has
Both red tags have to be match. Now since the method name can arbitrary, if you want to utilize this freedom, then if we can stuck the method name into the tag, the problem is really now how to get the method name within the current method. It seems to me this is a general question rather than something that we need to hack into iBATIS. Here are two google links with the search "java method name": http://www.rgagnon.com/javadetails/java-0420.html http://dev.kanngard.net/Permalinks/ID_20030114224837.html While the way to get the method name is slow, we can always cache the result in the dao class(in the spring init method), so the speed is not quite relevent. Last, if I do manual coding, I still prefer explicit naming rather than implicit naming convention. This is mainly from the maintanence concern, i.e., easier to read. If it's a well known, standard convention, it's OK, otherwise ... . However, this convention can be quite useful if we want to build something on top of that, like Rails on something stuff because then it could fall through. So I can foresee some quite usefulness based on this idea. Great observation!!! |
5.Re:让DaoZero替我们实现DAO接口 [Re: floater] | Copy to clipboard |
Posted by: suntoe Posted on: 2006-05-08 02:01 非常感谢你提的意见。我尽力回答, 1.象"public void load(Account account);"这样的接口方法是一种好的实践,daozero也支持这种方法,没问题的(可能是给出的例子有所误导,很抱歉)。而到底用Bean作参数还是用String/int/...等primitive类型作参数,是设计者自己的选择或受限制下的选择。实际上,还有第三种方式,即传1个Map,daozero同样会把它转给iBatis。 至于在传bean的情况下是否有必要仅为了节省1行代码而使用daozero,我的观点是: (1)能省则省,只要daozero要求的约定足够简单,为什么要让程序员去写重复类似的代码? (2)对于大部分dao接口来说,daozero都能代劳,因此甚至有些实现类可以完全去掉。 (3)这对于减少写TestCase的工作量也许也有好处。 2. 关于在daozero中如何取得method名是这样的:JDK的动态proxy机制使用的InvocationHandler和CGLIB使用的MethodInterceptor在他们的invoke和intercept方法中都提供了当前被调用方法的java.lang.reflect.Method,所以不需要用stack trace等方式,而且动态proxy机制和CGLIB导致的性能下降值是数百纳秒,对于数据库操作可以忽略不计;所以,这也是要求method名和iBatis的statement名必须一致的原因。 3.关于“Last, if I do manual coding, I still prefer explicit naming rather than implicit naming convention. This is mainly from the maintanence concern, i.e., easier to read. If it's a well known, standard convention, it's OK, otherwise ... . However, this convention can be quite useful if we want to build something on top of that, like Rails on something stuff because then it could fall through.” 老实说,没看得太明白。我的考虑是,“method名和iBatis的statement名必须一致”这个约定对于大部分情况是可以被接受的,我个人经历的是在不知道daozero的许多人都这么在用,实际上我就是因此而想到可以利用这种编程习惯来用daozero节省些实现代码的。但是做daozero的初衷也不是强迫大家必须遵守这个约定,daozero既支持自动实现1个DAO接口,也支持自动从1个abstract class继承出新的实现类,新的实现类会尝试实现基类未实现的abstract的method,只要该基类继承自spring的iBatis support类。这样就为使用者提供了把大部分的工作交给daozero而保持自己手工完成1部分代码的灵活性,这部分手工代码和我们一直在写的没有什么不同。 实际上,daozero一点忙都帮不上的地方是dao需要组合其他dao操作时的情况(象spring带的jpetstore例子就是这样做的)。所以daozero更提倡不要只把service作为dao层的delegation,提倡大部分对DAO的操作的组合都放到service层中去,具体例子可以比较一下spring jpetstore的service/dao和daozero改自spring的jpetstore的service/dao。 |
6.Re:让DaoZero替我们实现DAO接口 [Re: suntoe] | Copy to clipboard |
Posted by: lianqing_45 Posted on: 2006-05-10 09:11 虽说没有什么难认的单词,不过感觉还是有点不爽,敢问下floate是中国人还是 外国人?不能用汉语写吗? |
7.Re:让DaoZero替我们实现DAO接口 [Re: suntoe] | Copy to clipboard |
Posted by: floater Posted on: 2006-05-11 22:37 chinese, on an english system, with no admin right to install anything, so can't type in chinese. |
8.Re: [Re: suntoe] | Copy to clipboard |
Posted by: suntoe Posted on: 2006-05-16 23:34 这个小东西是我做的1个方便基于ibatis+spring开发的小工具,大家鼓励我会更有干劲继续下去!请大家尽情提意见。 iBatis 的实际应用中,其实工作量大的不是在于dao-zero关注的代码部分,而是写sql statement,但是我自己的观点是只要代价不大,能机器做的就不要人工做--程序员应该把精力放在机器所不能完成的事情上,别去做繁复的事情。 dao-zero的使用方法简单,几乎不用配置,而且所要求的statement名与方法名保持一致等约定遵守起来也很容易,再者,得益于cglib,也允许混合使用原来的iBatis API,所以我有了 dao-zero这个想法以后,就认为它容易实现,容易使用,值得公开给大家试试。 从包括其他地方得到的反馈来看,喜欢用户代码生成的不少,代码自动生成在具体应用时有一定针对性,但是个人认为因为难有普遍性,只能流传于创建者周围(比如公司/项目),因为往往会太细节化而导致使用者不得不深入了解模板。 |
Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1 客服电话 18559299278 客服信箱 714923@qq.com 客服QQ 714923 |