概述
C++Mysql8.0数据库跨平台编程实战(中)
- 第四章 MySQL API C++封装
- 1、MySQLAPIC++封装策略和方法说明
- 2、ZPMysql动态链接库和测试vs2017项目创建
- 3、完成封装的Init和Close接口
- 4、完成Connect连接数据的接口和测试
- 5、完成Query执行sql语句的接口封装和测试
- 6、完成Options接口封装设置自动重连和超时并加入命名空间
- 引入命名空间
- 7、结果集获取StoreResult和清理接口完成并测试
- 8、完成FetchRow获取一行vector数据并完成自定义类型
- 返回一条记录
- 9、完成支持map的Insert插入数据接口封装
- 第五章 插入和读取二进制数据并移植到ubuntu
- 1、完成文件读取接口的封装
- 2、完成二进制文件内容插入的接口InsertBin封装
- 插入二进制类型的数据
- 接下来,我们来做插入整型类型的数据
- 3、完成文件存储接口并读取插入的二进制数据
- 4、完成支持map的Update接口并测试修改数据
- 5、完成UpdateBin修改二进制数据逇接口并测试
- 6、完成ZPMysql事务的接口封装
第四章 MySQL API C++封装
1、MySQLAPIC++封装策略和方法说明
- 第一个策略:我们把引用的头文件都放到cpp当中,因为每个cpp的编译顺序你是知道的,都是独立编译的,而不是头文件出去而你不知道头文件编到哪边去了(你不知道头文件的编译顺序);而cpp会编译到我们的动态链接库当中。
- 第二个策略:可扩展线程安全相对比较容易,所有的公共变量你要控制好,能不放到成员里面的尽量不要放到成员里面,因为每个公共变量都会涉及到线程安全,当你引用线程安全的时候,你把互斥量给它加进来,互斥量建议用STL的,也就是C++自带的已经有线程的互斥了。
- 第三个策略:我们利用C++的一些特性(例如STL的map和vector等容器)简化数据接口(很容易获得大小、类型等信息)。
- 第四个策略:我们一旦做了一些扩展功能的时候,你就没法确定它跨平台;文件存取可以跨平台,但字符集转换几乎是不可以跨平台的,在往里面添每一个功能的时候我们都要考虑它的跨平台性;字符集的转换,我们根据windows和linux当中的不同方式,我们通过宏预处理来判别不同的系统我们给它实现不同的代码。
- 第五个策略:在linux下我们项目文件用makefile来做,包括install安装、清理、卸载、编译动态链接库,我们全部放到makefile当中。
2、ZPMysql动态链接库和测试vs2017项目创建
创建动态链接库项目:
我们在windows当中做动态链接库的时候,其实有两种方式:
一种方式是同构windows的API载入dll文件,调用它里面的接口;
还有一种方式,直接通过lib文件,lib文件记录了动态链接库里面的函数,所以需要生成这个lib文件。
我们发现它没有生成这个lib文件,因为它没有导出的类型,所以我们必须把自定义的ZPMysql类给导出一下。
我们先实现把这个动态链接库里面的函数调用起来,为了方便测试,我们给动态链接库添加一些输出:
我们在该解决方案里面添加新项目,新建一个控制台应用程序:
我们打开解决方案属性,设置启动项目:
设置test_ZPMysql为我们的启动项目,并设置项目依赖项,我们的test_ZPMysql的编译要依赖于已经编译好的ZPMysql。
我们给test_ZPMysql.h添加ZPMysql.h头文件,提示无法打开该文件:
根据上图项目文件目录,设置test_ZPMysql项目的附加包含目录:
我们看到找不到这个init函数,这说明我们的库没有被引用。
这次编译没问题了,但是执行的时候报错,找不到dll文件。
其实在我们每一个程序编译的过程中都有3种错误,第一个是找不到头文件的错误(要找头文件),第二个是找不到函数定义的错误(函数的库没有被引用)(要找lib文件),第三个就是执行错误(要找dll文件)。
要把项目所在目录改为我们那个统一的执行文件的目录:
vs2019直接运行成功,但是在vs2017、vs2015的话可能会运行失败,可能会报应用程序错误:
这是因为我们虽然在链接的时候有了init这个函数的定义,但是我们引用的ZPMysql.h头文件有问题:
在dll文件中你是dllexport,是导出的;
而我们test_ZPMysql调用方是要import,是要导入的,同一个头文件你得区分是exe调用的,还是动态链接库调用的,这样比较麻烦。
区分方法也很简单,我们先看下exe和dll属性当中各自的预处理器宏有什么区别:
我们定义一个宏ZPAPI来统一这两种调用方式,如果项目定义了ZPMYSQL_EXPORTS这个宏,说明是动态链接库项目调用,如果没有的话就是执行程序调用:
如果报错的话,再设置一下测试程序的输出目录,因为它可能还调用了旧的历史文件:
清理后重新生成解决方案,执行成功!
第一步,我们就完成了ZPMysql库的初始化函数接口的调用,它的测试程序也完成了;
我们讲测试先行,这样的话,我们每添加一个接口都能对它进行测试反馈。
3、完成封装的Init和Close接口
下面我们来完成这个初始化的代码,我们知道一个Mysql连接就是一个对象,那这个时候就要考虑这个对象的空间的申请、清理和销毁,这里我们选择的方案是,整个的初始化和清理的工作由我们直接进行控制。
我们先把mysql.h头文件引进来:
我们在ZPMysql类里面定义一个MYSQL类型的指针变量:
它肯定会找不到MYSQL这个类型的。
我们只对这个MYSQL结构体的类型名称做一下前置声明,我不管它实现,因为在dll库里面你没调用它,在这里只用来声明一个指针,这样的话编译就可以过去了。
我们看上图,这是link链接错误,肯定是找不到函数定义。
我们把mysql的库引用进来:
我们再获取对应的动态库的路径:
编译并执行成功。
在你做了更多功能的时候,初始化函数和清理函数后面我们还要扩充,我们一点一点添加。
4、完成Connect连接数据的接口和测试
我们在测试的代码中来连接到本地的mysql数据库:
执行程序后报错,我们下断点分析:
我们发现由于在Init初始化的时候先调用了Close函数,而此时mysql指针的值默认为0xcccccccccccccccc,所以上图if语句不为空,执行mysql_close函数必然失败。
所以我们在构造函数中初始化mysql指针为NULL,修改代码如下:
初始化两次都没有问题。
// ZPMysql.h
//
#pragma once
#ifdef ZPMYSQL_EXPORTS
// 动态链接库调用
#define ZPAPI __declspec(dllexport)
#else
// 执行程序调用
#define ZPAPI __declspec(dllimport)
#endif
struct MYSQL;
class ZPAPI ZPMysql
{
public:
ZPMysql();
// 初始化MYSQL API
bool Init();
// 清理占用的所有资源
void Close();
// 数据库连接(先暂时不考虑线程安全) flag可以设置支持多条sql语句
bool Connect(const char* host, const char* user, const char* pwd, const char* db, unsigned short port = 3306, unsigned long flag = 0);
protected:
MYSQL* mysql;
};
// ZPMysql.cpp
//
#include "ZPMysql.h"
#include <iostream>
#include "mysql.h"
using namespace std;
ZPMysql::ZPMysql()
{
mysql = NULL;
}
bool ZPMysql::Init()
{
// 为防止内存泄漏,先调用一下Close
Close();
cout << "ZPMysql::init()" << endl;
// 新创建一个MYSQL对象
mysql = mysql_init(0);
if (!mysql)
{
cerr << "ZPMysql::Init() failed!" << endl;
return false;
}
return true;
}
// 清理占用的所有资源
void ZPMysql::Close()
{
if (mysql)
{
mysql_close(mysql);
mysql = NULL;
}
cout << "ZPMysql::Close()" << endl;
}
// 数据库连接(先暂时不考虑线程安全) flag可以设置支持多条sql语句
bool ZPMysql::Connect(const char* host, const char* user, const char* pwd, const char* db, unsigned short port, unsigned long flag)
{
// 我们每次都重新生成一个MYSQL
if (!Init())
{
cerr << "Mysql Connect failed! mysql is not Init!" << endl;
return false;
}
if (!mysql_real_connect(mysql, host, user, pwd, db, port, NULL, flag))
{
cerr << "Mysql mysql_real_connect failed! " << mysql_error(mysql) << endl;
return false;
}
cout << "mysql connect success!" << endl;
return true;
}
// test_ZPMysql.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include "ZPMysql.h"
using namespace std;
int main()
{
ZPMysql my;
cout << "my.Init() = " << my.Init() << endl;
if (my.Connect("127.0.0.1", "root", "123456", "mysql"))
{
cout << "my.Connect success!" << endl;
}
my.Close();
cout << "test_ZPMysql!" << endl;
}
5、完成Query执行sql语句的接口封装和测试
// ZPMysql.h
//
class ZPAPI ZPMysql
{
public:
// 执行sql语句 if sqlen == 0 在内部会调用strlen来获取sql字符串长度
bool Query(const char* sql, unsigned long sqllen = 0);
};
// ZPMysql.cpp
//
bool ZPMysql::Query(const char* sql, unsigned long sqllen)
{
if (!mysql)
{
cerr << "Query failed: mysql is NULL" << endl;
return false;
}
if (!sql)
{
cerr << "sql is NULL" << endl;
return false;
}
if (sqllen <= 0)
sqllen = (unsigned long)strlen(sql);
if (sqllen <= 0)
{
cerr << "Query sql is empty or wrong format!" << endl;
return false;
}
int result = mysql_real_query(mysql, sql, sqllen);
if (result != 0)
{
cerr << "mysql_real_query failed!" << mysql_error(mysql) << endl;
return false;
}
return true;
}
// test_ZPMysql.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include "ZPMysql.h"
using namespace std;
int main()
{
ZPMysql my;
// 1 mysql 初始化
cout << "my.Init() = " << my.Init() << endl;
// 2 连接mysql 如果前面没有调用Init 那么Connect函数内部会自动调用Init函数
if (my.Connect("127.0.0.1", "root", "123456", "zpdatabase"))
{
cout << "my.Connect success!" << endl;
}
// 3 执行sql语句创建表
string sql = "";
sql = "CREATE TABLE IF NOT EXISTS `t_video`
(id INT AUTO_INCREMENT,
`name` VARCHAR(1024),
`data` BLOB,
`size` INT,
PRIMARY KEY(`id`))";
cout << "CREATE TABLE result: " << my.Query(sql.c_str()) << endl;
// 清理资源
my.Close();
cout << "test_ZPMysql!" << endl;
}
6、完成Options接口封装设置自动重连和超时并加入命名空间
// ZPMysql.h
//
#pragma once
#ifdef ZPMYSQL_EXPORTS
// 动态链接库调用
#define ZPAPI __declspec(dllexport)
#else
// 执行程序调用
#define ZPAPI __declspec(dllimport)
#endif
// 我们把mysql_option枚举定义复制过来,并且为了避免重定义、重名,我们修改名称
enum ZP_option {
ZP_OPT_CONNECT_TIMEOUT,
ZP_OPT_COMPRESS,
ZP_OPT_NAMED_PIPE,
ZP_INIT_COMMAND,
ZP_READ_DEFAULT_FILE,
ZP_READ_DEFAULT_GROUP,
ZP_SET_CHARSET_DIR,
ZP_SET_CHARSET_NAME,
ZP_OPT_LOCAL_INFILE,
ZP_OPT_PROTOCOL,
ZP_SHARED_MEMORY_BASE_NAME,
ZP_OPT_READ_TIMEOUT,
ZP_OPT_WRITE_TIMEOUT,
ZP_OPT_USE_RESULT,
ZP_REPORT_DATA_TRUNCATION,
ZP_OPT_RECONNECT,
ZP_PLUGIN_DIR,
ZP_DEFAULT_AUTH,
ZP_OPT_BIND,
ZP_OPT_SSL_KEY,
ZP_OPT_SSL_CERT,
ZP_OPT_SSL_CA,
ZP_OPT_SSL_CAPATH,
ZP_OPT_SSL_CIPHER,
ZP_OPT_SSL_CRL,
ZP_OPT_SSL_CRLPATH,
ZP_OPT_CONNECT_ATTR_RESET,
ZP_OPT_CONNECT_ATTR_ADD,
ZP_OPT_CONNECT_ATTR_DELETE,
ZP_SERVER_PUBLIC_KEY,
ZP_ENABLE_CLEARTEXT_PLUGIN,
ZP_OPT_CAN_HANDLE_EXPIRED_PASSWORDS,
ZP_OPT_MAX_ALLOWED_PACKET,
ZP_OPT_NET_BUFFER_LENGTH,
ZP_OPT_TLS_VERSION,
ZP_OPT_SSL_MODE,
ZP_OPT_GET_SERVER_PUBLIC_KEY,
ZP_OPT_RETRY_COUNT,
ZP_OPT_OPTIONAL_RESULTSET_METADATA,
ZP_OPT_SSL_FIPS_MODE
};
struct MYSQL;
class ZPAPI ZPMysql
{
public:
ZPMysql();
// 初始化MYSQL API
bool Init();
// 清理占用的所有资源
void Close();
// 数据库连接(先暂时不考虑线程安全) flag可以设置支持多条sql语句
bool Connect(const char* host, const char* user, const char* pwd, const char* db, unsigned short port = 3306, unsigned long flag = 0);
// 执行sql语句 if sqlen == 0 在内部会调用strlen来获取sql字符串长度
bool Query(const char* sql, unsigned long sqllen = 0);
// Mysql参数的设定(这是公共的设定,要在Connect之前调用)
bool Options(ZP_option opt, const void* arg);
// 连接超时时间
bool SetConnectTimeout(int sec);
// 自动重连,默认不自动
bool SetReconnect(bool isre = false);
protected:
MYSQL* mysql;
};
// ZPMysql.cpp
//
// 数据库连接(先暂时不考虑线程安全) flag可以设置支持多条sql语句
bool ZPMysql::Connect(const char* host, const char* user, const char* pwd, const char* db, unsigned short port, unsigned long flag)
{
// 我们每次都先判断mysql是否存在,如果不存在则重新生成一个MYSQL
if (!mysql && !Init())
{
cerr << "Mysql Connect failed! mysql is not Init!" << endl;
return false;
}
if (!mysql_real_connect(mysql, host, user, pwd, db, port, NULL, flag))
{
cerr << "Mysql mysql_real_connect failed! " << mysql_error(mysql) << endl;
return false;
}
cout << "mysql connect success!" << endl;
return true;
}
bool ZPMysql::Options(ZP_option opt, const void* arg)
{
if (!mysql)
{
cerr << "Option failed: mysql is NULL" << endl;
return false;
}
int result = mysql_options(mysql, (mysql_option)opt, arg);
if (result != 0)
{
cerr << "mysql_options failed! " << mysql_error(mysql) << endl;
return false;
}
return true;
}
// 连接超时时间
bool ZPMysql::SetConnectTimeout(int sec)
{
return Options(ZP_OPT_CONNECT_TIMEOUT, &sec);
}
// 自动重连,默认不自动
bool ZPMysql::SetReconnect(bool isre)
{
return Options(ZP_OPT_RECONNECT, &isre);
return true;
}
为了测试超时和自动重连,我们写一个无限循环:
执行程序后,打开服务,停止mysql8.0服务:
我们可以看到连接失败了,这时候再启动mysql服务:
就又成功连接mysql了(记得测试完我们把该测试代码注释掉)。
// test_ZPMysql.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include "ZPMysql.h"
using namespace std;
int main()
{
ZPMysql my;
// 1 mysql 初始化
cout << "my.Init() = " << my.Init() << endl;
my.SetConnectTimeout(3); // 连接超时秒
my.SetReconnect(true); // 自动重连
// 2 连接mysql 如果前面没有调用Init 那么Connect函数内部会自动调用Init函数
if (my.Connect("127.0.0.1", "root", "123456", "zpdatabase"))
{
cout << "my.Connect success!" << endl;
}
// 3 执行sql语句创建表
string sql = "";
sql = "CREATE TABLE IF NOT EXISTS `t_video`
(id INT AUTO_INCREMENT,
`name` VARCHAR(1024),
`data` BLOB,
`size` INT,
PRIMARY KEY(`id`))";
cout << "CREATE TABLE result: " << my.Query(sql.c_str()) << endl;
// 测试自动重连
//for (;;)
//{
// cout << my.Query(sql.c_str()) << flush;
//}
cout << endl;
// 清理资源
my.Close();
cout << "test_ZPMysql!" << endl;
}
引入命名空间
我们不希望我们用C++封装的API函数搞得那么长,当你的项目越来越大的时候,很难保证你的项目不和别的项目发生冲突,解决方案就是命名空间。
我们有两种方案:
- 在没有出现冲突的情况下,我们直接用
using namespace
把它加进来,这样的话代码很简洁; - 一旦出现冲突,我们就不要用这个
using namespace
了,我们直接把命名空间加在前面,就像std命名空间,可以这样用:std::cout
、std::cin
、std::cerr
之类。
所以这种方式呢,我们就把命名空间引用进来,这样就能保证我们真正的是一个可持续开发的这样一个库,因为是C++库嘛,C语言的话就不考虑这样的问题了。
// ZPMysql.h
//
#pragma once
#ifdef ZPMYSQL_EXPORTS
// 动态链接库调用
#define ZPAPI __declspec(dllexport)
#else
// 执行程序调用
#define ZPAPI __declspec(dllimport)
#endif
struct MYSQL;
// 添加命名空间
namespace ZP {
// 我们把mysql_option枚举定义复制过来,并且为了避免重定义、重名,我们修改名称
enum ZP_option {
ZP_OPT_CONNECT_TIMEOUT,
ZP_OPT_COMPRESS,
ZP_OPT_NAMED_PIPE,
ZP_INIT_COMMAND,
ZP_READ_DEFAULT_FILE,
ZP_READ_DEFAULT_GROUP,
ZP_SET_CHARSET_DIR,
ZP_SET_CHARSET_NAME,
ZP_OPT_LOCAL_INFILE,
ZP_OPT_PROTOCOL,
ZP_SHARED_MEMORY_BASE_NAME,
ZP_OPT_READ_TIMEOUT,
ZP_OPT_WRITE_TIMEOUT,
ZP_OPT_USE_RESULT,
ZP_REPORT_DATA_TRUNCATION,
ZP_OPT_RECONNECT,
ZP_PLUGIN_DIR,
ZP_DEFAULT_AUTH,
ZP_OPT_BIND,
ZP_OPT_SSL_KEY,
ZP_OPT_SSL_CERT,
ZP_OPT_SSL_CA,
ZP_OPT_SSL_CAPATH,
ZP_OPT_SSL_CIPHER,
ZP_OPT_SSL_CRL,
ZP_OPT_SSL_CRLPATH,
ZP_OPT_CONNECT_ATTR_RESET,
ZP_OPT_CONNECT_ATTR_ADD,
ZP_OPT_CONNECT_ATTR_DELETE,
ZP_SERVER_PUBLIC_KEY,
ZP_ENABLE_CLEARTEXT_PLUGIN,
ZP_OPT_CAN_HANDLE_EXPIRED_PASSWORDS,
ZP_OPT_MAX_ALLOWED_PACKET,
ZP_OPT_NET_BUFFER_LENGTH,
ZP_OPT_TLS_VERSION,
ZP_OPT_SSL_MODE,
ZP_OPT_GET_SERVER_PUBLIC_KEY,
ZP_OPT_RETRY_COUNT,
ZP_OPT_OPTIONAL_RESULTSET_METADATA,
ZP_OPT_SSL_FIPS_MODE
};
class ZPAPI ZPMysql
{
public:
ZPMysql();
// 初始化MYSQL API
bool Init();
// 清理占用的所有资源
void Close();
// 数据库连接(先暂时不考虑线程安全) flag可以设置支持多条sql语句
bool Connect(const char* host, const char* user, const char* pwd, const char* db, unsigned short port = 3306, unsigned long flag = 0);
// 执行sql语句 if sqlen == 0 在内部会调用strlen来获取sql字符串长度
bool Query(const char* sql, unsigned long sqllen = 0);
// Mysql参数的设定(这是公共的设定,要在Connect之前调用)
bool Options(ZP_option opt, const void* arg);
// 连接超时时间
bool SetConnectTimeout(int sec);
// 自动重连,默认不自动
bool SetReconnect(bool isre = false);
protected:
MYSQL* mysql;
};
}
// ZPMysql.cpp
//
#include "ZPMysql.h"
#include <iostream>
#include "mysql.h"
using namespace std;
namespace ZP {
ZPMysql::ZPMysql()
{
mysql = NULL;
}
bool ZPMysql::Init()
{
// 为防止内存泄漏,先调用一下Close
Close();
cout << "ZPMysql::init()" << endl;
// 新创建一个MYSQL对象
mysql = mysql_init(0);
if (!mysql)
{
cerr << "ZPMysql::Init() failed!" << endl;
return false;
}
return true;
}
// 清理占用的所有资源
void ZPMysql::Close()
{
if (mysql)
{
mysql_close(mysql);
mysql = NULL;
}
cout << "ZPMysql::Close()" << endl;
}
// 数据库连接(先暂时不考虑线程安全) flag可以设置支持多条sql语句
bool ZPMysql::Connect(const char* host, const char* user, const char* pwd, const char* db, unsigned short port, unsigned long flag)
{
// 我们每次都先判断mysql是否存在,如果不存在则重新生成一个MYSQL
if (!mysql && !Init())
{
cerr << "Mysql Connect failed! mysql is not Init!" << endl;
return false;
}
if (!mysql_real_connect(mysql, host, user, pwd, db, port, NULL, flag))
{
cerr << "Mysql mysql_real_connect failed! " << mysql_error(mysql) << endl;
return false;
}
cout << "mysql connect success!" << endl;
return true;
}
bool ZPMysql::Query(const char* sql, unsigned long sqllen)
{
if (!mysql)
{
cerr << "Query failed: mysql is NULL" << endl;
return false;
}
if (!sql)
{
cerr << "sql is NULL" << endl;
return false;
}
if (sqllen <= 0)
sqllen = (unsigned long)strlen(sql);
if (sqllen <= 0)
{
cerr << "Query sql is empty or wrong format!" << endl;
return false;
}
int result = mysql_real_query(mysql, sql, sqllen);
if (result != 0)
{
cerr << "mysql_real_query failed!" << mysql_error(mysql) << endl;
return false;
}
return true;
}
bool ZPMysql::Options(ZP_option opt, const void* arg)
{
if (!mysql)
{
cerr << "Option failed: mysql is NULL" << endl;
return false;
}
int result = mysql_options(mysql, (mysql_option)opt, arg);
if (result != 0)
{
cerr << "mysql_options failed! " << mysql_error(mysql) << endl;
return false;
}
return true;
}
// 连接超时时间
bool ZPMysql::SetConnectTimeout(int sec)
{
return Options(ZP_OPT_CONNECT_TIMEOUT, &sec);
}
// 自动重连,默认不自动
bool ZPMysql::SetReconnect(bool isre)
{
return Options(ZP_OPT_RECONNECT, &isre);
return true;
}
}
// test_ZPMysql.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include "ZPMysql.h"
using namespace std;
using namespace ZP;
int main()
{
ZPMysql my;
// 1 mysql 初始化
cout << "my.Init() = " << my.Init() << endl;
my.SetConnectTimeout(3); // 连接超时秒
my.SetReconnect(true); // 自动重连
7、结果集获取StoreResult和清理接口完成并测试
// ZPMysql.h
//
#pragma once
#ifdef ZPMYSQL_EXPORTS
// 动态链接库调用
#define ZPAPI __declspec(dllexport)
#else
// 执行程序调用
#define ZPAPI __declspec(dllimport)
#endif
struct MYSQL;
struct MYSQL_RES;
// 添加命名空间
namespace ZP {
// 我们把mysql_option枚举定义复制过来,并且为了避免重定义、重名,我们修改名称
enum ZP_option {
ZP_OPT_CONNECT_TIMEOUT,
ZP_OPT_COMPRESS,
ZP_OPT_NAMED_PIPE,
ZP_INIT_COMMAND,
ZP_READ_DEFAULT_FILE,
ZP_READ_DEFAULT_GROUP,
ZP_SET_CHARSET_DIR,
ZP_SET_CHARSET_NAME,
ZP_OPT_LOCAL_INFILE,
ZP_OPT_PROTOCOL,
ZP_SHARED_MEMORY_BASE_NAME,
ZP_OPT_READ_TIMEOUT,
ZP_OPT_WRITE_TIMEOUT,
ZP_OPT_USE_RESULT,
ZP_REPORT_DATA_TRUNCATION,
ZP_OPT_RECONNECT,
ZP_PLUGIN_DIR,
ZP_DEFAULT_AUTH,
ZP_OPT_BIND,
ZP_OPT_SSL_KEY,
ZP_OPT_SSL_CERT,
ZP_OPT_SSL_CA,
ZP_OPT_SSL_CAPATH,
ZP_OPT_SSL_CIPHER,
ZP_OPT_SSL_CRL,
ZP_OPT_SSL_CRLPATH,
ZP_OPT_CONNECT_ATTR_RESET,
ZP_OPT_CONNECT_ATTR_ADD,
ZP_OPT_CONNECT_ATTR_DELETE,
ZP_SERVER_PUBLIC_KEY,
ZP_ENABLE_CLEARTEXT_PLUGIN,
ZP_OPT_CAN_HANDLE_EXPIRED_PASSWORDS,
ZP_OPT_MAX_ALLOWED_PACKET,
ZP_OPT_NET_BUFFER_LENGTH,
ZP_OPT_TLS_VERSION,
ZP_OPT_SSL_MODE,
ZP_OPT_GET_SERVER_PUBLIC_KEY,
ZP_OPT_RETRY_COUNT,
ZP_OPT_OPTIONAL_RESULTSET_METADATA,
ZP_OPT_SSL_FIPS_MODE
};
class ZPAPI ZPMysql
{
public:
ZPMysql();
// 初始化MYSQL API
bool Init();
// 清理占用的所有资源
void Close();
// 数据库连接(先暂时不考虑线程安全) flag可以设置支持多条sql语句
bool Connect(const char* host, const char* user, const char* pwd, const char* db, unsigned short port = 3306, unsigned long flag = 0);
// 执行sql语句 if sqlen == 0 在内部会调用strlen来获取sql字符串长度
bool Query(const char* sql, unsigned long sqllen = 0);
// Mysql参数的设定(这是公共的设定,要在Connect之前调用)
bool Options(ZP_option opt, const void* arg);
// 连接超时时间
bool SetConnectTimeout(int sec);
// 自动重连,默认不自动
bool SetReconnect(bool isre = false);
// 结果集获取
// 返回全部结果
bool StoreResult();
// 开始接收结果,通过Fetch获取
bool UseResult();
// 释放结果集占用的空间
void FreeResult();
protected:
MYSQL* mysql;
MYSQL_RES* result;
};
}
// ZPMysql.cpp
//
#include "ZPMysql.h"
#include <iostream>
#include "mysql.h"
using namespace std;
namespace ZP {
ZPMysql::ZPMysql()
{
mysql = NULL;
result = NULL;
}
bool ZPMysql::Init()
{
// 为防止内存泄漏,先调用一下Close
Close();
cout << "ZPMysql::init()" << endl;
// 新创建一个MYSQL对象
mysql = mysql_init(0);
if (!mysql)
{
cerr << "ZPMysql::Init() failed!" << endl;
return false;
}
return true;
}
// 清理占用的所有资源
void ZPMysql::Close()
{
// 是否要把上次的结果集给清掉
FreeResult();
if (mysql)
{
mysql_close(mysql);
mysql = NULL;
}
cout << "ZPMysql::Close()" << endl;
}
// 数据库连接(先暂时不考虑线程安全) flag可以设置支持多条sql语句
bool ZPMysql::Connect(const char* host, const char* user, const char* pwd, const char* db, unsigned short port, unsigned long flag)
{
// 我们每次都先判断mysql是否存在,如果不存在则重新生成一个MYSQL
if (!mysql && !Init())
{
cerr << "Mysql Connect failed! mysql is not Init!" << endl;
return false;
}
if (!mysql_real_connect(mysql, host, user, pwd, db, port, NULL, flag))
{
cerr << "Mysql mysql_real_connect failed! " << mysql_error(mysql) << endl;
return false;
}
cout << "mysql connect success!" << endl;
return true;
}
bool ZPMysql::Query(const char* sql, unsigned long sqllen)
{
if (!mysql)
{
cerr << "Query failed: mysql is NULL" << endl;
return false;
}
if (!sql)
{
cerr << "sql is NULL" << endl;
return false;
}
if (sqllen <= 0)
sqllen = (unsigned long)strlen(sql);
if (sqllen <= 0)
{
cerr << "Query sql is empty or wrong format!" << endl;
return false;
}
int result = mysql_real_query(mysql, sql, sqllen);
if (result != 0)
{
cerr << "mysql_real_query failed!" << mysql_error(mysql) << endl;
return false;
}
return true;
}
bool ZPMysql::Options(ZP_option opt, const void* arg)
{
if (!mysql)
{
cerr << "Option failed: mysql is NULL" << endl;
return false;
}
int result = mysql_options(mysql, (mysql_option)opt, arg);
if (result != 0)
{
cerr << "mysql_options failed! " << mysql_error(mysql) << endl;
return false;
}
return true;
}
// 连接超时时间
bool ZPMysql::SetConnectTimeout(int sec)
{
return Options(ZP_OPT_CONNECT_TIMEOUT, &sec);
}
// 自动重连,默认不自动
bool ZPMysql::SetReconnect(bool isre)
{
return Options(ZP_OPT_RECONNECT, &isre);
return true;
}
// 返回全部结果
bool ZPMysql::StoreResult()
{
if (!mysql)
{
cerr << "StoreResult failed: mysql is NULL" << endl;
return false;
}
// 是否要把上次的结果集给清掉
FreeResult();
result = mysql_store_result(mysql);
if (!result)
{
cerr << "mysql_store_result failed! " << mysql_error(mysql) << endl;
return false;
}
return true;
}
// 开始接收结果,通过Fetch获取
bool ZPMysql::UseResult()
{
if (!mysql)
{
cerr << "UseResult failed: mysql is NULL" << endl;
return false;
}
// 是否要把上次的结果集给清掉
FreeResult();
result = mysql_use_result(mysql);
if (!result)
{
cerr << "mysql_use_result failed! " << mysql_error(mysql) << endl;
return false;
}
return true;
}
// 释放结果集占用的空间
void ZPMysql::FreeResult()
{
if (result)
{
mysql_free_result(result);
result = NULL;
}
}
}
// test_ZPMysql.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include "ZPMysql.h"
using namespace std;
using namespace ZP;
int main()
{
ZPMysql my;
// 1 mysql 初始化
cout << "my.Init() = " << my.Init() << endl;
my.SetConnectTimeout(3); // 连接超时秒
my.SetReconnect(true); // 自动重连
// 2 连接mysql 如果前面没有调用Init 那么Connect函数内部会自动调用Init函数
if (my.Connect("127.0.0.1", "root", "123456", "zpdatabase"))
{
cout << "my.Connect success!" << endl;
}
// 3 执行sql语句创建表
string sql = "";
sql = "CREATE TABLE IF NOT EXISTS `t_video`
(id INT AUTO_INCREMENT,
`name` VARCHAR(1024),
`data` BLOB,
`size` INT,
PRIMARY KEY(`id`))";
cout << "CREATE TABLE result: " << my.Query(sql.c_str()) << endl;
// 测试自动重连
//for (;;)
//{
// cout << my.Query(sql.c_str()) << flush;
//}
// 插入一条记录
sql = "insert into `t_video`(`name`) values('test001')";
// 测试StoreResult和UseResult
cout << my.Query(sql.c_str()) << endl;
// 获取结果集
sql = "select * from `t_video`";
cout << my.Query(sql.c_str()) << endl;
my.StoreResult(); // 结果集本地全部存储
my.FreeResult();
cout << my.Query(sql.c_str()) << endl;
my.UseResult(); // 开始接收结果集
my.FreeResult();
cout << endl;
// 清理资源
my.Close();
cout << "test_ZPMysql!" << endl;
}
- 错误 警告 MSB8028 中间目录(x64Debug)包含从另一个项目(ZPMysql.vcxproj)共享的文件。 这会导致错误的清除和重新生成行为。 ZPMysql - store and fetch D:Program Files (x86)Microsoft Visual Studio2019ProfessionalMSBuildMicrosoftVCv160Microsoft.CppBuild.targets 513
清理解决方案、重新生成解决方案,均无法解决。 - 解决:
删除对应修改的工程的源文件目录下的 x64Debug 和 x64 Release 文件夹的*.obj、.log、.tlog、*.pdb文件,然后重新编译该工程即可。
8、完成FetchRow获取一行vector数据并完成自定义类型
返回一条记录
首先定义一个结构体ZPData用来存放返回的一条记录:
// ZPMysql.h
//
#pragma once
#include <vector>
#include "ZPData.h"
// ZPMysql.cpp
//
// 获取一行数据
std::vector<ZPData> ZPMysql::FetchRow()
{
std::vector<ZPData> re;
if (!result)
{
return re;
}
MYSQL_ROW row = mysql_fetch_row(result);
if (!row)
{
cerr << "mysql_fetch_row failed!" << mysql_error(mysql) << endl;
return re;
}
// 列数
int num = mysql_num_fields(result);
for (int i = 0; i < num; i++)
{
ZPData data;
data.data = row[i];
re.push_back(data);
}
return re;
}
// test_ZPMysql.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
// 插入一条记录
sql = "insert into `t_video`(`name`) values('test001')";
// 测试StoreResult和UseResult
cout << my.Query(sql.c_str()) << endl;
cout << my.Query(sql.c_str()) << endl;
cout << my.Query(sql.c_str()) << endl;
cout << my.Query(sql.c_str()) << endl;
cout << my.Query(sql.c_str()) << endl;
// 获取结果集
sql = "select * from `t_video`";
cout << my.Query(sql.c_str()) << endl;
my.StoreResult(); // 结果集本地全部存储
for (;;)
{
// 获取一行数据
auto row = my.FetchRow();
if (row.size() == 0)
break;
for (int i = 0; i < row.size(); i++)
{
if (row[i].data)
cout << row[i].data << " ";
}
cout << endl;
}
my.FreeResult();
cout << my.Query(sql.c_str()) << endl;
my.UseResult(); // 开始接收结果集
my.FreeResult();
cout << endl;
// 清理资源
my.Close();
cout << "test_ZPMysql!" << endl;
}
9、完成支持map的Insert插入数据接口封装
我们把ZPMysql.h中定义的枚举类型ZP_option放到ZPData.h中,并在ZPData.h中新定义一个XDATA类型:
// ZPData.h : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#pragma once
#include <map>
#include <string>
#ifdef ZPMYSQL_EXPORTS
// 动态链接库调用
#define ZPAPI __declspec(dllexport)
#else
// 执行程序调用
#define ZPAPI __declspec(dllimport)
#endif
namespace ZP
{
struct ZPAPI ZPData
{
ZPData(const char* strdata = nullptr);
const char* data = nullptr;
int size = 0;
};
// 我们把mysql_option枚举定义复制过来,并且为了避免重定义、重名,我们修改名称
enum ZP_option {
ZP_OPT_CONNECT_TIMEOUT,
ZP_OPT_COMPRESS,
ZP_OPT_NAMED_PIPE,
ZP_INIT_COMMAND,
ZP_READ_DEFAULT_FILE,
ZP_READ_DEFAULT_GROUP,
ZP_SET_CHARSET_DIR,
ZP_SET_CHARSET_NAME,
ZP_OPT_LOCAL_INFILE,
ZP_OPT_PROTOCOL,
ZP_SHARED_MEMORY_BASE_NAME,
ZP_OPT_READ_TIMEOUT,
ZP_OPT_WRITE_TIMEOUT,
ZP_OPT_USE_RESULT,
ZP_REPORT_DATA_TRUNCATION,
ZP_OPT_RECONNECT,
ZP_PLUGIN_DIR,
ZP_DEFAULT_AUTH,
ZP_OPT_BIND,
ZP_OPT_SSL_KEY,
ZP_OPT_SSL_CERT,
ZP_OPT_SSL_CA,
ZP_OPT_SSL_CAPATH,
ZP_OPT_SSL_CIPHER,
ZP_OPT_SSL_CRL,
ZP_OPT_SSL_CRLPATH,
ZP_OPT_CONNECT_ATTR_RESET,
ZP_OPT_CONNECT_ATTR_ADD,
ZP_OPT_CONNECT_ATTR_DELETE,
ZP_SERVER_PUBLIC_KEY,
ZP_ENABLE_CLEARTEXT_PLUGIN,
ZP_OPT_CAN_HANDLE_EXPIRED_PASSWORDS,
ZP_OPT_MAX_ALLOWED_PACKET,
ZP_OPT_NET_BUFFER_LENGTH,
ZP_OPT_TLS_VERSION,
ZP_OPT_SSL_MODE,
ZP_OPT_GET_SERVER_PUBLIC_KEY,
ZP_OPT_RETRY_COUNT,
ZP_OPT_OPTIONAL_RESULTSET_METADATA,
ZP_OPT_SSL_FIPS_MODE
};
//
typedef std::map<std::string, ZPData> XDATA;
}
// ZPData.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "ZPData.h"
namespace ZP
{
ZPData::ZPData(const char* strdata)
{
if (!strdata)
return;
this->data = strdata;
this->size = strlen(strdata);
}
}
// ZPMysql.h :
//
#pragma once
#include <vector>
#include "ZPData.h"
struct MYSQL;
struct MYSQL_RES;
// 添加命名空间
namespace ZP
{
class ZPAPI ZPMysql
{
public:
ZPMysql();
// 初始化MYSQL API
bool Init();
// 清理占用的所有资源
void Close();
// 数据库连接(先暂时不考虑线程安全) flag可以设置支持多条sql语句
bool Connect(const char* host, const char* user, const char* pwd, const char* db, unsigned short port = 3306, unsigned long flag = 0);
// 执行sql语句 if sqlen == 0 在内部会调用strlen来获取sql字符串长度
bool Query(const char* sql, unsigned long sqllen = 0);
// Mysql参数的设定(这是公共的设定,要在Connect之前调用)
bool Options(ZP_option opt, const void* arg);
// 连接超时时间
bool SetConnectTimeout(int sec);
// 自动重连,默认不自动
bool SetReconnect(bool isre = false);
// 结果集获取
// 返回全部结果
bool StoreResult();
// 开始接收结果,通过Fetch获取
bool UseResult();
// 释放结果集占用的空间
void FreeResult();
// 获取一行数据
std::vector<ZPData> FetchRow();
// 生成insert sql语句
std::string GetInsertSql(XDATA& kv, const std::string& table);
// 插入非二进制数据
bool Insert(XDATA& kv, const std::string& table);
protected:
MYSQL* mysql;
MYSQL_RES* result;
};
}
// ZPMysql.cpp :
//
#include "ZPMysql.h"
#include <iostream>
#include "mysql.h"
using namespace std;
namespace ZP
{
ZPMysql::ZPMysql()
{
mysql = NULL;
result = NULL;
}
bool ZPMysql::Init()
{
// 为防止内存泄漏,先调用一下Close
Close();
cout << "ZPMysql::init()" << endl;
// 新创建一个MYSQL对象
mysql = mysql_init(0);
if (!mysql)
{
cerr << "ZPMysql::Init() failed!" << endl;
return false;
}
return true;
}
// 清理占用的所有资源
void ZPMysql::Close()
{
// 是否要把上次的结果集给清掉
FreeResult();
if (mysql)
{
mysql_close(mysql);
mysql = NULL;
}
cout << "ZPMysql::Close()" << endl;
}
// 数据库连接(先暂时不考虑线程安全) flag可以设置支持多条sql语句
bool ZPMysql::Connect(const char* host, const char* user, const char* pwd, const char* db, unsigned short port, unsigned long flag)
{
// 我们每次都先判断mysql是否存在,如果不存在则重新生成一个MYSQL
if (!mysql && !Init())
{
cerr << "Mysql Connect failed! mysql is not Init!" << endl;
return false;
}
if (!mysql_real_connect(mysql, host, user, pwd, db, port, NULL, flag))
{
cerr << "Mysql mysql_real_connect failed! " << mysql_error(mysql) << endl;
return false;
}
cout << "mysql connect success!" << endl;
return true;
}
bool ZPMysql::Query(const char* sql, unsigned long sqllen)
{
if (!mysql)
{
cerr << "Query failed: mysql is NULL" << endl;
return false;
}
if (!sql)
{
cerr << "sql is NULL" << endl;
return false;
}
if (sqllen <= 0)
sqllen = (unsigned long)strlen(sql);
if (sqllen <= 0)
{
cerr << "Query sql is empty or wrong format!" << endl;
return false;
}
int result = mysql_real_query(mysql, sql, sqllen);
if (result != 0)
{
cerr << "mysql_real_query failed!" << mysql_error(mysql) << endl;
return false;
}
return true;
}
bool ZPMysql::Options(ZP_option opt, const void* arg)
{
if (!mysql)
{
cerr << "Option failed: mysql is NULL" << endl;
return false;
}
int result = mysql_options(mysql, (mysql_option)opt, arg);
if (result != 0)
{
cerr << "mysql_options failed! " << mysql_error(mysql) << endl;
return false;
}
return true;
}
// 连接超时时间
bool ZPMysql::SetConnectTimeout(int sec)
{
return Options(ZP_OPT_CONNECT_TIMEOUT, &sec);
}
// 自动重连,默认不自动
bool ZPMysql::SetReconnect(bool isre)
{
return Options(ZP_OPT_RECONNECT, &isre);
return true;
}
// 返回全部结果
bool ZPMysql::StoreResult()
{
if (!mysql)
{
cerr << "StoreResult failed: mysql is NULL" << endl;
return false;
}
// 是否要把上次的结果集给清掉
FreeResult();
result = mysql_store_result(mysql);
if (!result)
{
cerr << "mysql_store_result failed! " << mysql_error(mysql) << endl;
return false;
}
return true;
}
// 开始接收结果,通过Fetch获取
bool ZPMysql::UseResult()
{
if (!mysql)
{
cerr << "UseResult failed: mysql is NULL" << endl;
return false;
}
// 是否要把上次的结果集给清掉
FreeResult();
result = mysql_use_result(mysql);
if (!result)
{
cerr << "mysql_use_result failed! " << mysql_error(mysql) << endl;
return false;
}
return true;
}
// 释放结果集占用的空间
void ZPMysql::FreeResult()
{
if (result)
{
mysql_free_result(result);
result = NULL;
}
}
// 获取一行数据
std::vector<ZPData> ZPMysql::FetchRow()
{
std::vector<ZPData> re;
if (!result)
{
return re;
}
MYSQL_ROW row = mysql_fetch_row(result);
if (!row)
{
cerr << "mysql_fetch_row failed!" << mysql_error(mysql) << endl;
return re;
}
// 列数
int num = mysql_num_fields(result);
for (int i = 0; i < num; i++)
{
ZPData data;
data.data = row[i];
re.push_back(data);
}
return re;
}
// 生成insert sql语句
std::string ZPMysql::GetInsertSql(XDATA& kv, const std::string& table)
{
string sql = "";
if (kv.empty() | table.empty())
{
cerr << "GetInsertSql failed! kv.empty() | table.empty()" << endl;
return "";
}
sql = "insert into `";
sql += table;
sql += "`";
// insert into t_video(name, size) values('name1', '1024')
string keys = "";
string vals = "";
// 迭代mpa
for (auto ptr = kv.begin(); ptr != kv.end(); ptr++)
{
keys += "`";
keys += ptr->first;
keys += "`,";
vals += "'";
vals += ptr->second.data;
vals += "',";
}
// 去除结尾多余的逗号
keys[keys.size() - 1] = ' ';
vals[vals.size() - 1] = ' ';
sql += "(";
sql += keys;
sql += ") values(";
sql += vals;
sql += ")";
return sql;
}
// 插入非二进制数据
bool ZPMysql::Insert(XDATA& kv, const std::string& table)
{
if (!mysql)
{
cerr << "Insert failed: mysql is NULL" << endl;
return false;
}
string sql = GetInsertSql(kv, table);
if (sql.empty())
{
cerr << "GetInsertSql failed! sql is NULL" << endl;
return false;
}
if (!Query(sql.c_str()))
{
cerr << "Insert failed! Query failed!" << endl;
return false;
}
// 通过mysql_affected_rows来判断sql语句执行是否成功
int num = mysql_affected_rows(mysql);
if (num <= 0)
{
cerr << "sql execute failed!" << endl;
return false;
}
cout << "sql execute success!" << endl;
return true;
}
}
// 测试程序
// test_ZPMysql.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include "ZPMysql.h"
using namespace std;
using namespace ZP;
int main()
{
ZPMysql my;
// 1 mysql 初始化
cout << "my.Init() = " << my.Init() << endl;
my.SetConnectTimeout(3); // 连接超时秒
my.SetReconnect(true); // 自动重连
// 2 连接mysql 如果前面没有调用Init 那么Connect函数内部会自动调用Init函数
if (my.Connect("127.0.0.1", "root", "123456", "zpdatabase"))
{
cout << "my.Connect success!" << endl;
}
// 3 执行sql语句创建表
string sql = "";
sql = "CREATE TABLE IF NOT EXISTS `t_video`
(id INT AUTO_INCREMENT,
`name` VARCHAR(1024),
`data` BLOB,
`size` INT,
PRIMARY KEY(`id`))";
cout << "CREATE TABLE result: " << my.Query(sql.c_str()) << endl;
// 测试自动重连
//for (;;)
//{
// cout << my.Query(sql.c_str()) << flush;
//}
插入一条记录
//sql = "insert into `t_video`(`name`) values('test001')";
测试StoreResult和UseResult
//cout << my.Query(sql.c_str()) << endl;
//cout << my.Query(sql.c_str()) << endl;
//cout << my.Query(sql.c_str()) << endl;
//cout << my.Query(sql.c_str()) << endl;
//cout << my.Query(sql.c_str()) << endl;
XDATA data1;
//data1.insert(make_pair("name", ZPData("insertname001")));
data1["name"] = "insertname001";
data1["size"] = "1024";
//cout << my.GetInsertSql(data1, "t_video") << endl;
my.Insert(data1, "t_video");
data1["name"] = "insertname002";
my.Insert(data1, "t_video");
// 获取结果集
sql = "select * from `t_video`";
cout << my.Query(sql.c_str()) << endl;
my.StoreResult(); // 结果集本地全部存储
for (;;)
{
// 获取一行数据
auto row = my.FetchRow();
if (row.size() == 0)
break;
for (int i = 0; i < row.size(); i++)
{
if (row[i].data)
cout << row[i].data << " ";
}
cout << endl;
}
my.FreeResult();
cout << my.Query(sql.c_str()) << endl;
my.UseResult(); // 开始接收结果集
my.FreeResult();
cout << endl;
// 清理资源
my.Close();
cout << "test_ZPMysql!" << endl;
}
第五章 插入和读取二进制数据并移植到ubuntu
1、完成文件读取接口的封装
下面来准备插入二进制的数据,首先一点,我们要读取这个文件,待会我们还要测试,在存储文件,所以我们先在ZPData这边封装了一个文件读写接口,这样的话我们调用接口简单一点,就是固定的把一个文件全读出来,全读到内存当中,还有一个就是把内存当中的内容直接写到文件当中去。
// ZPData.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "ZPData.h"
#include <fstream>
#include <iostream>
using namespace std; // 在cpp当中其实可以大胆地引用命名空间,但是在.h中你必须极其的慎重
namespace ZP
{
ZPData::ZPData(const char* strdata)
{
if (!strdata)
return;
this->data = strdata;
this->size = strlen(strdata);
}
// 读取文件,内容写入到data中,size是大小
bool ZPData::LoadFile(const char* filename)
{
if (!filename)
return false;
fstream in(filename, ios::in | ios::binary);
if (!in.is_open())
{
cerr << "LoadFile " << filename << "failed!" << endl;
return false;
}
// 文件大小
in.seekg(0, ios::end);
size = in.tellg();
in.seekg(0, ios::beg);
if (size <= 0)
{
cerr << "文件读取失败,或者文件没有内容" << endl;
return false;
}
data = new char[size];
int readed = 0;
while (!in.eof())
{
in.read((char*)data + readed, size - readed);
if (in.gcount() > 0) // 这次我们读了多少
readed += in.gcount();
else
break;
}
in.close();
return true;
}
// 释放LoadFile申请的data控件
void ZPData::Drop()
{
if (data)
delete data;
data = nullptr;
}
}
我们这时候插入肯定是有问题的,此时的这个data1你没法拼成字符串,所以说我们这里也不用实验了;
我们需要创建一个插入的数据,然后我们一个预处理语句根据插入的数据类型来确定对这个ZPData我们需要做哪些操作。
2、完成二进制文件内容插入的接口InsertBin封装
测试的数据(maomi.jpg)已经准备好了,那么我们现在开始正式的插入数据。
插入二进制类型的数据
// 插入二进制数据
bool ZPMysql::InsertBin(XDATA& kv, const std::string& table)
{
string sql = "";
if (kv.empty() | table.empty() | !mysql)
{
cerr << "GetInsertSql failed! kv.empty() | table.empty() | !mysql" << endl;
return false;
}
sql = "insert into `";
sql += table;
sql += "`";
// insert into t_video(name, size) values(?, ?')
string keys = "";
string vals = "";
// 绑定字段,这里的空间大小先写死
MYSQL_BIND bind[256] = { 0 };
int i = 0;
// 迭代mpa
for (auto ptr = kv.begin(); ptr != kv.end(); ptr++)
{
keys += "`";
keys += ptr->first;
keys += "`,";
vals += "?,"; // 跟一个半角的问号就可以了
bind[i].buffer = (char*)ptr->second.data;
bind[i].buffer_length = ptr->second.size;
// 这里我们先这样写,InsertBin这里的接口参数我们没有传入类型,如果不写类型的话,你不知道写进去的是二进制还是普通的字符串
bind[i].buffer_type = (enum_field_types)ptr->second.type;
i++;
}
// 去除结尾多余的逗号
keys[keys.size() - 1] = ' ';
vals[vals.size() - 1] = ' ';
sql += "(";
sql += keys;
sql += ") values(";
sql += vals;
sql += ")";
return true;
}
InsertBin这里的接口参数我们没有传入类型,如果不写类型的话,你不知道写进去的是二进制还是普通的字符串;
那么这个类型从哪里来呢?
我们可以把该类型复制到我们的项目中,而且名字还不能一样,否则会冲突。
// ZPData.h : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#pragma once
#include <map>
#include <string>
#ifdef ZPMYSQL_EXPORTS
// 动态链接库调用
#define ZPAPI __declspec(dllexport)
#else
// 执行程序调用
#define ZPAPI __declspec(dllimport)
#endif
namespace ZP
{
// 我们把mysql_option枚举定义复制过来,并且为了避免重定义、重名,我们修改名称
enum ZP_option {
ZP_OPT_CONNECT_TIMEOUT,
ZP_OPT_COMPRESS,
ZP_OPT_NAMED_PIPE,
ZP_INIT_COMMAND,
ZP_READ_DEFAULT_FILE,
ZP_READ_DEFAULT_GROUP,
ZP_SET_CHARSET_DIR,
ZP_SET_CHARSET_NAME,
ZP_OPT_LOCAL_INFILE,
ZP_OPT_PROTOCOL,
ZP_SHARED_MEMORY_BASE_NAME,
ZP_OPT_READ_TIMEOUT,
ZP_OPT_WRITE_TIMEOUT,
ZP_OPT_USE_RESULT,
ZP_REPORT_DATA_TRUNCATION,
ZP_OPT_RECONNECT,
ZP_PLUGIN_DIR,
ZP_DEFAULT_AUTH,
ZP_OPT_BIND,
ZP_OPT_SSL_KEY,
ZP_OPT_SSL_CERT,
ZP_OPT_SSL_CA,
ZP_OPT_SSL_CAPATH,
ZP_OPT_SSL_CIPHER,
ZP_OPT_SSL_CRL,
ZP_OPT_SSL_CRLPATH,
ZP_OPT_CONNECT_ATTR_RESET,
ZP_OPT_CONNECT_ATTR_ADD,
ZP_OPT_CONNECT_ATTR_DELETE,
ZP_SERVER_PUBLIC_KEY,
ZP_ENABLE_CLEARTEXT_PLUGIN,
ZP_OPT_CAN_HANDLE_EXPIRED_PASSWORDS,
ZP_OPT_MAX_ALLOWED_PACKET,
ZP_OPT_NET_BUFFER_LENGTH,
ZP_OPT_TLS_VERSION,
ZP_OPT_SSL_MODE,
ZP_OPT_GET_SERVER_PUBLIC_KEY,
ZP_OPT_RETRY_COUNT,
ZP_OPT_OPTIONAL_RESULTSET_METADATA,
ZP_OPT_SSL_FIPS_MODE
};
enum FIELD_TYPE {
ZP_TYPE_DECIMAL,
ZP_TYPE_TINY,
ZP_TYPE_SHORT,
ZP_TYPE_LONG,
ZP_TYPE_FLOAT,
ZP_TYPE_DOUBLE,
ZP_TYPE_NULL,
ZP_TYPE_TIMESTAMP,
ZP_TYPE_LONGLONG,
ZP_TYPE_INT24,
ZP_TYPE_DATE,
ZP_TYPE_TIME,
ZP_TYPE_DATETIME,
ZP_TYPE_YEAR,
ZP_TYPE_NEWDATE, /**< Internal to MySQL. Not used in protocol */
ZP_TYPE_VARCHAR,
ZP_TYPE_BIT,
ZP_TYPE_TIMESTAMP2,
ZP_TYPE_DATETIME2, /**< Internal to MySQL. Not used in protocol */
ZP_TYPE_TIME2, /**< Internal to MySQL. Not used in protocol */
ZP_TYPE_JSON = 245,
ZP_TYPE_NEWDECIMAL = 246,
ZP_TYPE_ENUM = 247,
ZP_TYPE_SET = 248,
ZP_TYPE_TINY_BLOB = 249,
ZP_TYPE_MEDIUM_BLOB = 250,
ZP_TYPE_LONG_BLOB = 251,
ZP_TYPE_BLOB = 252,
ZP_TYPE_VAR_STRING = 253,
ZP_TYPE_STRING = 254,
ZP_TYPE_GEOMETRY = 255
};
struct ZPAPI ZPData
{
ZPData(const char* strdata = nullptr);
// 读取文件,内容写入到data中,size是大小,会在堆中申请data的空间,需要用Drop释放
bool LoadFile(const char* filename);
// 释放LoadFile申请的data控件
void Drop();
const char* data = nullptr;
int size = 0;
FIELD_TYPE type;
};
//
typedef std::map<std::string, ZPData> XDATA;
}
// ZPData.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "ZPData.h"
#include <fstream>
#include <iostream>
using namespace std; // 在cpp当中其实可以大胆地引用命名空间,但是在.h中你必须极其的慎重
namespace ZP
{
ZPData::ZPData(const char* strdata)
{
this->type = ZP_TYPE_STRING; // 默认我们就用STRING类型
if (!strdata)
return;
this->data = strdata;
this->size = strlen(strdata);
}
// 读取文件,内容写入到data中,size是大小
bool ZPData::LoadFile(const char* filename)
{
if (!filename)
return false;
fstream in(filename, ios::in | ios::binary);
if (!in.is_open())
{
cerr << "LoadFile " << filename << "failed!" << endl;
return false;
}
// 文件大小
in.seekg(0, ios::end);
size = in.tellg();
in.seekg(0, ios::beg);
if (size <= 0)
{
cerr << "文件读取失败,或者文件没有内容" << endl;
return false;
}
data = new char[size];
int readed = 0;
while (!in.eof())
{
in.read((char*)data + readed, size - readed);
if (in.gcount() > 0) // 这次我们读了多少
readed += in.gcount();
else
break;
}
in.close();
this->type = ZP_TYPE_BLOB; // 设置为二进制类型
return true;
}
// 释放LoadFile申请的data控件
void ZPData::Drop()
{
if (data)
delete data;
data = nullptr;
}
}
接下来,我们来做插入整型类型的数据
// 插入二进制数据
bool ZPMysql::InsertBin(XDATA& kv, const std::string& table)
{
string sql = "";
if (kv.empty() | table.empty() | !mysql)
{
cerr << "GetInsertSql failed! kv.empty() | table.empty() | !mysql" << endl;
return false;
}
sql = "insert into `";
sql += table;
sql += "`";
// insert into t_video(name, size) values(?, ?')
string keys = "";
string vals = "";
// 绑定字段,这里的空间大小先写死
MYSQL_BIND bind[256] = { 0 };
int i = 0;
// 迭代mpa
for (auto ptr = kv.begin(); ptr != kv.end(); ptr++)
{
keys += "`";
keys += ptr->first;
keys += "`,";
vals += "?,"; // 跟一个半角的问号就可以了
bind[i].buffer = (char*)ptr->second.data;
bind[i].buffer_length = ptr->second.size;
// 这里我们先这样写,InsertBin这里的接口参数我们没有传入类型,如果不写类型的话,你不知道写进去的是二进制还是普通的字符串
bind[i].buffer_type = (enum_field_types)ptr->second.type;
i++;
}
// 去除结尾多余的逗号
keys[keys.size() - 1] = ' ';
vals[vals.size() - 1] = ' ';
sql += "(";
sql += keys;
sql += ") values(";
sql += vals;
sql += ")";
// 预处理sql语句
MYSQL_STMT* stmt = mysql_stmt_init(mysql);
if (!stmt)
{
cerr << "mysql_stmt_init failed! " << mysql_error(mysql) << endl;
return false;
}
if (mysql_stmt_prepare(stmt, sql.c_str(), sql.length()) != 0)
{
mysql_stmt_close(stmt);
cerr << "mysql_stmt_prepare failed! " << mysql_error(mysql) << endl;
return false;
}
if (mysql_stmt_bind_param(stmt, bind) != 0)
{
mysql_stmt_close(stmt);
cerr << "mysql_stmt_bind_param failed! " << mysql_stmt_error(stmt) << endl;
return false;
}
if (mysql_stmt_execute(stmt) != 0)
{
mysql_stmt_close(stmt);
cerr << "mysql_stmt_bind_param failed! " << mysql_stmt_error(stmt) << endl;
return false;
}
mysql_stmt_close(stmt);
return true;
}
3、完成文件存储接口并读取插入的二进制数据
4、完成支持map的Update接口并测试修改数据
- 我们先来实现修改非二进制数据的版本
我们Update跟Insert还是有区别的,如果我们不支持多条语句的话,Insert一次只会影响一条,但是我们这个Update它其实会修改多条,所以这里我们封装的Update的返回值为int类型。
// ZPMysql.cpp
//
// 获取更新数据的sql语句 sql语句中用户要包含where条件(这个条件可以传空,也就是要改所有的数据)
std::string ZPMysql::GetUpdateSql(XDATA& kv, const std::string& table, std::string where)
{
string sql = "";
if (kv.empty() | table.empty())
{
cerr << "GetInsertSql failed! kv.empty() | table.empty()" << endl;
return "";
}
// update `t_video` set `name` = 'update001', `size` = 1000 where `id` = 2
sql = "update `";
sql += table;
sql += "` set `";
for (auto ptr = kv.begin(); ptr != kv.end(); ptr++)
{
sql += ptr->first;
sql += "`='";
sql += ptr->second.data;
sql += "',";
}
// 去除多余的逗号
sql[sql.size() - 1] = ' ';
sql += where;
return sql;
}
int ZPMysql::Update(XDATA& kv, const std::string& table, std::string where)
{
if (!mysql)
{
cerr << "Update failed: mysql is NULL" << endl;
return -1;
}
string sql = GetUpdateSql(kv, table, where);
if (sql.empty())
{
cerr << "GetUpdateSql failed! sql is NULL" << endl;
return -1;
}
if (!Query(sql.c_str()))
{
cerr << "Query update failed!" << endl;
return -1;
}
return mysql_affected_rows(mysql); // 成功的话,我们要返回影响的行数
}
5、完成UpdateBin修改二进制数据逇接口并测试
// ZPMysql.cpp
//
int ZPMysql::UpdateBin(XDATA& kv, const std::string& table, std::string where)
{
if (!mysql | kv.empty() | table.empty())
{
cerr << "UpdateBin failed! mysql==NULL | kv.empty() | table.empty()" << endl;
return -1;
}
string sql = "";
// update `t_video` set `name` = 'update001', `size` = 1000 where `id` = 2
sql = "update `";
sql += table;
sql += "` set";
MYSQL_BIND bind[256] = { 0 };
int i = 0;
for (auto ptr = kv.begin(); ptr != kv.end(); ptr++)
{
sql += "`";
sql += ptr->first;
sql += "`=?,";
bind[i].buffer = (char*)ptr->second.data;
bind[i].buffer_length = ptr->second.size;
bind[i].buffer_type = (enum_field_types)ptr->second.type;
i++;
}
// 去除多余的逗号
sql[sql.size() - 1] = ' ';
sql += " "; // 防止用户没有传递,我们要有一定的容错性
sql += where;
// 预处理SQL语句的上下文
MYSQL_STMT* stmt = mysql_stmt_init(mysql);
if (!stmt)
{
cerr << "mysql_stmt_init failed!" << mysql_error(mysql) << endl;
return -1;
}
if (mysql_stmt_prepare(stmt, sql.c_str(), sql.length()) != 0)
{
mysql_stmt_close(stmt);
cerr << "mysql_stmt_prepare failed!" << mysql_error(mysql) << endl;
return -1;
}
if (mysql_stmt_bind_param(stmt, bind) != 0)
{
mysql_stmt_close(stmt);
cerr << "mysql_stmt_bind_param failed!" << mysql_error(mysql) << endl;
return -1;
}
if (mysql_stmt_execute(stmt) != 0)
{
mysql_stmt_close(stmt);
cerr << "mysql_stmt_execute failed!" << mysql_error(mysql) << endl;
return -1;
}
mysql_stmt_close(stmt);
return mysql_stmt_affected_rows(stmt);
}
测试成功!
6、完成ZPMysql事务的接口封装
可以看到,上图中我们插入了3条(5、6、7),回滚后都撤销了;所以事务最重要的是回滚,事务是可以一次执行多条sql语句,比你插入一条执行一条的效率高太多了(前面我们插入1千或1万条记录的时候测试过)。
最后
以上就是风趣盼望为你收集整理的C++Mysql8.0数据库跨平台编程实战(中)第四章 MySQL API C++封装第五章 插入和读取二进制数据并移植到ubuntu的全部内容,希望文章能够帮你解决C++Mysql8.0数据库跨平台编程实战(中)第四章 MySQL API C++封装第五章 插入和读取二进制数据并移植到ubuntu所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复