问题描述
使用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文件复制粘贴过来。
以下是详细流程:
-
创建好一个C++项目(个人习惯创建了“控制台应用程序”,也就多俩pch文件,省的在正文中穿插
system("pause")
)
-
对项目右键,点击【属性】
-
由于我的MySQL安装的似乎是64位的版本,还是将平台设置为x64
-
在【C/C++】>【常规】下编辑【附加包含目录】,将类似
C:\Program Files\MySQL\MySQL Server 5.7\include
这样的目录引入
-
在【链接器】>【常规】下编辑【附加库目录】,将类似
C:\Program Files\MySQL\MySQL Server 5.7\lib
这样的目录引入
-
在【链接器】>【输入】下编辑【附加依赖项】,将libmysql.lib写入
-
点击【确定】完成属性页的配置
-
将libmysql.dll文件复制到当前项目的.cpp文件同级目录下
-
选择【解决方案平台】为“x64”
-
输入以下代码,测试连接(可以把第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_init
、mysql_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
————————————————————————————————
数据库最终结果