项目中使用hibernate作为数据持久层框架,主要考虑hibernate在进行一些简单的crud操作时非常便利,不需要和ibatis似的为每个sql操作都写一堆文件,但是同时也带来了一些局限性,如类似ibatis强大的动态查询功能用不了了,但是这个功能在项目中的应用场景又很大,hibernate自身也支持将sql/hql语句写在.hbm.xml映射文件中<sql-query>和<query>元素,但是这个功能只能对那些查询限制条件固定的sql有用,对于需要动态拼接的sql语句,hibernate就显得力不从心了,如何给hibernate插上ibatis动态查询的翅膀,既保留crud的简洁性,又能收获ibatis的特性呢?接下来的文章将会重点介绍
2.设计思路
先看一下ibatis的动态查询时怎么做的
<select id="getUserList" re sultMap="user">
select * from user
<isGreaterThan prepend="and" property="id" compareValue="0">
where user_id = #userId#
</isGreaterThan>
order by createTime desc
</select>
ibatis在程序实现内部回去解析sql语句中的标签,然后去解析计算,我们在ibatis在实现的时候也参考了这个解决思路,但是否是需要把ibatis里的解析sql的语法都抄到我们的dao框架中呢-显然这样太复杂了,而且ibatis自己的sql元素是和那些resultMap等是绑定在一起用的,而在hibernate是没用这些东西的,要改造这些东西是一项非常浩大的工程,因此这个方案被放弃了
我们在实现的时候采取了一种非常简洁又功能强大的方式-模板技术!对,就是利用freemarker把sql/hql中的动态拼接条件判断语法都交给freemarker语法去处理,这样既能复用freemarker框架,又保持了我们框架设计的简洁性-不需要自己写过多的处理逻辑,以下是我们需要进行动态处理的sql/hql语句的样例
这个文件看起来非常类似ibatis的语句了,只是没用ibatis的哪些标签-改成了freemarker语法,没错,我们就是复用freemarker来帮我们解决这些烦杂的判断操作的<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE dynamic-hibernate-statement PUBLIC "-//Haier/HOP Hibernate Dynamic Statement DTD 1.0//EN" "http://www.haier.com/dtd/dynamic-hibernate-statement-1.0.dtd"> <dynamic-hibernate-statement> <!-- 查询某个资源下的直接子节点 --> <hql-query name="resource.getChildren"> <![CDATA[ from Resource where parent.id=${parentId} and parent.id != id ]]> </hql-query> <!-- 查询系统中所有的root资源 --> <hql-query name="resource.getRoots"> <![CDATA[ from Resource where parent.id = id order by orderIndex ]]> </hql-query> <!-- 获取某个用户可访问的某个资源下的所有子资源 --> <sql-query name="resource.getDescendants"> <![CDATA[ select distinct t.id, t.name, t.description, t.url, t.type, t.status, t.code, t.configuration, t.module_name, t.gmt_create, t.gmt_modified, t.create_by, t.last_modified_by, t.order_index, t.parent_id from resource_info t inner join role_resource rr on t.id = rr.resource_id inner join user_role ur on rr.role_id = ur.role_id where ur.user_id = ${userId} <#if type == '1'> and t.type=1 <#else> and t.type=0 </#if> and t.type = ${type} and t.status = ${status} start with t.code = '${code}' connect by nocycle prior t.id = t.parent_id ]]> </sql-query> </dynamic-hibernate-statement>
这样我们的动态sql程序就可以总结成以下流程
a.系统加载阶段
这个阶段程序负责将指定路径下的动态sql文件加载到内存中,一次性缓存起来,没错,这些东西只需要加载一次,以后直接读取就行了,没必要每次去查找,缓存也非常简单,一个Map<String,String>就搞定,key是sql-query或hql-query元素的name属性,value就是与其对应的sql/hql语句
b.程序调用查询阶段
调用程序通过sql/hql语句的name属性和传入查询参数来得到最终解析出来的语句
我们期望的方法可能是这样的:
通过queryName从缓存中查找出其对应的sql/hql语句(最原始的,里面带有freemarker语法)public <X> List<X> findByNamedQuery(final String queryName, final Map<String, ?> parameters)
然后通过freemarker模板和传递进去的parameters参数对模板进行解析,得到最终的语句(纯sql/hql)
最后将解析后的sql/hql传递给底层api,返回查询结果
3.实现
上面介绍了大致的思路,这里介绍具体的代码实现
3.1DTD定义
我们是把动态的sql/hql语句放在单独的xml配置文件里的,为了规范xml文档,我们给文档定义了dtd文件,这里我们只定义了两个元素<sql-query>和<hql-query>分别表示sql查询语句和hql查询语句,这两个元素目前自有一个name属性用来唯一标示该语句,如下
<!-- HOP Hibernate Dynamic Statement Mapping DTD. <!DOCTYPE dynamic-hibernate-statement PUBLIC "-//Haier/HOP Hibernate Dynamic Statement DTD 1.0//EN" "http://www.haier.com/dtd/dynamic-hibernate-statement-1.0.dtd"> 这个文件时用来定义动态参数语句,类似itabis --> <!-- The document root. --> <!ELEMENT dynamic-hibernate-statement ( (hql-query|sql-query)* )> <!-- default: none --> <!-- The query element declares a named Hibernate query string --> <!ELEMENT hql-query (#PCDATA)> <!ATTLIST hql-query name CDATA #REQUIRED> <!-- The sql-query element declares a named SQL query string --> <!ELEMENT sql-query (#PCDATA)> <!ATTLIST sql-query name CDATA #REQUIRED>
然后将其保存为dynamic-hibernate-statement-1.0.dtd,放在classpath下
编写DTD校验器
/** * hibernate动态sql dtd解析器 * @author WangXuzheng * */ public class DynamicStatementDTDEntityResolver implements EntityResolver, Serializable{ private static final long serialVersionUID = 8123799007554762965L; private static final Logger LOGGER = LoggerFactory.getLogger( DynamicStatementDTDEntityResolver.class ); private s