这个思考源于最近项目中对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只是在查询的语义下显得更加适合。
————————————————————————————————————-
上面的代码经过了这样三个步骤的演进过程:
- DAO接口和实现全部都要开发人员自己实现;
- 抽象出部分共同的基础增删改查方法不需要实现;
- 将所有实现全部约束到同一个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博客在数日后将不再更新。
分享到:
相关推荐
随着微服务架构的发展,DAO模式也逐渐演变为更高级的模式,如Repository模式。Repository模式进一步提升了数据访问层的抽象,它不仅限于数据库操作,还可以涵盖其他数据源,如文件系统、远程API等。 总之,DAO模式...
然而,了解DAO对于理解数据库访问的历史和技术演进是有帮助的。 在提供的"VirtualList"压缩包文件中,可能包含了使用DAO的VC++代码示例,你可以解压后查看具体实现。这些代码会展示如何创建和使用DAO对象,如何执行...
标题中的“vc6程序:DAO控制本地MDB文件”指的是使用Microsoft Visual C++ 6.0(简称VC6)开发的应用程序,通过数据访问对象(DAO)接口来...了解DAO的工作原理和使用方式,有助于理解早期数据库编程的历史和技术演进。
在IT行业中,JSP(JavaServer Pages)是一种用于创建动态网页的技术,它允许开发者将Java代码嵌入到HTML页面中,以实现服务器端的数据...此外,持续关注项目的更新,将有助于深入理解Web开发中的最佳实践和技术演进。
分布式自治组织(DAO)是一种基于区块链技术的新型组织形态,由共享规则驱动,实现分散决策和自动化任务...随着技术的演进和社会接受度的提高,DAO有望在解决现有问题的基础上,开创出更加创新的治理模式和应用场景。
区块链的潜在用途不仅仅是加密货币。 区块链可以并且正在被用于... 本文表明,业务结构已经演变了几个世纪,而 DAO 只是另一种演变。 DAO 需要重新思考法律,包括授予 DAO 法人资格以及授予 DAO 代币持有者有限责任。
同时,由于模板方法模式遵循开闭原则,即对扩展开放,对修改关闭,所以它有利于软件系统的长期演进。 总结起来,模板设计模式在构建公共通用的Dao时发挥着至关重要的作用,它允许我们定义一个通用的数据库操作流程...
随着版本的演进,Spring 2.0 引入了定制命名空间功能以减少XML配置文件的数量,这一特性在后续版本中得到了广泛应用。到了Spring 2.5,框架进一步推出了完整的注解集,旨在替代原有的基于XML的配置方式。 #### 二、...
内容 概述和好处 TributeDAO 是一个新的模块化、低成本的 DAO 框架。 该框架旨在通过修复以下问题来改进 DAO: ...MolochDAO 框架的拟议演变 TributeDAO 框架是我们团队对 MolochDAO 生态系统的致敬
未来,DAO的发展趋势可能会向以下几个方向演进:首先,法律和监管框架的完善将为DAO提供更清晰的操作空间;其次,技术的进步,如区块链的扩容解决方案,将进一步提升DAO的处理能力和效率;再次,DAO将与传统组织更...
4. DAO层:Data Access Object(DAO)层的引入,是为了隔离数据访问逻辑,提高代码复用,简化业务层对数据操作的依赖。 5. 基础数据服务层:随着业务复杂度增加,基础数据服务层负责提供统一的数据接口,处理如缓存...
3. **维护成本增加**:随着项目的演进,直接依赖于框架内部实现的代码可能会因为框架版本更新而变得难以维护。 #### 三、解决方案:使用抽象层 为了解决上述问题,推荐的做法是在DAO层引入一个抽象层,如使用...
- **集中配置**:对关键配置项如DAO、服务URL等进行集中管理,确保配置的统一性和准确性。 - **自动下发与更新**:支持配置的实时自动下发和更新,减少手动操作带来的风险。 ##### ELB应用级超级负载均衡 - **实现...
在上述的使用经验中,开发者逐步从简单的 JDBC 数据库操作,过渡到使用数据源,再到 DAO 模式,最后接触到 Spring 框架,展示了对软件设计模式和架构演进的理解。 1. **JDBC 与数据源**:最初,开发者在 Action 类...
这是一个很好的实践案例,可以帮助你深入理解Java在嵌入式环境中的应用,同时也可以复习Java Applet的历史和技术演进。通过查看源代码并尝试运行这个小程序,你可以更深入地了解其内部运作机制,提升自己的编程技能...
【标题】:“分散自治组织:超越炒作(英).pdf” 【描述】:“分散自治组织:超越炒作(英).pdf” 本文档探讨了分散自治组织(DAOs)的...随着区块链技术的不断演进,DAOs有可能塑造未来的组织形态和协作模式。
- 引入DAO(Data Access Object)设计模式,将数据访问逻辑封装成对象,简化了数据访问过程。 - 使用ORM(Object-Relational Mapping),进一步简化数据库查询流程。 #### 二、中等规模站点架构 - **发展阶段**...
VB 的数据访问功能概述:VB 是从最早广泛用于个人计算机的 Basic 语言演变而来的,为开发人员提供了生生成强大的 Windows 95/98 和 Windows NT 兼容的应用程序的有效工具。现在对于希望快速开发和使用基于客户机/...