目錄
一、下載第三方庫
1. 準備工作
1. 使用mysql官網提供的庫
2. yum源安裝
二、測試第三方庫是否可用
三、mysql常用接口介紹
1. 查看官方文檔
2. 初始化
3. 關閉mysql
4. 連接mysql
5. 下達sql指令
四、一個簡單的C++客戶端庫連接mysql程序
1. 頭文件
2. 初始化與退出
3. 連接mysql
4. 下達sql指令
5. 測試
5.1 退出
5.2 插入數據
5.3 修改程序
5.4 更新數據
5.5 刪除數據
5.6 查詢數據
5.7 當前插入存在的問題
6. 獲取select后的數據
6.1 提取數據
6.2 理解MYSQL_RES結構
6.3 提取表的行和列
6.4 獲取表數據
6.5 獲取列名
7. 釋放空間
8. 事務支持
9. 程序代碼
一、下載第三方庫
在以前的文章中,我們使用的都是命令行式的mysql客戶端。但是還有其他形式的客戶端的,比如圖形化界面、網頁版等等。當然, 也包括語言級別的庫或包能幫我們去直接訪問數據庫。
因此,在這里就為大家介紹一下如何使用語言級別的庫來訪問mysql。在這之前,可以先在mysql中創建一個用戶,方便后續的測試。
1. 準備工作
創建如下一個用戶:
然后在root用戶下再創建如下一個數據庫,并將這個數據庫的所有權限交給該用戶:
在后續的所有關于mysql的測試,都是使用的這個數據庫。
然后再在這個數據庫中建立一個如下的user表:
1. 使用mysql官網提供的庫
要使用語言級別的庫,這里提供兩種方法。第一種方法就是直接到mysql官網上下載對應的庫。
首先,搜索“mysql.com”,打開mysql的官網:
大家打開后的界面可能會這里的不太一樣,因為mysql的官網樣式可能會變化。
進入官網后,選擇“DOWNLOADS”,在里面可以看到如下內容:
點擊進去后,就可以看到如下界面:
里面可以選擇下載你需要的各種類型的庫。其中就包括封裝好的語言級別的庫。因為使用的語言是C++,大家可能就會想下載C++語言級別的庫。但在這里比較推薦使用 C API庫,因為這個庫最簡單。
點擊后就會出現如上界面。在圖中圈出來的部分顯示,mysql官網推薦下載的版本。直接點擊下載即可。
點擊后就可以看到如下內容:
在這里,選擇你要將這個庫安裝在哪個平臺下,然后選擇對應的版本即可。因為我們使用的是linux64位系統,所以選擇對應版本即可:
選擇好后下載即可。
當你下載好后,它就是一個壓縮包,將它通過“rz”命令放到你的linux機器下,然后解壓安裝即可。但是在這里, 不太推薦這種方法。因為大家在安裝的過程中可能會出現一些問題。
2. yum源安裝
如果大家看過我之前的文章“mysql數據庫安裝”的話,里面就介紹了如何安裝mysql的yum源,這里不再贅述。如果大家是使用的yum源安裝,那么在大家安裝好mysql時,其實就已經把mysql的安裝包下載好了。
大家可以輸入“ls /lib64/mysql/”查看linux中是否有如下內容:
然后在輸入“ls /usr/include/mysql”命令,查看是否存在該頭文件。
如果這兩個內容都存在,那么就說明此時你的linux下已經安裝好了需要的東西。但如果沒有,就輸入“yum install -y mysql-devel”命令安裝一下即可。
二、測試第三方庫是否可用
當做好上面的準備工作后,,就可以打開vscode,在上文中創建的test_db目錄下創建一個test.cpp文件。然后引入linux中的mysql庫。
注意,這個mysql目錄會在安裝好mysql后自動被放到/usr/include/路徑下。這個路徑是已經配置好了的默認路徑,不用我們寫。但是mysql.h在mysql目錄下,而mysql并沒有被配置到默認路徑,所以需要自己寫剩下的路徑。
頭文件包好后,此時就可以使用“mysql_get_client_info()”函數了,該函數就是mysql.h中攜帶的,它的作用是獲取當前使用的mysql的客戶端版本。在這里,就用這個函數來測試一下是否這個頭文件可用。
寫出如下測試信息:
保存好后,就可以進行編譯生成對應的文件了:
但是當編譯后可以發現,此時會出現報錯。報錯的原因很簡單,其實是因為在這里使用的庫是一個第三方庫,編譯器并不知道這個庫的位置,也就無法使用庫中的內容。因此,在編譯時,需要帶上使用的第三方庫的路徑。
那這個路徑在哪里呢?其實就在"/lib64/mysql/"下:
在這里,要使用的就是上圖中圈出來的靜態庫。因此,在編譯時帶上庫的路徑和庫名稱:
如果大家不知道為什么這里要帶-L和-l,以及為什么編譯時的靜態庫名稱少了lib和.a,可以到我以前的文章“動態庫鏈接”中查看。這里不再贅述。
可以發現,當鏈接了庫后,就可以編譯成功了。執行該程序:
執行成功,這就說明此時已經能夠使用第三方庫的內容了。
如果大家在測試時發現還是無法運行,就可以輸入“ldd 文件名”查看該可執行文件鏈接的庫:
如果大家發現上圖中圈出來的庫的動態庫鏈接對象為空,就說明這個庫沒有能夠鏈接到指定的庫上。此時大家可以把這個庫后面指向的庫添加到系統的配置文件或環境變量中。至于如何添加,這里不再多說,大家可以自行網上搜索。
三、mysql常用接口介紹
1. 查看官方文檔
在介紹mysql的常用接口之前,大家可以先到mysql的官網“mysql.com”中選擇下圖中的內容:
點擊后往下翻,里面全是mysql庫的文檔。找到C API庫,點擊5.7:
?在進入的頁面的左邊點擊下圖內容:
里面存放的就是關于mysql的接口的介紹。
這里只截取了其中一部分。
大家可以將這個網頁保存一下,當你需要查看某些函數的作用和怎么使用時,就可以查看這個文檔。
2. 初始化
首先大家要知道,mysqld是一個網絡服務,這就意味著在實際進行mysql操作之前,一定需要連接上mysql。而在連接mysql之前,還需要對mysql進行初始化。
要初始化化,就需要使用“mysql_init(MYSQL *mysql)”接口:
該接口會返回一個MYSQL結構體,里面包含了創建mysql需要的一些數據。如果初始化失敗,它就會返回一個空指針。在使用時,該接口的參數直接填nullptr即可。
3. 關閉mysql
當不需要使用mysql后,就需要手動關閉。此時就需要調用“mysql_close()”接口:
它的參數就是mysql_init()的返回值。
4. 連接mysql
上文說了, mysqld是一個網絡服務,所以要使用mysql,我們必須要先連接。當然,在連接之前還需要初始化,初始化的函數上文中已經介紹了。
要連接mysql,就需要使用"mysql_real_connect()"接口:
介紹一下里面的參數。
第一個參數mysql,就是mysql_init()接口的返回值;
第二個參數host,就是要連接的主機;
第三個參數user,就是要用哪個用戶登錄mysql;
第三個參數password,就是這個用戶的密碼;
第五個參數db,就是要使用的數據庫;
第六個參數port,就是要連接的mysqld的端口號;第五個參數unix_socket是域間套接字,大家可以看成就是使用管道,但這里我們是要使用網絡,所以不用管這個參數,填為nullptr即可;
最后一個參數client_flag是用戶的一些選項,這里填為0即可。
5. 下達sql指令
要向mysqld下達sql指令,需要使用“mysql_query()”接口:
該接口的第一個參數就是mysql_init()的返回值;第二個參數就是我們要下達的sql指令。以字符串的方式傳遞給該接口。在這個字符串中的sql指令,可以帶,也可以不帶。
當這個接口執行成功后,它會返回0;失敗則會返回非0的錯誤碼。
四、一個簡單的C++客戶端庫連接mysql程序
有了上面的幾個接口,其實就已經可以初步的使用C++客戶端庫了。為了方便大家看到實際的使用過程,這里利用C++客戶端庫寫一個簡單的可以控制mysql內的數據庫的程序。
1. 頭文件
首先,準備如下頭文件:
里面的內容大家應該都很清楚,不再多說。
2. 初始化與退出
調用mysql_init()接口初始化,獲得一個MYSQL結構體指針:
然后再將退出mysql寫好:
3. 連接mysql
調用mysql_real_connect()接口,連接mysql:
有了上面的內容,其實我們就已經可以開始對特定的數據庫做操作了。在這之前,先編譯生成可執行文件,看看該程序是否可以正常連接:
?可以正常運行,此時就可以著手操作數據庫了。
4. 下達sql指令
為了方便測試,在這里就采取從簡單讀取sql指令的方式:
5. 測試
準備好如上代碼后,我們就可以開始測試了,測試用的數據庫和表在上文中已經說過了,這里就不再多說。
首先,為了能看到數據庫中表的數據的變化,我們先用該程序中的用戶登錄mysql:
登錄成功后,進入conn庫,然后查看user表的數據:
可以看到,此時user表內沒有任何數據。然后啟動寫好的程序:
5.1 退出
首先來測試一下能否退出:
退出沒有問題,進行下一個測試。
5.2 插入數據
重新啟動程序,然后輸入insert語句:
此時就提示該指令執行成功。那到底是不是真的成功了呢?查看一下user表的數據:
可以看到,user表中確實多出了剛剛在mytest程序中要插入的數據。注意,在上面的mysql語句中,是帶了“;”的,其實在mysql_query()接口中,也是可以不帶“;”的。再插入一個數據:
這條sql語句中就沒有帶“;”。查看user表的數據:
依然插入成功了。
要知道,在linux中,我們使用mysql的時候,不就是使用的mysql的客戶端進行操作么?因此,如果大家愿意,其實也可以使用C++客戶端庫自己寫一個客戶端來與mysqld交互。
5.3 修改程序
通過上面的測試其實就可以發現,我們是可以用C++客戶端庫自己寫一份客戶端的。但沒有這個必要,畢竟有現成的何必自己去寫呢?
同時大家可以發現,在linux下以命令行的方式去提交sql指令,有點麻煩。因此,修改一下程序,直接從程序中提供sql語句:
此時就可以直接在sql字符串內寫好要執行的sql語句,然后重新編譯執行即可。測試起來就比在命令行中寫方便。
5.4 更新數據
修改好程序后,在sql字符串中寫好update語句:
重新編譯并執行。然后查看user表內的數據:
修改成功。
5.5 刪除數據
再來測試一下delete語句:
重新編譯并執行。查看user表內的數據:
刪除成功。
5.6 查詢數據
再來測試一下select語句:
重新編譯并執行:
可以看到,執行成功了。然后呢?數據庫的數據呢?在這個場景下,我們自己寫的程序就是一個上層應用,該程序將指定的sql語句發送給數據庫后,這些sql語句就被會看成事務。因此,在以前的文章中講的事務執行失敗、事務需要回滾等等操作,都由數據庫自行處理,無需上層應用去考慮。
同時,在insert、delete、update這些sql語句中,都是對數據庫做操作。因此,使用這些數據后,上層應用只需要知道這些sql語句是否執行成功,無需看到執行成功后的數據。但是select語句不一樣,在上層應用中調用該語句,不就是想查看特定數據,然后用這些數據去執行一些特定的操作么。但是在這里,雖然執行select語句成功了,但是上層應用中依然僅僅知道執行成功,而無法看到查詢結果。
因此, 在select語句之后,還需要調用其他接口來讓我們看到查詢出來的結果。
5.7 當前插入存在的問題
如果大家仔細觀察了user表的數據,就會發現,在這個表中,插入的用戶名字都是英文的。那如果插入中文呢?測試一下:
重新編譯并執行:
程序執行成功,沒有問題。我們再來看一下user表內的數據:
可以發現,雖然插入成功了,但是user表中本應存儲“張三”這個名字的位置,卻是亂碼。這其實就是編碼格式的問題。
在mysql數據庫中已經配置過了,在該數據庫下默認使用utf8的編碼集。但是這僅僅是服務端下的編碼格式。當前使用的客戶端,即該程序下,它使用的編碼集并不是utf8。而是默認的latin1。因此,在此時我們的客戶端在發送數據時是將數據按Latin1的編碼格式進行編碼的;但是當mysqld服務端接收到數據后,確實以utf8的格式進行解碼的。編碼與解碼使用的編碼格式不同,也就導致了服務端中出現亂碼。
由此,在客戶端中,我們還需要設置編碼格式。此時,就需要使用“msql_set_character_set()”接口:
第一個參數mysql,就是mysql_init()的返回值;第二個參數csname,就是要使用的編碼集的名字。這個接口需要在連接成功,即調用mysql_real_connect()接口后使用。
重新執行一次insert語句:
重新編譯并執行。查看user表的數據:
沒有出現亂碼。
此時大家可能有個疑問,在上面沒有設置編碼格式時,為什么在編碼格式不同的情況下,中文會亂碼,英文和數字卻不會亂碼呢?其實很簡單,英文字母一共也就26個,個位數字也是只有10個。因為它們的數量很少,所以不同的編碼集對這些內容的適配都很好。但中文不同,中文字符有上萬個,常用的中文字符也有數千個,大量的字符就導致了不同的編碼集可能采用不同的編碼方式進行處理,也就可能出現亂碼了。?
6. 獲取select后的數據
6.1 提取數據
在實際上,當用mysql_query()接口執行select語句后,它會將數據庫中查詢到的結果保存到該接口傳入的參數MSYQL結構體中。在這個結構體中是有專門的緩沖區來保存這些數據的。
雖然這些數據是保存在MYSQL結構體提供的緩沖區內,但依然需要將這些數據從它的緩沖區中提取出來。
要從MYSQL中提取出數據,就需要使用“mysql_store_result()”接口:
這個接口的參數就是mysql_init()的返回值。成功時返回對應的結構體指針,失敗則返回null。
我們可以在官網文檔中查看關于該結構的說明:
根據文檔說明可以知道,這個結構會將查詢結果按行為單位放置在“結果集”,即該結構當中。
由此,就可以使用對應接口提取了:
當提取出來對應的數據后,如何顯示呢?在了解如何顯示之前,還需要了解一下MYSQL_RES是如何存儲數據的。
6.2 理解MYSQL_RES結構
假設現在有如下一張表:
對于這張表,可以將其組成部分看做兩個,分別是表結構和表數據。表結構就是列屬性,表數據就是每列中的數據。而組成表的這些符號,在數據庫中實際并未存儲,而是在顯示時打印出來供用戶區分表內的各個部分的。
首先大家要知道,當數據庫中查詢出來的數據被轉儲到MYSQL_RES結構中時,它們必然已經是被放到了內存里,這也就說明,此時這些函數是有對應的地址的。
當上層應用調用select語句查詢表時,它就是將表結構和表數據添加到MYSQL結構體內準備好的緩沖區中:
當調用mysql_store_result()接口時,就是將MYSQL結構體里面的緩沖區中保存的數據按行轉儲到MYSQL_RES結構當中:
那MYSQL_RES內如何保存這些數據呢?為了方便大家理解,這里用只以表數據來舉例。MYSQL_RES可以將其看成兩層數組。第一層數組里面保存的是char**的二級指針,每個二級指針指向一個數組,這個數組里面保存的是char*的一級指針。每個指針都指向對應行數據:
當然,實際的MYSQL_RES中還存有很多余這張表相關的其他數據。但是大家可以將其整體結構就理解為上圖所示。
由此,如果我們想從MYSQL_RES內拿到數據,其實就可以按照數組的方式,從這個結構體的各個成員變量中以下標的形式拿取。
但是,我們怎么知道表數據一共有多少行和多少列呢?此時就可以調用C++客戶端庫內的其他相關函數了。
6.3 提取表的行和列
要提取表的行,可以使用“mysql_num_rows()”接口:
它的參數就是“mysql_store_result()”的返回值。
要提取表的列,可以使用“mysql_num_fields()”接口:
它的參數也是“mysql_store_result()”的返回值。
通過這兩個接口,就可以分別獲得查詢出來的表的行和列了。那么這兩個接口到底能不能正確獲取呢?我們來測試一下:
傳入如下select語句:
重新編譯并運行:
打印的結果和我們的預期一樣,沒有問題。
6.4 獲取表數據
要獲取表數據,還需要了解一個接口,即“mysql_fetch_row()”接口:
這個接口的參數就是mysql_store_result()接口的返回值。當它調用成功時,它會返回一個MYSQL_ROW變量。
對于這個接口,大家可以將其看做一個迭代器,當第一次調用這個接口時,它會指向MYSQL_RES結構中的第一行的數據的起點。讓我們可以通過指針的形式訪問數據。當再次調用的時候,它會自動跳到第二行數據的起點。
轉到這個類型的定義上去看看:
可以看到,在源碼中,它就是一個char**。
通過這個接口,我們就能以下標的形式獲取表數據。
注意,mysql中的所有數據,在上層應用讀取出來時全部都是被看做字符串。
有了上面的認識,就可以在程序中添加如下代碼了:
然后選擇查詢表內的所有數據:
重新編譯并執行:
成功拿取到了表數據。由此,我們就可以拿到我們對應的表數據了。未來大家想拿著這份數據去做什么,就由大家自己決定。
6.5 獲取列名
現在我們已經可以獲取表數據了。那如果還想獲取列名呢?此時就需要使用“mysql_fetch_fields()”接口了:
這個接口可以一次性獲取所有列的列名。它的參數就是mysql_store_result()的返回值。當調用成功時,它返回一個MYSQL_FIELD結構體,里面就保存了列的各項屬性:
例如列名、列的原生名(給列取別名的情況)、屬于哪個表、屬于哪個原生表、屬于哪個數據庫、列的類型等等屬性。
此時就可以解答大家的一個問題了。上文中說了,查詢到的表數據在上層應用中是以字符串的形式保存的。但是我們在使用的時候不一定是用字符串形式使用啊。例如age就是int類型的。那我們在上層使用這些數據時,如何將它們從字符串轉化為原來的類型呢?其實就是通過這個接口拿到列屬性,然后通過MSYQL_FIELD結構體中的type變量里面保存的類型,來將其重新轉化回去。
由此,就可以在程序中添加如下代碼,來獲取列了:
重新編譯并執行:
此時就成功的將列名獲取了。當然,大家也可以嘗試下獲取其他列屬性,這里就不再測試了。
7. 釋放空間
在上文中我們知道了,當我們查詢數據時,查詢結果其實已經被保存在內存中了。而這些數據其實就是被保存在用new這類申請內存空間的接口申請的內存中。而我們知道,在C++中,用new這類接口申請的空間是需要用戶自行釋放的。這里也是如此。
但是和以前不同,在這里我們最好不要用free()、delete這類釋放空間的接口,而是使用C++客戶端庫為我們提供的接口,即“mysql_free_result()”接口:????????
這個接口就會釋放掉調用mysql_store_result()接口后開辟的內存空間。
8. 事務支持
在C++客戶端庫中也是支持事務的。可以選擇一次性將一批sql語句傳給數據庫。可以采用如下接口:
這里就不再演示了,大家可以自行嘗試使用一下。
9. 程序代碼
在上文中所有的接口整合起來,就可以寫出如下的程序:
#include <iostream>
#include <string>
#include <mysql/mysql.h>// const std::string host = "127.0.0.1";
const std::string host = "localhost";//當使用本地登錄時,本地換回和localhost都是可行的
const std::string user = "connector";
const std::string password = "123456";
const std::string db = "conn";
const unsigned int port = 3306;int main()
{//1. 初始化MYSQL *ms = mysql_init(nullptr);if(ms == nullptr){std::cerr << "init MYSQL error" << std::endl;return 1;}//2. 連接mysqlif(mysql_real_connect(ms, host.c_str(), user.c_str(), password.c_str(), db.c_str(), port, nullptr, 0) == nullptr){std::cerr << "connect MYSQL error" << std::endl;return 2;}mysql_set_character_set(ms, "utf8");//設置編碼格式//3. 執行各類sql語句// std::string sql = "update user set name='jimmy' where id=2";// std::string sql = "delete from user where id=2";std::string sql = "select * from user";// std::string sql = "insert into user (name, age, telphone) values ('張三', 10, '3456')";// std::string sql = "insert into user (name, age, telphone) values ('李四', 13, '4567')";// std::string sql = "select * from user where id=1";int n = mysql_query(ms, sql.c_str());if(n == 0)std::cout << "success" << std::endl;else{std::cerr << "failed: " << n << std::endl;return 3;}// std::string sql;//接收sql指令// while(true)// {// std::cout << "MYSQL>> ";// if(!std::getline(std::cin, sql) || sql == "quit")//從鍵盤讀取sql指令// {// std::cout << "bye bye" << std::endl;// break;// }// int n = mysql_query(ms, sql.c_str());// if(n == 0)// std::cout << sql << " success " << n << std::endl;// else// std::cerr << sql << " error " << n << std::endl;// }// std::cout << "connect MYSQL success" << std::endl;//4. 查詢結果轉儲MYSQL_RES *res = mysql_store_result(ms);if(res == nullptr){std::cerr << "mysql_store_result failed" << std::endl;return 4;;}//5. 獲取表的行和列my_ulonglong rows = mysql_num_rows(res);my_ulonglong fields = mysql_num_fields(res);// std::cout << "行: " << rows << " 列: " << fields << std::endl;//6. 獲取列屬性MYSQL_FIELD *field_array = mysql_fetch_fields(res);for(int i = 0; i < fields; ++i)std::cout << field_array[i].name << "\t";std::cout << std::endl;//7. 獲取表數據for(int i = 0;i < rows; ++i)//先遍歷行{MYSQL_ROW row = mysql_fetch_row(res);for(int j = 0; j < fields; ++j)std::cout << row[j] << "\t";std::cout << std::endl;}//8. 釋放空間并關閉mysqlmysql_free_result(res);mysql_close(ms);return 0;
}
里面的各個部分所使用的接口和出現的結果在上文中已經演示了,這里不再贅述。