《编码规范和测试方法——C/C++版》作业 ·007——C++引入MySQL给C的API并简单封装

问题描述

使用MySQL为C提供的API,封装出一个访问MySQL数据库的类。

要求至少能实现如下基本功能:
	1. 初始化获取连接
	2. 执行select语句
	3. 执行update语句、insert语句、delete语句
	4. 关闭连接

PS:MySQL提供的API可以在C:\Program Files\MySQL\MySQL Server 5.7下找到

参考解答

API引入

API的引入主要是:将MySQL安装目录下,include目录下的文件、lib目录下的libmysql.dll文件和libmysql.lib文件引入到项目中。

最简单暴力的方法自然是通通给复制粘贴过来,当然肯定也不是很好的策略。

比较好的方法是在项目属性里配置好include目录、lib目录、libmysql.dll文件,最后把libmysql.dll文件给复制粘贴到和.cpp文件同级的目录下。只把前三个目录/文件引入配置中在运行时还是会报错找不到"libmysql.dll"文件
在网上参考了很多教程,大多数的说法都是只配置好前三项,最后找到了一个比较靠谱一点的说法(网址https://blog.51cto.com/corasql/1699619)是配置好前三项后再把libmysql.dll文件复制粘贴过来。

以下是详细流程:

  1. 创建好一个C++项目(个人习惯创建了“控制台应用程序”,也就多俩pch文件,省的在正文中穿插system("pause")
    新建项目

  2. 对项目右键,点击【属性】
    项目属性

  3. 由于我的MySQL安装的似乎是64位的版本,还是将平台设置为x64
    x64

  4. 在【C/C++】>【常规】下编辑【附加包含目录】,将类似C:\Program Files\MySQL\MySQL Server 5.7\include这样的目录引入
    C、C++-1
    C、C++-2

  5. 在【链接器】>【常规】下编辑【附加库目录】,将类似C:\Program Files\MySQL\MySQL Server 5.7\lib这样的目录引入
    链接器-1
    链接器-2

  6. 在【链接器】>【输入】下编辑【附加依赖项】,将libmysql.lib写入
    链接器-3
    链接器-4

  7. 点击【确定】完成属性页的配置
    确定

  8. 将libmysql.dll文件复制到当前项目的.cpp文件同级目录下
    dll

  9. 选择【解决方案平台】为“x64”
    x64-2

  10. 输入以下代码,测试连接(可以把第18行的主机名、用户名、密码、数据库名换成自己的,不知道用户该填啥的就填root好了,端口号一般就是3306,要是不一样就换成自己对应的端口号😅)

    #include "pch.h"
    
    #include <iostream>
    #include "mysql.h"
    
    using namespace std;
    
    int main()
    {
    	MYSQL conn;
    	MYSQL_RES *res_set;
    	MYSQL_ROW row;
    
    	// 获得或初始化一个MYSQL结构
    	mysql_init(&conn);
    
    	// 连接一个MySQL服务器。
    	if (!mysql_real_connect(&conn, "主机", "用户名", "密码", "数据库名", 3306, NULL, 0))
    	{
    		fprintf(stderr, "Failed to connect to database: Error: %s\n", mysql_error(&conn));
    	}
    	else
    	{
    		fprintf(stdout, "Successfully connected to Database.\n");
    
    		// 执行指定为一个空结尾的字符串的SQL查询。
    		int status = mysql_query(&conn, "SELECT 1 + 2");
    
    		// 检索一个完整的结果集合给客户。
    		res_set = mysql_store_result(&conn);
    
    		// 返回一个结果集合中的行的数量
    		int count = mysql_num_rows(res_set); printf("No of rows = %d\n", count);
    
    		while ((row = mysql_fetch_row(res_set)) != NULL)  // 从结果集合中取得下一行
    		{
    			// 返回最近查询的结果列的数量。
    			for (int i = 0; i < mysql_num_fields(res_set); i++)
    			{
    				printf("%s \t", row[i] != NULL ? row[i] : "NULL");	// 第i列的值
    			}
    			printf("\n");
    		}
    	}
    
    	// 关闭数据库
    	mysql_close(&conn);
    	
    	return 0;
    }
    

    成功连接的表现:
    结果

API测试

封装前,对原来MySQL给C的API进行了一些测试,得到了以下结论

  • 已经通过mysql_initmysql_real_connect成功连接上的同一个MySQL结构体conn不能再次连接其他数据库
  • 已经通过mysql_close关闭掉的MySQL结构体conn如果要再次连接其他数据库,需要重新用mysql_init初始化一下才能进行连接
  • 操作mysql_close与操作mysql_init相对应,没有通过mysql_init初始化的conn,直接进行mysql_close会异常终止程序

一些问题

在封装中测试时,遇到了一些小问题:

问题 解决方法
表格的charset确实是utf8,但是读取就会乱码 查询前先用mysql_query(&conn, "set names gbk")
虽然很奇怪的是"set names utf8"依然会乱码,但是"set names gbk"确实解决了乱码问题
表格的charset确实是utf8,但是写入就会乱码 写入前先用mysql_query(&conn, "set names gbk")
虽然很奇怪的是"set names utf8"依然会乱码,但是"set names gbk"确实解决了乱码问题
对于增、删、改语句的执行结果如何显示 通过mysql_affected_rows(&conn)可以知道执行后受到影响的行数有多少行

封装代码

本次就是根据MySQL提供给C的API简单封装了一下,封装出了一个MyMySQL类,以下是MyMySQL类部分的代码:

  • MyMySQL.h文件
    #include "mysql.h"  // 需要有MYSQL等结构体的定义
    
    #ifndef MYMYSQL_H
    #define MYMYSQL_H
    
    class MyMySQL
    {
    public:
    	MyMySQL();
    	MyMySQL(const char* host, const char* user, const char* password, const char* db, unsigned int port);
    	int MyMySQL_connect(const char * host, const char * user, const char * password, const char * db, unsigned int port);
    	int MyMySQL_query(const char * sql, const char * q = "gbk");
    	int MyMySQL_execute(const char * sql, const char * q = "gbk");
    	void MyMySQL_close();
    private:
    	void MyMySQL_init();
    	MYSQL conn;      // MySQL结构体
    	int init_state;  // 初始化状态 —— 0代表未初始化、1代表已初始化
    	int conn_state;  // 连接状态 —— 0代表未连接、1代表已连接
    };
    
    #endif // !MYMYSQL_H
    
  • MyMySQL.cpp文件
    #include "pch.h"
    
    #include "MyMySQL.h"
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    /*
     *	功能:
     *		MyMySQL的缺省构造函数
     *	参数:
     *		void
     *	返回值:
     *		无
     *	说明:
     *		只是初始化了conn,并没有连接到某个数据库
     *	创建时间:
     *		2020-04-01 10:59:20
     *	作者:
     *		Excious
     **/
    MyMySQL::MyMySQL()
    	:init_state(1), conn_state(0)
    {
    	mysql_init(&this->conn);
    }
    
    /*
     *	功能:
     *		MyMySQL的构造函数
     *	参数:
     *		host        in    主机
     *		user        in    用户名
     *		password    in    密码
     *		db          in    要连接的数据库
     *		port        in    端口
     *	返回值:
     *		无
     *	说明:
     *		使用成员函数MyMySQL_connect尝试连接(里面附带着初始化过程)
     *	创建时间:
     *		2020-04-01 14:36:46
     *	作者:
     *		Excious
     **/
    MyMySQL::MyMySQL(const char * host, const char * user, const char * password, const char * db, unsigned int port)
    	:init_state(0), conn_state(0)
    {
    	this->MyMySQL_connect(host, user, password, db, port);
    }
    
    /*
     *	功能:
     *		[连接/重新连接]到某个数据库
     *	参数:
     *		host        in    主机
     *		user        in    用户名
     *		password    in    密码
     *		db          in    要连接的数据库
     *		port        in    端口
     *	返回值:
     *		-1    连接失败
     *		0     连接成功
     *	说明:
     *		如果conn尚未进行初始化,会对其进行初始化,再重新进行连接
     *		如果conn已经连接上某个数据库,会先将其关闭,再重新初始化,再重新进行连接
     *		如果重新连接失败,则之前的连接状态也会被清除,并且将会关闭连接
     *	创建时间:
     *		2020-04-01 14:19:03
     *	作者:
     *		Excious
     **/
    int MyMySQL::MyMySQL_connect(const char * host, const char * user, const char * password, const char * db, unsigned int port)
    {
    	// 根据状态进行相应的预处理
    	if (!this->init_state)      // 未初始化
    	{
    		this->MyMySQL_init();
    	}
    	else if (this->conn_state)  // 已初始化,并已连接某数据库
    	{
    		this->MyMySQL_close();
    		this->MyMySQL_init();
    	}
    
    	// 尝试连接到某个数据库
    	int flag = 0;
    	if (!mysql_real_connect(&this->conn, host, user, password, db, port, NULL, 0))
    	{
    		cerr << "连接数据库失败: \n" << mysql_error(&conn) << endl;
    		this->MyMySQL_close();
    		flag = -1;
    	}
    	else
    	{
    		cout << "连接数据库成功!\n";
    		this->conn_state = 1;
    	}
    
    	return flag;
    }
    
    /*
     *	功能:
     *		执行select语句
     *	参数:
     *		sql    in    要执行的select语句
     *		q      in    读取时的编码字符集(缺省值为"gbk")
     *	返回值:
     *		-2    尚未连接
     *		-1    查询失败
     *		0     查询成功
     *	说明:
     *		前三行代码设置了读取时的字符集(目前感觉读取字符集utf8的表,最靠谱的还是gbk)
     *	创建时间:
     *		2020-04-01 14:44:49
     *	作者:
     *		Excious
     **/
    int MyMySQL::MyMySQL_query(const char * sql, const char * q)
    {
    	string q_s("set names ");
    	q_s += q;
    	mysql_query(&this->conn, q_s.c_str());
    
    	// 尚未初始化、连接
    	if (!this->init_state || !this->conn_state)
    	{
    		cout << "尚未连接!" << endl;
    		return -2;
    	}
    
    	// 尝试执行SQL-select语句
    	if (mysql_query(&this->conn, sql))  // 执行失败
    	{
    		cerr << "查询失败:\n" << mysql_error(&this->conn) << endl;
    		return -1;
    	}
    
    	// 执行成功
    	MYSQL_RES* res = mysql_store_result(&this->conn);
    	MYSQL_ROW row;
    	int num = mysql_num_fields(res);
    	cout << "查询成功,共有如下" << mysql_num_rows(res) << "条记录:" << endl;
    
    	while ((row = mysql_fetch_row(res)) != NULL)
    	{
    		for (int i = 0; i < mysql_num_fields(res); i++)
    		{
    			cout << (row[i] != NULL ? row[i] : "NULL") << '\t';
    		}
    		cout << endl;
    	}
    	mysql_free_result(res);  //释放内存
    
    	return 0;
    }
    
    /*
     *	功能:
     *		
     *	参数:
     *		sql    in    要执行的update语句、insert语句、delete语句
     *		q      in    读取时的编码字符集(缺省值为"gbk")
     *	返回值:
     *		-2    尚未连接
     *		-1    执行失败
     *		0     执行成功
     *	说明:
     *		前三行代码设置了写入时的字符集(目前感觉写入字符集utf8的表,最靠谱的还是gbk)
     *	创建时间:
     *		2020-04-01 15:25:46
     *	作者:
     *		Excious
     **/
    int MyMySQL::MyMySQL_execute(const char * sql, const char * q)
    {
    	string q_s("set names ");
    	q_s += q;
    	mysql_query(&this->conn, q_s.c_str());
    
    	// 尚未初始化、连接
    	if (!this->init_state || !this->conn_state)
    	{
    		cout << "尚未连接!" << endl;
    		return -2;
    	}
    
    	// 尝试执行
    	if (mysql_query(&this->conn, sql))  // 执行失败
    	{
    		cerr << "执行失败:\n" << mysql_error(&this->conn) << endl;
    		return -1;
    	}
    
    	// 执行成功
    	cout << "执行成功:" << endl;
    	cout << "Affected rows: " << mysql_affected_rows(&conn) << endl;
    	mysql_commit(&this->conn);  // 提交事务
    
    	return 0;
    }
    
    /*
     *	功能:
     *		关闭连接conn
     *	参数:
     *		void
     *	返回值:
     *		void
     *	说明:
     *		如果初始化状态(init_state)为0,则不会关闭连接
     *		如果初始化状态(init_state)为1,则对conn进行关闭,并将init_state、conn_state调整至0
     *	创建时间:
     *		2020-04-01 14:13:35
     *	作者:
     *		Excious
     **/
    void MyMySQL::MyMySQL_close()
    {
    	if (this->init_state)
    	{
    		mysql_close(&this->conn);
    		this->init_state = 0;
    		this->conn_state = 0;
    		cout << "数据库连接已关闭" << endl;
    	}
    }
    
    /*
     *	功能:
     *		初始化连接conn,是连接数据库前的准备
     *	参数:
     *		void
     *	返回值:
     *		void
     *	说明:
     *		如果初始化状态(init_state)为1,则不会进行操作
     *		如果初始化状态(init_state)为0,则对conn进行初始化,并将init_state调整至1
     *	创建时间:
     *		2020-04-01 14:00:58
     *	作者:
     *		Excious
     **/
    void MyMySQL::MyMySQL_init()
    {
    	if (!this->init_state)
    	{
    		mysql_init(&this->conn);
    		this->init_state = 1;
    	}
    }
    

测试环境

主要是数据库的环境。

测试前,MySQL数据库

  • 端口号为3306
  • 有一个名为tester、密码test的用户
  • 有一个名为test的数据库,该数据库下有一个名为test的表格,表格的初始数据如下
    初始数据

测试代码

以下是测试代码

#include "pch.h"

#include "MyMySQL.h"
#include <iostream>
#include "mysql.h"

using namespace std;

int main()
{
	// 构造一个MySQL数据库连接,并连接到某个数据库
	MyMySQL mymysql("localhost", "tester", "test", "homework", 3306);
	cout << "————————————————————————————————" << endl;

	// 改变数据库连接(值得注意的是,不论成功与否都会清除之前的连接)
	mymysql.MyMySQL_connect("localhost", "tester", "test", "test", 3306);
	cout << "————————————————————————————————" << endl;

	// 查询所有数据
	mymysql.MyMySQL_query("select * from test", "gbk");
	cout << "————————————————————————————————" << endl;

	// 插入两条数据,执行完查询
	mymysql.MyMySQL_execute("insert into test values(4, \'丁六\'), (5, \'赵七\')", "gbk");
	cout << "————————————————————————————————" << endl;
	mymysql.MyMySQL_query("select * from test", "gbk");
	cout << "————————————————————————————————" << endl;

	// 更新一条数据,执行完查询
	mymysql.MyMySQL_execute("update test set name=\'赵八\' where name=\'赵七\'", "gbk");
	cout << "————————————————————————————————" << endl;
	mymysql.MyMySQL_query("select * from test", "gbk");
	cout << "————————————————————————————————" << endl;

	// 删除一条数据,执行完查询
	mymysql.MyMySQL_execute("delete from test where name=\'丁六\'", "gbk");
	cout << "————————————————————————————————" << endl;
	mymysql.MyMySQL_query("select * from test", "gbk");
	cout << "————————————————————————————————" << endl;

	// 主动关闭数据库连接
	mymysql.MyMySQL_close();
	cout << "————————————————————————————————" << endl;

	//*****************以下开始为特殊示例*****************
	cout << "\n■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n" << endl;
	cout << "————————————————————————————————" << endl;
	
	// 缺省构造函数构造出的对象里的conn被init过,但是没有connect上
	MyMySQL default_my;

	// 对一个没有connect上数据库的对象执行查询(字符集缺省值"gbk",可以不用显式填到参数列表)
	default_my.MyMySQL_query("select 1 + 2");
	cout << "————————————————————————————————" << endl;

	// 对一个没有connect上数据库的对象执行修改(字符集缺省值"gbk",可以不用显式填到参数列表)
	default_my.MyMySQL_query("update test set id=1");
	cout << "————————————————————————————————" << endl;

	// 关闭一个连接尚未初始化的对象时,不会执行任何操作
	default_my.MyMySQL_close();  // 正常连接对象的关闭
	default_my.MyMySQL_close();  // 未初始化对象的关闭
	cout << "————————————————————————————————" << endl;

	// 现在我们重新连接上去,马上将执行一些错误的增删改查语句
	// 连接关闭的对象也可以直接connect上去,因为connect会根据情况进行init
	default_my.MyMySQL_connect("localhost", "tester1", "test", "test", 3306);  // 展示一下连接出错的效果
	cout << "————————————————————————————————" << endl;
	default_my.MyMySQL_connect("localhost", "tester", "test", "test", 3306);   // 正常连接上去
	cout << "————————————————————————————————" << endl;

	// 执行错误的select语句
	default_my.MyMySQL_query("select * from no_such_table");
	cout << "————————————————————————————————" << endl;

	// 执行错误的insert语句
	default_my.MyMySQL_execute("insert into test values(1, 1, 1, 1)");
	cout << "————————————————————————————————" << endl;

	// 执行无影响的delete语句
	default_my.MyMySQL_execute("delete from test where id=9999999");
	cout << "————————————————————————————————" << endl;

	// 执行空语句
	default_my.MyMySQL_execute("");
	cout << "————————————————————————————————" << endl;

	return 0;
}

测试结果

以下是运行结果

连接数据库成功!
————————————————————————————————
数据库连接已关闭
连接数据库成功!
————————————————————————————————
查询成功,共有如下4条记录:
1       张三
2       李四
3       王五
NULL    NULL
————————————————————————————————
执行成功:
Affected rows: 2
————————————————————————————————
查询成功,共有如下6条记录:
1       张三
2       李四
3       王五
NULL    NULL
4       丁六
5       赵七
————————————————————————————————
执行成功:
Affected rows: 1
————————————————————————————————
查询成功,共有如下6条记录:
1       张三
2       李四
3       王五
NULL    NULL
4       丁六
5       赵八
————————————————————————————————
执行成功:
Affected rows: 1
————————————————————————————————
查询成功,共有如下5条记录:
1       张三
2       李四
3       王五
NULL    NULL
5       赵八
————————————————————————————————
数据库连接已关闭
————————————————————————————————

■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

————————————————————————————————
尚未连接!
————————————————————————————————
尚未连接!
————————————————————————————————
数据库连接已关闭
————————————————————————————————
连接数据库失败:
Access denied for user 'tester1'@'localhost' (using password: YES)
数据库连接已关闭
————————————————————————————————
连接数据库成功!
————————————————————————————————
查询失败:
Table 'test.no_such_table' doesn't exist
————————————————————————————————
执行失败:
Column count doesn't match value count at row 1
————————————————————————————————
执行成功:
Affected rows: 0
————————————————————————————————
执行失败:
Query was empty
————————————————————————————————

数据库最终结果
res2

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章