用C++封裝MySQL的API的教程
其實(shí)相信每個和mysql打過交道的程序員都應(yīng)該會嘗試去封裝一套mysql的接口,這一次的封裝已經(jīng)記不清是我第幾次了,但是每一次我希望都能做的比上次更好,更容易使用。
先來說一下這次的封裝,遵守了幾個原則,其中部分思想是從python借鑒過來的:
1.簡單
簡單,意味著不為了微小的效率提升,而去把接口搞的復(fù)雜。因?yàn)楸旧頂?shù)據(jù)庫存儲效率的瓶頸并不是那一兩次內(nèi)存copy,代碼中隨處可以看到以這個為依據(jù)的設(shè)計(jì)。
2.低學(xué)習(xí)成本
使用一套新庫通常意味著投入學(xué)習(xí)成本,而這次的封裝并沒有像django那樣實(shí)現(xiàn)一套完整的模型系統(tǒng),也沒有做soci那樣的語法分析器,我選擇最簡單易懂的方式:做sql語句拼接器,所以對習(xí)慣了使用原生mysql api的朋友,學(xué)習(xí)成本很低
3.模塊化
代碼實(shí)際包括了兩個模塊,一個是mysql client端的封裝,一個是sql的拼接器,這兩個模塊是完全獨(dú)立的,調(diào)用者可以任意組合或者獨(dú)立使用。
4.盡量使用STL以及模板,簡化代碼編寫
最大的特點(diǎn)就是大量使用了stringstream進(jìn)行類型轉(zhuǎn)化,減少了大量的重復(fù)代碼。
OK,基于以上的簡單介紹,我們先來看一下
一.mysql client端的封裝:
class CMYSQLWrapper { /** * @brief 獲取錯誤信息 * * @return 錯誤信息 */ char* GetErrMsg(); /** * @brief 連接MYSQL,已經(jīng)支持了自動重連模式,即mysql server關(guān)閉鏈接會自動重連 * * @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 關(guān)閉鏈接并釋放result */ void Close(); /** * @brief 執(zhí)行SQL語句 * * @param strSql 執(zhí)行語句 * @param result 執(zhí)行結(jié)果 * * @return 0 succ * else fail */ int Query(const char* strSql); /** * @brief 針對Read(select)相關(guān)的的Query,可以支持blob了 * * @param strSql sql語句 * @param vecData rows * * @return 0 succ * else fail */ int Query(const char* strSql, vector<map<string, MYSQLValue> > &vecData); /** * @brief 針對Write(insert,update,delete)相關(guān)的Query * * @param strSql sql語句 * @param affectRowsCount 影響的行的個數(shù) * * @return 0 succ * else fail */ int Query(const char* strSql, int& affectRowsCount); /** * @brief Select時獲取數(shù)據(jù),記得手工析構(gòu),或者用StMYSQLRes * * @param result 執(zhí)行結(jié)果 * * @return 0 succ * else fail */ int Result(MYSQL_RES *&result); /** * @brief 返回影響行數(shù) * * @return >0 succ * 0 沒有更新 * <0 fail */ int AffectedRows(); /** * @brief 主要是將blob轉(zhuǎn)成字符串 * * @param src blob源 * @param len 長度 * * @return 轉(zhuǎn)化后的字符串 */ string EscStr(const char* src,uint32_t len); /** * @brief 將字符串中的某些字符轉(zhuǎn)化(如') * * @param src 字符串 * * @return 轉(zhuǎn)化后的字符串 */ string EscStr(const char* src); }; class CMYSQLWrapper { /** * @brief 獲取錯誤信息 * * @return 錯誤信息 */ char* GetErrMsg(); /** * @brief 連接MYSQL,已經(jīng)支持了自動重連模式,即mysql server關(guān)閉鏈接會自動重連 * * @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 關(guān)閉鏈接并釋放result */ void Close(); /** * @brief 執(zhí)行SQL語句 * * @param strSql 執(zhí)行語句 * @param result 執(zhí)行結(jié)果 * * @return 0 succ * else fail */ int Query(const char* strSql); /** * @brief 針對Read(select)相關(guān)的的Query,可以支持blob了 * * @param strSql sql語句 * @param vecData rows * * @return 0 succ * else fail */ int Query(const char* strSql, vector<map<string, MYSQLValue> > &vecData); /** * @brief 針對Write(insert,update,delete)相關(guān)的Query * * @param strSql sql語句 * @param affectRowsCount 影響的行的個數(shù) * * @return 0 succ * else fail */ int Query(const char* strSql, int& affectRowsCount); /** * @brief Select時獲取數(shù)據(jù),記得手工析構(gòu),或者用StMYSQLRes * * @param result 執(zhí)行結(jié)果 * * @return 0 succ * else fail */ int Result(MYSQL_RES *&result); /** * @brief 返回影響行數(shù) * * @return >0 succ * 0 沒有更新 * <0 fail */ int AffectedRows(); /** * @brief 主要是將blob轉(zhuǎn)成字符串 * * @param src blob源 * @param len 長度 * * @return 轉(zhuǎn)化后的字符串 */ string EscStr(const char* src,uint32_t len); /** * @brief 將字符串中的某些字符轉(zhuǎn)化(如') * * @param src 字符串 * * @return 轉(zhuǎn)化后的字符串 */ string EscStr(const char* src); };
代碼中的注釋已經(jīng)描述的很清楚了,語言描述不清楚,我們直接來看一下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<map<string,MYSQLValue> > 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<uint32_t>(); } 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<map<string,MYSQLValue> > 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<uint32_t>(); } 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的收發(fā)包已經(jīng)很簡潔了,但是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 分割符,默認(rèn)是用',',也可以用and、or之類 * * @return 所有列名-列值 */ string pairs(const string& split_str = ","); /** * @brief 清空所有數(shù)據(jù) */ 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 分割符,默認(rèn)是用',',也可以用and、or之類 * * @return 所有列名-列值 */ string pairs(const string& split_str = ","); /** * @brief 清空所有數(shù)據(jù) */ void clear(); };
看看我們用了SQLJoin之后的代碼應(yīng)該如何:
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(); }
從上面的代碼可以看出,代碼的可維護(hù)性和健壯性得到了很大的提升。
OK,簡單的介紹就是這樣,說的比較簡略,大家有興趣可以直接看代碼,也歡迎給我提意見和建議。代碼下載路徑如下:
mysql_wrapper
明天這份代碼就會作為數(shù)據(jù)庫訪問層正式進(jìn)入生產(chǎn)環(huán)境的代碼中,因此有什么bug我也會及時在這里更新。
相關(guān)文章
Python獲取網(wǎng)頁數(shù)據(jù)詳解流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用Python來獲取網(wǎng)頁的數(shù)據(jù),主要應(yīng)用了Requests庫,大家可以在過程中查缺補(bǔ)漏,提升水平2021-10-10python 生成器協(xié)程運(yùn)算實(shí)例
下面小編就為大家?guī)硪黄猵ython 生成器協(xié)程運(yùn)算實(shí)例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09解決TensorFlow模型恢復(fù)報(bào)錯的問題
今天小編就為大家分享一篇解決TensorFlow模型恢復(fù)報(bào)錯的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-02-02Python的Flask框架開發(fā)驗(yàn)證碼登錄的實(shí)現(xiàn)
在本文我們介紹了如何使用Python的Flask框架開發(fā)一個簡單的驗(yàn)證碼登錄功能,將涵蓋生成驗(yàn)證碼、處理用戶輸入、驗(yàn)證驗(yàn)證碼以及實(shí)現(xiàn)安全的用戶認(rèn)證等方面,感興趣的可以了解一下2023-11-11MacOS安裝python報(bào)錯"zsh:?command?not?found:python"的
這篇文章主要給大家介紹了關(guān)于MacOS安裝python報(bào)錯"zsh:?command?not?found:python"的解決方法,文中將解決的辦法介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02Python中使用matplotlib模塊errorbar函數(shù)繪制誤差棒圖實(shí)例代碼
在matplotlib中,errorbar方法用于繪制帶誤差線的折線圖,下面這篇文章主要給大家介紹了關(guān)于Python中使用matplotlib模塊errorbar函數(shù)繪制誤差棒圖的相關(guān)資料,需要的朋友可以參考下2022-08-08Python實(shí)現(xiàn)PC屏幕截圖并自動發(fā)送郵件
在當(dāng)前的數(shù)字化世界中,自動化已經(jīng)成為我們?nèi)粘I詈凸ぷ髦械年P(guān)鍵部分,本文我們將探討如何使用Python來實(shí)現(xiàn)一個特定的自動化任務(wù) - PC屏幕截圖自動發(fā)送到指定的郵箱,感興趣的可以了解下2023-11-11