程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Hibernate從2升級到3不支持Oracle8外連接(+)的解決辦法

Hibernate從2升級到3不支持Oracle8外連接(+)的解決辦法

編輯:關於JAVA

最近接手了一個要維護的項目,是用Hibernate2+Oralce8寫成的,因為看到Hibernate3頁出來這麼久了,而且也感覺Hibernate3有它的許多新的特性,如批量刪除和更新,新的HQL語法解析器AST。

升級過程大致按照孫衛琴的那篇文章 如何把Hibernate2.1升級到Hibernate3.0?來做,該替換的替換完,該設置的設置完,程序一跑,當程序執行到向下面這種查詢的時候(Oracle所特有的外連接查詢),報錯。

語句為:(描述為類似語句,把項目中的實際表名隱去了)

session.createQuery("select t1.c1,t2.c1 from Table1 t1,Table2 t2 where t1.c1=t2.c1(+)").list();

出錯信息為:

org.hibernate.hql.ast.QuerySyntaxException: unexpected token: ) near line 1, column 106 [select t1.c1,t2.c1 from Table1 t1,Table2 t2 where t1.c1=t2.c1(+)]
at org.hibernate.hql.ast.QuerySyntaxException.convert(QuerySyntaxException.java:31)
at org.hibernate.hql.ast.QuerySyntaxException.convert(QuerySyntaxException.java:24)
at org.hibernate.hql.ast.ErrorCounter.throwQueryException(ErrorCounter.java:59)
at org.hibernate.hql.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:258)
at org.hibernate.hql.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:157)
at org.hibernate.hql.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:111)
at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:77)
at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:56)
at org.hibernate.engine.query.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:72)
at org.hibernate.impl.AbstractSessionImpl.getHQLQueryPlan(AbstractSessionImpl.java:133)
at org.hibernate.impl.AbstractSessionImpl.createQuery(AbstractSessionImpl.java:112)
at org.hibernate.impl.SessionImpl.createQuery(SessionImpl.java:1623)

再回頭看看孫衛琴的那篇升級注意事項中 1.3 查詢語句的變化 提到Hibernate3.0 采用新的基於ANTLR的HQL/SQL查詢翻譯器ASTQueryTranslator,它已經不支持像Oracle8i和Sybase11那樣的 THETA-STYLE 連接查詢方言。

解決這一問題的辦法有兩種:

(1)改為使用支持ANSI-STYLE連接查詢的方言,像 LEFT OUTER JOIN .. ON ..的寫法

(2)也可改用 Hibernate2的查詢翻譯器,可在 hibernate.cfg.xml 中進行配置。

因第一種方法,需要在映射文件中配置PO 間的X 對X的關聯關系才能用,如過哪位朋友在不配置 PO 間關聯關系時也能用LEFT OUTER JOIN .. ON ..的寫法連接查詢,能告訴我怎麼做的號嗎?讓咱也學一招,先謝了!

所以想想還是在 hibernate.cfg.xml 中配置

<property name= "query.factory_class">
  org.hibernate.hql.classic.ClassicQueryTranslatorFactory
</property>
<property name= "query.factory_class">
  org.hibernate.hql.classic.ClassicQueryTranslatorFactory
</property> 

注:hibernate3默認的HQL語法翻譯器的配置為:

<property name= "query.factory_class">
  org.hibernate.hql.classic.ASTQueryTranslatorFactory
</property>
<property name= "query.factory_class">
  org.hibernate.hql.classic.ASTQueryTranslatorFactory
</property>

使用傳統的hibernat2所用的HQL語法翻譯器。然後程序再跑一跑,剛剛那個(+)的地方是沒有錯了,可是新麻煩有冒起來了,程序執行到

session.createQuery("delete User u where u.name='Unmi'").executeUpdate();

有報錯了:

org.hibernate.QueryException: query must begin with SELECT or FROM: delete [delete com.unmi.User where u.name='Unmi']

原來舊的HQL語法解析器不支持 delete User 的寫法,hibernate2在刪除持久化對象時必須寫成

session.delete("delete User u where u.name='Unmi'");

然而新的 org.hibernate.Session 的接口方法已去除了 Session.delete(String hql)方法,看來這條路也是受阻了。正是兩頭受難,無奈之時暫時放棄了升級的念頭,把該還原的地方都恢復舊模樣了。

過了好一段時間,也就是個把月吧……

心裡總也覺不甘心,覺得事情總有解決的辦法,於是采用了終極辦法:從原代碼下手,進行單步的跟蹤,看看hibernate3何時進行HQL到SQL的轉換,何時取用配置的語法翻譯器。

下面要解決的一個課題就是:

如何讓Hibernate3既能使用新的Delete和Update語法,又能使用 Oracle Theta-Style 的 t1.c1=t2.c1(+)外連接寫法

其中的語法翻譯器如何把傳入的一條HQL語句拆解進行分析這裡就不詳敘,不過最好還是要明白一點:

Classic語法翻譯器會把傳入的t1.c1=t2.c1(+)中的(+)作為一個整體,不拆開來,而AST語法分析器卻會把其中的(+)依括號拆成 ”(” , ”+” , ”)”三部分。

我們首先來看看HQL語法翻譯工廠接口 QueryTranslatorFatory 有兩個接口方法:

public QueryTranslator createQueryTranslator(String queryIdentifier, String queryString,
    Map filters, SessionFactoryImplementor factory);
public QueryTranslator createFilterTranslator(String queryIdentifier, String queryString,
    Map filters, SessionFactoryImplementor factory);
public QueryTranslator createQueryTranslator(String queryIdentifier, String queryString,
 Map filters, SessionFactoryImplementor factory);
public QueryTranslator createFilterTranslator(String queryIdentifier, String queryString,
 Map filters, SessionFactoryImplementor factory);

調用以上兩個方法只在類 HQLQueryPlan的構造函數中(五個參數的那個)

protected HQLQueryPlan(String hql, String collectionRole, boolean shallow,
    Map enabledFilters, SessionFactoryImplementor factory)
{
......
}
protected HQLQueryPlan(String hql, String collectionRole, boolean shallow,
    Map enabledFilters, SessionFactoryImplementor factory)
{
......
}

這個構造函數接收你寫的HQL語句還有一個 SessionFactoryImplementor (extends SessionFactory),這個SessionFactory持有hibernate.cfg.xml的配置項HQL語法翻譯器。

讀這個構造函數的代碼,我們發現有兩段代碼

translators[i] = factory.getSettings().getQueryTranslatorFactory()
        .createQueryTranslator(hql,concreteQueryStrings[i],enabledFilters, factory );
translators[i] = factory.getSettings().getQueryTranslatorFactory()
        .createFilterTranslator(hql, concreteQueryStrings[i], enabledFilters, factory );
translators[i] = factory.getSettings().getQueryTranslatorFactory()
        .createQueryTranslator(hql,concreteQueryStrings[i],enabledFilters, factory );
translators[i] = factory.getSettings().getQueryTranslatorFactory()
        .createFilterTranslator(hql, concreteQueryStrings[i], enabledFilters, factory );

它們的職能是獲取SessionFactory (hibernate.cfg.xml)所配置的HQL語法分析器,這也就是我們的切入點,我們所希望的事情是:

當構造HQLQueryPlan時,發現傳給的hql是一個Oracle 那樣的THETA-STYLE 連接查詢語句(即像有(+)那樣的語句),我們就繞開在 hibernate.cfg.xml 所配置的AST HQL語法翻譯器,而是采用能夠理解這種語法的傳統的語法翻譯器。

因此我們只要把 HQLQueryPlan類的這個構造函數中的

if ( collectionRole == null) { …………………………… }

改為如下:

if (collectionRole == null) {
  // 如果hql語句中使用Oralce式的外連接方式就用傳統的語法翻譯器 
  if (hql.replaceAll(" s*", "").indexOf("(+)") != -1) {
    translators[i] = new ClassicQueryTranslatorFactory().createQueryTranslator(hql,
        concreteQueryStrings[i], enabledFilters, factory);
  } else {
    translators[i] = factory.getSettings().getQueryTranslatorFactory()
        .createQueryTranslator(hql, concreteQueryStrings[i], enabledFilters,
            factory);
  }
  translators[i].compile(factory.getSettings().getQuerySubstitutions(), shallow);
} else {
  // 如果hql語句中使用Oralce式的外連接方式就用傳統的語法翻譯器 
  if (hql.replaceAll(" s*", "").indexOf("(+)") != -1) {
    translators[i] = new ClassicQueryTranslatorFactory().createFilterTranslator(hql,
        concreteQueryStrings[i], enabledFilters, factory);
  } else {
    translators[i] = factory.getSettings().getQueryTranslatorFactory()
        .createFilterTranslator(hql, concreteQueryStrings[i], enabledFilters,
            factory);
  }
  ((FilterTranslator) translators[i]).compile(collectionRole, factory.getSettings()
      .getQuerySubstitutions(), shallow);
}
if (collectionRole == null) {
// 如果hql語句中使用Oralce式的外連接方式就用傳統的語法翻譯器
if (hql.replaceAll(" s*", "").indexOf("(+)") != -1) {
 translators[i] = new ClassicQueryTranslatorFactory().createQueryTranslator(hql,
  concreteQueryStrings[i], enabledFilters, factory);
} else {
 translators[i] = factory.getSettings().getQueryTranslatorFactory()
  .createQueryTranslator(hql, concreteQueryStrings[i], enabledFilters,
   factory);
}
translators[i].compile(factory.getSettings().getQuerySubstitutions(), shallow);
} else {
// 如果hql語句中使用Oralce式的外連接方式就用傳統的語法翻譯器
if (hql.replaceAll(" s*", "").indexOf("(+)") != -1) {
 translators[i] = new ClassicQueryTranslatorFactory().createFilterTranslator(hql,
  concreteQueryStrings[i], enabledFilters, factory);
} else {
 translators[i] = factory.getSettings().getQueryTranslatorFactory()
  .createFilterTranslator(hql, concreteQueryStrings[i], enabledFilters,
   factory);
}
((FilterTranslator) translators[i]).compile(collectionRole, factory.getSettings()
 .getQuerySubstitutions(), shallow);
}

改完之後,把編譯後的HQLQueryPlan.class覆蓋到hibernate3.jar包中相應的目錄中即可,或者把這個類放在classes下相應的目錄中,在WEB應用程序中 WEB-INF/classes中的類是優先於jar包中的類先加載。

以上做法兩種有些矛盾的問題也就得到解決了,org.hibernate.Session既可以執行Hibernate3 引入的 delete/update語句,還能夠在 Oracle/Sybase中用(+)外連接方式而不需要配置X對X的連接關系。

下面再介紹一種折中的解決辦法,不知大家注意到沒有,在Hibernate3中的

org.hibernate.SessionFactory的openSession方法返回的是一個

org.hibernate.classic.Session對象,而org.hibernate.classic.Session是繼承自org.hibernate.Session的。

public org.hibernate.classic.Session openSession(Connection connection);

public interface Session extends org.hibernate.Session

而通常我們順應新潮流,是用org.hibernate.Session去引用SessionFactory的方法openSession()的返回值的,於是我們想用 session.delete(sql) 方法時,就把返回的Session實例轉型為 org.hibernate.classic.Session即可。

((org.hibernate.classic.Session)session).delete("from User u where u.name='Unmi'");

如果你也想用原始Session的其他已被擯棄的方法,亦可如此這般做。

當然了,在另一方面要讓Hibernate 能支持 Oracle/Sybase中用(+)外連接方式, 您還是要使

用傳統的語法分析器,他將不能理解新的delete/update語句,很遺憾。

所以為了順應新的潮流的發展,應使用第一種方法。要知道hibernate3中的delete/update語句可比2中的session.delete(hql)方法效率高,hibernate3中直接向數據庫發一個delete語句,而在hibernate2中的delete(hql)方法是需要首先加載對象在刪除,確有些多次一舉,不過又是也有它的道理,update也類此。

在補充一個:hibernate會對 hql 對應的 HQLQueryPlan 進行緩沖的,在類 QueryPlanCache 中處理

HQLQueryPlanKey key = new HQLQueryPlanKey( queryString, shallow, enabledFilters );
HQLQueryPlan plan = ( HQLQueryPlan ) planCache.get ( key );
if ( plan == null ){ ..................}
HQLQueryPlanKey key = new HQLQueryPlanKey( queryString, shallow, enabledFilters );
HQLQueryPlan plan = ( HQLQueryPlan ) planCache.get ( key );
if ( plan == null ){ ..................} 

依據queryString(hql)生成key值.

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved