用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实现设置windows桌面壁纸代码分享
Mar 28 Python
python通过邮件服务器端口发送邮件的方法
Apr 30 Python
Python中偏函数用法示例
Jun 07 Python
Python实现字典排序、按照list中字典的某个key排序的方法示例
Dec 18 Python
PyTorch的深度学习入门教程之构建神经网络
Jun 27 Python
Python matplotlib学习笔记之坐标轴范围
Jun 28 Python
在Python中构建增广矩阵的实现方法
Jul 01 Python
python按键按住不放持续响应的实例代码
Jul 17 Python
Python判断字符串是否xx开始或结尾的示例
Aug 08 Python
Scrapy实现模拟登录的示例代码
Feb 21 Python
上手简单,功能强大的Python爬虫框架——feapder
Apr 27 Python
python基础之类属性和实例属性
Oct 24 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中创建和验证哈希的简单方法实探
2015/07/06 PHP
PHP中__autoload和Smarty冲突的简单解决方法
2016/04/08 PHP
laravel 5.5 关闭token的3种实现方式
2019/10/24 PHP
js 表单提交后按钮变灰的实例代码
2013/08/16 Javascript
js导航栏单击事件背景变换示例代码
2014/01/13 Javascript
用js将内容复制到剪贴板兼容浏览器
2014/03/18 Javascript
Javascript中的几种URL编码方法比较
2015/01/23 Javascript
JavaScript程序设计之JS调试
2015/12/09 Javascript
Javascript中的数组常用方法解析
2016/06/17 Javascript
Vue组件BootPage实现简单的分页功能
2016/09/12 Javascript
easyUI下拉列表点击事件使用方法
2017/05/18 Javascript
ES6中的Promise代码详解
2017/10/09 Javascript
webpack下实现动态引入文件方法
2018/02/22 Javascript
javascript中toFixed()四舍五入使用方法详解
2018/09/28 Javascript
搭建基于express框架运行环境的方法步骤
2018/11/15 Javascript
node.js实现为PDF添加水印的示例代码
2018/12/05 Javascript
vue-cli webpack配置文件分析
2019/05/20 Javascript
vue实现一个6个输入框的验证码输入组件功能的实例代码
2020/06/29 Javascript
[14:20]刀塔大凶女神互压各路奇葩屌丝
2014/05/16 DOTA
Python Trie树实现字典排序
2014/03/28 Python
Python中的异常处理相关语句基础学习笔记
2016/07/11 Python
浅谈pyhton学习中出现的各种问题(新手必看)
2017/05/17 Python
Django中的CBV和FBV示例介绍
2018/02/25 Python
Python中pip更新和三方插件安装说明
2018/07/08 Python
Python字典推导式将cookie字符串转化为字典解析
2019/08/10 Python
python十进制转二进制的详解
2020/02/07 Python
如何基于Python代码实现高精度免费OCR工具
2020/06/18 Python
trivago美国:全球最大的酒店价格比较网站
2018/01/18 全球购物
北京泡泡网网络有限公司.net面试题
2012/07/17 面试题
装潢设计专业推荐信模板
2013/11/26 职场文书
工程造价专业大学生职业规划范文
2014/03/09 职场文书
企业党的群众路线教育实践活动学习心得体会
2014/10/31 职场文书
农村党员干部承诺书
2015/05/04 职场文书
工作转正自我鉴定范文
2019/06/21 职场文书
sql字段解析器的实现示例
2021/06/23 SQL Server
《金肉人》米特&《航海王》阿鹤声优松岛实因胰脏癌去世 享寿81岁
2022/04/13 日漫