`
raymond2006k
  • 浏览: 290916 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

解决Hibernate SQL Query Cache的一个可靠性问题(附源码)

阅读更多
    上篇 帖子 Hibernate查询缓存的一个可靠性问题 说发现Hibernate 的 Query Cache 在使用 SQL Query 时的一个可靠性 Bug, 即在关联表数据修改后,无法查询出最新数据的问题。 经过源码分析,找到问题所在,并通过修改 Hibernate 源代码,成功解决了此问题。

    Hibernate 3.1, JDK 1.4

1. 问题原因:
    走了一下源码,大致找到了问题所在,  SQLQueryReturnProcessor 在分析 query 时,用于判断缓存失效的  querySpaces[]只分析出了返回结果对应的表,例如例子中的权限点表:sys_perm,  而sql中的关联表没有分析出来,并放到 querySpaces[] 中。

   具体分析:
    类 org.hibernate.loader.custom.SQLCustomQuery, 它是解析 SQL Query 的一个包装类。
    SQLCustomQuery 类有一个与缓存相关的属性,querySpaces 集合 :
  
 private final Set querySpaces = new HashSet(); 


    通过源码分析,发现 querySpaces  主要用来进行Cache更新检查,querySpaces  存放的是基本VOClass 对应的 tableName, 例如: {SYS_PERM,SYS_USER} . Hibernate 在执行查询时,会检查这个集合中的所有 tableName, 如果该任意一个 tableName 对应 VOClass 二级缓存 有增,删,改的更新操作,即 UpdateTimestampsCache 不是最新的 ,那么该 Query 的 cache 就失效,就会重新去数据库中查询 ID 集合。

    SQLCustomQuery 在构造函数中即进行 sql 的解析和querySpaces[]的判断,其中中有这样一段代码:
  public SQLCustomQuery(.....) throws HibernateException {
    SQLQueryReturnProcessor processor = new SQLQueryReturnProcessor(queryReturns, scalarQueryReturns, factory);
   processor.process();
     ....
    SQLLoadable[] entityPersisters = (SQLLoadable[]) processor.getPersisters().toArray( new SQLLoadable[0] );
     ....
   for (int i = 0; i < entityPersisters.length; i++) {
      SQLLoadable persister = entityPersisters[i];
   //alias2Persister.put( aliases[i], persister );
  //TODO:Does not consider any other tables referenced in the query    
ArrayHelper.addAll( querySpaces, persister.getQuerySpaces() );
    ....
   }
....


  
    //TODO: Does not consider any other tables referenced in the query
    ArrayHelper.addAll( querySpaces, persister.getQuerySpaces() );

    其中红色部分是将 persister 的querySpaces[] 赋给 sqlQuery 的 querySpaces, 但是 persister 代表返回结果集类型对应的表,测试用例中是权限表 SYS_PERM; 因此漏了关联表 (Reference Table),这就是问题所在。 我们看到作者 author Gavin King, Max Andersen 也打了 TODO 注释:
//TODO: Does not consider any other tables referenced in the query
“不要考虑其他查询中的关联表”, 看来作者也留下一手,也许是遗忘了该 TODO 的处理。

2. 解决原理
    关键就是要对 sql 分析出关联表,将其加入 querySpaces[] , 这样sql query查询时,就能自动检查关联表是否有更新。
    分析关联表,用正则表达式解析 sql 中的所有单词 word,并逐个检查word 是否为 sessionFactory 中已经映射的表,凡是映射的表,就作为 reference table 加入 querySpaces[] 。
  
   1) 新增分析类: org.hibernate.loader.custom.SQLQueryReferencesAnalyzer.
  
/**
 * analyze reference table of a specified sql query
 * @author Raymond He, Guangzhou China
 * Oct 8, 2008
 *
 */
public class SQLQueryReferencesAnalyzer {	
  private static Pattern p = Pattern.compile( "\\w{3,}" );	 
  private static Map sqlquery2ReferenceTableMapping  = new HashMap();
  private static Map tableName2EntityNameMapping = new HashMap(100);
	 
  private static final Log log = LogFactory.getLog( SQLQueryReferencesAnalyzer.class );
	 
  public List analyzeSQL(SessionFactory sessionFactory,String sql) {		 
    if(sqlquery2ReferenceTableMapping.containsKey(sql)) {
			 
	List refTables  = (List)sqlquery2ReferenceTableMapping.get(sql);
	if(log.isDebugEnabled())
	 log.debug("Got ref tables:" + refTables + "\r\n of SQL: " + sql);
	 return refTables;
   }else {
		 
     if(tableName2EntityNameMapping.size() == 0) {  //init it once
	initTableName2EntityNameMapping(sessionFactory); 
      }
			 
    List refTables = new ArrayList(3);
    Matcher m = p.matcher(sql);
			
    while(m.find()) {				 
	 String word  = m.group();
	 word = word.toUpperCase();
	//check if the word is a table name in sessionFactory
         //cache table for every sessionFactory independently, for multi sessionFactory env.
	 String key = "SF" + sessionFactory.hashCode() + "_" + word; 
	 if(tableName2EntityNameMapping.containsKey( key )) {
	   if(log.isDebugEnabled()) log.debug("word is table: "+ word);
	   refTables.add( word);
          }
     }
			
     if(log.isDebugEnabled())
	 log.debug("To cache sqlquery2ReferenceTableMapping, ref tables:" + refTables + "\r\n of SQL: " + sql);
      //cache it
      sqlquery2ReferenceTableMapping.put(sql, refTables);
     return refTables;
   }
 }


   2) 调用reference table分析类,将关联表加入 querySpaces[]

    在 SQLCustomQuery 中 完成 Gavin King  的 TODO 任务。
   
//2008-10-6,Raymond He fix bug here,old text: Does not consider any other tables referenced in the query
   /**start: analyze ref tables and add it to querySpaces ***********/
   SQLQueryReferencesAnalyzer referencesAnalyzer = new SQLQueryReferencesAnalyzer();
   List refTables = referencesAnalyzer.analyzeSQL(factory, sql);
   for (int k = 0; k < refTables.size(); k++) {
	querySpaces.add(refTables.get(k));
   }
   /**end ***********/


3. 验证
    还是之前那个测试用例,观察日志:

Execute No. 0 ********************
2008-10-08 17:32:50,140 [DEBUG](AbstractBatcher.java,346) - select  this.PERMCODE as  PERM1_15_0_,  this.MODULECODE as  MODULE2_15_0_,  this.PERMTYPECODE as  PERM3_15_0_,  this.PERMNAME as  PERM4_15_0_,  this.PERMDESC as  PERM5_15_0_,  this.PORTNO as  PORT6_15_0_ from (select  t.perm_code as permCode,
       t.module_code as moduleCode,
       t.perm_name as permName,
       t.perm_desc as permDesc,
       t.port_no as portNo,
       t.perm_type_code as permTypeCode
  from sys_perm t join sys_role_perm o
    on t.perm_code = o.perm_code
    where o.role_code = ? ) this 
(No.0)result size:1

Execute No. 1 ********************
(No.1)result size:1
2008-10-08 17:32:50,187 [DEBUG](AbstractBatcher.java,346) - delete from SYS_ROLE_PERM where PERM_CODE=? and ROLE_CODE=?

Execute No. 2 ********************
2008-10-08 17:32:50,187 [DEBUG](AbstractBatcher.java,346) - select  this.PERMCODE as  PERM1_15_0_,  this.MODULECODE as  MODULE2_15_0_,  this.PERMTYPECODE as  PERM3_15_0_,  this.PERMNAME as  PERM4_15_0_,  this.PERMDESC as  PERM5_15_0_,  this.PORTNO as  PORT6_15_0_ from (select  t.perm_code as permCode,
       t.module_code as moduleCode,
       t.perm_name as permName,
       t.perm_desc as permDesc,
       t.port_no as portNo,
       t.perm_type_code as permTypeCode
  from sys_perm t join sys_role_perm o
    on t.perm_code = o.perm_code
    where o.role_code = ? ) this 
(No.2)result size:0



注意到第3次又执行了sql 语句,并且 (No.2)result size:0, 表明第二次查询后, 删除了 角色授权记录,因此第3次查询 角色 STESTOR 授权限结果为 0 。 表明成功修复此问题。

4. fix包及源码下载:
     用法:    
   
    1) remove one class from hibernate3.jar
        org.hibernate.loader.custom.SQLCustomQuery.class
    2) add  hibernate3-sqlquerycache-fix.jar to classpath, it provides another  SQLCustomQuery impl to solve the bug. 
  
6
1
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics