問題描述
使用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
————————————————————————————————
數據庫最終結果