现在在做JBPM,JBPM里面有个接口是History Process Instance Query。 后来有需求查找的时候要和实际业务中的数据关联,考虑到性能,希望把业务表和JBPM的表关联起来,所以我扩展了一下这个接口,用的也是JBPM的Command模板,调用CommandService执行,不同就只是我写的是SQL,JBPM用的是HQL。
今天出现了个异常,我们创建了一个Process,在没结束前,立即用我写的那个接口去查,发现查不到刚创建的那条数据,很奇怪。理论上来说,虽然数据没有被commit到db,但是应该在session中的,查询也要能查到。
Debug一下,又发现同样的查找,用SQL查不到,用HQL却能查得到,崩溃。。最奇怪的是,在用HQL查完后,再用同样的SQL再查一次,又能查到数据了。 顿时我觉得是bug。哈哈
我猜想可能是SQL查询不会去看session,所以拿不到,那为什么HQL查完后,SQL查又能有呢? 可能是Hibernate缓存了查找,因为他们的搜索条件是一样的,所以匹配了HQL查找的结果。是不是这样呢?
跟踪代码(基于hibernate 3.3.1.GA)。在进入SessionImpl后,SQL查找和HQL查找会进入不同的方法,分别是:
SQL public List listCustomQuery(CustomQuery customQuery, QueryParameters queryParameters) line 1706
HQL public List list(String query, QueryParameters queryParameters) line 1136
其实还有一个同样性质的方法在 line1567 public List list(CriteriaImpl criteria),这个应该是给Criteria Query用的。
这三个方法都有调用同一个方法: line:987 autoFlushIfRequired(spaces)
看这个方法的解释
/**
* detect in-memory changes, determine if the changes are to tables
* named in the query and, if so, complete execution the flush
*/
知道了,hibernate会看查找的对象(spaces参数)在hibernate session中有没有缓存在session中数据,如果有就会提交到DB。
这里的提交不是transaction的commit ! 指把hibernate session的数据同步到db 的同一个session中,两个session在不同地方,但是还是没有commit,调session.flush就能达到效果。
关键是,发现在HQL查找的时候,传给autoFlushIfRequired的参数是要查找的对象名字,但是在SQL查找的时候却是空的。
原因找到了,因为在SQL查找的时候没有flush,在HQL查找的时候flush了,所以结果不一致了。也能解释为什么HQL查找完,SQL查就是对的了。
为什么SQL查没有传对参数呢? 跟了好久,一层套一层,放弃,懒得找。呵呵
感觉应该是bug吧,不是的话,就不要在listCustomQuery里调用autoFlushIfRequired了。 也有可能是些SQL的时候有些技巧,以后再研究研究。先调session.flush()把问题绕过再说,哈哈。
Update:又看了下代码,调用autoFlushIfRequired有去判断一个条件,那个条件大概指这个查询影响到的对象。这个条件是在查询创建的时候就应该确定的,但是SQL查询没有设这个值,所以忽略了。要扩展,更改SQL查询创建的那块代码就行了。
后来搜了一篇文章:
http://i-proving.ca/space/Technologies/Hibernate/SQL+vs+HQL+with+the+Session+Cache
里面的图还是不错的,看一下