锁定老帖子 主题:Dao和Service的设计方法讨论
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2004-10-16
方法一: public class StudentDao { public List getStudents(String queryString){ //find是已经构造好的查找方法 return find(queryString); } } public class StudentService { public List getAllStudentNames() { return studentDao.getStudents("select name age from student"); } public List getAllStudentIds() { return studentDao.getStudents("select id age from student"); } } 方法二: public class StudentDao { public List getStudentNames(){ return find("select name from student"); } public List getStudentIds(){ //find是已经构造好的查找方法 return find("select id from student"); } } public class StudentService { public List getAllStudentNames() { return studentDao.getStudentNames(); } public List getAllStudentIds() { return studentDao.getStudentIds(); } } 两种方法各有优势。其中第一种方法简单,但是暴露了底层的实现细节。Service层的编写人员需要构造queryString,当然queryString不一定就是sql,也可能是项目组实现约定的queryString,Dao的编写者负责把这个queryString,变成操作数据的操作。 第二种编写方法很复杂,并且很多时候显得多余了。但是Service层不需要知道Dao是如何编写的,只要Dao提供了足够的接口。最容易出现的问题是,一旦Service层需要一个getStudentAges,而Dao没有编写,那么就惨了,这个时候Service层的编写人员只能等待Dao的编写者编写。 那种方式更好?我在网上看到的demo中两种方式都有采用,但是demo都是很小的东西,完全体现不出到底哪个有优势。以第一种方式来说,虽然Service层的编写人员需要构造一个让底层能够理解的queryString,但是造成的耦合并不是想象的如此严重。因为一旦数据访问发生改变,例如改变了数据库,Dao的编写者只要重新解释这个queryString就可以了。严格来说sql作为通用的queryString进行传递也不是不可以的,因为如何解释queryString完全是Dao编写者的问题,只是queryString设计得不好,可能Dao的解析工作很麻烦。另一种方法是将queryString变成一个通用的api,这样Dao的解析过程也比较容易,毕竟string解析不用到编译原理是不可能的,而设计好的api,其语义是非常明确的。 来看看第二种方式,虽然Service层和Dao层的耦合降低到了0,但是Dao可能没有提供足够的接口,这是严重的问题。另外Dao层虽然不用不用编写复杂的queryString解析,但是却需要编写更多的方法,这样Dao还是非常庞大。更重要的是在国内的环境下,软件分工并不明确,经常一个程序员既要写Service又要写Dao,第二种方式难免在这种情况下让程序员产生厌恶。 目前我的程序采用了第二种方式,但是由于显著的复杂性,正准备转换到第一种方式(我实际上是从view一直写到Dao,严格的分层,好痛苦)。 采用第一种方式,我准备设计一个BaseDao,包括基本的load,add,edit,delete和find操作。然后针对不同的情况实现其他的Dao,比如操作Student,那么就有StudentDao,但是查询操作不再提供各个方法只针对一些必要的操作封装。 例如 public class BaseDao{ public Object load(String id) { load obj; } public void add(Object obj) { add obj; } public void edit(Object obj) { edit obj; } public void delete() { delete obj; } public List find(String queryString) { find obj by queryString; } } 然后是我的studentDao public class StudentDao { //用组合而不用继承,这样是有好处的,今后一旦改变数据库也是baseDao改变而已 private BaseDao baseDao; setter and getter of baseDao public Student loadStudent(String id) { return (Student)load(id); } public void addStudent(Student student) { baseDao.add(student); } public void editStudent(Student student) { baseDao.edit(student); } public void deleteStudent(Student student) { baseDao.deleteStudent(student); } //一些可能要重复使用的方法,通常是些查询操作,但是会用到多个地方,这时候就可以将其抽取出来放在Dao中,当然也也可以是直接的放在Service层就可以了。另外一些和Student相关的数据访问操作可能也放在下面。例如如下方法: public boolean hasSelectedTheCourse(Student student,Course course) { 检查student是不是已经选择了这个course。 } } 看看service层的调用 public class Student { public void addCourse2Student(String studentId,String courseId) { Student student=studentDao.getStudent(studentId); Course course=courseDao.getCourse(courseId); if(studentDao.hasSelectedTheCourse(student,course)) throw new Exception(); student.getCourses().add(course); studentDao.editStudent(student); } //下面是查询 public List getStudentNames() { return baseDao.find("select name from student"); } public List getStudentNamesAndIds() { List list =baseDao.find("select name,id from student"); //返回一个Object[]的list List returnList=new ArrayList(); for(int i=0;i<list.size();i++) { Object[] objs=(Object[])list.get(i); String name=(String)objs[0]; String id=(String)objs[1]; NameAndIdVo nameAndId=new NameAndIdVo(name,id); returnList.add(nameAndId); } } } 大家看看这种使用方法有什么不足? 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2004-10-16
这个访问层真的可以隔离底层么?
感觉你的BaseDao渐渐的会发展成iBatis之类的东西 而你的service层与BaseDao的结合太紧密了,StudentDao的似乎没有很好的存在意义。 DAO除了隔离变化以外,还应该隔离不同领域的概念,你的service层对数据查询知道的太多了 举例来说,如果数据访问层出现性能之类的问题,恐怕改进起来牵一发而动全身 |
|
返回顶楼 | |
发表时间:2004-10-16
我感觉studentDao存在的意义是重复的可以重用的BaseDao中对student的操作。
显然service对baseDao的结合是非常紧密的,但是我不认为会产生你说的问题。 如果是性能问题,那么肯定要修改代码了,无论是修改service代码还是Dao代码都是修改代码,而且我说了queryString应该是通用的,其解释应该由Dao来做。 如果由于queryString产生了性能问题,那么是一方面可以认为是Service层的人员采用了错误的方式调用了Dao,也可以理解为Dao错误的解释了queryString。 后一种情况只改dao,前一种情况改Service。 不会出现两边都改的情况。 另外性能问题,估计是比较难遇到的。 |
|
返回顶楼 | |
发表时间:2004-10-16
结合紧密如何方便进行测试?
怎样方便的验证充斥于service层的queryString表达了正确的含义? 如果你因为重构,更改了student的name字段的名称,同时要做的改动可能会遍及整个service层,如何保证没有遗漏? potian先生说过,DAO相当于一份显示的说明文档,维护它的工作量是值得的。 queryString不是不好,但是自己实现工作量很大,优化很难做好,而大量存在的数据访问层工具不好利用,经常会重新发明轮子。 即使要用queryString,也不要暴露给整个service,一般采用command模式来隔离业务逻辑 |
|
返回顶楼 | |
发表时间:2004-10-16
如果你因为重构,更改了student的name字段的名称,同时要做的改动可能会遍及整个service层,如何保证没有遗漏?
=============================================== 恕我无法理解,如果student更改了student的name字段,难道不需要更改Dao层吗?难道改动Dao层的难度会少于更改Service层的难度? |
|
返回顶楼 | |
发表时间:2004-10-16
另外我如果把这些queryString全部写在外部的一个配置文件中,然后通过查找配置文件得到这个queryString,当模式发生改变时,我也只需要去修改这个外部的配置文件,全部的queryString都在一个文件中,如何不能够修改?
|
|
返回顶楼 | |
发表时间:2004-10-16
你提到studentDao完全没有必要存在,因为studentDao不过是一种重复,我的studentService实际严重依赖baseDao。那么完全可以将baseDao提到studentService。我以前认为存在studentDao是有必要的,因为service层需要控制事务,而如果studentDao提供了原子操作,那么就可以对这些操作进行组合。而如果把studentDao中的方法都提到service,那么这种好处就没有了。因为每个service都是独立控制事务的。那么如果组合这些service方法来构造一个大的service方法就无法控制事务了。
例如 public class StudentService{ public Student getStudent(string id){ return (Student)baseDao.load(id); } public void editStudent(Student student){ //有事务处理 baseDao.edit(student); } public boolean hasSelectedTheCourse(Student student,Course course) { 检查student是不是已经选择了这个course。 } public void setCourse2Students(String ids[],Course course) { //有事务处理 for(int i=0;i<ids.length;i++) { Student student=getStudent(ids[i]); if(hasSelectedTheCourse(student,course) { throw new Exception("Student has already selected the course"); } student.setCourse(course); editStudent(student); } } } 按照事务的概念,当setCourse2Students执行过程中发生异常是,必须回滚所有的操作。但是由于editStudent独立提交了事务,因此在出错之前的都无法回滚! 而如果采用原子的Dao,就可以用下面的方式: public void setCourse2Students(String ids[],Course course) { //有事务处理 for(int i=0;i<ids.length;i++) { Student student=studentDao.getStudent(ids[i]); if(hasSelectedTheCourse(student,course) { throw new Exception("Student has already selected the course"); } student.setCourse(course); studentDao.editStudent(student); } } 由于studentDao没有控制事务,因此事务是可以回滚的。 但是如果使用了spring框架,用spring来控制事务,第一种写法就是可能是完全合适的。我没有做试验,但是spring的事务控制机制似乎就是这样的。 如果这样是成立的,那么studentDao中的crud操作就完全可以取消了。即使不采用spring框架,我们也完全可以采用其他方式来处理。 如此一来,Dao就瘦身为一个BaseDao了,而queryString写配置,并由BaseDao来解析。 Dao是不是应该这么瘦呢?我现在都有点动摇了。 这样看似乎真的没有多大问题,数据访问的更改顶多动到一个配置文件和一个BaseDao,难道会影响很多地方吗? |
|
返回顶楼 | |
发表时间:2004-10-16
DAO绝对不应该这样设计。如果仅仅需要一个执行query语句的地方,你从某个地方把Hibernate Session暴露出来就行了。为什么我们要设计DAO这样一个对象(请注意,“对象”),不就是为了获得一个接口上的声明吗?
|
|
返回顶楼 | |
发表时间:2004-10-17
如果写一个类
public class QueryStringConstants { public final STUDENTNAMES_QUERY="select name from student"; public final STUDENTIDS_QUERY="select id from student"; } 如果真的出现了模式改变的情况,修改这个文件就ok了。 如果改成了hibernate或者jdo,只要这个产品支持queryString那么就不会有太大的问题。 而如果是EJB,估计就会出较大的问题,但是EJB作为存储实在是个非常坏的主意,如果需要很多查询EJB显然不合适的,其查询能力太低,代价太高。我到是见过EJB系统中为了性能使用sql的情况。 另一种方式就是把query封装成为一个类。 楼上的暴露session的方式是更糟糕的事情。 |
|
返回顶楼 | |
发表时间:2004-10-17
liujiboy先生说的是一个很普遍的问题,因为SQL语句的功能强大,我们完全可以把一个复杂的业务在一条SQL语句里就完成。那么业务层和持久层之间的划分就变成一种无聊的重复。但是,另一方面,不可能所有的业务都可以用SQL语句完成,这时业务层又显得必须。
那么,到底应该如何划分业务的粒度,使得业务层和持久层不会产生重复的代码呢? 不知道坛子里的各位同仁在实际的项目中是如何处理这个问题的呢? |
|
返回顶楼 | |