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的动态查询时怎么做的
ibatis在程序实现内部实现原理:
我猜是这样 首先加载xml文件 然后解析xml文件,最后生成标准sql文件。
那我何不自己伪造一个呢,要想当年freemaker的能力,那解析模板技术啥子都可以,呵呵。
a.首先自创xml文件
Ibatis不是把sql放到xml里吗,小弟也依葫芦画瓢也来一下,不过简单点:
在web-inf 下新建dynamic_hibernate_sql.xml内容如下
b.系统加载文件
这个阶段程序负责将指定路径下的动态sql文件加载到内存中,一次性缓存起来,没错,这些东西只需要加载一次,以后直接读取就行了,没必要每次去查找,缓存也非常简单,一个Map
我用的是spring框架,那么用什么类可以再它加载是,运行配置文件里,没错,大家想到了
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
ContextLoaderListener这个类,我也可以继承他啊,自己来加载,呵呵
<listener>
<listener-class>com.deal.util.DsqlContextLoaderListenerlistener-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配置
<bean id="dynamicSqlBuilder"class="com.deal.util.DefaultDynamicSqlBuilder">
<property name="fileNames">
<list>
<value>classpath*:dynamic_hibernate_*.xmlvalue>
list>
property>
bean>
b.xml解析
重点来了,解析xml放入map里
public classDefaultDynamicSqlBuilder implements ResourceLoaderAware {
private staticfinalLogger LOGGER= Logger.getLogger(DefaultDynamicSqlBuilder.class);
private static Map
private static Map
private String[] fileNames = new String[0]; //set 注入
private ResourceLoader resourceLoader; //set 注入
/**
* 查询语句名称缓存,不允许重复
*/
private Set
public void setFileNames(String[] fileNames) {
this.fileNames = fileNames;
}
public static Map
return namedHQLQueries;
}
public static Map
return namedSQLQueries;
}
//初始化
public void init() throws IOException {
LOGGER.info("初始化加载动态sql语句");
namedHQLQueries = new HashMap
namedSQLQueries = new HashMap
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
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
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
最后将解析后的sql/hql传递给底层api,返回查询结果
会freemaker语法的人,写sql完全能做到iBATIS那样了,强大的动态查询,代码里再也看不到一个if else 了,是不是好爽啊