最近接手了一個(gè)要維護的項目,是用Hibernate2+Oralce8寫(xiě)成的,因為看到Hibernate3頁(yè)出來(lái)這么久了,而且也感覺(jué)Hibernate3有它的許多新的特性,如批量刪除和更新,新的HQL語(yǔ)法解析器AST。
升級過(guò)程大致按照孫衛琴的那篇文章 如何把Hibernate2.1升級到Hibernate3.0?來(lái)做,該替換的替換完,該設置的設置完,程序一跑,當程序執行到向下面這種查詢(xún)的時(shí)候(Oracle所特有的外連接查詢(xún)),報錯。
語(yǔ)句為:(描述為類(lèi)似語(yǔ)句,把項目中的實(shí)際表名隱去了)
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 查詢(xún)語(yǔ)句的變化 提到Hibernate3.0 采用新的基于A(yíng)NTLR的HQL/SQL查詢(xún)翻譯器ASTQueryTranslator,它已經(jīng)不支持像Oracle8i和Sybase11那樣的 THETA-STYLE 連接查詢(xún)方言。
解決這一問(wèn)題的辦法有兩種:
(1)改為使用支持ANSI-STYLE連接查詢(xún)的方言,像 LEFT OUTER JOIN .. ON ..的寫(xiě)法
(2)也可改用 Hibernate2的查詢(xún)翻譯器,可在 hibernate.cfg.xml 中進(jìn)行配置。
因第一種方法,需要在映射文件中配置PO 間的X 對X的關(guān)聯(lián)關(guān)系才能用,如過(guò)哪位朋友在不配置 PO 間關(guān)聯(lián)關(guān)系時(shí)也能用LEFT OUTER JOIN .. ON ..的寫(xiě)法連接查詢(xún),能告訴我怎么做的號嗎?讓咱也學(xué)一招,先謝了!
所以想想還是在 hibernate.cfg.xml 中配置
<property name="query.factory_class">
org.hibernate.hql.classic.ClassicQueryTranslatorFactory
</property>
注:hibernate3默認的HQL語(yǔ)法翻譯器的配置為:
<property name="query.factory_class">
org.hibernate.hql.classic.ASTQueryTranslatorFactory
</property>
使用傳統的hibernat2所用的HQL語(yǔ)法翻譯器。然后程序再跑一跑,剛剛那個(gè)(+)的地方是沒(méi)有錯了,可是新麻煩有冒起來(lái)了,程序執行到
session.createQuery("delete User u where u.id=4").executeUpdate();
有報錯了:
org.hibernate.QueryException: query must begin with SELECT or FROM: delete [delete com.unmi.User where u.id=4]
原來(lái)舊的HQL語(yǔ)法解析器不支持 delete User 的寫(xiě)法,hibernate2在刪除持久化對象時(shí)必須寫(xiě)成
session.delete("delete User u where u.id=4");
然而新的 org.hibernate.Session 的接口方法已去除了 Session.delete(String hql)方法,看來(lái)這條路也是受阻了。正是兩頭受難,無(wú)奈之時(shí)暫時(shí)放棄了升級的念頭,把該還原的地方都恢復舊模樣了。
過(guò)了好一段時(shí)間,也就是個(gè)把月吧……
心里總也覺(jué)不甘心,覺(jué)得事情總有解決的辦法,于是采用了終極辦法:從原代碼下手,進(jìn)行單步的跟蹤,看看hibernate3何時(shí)進(jìn)行HQL到SQL的轉換,何時(shí)取用配置的語(yǔ)法翻譯器。
下面要解決的一個(gè)課題就是:
如何讓Hibernate3既能使用新的Delete和Update語(yǔ)法,又能使用 Oracle Theta-Style 的 t1.c1=t2.c1(+)外連接寫(xiě)法
其中的語(yǔ)法翻譯器如何把傳入的一條HQL語(yǔ)句拆解進(jìn)行分析這里就不詳敘,不過(guò)最好還是要明白一點(diǎn):
Classic語(yǔ)法翻譯器會(huì )把傳入的t1.c1=t2.c1(+)中的(+)作為一個(gè)整體,不拆開(kāi)來(lái),而AST語(yǔ)法分析器卻會(huì )把其中的(+)依括號拆成 ”(” , ”+” , ”)” 三部分。
我們首先來(lái)看看HQL語(yǔ)法翻譯工廠(chǎng)接口 QueryTranslatorFatory 有兩個(gè)接口方法:
public QueryTranslator createQueryTranslator(String queryIdentifier, String queryString,
Map filters, SessionFactoryImplementor factory);
public QueryTranslator createFilterTranslator(String queryIdentifier, String queryString,
Map filters, SessionFactoryImplementor factory);
調用以上兩個(gè)方法只在類(lèi) HQLQueryPlan的構造函數中(五個(gè)參數的那個(gè))
protected HQLQueryPlan(String hql, String collectionRole, boolean shallow,
Map enabledFilters, SessionFactoryImplementor factory)
{
......
}
這個(gè)構造函數接收你寫(xiě)的HQL語(yǔ)句還有一個(gè) SessionFactoryImplementor (extends SessionFactory),這個(gè)SessionFactory持有hibernate.cfg.xml的配置項HQL語(yǔ)法翻譯器。
讀這個(gè)構造函數的代碼,我們發(fā)現有兩段代碼
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語(yǔ)法分析器,這也就是我們的切入點(diǎn),我們所希望的事情是:
當構造HQLQueryPlan時(shí),發(fā)現傳給的hql是一個(gè)Oracle 那樣的THETA-STYLE 連接查詢(xún)語(yǔ)句(即像有(+)那樣的語(yǔ)句),我們就繞開(kāi)在 hibernate.cfg.xml 所配置的AST HQL語(yǔ)法翻譯器,而是采用能夠理解這種語(yǔ)法的傳統的語(yǔ)法翻譯器。
因此我們只要把 HQLQueryPlan類(lèi)的這個(gè)構造函數中的
if ( collectionRole == null) { …………………………… }
改為如下:
if (collectionRole == null)
{
{
translators[i] = .createQueryTranslator(hql, concreteQueryStrings[i], enabledFilters, factory);
}
{
translators[i] = factory.getSettings().getQueryTranslatorFactory()
.createQueryTranslator(hql, concreteQueryStrings[i], enabledFilters, factory);
}
translators[i].compile(factory.getSettings().getQuerySubstitutions(), shallow);
}
{
{
translators[i] =
.createFilterTranslator(hql, concreteQueryStrings[i], enabledFilters, factory);
}
{
translators[i] = factory.getSettings() .getQueryTranslatorFactory()
.createFilterTranslator(hql, concreteQueryStrings[i], enabledFilters, factory);
}
((FilterTranslator) translators[i])
.compile(collectionRole, factory.getSettings().getQuerySubstitutions(), shallow);
}
//如果hql語(yǔ)句中使用Oralce式的外連接方式就用傳統的語(yǔ)法翻譯器
if (hql.replaceAll("\\s*", "").indexOf("(+)") != -1)
new ClassicQueryTranslatorFactory()
else
else
//如果hql語(yǔ)句中使用Oralce式的外連接方式就用傳統的語(yǔ)法翻譯器
if (hql.replaceAll("\\s*", "").indexOf("(+)") != -1)
new ClassicQueryTranslatorFactory()else
改完之后,把編譯后的HQLQueryPlan.class覆蓋到hibernate3.jar包中相應的目錄中即可,或者把這個(gè)類(lèi)放在classes下相應的目錄中,在WEB應用程序中 WEB-INF/classes中的類(lèi)是優(yōu)先于jar包中的類(lèi)先加載。
以上做法兩種有些矛盾的問(wèn)題也就得到解決了,org.hibernate.Session既可以執行Hibernate3 引入的 delete/update語(yǔ)句,還能夠在 Oracle/Sybase中用(+)外連接方式而不需要配置X對X的連接關(guān)系。
下面再介紹一種折中的解決辦法,不知大家注意到?jīng)]有,在Hibernate3中的
org.hibernate.SessionFactory的openSession方法返回的是一個(gè)
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) 方法時(shí),就把返回的Session實(shí)例轉型為 org.hibernate.classic.Session即可。
((org.hibernate.classic.Session)session).delete("from User u where u.id=4");
如果你也想用原始Session的其他已被擯棄的方法,亦可如此這般做。
當然了,在另一方面要讓Hibernate 能支持 Oracle/Sybase中用(+)外連接方式, 您還是要使
用傳統的語(yǔ)法分析器,他將不能理解新的delete/update語(yǔ)句,很遺憾。
所以為了順應新的潮流的發(fā)展,應使用第一種方法。要知道hibernate3中的delete/update語(yǔ)句可比2中的session.delete(hql)方法效率高,hibernate3中直接向數據庫發(fā)一個(gè)delete語(yǔ)句,而在hibernate2中的delete(hql)方法是需要首先加載對象在刪除,確有些多次一舉,不過(guò)又是也有它的道理,update也類(lèi)此。
在補充一個(gè):hibernate會(huì )對 hql 對應的 HQLQueryPlan 進(jìn)行緩沖的,在類(lèi) QueryPlanCache 中處理
HQLQueryPlanKey key = new HQLQueryPlanKey( queryString, shallow, enabledFilters );
HQLQueryPlan plan = ( HQLQueryPlan ) planCache.get ( key );
if ( plan == null ){ ..................}
依據queryString(hql)生成key值.
聯(lián)系客服