论坛首页 Java企业应用论坛

Dao和Service的设计方法讨论

浏览 9854 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2004-10-16  
DAO
考虑一下两种设计DAO和Service的方法。
方法一:
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);
       }
      
    }
}
大家看看这种使用方法有什么不足?
   发表时间:2004-10-16  
这个访问层真的可以隔离底层么?
感觉你的BaseDao渐渐的会发展成iBatis之类的东西
而你的service层与BaseDao的结合太紧密了,StudentDao的似乎没有很好的存在意义。
DAO除了隔离变化以外,还应该隔离不同领域的概念,你的service层对数据查询知道的太多了
举例来说,如果数据访问层出现性能之类的问题,恐怕改进起来牵一发而动全身
0 请登录后投票
   发表时间:2004-10-16  
我感觉studentDao存在的意义是重复的可以重用的BaseDao中对student的操作。
显然service对baseDao的结合是非常紧密的,但是我不认为会产生你说的问题。
如果是性能问题,那么肯定要修改代码了,无论是修改service代码还是Dao代码都是修改代码,而且我说了queryString应该是通用的,其解释应该由Dao来做。
如果由于queryString产生了性能问题,那么是一方面可以认为是Service层的人员采用了错误的方式调用了Dao,也可以理解为Dao错误的解释了queryString。
后一种情况只改dao,前一种情况改Service。
不会出现两边都改的情况。
另外性能问题,估计是比较难遇到的。
0 请登录后投票
   发表时间:2004-10-16  
结合紧密如何方便进行测试?
怎样方便的验证充斥于service层的queryString表达了正确的含义?
如果你因为重构,更改了student的name字段的名称,同时要做的改动可能会遍及整个service层,如何保证没有遗漏?
potian先生说过,DAO相当于一份显示的说明文档,维护它的工作量是值得的。
queryString不是不好,但是自己实现工作量很大,优化很难做好,而大量存在的数据访问层工具不好利用,经常会重新发明轮子。
即使要用queryString,也不要暴露给整个service,一般采用command模式来隔离业务逻辑
0 请登录后投票
   发表时间:2004-10-16  
如果你因为重构,更改了student的name字段的名称,同时要做的改动可能会遍及整个service层,如何保证没有遗漏?
===============================================
恕我无法理解,如果student更改了student的name字段,难道不需要更改Dao层吗?难道改动Dao层的难度会少于更改Service层的难度?
0 请登录后投票
   发表时间:2004-10-16  
另外我如果把这些queryString全部写在外部的一个配置文件中,然后通过查找配置文件得到这个queryString,当模式发生改变时,我也只需要去修改这个外部的配置文件,全部的queryString都在一个文件中,如何不能够修改?
0 请登录后投票
   发表时间: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,难道会影响很多地方吗?
0 请登录后投票
   发表时间:2004-10-16  
DAO绝对不应该这样设计。如果仅仅需要一个执行query语句的地方,你从某个地方把Hibernate Session暴露出来就行了。为什么我们要设计DAO这样一个对象(请注意,“对象”),不就是为了获得一个接口上的声明吗?
0 请登录后投票
   发表时间: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的方式是更糟糕的事情。
0 请登录后投票
   发表时间:2004-10-17  
  liujiboy先生说的是一个很普遍的问题,因为SQL语句的功能强大,我们完全可以把一个复杂的业务在一条SQL语句里就完成。那么业务层和持久层之间的划分就变成一种无聊的重复。但是,另一方面,不可能所有的业务都可以用SQL语句完成,这时业务层又显得必须。
  那么,到底应该如何划分业务的粒度,使得业务层和持久层不会产生重复的代码呢?
  不知道坛子里的各位同仁在实际的项目中是如何处理这个问题的呢?
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics