`

DAO的演进

阅读更多

这个思考源于最近项目中对DAO的使用和讨论。数据访问对象,在贫血模型下,要怎样去设计,框架需要完成什么,后续的开发人员需要关注什么,设计的时候到底需要把握怎样的粒度?

最早做项目的时候,是老老实实给每个必要的模型增加DAO接口和实现类的:

public interface IUserDAO{
    public long add(User user);
    public void delete(User user);
    public int count(String condition);
    ... ...
}
public class UserDAOImpl{
}

这样做的好处是针对每个模型都可以自由地扩展和定义想要的数据访问方法,但是明显缺乏控制,每个人实现自己的东西,基础增删改查这种通用的逻辑没有办法规约起来,也没有办法重用起来

查询条件的部分,上面用了一个字符串拼接sql语句的片段传入,这其实是让数据层的东西泄漏到业务层去了,不是一个好的实现;但是也要看到,对于复杂的查询方案,这又是比较容易实现的。

————————————————————————————————————-

后来做了一些改进,采用了下面这种DAO模型:

IBaseDAO ← BaseDAOImpl
      ↑                      ↑
IUserDAO ← UserDAOImpl

IUserDAO实现IBaseDAO接口,同时BaseDAOImpl是IBaseDAO的一个增删改查的基本实现,而UserDAOImpl继承自BaseDAOImpl,又实现了IUserDAO接口。

这样一来起码增删改查这样标准的简单操作全部统一起来了,也不需要在各个模型中重新定义。借由iBatis框架,把SQL语句全部放到xml里去,而又因为有了BaseDAOImpl这个通用实现,对于大多数只需要增删改查的模型来说,在实现类中就不需要做任何事情了。

对于条件查询,部分可以通过对模型中字段取值的特殊情况来处理,name取值为null表示不把该字段放入where子句中,否则则作为匹配条件:

<if test="name != null">
    AND NAME LIKE '%#{name}'
</if>

不过把增删改查(CRUD)这样的基础方法(或者可以增加一些其他的方法)放到基类中也存在一些问题。比如有的类其实不需要update方法,但是没有办法,BaseDAOImpl给实现了——换言之,实现或暴露了本不想实现或暴露的方法,这是让DAO的调用者不舒服的地方。

对于复杂的查询,当时我们引入了少量查询对象,避免了DAO的以外的上层去拼接SQL语句。但是查询对象并不总是一个好东西,往往使得整个对象很庞大,设计很臃肿:

Criteria c = session.createCriteria(User.class);
c.add(Restrictions.eq("name",name));
c.add(Restrictions.lt("age", 18));

如果是某些动态语言,查询对象可以做到优雅一些:

userDAOImpl.query({
    name: 'Jimmy',
    desc: {like: '%funny'}
    age:  and(
        {lt:30},
        {gt:18}
    )
});

如果用Java等语言实现,代码可能写不了那么漂亮,不过也可以做得优雅一些,比如这种链式调用:

CriteriaBuilder.eq("name", "Jimmy").like("desc", "%funny").and().gt("age", 18).lt("age", 30).and0().toCriteria();

————————————————————————————————————-

最近的项目,则是干脆把实现类全部都省了,用Spring对AOP支持的方式,把这些DAO的实现全部指引到一个GenericDAOImpl上了:

public interface IBaseDAO<T>{
    List<T> list(Map<String, String> conditions);
    void create(T object);
    ... ...
}
public class GenericDAOImpl<T> extends DAOSupport implements IBaseDAO<T>{
}

不同的模型DAO可以完成自己各异的查询方法定义,但是最基础的增删改查全部都由IBaseDAO定义,而所有DAO的实现全部都被Spring拦截后指向GenericDAOImpl完成——换言之,不需要写任何DAO的实现类,而且连类定义都免了

但是有利必有弊,除了前面提到的会不得不暴露所有增删改查基础接口的问题,这样的方式还使得对每个DAO做不同的灵活扩展不太容易,而且固定的接口为了通用性可能显得有些啰嗦(比如我在查询时只需要返回一个数的时候,由于查询接口被定义为返回一个对象的链表,所以被迫要把这个数封装到对象里,再塞进一个链表中返回),当然这也算是框架给开发人员带来的约束力。

值得一提的是,查询条件呢?这次用一个Map来承载,看起来这样查询条件的控制就比较灵活,比如:

map.put("name", "Jimmy");
map.put("ageGreatThan", "18");

而这样的map业务语义只有到了存储查询sql的xml中才能被理解,例如上面的条件也许会变成这样的子句:

where name like '%#{name}'
<if test="ageGreatThan != null">
    and ang > #{ageGreatThan}
</if>

总之,相较于查询对象,用map的方式就要自如得多。但是有利必有弊,map方式也存在一些问题,比如多数情况下嵌套层次不如对象易于理解,比如说对开发人员的约束力弱,实现可能五花八门,而且如果拼写错误,在insert/update/delete操作的时候后果会尤其严重。举例来说,有这样一条SQL:

delete from user u
where
<if test="name != null">
u.name = '#{name}'
</if>
... ...

要根据用户名字来删除记录,如果匹配该条件的参数写错了,比如写成这样(多写了一个“s”):

map.put("names", "Jimmy");

就失去了通过该条件寻找被删除条目的能力,导致全表数据被清空。所以通常不建议在update/delete/insert的时候使用map来传递参数,还是考虑对象方式传参优先,map只是在查询的语义下显得更加适合。

————————————————————————————————————-

上面的代码经过了这样三个步骤的演进过程:

  1. DAO接口和实现全部都要开发人员自己实现;
  2. 抽象出部分共同的基础增删改查方法不需要实现;
  3. 将所有实现全部约束到同一个DAOImpl中,开发人员只需要实现各个模型的DAO接口。

看起来逐步地后续开发人员的工作似乎越来越少了,那么能不能达成终极的第4步,把这个工作全部省去,让DAO层完全由框架自动完成呢?

其实也是可以的,只是这个时候DAO方法的执行只能被约束在比较有限的几个增删改查基础方法之内了,这样的DAO是完全不具备业务语义的——换言之,真正将业务逻辑从DAO解耦出去了。

这种情况下后续的开发人员只需要完成存放SQL的xml文件,如果命名按照规约来办,连这个存放SQL的xml文件都可以省去(请参见Grails利用Hibernate自动生成数据库、增删改查的SQL语句,自动完成OR mapping的过程),只是,很多情况下看起来美好而已,这样的解耦未必是一件好事:我们始终要在各种利弊的分析和选择中权衡,如果因为性能等原因需要涉及到联表查询怎么做?业务语义已经不能侵入DAO层了,那么只能以某种方式在DAO外上方的Service来实现条件的拼装,可以用代码来实现,也可以用某种自定义的DSL来实现,这又容易显得过于臃肿了。

所以,兼容也好,灵活也好,都要讲究个度,在DAO层的设计上亦如此。权衡的技巧。没有通用的和完美的解决办法,只有适合和不适合一说而已。

文章系本人原创,转载请注明作者和出处(http://www.raychase.net

注:本博客已经迁移到个人站点 http://www.raychase.net/ ,欢迎大家访问收藏,本ITEye博客在数日后将不再更新。

0
0
分享到:
评论

相关推荐

    泛型dao 泛型dao 泛型dao

    Struts2、Hibernate、Spring整合的泛型DAO (本人评价: 代码开发效率提高30% 代码出错率减少70%) 对于大多数开发人员,系统中的每个 DAO 编写几乎相同的代码到目前为止已经成为一种习惯。虽然所有人都将这种重复...

    DAO设计模式DAO设计模式

    DAO设计模式DAO设计模式DAO设计模式DAO设计模式DAO设计模式DAO设计模式DAO设计模式DAO设计模式DAO设计模式DAO设计模式

    dao层基类dao层基类dao层基类

    dao层基类dao层基类dao层基类dao层基类dao层基类

    Struts+DAO登陆Struts+DAO登陆

    Struts+DAO登陆Struts+DAO登陆Struts+DAO登陆Struts+DAO登陆Struts+DAO登陆Struts+DAO登陆Struts+DAO登陆

    dao3.5数据库安装文件

    DAO数据库安装文件。

    C#特性标签实现通用Dao层

    C#特性标签实现通用Dao层C#特性标签实现通用Dao层C#特性标签实现通用Dao层

    J2EE之DAO设计模式

    暴露给客户端的DAO接口在低层数据源的实现发生改变时并不会随着改变,所以这种设计模式使得DAO可以适应不同的数据储存方式类型而不影响客户端和商业组件.最主要的, DAO还在组件和数据源之间扮演着协调者的角色. DAO...

    Dao Jet数据库引擎

    这是从VC6光盘提取的,遇到“无法对DAO/Jet db引擎进行初始化”问题的伙计们,可以下载此Dao Jet数据库引擎重新安装即可。

    dao包封装包

    dao封装包

    如何重构DAO模式

    使用数据访问对象(DAO,Data Access Object)模式来抽象和封装所有对数据源的访问。DAO管理着与数据源的连接以便检索和存储数据。可以降低商业逻辑层和数据访问层的耦合度,提高应用的可维护性和可移植性。 由于...

    HibernateDao 通用

    HibernateDao 通用HibernateDao 通用HibernateDao 通用HibernateDao 通用HibernateDao 通用HibernateDao 通用HibernateDao 通用HibernateDao 通用HibernateDao 通用HibernateDao 通用HibernateDao 通用HibernateDao ...

    DAO设计模式 DAO 设计模式 JAVA设计模式

    DAO设计模式 DAO 设计模式 JAVA设计模

    DAO详解 详细讲述了DAO的使用

    这是我从网上收集而来的,详细讲述了DAO的使用,我觉得对学习DAO编程很有帮助

    DAO35资源比

    DAO35

    9.DAO数据库操作演示(Visual C++编程 源代码)

    9.DAO数据库操作演示(Visual C++编程 源代码)9.DAO数据库操作演示(Visual C++编程 源代码)9.DAO数据库操作演示(Visual C++编程 源代码)9.DAO数据库操作演示(Visual C++编程 源代码)9.DAO数据库操作演示...

    DAO3.6支持64位win10

    win10不再支持DAO,,某些数据库出现问题,需要单独安装DAO并进行注册,

    一个很好的通用泛型dao(含源码)

    为什么我们要使用通用DAO接口呢,因为我们的数据库操作无非是增删改查,CRUD操作,我们不需要为每个实体去编写一个dao接口,对于相似的实体操作可以只编写一个通用接口,然后采用不同的实现! DAO已经成为持久层...

    dao jet数据库引擎

    有些计算机缺失这个文件,导致有些软件运行不起来,出现“无法对DAO/Jet db引擎进行初始化”的问题,下载后解压,并运行起来就可以了。

    DAO白皮书中文版

    DAO白皮书中文版 去中心化自治组织 DAO 以太坊 智能合约 区块链

Global site tag (gtag.js) - Google Analytics