評“CPQuery, 解決拼接SQL的新方法”


最近看到一篇“CPQuery, 解決拼接SQL的新方法”(http://www.cnblogs.com/fish-li/archive/2012/09/10/CPQuery.html),由於里面的思路基本是錯誤的,很擔心影響園里的初學者。我在帖子里回復了幾次,給博主 fish-li 一點小小的建議,沒有想到,這位老兄說不過,就直接刪除我的回帖,包含別人在貼中對我的發問,如此惡劣行徑,實屬罕見。鄙人不才,不能保證每次發帖回帖都正確,也有在園中說錯話道歉的時候,但從不刪說錯的、道歉的貼/回復。

特另單寫一篇博文,闡述我的觀點,也歡迎大家用踴躍討論。

 

就事論事,這篇博文的錯誤有以下幾點:

1. 該 CPQuery 嘗試繞開 Ado.net 的 command.Parameters.Add() ,另寫函數來對付 SQL 注入。這是很錯誤的做法。

很多人都以為,對付SQL 注入很容易,只要把單引號處理掉就行了,容易。其實,這種想法是非常錯誤的

對於類似這樣的代碼:

query = query + " and ProductName like '" + p.ProductName + "'";

 

轉換字符數據中間的特殊字符,實際上是一件相當困難的事情。PHP 中有個 addslashes 函數,發布多年,漏洞不斷,補丁也不斷:

Addslashes() 漏洞(2004年,%00)

http://www.ugia.cn/?p=23

 

 

addslashes:會導致SQL注入漏洞(2006年,0xbf27)
http://blog.csdn.net/felio/article/details/1226569

 

GBK字符集下addslashes函數的注入漏洞及BUG的解決辦法(2011 年,addslashes函數在進行轉義的時候,只對二進制字符串操作二不考慮字符集)

http://www.itlearner.com/article/4824

 

 

從上面可以從側面看出其中的難度。

 

在 SQL 注入(http://msdn.microsoft.com/zh-cn/library/ms161953.aspx) 這篇文章中,提到:

如果可能,拒絕包含以下字符的輸入:

輸入字符

在 Transact-SQL 中的含義

;

查詢分隔符。

'

字符數據字符串分隔符。

--

注釋分隔符。

/* ... */

注釋分隔符。服務器不對 /* 和 */ 之間的注釋進行處理。

xp_

用於目錄擴展存儲過程的名稱的開頭,如 xp_cmdshell

 

然而,這僅僅是 SQL Server 所需要處理的特殊字符,如果是其他數據庫,又有別的字符需要處理,難度不是一點點。

如果用 Ado.net ,它所帶的驅動程序,已經替我們做了這些事情,我們只需要調用 cmd.Parameters.Add(p); 即可。

  

 

當然了,這篇微軟的文章,還提到,"應檢查所有調用 EXECUTE、EXEC 或 sp_executesql 的代碼 ...在選擇的每個存儲過程中,驗證是否對動態 Transact-SQL 中使用的所有變量都進行了正確處理..."。這個難度就大了。存儲過程中,難以調用 cmd.Parameters.Add(p) 來轉換參數。看來在存儲過程中,處理字符串起來,要格外小心。

 

嘗試自己寫 SQL 特殊字符處理函數,是一個特別沒有“投資收益比”的事情,很難寫好,就算寫好了,也沒有什么特別的收益,因為數據庫的驅動程序,已經把這個做好了。無論是 C# 還是 Java 的程序員,都不應自己寫 addslashes 函數。

 

在 Java 中,Apache 有個 http://commons.apache.org/lang/ ,本來提供了一個 StringEscapeUtils.escapeSql() 函數,但是后來主動去掉了這個函數,理由是:

StringEscapeUtils.escapeSql has been removed from the API as it was misleading developers to not use PreparedStatement

 

也就是說,這類 escapeSql() 函數,誤導開發者不用開發語言本身的 PreparedStatement,是不好的。所以,最好不要寫此類函數。

 

2. CPQuery 的另一個,據博主 fish-li 所說,“優點”之一,就是拼接 SQL 方便。然而,使用 cmd.Parameters.Add(p) 一樣拼接 SQL 方便:

using (DbCommand cmd = this.mCon.CreateCommand()) 
{ 
    cmd.Transaction = this.mTrans; 

    string sql = "select ...  from ... where 1=1 and ... "; 

    if (uploadFlag != null) 
    { 
        sql += " and t." + TtDataVeccTransferBaseDao.COL_UPLOADFLAG + "= ?"; 

        DbParameter p = cmd.CreateParameter(); 
        p.ParameterName = "@COL_UPLOADFLAG"; 
        p.Value = uploadFlag; 
        cmd.Parameters.Add(p); 
    } 
    ...
}

 

 以下幾行,可以寫在一個公共函數中: 

DbParameter p = cmd.CreateParameter(); 
p.ParameterName = "@COL_UPLOADFLAG"; 
p.Value = uploadFlag; 
cmd.Parameters.Add(p); 

 


這樣最后變成只有兩行: 

 

sql += " and t." + TtDataVeccTransferBaseDao.COL_UPLOADFLAG + "= ?"; 
addParameterValue(cmd,uploadFlag); 

 

從這兩方面看來,CPQuery 本身出現的意義就有疑問。

 

一般來說,另寫數據庫訪問組件/庫,如果有以下幾個方面的優勢,是可以考慮的:

a. 代碼自動生成,可以提高開發效率

 

b. 封裝后更不容易出錯,這也可以做,畢竟很多開發隊伍中,都有新手。

 

c. 減少代碼量。

 

d. 提供 Ado.net 或者數據庫驅動沒有的額外功能

比如 Hibernate 的緩存(單表按主鍵查詢,查詢多次,實際只會執行一次,這里是假設數據在短時間內不會被其他人更改,利用緩存提高性能)。又比如,Ado.net 之后,微軟相繼發布 linq to sql, EF, 但是這兩個都只支持 SQL Server ,於是很多人做出類似的、支持其他數據庫的 linq/EF,拿出來賣,也不錯。

 

從“CPQuery, 解決拼接SQL的新方法” 這篇文章所說,以上幾點,它都沒有做到。因此,這個 CPQuery 是沒有什么用處的。

 

至於該文提到的幾點,都是錯誤的:

2. 可能會影響性能:每條SQL語句都需要數據庫引擎執行[語句分析]之類的開銷。

3. 影響代碼的可維護性:SQL語句與C#混在一起,想修改SQL就得重新編譯程序,而且二種代碼混在一起,可讀性也不好。

 

在數據庫系統中,SQL 的解析時間,基本可以忽略不計。相對於 SQL 及結果數據在網絡中傳輸、SQL查詢數據的搜索時間,那么一點點 SQL 的解析時間,根本可以忽略。所謂 SQL 解析時間要優化,純粹是學院派的胡說。

至於 SQL語句與C#混在一起,這個要看個人的寫程序風格了。用 Ado.net 還是用 CPQuery,都有這個問題。因此,這不是 CPQuery 的優點。

 

用 Ado.net 也可以寫成這樣的函數:

GetUploadData(uploadFlag), 然后把上面的代碼放在里面。這樣僅僅數據庫相關的代碼在一起,其他代碼都在這個 GetUploadData 外面,也沒有什么“SQL語句與C#混在一起”的問題。

至於“想修改SQL就得重新編譯程序”,這是避免不了的。如果從業務邏輯上看,需要把 SQL 中的 > 改成 >= ,確實需要重新編譯程序。用 CPQuery 還是用 Ado.net ,都是如此。

 

我個人覺得,CPQuery 的作者 fish-li ,對  Ado.net 的不熟悉,才導致了他寫出了 CPQuery 。這本沒有什么。Hibernate 的作者也是不願意手工寫 SQL 才弄出了一個 Java ORM,但是人家有自己的賣點:通過反射來把結果集數據,填充到 java 對象里,不用一個個字段賦值;通過緩存,單表按主鍵查詢,查詢多次,實際只會執行一次,提高性能。這兩個賣點,都是 Java JDBC 本身沒有的。

CPQuery 提供了什么 Ado.net 缺乏的東西么?看不到。

 

所以說,這個 CPQuery 沒有什么用處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM