Rapae 弱化DAO的一种方法
可怜的DAO层已经被各位大侠蹂躏得体肤完肤了,从范型DAO一直被蹂躏到现在只剩下一个可怜巴巴的接口,无不体现Java人追求敏捷开发的热情。其实,DAO层本来的作用就应该自从Hibernate一类优秀的ORM框架诞生之日起就应该消失灭迹了的。既然如此,那么我们就毁灭得更彻底一点。
下面是我对Service与DAO层整合的一些构想
约定优先于配置
一、Rapae代理接口标签定义
- 不止由一个单词组成时:简单查询语句
- 由单个单词组成时:为命名查询,名称规则为[query的值]
- 为默认值""时:为命名查询,名称规则为[类名.方法名]
- 不止由一个单词组成时:简单查询语句
- 由单个单词组成时:为民命查询,名称规则为[count的值]
- 为默认值""时:自动生成count(*)语句
- query为语句查询时:count自动生成为语句查询,规则为:select count(*) + query从第一个from开始到语句结束
- query为命名查询时:count自动生成为命名查询,规则为:[query命名查询名称_count]
二、Pagination翻页注释
- FirstResult注释:方法级上的Annotation,它标记了分页查询时所要知道的第一条记录所在的位置。期待的类型为int
- MaxSize注释:方法级上的Annotation,它标记了分页查询时所要知道的每页最大查询记录数。期待的类型为int
- Total注释:方法级上的Annotation,它标记了分页查询后返回的总数统计。期待的类型为long
- Result注释:方法级上的Annotation,它标记了分页查询后返回的结果。期待的类型为java.util.Collection<E>
- 分页查询时:第一个参数必须是能同时提供有FirstResult和MaxSize注释方法的类,并且方法期待的返回类型都必须匹配,否则将会抛出异常。Rapae通过调用被标注的方法来进行分页查询。
- 分页查询返回时:返 回的类必须同时提供有Result和Total注释的方法,参数个数为1,并且期待的类型都必须匹配,否则将会抛出异常。与此同时,若返回的类同时还能提 供FirstResult、MaxSize注释的方法,方法的参数个数为1且为期待类型,那么Rapae在分页查询完成后将查询用到的 FirstResult、MaxSize值原封不动的通过标注的方法设回给返回类。
三、CRUD基本查询注释
- Create注释:格式 T [方法名] (T t);
- Read注释:格式 T [方法名] (java.io.Serializable pk);
- Update注释:格式 T [方法名] (T t);
- Delete注释:格式 T [方法名] (java.io.Serializable pk);
四、查询行为方式与方法的返回类型、参数类型、参数个数以及方法名称之间的约定
-
- 执行查询
- 返回值必须为void
- 查询多条记录(不翻页)
- 返回值是java.util.Collection<E>的实现
- 返回值是java.util.Collection<E>的实现
- 查询多条记录(翻页)
- 返回类必须同时提供标注有Result与Total的方法,并且参数个数为1,参数类型为标签所期待的类型
- 返回类可以选择性的提供标注有FirstResult和MaxSize的方法,并且参数个数为1,参数类型为标签所期待的类型
- 传入的第一个参数必须同时提供标注有FirstResult与MaxSize的方法,参数个数为0,返回类型为标签所期待的类型
- 查询单条记录
- 返回值必须是一个对象
- 执行查询
- 若是翻页查询,则参数个数至少为一个,且第一个参数必须为提供标标注有FirstResult与MaxSize的方法。
- 若查询条件是通过可变参或者Collection集合类进行传递的,则按顺序对查询条件进行设置
- 若查询条件是通过参数列表直接传递进来的,则按参数列表定义的顺序对查询条件进行设置
- 若查询条件是通过Map传递进来的,则可通过Map对参数的参数进行设置
评论
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语句的生成。
DAO也许不会消失,但是绝对不是这个理由
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混合,反而没有达到效果
Service层应该避免相互感知,DAO层也应该避免相互感知。这样可以避免不必要的循环依赖,而且还可以避免更隐蔽点的循环调用。
我赞同这种做法,而且也方便进行测试。
比如:
UserDAO,UserDAOImpl,UserManager,UserManagerImpl;
DepartmentDAO,DepartmentDAOImpl,DepartmentManager,DepartmentManagerImpl;
其中UserManagerImpl中注入UserDAO的实现类UserDAOImpl,
DepartmentManagerImpl中注入DepartmentDAO的实现类DepartmentDAOImpl,
而如果DepartmentManager要操纵User,则注入UserManagerImpl,而并不是注入UserDAOImpl。
这样可以将业务逻辑与具体的数据读写操作剥离。
我也在一直是用这种做法,感觉比较灵活,直观层次分明.
正好热点中另有一个循环依赖的问题,DepartmentManager和UserManager不宜互相依赖。
http://www.javaeye.com/topic/205271
针对这个例子把我的建议再阐述一下:
1.DAO和Manager合并,manager中的单表业务应该是被复用的。
2.上面说到复用,被谁复用呢,被service复用。只能在service中注入manager。
3.你可以说,你的DAO相当于我说的Manager,你说的Manager相当于我说的service,不一样,我认为一般情况,可以约定不允许在service中注入service。“只能在service中注入manager”。
比如:
UserDAO,UserDAOImpl,UserManager,UserManagerImpl;
DepartmentDAO,DepartmentDAOImpl,DepartmentManager,DepartmentManagerImpl;
其中UserManagerImpl中注入UserDAO的实现类UserDAOImpl,
DepartmentManagerImpl中注入DepartmentDAO的实现类DepartmentDAOImpl,
而如果DepartmentManager要操纵User,则注入UserManagerImpl,而并不是注入UserDAOImpl。
这样可以将业务逻辑与具体的数据读写操作剥离。
我也在一直是用这种做法,感觉比较灵活,直观层次分明.
比如:
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里去。
也许是我说的不够详细吧,像这种简单的业务的确是放在DAO中的,而更复杂的、涉及多个表的业务逻辑放在Service中。而异常处理则是从DAO中抛出到Service,然后抛出到控制层集中处理。
还有关于测试的问题,我的确没有考虑太多,一般只需要对每一个Manager单独进行测试。
这个我写TestCase的时候遇到了,确实不能模拟DAO单独测试Service,只能在@Before、@BeforeClass的时候创建测试用的数据,然后再在@After、@AfterClass中进行清理。
写完这个帖子至少也让我明白Service与DAO层在项目中还是各自有各自的用途,谢谢。我现在也不再拿它来简化我在工作中遇到的Service,只是退一步简化DAO而已。
不过我打算会为以后接私活的时候考虑用来替换Service,毕竟那些小项目的业务逻辑都非常简单。
对于业务复杂的操作,DAO和Service整合到一起以后,会导致业务方法实在太复杂了。
在我涉及到的领域(电信的信息系统和GIS系统),你的所谓弱化毫无价值。在其他领域的情况我不了解,不好说。
个人感觉帖主在有了一定的商业项目实践经验以后再来看这个问题,可能会有截然不同的看法。
比如:
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里去。
刚刚学院开毕业生会回来,昏昏欲睡,不过想清楚了一些问题。
这种构想的技术还是多在实战中用用比较好,所以我昨天晚上用来帮我的同学的毕业设计写了一个留言板,或许是我自己比较了解自己写的代码的缘故吧,觉得用Rapae还挺快...![]()
写的过程中也总结出一些使用的技巧与规则。Service层严格避免相互调用,如果必须要自己实现的方法需要操纵数据库的,都统一通过ORM框架来完成。至于
所说的问题。我认为是简单问题复杂化了。现在的ORM框架操纵数据库都非常简洁。基本上很少出现超过1000行的代码,如果真的出现了,那真的也是这条业务非常繁琐了。
...
而如果DepartmentManager要操纵User,则注入UserManagerImpl,而并不是注入UserDAOImpl。
这样的做法以前我也迷茫过,不过后来实践中我发现一个问题:如果DepartmentManager需要操纵User做UserManager接口没有定义的业务,那这个模型要实现起来就必须要为UserManager暴露出很多没有必要的public method;而且将会束缚UserManagerImpl类的维护人员:他们不仅要测试UserManagerImpl,还得测试使用了这个接口的所有Manager!
虽然我在前面曾经说过:
但是同时也认为这是一种不好的行为。
也正如 aninfeel 所说,我也越来越觉得这更像是一种玩具了。只不过之前我们是这么玩,现在腻烦了换个花样玩,而且这个花样还不一定好玩罢了。
感谢和我一起讨论的所有javaeyer,让我看到了不足。感谢浏览过这个帖子的网友。同时我也觉得,我作为一个毕业生,参加的实际项目实在是太少了。要做出一个不像玩具的玩具,还是得经受更多的磨炼。当然,我不是想将这些代码变成一个框架,现在大家所学的框架已经够多的了,而且我也没这个资格。我只是希望能和大家更多的分享从 构想 -> 设计 -> 实现 -> 应用 的快乐。谢谢大家的支持。
顺便B4一下我的学院,为啥毕业论文的格式要求比Compiler还严,我已经重新打过不下5次了,气愤。
比如:
UserDAO,UserDAOImpl,UserManager,UserManagerImpl;
DepartmentDAO,DepartmentDAOImpl,DepartmentManager,DepartmentManagerImpl;
其中UserManagerImpl中注入UserDAO的实现类UserDAOImpl,
DepartmentManagerImpl中注入DepartmentDAO的实现类DepartmentDAOImpl,
而如果DepartmentManager要操纵User,则注入UserManagerImpl,而并不是注入UserDAOImpl。
这样可以将业务逻辑与具体的数据读写操作剥离。
和泛型DAO比较,问题归结在使用注解还是代码上
我在xml和注解之间的归结是:
如果是配置的内容就是用配置文件xml,
如果是代码的一部分而不是配置就是用注解。
现在是代码和注解的归结:
如果能使用代码描述优先使用代码描述,这样才合乎逻辑,
如果需要描述的信息是对代码的描述那么就使用注解。
上面我提到了,这样的简化只是把本来是一行的代码写成了注解的形式而已。我还提到这种简化是没有必要的。
仔细考虑一下发现这根本就是没有必要的。^_^~~
设想一下假如我们使用泛型DAO是什么效果?我们只要一个DAO就行了(你可以起名叫做Manager),CURD的代码也是自动获得的。比起注解的好处就是更加的灵活。
比如我们一个注册,
save(user); sendMail(user.getEmail());
这样的代码完全可以写在泛型DAO中达到DAO和Service合并的目的。
而采用楼主这样的注解形式的简化方法~碰到这样的问题,没有办法了。只能引入Service~~这不是一种简化了,并没有达到二合一的目的。只是让本来可以看起来“富”的DAO变成了一个空壳子,以前的Service还是Service。DAO不能进行任何的修改,如果修改就必须引入Service~这样的DAO是那么的“瘦”以至于变成了一种看着蹩脚的代码(嗯,那一堆一堆的getter、setter就是这样的~)
总是在DAO和Service里定义相同的接口,让人很不爽。
其实关于DAO弱化,将使复杂业务的service臃肿的情况,
为什么不在复杂业务的时候强化DAO呢?
让service同时持有弱化DAO和专有DAO怎么样?
实际上至少有一半的情况弱化的DAO方式更合适。
让我企图再度挣扎一下:
Service与Service之间的交流就好像人与人之间的交流一样,是相互协作的关系。当一个业务逻辑改变,相应的,和他有关的业务逻辑都将会受到改变。
举个简单的例子:
如 某个应用原来允许所有用户进行操作A,然后通过操作x才能到操作y。此时涉及到两个Service:ServiceA与ServiceB。B的y方法依赖于A的x方法。
若此时业务逻辑发生更改,要求只有在白名单的用户方能操作A.X。
如果根据传统的Service与DAO模型,我们必须要同时在A.x与B.y中判断用户是否属于白名单,少一个都将会发生事故。因为我们不知道那个WebController没经过A就直接调用了B.y(当然,实际上通过IDE就可以很快查出,不过至少你得为这个进行操心)
若是B.y完全依赖于A.x,那么我们只要在A.x中进行一次判断就可以解决问题 -- 当A的业务逻辑发生改变,相应的,和A有关的业务逻辑都将会受到改变
也就是说,Service的接口继承一个AbstractService接口,Service的实现继承AbstractServiceImpl的实现。在这个实现类中,你可以封装简单的数据库操作逻辑。这样你的Service中可以不需要为了“根据主键查询对象”这种简单的需求而再去额外定义一个接口函数。
不过这实在是有点不雅观。一个Service层的代码,原本应该是业务逻辑的核心,他对于容器、外部环境的依赖应该降到最低。但是突然你发现,一旦按照上面这种方法来,代码是简单了,但是所有能依赖的东西你全依赖上了。
在这个问题上,Java我觉得很无奈。
发表评论
提醒: 该博客已发表在公共论坛,博客所有留言会成为论坛回贴,留言请注意遵守论坛发贴规则
- 浏览: 183 次
- 性别:

- 来自: 广西南宁

- 详细资料
搜索本博客
最新评论
-
Rapae 弱化DAO的一种方法
zhu_chen001 写道@Transactional public ab ...
-- by icewubin -
Rapae 弱化DAO的一种方法
EQL、HQL的出现就是屏蔽了具体的SQL。DAO也许不会消失,但是绝对不是这个 ...
-- by vlinux -
Rapae 弱化DAO的一种方法
@Transactional public abstract class A ...
-- by zhu_chen001 -
Rapae 弱化DAO的一种方法
Service层应该避免相互感知,DAO层也应该避免相互感知。这样可以避免不必要 ...
-- by vlinux -
Rapae 弱化DAO的一种方法
liubaoshan 写道roy042 写道结合自己经历过的一个项目的体验,我觉 ...
-- by icewubin






评论排行榜