1. 程式人生 > >OLEDB 參數化查詢

OLEDB 參數化查詢

高效率 跳過 使用 wpar bre man 成了 正則表達 div

一般情況下,SQL查詢是相對固定的,一條語句變化的可能只是條件值,比如之前要求查詢二年級學生信息,而後面需要查詢三年級的信息,這樣的查詢一般查詢的列不變,後面的條件只有值在變化,針對這種查詢可以使用參數化查詢的方式來提高效率,也可以時SQL操作更加安全,從根本上杜絕SQL註入的問題。

參數化查詢的優勢:

  1. 提高效率:之前說過,數據庫在執行SQL的過程中,每次都會經過SQL的解析,編譯,調用對應的數據庫組件,這樣如果執行多次同樣類型的SQL語句,解析,編譯的過程明顯是在浪費資源,而參數化查詢就是使用編譯好的過程(也就是提前告訴數據庫要調用哪些數據庫組件),這樣就跳過了對SQL語句的解析,編譯過程,提高了效率(這個過程我覺得有點類似於C/C++語言的編譯執行與腳本語言的解釋執行)。
  2. 更加安全:從安全編程的角度來說,對於防範SQL註入方面,它比關鍵字過濾更有效,實現起來也更加方便。

科普SQL註入和安全編程

  • 什麽是SQL註入:

所謂SQL註入,就是通過把SQL命令插入到Web表單提交或輸入域名或頁面請求的查詢字符串,最終達到欺騙服務器執行惡意的SQL命令。舉個例子來說在用戶登錄時會輸入用戶名密碼,這個時候在後臺就可以執行這樣的SQL語句
SQL select count(*) from user where username = ‘haha‘ and password = ‘123456‘
只有輸入對了用戶名和密碼才能登錄,但是如果沒有對用戶輸入進行校驗,當用戶輸入一些SQL中的語句,而後臺直接將用戶輸入進行拼接並執行,就會發生註入,比如此時用戶輸入 *** ‘haha‘ or 1 = 1 -- *** ,此時再後臺執行的sql語句就變成了這樣:
SQL select count(*) from user where username = ‘haha‘ or 1 = 1 -- and password = ‘‘


這樣用戶就可以不用密碼,直接使用用戶名就登錄了。而防範這類攻擊,一般采用的是關鍵字過濾的方式,但是關鍵字過濾並不能杜絕這類工具,當一時疏忽忘記了過濾某個關鍵字仍然會產生這類問題。而且關鍵字過濾一般采用正則表達式,而正則表達式並不是一般人可以駕馭的。而防範SQL註入最簡單也是最一勞永逸的方式就是參數化查詢。

  • 為什麽參數化查詢能夠從根本上解決SQL註入

發生SQL註入一般的原因是程序將用戶輸入當做SQL語句的一部分進行執行,但是參數化查詢它只是將用戶輸入當做參數,當做查詢的條件,從數據庫的層面上來說,它不對應於具體的數據庫組件,它只是一組數據,而不會執行。這裏可以簡單的將傳統的SQL拼接方式理解為C語言中的宏,宏也可以有參數,但是它不對參數進行校驗,只是簡單的進行替換,那麽我可以使用一些指令作為參數傳入,但是函數就不一樣,函數的參數就是具體類型的變量或者常量。所以參數化查詢從根本上解決的SQL註入的問題。

參數化查詢的使用

前面說了這麽多參數化查詢的好處,那麽到底怎麽使用它呢?
在Java等語言中內置了數據庫操作,而對於C/C++來說,它並沒有提供這方方面的標準。不同的平臺有自己獨特的一套機制,但是從總體來說,思想是共通的,只是語法上的不同,這裏主要是說明OLEDB中的使用方式。

  1. 使用“?”符將SQL語句中的條件值常量進行替換,組成一個新的SQL語句,比如上面登錄的查詢語句可以寫成
    SQL select count(*) from user where username = ? and password = ?
  2. 調用ICommandText的SetCommandText設置sql語句。
  3. 調用ICommandParpare的Prepare方法對含有"?"的語句進行預處理
  4. 調用ICommandWithParameters方法的GetParameterInfo方法獲取參數詳細信息的DBPARAMINFO結構(類似於DBCOLUMNINFO)
  5. 分配對應大小的DBBINDING緩沖用來保存每個參數的綁定信息
  6. 調用IAccessor的CreateAccessor方法創建對應的訪問器
  7. 為參數分配緩沖,設置合適的參數後準備DBPARAMS結構
  8. 調用ICommandText的Execute方法並將DBPARAMS結構的指針作為參數傳入。
  9. 操作返回的結果集對象
typedef struct tagDBPROPIDSET {
   DBPROPID *   rgPropertyIDs;
   ULONG        cPropertyIDs;
   GUID         guidPropertySet;
} DBPROPIDSET;

DBPARAMS結構的定義如下:

typedef struct tagDBPARAMS
{
    void *pData;
    DB_UPARAMS cParamSets;
    HACCESSOR hAccessor;
}   DBPARAMS;
  • pData是保存參數信息的緩沖;
  • cParamSets: 表示又多少個參數
  • hAccessor: 之前獲取到的綁定結構的訪問器句柄

下面是一個使用的例子:

BOOL QueryData(LPOLESTR pQueryStr, IOpenRowset* pIOpenRowset, IRowset* &pIRowset)
{
    IAccessor *pParamAccessor = NULL; //與參數化查詢相關的訪問器接口

    LPOLESTR pSql = _T("Select * From aa26 Where Left(aac031,2) = ?"); //參數化查詢語句
    BOOL bRet = FALSE;
    DB_UPARAMS uParams = 0;
    DBPARAMINFO* rgParamInfo = NULL;
    LPOLESTR pParamBuffer = NULL;
    DWORD dwOffset = 0;
    DBBINDING *rgParamBinding = NULL;
    HACCESSOR hAccessor = NULL;
    DBPARAMS dbParams = {0};
    DBBINDSTATUS *pdbBindStatus = NULL;
    //設置SQL
    hRes = pICommandText->SetCommandText(DBGUID_DEFAULT, pSql);

    //預處理SQL命令
    pICommandPrepare->Prepare(0);
    hRes = pICommandText->QueryInterface(IID_ICommandPrepare, (void**)&pICommandPrepare);

    //獲取參數信息
    hRes = pICommandText->QueryInterface(IID_ICommandWithParameters, (void**)&pICommandWithParameters);
    COM_SUCCESS(hRes, _T("查詢接口ICommandWithParameters失敗,錯誤碼為:%08x\n"), hRes);
    hRes = pICommandWithParameters->GetParameterInfo(&uParams, &rgParamInfo, &pParamBuffer);
    COM_SUCCESS(hRes, _T("獲取參數信息失敗,錯誤碼為:%08x\n"), hRes);

    rgParamBinding = (DBBINDING*)MALLOC(sizeof(DBBINDING) * uParams);
    ZeroMemory(rgParamBinding, sizeof(DBBINDING) * uParams);

    //綁定參數信息
    for (int i = 0; i < uParams; i++)
    {
        rgParamBinding[i].bPrecision = rgParamInfo[i].bPrecision;
        rgParamBinding[i].bScale = rgParamInfo[i].bScale;
        rgParamBinding[i].cbMaxLen = 7 * sizeof(WCHAR); //行政區編號最大長度為6
        rgParamBinding[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
        rgParamBinding[i].dwPart = DBPART_LENGTH | DBPART_VALUE;
        rgParamBinding[i].eParamIO = DBPARAMIO_INPUT;
        rgParamBinding[i].iOrdinal = rgParamInfo[i].iOrdinal;
        rgParamBinding[i].obLength = dwOffset;
        rgParamBinding[i].obStatus = 0;
        rgParamBinding[i].obValue = dwOffset + sizeof(ULONG);
        rgParamBinding[i].wType = DBTYPE_WSTR;

        dwOffset = dwOffset + sizeof(ULONG) + rgParamBinding[i].cbMaxLen;
        dwOffset = UPROUND(dwOffset);
    }

    //獲取訪問器
    pdbBindStatus = (DBBINDSTATUS*)MALLOC(uParams * sizeof(DBBINDSTATUS));
    ZeroMemory(pdbBindStatus, uParams * sizeof(DBBINDSTATUS))
    pParamAccessor->CreateAccessor(DBACCESSOR_PARAMETERDATA, uParams, rgParamBinding, dwOffset, &hAccessor, pdbBindStatus);
    COM_SUCCESS(hRes, _T("獲取參數訪問器失敗,錯誤碼為:%08x\n"), hRes);

    //準備參數
    dbParams.pData = MALLOC(dwOffset);

    ZeroMemory(dbParams.pData, dwOffset);
    dbParams.cParamSets = uParams;
    dbParams.hAccessor = hAccessor;
    for (int i = 0; i < uParams; i++)
    {
        *(ULONG*)((BYTE*)dbParams.pData + rgParamBinding[i].obLength) = _tcslen(pQueryStr) * sizeof(WCHAR);
        StringCchCopy((LPTSTR)((BYTE*)dbParams.pData + rgParamBinding[i].obValue), _tcslen(pQueryStr) + 1, pQueryStr);
    }

    //執行SQL
    hRes = pICommandText->Execute(NULL, IID_IRowset, &dbParams, NULL, (IUnknown**)&pIRowset);
    return bRet;
}

完整代碼


OLEDB 參數化查詢