其實相信每個和mysql打過交道的程序員都應該會嘗試去封裝一套mysql的接口,這一次的封裝已經記不清是我第幾次了,但是每一次我希望都能做的比上次更好,更容易使用。
先來說一下這次的封裝,遵守了幾個原則,其中部分思想是從python借鑒過來的:
1.簡單
簡單,意味著不為了微小的效率提升,而去把接口搞的復雜。因為本身數據庫存儲效率的瓶頸并不是那一兩次內存copy,代碼中隨處可以看到以這個為依據的設計。
2.低學習成本
使用一套新庫通常意味著投入學習成本,而這次的封裝并沒有像django那樣實現一套完整的模型系統,也沒有做soci那樣的語法分析器,我選擇最簡單易懂的方式:做sql語句拼接器,所以對習慣了使用原生mysql api的朋友,學習成本很低
3.模塊化
代碼實際包括了兩個模塊,一個是mysql client端的封裝,一個是sql的拼接器,這兩個模塊是完全獨立的,調用者可以任意組合或者獨立使用。
4.盡量使用STL以及模板,簡化代碼編寫
最大的特點就是大量使用了stringstream進行類型轉化,減少了大量的重復代碼。
OK,基于以上的簡單介紹,我們先來看一下
一.mysql client端的封裝:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
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<map<string, MYSQLValue> > &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<map<string, MYSQLValue> > &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的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
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的收發包已經很簡潔了,但是sql語句的拼裝卻顯得十分臃腫。所以這個時候sql語句拼裝器-SQLJoin閃亮登場!
二.sql語句拼裝器-SQLJoin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
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之后的代碼應該如何:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
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我也會及時在這里更新。