给hibernate插上ibatis动态查询的翅膀,既保留crud的简洁性,又能收获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把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. 好处:
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 @Override18 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 resource143 * @param element144 * @param statementMap145 * @param nameCache146 * @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 xmlDocument163 * @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 @Component19 public class DynamicSqlInitCache {20 21 private Configuration configuration;22 23 private StringTemplateLoader stringLoader;24 25 /**26 * 根据key获取sql对应的StatementTemplate27 * @param key28 * @param sql29 * @param isHql30 * @return StatementTemplate 31 * @throws IOException32 */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 * HQL44 */45 HQL,46 SQL47 }48 }