《編碼規範和測試方法——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

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