【START 修改日志 当天 22:12 】:本文主要是针对基于Hibernate框架的项目开发中复杂查询的最佳实践。分析了前几个网友的意见,我想在几个基本方面hibernate QBC,QBE还是很方便的:
1) 单表简单查询(基于字段的等值查询, 全部And运算)时,简单的汇总,如count等。
2) 全动态查询条件, QBE很方便。
【END 修改日志 当天 22:12 】
【START 修改日志 2008-9-19 21:20】:
[quote="ziyuan"] ibatis完全符合lz的需求,,why not try it[/quote]
同意你,我在公司项目中制定这个开发规范正符合 ibatis 的特点, 不过 Hibernate 对其也提供了完全的支持, 这两者在该功能上是一样的。
因为 2006年项目启动时,已经确定使用 Hibernate 3, 而公司以前编程规范是使用 HQL, QBC 的 , 正好那次在项目初期对Hibernate 这一使用规范逐渐纠 正过来。
如果是新的项目, 我想 ibatis 也是可以考虑的方案。
【START 修改日志 2008-9-19 21:20】
2006年公司电信项目启动时,项目组选择沿用使用已久 的Hibernate及开发规范做 ORM方案。 做为公司新的电信项目的架构师一员,根据一直以来的项目经验,在项目进行2月后,面对复杂查询业务陆续增多,原有以Hibernate HQL,QBC 为特点的开发规范有些无法很好满足需求。
因此在分析复杂查询业务 及 Hibernate 的特性特点, 我为 Hibernate 开发时制定了一个规范:禁用 HQL,QBC,QBE编程, 有三个要点,对于非单表的简单查询:
1)程序员不得在Java代码中直接使用HQL;
2)不得进行HQL拼装;
3) 不得在 hbm.xml 映射文件中使用named HQL query。
该规范按重要程度 基于四个方面的考虑:业务复杂度,程序员开发效率,维护难度和执行性能。 规范要求在 Hibernate 的 hbm.xml 文件配置Named SQL Query 来进行这些功能的开发。
项目中我们对其进行了适当的易用性改造,关于我们项目中Named SQL如何具体应用,见下一篇分享:我的开发规范分享(二)- 禁用Hibernate HQL,QBC,QBE编程(2)
1. HQL,QBC:
今天写这个分享短文,源于前几天回覆网友icewubin 的帖子: “一个关于Hibernate的优化实例:从HQL到QBC,从QBC到QBE,再到‘增强的’QBE”,主要是Hibernate查询的几种使用方式:HQL,QBC,QBE。
先说 QBC,QBE,例如:(Hibernate QBC 示例代码)
public List<Product> getProducts(Product product) { final Example exampleProduct = Example.create(product). enableLike(MatchMode.ANYWHERE). excludeZeroes(); return (List<Product>) getHibernateTemplate().execute( new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { Criteria crit =session.createCriteria(Product.class). add(exampleProduct); return crit.list(); } } ); }
或例如:(Hibernate QBC 构造查询条件示例代码)
Restrictions.ne(propertyName, propertyValue); crit = Restrictions.gt(propertyName, propertyValue); crit = Restrictions.lt(propertyName, propertyValue);
2 ORM框架下复杂查询的处理
几年前,我使用另一个ORM框架Ofbiz, 它的QBC,QBE与Hibernate类似,而且条件构造的类库更为完善. 当时使用过JDBC编程的我也对这种纯Java的,优雅的查询编程吸引,折服(当然这种思想确实是大牛们的伟大创新), QBC,QBE对单表,简单查询条件,两表关联查询支持很好.
然而当项目设计完毕,进入开发阶段后,我们发现,这种查询开发模式遇到了瓶颈. 因为当时项目是财务系统,业务比较复杂, 多表关联查询,汇总查询,子查询等复杂查询陆续涌来. QBC,QBE的不足马上体现出现. 为此项目组对此类业务不得不用改用native sql来开发, 为与Ofbiz的ORM思想保持统一, 以配置sql为数据源的实体成为虚拟实体(Virtual Entity), 虚拟实体同样在映射文件中配置. 这个对Ofbiz的封装和改造,马上发挥用途,项目顺利,流畅的推进了。
3. 我们的项目规范:禁用 HQL,QBC,QBE编程
回到我们的2006年启动的电信项目,使用的是Hibernate,它增加了HQL,以OO的方式写查询语句,也是牛人Gavin King的力作。但与Ofbiz类似,它对多表关联查询,汇总查询,子查询,三者混合查询的支持依然不足。作为新任架构师经过对公司之前架构规范分析,及一番思考,决定使用Hibernate时的一个规范:禁用 HQL,QBC,QBE编程。
在项目开发规范中,我对这个规范进行了要点分析和总结。
HQL,QBC开发的缺点:
1) Java代码和查询语言混合开发,没有分离,易读性,维护性差;
2) HQL编写和调试困难,对于较为复杂的查询,通常需要转化为SQL进行调试,调试完毕又要转为HQL,转换过程需要对HQL有较好的理解,但容易出错,开发时间长;
3) QBC 编码量多,开发慢,易读性,维护性差;
4) HQL,QBC功能有限,前面提到的多表关联查询,汇总查询,子查询,三者混合查询支持不佳或无法支持。相信朋友们都遇到过类似的需求。
5) 还有一个考虑就是性能问题,有的HQL被Hibernate内部组装为SQL后,可能存在性能隐患,如果修改的话比较麻烦。
4. 复杂查询的解决方案
解决方案仍然是,在hbm.xml 文件中配置named sql query。Ibatis 也有类似功能。
例子1:查询指定模块的所有下级模块, Hibernate 能将查询结果自动和 ModuleVO 映射起来
<!-- 查询指定模块的所有下级模块 --> <sql-query name="system.module.queryAllSubModules"> select module.MODULE_CODE as MODULECODE, module.MODULE_NAME as MODULENAME, module.MODULE_TYPE as MODULETYPE, module.PARENT_CODE as PARENTCODE, module.MODULE_DESC as MODULEDESC, module.MODULE_URI as MODULEURI, module.MODULE_VIEW as MODULEVIEW from ( select t.child_code, t.offset from sys_module_rela t where t.ancestor_code = :parentCode ) m1 join sys_module module on m1.child_code = module.module_code order by parent_code, module_order </sql-query>
例子2:以判断操作员是否对某个URI有访问权限为例, 返回值 val 大于0时表示有权限:
<sql-query name="system.perm.hasURIPermisson"> <return-scalar column="val" type="java.lang.Integer"/> select count(1) val from sys_module module , ( select distinct p.module_code from sys_oper_perm op , sys_perm p where op.oper_id = :oprcode and op.perm_code = p.perm_code ) op where module.module_code = op.module_code and module.module_uri = :currentURI </sql-query>
我们项目中的Java代码的调用就更为简单了:
/** * 检查指定工号对 web请求 uri是否有权限。 * @param oprcode * @param currentURI * @return * @throws Exception */ public boolean doCheckURIPermission(String oprcode, String currentURI) throws Exception { PermDAO dao = (PermDAO) DAOFactory.build(PermDAO.class,user); Param param = new Param(); param.getQueryConditions().put("oprcode", oprcode); //设置固定参数 :oprcode param.getQueryConditions().put("currentURI", currentURI); //设置固定参数 :currentURI Integer count = (Integer)dao.queryUniqueByNamedSqlQuery("system.perm.hasURIPermisson", param); return count.intValue() > 0; }
而如果使用HQL,估计很难实现;
使用QBC查询,至少需要2-3步,代码较多,并且可能或查出冗余数据, 影响应用性能。
named sql 的优点和上面HQL,QBC的缺点正好相反:
1) Java 代码和查询语句分离,易读性好,维护性好;
2) 使用原生 SQL 开发,调试简便。如果是oracle,在pl/sql中进行调试,修改快捷而高效;
3) 原生 SQL功能自然不必说,它是最全的。复杂的业务如果它都支持不了,那就得歇菜了。
4) 充分发挥 DBMS 本身特定sql 语法的特性。
5. 跨数据库问题
在和网友icewubin 的讨论中,有一个问题值得说一说:跨数据库问题。他的意思是使用HQL,QBC可以保持数据库通用,这是他们的项目需求。实际上,我们当初的项目也是要求跨数据库的。解决方法是,针对Oracle,DB2,Informix等客户可能用到的db专门做sql调整, 也就是有两个DB的 sql 实现,(当时为新客户调整到DB2 sql,也就花了一周时间)。
这个方案与Oracle BPEL等产品一样,例如:改产品出厂时,有针对不同数据库的DDL脚本文件,一个道理。
6. Named SQL在我们项目中的具体应用。
我们对其进行了适当的易用性改造,关于我们项目中Named SQL如何具体应用,要工作了,呵呵。下一篇分享文章,再聊. 我的开发规范分享(二)- 禁用Hibernate HQL,QBC,QBE编程(2)