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