目录
一、下载第三方库
在以前的文章中,我们使用的都是命令行式的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()"接口:
介绍一下里面的参数。
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. 连接mysql
if(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. 释放空间并关闭mysql
mysql_free_result(res);
mysql_close(ms);
return 0;
}
里面的各个部分所使用的接口和出现的结果在上文中已经演示了,这里不再赘述。