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


Posted in Python onMay 06, 2015

其实相信每个和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<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的代码:

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

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我也会及时在这里更新。

Python 相关文章推荐
Python的Django框架中使用SQLAlchemy操作数据库的教程
Jun 02 Python
深入解析Python中的线程同步方法
Jun 14 Python
Django应用程序中如何发送电子邮件详解
Feb 04 Python
Python模拟鼠标点击实现方法(将通过实例自动化模拟在360浏览器中自动搜索python)
Aug 23 Python
Python数据结构与算法之图结构(Graph)实例分析
Sep 05 Python
python中模块的__all__属性详解
Oct 26 Python
python删除过期log文件操作实例解析
Jan 31 Python
Python 实现在文件中的每一行添加一个逗号
Apr 29 Python
Python3+Appium安装使用教程
Jul 05 Python
手把手教你pycharm专业版安装破解教程(linux版)
Sep 26 Python
python用Tkinter做自己的中文代码编辑器
Sep 07 Python
Python基于callable函数检测对象是否可被调用
Oct 16 Python
使用wxPython获取系统剪贴板中的数据的教程
May 06 #Python
用Python遍历C盘dll文件的方法
May 06 #Python
使用Python压缩和解压缩zip文件的教程
May 06 #Python
Python发送以整个文件夹的内容为附件的邮件的教程
May 06 #Python
在Linux中通过Python脚本访问mdb数据库的方法
May 06 #Python
python中黄金分割法实现方法
May 06 #Python
使用rpclib进行Python网络编程时的注释问题
May 06 #Python
You might like
用php将任何格式视频转为flv的代码
2009/09/03 PHP
php 读取文件乱码问题
2010/02/20 PHP
CI(CodeIgniter)框架配置
2014/06/10 PHP
round robin权重轮循算法php实现代码
2016/05/28 PHP
php输出图像的方法实例分析
2017/02/16 PHP
浅谈PHP中pack、unpack的详细用法
2018/03/12 PHP
php 读取文件夹下所有图片、文件的实例
2018/10/17 PHP
通过代码实例解析PHP session工作原理
2020/12/11 PHP
jQuery 瀑布流 浮动布局(一)(延迟AJAX加载图片)
2012/05/23 Javascript
使用insertAfter()方法在现有元素后添加一个新元素
2014/05/28 Javascript
node.js中的buffer.Buffer.isBuffer方法使用说明
2014/12/14 Javascript
JavaScript中的标签语句用法分析
2015/02/10 Javascript
微信中一些常用的js方法汇总
2015/03/12 Javascript
javascript获取当前的时间戳的方法汇总
2015/07/26 Javascript
jQuery实现彩带延伸效果的网页加载条loading动画
2015/10/29 Javascript
JS控制页面跳转时未请求要跳转的地址怎么回事
2016/10/14 Javascript
基于jQuery中ajax的相关方法汇总(必看篇)
2017/11/08 jQuery
JavaScript实现求最大公共子串的方法
2018/02/03 Javascript
webpack+vue-cil 中proxyTable配置接口地址代理操作
2020/07/18 Javascript
python实现比较两段文本不同之处的方法
2015/05/30 Python
使用Pandas的Series方法绘制图像教程
2019/12/04 Python
一款纯css3实现的响应式导航
2014/10/31 HTML / CSS
微软澳洲官方网站:Microsoft Australia
2017/01/10 全球购物
WatchShop法国:英国排名第一的独立手表零售商
2020/02/17 全球购物
软件测试企业面试试卷
2016/07/13 面试题
市场营销专业推荐信
2013/11/03 职场文书
房地产销售经理岗位职责
2014/01/01 职场文书
出纳工作岗位责任制
2014/02/02 职场文书
布达拉宫导游词
2015/02/02 职场文书
幼儿园百日安全活动总结
2015/05/07 职场文书
不知如何爱孩子,这些方法教会您
2019/08/06 职场文书
古诗之爱国古诗5首
2019/09/20 职场文书
曾国藩励志经典名言37句,蕴含哲理
2019/10/14 职场文书
MySQL5.7并行复制原理及实现
2021/06/03 MySQL
java多态注意项小结
2021/10/16 Java/Android
mybatis源码解读之executor包语句处理功能
2022/02/15 Java/Android