hibernate动态sql查询(仿ibatis查询),让你sql代码再也木有if else 了
项目中使用hibernate作为数据持久层框架,主要考虑hibernate在进行一些简单的crud操作时非常便利,不需要和ibatis似的为每个sql操作都写一堆文件,但是同时也带来了一些局限性,如类似ibatis强大的动态查询功能用不了了,看着别人用hibernate拼sql语句一串串if else,吓死个人,此时想到了ibatis强大的动态语句查询,一咬牙,百度之,自己思考测试一下,仿制ibatis查询,麻麻再也不用看那么多if else 吓人
1)
用到的技术dom4j解析xml
2)
Freemark生成模板
正文来了(感谢百度,谷歌获得灵感,修改,整合就是自己的)
设计思路
先看一下ibatis的动态查询时怎么做的
<select id="getUserList"resultMap="user">
select * from user
<isGreaterThan prepend="and" property="id"compareValue="0">
where user_id = #userId#
</isGreaterThan>
orderby createTime desc
</select>
ibatis在程序实现内部实现原理:
我猜是这样 首先加载xml文件 然后解析xml文件,最后生成标准sql文件。
那我何不自己伪造一个呢,要想当年freemaker的能力,那解析模板技术啥子都可以,呵呵。
a.首先自创xml文件
Ibatis不是把sql放到xml里吗,小弟也依葫芦画瓢也来一下,不过简单点:
在web-inf 下新建dynamic_hibernate_sql.xml内容如下
- <?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>
b.系统加载文件
这个阶段程序负责将指定路径下的动态sql文件加载到内存中,一次性缓存起来,没错,这些东西只需要加载一次,以后直接读取就行了,没必要每次去查找,缓存也非常简单,一个Map<String,String>就搞定,key是sql-query或hql-query元素的name属性,value就是与其对应的sql/hql语句
我用的是spring框架,那么用什么类可以再它加载是,运行配置文件里,没错,大家想到了
<!-- Spring环境加载 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
ContextLoaderListener这个类,我也可以继承他啊,自己来加载,呵呵
<!-- 动态sql/hql语句加载器 -->
<listener>
<listener-class>com.deal.util.DsqlContextLoaderListener</listener-class>
</listener>
DsqlContextLoaderListener类如下
/**
* @author cooly
* 动态sql/hql语句加载器
*/
public classDsqlContextLoaderListenerextendsContextLoaderListener {
@Override
public voidcontextInitialized(ServletContextEvent event) {
ApplicationContextac = WebApplicationContextUtils.getWebApplicationContext(event.getServletContext());
DefaultDynamicSqlBuilderdynamicSqlBuilder = (DefaultDynamicSqlBuilder)ac.getBean("dynamicSqlBuilder");//后文有
try {
dynamicSqlBuilder.init();
}catch(Exception e) {
// TODO: handle exception
}
}
}
dynamicSqlBuilder需要spring配置
<!-- 动态dynamicStatement sql -->
<bean id="dynamicSqlBuilder"class="com.deal.util.DefaultDynamicSqlBuilder">
<property name="fileNames">
<list>
<value>classpath*:dynamic_hibernate_*.xml</value><!--这里我们指定要加载某个文件夹下所有以- dynamic_hibernate_.xml结尾的文件 -->
</list>
</property>
</bean>
b.xml解析
重点来了,解析xml放入map里
public classDefaultDynamicSqlBuilder implements ResourceLoaderAware {
private staticfinalLogger LOGGER= Logger.getLogger(DefaultDynamicSqlBuilder.class);
private static Map<String, String> namedHQLQueries;//hql语句缓存
private static Map<String, String> namedSQLQueries; //sql语句缓存
private String[] fileNames = new String[0]; //set 注入
private ResourceLoader resourceLoader; //set 注入
/**
* 查询语句名称缓存,不允许重复
*/
private Set<String> nameCache = newHashSet<String>();
public void setFileNames(String[] fileNames) {
this.fileNames = fileNames;
}
public static Map<String, String>getNamedHQLQueries() {
return namedHQLQueries;
}
public static Map<String, String>getNamedSQLQueries() {
return namedSQLQueries;
}
//初始化
public void init() throws IOException {
LOGGER.info("初始化加载动态sql语句");
namedHQLQueries = new HashMap<String,String>();
namedSQLQueries = new HashMap<String,String>();
boolean flag = this.resourceLoader instanceofResourcePatternResolver;
for (String file : fileNames) {
if (flag) {
Resource[] resources =((ResourcePatternResolver) this.resourceLoader).getResources(file);
buildMap(resources);
} else {
Resource resource = resourceLoader.getResource(file);
buildMap(resource);
}
}
//clear name cache
nameCache.clear();
}
public void setResourceLoader(ResourceLoaderresourceLoader) {
this.resourceLoader = resourceLoader;
}
//解析resources
private void buildMap(Resource[] resources) throws IOException {
if (resources == null) {
return;
}
for (Resource resource : resources) {
buildMap(resource);
}
}
@SuppressWarnings({ "rawtypes" })
//解析xml讲动态语句sql方法map
private void buildMap(Resource resource) {
InputSource inputSource = null;
try {
inputSource = newInputSource(resource.getInputStream());
SAXReader saxReader = new SAXReader();
Document doc = saxReader.read(inputSource);
final Element dynamicHibernateStatement =doc.getRootElement();
Iterator rootChildren =dynamicHibernateStatement.elementIterator();
while (rootChildren.hasNext()) {
final Element element = (Element)rootChildren.next();
final String elementName = element.getName();
if ("sql-query".equals(elementName)) {
putStatementToCacheMap(resource,element, namedSQLQueries);
} else if ("hql-query".equals(elementName)){
putStatementToCacheMap(resource, element, namedHQLQueries);
}
}
} catch (Exception e) {
LOGGER.error(e.toString());
throw new RuntimeException(e);
} finally {
if (inputSource != null && inputSource.getByteStream() != null) {
try {
inputSource.getByteStream().close();
} catch (IOException e) {
LOGGER.error(e.toString());
throw new RuntimeException(e);
}
}
}
}
private void putStatementToCacheMap(Resource resource, final Element element,Map<String, String> statementMap)
throws IOException {
String sqlQueryName =element.attribute("name").getText();
Validate.notEmpty(sqlQueryName);
if (nameCache.contains(sqlQueryName)) {
throw new RuntimeException("重复的sql-query/hql-query语句定义在文件:" + resource.getURI() + "中,必须保证name的唯一.");
}
nameCache.add(sqlQueryName);
String queryText = element.getText();
LOGGER.info("缓存key="+sqlQueryName+"value="+queryText);
statementMap.put(sqlQueryName,queryText);
}
}
一看就明白,不解释了。
现在sql已经缓存了,是该freemaker出场了。
C.freemarker还原真实sql
通过queryName从缓存中查找出其对应的sql/hql语句(最原始的,里面带有freemarker语法)
然后通过freemarker模板和传递进去的parameters参数对模板进行解析,得到最终的语句(纯sql/hql)
我们得到的fromTmDocTemplate t wheret.auditStage='${stage}' order by t.orders,freemaker田田就可以了
工具包
/**
* @param key 模板名称
* @param value 模板值
* @param rootMap 传参数
* @return
* @throws Exception
*/
public static String parseConfig(String key,Stringvalue,Map<String,Object> rootMap) {
try {
Configurationconfiguration = newConfiguration();
configuration.setNumberFormat("#");
StringTemplateLoader stringLoader = new StringTemplateLoader();
Template t = new Template(key,new StringReader(value),configuration);
configuration.setTemplateLoader(stringLoader);
StringWriter sw = new StringWriter(100);
t.process(rootMap, sw);
System.out.println("解析后模板="+sw.toString());
return sw.toString();
}catch(Exception e) {
// TODO: handle exception
throw new RuntimeException(e);
}
}
d.使用
调用程序通过sql/hql语句的name属性和传入查询参数来得到最终解析出来的语句
我们期望的方法可能是这样的:
public <X> List<X> findByNamedQuery(final String queryName, final Map<String, ?> parameters)
最后将解析后的sql/hql传递给底层api,返回查询结果
会freemaker语法的人,写sql完全能做到iBATIS那样了,强大的动态查询,代码里再也看不到一个if else 了,是不是好爽啊