共计 10735 个字符,预计需要花费 27 分钟才能阅读完成。
作者 Eaton
导语
在后盾开发中,咱们常常须要和数据库打交道,而在 C++ 开发中,MySQL Connector/C++ 只提供了根底操作接口,简单的业务经常须要一系列简单的调用过程,非常容易出错,那有什么办法能够防止呢?TarsCpp 中提供了数据库操作类 TC_Mysql
,使咱们可能不便地进行数据库操作,进步业务开发效率。本文将对 TC_Mysql
进行介绍剖析。
目录
-
MySQL
- 简介
- MySQL 罕用 API
- 存在的问题
-
TC_Mysql
- 数据库配置接口 TC_DBConf
-
数据库操作类 TC_Mysql
- 构造函数
- MySQL 操作函数
- 数据存储类 MysqlRecord 与 MysqlData
- 数据库异样类 TC_Mysql_Exception
MySQL
简介
数据库是计算机利用零碎中一种专门治理数据资源的零碎,以数据为核心,解决了传统文件存储数据存在的数据冗余、移植性差等问题。在后盾开发中,数据库操作具备十分重要的位置,不可避免地须要和数据库打交道。当初市面上的数据库软件多种多样,最罕用的便是 MySQL。它是一款平安、跨平台、高效的关系型数据库系统,由瑞典的 MySQL AB 公司开发、公布并反对。因为其体积小、速度快、总体领有成本低,尤其是开放源码这一特点,使得很多公司都采纳 MySQL 数据库以降低成本。
数据库的根底操作包含增、删、改、查四种操作,对应的在 MySQL 中为 Insert, Delete, Update, Select 等,可能不便地实现对数据库中数据的治理。
MySQL 罕用 API
MySQL 反对了各种支流的程序设计语言,提供了丰盛的数据库操作 API 函数。在 C++ 开发中,MySQL 官网提供了相干的数据库连贯动静库 MySQL Connector/C++,接口定义在 mysql.h
中,蕴含 MySQL 根底操作 API,像罕用的 mysql_real_connect
, mysql_real_query
, mysql_store_result
, mysql_fetch_row
等。其中
mysql_real_connect
用于创立到 MySQL 数据库服务的连贯;mysql_real_query
用于执行 MySQL 语句;mysql_store_result
用于将执行后果保留到本地;mysql_fetch_row
用于获取返回后果中的行。
这四个函数个别状况下可能满足大部分需要,它们在 mysql.h
中的申明如下
MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host,
const char *user, const char *passwd,
const char *db, unsigned int port,
const char *unix_socket,
unsigned long clientflag);
int STDCALL mysql_real_query(MYSQL *mysql, const char *q, unsigned long length);
MYSQL_RES *STDCALL mysql_store_result(MYSQL *mysql);
MYSQL_ROW STDCALL mysql_fetch_row(MYSQL_RES *result);
mysql_real_connect
函数有很多参数,涵盖了连贯数据库所须要的根本信息如 host, user, password 等,胜利创立连贯会取得一个 MYSQL
对象,并将参数中的 MYSQL *
指针 mysql
指向该对象,供其它操作应用。
mysql_real_query
函数须要传入方才提到的连贯对象指针,SQL 字符串 q
和字符串长度 length
,返回执行后果的行数。
mysql_store_result
函数传入 MYSQL
对象指针,返回执行后果,类型为 MYSQL_RES
。
mysql_fetch_row
传入获取的后果,返回每行的数据。
存在的问题
然而应用这些接口进行一次 MySQL 操作是十分麻烦的一件事,上面咱们通过一个例子来看看如何通过这四个函数实现一次 MySQL 查问操作
#include "mysql.h"
using namespace std;
int main()
{
// 创立 MYSQL 对象
MYSQL *mysql = mysql_init(NULL);
// 创立连贯
mysql_real_connect(mysql, "127.0.0.1", "root", "123456", "db_tars", 3306, NULL, 0);
// 执行 sql 语句
string sql = "select * from server";
mysql_real_query(mysql, sql.c_str(), sql.length());
// 获取执行后果
MYSQL_RES *res = mysql_store_result(mysql);
// 字段名数组
vector<string> fields;
MYSQL_FIELD *field;
// 通过 mysql_fetch_field 获取字段名保留在数组 fields 中
while((field = mysql_fetch_field(res)))
{fields.push_back(field->name);
}
// 申明 row 用于保留每行数据
MYSQL_ROW row;
// 读取返回后果所有字段值
// mysql_fetch_row 从 res 中获取一行数据
while((row = mysql_fetch_row(res) != (MYSQL_ROW)NULL))
{// 获取每个字段值的字符串长度,因为每个字段是一个字符数组 (C 格调字符串)
unsigned long * lengths = mysql_fetch_lengths(res);
for(size_t i = 0; i < fields.size(); i++)
{cout << fields[i] << ":" << string(row[i], lengths[i]) << ";";
}
cout << endl;
}
return 0;
}
上述代码在 main
函数中,用最简略的形式实现了查问操作,没有蕴含任何谬误和返回值判断的逻辑,然而看起来曾经很简单了。而理论业务场景中通常还须要对一些错误码还有返回后果进行判断,比方连贯失败或断开,返回值为空等,总的来说,存在以下几个问题
- 须要本人结构 SQL 语句,容易出错;
- 须要开发者本人增加错误码和异样的判断和解决逻辑;
- 每次查问返回的后果须要调用
mysql_fetch
系列的多个函数能力实现后果读取。
可见,开发者须要关注的细节太多,会很大水平上升高了开发效率。因而,把开发者无需关注的或反复的过程和细节暗藏起来,将 MySQL 操作 API 进一步封装,显得十分必要。本人封装的话未免太过于小题大做,而且不免有疏漏,对开发者本身能力要求也比拟高。常见的形式是引入齐备的第三方库,TarsCpp 的工具组件中就蕴含数据库操作类 TC_Mysql
,可能完满解决这些问题。
TC_Mysql
TC_Mysql
是 TarsCpp 中提供的 MySQL 操作类,定义在文件 tc_mysql.h 中,对 MySQL C++ 库中提供的 API 进一步地封装,屏蔽了很多与业务开发无关的细节,应用上更加不便简略。
文件 tc_mysql.h
中定义了三个类 TC_DBConf
, TC_Mysql
, TC_Mysql_Exception
。其中 TC_Mysql
类中还定义两个子类。如图所示
tc_mysql.h
│
├── TC_DBConf # 数据库配置接口
├── TC_Mysql # 数据库操作类
│ ├── MysqlRecord # 数据库记录类(单条查问记录)│ └── MysqlData # 数据库数据类(所有查问记录)└── TC_Mysql_Exception # 数据库异样类
其中异样类 TC_Mysql_Exception
和数据库配置接口 TC_DBConf
作为辅助类,次要在类 TC_Mysql
中应用。
TC_Mysql
类中的两个子类 MysqlRecord
和 MysqlData
,作为数据存储类,相似于 MySQL C++ 库中的 MYSQL_ROW
和 MYSQL_RES
,用于保留每次查问返回的后果。
上面咱们就来对每个类进行剖析。
数据库配置接口 TC_DBConf
在类 TC_Mysql
中,应用 TC_DBConf
类型的成员变量 _dbConf
来保留 MySQL 配置信息。TC_DBConf
用于保留数据库的连贯配置如 host、user 等,提供了 loadFromMap
接口,可能从 map
类型变量中读取数据库配置。咱们能够通过两种形式来加载配置
- 间接对成员变量赋值
TC_DBConf dbConf;
dbConf._host = "127.0.0.1";
dbConf._user = "root";
...
- 应用
loadFromMap
从map<string, string>
类型参数读取配置
map<string, string> mapConf;
mapConf.insert(make_pair("dbhost", "127.0.0.1"));
mapConf.insert(make_pair("dbuser", "root"));
...
TC_DBConf dbConf;
dbConf.loadFromMap(mapConf);
TC_DBConf
的定义也非常简单,具体如下
/**
* @brief 数据库配置接口
*/
struct TC_DBConf
{
string _host;
string _user;
string _password;
string _database;
string _charset;
int _port;
int _flag;
/**
* @brief 构造函数
*/
TC_DBConf()
: _port(0)
, _flag(0)
{}
/**
* @brief 读取数据库配置.
*
* @param mpParam 寄存数据库配置的 map
* dbhost: 主机地址
* dbuser: 用户名
* dbpass: 明码
* dbname: 数据库名称
* dbport: 端口
*/
void loadFromMap(const map<string, string> &mpParam)
{
map<string, string> mpTmp = mpParam;
_host = mpTmp["dbhost"];
_user = mpTmp["dbuser"];
_password = mpTmp["dbpass"];
_database = mpTmp["dbname"];
_charset = mpTmp["charset"];
_port = atoi(mpTmp["dbport"].c_str());
_flag = 0;
if(mpTmp["dbport"] == "")
{_port = 3306;}
}
};
数据库操作类 TC_Mysql
构造函数
TC_Mysql
提供了三个版本的构造函数,实现了多种初始化形式。
- 反对传入一个
TC_DBConf
对象来进行初始化,简化了参数,升高了出错的可能,也能进步代码可读性。
TC_Mysql(const TC_DBConf& tcDBConf);
- 提供默认构造函数,不传入参数,后续应用时再进行初始化。
TC_Mysql();
- 与
mysql_real_connet
参数类似,传入数据库配置信息来初始化。
不同的是这种形式在结构对象时即实现 MySQL 连贯的初始化,而且能够间接应用
string
类型字符串。mysql_real_connect
须要先通过mysql_init
创建对象后能力调用(可见 MySQL 罕用 API 局部的示例),并且只能传入 C 格调的字符串。
TC_Mysql(const string& sHost, const string& sUser = "", const string& sPasswd ="", const string& sDatabase = "", const string &sCharSet ="", int port = 0, int iFlag = 0);
上面这个例子展现了这三种结构形式
// 申明并初始化数据库配置对象 dbConf
TC_DBConf dbConf;
dbConf._port = 3306;
dbConf._host = "127.0.0.1";
dbConf._user = "root";
dbConf._password = "12345";
dbConf._database = "db_tars";
dbConf._charset = "utf8";
// 通过 TC_DBConf 对象结构初始化
TC_Mysql mysqlObj0(dbConf);
// 先结构对象,不初始化,后续应用 init 初始化
TC_Mysql mysqlObj1 = new TC_Mysql();
mysqlObj1.init(dbConf);
// 间接传入数据库配置初始化
TC_Mysql mysqlObj2("127.0.0.1", "root", "12345", "db_tars", "utf8", 3306);
MySQL 操作函数
TC_Mysql
中的蕴含了 Insert、Update、Replace、Delete、Query 等罕用操作的函数,更加合乎个别应用场景,相比 MySQL C++ 库都通过 mysql_real_query
来实现,应用上要简略得多。罕用的几个操作函数申明如下
// 插入记录
size_t insertRecord(const string &sTableName, const map<string, pair<FT, string> > &mpColumns);
// 更新记录
size_t updateRecord(const string &sTableName, const map<string, pair<FT, string> > &mpColumns, const string &sCondition);
// 替换记录
size_t replaceRecord(const string &sTableName, const map<string, pair<FT, string> > &mpColumns);
// 删除记录
size_t deleteRecord(const string &sTableName, const string &sCondition = "");
// 获取记录计数
size_t getRecordCount(const string& sTableName, const string &sCondition = "");
// 字符本义
string realEscapeString(const string& sFrom);
// 查问记录
MysqlData queryRecord(const string& sSql);
更多其余操作函数参见 tc_mysql.h
- Insert、Update、Replace、Delete
从上述定义中能够看出,增、删、改相干的操作不须要本人构建 SQL 语句,传入相干参数就可能实现操作。
实际上,结构 SQL 语句的过程封装在函数中。
其中参数 sTableName
为表名,mpColumns
为须要插入、更新或替换的数据,sCondition
为条件。上面通过一个例子来看看如何应用这些操作函数
...
// 表名
string tableName = "tars_table";
// 结构须要增、删、改的数据
map<string, pair<TC_Mysql::FT, string>> record0;
record0.insert(make_pair("user_id" , make_pair(TC_Mysql::DB_STR, "abcd")));
record0.insert(make_pair("age" , make_pair(TC_Mysql::DB_INT, "25")));
// 结构用于替换的数据
map<string, pair<TC_Mysql::FT, string>> record1;
record1.insert(make_pair("user_id" , make_pair(TC_Mysql::DB_STR, "abcd")));
record1.insert(make_pair("age" , make_pair(TC_Mysql::DB_INT, "40")));
try
{
// mysqlObj0 为前文曾经初始化好的 TC_Mysql 对象指针
// 向 tars_table 插入记录
mysqlObj0->insertRecord(tableName, record0);
// 获取 user_id 为 abcd 的记录计数
int count = mysqlObj0->getRecordCount(tableName, "where `user_id`='abcd'");
// 替换 user_id 为 abcd 的记录(不存在则为插入数据), 替换后 age 值为 40
mysqlObj0->replaceRecord(tableName, record1);
// 更新 user_id 为 abcd 的记录,更新后 age 值为 25
mysqlObj0->updateRecord(tableName, record0, "where `user_id`='abcd'");
// 删除 age 为 25 的记录
mysqlObj0->deleteRecord(tableName, "where `age`=40");
}
catch (exception &e)
{
// 异样解决
...
}
...
能够看到上述示例中存在一个 TC_Mysql::FT
类型,定义如下
/**
* @brief 定义字段类型,* DB_INT: 数字类型
* DB_STR: 字符串类型
*/
enum FT
{
DB_INT,
DB_STR,
};
它是 TC_Mysql
类中定义的枚举类型,用于定义字段的类型为字符串还是数字,判断在构建 SQL 语句时是否须要增加引号 ''
并本义。
例如上述例子中,最初理论执行的 Insert SQL 语句中,abcd
有引号,25
没有引号,如下
insert into tars_table (`user_id`, `age`) values ('abcd', 25)
- Query
增、删、改都有了,那么查(Query)呢?就是后面定义中的 queryRecord
了。与 mysql_real_query
相似,参数传入 SQL 语句字符串。
MysqlData queryRecord(const string& sSql);
不同的是,queryRecord
传入参数类型为 string
,不须要额定传入字符串长度;并且 queryRecord
间接返回查问后果记录,不须要再调用 mysql_store_result
等函数来获取(实际上这个过程封装在函数内,参见 TC_Mysql 源码)。
具体应用形式如下
...
// 申明返回数据
TC_Mysql::MysqlData res;
try
{res = mysqlObj->queryRecord("select user_id, age from tars_table");
}
catch (exception &e)
{cout << "Error:" << e.what() << endl;
}
size_t resCount = res.size();
// 输入返回数据
for (size_t i = 0; i < resCount; ++i)
{cout << "user_id:" << res[0]["user_id"]
<< "age:" << res[0]["age"]
<< endl;
}
...
返回数据的类型为 MysqlData
,它是 TC_Mysql
的子类,用于保留数据,相当于 MySQL C++ 库中的 MYSQL_RES
,咱们会在下一部分具体介绍它。读取 MysqlData
类型数据的过程十分敌对不便,可能间接以数组的形式遍历,并且读取字段值的类型为 string
而不是 char *
,不须要额定获取字符串长度。这也使得代码变得更加简洁清晰。
数据存储类 MysqlRecord 与 MysqlData
类 TC_Mysql
中蕴含了两个子类 MysqlRecord
和 MysqlData
,用于保留查问返回的数据,不便数据的读取。MysqlRecord
相当于 map
,用于保留一条记录;MysqlData
相当于 MysqlRecord
类型数组,用于保留多条记录。
MysqlRecord
MysqlRecord
类用于记录一条 mysql 的记录,相当于 MYSQL_ROW
,在 MysqlData
中被应用。MysqlRecord
类型的对象可能间接应用下标拜访数据,例如
map<string, string> record_map;
record_map.insert(make_pair("name", "TARS"));
// 构建并初始化对象
TC_Mysql::MysqlRecord record(record_map);
// 通过下标拜访
cout << record["name"] << endl;
MysqlRecord
类的定义如下
/**
* @brief mysql 的一条记录
*/
class MysqlRecord
{
public:
/**
* @brief 构造函数.
* @param record
*/
MysqlRecord(const map<string, string> &record);
/**
* @brief 获取数据,s 个别是指数据表的某个字段名
* @param s 要获取的字段
* @return 合乎查问条件的记录的 s 字段名
*/
const string& operator[](const string &s);
protected:
const map<string, string> &_record;
};
能够看到 MysqlRecord
重载了 []
运算符,实现了像 map
一样的下标拜访数据的形式。MysqlRecord
还蕴含 map<string, string>
类型援用的成员变量 _record
,因而理论保留记录的数据类型为 map<string, string>
。
MysqlData
保留一次查问的所有 mysql 数据记录,相当于 MYSQL_RES
,每条记录即为一个 MysqlRecord
类型的数据。MysqlData
类型的数据可能通过下标拜访每条记录,与数组类似,比方上面的例子
map<string, string> record_map;
record_map.insert(make_pair("name", "TARS"));
// 申明对象
TC_Mysql::MysqlData data;
// 增加记录
data.data().push_back(record_map);
// 通过下标获取 MysqlRecord 对象
TC_Mysql::MysqlRecord record = data[0];
cout << record["name"] << endl;
cout << data[0]["name"] << endl;
MysqlData
重载了 []
运算符,实现了与数组一样的形式遍历和读取记录,每条记录类型为 MysqlRecord
。理论保留数据的成员变量为 _data
,类型为 vector< map<string, string> >
,即通过 vector
容器来保留所有记录。定义如下
/**
* @brief 查问进去的 mysql 数据
*/
class MysqlData
{
public:
/**
* @brief 所有数据.
* @return vector<map<string,string>>&
*/
vector<map<string, string> >& data();
/**
* @brief 数据的记录条数
* @return size_t
*/
size_t size();
/**
* @brief 获取某一条记录.
* @param i 要获取第几条记录
* @return MysqlRecord 类型的数据,能够依据字段获取相干信息,*/
MysqlRecord operator[](size_t i);
protected:
vector< map<string, string> > _data;
};
数据库异样类 TC_Mysql_Exception
在后面 MySQL 的介绍中能够看到,惯例 MySQL 的操作没有进行异样判断和解决,须要本人实现相干的逻辑。
tc_mysql.h
中定义了异样类 TC_Mysql_Exception
,用于在 MySQL 操作呈现谬误时抛出异样,作为类 TC_Mysql
的辅助类被应用。例如上面是 TC_Mysql
中函数 queryRecord
的局部实现,返回的指针 pstRst
为空时抛出一个谬误:
...
MYSQL_RES *pstRes = mysql_store_result(_pstMql);
if(pstRes == NULL)
{throw TC_Mysql_Exception("[TC_Mysql::queryRecord]: mysql_store_result:" + sSql + ":" + string(mysql_error(_pstMql)));
}
...
TC_Mysql
类中蕴含了异样解决逻辑,并将异样通过 TC_Mysql_Exception
抛出。不便用户在应用时,可能间接通过 try catch
来捕获异样,比方
...
tars::TC_Mysql *mysql_ptr = new tars::TC_Mysql();
try
{mysql_ptr->init("127.0.0.1", "root", "123456", "db_tars", "utf-8", 3306);
mysql_ptr->connect();}
catch (exception &e)
{
// 获取异样信息
cout << e.what() << endl;
...
}
...
总结
TC_Mysql
在 MySQL Connector C++ API 根底上进一步封装,对一些罕用的流程例如 Insert、Update、Replace 等操作封装了独立的操作函数,使开发者无需自行拼接 SQL 语句,升高了出错的可能;同时屏蔽了错误处理等逻辑,开发者无需关注,仅通过 try catch
语句即可捕获异样。另外,通过类 MysqlRecord
, MysqlData
保留返回数据类型,极大的不便了数据的读取。这些都使得开发者可能更加不便地操作 MySQL,专一于业务代码的开发,进一步提高开发效率。
TARS 能够在思考到易用性和高性能的同时疾速构建零碎并主动生成代码,帮忙开发人员和企业以微服务的形式疾速构建本人稳固牢靠的分布式应用,从而令开发人员只关注业务逻辑,进步经营效率。多语言、麻利研发、高可用和高效经营的个性使 TARS 成为企业级产品。
TARS 微服务助您数字化转型,欢送拜访:
TARS 官网:https://TarsCloud.org
TARS 源码:https://github.com/TarsCloud
Linux 基金会官网微服务收费课程:https://www.edx.org/course/bu…
获取《TARS 官网培训电子书》:https://wj.qq.com/s2/6570357/…
或扫码获取: