1. 程式人生 > >基於ODBC API實現對資料庫的訪問

基於ODBC API實現對資料庫的訪問

Visual C++提供了多種多樣的資料庫訪問技術,ODBC API,MFC ODBC,DAO,OLEDB、ADO等。這些技術各有自己的特點,他們提供了簡單、靈活、訪問速度快、可擴充套件性強的開發技術,而這些正是Visual C++和其他開發工具相比優勢所在。ODBC API是為客戶應用程式訪問關係資料庫時提供了一個的標準介面,對不同的資料庫,ODBC提供了一套統一的API,使得應用程式可以應用所提供的API,訪問任何提供了ODBC驅動程式的資料庫。而且,ODBC已經成為了一種標準,所以現在幾乎所有的關係型資料庫都提供了ODBC驅動程式,從而使得ODBC的應用更加廣泛。ODBC API可以進行一些底層的資料庫操作,訪問速度快捷,使用靈活,但編碼相對來說比較複雜。對於一個刨根究底的研究型愛好者來說,卻是一個很好的選擇。同時也讓大家對於ADO、DAO的底層封裝機制有個初步的瞭解和認識。

1、ODBC資料來源的建立

從控制面板中雙擊管理工具圖示,然後在新出現的視窗中雙擊資料來源(ODBC)。在彈出的對話方塊中選擇不同的選項卡來確定建立資料來源的型別。這樣手工配置資料來源我個人感覺比較麻煩,是不是可以用API來自動實現配置呢?答案是肯定的。配置資料來源的程式碼如下:

  1.     SQLRETURN retcode;
  2.     retcode = SQLConfigDataSource(NULL,ODBC_ADD_SYS_DSN,"SQL Server","DSN=master/0Server=(local)/0Database=master/0/0");
  3. if(!retcode)
  4.     {
  5.         AfxMessageBox(
    "系統資料來源配置失敗!");
  6. return FALSE;
  7.     }

2、連線資料來源

在Visual C++程式中使用剛才建立的資料來源之前,還必須建立一個到資料來源的連線。以連線master資料庫為例,實現一個數據源的連線。程式碼如下:

  1. // 連線資料來源
  2. BOOL CDbLink::OpenDatabase()
  3. {
  4.     SQLINTEGER cbLenth = 0 ;    
  5.     SQLRETURN retcode;
  6.     retcode = SQLConfigDataSource(NULL,ODBC_ADD_SYS_DSN,"SQL Server","DSN=master/0Server=(local)/0Database=master/0/0"
    );
  7. if(!retcode)
  8.     {
  9.         AfxMessageBox("系統資料來源配置失敗!");
  10. return FALSE;
  11.     }
  12.     retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv) ;
  13. if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) 
  14.     {
  15.         retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); 
  16. if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) 
  17.         {
  18.             retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
  19. if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) 
  20.             {
  21.                 retcode = SQLConnect(hdbc, (SQLCHAR*)(LPCTSTR)m_strDSN, SQL_NTS, (SQLCHAR*)(LPCTSTR)m_strUSER, SQL_NTS, 
  22.                     (SQLCHAR*)(LPCTSTR)m_strPWD, SQL_NTS);
  23. if (!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
  24.                 {
  25.                     AfxMessageBox("資料庫連線失敗!") ;
  26.                     SQLFreeHandle(SQL_HANDLE_DBC, hdbc); 
  27.                     SQLFreeHandle(SQL_HANDLE_ENV, henv);
  28. return FALSE;
  29.                 }
  30. else
  31.                 {
  32.                     m_bLink = TRUE;
  33. return TRUE;
  34.                 }
  35.             } 
  36. else
  37.             {
  38.                 AfxMessageBox("連線控制代碼分配出錯") ;
  39.                 SQLFreeHandle(SQL_HANDLE_DBC, hdbc); 
  40.                 SQLFreeHandle(SQL_HANDLE_ENV, henv);
  41. return FALSE;
  42.             }
  43.         }
  44. else
  45.         {
  46.             AfxMessageBox("屬性設定出錯!") ;
  47.             SQLFreeHandle(SQL_HANDLE_ENV, henv);
  48. return FALSE;
  49.         }
  50.     }
  51. else
  52.     {
  53.         AfxMessageBox("環境變數分配出錯!") ;
  54.         SQLFreeHandle(SQL_HANDLE_ENV, henv);
  55. return FALSE;
  56.     }
  57. }

其中,連線資料來源中比較關鍵的是:資料來源名DSN、使用者名稱和密碼。

3、動態建立資料庫

這裡先動態建立一個數據庫,然後我們接下來以這個資料庫來分析資料庫的表的操作過程。那如何動態建立資料庫呢?這個可能有點挑戰吧!master資料庫控制SQL Server的所有方面,這個資料庫中包括所有的配置資訊、使用者登入資訊、當前正在伺服器中執行的過程的資訊,當然要想建立資料庫還是得靠他了,這個資料庫中有一張系統表sysdatabases就是用來專門負責登記所有資料庫的情況。所以你需要為master配置一個數據源,然後直接連線到這個資料來源後,執行建立資料庫的SQL語句就可以建立一個數據庫了,當然在建立資料庫之前你必須得到系統表sysdatabases查詢看看,你的資料庫是不是已經存在了,如果已經存在了就不必再建立了,明白了原理後,其實也很簡單的,現在讓我們踏上建立資料庫的美麗旅程吧!程式碼如下:

  1. // 判斷我們所建立的資料庫是否已經存在
  2. BOOL CDbLink::IsDatabaseExisted(CString strDbName)
  3. {
  4.     SQLHSTMT hstmt ;
  5.     SQLRETURN retcode;
  6.     SQLINTEGER cbLenth = 0 ;
  7.     CString strSQL;
  8.     strSQL.Format("SELECT * FROM sysdatabases WHERE name='%s'", strDbName);
  9.     retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);    
  10. if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) 
  11.     {   
  12. if (SQLExecDirect(hstmt, (SQLCHAR*)(LPCTSTR)strSQL, SQL_NTS) == SQL_ERROR)
  13.         {
  14.             SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
  15. return FALSE;
  16.         }
  17. if((SQLFetch(hstmt) == SQL_SUCCESS) || (SQLFetch(hstmt) == SQL_SUCCESS_WITH_INFO))
  18.         {       
  19.             SQLFreeHandle(SQL_HANDLE_STMT, hstmt);  
  20. return  TRUE;
  21.         }
  22. else
  23.         {
  24.             SQLFreeHandle(SQL_HANDLE_STMT, hstmt);  
  25. return FALSE;
  26.         }           
  27.     }
  28. else
  29.     {   
  30.         SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
  31. return FALSE;   
  32.     }
  33. }
  34. // 建立資料庫,其中strDbName代表資料庫名
  35. BOOL CDbLink::CreateDatabase(CString strDbName)
  36. {
  37.     SQLHSTMT hstmt ;
  38.     SQLRETURN retcode;
  39.     SQLINTEGER cbLenth = 0 ;
  40. BOOL bIsExisted = IsDatabaseExisted(strDbName);
  41. if(bIsExisted)
  42.     {
  43. return TRUE;
  44.     }
  45. else
  46.     {
  47.         CString strSQL;
  48.         strSQL.Format("CREATE DATABASE [%s]  ON (NAME = N'%s_dat', FILENAME = N'D://%s.mdf' ,SIZE = 39, FILEGROWTH = 2) LOG ON (NAME = N'%s_log', FILENAME = N'D://%s.ldf' , SIZE = 2, FILEGROWTH = 1) COLLATE Chinese_PRC_CI_AS"
  49.             strDbName,strDbName,strDbName,strDbName,strDbName);
  50.         retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);    
  51. if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) 
  52.         {   
  53. if (SQLExecDirect(hstmt, (SQLCHAR*)(LPCTSTR)strSQL, SQL_NTS) != SQL_ERROR)
  54.             {
  55.                 SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
  56. return TRUE;
  57.             }
  58. else
  59.             {
  60.                 SQLFreeHandle(SQL_HANDLE_STMT, hstmt);  
  61. return FALSE;
  62.             }           
  63.         }
  64. else
  65.         {   
  66.             SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
  67. return FALSE;   
  68.         }
  69.     }
  70. }

4、建立表

上面我們已經在SQL SERVER中建立了一個新的資料庫,當然這個資料庫除了裡面的系統表以外,別無它表。我們得自己在裡面建立一個表。如何建立一個表呢?同樣,這個時候我們得連線到我們新建立的資料庫的資料來源中,然後再執行建立表的SQL語句就可以建立一張表了,當然在建立表之前同樣得查詢看看這個表是否已經存在了。配置資料來源的部分仿照上面的步驟進行即可。建立表的SQL語句如下:

  1. CREATE TABLE [Obj_User] 
  2. (
  3. [User_Iden] [int] NOT NULL ,
  4. [User_Nina] [varchar] (32) COLLATE Chinese_PRC_CI_AS NOT NULL ,
  5. [User_Pawo] [varchar] (32) COLLATE Chinese_PRC_CI_AS NOT NULL ,
  6. [User_RoId] [int] NOT NULL ,
  7. [User_Name] [varchar] (32) COLLATE Chinese_PRC_CI_AS NULL ,
  8. [User_OfPh] [varchar] (32) COLLATE Chinese_PRC_CI_AS NULL ,
  9. [User_MoPh] [varchar] (32) COLLATE Chinese_PRC_CI_AS NULL ,
  10. [User_Mail] [varchar] (64) COLLATE Chinese_PRC_CI_AS NULL ,
  11. [User_QQ] [varchar] (16) COLLATE Chinese_PRC_CI_AS NULL ,
  12. [User_Addr] [varchar] (256) COLLATE Chinese_PRC_CI_AS NULL ,
  13. [User_Phot] [varchar] (256) COLLATE Chinese_PRC_CI_AS NULL ,
  14. [User_Born] [datetime] NULL ,
  15. [User_Job] [varchar] (64) COLLATE Chinese_PRC_CI_AS NULL ,
  16. [User_ShId] [int] NULL ,
  17. CONSTRAINT [PK_Obj_User] PRIMARY KEY  CLUSTERED ([User_Iden])  ON [PRIMARY] 
  18. ) ON [PRIMARY]

建立的表格名為Obj_User,如下:

欄位名 資料型別 主鍵 允許空 備註
User_Iden int * *(*代表不允許) 使用者ID
User_Nina varchar(32) * 使用者暱稱
User_Pawo varchar(32) * 使用者密碼
User_RoId int * 使用者角色
User_Name varchar(32) 使用者名稱稱
User_OfPh varchar(32) 辦公號碼
User_MoPh varchar(32) 手機號碼
User_Mail varchar(64) 使用者郵箱
User_QQ varchar(16) 使用者QQ
User_Addr varchar(256) 使用者地址
User_Phot varchar(256) 使用者相片
User_Born datetime 使用者生日
User_Job varchar(32) 使用者工作
User_ShId int 使用者店鋪
建立表程式碼如下:
  1. // 先判斷表是否已經存在,dbmarket資料庫的系統表sysobjects記錄了當前所建立的所有使用者表。 
  2. BOOL CDbOperator::IsTableExisted(CString strTableName)
  3. {
  4.     SQLHSTMT hstmt ;
  5.     SQLRETURN retcode;
  6.     SQLINTEGER cbLenth = 0 ;
  7.     CString strSQL;
  8.     strSQL.Format("SELECT * FROM sysobjects WHERE name='%s'", strTableName);
  9.     retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);    
  10. if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) 
  11.     {   
  12. if (SQLExecDirect(hstmt, (SQLCHAR*)(LPCTSTR)strSQL, SQL_NTS) == SQL_ERROR)
  13.         {
  14.             SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
  15. return FALSE;
  16.         }
  17. if((SQLFetch(hstmt) == SQL_SUCCESS) || (SQLFetch(hstmt) == SQL_SUCCESS_WITH_INFO))
  18.         {       
  19.             SQLFreeHandle(SQL_HANDLE_STMT, hstmt);  
  20. return  TRUE;
  21.         }
  22. else
  23.         {
  24.             SQLFreeHandle(SQL_HANDLE_STMT, hstmt);  
  25. return FALSE;
  26.         }           
  27.     }
  28. else
  29.     {   
  30.         SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
  31. return FALSE;   
  32.     }
  33. }
  34. // 執行建立表操作
  35. BOOL CDbOperator::CreateSQL(CString strSQL)
  36. {
  37.     SQLHSTMT hstmt ;
  38.     SQLRETURN retcode;
  39.     SQLINTEGER cbLenth = 0 ;
  40.     retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);    
  41. if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) 
  42.     {   
  43. if (SQLExecDirect(hstmt, (SQLCHAR*)(LPCTSTR)strSQL, SQL_NTS) != SQL_ERROR)
  44.         {
  45.             SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
  46. return TRUE;
  47.         }
  48. else
  49.         {
  50.             SQLFreeHandle(SQL_HANDLE_STMT, hstmt);  
  51. return FALSE;
  52.         }           
  53.     }
  54. else
  55.     {   
  56.         SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
  57. return FALSE;   
  58.     }   
  59. }

4、訪問表

更新、插入、刪除語句都比較簡單,直接執行SQL語句即可實現,現在就Select情況做如下說明:

1、準備SQL語句。

2、將指定的變數與列繫結,記住你成功的關鍵在於你對SQL中繫結型別的熟練程度。

3、提取資料,每提取一個記錄後,遊標自動向後移動,直到移動到最後時返回。

為了提高程式碼的可重用性,應該宣告一個的標頭檔案並提供統一資料庫介面。根據上面的表模型,設計的結構體USER,程式碼如下:

  1. #ifndef __DBDEFINE_H__
  2. #define __DBDEFINE_H__
  3. typedefstruct _USER
  4. {
  5. int Iden;
  6. char Nina[32];
  7. char Pawo[32];
  8. int RoId;
  9. char Name[32];
  10. char OfPh[32];
  11. char MoPh[32];
  12. char Mail[64];
  13. char QQ[16];
  14. char Addr[256];
  15. char Phot[256];
  16.     SQL_TIMESTAMP_STRUCT Born;
  17. char Job[32];
  18. int ShId;
  19. }USER, *PUSER;
  20. #endif
  21. // 根據使用者ID獲取使用者資訊
  22. BOOL CDbOperator::GetUserByUserID(int nUserId, USER* pUser)
  23. {
  24.     SQLHSTMT hstmt ;
  25.     SQLRETURN retcode;
  26.     SQLINTEGER cbLenth = 0 ;
  27.     ZeroMemory(pUser, sizeof(USER));
  28.     pUser->Iden = nUserId;
  29.     CString strSQL;
  30.     strSQL.Format("SELECT User_Nina, User_Pawo, User_RoId, User_Name, User_OfPh, User_MoPh, User_Mail, User_QQ, User_Addr,/
  31.                   User_Phot, User_Born, User_Job, User_ShId FROM Obj_User WHERE User_Iden=%d", nUserId);
  32.     retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);    
  33. if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) 
  34.     {   
  35.         SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->Nina, 32, &cbLenth);    
  36.         SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->Pawo, 32, &cbLenth);    
  37.         SQLBindCol(hstmt, 1, SQL_C_ULONG, (SQLPOINTER)&pUser->RoId, 4, &cbLenth);   
  38.         SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->Name, 32, &cbLenth);    
  39.         SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->OfPh, 32, &cbLenth);    
  40.         SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->MoPh, 32, &cbLenth);    
  41.         SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->Mail, 64, &cbLenth);    
  42.         SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->QQ, 16, &cbLenth);  
  43.         SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->Addr, 256, &cbLenth);   
  44.         SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->Phot, 256, &cbLenth);   
  45.         SQLBindCol(hstmt, 1, SQL_C_TYPE_TIMESTAMP, (SQLPOINTER)&pUser->Born, sizeof(SQL_C_TYPE_TIMESTAMP), &cbLenth);   
  46.         SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->Job, 32, &cbLenth); 
  47.         SQLBindCol(hstmt, 1, SQL_C_ULONG, (SQLPOINTER)&pUser->ShId, 4, &cbLenth);   
  48. if (SQLExecDirect(hstmt, (SQLCHAR*)(LPCTSTR)strSQL, SQL_NTS) == SQL_ERROR)
  49.         {
  50.             AfxMessageBox(strSQL) ;
  51.             SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
  52. return FALSE;
  53.         }
  54.         retcode = SQLFetch(hstmt);
  55. if(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
  56.         {       
  57.         }
  58. else
  59.         {
  60.             AfxMessageBox(strSQL) ;
  61.             SQLFreeHandle(SQL_HANDLE_STMT, hstmt);  
  62. return FALSE;
  63.         }
  64.         SQLFreeHandle(SQL_HANDLE_STMT, hstmt);  
  65. return  TRUE;       
  66.     }
  67. else
  68.     {   
  69.         AfxMessageBox(strSQL) ;
  70.         SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
  71. return FALSE;   
  72.     }
  73. }

總結:

用ODBC API來訪問資料庫其實是一件比較簡單的事情,做完一次後,以後複製就可以了。設計上時候,資料庫的部分應該先設計好各種共同的結構宣告,然後實現對資料庫的介面,以DLL方式提供給使用者。使用者不必再考慮資料庫的訪問問題,若資料庫接口出現錯誤,直接跟資料庫編寫的人聯絡就OK。這樣資料庫的訪問就統一了,避免了各個程式設計師都要寫資料庫訪問介面的麻煩。但呼叫時候必須有共同的資料庫的結構宣告,以及你提供的資料庫介面函式的說明。呼叫者只要嵌入標頭檔案後,產生資料庫物件即可訪問,因為我的設計是將資料庫的連線寫在建構函式裡,呼叫程式碼如下:

  1. #include "DbOperator/DbOperator.h"
  2. CDbOperator dbOperator;// 建構函式實現連線
  3. if(!dbOperator.m_bLink)
  4. {
  5.     AfxMessageBox("資料庫連線不成功!");
  6. return FALSE;
  7. }
  8. dbOperator.InitAllTable(); // 統一的資料庫介面函式,實現從檔案中讀取SQL語句,執行建立比操作。
  9. dbOperator.InitTableContent();// 從檔案中讀取你想要初始化的內容寫入到我們所建立的表中。

至於其他的訪問,比如修改表的欄位、Select一個image的內容等相對複雜操作,大家可以查閱相關資料獲取。

動態建立資料庫的實現效果如下: