论坛首页 Java版 DAO

一次关于简化DAO设计的初步思考!

浏览 24684 次
该帖已经被评为精华帖
作者 正文
时间:2004-10-10
当应用中需要使用到上十张表时,DAO的维护变得日益困难,主要表现在这几个方面:
1)dao类的繁多,很多设计都是一个entity对应一个dao
2)dao接口需要维护的method庞大。
3)业务逻辑改变时,dao需要同时修改两个类文件(接口和实现类)

出于上述问题,有必要从新设计dao包。要求
1)减少dao类的数目,dao包暴露的接口稳定且易于扩展新的查询。
2)减少dao接口的method
3)可以动态增加dao提供的数据访问逻辑,比如增加相应的查询实现等等

我的分析:
可以使用Criteria,NamedQuery来构造复杂的查询逻辑。
对于改动最多的read/find逻辑,可以作一个抽象设计,以提供动态增加的同时提供稳定的接口。
对于update,create,delete逻辑,dao的设计遵循原子操作的原则,不在这些接口中实现复杂的业务逻辑。总体来说,这些接口的改动很少,也很容易规范。

下面是一些设计的类代码:
[code:1]
public interface QueryObject{
public void setNamedQuery(String qstring);
public Object query(Session session,Set parameterCollection);
public QueryCfg getQueryCfg();
public boolean validate(QuryCfg cfg,Set params) ;
public void setValidatable(boolean bool);
public booelan getValidatable();
}

public interface QueryCfg{
public List getParameterCfgs();
public String getName();
public void setName(String name);
}


public interface Parameter{
public ParameterCfg getParameterCfg();
public String getValue();

}

public interface ParameterCfg{
public int getOrder();
public void setOrder(int order);
public String getName();
public void setName(String name);
public Type getType();
public void setType();

}

public interface Type{
public static final String PRIMITIVE_TYPE="primitive";
public boolean isPrimitive();
public Class getObjectType();
public String getValue(Object o);
}



[/code:1]
上面这些基本接口便是组成我的dao包的设计基架。虽然与普通dao实现比较起来,这个设计多了许多复杂度,在项目初期也需要一定工作量来编码实现这些构架,但是当在项目进展到中后期时,这个设计已经显露了它优良的设计风格,它与普通DAO的设计相比较,有如下优点:
1)DAO的设计将会更加简洁,用一个DAO接口就可以实现所有entity的dao操作,可以动态扩展复杂查询。DAO设计:
[code:1]public interface DAO{
// find 可以根据
public Object find(String namedQuery,Set parameters);
public Object createEntity(Object entity);
public Object updateEntity(Object entity);
public void removeEntity(Object entity);
}

//具体的实现可以整合到优秀的容器里面。比如spring
publi class DAOImpl{
//....
public Object find(String name,Set ps){
return (Object)getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException {
return QueryLocator.getQueryObject(name).query(session,ps);
});

}
}
//其中QueryLocator的实现可以用Spring,PicoContainer等等。
[/code:1]

2)业务逻辑层依赖于一个较稳定的DAO接口,使得可以更独立更快速的进行开发。
在业务逻辑层,他们可以这样写代码:

[code:1]public class YourBusiness{
public void doBusiness(){
Set parameters=new LinkedHashSet();
parameters.add("firstName","test");
parameters.add(2,"test2");
List users=(List)getDao.find("findUserWithFullName",parameters);

}
}[/code:1]
这里的代码依赖于一个稳定的DAO接口,而不需要先实现DAO修改,再回来做相应的业务实现。
3)可以支持参数检查,因为dao包的设计中加入了诸如ParameterCfg,QueryCfg等配置类,通过扩展这些抽象配置类实现我们定义的查询的配置,可以有效地帮助我们检查客户端的非法查询。
4)工作量统计。可以计算一下,dao增加一个新的复杂的查询,需要增加一个QureyCfg类(可选,如果queryObject支持验证参数,则需要否则不需要),一个QueryObject类,增加Spring的一个Bean配置。
传统的DAO设计的话,需要修改一个DAO接口,修改DAO实现。
传统的DAO设计对于增加或者修改查询接口的话,工作量稍微少于我的设计。
当系统的表为10个时,传统的DAo设计一般需要产生10个dao类(需要接口的话,为20个),如果一个dao类需要支持复杂的read查询为5个的话,对于查询的method,至少需要50个method实现。
而我的设计除去Type这个底层稳定构架层次以外,仅仅需要一个DAO实现类,如果尽量采用NamedQuery,那么整体QueryObject类实现一般仅仅需要10个左右即可,QueryObject还有利于抽象复杂的查询,甚至包括需要使用到Criteria的QueryObject实现。比如这么一个抽象:
[code:1]public class CommonQuery implments QueryObject{
public Object query(Session session,Set parametes){
//验证参数合法性
if(getValidatable())
validate(getQueryCfg(),parameters);
List res = session.createCriteria(parameters.get("type"))
.add( Expression.eq(parameters.get("queryParam"),parameters.get("value")))
.list();
}
}[/code:1]
他可以抽象对某个表的某个字段等于某值的查询。
当然这些设计需要涉及到一个参数集合规范的问题,这些都是可以在项目初期很快设计明确下来以形成正式的QueryObject设计规范文档。

整体评估:
当应用中的entity数量极大时,这种dao的设计将大大优越于传统的dao设计。
   
时间:2004-10-10
好复杂的设计啊, 偶向来都是用一个map <-> criteria转换器搞定的.

http://forum.javaeye.com/viewtopic.php?t=7541&highlight=
   
0 请登录后投票
时间:2004-10-10
呵呵,今天再想了一下,确实设计得太过复杂,over design.
真正搞的话,也会采用ReadOnly的说法,呵呵,权当一次胡思乱想罢了,这些咚咚都是捉着思路一口气写下来的,谁要是看着累,还望多多海涵。 :wink:
另附一点,我这个设计主要针对动态添加查询接口,而不用修改dao,可以验证参数的合法性而作的。没有经过实际项目的时间,如果有人想继续了解,我可以做无偿的帮助。
   
0 请登录后投票
时间:2004-10-10
Map的缺点是弱类型, 不过偶这个人很懒惰的, 是绝对设计不出Parameter, ParameterCfg, Type这种东西了, 所以就偷懒用Map了.
   
0 请登录后投票
时间:2004-10-10
Readonly 写道
Map的缺点是弱类型, 不过偶这个人很懒惰的, 是绝对设计不出Parameter, ParameterCfg, Type这种东西了, 所以就偷懒用Map了.

其实我认为我这个设计最大的好处还是在于提供稳定的dao查询接口。动态增加查询的能力。paramegerCfg可以规范查询参数的设计
   
0 请登录后投票
时间:2004-10-10
map也可以达到稳定DAO api的目的, 举个实际的应用例子吧, 偶再来对应着用map来做做看
   
0 请登录后投票
时间:2004-10-10
呵呵这样说来,又是一个复杂的设计,呵呵我的over design比较暴露了。只不过多了这个参数检查而已。
   
0 请登录后投票
时间:2004-10-10
DAO的接口不应该简化,因为你需要用一个可以表达含义的名字,如果需要变化同一意义下的内部的查询实现,那么DAO本身的名字和参数不需要改变,如果你要改变的是这个方法本身的行为,那么当然需要改变了

至于Paramter和NamedQuery,Hibernate本身已经提供了,为什么还要设计一套,你有没有发现你做
[code:1]
public class YourBusiness{
...}
public void doBusiness(){
...}
Set parameters=new LinkedHashSet();
parameters.add("firstName","test");
parameters.add(2,"test2");
List users=(List)getDao.find("findUserWithFullName",parameters);
[/code:1]
的过程和Hibernate提供的大同小异吗,唯一区别就是你不需要每次都去
[code:1]
getHibernateTemplate().execute(new HibernateCallback()
[/code:1]
而已
但这个只需要几个快捷方法而已,例如带1个、2个、3个、4个、5个参数的find方法,这样写起来也很方便,一句话就可以了,可以写在DAO的超类里面,并且不应该作为外部接口暴露出来
   
0 请登录后投票
时间:2004-10-10
一开始我做这个设计的思路是想简化dao类的数目,我想通过提供一个统一而稳定的DAO接口来简化整体需要公开的dao类的数目。
不过再细细考虑,我的设计被证明是错误的。因为结果下来,我的设计是减少了公开的dao接口(今需要一个),而增加了内部实现的复杂度,需要增加一个QueryObject,QueryCfg类。还要修改相关的 配置文件(spring的bean配置)。用复杂度换外部调用的简单性,不知道这算不算一个过渡设计。
用map构造复杂查询的语句也未尝不可,正如readonly所说的一样。
   
0 请登录后投票
时间:2004-10-10
DAO本身的目的有两个:
1。提供统一的存取层,以便外部框架和机制针对这一层进行特别的处理,或者外部可以提供这一层次上的超类为你服务,你可以在一个明确的层次上重用别人的工作,但这不是专门针对DAO,而是分层结构自动带来的好处。

2。而DAO本身的目的就是为了封装数据存取的差异性,如果你直接用这种方法来做的话,DAO有什么用,根本不需要了
[code:1]
List users=(List)getDao.find("findUserWithFullName",parameters);
[/code:1]
如果DAO改成 JDBC实现,这算什么?
   
0 请登录后投票
论坛首页 Java版 DAO

跳转论坛: