C++Mysql8.0数据库跨平台编程实战(中)



第四章 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::coutstd::cinstd::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 中间目录(x64\Debug)包含从另一个项目(ZPMysql.vcxproj)共享的文件。 这会导致错误的清除和重新生成行为。 ZPMysql - store and fetch D:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Microsoft\VC\v160\Microsoft.CppBuild.targets 513
    清理解决方案、重新生成解决方案,均无法解决。
  • 解决:
    删除对应修改的工程的源文件目录下的 x64\Debug\ 和 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万条记录的时候测试过)。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值