標籤:str 而不是 from 資料庫管理 語句 指定 階段 編譯 column
一、背景
我們如今做的項目,用NHibernate實現資料訪問層。
訪問資料時,有的資料庫表是確定的:有明白的表名、欄位名。這時候依照常規的方法處理就可以:建立資料庫表到類的映射。使用HQL讀寫資料庫。
但有的資料訪問,所針對的資料庫表是不確定的,在執行階段確定訪問哪些資料庫表的哪些欄位。
資料庫表和欄位都不確定,自然沒辦法建議O-R映射,僅僅好構造SQL語句了。
既然已經用了NHibernate,我們利用SQL訪問資料庫時,也仍然使用NHibernate。主要是想利用它管理的Session,還有對分頁查詢的支援:能夠指定開始行。還有所須要的總行數。
就由於貪這個廉價。出問題了。
二、錯誤
使用SQL(而不是HQL)訪問資料庫。並且加上訪問範圍(開始行或總行數),有時候會出現奇怪的問題:有的欄位,在資料庫中明明有值,但就是讀不出來。
比如。有一個資料庫表,表的定義大致是:MyTable(Id, Name, FromPoint)。
如今須要查詢當中的前10條記錄,利用以下的SQL語句:
select Id, Name, FromPoint from MyTable
建立好SQL查詢後,設定查詢範圍:
...var query = session.CreateSQLQuery(sql);query.SetFirstResult(0);query.SetMaxResults(10);...
對於運行結果,期望的是,每條記錄有三個欄位。但實際上,僅僅返回前兩個欄位的值,第三個欄位,FromPoint的值。沒有返回。
查看NHibernate的日誌,所產生的SQL是:
select Id, Name from (select Id, Name, FromPoint from MyTable) where rownum<=10
這實在令人費解。
三、原因
在網上搜尋。找不到原因。
僅僅好祭出最後的殺手鐧:跟蹤源碼。
結論是:NHibernate在解析SQL語句時有問題。導致過濾掉了不該過濾掉的列。
詳細地說,在類 NHibernate.Dialect.Dialect的方法 ExtractColumnOrAliasNames 中有例如以下代碼:
if (token.StartsWithCaseInsensitive("select"))continue;if (token.StartsWithCaseInsensitive("distinct"))continue;if (token.StartsWithCaseInsensitive(","))continue;if (token.StartsWithCaseInsensitive("from"))break;
這段代碼的本意。是不將select、distinct等SQL保留字作為列。並且一旦遇到from保留字。就意味著列結束。
問題在於,它用了StartsWith,而不是整詞推斷。從而誤傷了以這些keyword開頭的欄位。並且一旦有以from開始的欄位,後面的欄位都被過濾掉了。
四、辦法
出問題的方法 ExtractColumnOrAliasNames 是 static internal 的。沒有改動機會。
我們僅僅好繞道走:自行加上rownum的過濾條件。
本來要藉助NHibernate實現跨資料庫,我還一直告誡小夥伴們避免使用Oracle、SQL Server等資料庫管理系統特定的SQL文法來著。
不少程式猿會本能地想到一個解決方案:既然是開源的,並且錯誤原因找到了,拿過來改動好。編譯一個新版本號碼用就OK。我對此明白表示不贊成,一方面。這會脫離NHibernate主線版本號碼的發展。還有一方面,也給組件的引用帶來不便:我們正在用Spring.NET管理NHibernate。build一個自己的版本號碼。要設定一堆元資訊。想想就頭大。
五、範圍
就我眼下所知,該BUG出現的情景例如以下:
- 使用原生SQL訪問資料庫;
- 設定了FirstResult、MaxResults等查詢結果範圍;
- 最早的引入版本號碼不詳,最新的版本號碼中。從源碼上推斷。此問題仍然存在。
- 我們使用的是Oracle資料庫,其他資料庫管理系統不詳。
??
一個NHibernate的BUG