Skip to content

四火的唠叨

一个纯正程序员的啰嗦

Menu
  • 所有文章
  • About Me
  • 关于四火
  • 旅行映像
  • 独立游戏
  • 资源链接
Menu

DAO 的演进

Posted on 09/28/201210/07/2024 by 四火

1 这个思考源于最近项目中对 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 等语言实现,代码可能写不了那么漂亮,不过也可以做得优雅一些,比如这种链式调用:

new 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 age > #{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 层的设计上亦如此。权衡的技巧。没有通用的和完美的解决办法,只有适合和不适合一说而已。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

×Scan to share with WeChat

你可能也喜欢看:

  1. 关于接口设计,还有 Fluent Interface,这种有趣的接口设计风格
  2. 关于 if (someobject != null) 的问题
  3. EasyMock、EasyMock Class Extension 和 PowerMock
  4. 分页的那些事儿
  5. 如何思考面向对象

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

订阅·联系

四火,啰嗦的程序员一枚,现居西雅图

Amazon Google Groovy Hadoop Haskell Java JavaScript LeetCode Oracle Spark 互联网 亚马逊 前端 华为 历史 同步 团队 图解笔记 基础设施 工作 工作流 工具 工程师 应用系统 异步 微博 思考 技术 数据库 曼联 测试 生活 眼界 程序员 管理 系统设计 缓存 编程范型 美股 英语 西雅图 设计 问题 面向对象 面试

分类

  • Algorithm and Data Structure (30)
  • Concurrency and Asynchronization (6)
  • System Architecture and Design (43)
  • Distributed System (18)
  • Tools Frameworks and Libs (13)
  • Storage and Data Access (8)
  • Front-end Development (33)
  • Programming Languages and Paradigms (55)
  • Testing and Quality Assurance (4)
  • Network and Communication (6)
  • Authentication and Authorization (6)
  • Automation and Operation Excellence (13)
  • Machine Learning and Artificial Intelligence (6)
  • Product Design (7)
  • Hiring and Interviews (14)
  • Project and Team Management (14)
  • Engineering Culture (17)
  • Critical Thinking (25)
  • Career Growth (57)
  • Life Experience and Thoughts (45)

推荐文章

  • 谈谈分布式锁
  • 常见分布式系统设计图解(汇总)
  • 系统设计中的快速估算技巧
  • 从链表存在环的问题说起
  • 技术面试中,什么样的问题才是好问题?
  • 从物理时钟到逻辑时钟
  • 近期面试观摩的一些思考
  • RSA 背后的算法
  • 谈谈 Ops(汇总 + 最终篇):工具和实践
  • 不要让业务牵着鼻子走
  • 倔强的程序员
  • 谈谈微信的信息流
  • 评审的艺术——谈谈现实中的代码评审
  • Blog 安全问题小记
  • 求第 K 个数的问题
  • 一些前端框架的比较(下)——Ember.js 和 React
  • 一些前端框架的比较(上)——GWT、AngularJS 和 Backbone.js
  • 工作流系统的设计
  • Spark 的性能调优
  • “残酷” 的事实
  • 七年工作,几个故事
  • 从 Java 和 JavaScript 来学习 Haskell 和 Groovy(汇总)
  • 一道随机数题目的求解
  • 层次
  • Dynamo 的实现技术和去中心化
  • 也谈谈全栈工程师
  • 多重继承的演变
  • 编程范型:工具的选择
  • GWT 初体验
  • java.util.concurrent 并发包诸类概览
  • 从 DCL 的对象安全发布谈起
  • 不同团队的困惑
  • 不适合 Hadoop 解决的问题
  • 留心那些潜在的系统设计问题
  • 再谈大楼扔鸡蛋的问题
  • 几种华丽无比的开发方式
  • 我眼中的工程师文化
  • 观点的碰撞
  • 谈谈盗版软件问题
  • 对几个软件开发传统观点的质疑和反驳
  • MVC 框架的映射和解耦
  • 编程的未来
  • DAO 的演进
  • 致那些自嘲码农的苦逼程序员
  • Java 多线程发展简史
  • 珍爱生命,远离微博
  • 网站性能优化的三重境界
  • OSCache 框架源码解析
  • “ 你不适合做程序员”
  • 画圆画方的故事

近期评论

  • panshenlian.com on 初涉 ML Workflow 系统:Kubeflow Pipelines、Flyte 和 Metaflow
  • panzhixiang on 关于近期求职的近况和思考
  • Anonymous on 闲聊投资:亲自体验和护城河
  • 四火 on 关于近期求职的近况和思考
  • YC on 关于近期求职的近况和思考
  • mafulong on 常见分布式基础设施系统设计图解(四):分布式工作流系统
  • 四火 on 常见分布式基础设施系统设计图解(八):分布式键值存储系统
  • Anonymous on 我裸辞了
  • https://umlcn.com on 资源链接
  • Anonymous on 我裸辞了
© 2025 四火的唠叨 | Powered by Minimalist Blog WordPress Theme