用C++封裝MySQL的API的教程

來源:互聯網
上載者:User
其實相信每個和mysql打過交道的程式員都應該會嘗試去封裝一套mysql的介面,這一次的封裝已經記不清是我第幾次了,但是每一次我希望都能做的比上次更好,更容易使用。

先來說一下這次的封裝,遵守了幾個原則,其中部分思想是從python借鑒過來的:

1.簡單

簡單,意味著不為了微小的效率提升,而去把介面搞的複雜。因為本身資料庫儲存效率的瓶頸並不是那一兩次記憶體copy,代碼中隨處可以看到以這個為依據的設計。
2.低學習成本

使用一套新庫通常意味著投入學習成本,而這次的封裝並沒有像django那樣實現一套完整的模型系統,也沒有做soci那樣的文法分析器,我選擇最簡單易懂的方式:做sql語句拼接器,所以對習慣了使用原生mysql api的朋友,學習成本很低
3.模組化

代碼實際包括了兩個模組,一個是mysql client端的封裝,一個是sql的拼接器,這兩個模組是完全獨立的,調用者可以任意組合或者獨立使用。
4.盡量使用STL以及模板,簡化代碼編寫

最大的特點就是大量使用了stringstream進行類型轉化,減少了大量的重複代碼。

OK,基於以上的簡單介紹,我們先來看一下
一.mysql client端的封裝:

class CMYSQLWrapper{ /**  * @brief 擷取錯誤資訊  *  * @return 錯誤資訊  */ char* GetErrMsg(); /**  * @brief 串連MYSQL,已經支援了自動重連模式,即mysql server關閉連結會自動重連  *  * @param ip   IP  * @param user  使用者名稱  * @param pwd   密碼(沒有則傳NULL)  * @param db   庫(沒有則傳NULL)  *  * @return 0   succ  *   else  fail  */ int Open(const char* ip,const char* user,const char* pwd,const char* strDb); /**  * @brief 關閉連結並釋放result  */ void Close(); /**  * @brief 執行SQL語句  *  * @param strSql  執行語句  * @param result  執行結果  *  * @return 0   succ  *   else  fail  */ int Query(const char* strSql); /**  * @brief 針對Read(select)相關的的Query,可以支援blob了  *  * @param strSql   sql語句  * @param vecData   rows  *  * @return 0    succ  *   else   fail  */ int Query(const char* strSql, vector > &vecData); /**  * @brief 針對Write(insert,update,delete)相關的Query  *  * @param strSql   sql語句  * @param affectRowsCount 影響的行的個數  *  * @return 0    succ  *   else   fail  */ int Query(const char* strSql, int& affectRowsCount); /**  * @brief Select時擷取資料,記得手工析構,或者用StMYSQLRes  *  * @param result  執行結果  *  * @return 0   succ  *   else  fail  */ int Result(MYSQL_RES *&result); /**  * @brief 返回影響行數  *  * @return >0   succ  *   0   沒有更新  *   <0   fail  */ int AffectedRows(); /**  * @brief 主要是將blob轉成字串  *  * @param src   blob源  * @param len   長度  *  * @return 轉化後的字串  */ string EscStr(const char* src,uint32_t len); /**  * @brief 將字串中的某些字元轉化(如')  *  * @param src   字串  *  * @return 轉化後的字串  */ string EscStr(const char* src);}; class CMYSQLWrapper{ /**  * @brief 擷取錯誤資訊  *  * @return 錯誤資訊  */ char* GetErrMsg();  /**  * @brief 串連MYSQL,已經支援了自動重連模式,即mysql server關閉連結會自動重連  *  * @param ip   IP  * @param user  使用者名稱  * @param pwd   密碼(沒有則傳NULL)  * @param db   庫(沒有則傳NULL)  *  * @return 0   succ  *   else  fail  */ int Open(const char* ip,const char* user,const char* pwd,const char* strDb);  /**  * @brief 關閉連結並釋放result  */ void Close();  /**  * @brief 執行SQL語句  *  * @param strSql  執行語句  * @param result  執行結果  *  * @return 0   succ  *   else  fail  */ int Query(const char* strSql);  /**  * @brief 針對Read(select)相關的的Query,可以支援blob了  *  * @param strSql   sql語句  * @param vecData   rows  *  * @return 0    succ  *   else   fail  */ int Query(const char* strSql, vector > &vecData);  /**  * @brief 針對Write(insert,update,delete)相關的Query  *  * @param strSql   sql語句  * @param affectRowsCount 影響的行的個數  *  * @return 0    succ  *   else   fail  */ int Query(const char* strSql, int& affectRowsCount);   /**  * @brief Select時擷取資料,記得手工析構,或者用StMYSQLRes  *  * @param result  執行結果  *  * @return 0   succ  *   else  fail  */ int Result(MYSQL_RES *&result);  /**  * @brief 返回影響行數  *  * @return >0   succ  *   0   沒有更新  *   <0   fail  */ int AffectedRows();  /**  * @brief 主要是將blob轉成字串  *  * @param src   blob源  * @param len   長度  *  * @return 轉化後的字串  */ string EscStr(const char* src,uint32_t len);  /**  * @brief 將字串中的某些字元轉化(如')  *  * @param src   字串  *  * @return 轉化後的字串  */ string EscStr(const char* src);};

代碼中的注釋已經描述的很清楚了,語言描述不清楚,我們直接來看一下gtest的代碼:

select:string g_name = "good";int g_sex = 1;string g_name_up = "update";int g_sex_up = 2;TEST(mysql_wrapper_easy, select){ vector > vecData; string sql = "select * from tb_test where name = '"+g_name_up+"'"; int ret = g_client.Query(sql.c_str(),vecData); ASSERT_EQ(ret, 0) << g_client.GetErrMsg(); foreach(vecData, it_vec) {   foreach(*it_vec, it_map)  {    cout << it_map->first << ",";   if (it_map->first == "sex")   {    cout << it_map->second.as();   }   else   {    cout << it_map->second.data();   }   cout << "," << it_map->second.size() << endl;  }  }}int main(int argc, char **argv){ int ret = g_client.Open("localhost","dantezhu",NULL,"soci"); //int ret = g_client.Open("127.0.0.1","dantezhu",NULL,"soci"); if (ret) {  cout << ret << "," << g_client.GetErrMsg() << endl;  return -1; } ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();} string g_name = "good";int g_sex = 1; string g_name_up = "update";int g_sex_up = 2; TEST(mysql_wrapper_easy, select){ vector > vecData; string sql = "select * from tb_test where name = '"+g_name_up+"'"; int ret = g_client.Query(sql.c_str(),vecData); ASSERT_EQ(ret, 0) << g_client.GetErrMsg();  foreach(vecData, it_vec) {   foreach(*it_vec, it_map)  {    cout << it_map->first << ",";   if (it_map->first == "sex")   {    cout << it_map->second.as();   }   else   {    cout << it_map->second.data();   }   cout << "," << it_map->second.size() << endl;  }  }}int main(int argc, char **argv){ int ret = g_client.Open("localhost","dantezhu",NULL,"soci"); //int ret = g_client.Open("127.0.0.1","dantezhu",NULL,"soci"); if (ret) {  cout << ret << "," << g_client.GetErrMsg() << endl;  return -1; } ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();}insert:TEST(mysql_wrapper_easy, insert){ clear_data(); stringstream ss; ss   << "insert into tb_test(name,sex) values('"  << g_client.EscStr(g_name.c_str())  << "',"  << g_sex  << ");"; int affectRowsNum; int ret = g_client.Query(ss.str().c_str(), affectRowsNum); ASSERT_EQ(ret, 0) << g_client.GetErrMsg(); EXPECT_GE(affectRowsNum,0) << g_client.GetErrMsg();} TEST(mysql_wrapper_easy, insert){ clear_data(); stringstream ss; ss   << "insert into tb_test(name,sex) values('"  << g_client.EscStr(g_name.c_str())  << "',"  << g_sex  << ");";  int affectRowsNum; int ret = g_client.Query(ss.str().c_str(), affectRowsNum); ASSERT_EQ(ret, 0) << g_client.GetErrMsg();  EXPECT_GE(affectRowsNum,0) << g_client.GetErrMsg();}

可以看出,對於mysql的收發包已經很簡潔了,但是sql語句的拼裝卻顯得十分臃腫。所以這個時候sql語句拼裝器-SQLJoin閃亮登場!
二.sql語句拼裝器-SQLJoin

class SQLJoin{public: /**  * @brief 用流處理的方式,添加一個列名  *  * @param key   列名  *  * @return 0  */ SQLJoin& operator << (const string& key); /**  * @brief 用流處理的方式,添加一個SQLPair對象  *  * @param pair_data  SQLPair對象  *  * @return 0  */ SQLJoin& operator << (const SQLPair& pair_data); /**  * @brief 輸出所有列名(如name, sex, age)  *  * @return 所有列名  */ string keys(); /**  * @brief 輸出所有列值(如'dante', 1, 25)  *  * @return 所有列值  */ string values(); /**  * @brief 輸入所有列名-列值,並用指定分隔字元分割(如name='dante', sex=1, age=25)  *  * @param split_str 分割符,預設是用',',也可以用and、or之類  *  * @return 所有列名-列值  */ string pairs(const string& split_str = ","); /**  * @brief 清空所有資料  */ void clear();}; class SQLJoin{public: /**  * @brief 用流處理的方式,添加一個列名  *  * @param key   列名  *  * @return 0  */ SQLJoin& operator << (const string& key);  /**  * @brief 用流處理的方式,添加一個SQLPair對象  *  * @param pair_data  SQLPair對象  *  * @return 0  */ SQLJoin& operator << (const SQLPair& pair_data);  /**  * @brief 輸出所有列名(如name, sex, age)  *  * @return 所有列名  */ string keys();  /**  * @brief 輸出所有列值(如'dante', 1, 25)  *  * @return 所有列值  */ string values();  /**  * @brief 輸入所有列名-列值,並用指定分隔字元分割(如name='dante', sex=1, age=25)  *  * @param split_str 分割符,預設是用',',也可以用and、or之類  *  * @return 所有列名-列值  */ string pairs(const string& split_str = ",");  /**  * @brief 清空所有資料  */ void clear();};

看看我們用了SQLJoin之後的代碼應該如何:

TEST(mysql_wrapper_join, insert){ clear_data(); SQLJoin sql_join; sql_join   << SQLPair("name", g_client.EscapeRealString(g_name.c_str()))  << SQLPair("sex", g_sex); stringstream ss; ss   << "insert into tb_test("  << sql_join.keys()  << ") values("  << sql_join.values()  << ")"; int affectRowsNum; int ret = g_client.ExecuteWrite(ss.str().c_str(), affectRowsNum); ASSERT_EQ(ret, 0) << g_client.GetErrMsg(); EXPECT_GE(affectRowsNum,0) << g_client.GetErrMsg();}TEST(mysql_wrapper_join, update){ SQLJoin sql_join; sql_join   << SQLPair("name", g_name_up)  << SQLPair("sex", g_sex_up); stringstream ss; ss   << "update tb_test set "  << sql_join.pairs()  << " where name='"  << g_name  <<"';"; int affectRowsNum; int ret = g_client.ExecuteWrite(ss.str().c_str(),affectRowsNum); ASSERT_EQ(ret, 0) << g_client.GetErrMsg(); EXPECT_GE(affectRowsNum,0) << g_client.GetErrMsg();} TEST(mysql_wrapper_join, insert){ clear_data();  SQLJoin sql_join; sql_join   << SQLPair("name", g_client.EscapeRealString(g_name.c_str()))  << SQLPair("sex", g_sex);  stringstream ss; ss   << "insert into tb_test("  << sql_join.keys()  << ") values("  << sql_join.values()  << ")";  int affectRowsNum; int ret = g_client.ExecuteWrite(ss.str().c_str(), affectRowsNum); ASSERT_EQ(ret, 0) << g_client.GetErrMsg();  EXPECT_GE(affectRowsNum,0) << g_client.GetErrMsg();}TEST(mysql_wrapper_join, update){ SQLJoin sql_join; sql_join   << SQLPair("name", g_name_up)  << SQLPair("sex", g_sex_up);  stringstream ss; ss   << "update tb_test set "  << sql_join.pairs()  << " where name='"  << g_name  <<"';"; int affectRowsNum; int ret = g_client.ExecuteWrite(ss.str().c_str(),affectRowsNum); ASSERT_EQ(ret, 0) << g_client.GetErrMsg();  EXPECT_GE(affectRowsNum,0) << g_client.GetErrMsg();}

從上面的代碼可以看出,代碼的可維護性和健壯性得到了很大的提升。

OK,簡單的介紹就是這樣,說的比較簡略,大家有興趣可以直接看代碼,也歡迎給我提意見和建議。代碼下載路徑如下:
mysql_wrapper

明天這份代碼就會作為資料庫訪問層正式進入生產環境的代碼中,因此有什麼bug我也會及時在這裡更新。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.