当前位置:首页>>ASP.NET教程>>Asp.NET综合技巧>>nhibernate源码分析之八: 解析HQL
nhibernate源码分析之八: 解析HQL
2009/11/8 13:19:18
本文将对HQL查询文本的解析过程进行分析,这个可以说是NH中比较复杂的一块了(个人认为),涉及到的类也比较多。
建议阅读之前先深呼吸十下,看完之后脑袋成浆糊可不要找我哟。:-)


在HQL数据加载一文中,我们有提到QueryTranslator的创建过程,代码如下:

//*** SessionFactoryImpl.cs 429行 ***
private QueryTranslator GetQuery(string query, bool shallow)
{
   QueryCacheKey cacheKey = new QueryCacheKey(query, shallow);

   QueryTranslator q = (QueryTranslator) Get(cacheKey);
   if ( q==null) {
      q = new QueryTranslator(dialect);
      Put(cacheKey, q);
   }

   q.Compile(this, query, querySubstitutions, shallow);

   return q;
}
当创建(或从缓存取得)QueryTranslator对象后,就调用它的Compile方法对HQL文本进行解析,
参数querySubstitutions是用于指定要替换的关健字,它可在配置文件中给出。

//*** QueryTranslator.cs 116行 *** 
[MethodImpl(MethodImplOptions.Synchronized)]
public void Compile(ISessionFactoryImplementor factory, string queryString,
   IDictionary replacements, bool scalar) {
   if (!compiled) {
      this.factory = factory;
      this.replacements = replacements;
      this.shallowQuery = scalar;

      Compile(queryString);
   }
}
这个方法上加上了Synchronized选项,指定这是一个单线程的方法(同一时间只能有一个线程执行此方法,与使用Lock是等价的),不知出于什么原因考虑?

//*** QueryTranslator.cs 132行 ***
protected void Compile(string queryString) {
   this.queryString = queryString;

   try {
      ParserHelper.Parse(
         new PreprocessingParser(replacements),
         queryString,
         ParserHelper.HqlSeparators,
         this);
      RenderSql();
   }
   catch (QueryException qe) {
      qe.QueryString = queryString;
      throw qe;
   }
   catch (MappingException me)
   {
      throw me;
   }
   catch (Exception e)
   {
      log.Debug("unexpected query compilation problem", e);
      QueryException qe = new QueryException("Incorrect query syntax", e);
      qe.QueryString = queryString;
      throw qe;
   }

   PostInstantiate();

   compiled = true;
}
将解析HQL文本的任务交给ParserHelper处理,这是一个用于解析的帮助类,注意Parser方法的第一个参数是一个PreprocessingParser对象。

//*** ParserHelper.cs 25行 ***
public static void Parse(IParser p, string text, string seperators, QueryTranslator q)
{
   StringTokenizer tokens = new StringTokenizer(text, seperators, true);
   p.Start(q);
   foreach(string token in tokens) {
      p.Token(token, q);
   }
   p.End(q);
}
首先创建一个StringTokenizer对象,这是一个迭代HQL文本的类,稍后有详细分析。然后启动解析器,并迭代处理HQL文本片断。

所有的处理HQL文本片断的解析器都实现了IParser接口,
public interface IParser {
   void Token(string token, QueryTranslator q);
   void Start(QueryTranslator q);
   void End(QueryTranslator q);
}

StringTokenizer类

StringTokenizer类是一个实现了枚举接口IEnumerable的类, 用于迭代HQL文本中的片断,下面详细说明一下。

public class StringTokenizer : IEnumerable {

   //*** 28行 ***
   public StringTokenizer(string str, string delim, bool returnDelims) {
      _origin = str; // 要处理的HQL查询文本。
      _delim = delim; // 分隔符, 迭代器以分隔符为准返回片断。
          // HQL分隔符在ParseHelper中定义,为" \n\r\f\t,()=<>&|+-=/*'^![]#~\\"
      _returnDelim = returnDelims; // 指定是否返回分隔符。
   }
  
   public IEnumerator GetEnumerator() {
      return new StringTokenizerEnumerator(this);
   }
   枚举器,可用于foreach操作。

   private class StringTokenizerEnumerator : IEnumerator {
      private StringTokenizer _stokenizer;
      private int _cursor = 0;
      private String _next = null;

      // 此处省略部分代码...

      private string GetNext() {
         char c;
         bool isDelim;

         // 检查当前光标是否已超出HQL文本的长度.
         if( _cursor >= _stokenizer._origin.Length )
         return null;

         c = _stokenizer._origin[_cursor];
         isDelim = (_stokenizer._delim.IndexOf(c) != -1);

         // 检查当前字符是否为HQL分隔符.
         if ( isDelim ) {
            _cursor++;
            if ( _stokenizer._returnDelim ) {
               return c.ToString(); // 如允许返回分隔符,则返回它。
            }
            return GetNext(); // 处理下一片断。
         }

         // 取得下一分隔符的位置。
         int nextDelimPos = _stokenizer._origin.IndexOfAny(_stokenizer._delim.ToCharArray(), _cursor);
         if (nextDelimPos == -1) {
            nextDelimPos = _stokenizer._origin.Length;
         }

         // 取得当前光标至下一分隔符之前的字符串片断并返回。
         string nextToken = _stokenizer._origin.Substring(_cursor, nextDelimPos - _cursor);
         _cursor = nextDelimPos;
         return nextToken;
      }
   }
}

现在就不难理解