Hibernate动态SQL查询

Posted angelica-duhurica

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hibernate动态SQL查询相关的知识,希望对你有一定的参考价值。

一、需求背景

给hibernate插上ibatis动态查询的翅膀,既保留crud的简洁性,又能收获ibatis的特性。

二、ibatis的动态查询

 1 <select id="findUser" resultClass="User">
 2     SELECT * From User
 3     <dynamic prepend="WHERE">
 4         <isNull property="id">
 5             id IS NULL
 6         </isNull>
 7         <isNotNull property="id">
 8             id = #id#
 9         </isNotNull>
10     </dynamic>
11 </select>

ibatis在程序内部解析sql语句中的标签,然后去解析计算

三、Freemarker 模版技术

利用freemarker把sql/hql中的动态拼接条件判断语法都交给freemarker语法去处理,既能复用freemarker框架,又保持了框架设计的简洁性

Freemarker 生成静态页面,首先需要使用自己定义的模板页面,这个模板页面可以是最最普通的html,也可以是嵌套freemarker中的 取值表达式, 标签或者自定义标签等等,然后后台读取这个模板页面,解析其中的标签完成相对应的操作, 然后采用键值对的方式传递参数替换模板中的的取值表达式,做完之后 根据配置的路径生成一个新的html页面, 以达到静态化访问的目的。

Freemarker标签都是<#标签名称>这样子命名的,${value} 表示输出变量名的内容

四、实例

User-dynamicHbSql.xml

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <!DOCTYPE dynamic-hibernate-statement PUBLIC "-//xxx/HOP Hibernate Dynamic Statement DTD 1.0//EN"
 3 "https://www.xxx.com/dtd/dynamic-hibernate-statement-1.0.dtd">
 4 <dynamic-hibernate-statement>
 5     <sql-query name="User.findUser">
 6     <![CDATA[
 7       select * from user where id=:id
 8     ]]> 
 9     </sql-query>
10 </dynamic-hibernate-statement>

将sql语句编写的xml中,然后在java代码中,传入参数,经过freemarker处理,得到处理完的SQL. 好处:

  • sql编写在xml中,便于阅读
  • 可以使用freemarker语法,动态构建SQL
  • 可以使用freemarker的include语句,提取公用SQL

pom.xml

1 <!-- freemarker -->
2 <dependency>
3     <groupId>org.freemarker</groupId>
4     <artifactId>freemarker</artifactId>
5     <version>2.3.20</version>
6 </dependency>

 web.xml

1 <servlet>
2     <servlet-name>sqlCache</servlet-name>
3     <servlet-class>xx.servlet.DynamicSqlInitServlet</servlet-class>
4     <init-param>  
5         <param-name>fileName</param-name>  
6         <param-value>classpath*:hibernate/**/*-dynamicHbSql.xml</param-value>  
7     </init-param>  
8     <load-on-startup>2</load-on-startup>
9 </servlet>

dtd定义:dynamic-hibernate-statement-1.0.dtd

 1 <!-- HOP Hibernate Dynamic Statement Mapping DTD.
 2 <!DOCTYPE dynamic-hibernate-statement PUBLIC 
 3     "-//xxx/HOP Hibernate Dynamic Statement DTD 1.0//EN"
 4     "http://xxx.xxx.com/dtd/dynamic-hibernate-statement-1.0.dtd">
 5 这个文件时用来定义动态参数语句,类似itabis
 6 -->
 7  
 8 <!--
 9     The document root.
10  -->
11  
12 <!ELEMENT dynamic-hibernate-statement (
13     (hql-query|sql-query)*
14 )>
15 <!-- default: none -->
16  
17 <!-- The query element declares a named Hibernate query string -->
18  
19 <!ELEMENT hql-query (#PCDATA)>
20     <!ATTLIST hql-query name CDATA #REQUIRED>
21  
22 <!-- The sql-query element declares a named SQL query string -->
23  
24 <!ELEMENT sql-query (#PCDATA)>
25     <!ATTLIST sql-query name CDATA #REQUIRED>

 DTD校验器/解析器

 1 package xxx.dao;
 2 
 3 import java.io.InputStream;
 4 import java.io.Serializable;
 5 
 6 import org.hibernate.internal.util.ConfigHelper;
 7 import org.slf4j.Logger;
 8 import org.slf4j.LoggerFactory;
 9 import org.xml.sax.EntityResolver;
10 import org.xml.sax.InputSource;
11 
12 public class DynamicStatementDtdEntityResolver implements EntityResolver, Serializable {
13     private static final long serialVersionUID = 8123799007554762965L;
14     private static final Logger LOGGER = LoggerFactory.getLogger(DynamicStatementDtdEntityResolver.class);
15     private static final String HOP_DYNAMIC_STATEMENT = "https://xxx.xxx.com/dtd/";
16 
17     @Override
18     public InputSource resolveEntity(String publicId, String systemId) {
19         InputSource source = null;
20         if (systemId != null) {
21             LOGGER.debug("trying to resolve system-id [" + systemId + "]");
22             if (systemId.startsWith(HOP_DYNAMIC_STATEMENT)) {
23                 LOGGER.debug(
24                         "recognized hop dyanmic statement namespace; attempting to resolve on classpath under xxx");
25                 source = resolveOnClassPath(publicId, systemId, HOP_DYNAMIC_STATEMENT);
26             }
27         }
28         return source;
29     }
30 
31     private InputSource resolveOnClassPath(String publicId, String systemId, String namespace) {
32         InputSource source = null;
33         String path = "dtd/" + systemId.substring(namespace.length());
34         InputStream dtdStream = resolveInHibernateNamespace(path);
35         if (dtdStream == null) {
36             LOGGER.debug("unable to locate [" + systemId + "] on classpath");
37             String nameFlag = "2.0";
38             if (systemId.substring(namespace.length()).indexOf(nameFlag) > -1) {
39                 LOGGER.error("Don‘t use old DTDs, read the Hibernate 3.x Migration Guide!");
40             }
41         } else {
42             LOGGER.debug("located [" + systemId + "] in classpath");
43             source = new InputSource(dtdStream);
44             source.setPublicId(publicId);
45             source.setSystemId(systemId);
46         }
47         return source;
48     }
49 
50     protected InputStream resolveInHibernateNamespace(String path) {
51         return this.getClass().getClassLoader().getResourceAsStream(path);
52     }
53 
54     protected InputStream resolveInLocalNamespace(String path) {
55         try {
56             return ConfigHelper.getUserResourceAsStream(path);
57         } catch (Throwable t) {
58             return null;
59         }
60     }
61 }

加载*-dynamicHbSql.xml

  1 package xx.dao.servlet;
  2 
  3 import java.io.IOException;
  4 import java.util.HashMap;
  5 import java.util.HashSet;
  6 import java.util.Iterator;
  7 import java.util.Map;
  8 import java.util.Set;
  9 
 10 import javax.servlet.ServletContext;
 11 import javax.servlet.ServletException;
 12 import javax.servlet.http.HttpServlet;
 13 
 14 import org.apache.commons.lang3.Validate;
 15 import org.dom4j.Document;
 16 import org.dom4j.Element;
 17 import org.hibernate.internal.util.xml.MappingReader;
 18 import org.hibernate.internal.util.xml.OriginImpl;
 19 import org.hibernate.internal.util.xml.XmlDocument;
 20 import org.slf4j.Logger;
 21 import org.slf4j.LoggerFactory;
 22 import org.springframework.core.io.Resource;
 23 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
 24 import org.springframework.web.context.WebApplicationContext;
 25 import org.springframework.web.context.support.WebApplicationContextUtils;
 26 import org.xml.sax.EntityResolver;
 27 import org.xml.sax.InputSource;
 28 
 29 import xx.dao.DynamicStatementDtdEntityResolver;
 30 import xx.cache.DynamicSqlInitCache;
 31 
 32 /**
 33  * 持久层sql初始化加载类
 34  */
 35 public class DynamicSqlInitServlet extends HttpServlet {
 36 
 37     private static final long serialVersionUID = 1L;
 38 
 39     private static final Logger LOGGER = LoggerFactory.getLogger(DynamicSqlInitServlet.class);
 40 
 41     private EntityResolver entityResolver = new DynamicStatementDtdEntityResolver();
 42 
 43     private static DynamicSqlInitCache dynamicSqlInitCache;
 44 
 45     /**
 46      * 
 47      * 将sql初始化进入缓存中 
 48      * @Title: init 
 49      * @param @throws ServletException 设定文件 
 50      * @author Administrator 
 51      * @throws
 52      */
 53     @Override
 54     public void init() throws ServletException {
 55         super.init();
 56 
 57         PathMatchingResourcePatternResolver resourceLoader = new PathMatchingResourcePatternResolver();
 58         Map<String, String> namedHQLQueries = new HashMap<String, String>(10);
 59         Map<String, String> namedSQLQueries = new HashMap<String, String>(10);
 60         Set<String> nameCache = new HashSet<String>();
 61         String fileName = this.getInitParameter("fileName");
 62         try {
 63             Resource[] resources = resourceLoader.getResources(fileName);
 64             buildMap(resources, namedHQLQueries, namedSQLQueries, nameCache);
 65 
 66             ServletContext servletContext = this.getServletContext();
 67             WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
 68             dynamicSqlInitCache = (DynamicSqlInitCache) ctx.getBean("dynamicSqlInitCache");
 69 
 70             for (String hqlKey : namedHQLQueries.keySet()) {
 71 
 72                 dynamicSqlInitCache.getTemplateCacheByKey(hqlKey, namedHQLQueries.get(hqlKey), true);
 73 
 74             }
 75 
 76             for (String sqlKey : namedSQLQueries.keySet()) {
 77                 dynamicSqlInitCache.getTemplateCacheByKey(sqlKey, namedSQLQueries.get(sqlKey), false);
 78             }
 79         } catch (IOException e) {
 80             LOGGER.error("初始化sql缓存失败!", e);
 81         }
 82         // clear name cache
 83         namedHQLQueries.clear();
 84         namedSQLQueries.clear();
 85         nameCache.clear();
 86 
 87     }
 88 
 89     /**
 90      * 从文件中加载sql
 91      * @param resources
 92      * @param namedHQLQueries
 93      * @param namedSQLQueries
 94      * @param nameCache 设定文件 
 95      * @throws ServletException 
 96      */
 97     private void buildMap(Resource[] resources, Map<String, String> namedHQLQueries,
 98             Map<String, String> namedSQLQueries, Set<String> nameCache) throws ServletException {
 99         if (resources == null) {
100             return;
101         }
102         for (Resource resource : resources) {
103             InputSource inputSource = null;
104             try {
105                 inputSource = new InputSource(resource.getInputStream());
106                 XmlDocument metadataXml = MappingReader.INSTANCE.readMappingDocument(entityResolver, inputSource,
107                         new OriginImpl("file", resource.getFilename()));
108                 if (isDynamicStatementXml(metadataXml)) {
109                     final Document doc = metadataXml.getDocumentTree();
110                     final Element dynamicHibernateStatement = doc.getRootElement();
111                     Iterator rootChildren = dynamicHibernateStatement.elementIterator();
112                     while (rootChildren.hasNext()) {
113                         final Element element = (Element) rootChildren.next();
114                         final String elementName = element.getName();
115                         if ("sql-query".equals(elementName)) {
116                             putStatementToCacheMap(resource, element, namedSQLQueries, nameCache);
117                         } else if ("hql-query".equals(elementName)) {
118                             putStatementToCacheMap(resource, element, namedHQLQueries, nameCache);
119                         }
120                     }
121                 }
122             } catch (Exception e) {
123                 LOGGER.error(e.toString(),e);
124                 throw new ServletException(e.getMessage(), e);
125             } finally {
126                 if (inputSource != null && inputSource.getByteStream() != null) {
127                     try {
128                         inputSource.getByteStream().close();
129                     } catch (IOException e) {
130                         LOGGER.error(e.toString());
131                         throw new ServletException(e.getMessage(), e);
132                     }
133                 }
134             }
135 
136         }
137 
138     }
139 
140     /**
141      * 将sql放入map中
142      * @param resource
143      * @param element
144      * @param statementMap
145      * @param nameCache
146      * @throws IOException 设定文件 
147      */
148     private void putStatementToCacheMap(Resource resource, final Element element, Map<String, String> statementMap,
149             Set<String> nameCache) throws Exception {
150         String sqlQueryName = element.attribute("name").getText();
151         Validate.notEmpty(sqlQueryName);
152         if (nameCache.contains(sqlQueryName)) {
153             throw new Exception("重复的sql-query/hql-query语句定义在文件:" + resource.getURI() + "中,必须保证name的唯一.");
154         }
155         nameCache.add(sqlQueryName);
156         String queryText = element.getText();
157         statementMap.put(sqlQueryName, queryText);
158     }
159 
160     /**
161      * 判断是否是贮存sql的xml文件 
162      * @param xmlDocument
163      * @return boolean 返回类型
164      */
165     private static boolean isDynamicStatementXml(XmlDocument xmlDocument) {
166         return "dynamic-hibernate-statement".equals(xmlDocument.getDocumentTree().getRootElement().getName());
167     }
168 
169 }
 1 package xxx.cache;
 2 
 3 import java.io.IOException;
 4 import java.io.StringReader;
 5 
 6 import org.springframework.cache.annotation.Cacheable;
 7 import org.springframework.stereotype.Component;
 8 
 9 import xxx.dao.StatementTemplate;
10 
11 import freemarker.cache.StringTemplateLoader;
12 import freemarker.template.Configuration;
13 import freemarker.template.Template;
14 
15 /**
16  * Sql缓存类
17  */
18 @Component
19 public class DynamicSqlInitCache {
20     
21     private Configuration configuration;
22     
23     private StringTemplateLoader stringLoader;
24 
25     /**
26      * 根据key获取sql对应的StatementTemplate
27      * @param key
28      * @param sql
29      * @param isHql
30      * @return StatementTemplate 
31      * @throws IOException
32      */
33     @Cacheable(value = "sqlCache", key = "#key")
34     public StatementTemplate getTemplateCacheByKey(String key, String sql, boolean isHql) throws IOException {
35         if (configuration == null) {
36             configuration =new Configuration();
37         }
38         if (stringLoader == null) {
39             stringLoader =new StringTemplateLoader(); 
40         }
41         configuration.setNumberFormat("#");
42         if (isHql) {
43             stringLoader.putTemplate(key, sql);
44             StatementTemplate statementTemplate = new StatementTemplate(StatementTemplate.TYPE.HQL,new Template(key,new StringReader(sql),configuration));
45             configuration.setTemplateLoader(stringLoader);
46             return statementTemplate;
47         } else {
48             stringLoader.putTemplate(key, sql);
49             StatementTemplate statementTemplat e= new StatementTemplate(StatementTemplate.TYPE.SQL,new Template(key,new StringReader(sql),configuration));
50             configuration.setTemplateLoader(stringLoader);
51             return statementTemplate;
52         }
53     }
54 }
 1 package xxx.dao;
 2 
 3 import java.io.Serializable;
 4 
 5 import freemarker.template.Template;
 6  
 7 /**
 8  * @Description: sql模板类
 9  */
10 public class StatementTemplate implements Serializable{
11     /**
12      * 
13      */
14     private static final long serialVersionUID = 6887405509065040282L;
15 
16     private Template template;
17     
18     private TYPE type;
19     
20     public StatementTemplate(TYPE type, Template template) {
21         this.template = template;
22         this.type = type;
23     }
24  
25     public TYPE getType() {
26         return type;
27     }
28  
29     public void setType(TYPE type) {
30         this.type = type;
31     }
32  
33     public Template getTemplate() {
34         return template;
35     }
36  
37     public void setTemplate(Template template) {
38         this.template = template;
39     }
40  
41     public static enum TYPE {
42         /**
43          * HQL
44          */
45         HQL,
46         SQL
47     }
48 }

 

以上是关于Hibernate动态SQL查询的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis动态SQL标签用法

MyBatis高级特性

Hibernate有几种查询方法

hibernate怎么用查询

[mybatis]动态sql_sql_抽取可重用的sql片段

如何生成ibatis 动态sql