`
vlinux
  • 浏览: 53399 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

Rapae 弱化DAO的一种方法

阅读更多

        可怜的DAO层已经被各位大侠蹂躏得体肤完肤了,从范型DAO一直被蹂躏到现在只剩下一个可怜巴巴的接口,无不体现Java人追求敏捷开发的热情。其实,DAO层本来的作用就应该自从Hibernate一类优秀的ORM框架诞生之日起就应该消失灭迹了的。既然如此,那么我们就毁灭得更彻底一点。

 

下面是我对Service与DAO层整合的一些构想

 

约定优先于配置
一、Rapae代理接口标签定义

query属性:查询语句或者命名查询名称
  1. 不止由一个单词组成时:简单查询语句
  2. 由单个单词组成时:为命名查询,名称规则为[query的值]
  3. 为默认值""时:为命名查询,名称规则为[类名.方法名]
count属性:用于统计个数的查询语句或者命名查询名称,固定返回为一个Long型的数据
  1. 不止由一个单词组成时:简单查询语句
  2. 由单个单词组成时:为民命查询,名称规则为[count的值]
  3. 为默认值""时:自动生成count(*)语句
    • query为语句查询时:count自动生成为语句查询,规则为:select count(*) + query从第一个from开始到语句结束
    • query为命名查询时:count自动生成为命名查询,规则为:[query命名查询名称_count]
PS:当接口的方法没有Rapae注释的时候,按query与count均属于默认值情况处理








二、Pagination翻页注释

注释定义:
  1. FirstResult注释:方法级上的Annotation,它标记了分页查询时所要知道的第一条记录所在的位置。期待的类型为int
  2. MaxSize注释:方法级上的Annotation,它标记了分页查询时所要知道的每页最大查询记录数。期待的类型为int
  3. Total注释:方法级上的Annotation,它标记了分页查询后返回的总数统计。期待的类型为long
  4. Result注释:方法级上的Annotation,它标记了分页查询后返回的结果。期待的类型为java.util.Collection<E>
详细说明:
  1. 分页查询时:第一个参数必须是能同时提供有FirstResult和MaxSize注释方法的类,并且方法期待的返回类型都必须匹配,否则将会抛出异常。Rapae通过调用被标注的方法来进行分页查询。
  2. 分页查询返回时:返 回的类必须同时提供有Result和Total注释的方法,参数个数为1,并且期待的类型都必须匹配,否则将会抛出异常。与此同时,若返回的类同时还能提 供FirstResult、MaxSize注释的方法,方法的参数个数为1且为期待类型,那么Rapae在分页查询完成后将查询用到的 FirstResult、MaxSize值原封不动的通过标注的方法设回给返回类。






三、CRUD基本查询注释

注释定义:
  1. Create注释:格式 T [方法名] (T t);
  2. Read注释:格式 T [方法名] (java.io.Serializable pk);
  3. Update注释:格式 T [方法名] (T t);
  4. Delete注释:格式 T [方法名] (java.io.Serializable pk);
一旦被标注上了CRUD标签,则必须严格遵循标签所规定的格式,否则抛出异常。






四、查询行为方式与方法的返回类型、参数类型、参数个数以及方法名称之间的约定

查询行为方式
  1.  
    1. 执行查询
      • 返回值必须为void
    2. 查询多条记录(不翻页)
      • 返回值是java.util.Collection<E>的实现
    3. 查询多条记录(翻页)
      • 返回类必须同时提供标注有Result与Total的方法,并且参数个数为1,参数类型为标签所期待的类型
      • 返回类可以选择性的提供标注有FirstResult和MaxSize的方法,并且参数个数为1,参数类型为标签所期待的类型
      • 传入的第一个参数必须同时提供标注有FirstResult与MaxSize的方法,参数个数为0,返回类型为标签所期待的类型
    4. 查询单条记录
      • 返回值必须是一个对象
查询条件传递:
  1. 若是翻页查询,则参数个数至少为一个,且第一个参数必须为提供标标注有FirstResult与MaxSize的方法。
  2. 若查询条件是通过可变参或者Collection集合类进行传递的,则按顺序对查询条件进行设置
  3. 若查询条件是通过参数列表直接传递进来的,则按参数列表定义的顺序对查询条件进行设置
  4. 若查询条件是通过Map传递进来的,则可通过Map对参数的参数进行设置
分享到:
评论
34 楼 icewubin 2008-06-26  
zhu_chen001 写道
@Transactional 
public abstract class AccountServiceImpl extends JpaDaoSupport implements AccountService {  
      
    public Account login(String username, String password) {  
        List<?> result = getJpaTemplate().find("from Account where username = ? and password = ?", username, password);  
        return (Account) (result.isEmpty()?null:result.get(0));  
    }  
 
}

一看就是一个DAO的翻版,真正的逻辑层应该屏蔽sql的出现,从而使函数共用达到最高,所以dao可以简化,但是不会消失,否则也只是把dao和manager混合,反而没有达到效果


所以我建议dao和单表操作的manager混合,就是避开你说的这个问题。

但是我还是要说,dao不可能屏蔽sql(包括hql、qbc、qbe),也没有必要屏蔽。
完全屏蔽在稍微复杂一点的系统中,由于团队中水平的高低,绝对会带来性能问题,而且还不好查,不管是orm还是其他方式实现DAO,都需要灵活控制sql或者是sql语句的生成。
33 楼 vlinux 2008-06-26  
EQL、HQL的出现就是屏蔽了具体的SQL。

DAO也许不会消失,但是绝对不是这个理由
32 楼 zhu_chen001 2008-06-26  
@Transactional 
public abstract class AccountServiceImpl extends JpaDaoSupport implements AccountService {  
      
    public Account login(String username, String password) {  
        List<?> result = getJpaTemplate().find("from Account where username = ? and password = ?", username, password);  
        return (Account) (result.isEmpty()?null:result.get(0));  
    }  
 
}

一看就是一个DAO的翻版,真正的逻辑层应该屏蔽sql的出现,从而使函数共用达到最高,所以dao可以简化,但是不会消失,否则也只是把dao和manager混合,反而没有达到效果
31 楼 vlinux 2008-06-26  
<p>Service层应该避免相互感知,DAO层也应该避免相互感知。这样可以避免不必要的循环依赖,而且还可以避免更隐蔽点的循环调用。</p>
<div class='quote_title'>icewubin 写道</div>
<div class='quote_div'>我认为一般情况,可以约定不允许在service中注入service。“只能在service中注入manager”。 </div>
<p> 我赞同这种做法,而且也方便进行测试。</p>
30 楼 icewubin 2008-06-26  
liubaoshan 写道
roy042 写道
结合自己经历过的一个项目的体验,我觉得比较合适的做法是DAO层使用接口,DAO的实现类只封装数据库操作,而在Service层中处理业务逻辑。DAO层之间不相互调用,而由Spring向Service中注入要使用的DAO。
比如:
UserDAO,UserDAOImpl,UserManager,UserManagerImpl;
DepartmentDAO,DepartmentDAOImpl,DepartmentManager,DepartmentManagerImpl;
其中UserManagerImpl中注入UserDAO的实现类UserDAOImpl,
DepartmentManagerImpl中注入DepartmentDAO的实现类DepartmentDAOImpl,
而如果DepartmentManager要操纵User,则注入UserManagerImpl,而并不是注入UserDAOImpl。
这样可以将业务逻辑与具体的数据读写操作剥离。


我也在一直是用这种做法,感觉比较灵活,直观层次分明.


正好热点中另有一个循环依赖的问题,DepartmentManager和UserManager不宜互相依赖。
http://www.iteye.com/topic/205271
针对这个例子把我的建议再阐述一下:

1.DAO和Manager合并,manager中的单表业务应该是被复用的。

2.上面说到复用,被谁复用呢,被service复用。只能在service中注入manager。

3.你可以说,你的DAO相当于我说的Manager,你说的Manager相当于我说的service,不一样,我认为一般情况,可以约定不允许在service中注入service。“只能在service中注入manager”。
29 楼 liubaoshan 2008-06-20  
roy042 写道
结合自己经历过的一个项目的体验,我觉得比较合适的做法是DAO层使用接口,DAO的实现类只封装数据库操作,而在Service层中处理业务逻辑。DAO层之间不相互调用,而由Spring向Service中注入要使用的DAO。
比如:
UserDAO,UserDAOImpl,UserManager,UserManagerImpl;
DepartmentDAO,DepartmentDAOImpl,DepartmentManager,DepartmentManagerImpl;
其中UserManagerImpl中注入UserDAO的实现类UserDAOImpl,
DepartmentManagerImpl中注入DepartmentDAO的实现类DepartmentDAOImpl,
而如果DepartmentManager要操纵User,则注入UserManagerImpl,而并不是注入UserDAOImpl。
这样可以将业务逻辑与具体的数据读写操作剥离。


我也在一直是用这种做法,感觉比较灵活,直观层次分明.
28 楼 vlinux 2008-06-19  
<div class='quote_title'>Frederick 写道</div>
<div class='quote_div'>测试业务方式的时候,很难替换持久化层。 </div>
<p> 这个我写TestCase的时候遇到了,确实不能模拟DAO单独测试Service,只能在@Before、@BeforeClass的时候创建测试用的数据,然后再在@After、@AfterClass中进行清理。</p>
<p> </p>
<p>写完这个帖子至少也让我明白Service与DAO层在项目中还是各自有各自的用途,谢谢。我现在也不再拿它来简化我在工作中遇到的Service,只是退一步简化DAO而已。</p>
<p>不过我打算会为以后接私活的时候考虑用来替换Service,毕竟那些小项目的业务逻辑都非常简单。</p>
27 楼 Frederick 2008-06-19  
对于简单的操作,比如那个getCustomer(),用范型的DAO足够了。没看出你的改动简化在那里,还会带来其他一些问题,比如测试业务方式的时候,很难替换持久化层。
对于业务复杂的操作,DAO和Service整合到一起以后,会导致业务方法实在太复杂了。
在我涉及到的领域(电信的信息系统和GIS系统),你的所谓弱化毫无价值。在其他领域的情况我不了解,不好说。

个人感觉帖主在有了一定的商业项目实践经验以后再来看这个问题,可能会有截然不同的看法。
26 楼 icewubin 2008-06-18  
roy042 写道
结合自己经历过的一个项目的体验,我觉得比较合适的做法是DAO层使用接口,DAO的实现类只封装数据库操作,而在Service层中处理业务逻辑。DAO层之间不相互调用,而由Spring向Service中注入要使用的DAO。
比如:
UserDAO,UserDAOImpl,UserManager,UserManagerImpl;
DepartmentDAO,DepartmentDAOImpl,DepartmentManager,DepartmentManagerImpl;
其中UserManagerImpl中注入UserDAO的实现类UserDAOImpl,
DepartmentManagerImpl中注入DepartmentDAO的实现类DepartmentDAOImpl,
而如果DepartmentManager要操纵User,则注入UserManagerImpl,而并不是注入UserDAOImpl。
这样可以将业务逻辑与具体的数据读写操作剥离。



1.没那么容易剥离,完全剥离的话很容易产生性能问题。

2.既然orm已经封装了基础crud,就在dao里写业务逻辑好了,单表的业务逻辑,没什么不好。
  强调一点,业务逻辑CRUD不是基础CRUD,例如新增人员时,年龄小于5岁或者来自于日本的都不让注册或者数据库里已经有同名的用户名了,都抛出异常。这种代码就是业务逻辑,写在dao里面再合适不过了。
  多表的放到ServiceImpl里去。
25 楼 vlinux 2008-06-18  
<p>刚刚学院开毕业生会回来,昏昏欲睡,不过想清楚了一些问题。</p>
<p> </p>
<p>这种构想的技术还是多在实战中用用比较好,所以我昨天晚上用来帮我的同学的毕业设计写了一个留言板,或许是我自己比较了解自己写的代码的缘故吧,觉得用Rapae还挺快...<img src='../../../../../images/smiles/icon_biggrin.gif' alt=''/></p>
<p> </p>
<p>写的过程中也总结出一些使用的技巧与规则。Service层严格避免相互调用,如果必须要自己实现的方法需要操纵数据库的,都统一通过ORM框架来完成。至于</p>
<div class='quote_title'>downpour 写道</div>
<div class='quote_div'>这样你就会发现,当你的Service层和Dao层合并以后,会造成你的业务逻辑层代码有多臃肿。我一直很困惑,如果一个业务逻辑方法中要包含5,6次的复杂数据库操作,你如果不搞个Dao,你的Service层代码很可能会瞬间超过1000行。 </div>
<p> 所说的问题。我认为是简单问题复杂化了。现在的ORM框架操纵数据库都非常简洁。基本上很少出现超过1000行的代码,如果真的出现了,那真的也是这条业务非常繁琐了。</p>
<p> </p>
<div class='quote_title'>roy042 写道</div>
<div class='quote_div'>结合自己经历过的一个项目的体验,我觉得比较合适的做法是DAO层使用接口,DAO的实现类只封装数据库操作,而在Service层中处理业务逻辑。DAO层之间不相互调用,而由Spring向Service中注入要使用的DAO。 <br/>...<br/>而如果DepartmentManager要操纵User,则注入UserManagerImpl,而并不是注入UserDAOImpl。
<br/></div>
<p> 这样的做法以前我也迷茫过,不过后来实践中我发现一个问题:如果DepartmentManager需要操纵User做UserManager接口没有定义的业务,那这个模型要实现起来就必须要为UserManager暴露出很多没有必要的public method;而且将会束缚UserManagerImpl类的维护人员:他们不仅要测试UserManagerImpl,还得测试使用了这个接口的所有Manager!</p>
<p> </p>
<p>虽然我在前面曾经说过:</p>
<div class='quote_title'>vlinux 写道</div>
<div class='quote_div'>如图2所示。Service层不再相互独立,相互的调用使得整个Service层紧密的耦合在了一起。我不得不承认修改一处业务逻辑的方法非常很有可能会导致其他业务模块的改变。但是这样的确加快了我的开发效率。</div>
<p> 但是同时也认为这是一种不好的行为。</p>
<p> </p>
<p> </p>
<p>也正如 aninfeel 所说,我也越来越觉得这更像是一种玩具了。只不过之前我们是这么玩,现在腻烦了换个花样玩,而且这个花样还不一定好玩罢了。</p>
<div class='quote_title'>aninfeel 写道</div>
<div class='quote_div'>感觉没有简直这回事,少了一个DAO只是把代码移到别处而已,开始可能会因为新鲜感而感到很方便,但是久了可能就会怀念以前的。其实各种构架没有纯粹的好和坏,只是某些长处和短处相互转换而已。 </div>
<p> </p>
<p> </p>
<p>感谢和我一起讨论的所有javaeyer,让我看到了不足。感谢浏览过这个帖子的网友。同时我也觉得,我作为一个毕业生,参加的实际项目实在是太少了。要做出一个不像玩具的玩具,还是得经受更多的磨炼。当然,我不是想将这些代码变成一个框架,现在大家所学的框架已经够多的了,而且我也没这个资格。我只是希望能和大家更多的分享从 构想 -&gt; 设计 -&gt; 实现 -&gt; 应用 的快乐。谢谢大家的支持。</p>
<p> </p>
<p>顺便B4一下我的学院,为啥毕业论文的格式要求比Compiler还严,我已经重新打过不下5次了,气愤。</p>
<p> </p>
24 楼 aninfeel 2008-06-18  
感觉没有简直这回事,少了一个DAO只是把代码移到别处而已,开始可能会因为新鲜感而感到很方便,但是久了可能就会怀念以前的。其实各种构架没有纯粹的好和坏,只是某些长处和短处相互转换而已。
23 楼 fireflyc 2008-06-17  
我的认为这种简化方法不如泛型DAO直接有效。
和泛型DAO比较,问题归结在使用注解还是代码上

我在xml和注解之间的归结是:
如果是配置的内容就是用配置文件xml,
如果是代码的一部分而不是配置就是用注解。

现在是代码和注解的归结:
如果能使用代码描述优先使用代码描述,这样才合乎逻辑,
如果需要描述的信息是对代码的描述那么就使用注解。
22 楼 fireflyc 2008-06-17  
我又考虑了一下。

上面我提到了,这样的简化只是把本来是一行的代码写成了注解的形式而已。我还提到这种简化是没有必要的。
仔细考虑一下发现这根本就是没有必要的。^_^~~
设想一下假如我们使用泛型DAO是什么效果?我们只要一个DAO就行了(你可以起名叫做Manager),CURD的代码也是自动获得的。比起注解的好处就是更加的灵活。
比如我们一个注册,

save(user);
sendMail(user.getEmail());


这样的代码完全可以写在泛型DAO中达到DAO和Service合并的目的。
而采用楼主这样的注解形式的简化方法~碰到这样的问题,没有办法了。只能引入Service~~这不是一种简化了,并没有达到二合一的目的。只是让本来可以看起来“富”的DAO变成了一个空壳子,以前的Service还是Service。DAO不能进行任何的修改,如果修改就必须引入Service~这样的DAO是那么的“瘦”以至于变成了一种看着蹩脚的代码(嗯,那一堆一堆的getter、setter就是这样的~)
21 楼 zzfeng 2008-06-17  
非常同意楼主的观点,
总是在DAO和Service里定义相同的接口,让人很不爽。

其实关于DAO弱化,将使复杂业务的service臃肿的情况,
为什么不在复杂业务的时候强化DAO呢?
让service同时持有弱化DAO和专有DAO怎么样?
实际上至少有一半的情况弱化的DAO方式更合适。

20 楼 ray_linn 2008-06-17  
再改改就接近linq了
19 楼 downpour 2008-06-17  
重复发帖,删除
18 楼 vlinux 2008-06-17  
<p>让我企图再度挣扎一下:</p>
<p> </p>
<p>Service与Service之间的交流就好像人与人之间的交流一样,是相互协作的关系。当一个业务逻辑改变,相应的,和他有关的业务逻辑都将会受到改变。</p>
<p> </p>
<p>举个简单的例子:</p>
<p>如 某个应用原来允许所有用户进行操作A,然后通过操作x才能到操作y。此时涉及到两个Service:ServiceA与ServiceB。B的y方法依赖于A的x方法。</p>
<p> </p>
<p>若此时业务逻辑发生更改,要求只有在白名单的用户方能操作A.X。</p>
<p> </p>
<p>如果根据传统的Service与DAO模型,我们必须要同时在A.x与B.y中判断用户是否属于白名单,少一个都将会发生事故。因为我们不知道那个WebController没经过A就直接调用了B.y(当然,实际上通过IDE就可以很快查出,不过至少你得为这个进行操心)</p>
<p> </p>
<p>若是B.y完全依赖于A.x,那么我们只要在A.x中进行一次判断就可以解决问题 -- 当A的业务逻辑发生改变,相应的,和A有关的业务逻辑都将会受到改变</p>
17 楼 downpour 2008-06-17  
所以目前我的想法是,让Service具备Dao的基本功能。

也就是说,Service的接口继承一个AbstractService接口,Service的实现继承AbstractServiceImpl的实现。在这个实现类中,你可以封装简单的数据库操作逻辑。这样你的Service中可以不需要为了“根据主键查询对象”这种简单的需求而再去额外定义一个接口函数。

不过这实在是有点不雅观。一个Service层的代码,原本应该是业务逻辑的核心,他对于容器、外部环境的依赖应该降到最低。但是突然你发现,一旦按照上面这种方法来,代码是简单了,但是所有能依赖的东西你全依赖上了。

在这个问题上,Java我觉得很无奈。
16 楼 vlinux 2008-06-17  
<div class='quote_title'>downpour 写道</div>
<div class='quote_div'>这样你就会发现,当你的Service层和Dao层合并以后,会造成你的业务逻辑层代码有多臃肿。我一直很困惑,如果一个业务逻辑方法中要包含5,6次的复杂数据库操作,你如果不搞个Dao,你的Service层代码很可能会瞬间超过1000行。<br/><br/>另外,一般的业务逻辑层之所以能够成为一个层次,是因为其需要完成的功能是过程式的,是复杂的。所以我很难奢望在一个真正的企业应用中,一个register方法会如此简单。难道仅仅是保存一把数据库就好了?</div>
<p style='background-color: #ffffff;'><span style='color: #ff0000;'>首先我想说的是,我支持downpour对我的Service+DAO进行整合的观点的批驳。我在将自己的方法应用到实战的时候也的确遇到过一些非常严重的问题。</span> </p>
<p> </p>
<p>按照原来的Service+DAO架构,我们的确是需要DAO来封装我们的数据操作。DAO中也是不允许涉及到具体的业务逻辑的吧,所以5,6次复杂的数据库操作仍然得调用5、6次封装了复杂数据库操作的DAO来执行。但是DAO不涉及到业务逻辑的操作,所以我所遇到的DAO的每个单次操作都相对比较简单,都是类似于从数据库中增删改查等操作。</p>
<p>如附件的图1所示所示:为了降低Service层之间的耦合,一般我们都不让Service相互访问,都是使用对应的DAO层来进行访问。这样的好处自然是一块业务逻辑改变了,也不会影响到其他的业务模块。</p>
<p> </p>
<p>这样将会产生一些比较无语的代码:</p>
<pre name='code' class='java'> public Result&lt;Customer&gt; findCustomer(Customer customer, Page page) {

Map&lt;String, Object&gt; map = new HashMap&lt;String, Object&gt;();
map = BeanUtils.pojo2Map(customer);
return customerDao.pageCountByArgs(map, page);
}</pre>
 
<pre name='code' class='java'> public Customer getCustomer(Long customerId) {

return customerDao.read(customerId);
}</pre>
<p> 这些代码无非都是简单调用DAO层,仅此而已,而且到处都充斥这这样的代码。如果一旦需要新增或者改动将会涉及到好几个文件的修改(如果DAO层封装够好的话,也需要修改IService、ServiceImpl与DaoImpl),findCustomer方法的作用仅仅是为了暴露出customerDao的pageCountByArgs方法而已。</p>
<p> </p>
<p>如图2所示。Service层不再相互独立,相互的调用使得整个Service层紧密的耦合在了一起。我不得不承认修改一处业务逻辑的方法非常很有可能会导致其他业务模块的改变。但是这样的确加快了我的开发效率。</p>
<p> </p>
<p>写到这里,我的GF又飞来一句冷刀直插我狡辩的心脏:</p>
<div class='quote_title'>偶地GF 写道</div>
<div class='quote_div'>要加快开发速度,你还不如直接将SQL写到JSP好了...</div>
<p> 哎...算了吧,帖子到此为止了。</p>
15 楼 fuwang 2008-06-17  
这些东西弄来弄去,都一样。
我更关心的是:你的事务管理有没有问题?你的权限管理是否严谨?你的业务逻辑有没有问题?

相关推荐

Global site tag (gtag.js) - Google Analytics