我在夏理当农码 - CSDN: dio夹心小面包

『MySQL』C++ MySQL C-API 开发入门

  |   0 评论   |   6 浏览

『MySQL』访问

1 连接数据库的方式

连接数据库的方式通常有几种:

  • 命令行式连接
  • 图形化客户端连接
  • 语言库连接

以往的知识采用命令行式连接, 重点用于学习MySQL中的操作语句以及底层;

不同的连接方式有着不同的优势;

如, 命令行式连接能够直接对MySQL有直接的触碰, 并且能够更好了解数据库的底层, 但终究没有图形化客户端直观;

图形化客户端能够直观的看到数据库的数据及当前状态, 但查询出来的结果只能用于观看或者更便捷的操作数据库中的数据, 从其中取出的数据并不能很好的交付给上层业务处理;

语言库调用数据库可以让业务直接与数据库进行交互, 以更便捷的获取数据库的数据并完成业务逻辑, 但其并不能进行很好的数据库中的数据管理, 同时MySQL提供了一系列的接口, 可供不同的语言通过不同的语言对数据库进行不同的操作, 包括但不限于C/Cpp, Java, Python;

此处关于命令行式的连接不再进行赘述, 可回顾以往内容;


2 使用C++对MySQL进行连接

此处重点演示通过cpp对应的库来连接数据库;


2.1 前置条件

  1. 下载MySQL connect

    MySQL并不是C++标准的一部分, 因此标准库中并不会存在相关的库, 因此需要通过下载的方式进行使用;

    可以在[MySQL :: Download Connector/C++] 下载链接中找到对应的版本进行下载;

    作者的系统为Ubuntu22.04, 因此下载的版本也是对应系统的版本, 通常MySQL connector是可以向下兼容的, 因此需要下载版本>=当前版本的即可;

    除此之外, 可以通过命令行进行命令行安装:

    sudo apt update
    sudo apt install -y libmysqlclient-dev # C - API
    sudo apt install -y libmysqlcppconn-dev #Cpp - API
    

    此处与 C - API 进行演示;

    本质原因是, 在涉及到大部分的cpp开发环境下, 采用的更多的还是C-API;

    通过apt安装成功的头文件与库分别在以下位置:

    • 头文件

      /usr/include
      

    • 库文件(动静态库)

      /usr/lib/x86_64-linux-gnu
      

  2. 创建数据库数据表

    通常在使用库对数据库进行连接时, 需要指名对应的数据库, 因此需要首先创建一个对应的数据库, 并设计对应的表;

    mysql> use ConnectDB
    Database changed
    mysql> create table if not exists test_tb(
        -> id bigint primary key auto_increment,
        -> name varchar(25) not null,
        -> telnum char(11) not null unique
        -> ); # 建表
    Query OK, 0 rows affected (0.07 sec)
    
  3. 创建用户并赋权

    通常情况下, 用库对数据库进行连接时, 需要指名一个对应的用户来进行连接, 因此需要有一个前置的用户, 并拥有对操作库允许的对应权限(如增删改查);

    mysql> CREATE USER 'connector'@'localhost' IDENTIFIED BY 'passw02d'; # 创建用户
    Query OK, 0 rows affected (0.02 sec)
    mysql> GRANT ALL PRIVILEGES ON ConnectDB.* TO 'connector'@'localhost'; # 赋权
    Query OK, 0 rows affected (0.01 sec)
    mysql> flush privileges;
    Query OK, 0 rows affected (0.00 sec)
    
  4. 测试调用

    当一切就绪后, 可以尝试调用C - API来检查是否可以进行调用;

    创建一个cpp文件, 并且引入对应的文件夹, 调用mysql C - API提供的mysql_get_client_info()mysql_get_client_version()来查看是否能进行版本信息获取;

    #include <iostream>
    
    #include <mysql/mysql.h> // 头文件引入, 也可以直接include <mysql.h>, 但是需要 -I来找到所在路径
    
    int main() {
      std::cout << "Mysql Client info: " << mysql_get_client_info() << std::endl;
      std::cout << "Mysql Client version: " << mysql_get_client_version()
                << std::endl;
      return 0;
    }
    

    运行结果为:

    $ g++ -o test test.cpp -lmysqlclient # 需要指定库名
    $ ./test 
    Mysql Client info: 8.0.44
    Mysql Client version: 80044
    

    可以看到配置成功;

    此处的CMakeLists.txt为:

    cmake_minimum_required(VERSION 3.10)
    
    project(MYSQL_CONNECT_TEST) # 项目名称
    
    set(CMAKE_CXX_STANDARD 17) # 设置cpp标准
    
    add_executable(test test.cpp) # 目标文件
    
    target_link_libraries(test mysqlclient) # 需要链接的库
    

    程序编译后可以通过ldd查看链接情况;


2.2 获取版本信息

mysqlclient库提供了两个用于查询版本的接口, 分别为:

  • mysql版本(字符串)

    const char* 
    mysql_get_client_info(void)
    
  • mysql版本(长整型)

    unsigned long
    mysql_get_client_version(void)
    

这两个接口在2.1中测试是否配置成功时使用过, 两个函数均为返回mysql客户端的版本, 只不过返回的内容格式不同, 仅此而已, 其他不进行赘述;


2.3 接口介绍


2.3.1 数据库初始化

C - API中的数据库, 实际上可以理解为一个类, 因此在调用过程中需要实例化并初始化对应的mysql对象;

MYSQL *
mysql_init(MYSQL *mysql)

这个函数将会初始化对应的MYSQL对象;

而说实话, 这个接口可能在某种程度上设计的比较奇怪, 可以看到其返回值与参数都是MYSQL*;

其中返回值为返回一个初始化好的, 或是构造好的MYSQL对象指针, 而传参为传一个MYSQL结构体, 来初始化这个MYSQL结构体;

准确的来说这个初始化函数有两个行为:

  1. 没有MYSQL结构体(传NULL或是nullptr)

    当传NULL或是nullptr时, 将会认为没有这个对象, 将会从堆空间中申请内存并构造一个MYSQL对象(包括初始化)并返回, 相当于new;

    MYSQL* mysql = mysql_init(NULL);
    

    当这个MYSQL对象被close关闭连接后, 将会自动释放内存, 因为内存为该函数申请的;

    当失败后将会返回空指针表示内存申请失败;

  2. 存在MYSQL结构体(无论是栈上开辟还是堆上开辟)

    传入一个MYSQL对象指针, 表示需要对该对象进行初始化(填上默认字段);

    MYSQL mysql;
    mysql_init(&mysql); // 传入指针进行初始化
    

    当这个MYSQL对象被close关闭连接后, 将不会释放该内存, 需要自行释放内存, 因为这块空间是使用者提供的;

通常使用第一种方式创建MYSQL对象, 也是一种主流的方式;

#include <iostream>

#include <mysql/mysql.h> // 头文件引入, 也可以直接include <mysql.h>, 但是需要 -I来找到所在路径

int main() {
  /*初始化mysql*/
  MYSQL *mysql = mysql_init(nullptr);
  if (nullptr == mysql) {
    std::cerr << "init failed" << std::endl;
    return -1;
  }
  std::cout << "init successed" << std::endl; // 通常不会失败
    
  mysql_close(mysql); // 关闭连接并释放内存
  return 0;
}

2.3.2 关闭连接

关闭连接通常采用mysql_close()来进行关闭;

void
mysql_close(MYSQL *mysql)

这个函数通常给已经连接的数据库对象断开连接;

若是这个对象是通过mysql_init创建, 则释放内存;

因此当通过mysql_init(nullptr)创建的数据库对象, 通常都采用调用这个函数来关闭连接并释放对象内存;


2.3.3 连接数据库

通常使用mysql_real_connet()函数来连接数据库;

MYSQL *
mysql_real_connect(MYSQL *mysql, // 需要连接的数据库对象
                   const char *host, // 连接地址
                   const char *user, // 用户名
                   const char *passwd, // 密码
                   const char *db, // 数据库名称
                   unsigned int port, // 端口
                   const char *unix_socket, // 不为空时传指定要使用的套接字或命名管道
                   unsigned long client_flag) // 通常为0, 可通过一些标志启用某些功能(具体查看文档)

当连接失败时, 该返回值返回空值(NULL/nullptr);

当连接成功时, 返回所连接的MYSQL对象指针;

根据上文做的前置工作(创建库/表/用户/赋权)为基础, 对应的连接代码为如下:

#include <iostream>

#include <mysql/mysql.h> // 头文件引入, 也可以直接include <mysql.h>, 但是需要 -I来找到所在路径

int main() {
  /*初始化mysql*/
  MYSQL *mysql = mysql_init(nullptr);
  if (nullptr == mysql) {
    std::cerr << "init failed" << std::endl;
    return -1;
  }
  std::cout << "init successed" << std::endl; // 通常不会失败

  /*连接数据库*/
  if (!mysql_real_connect(mysql, "localhost", "connector", "passw02d",
                          "ConnectDB", 3306, nullptr, 0)) {

    std::cerr << "connect failed" << std::endl;
    return -2;
  }
  std::cout << "connect successed" << std::endl;
  
  /*断开连接并释放内存*/
  mysql_close(mysql);
  return 0;
}

当然在这段代码中, 我们可以将用户信息定义在全局中以方便使用:

#include <iostream>

#include <mysql/mysql.h> // 头文件引入, 也可以直接include <mysql.h>, 但是需要 -I来找到所在路径

/*定义数据库信息*/
namespace dbInfo {
const char *_host = "localhost"; // 也可以为 127.0.0.1 环回地址
const char *_username = "connector";
const char *_passwd = "passw02d";
const char *_dbname = "ConnectDB";
const int _port = 3306;
}; // namespace dbInfo

int main() {
  /*初始化mysql*/
  MYSQL *mysql = mysql_init(nullptr);
  if (nullptr == mysql) {
    std::cerr << "init failed" << std::endl;
    return -1;
  }
  std::cout << "init successed" << std::endl; // 通常不会失败

  /*连接数据库*/
  if (!mysql_real_connect(mysql, dbInfo::_host, dbInfo::_username,
                          dbInfo::_passwd, dbInfo::_dbname, dbInfo::_port,
                          nullptr, 0)) {

    std::cerr << "connect failed" << std::endl;
    return -2;
  }
  std::cout << "connect successed" << std::endl;

  /*断开连接并释放内存*/
  mysql_close(mysql);
  return 0;
}

运行结果为:

$ ./test 
init successed
connect successed

其中我们知道, 这是在mysqld服务启动时才可以成功进行连接, 那当然若是关闭连接, 当然不会连接成功;

可以看到, 当服务停止后, 连接失败, 重启服务后, 连接成功;


2.3.4 数据库的增删改

mysql C - API中, 通常调用mysql_query()函数来调用sql语句;

int
mysql_query(MYSQL *mysql, // 需要执行语句的MYSQL对象
            const char *stmt_str) // 需要执行的语句

返回值为0时表示执行成功, 非0则表示执行失败, 不同的错误码有着不同的错误表示;

  • CR_COMMANDS_OUT_OF_SYNC(code: 2014)

    命令执行顺序错误。

  • CR_SERVER_GONE_ERROR(code: 2006)

    MySQL 服务器已断开连接。

  • CR_SERVER_LOST(code: 2013)

    查询过程中与服务器的连接断开了。

  • CR_UNKNOWN_ERROR(code: 2000)

    发生未知错误。

具体的错误码可以查看[官方文档 - MySQL :: MySQL 9.5 Error Reference :: 3 Client Error Message Reference];

根据这个函数, 可以模拟出一个类似的mysql客户端:

#include <iostream>

#include <mysql/mysql.h> // 头文件引入, 也可以直接include <mysql.h>, 但是需要 -I来找到所在路径

#include <string>

namespace dbInfo {
const char *_host = "localhost"; // 也可以为 127.0.0.1 环回地址
const char *_username = "connector";
const char *_passwd = "passw02d";
const char *_dbname = "ConnectDB";
const int _port = 3306;
}; // namespace dbInfo

int main() {
  /*初始化mysql*/
  MYSQL *mysql = mysql_init(nullptr);
  if (nullptr == mysql) {
    std::cerr << "init failed" << std::endl;
    return -1;
  }
  std::cout << "init successed" << std::endl; // 通常不会失败

  /*连接数据库*/
  if (!mysql_real_connect(mysql, dbInfo::_host, dbInfo::_username,
                          dbInfo::_passwd, dbInfo::_dbname, dbInfo::_port,
                          nullptr, 0)) {

    std::cerr << "connect failed" << std::endl;
    return -2;
  }
  std::cout << "connect successed" << std::endl;

  /*执行语句*/
  std::string sql;
  while (true) {
    std::cout << "mysql >>> ";
    if (!std::getline(std::cin, sql) || "quit" == sql) {
      std::cout << "bye bye" << std::endl;
      break;
    }
    int n = mysql_query(mysql, sql.c_str());
    if (n) {
      std::cerr << sql << " failed: " << n << std::endl;
      continue;
    }
    std::cout << sql << " successed: " << n << std::endl;
  }

  /*断开连接并释放内存*/
  mysql_close(mysql);
  return 0;
}

运行结果为:

可以启动数据库来查看是否生效, 能否与数据库进行数据的基本CURD操作;

结果来看, 可以对数据库进行对应的插入, 删除, 更新操作;

不使用这种方式, 也可以直接单独定义对应的单sql语句进行执行;

#include <iostream>

#include <mysql/mysql.h> // 头文件引入, 也可以直接include <mysql.h>, 但是需要 -I来找到所在路径

#include <string>

namespace dbInfo {
const char *_host = "localhost"; // 也可以为 127.0.0.1 环回地址
const char *_username = "connector";
const char *_passwd = "passw02d";
const char *_dbname = "ConnectDB";
const int _port = 3306;
}; // namespace dbInfo

int main() {

  /*验证配置是否成功 - 调用获取版本接口*/
  //  std::cout << "Mysql Client info: " << mysql_get_client_info() <<
  //  std::endl;
  //  std::cout << "Mysql Client version: " << mysql_get_client_version()  //            << std::endl;

  /*初始化mysql*/
  MYSQL *mysql = mysql_init(nullptr);
  if (nullptr == mysql) {
    std::cerr << "init failed" << std::endl;
    return -1;
  }
  std::cout << "init successed" << std::endl; // 通常不会失败

  /*连接数据库*/
  if (!mysql_real_connect(mysql, dbInfo::_host, dbInfo::_username,
                          dbInfo::_passwd, dbInfo::_dbname, dbInfo::_port,
                          nullptr, 0)) {

    std::cerr << "connect failed" << std::endl;
    return -2;
  }
  std::cout << "connect successed" << std::endl;

  /*执行语句*/
  // 单sql语句
  std::string sql = "insert into test_tb(name, telnum) values('zhangsan', "
                    "'111111'), ('wangwu', '333333'), ('zhaoliu', '444444')";
  int n = mysql_query(mysql, sql.c_str());
  if (n) {
    std::cerr << sql << " failed: " << n << std::endl;
  }
  std::cout << sql << " successed: " << n << std::endl;

  /*断开连接并释放内存*/
  mysql_close(mysql);
  return 0;
}

运行结果为:

在上述示例中, 插入或修改的都为英文, 但是若是插入中文将可能会因为编码不匹配从而导致插入的内容无法识别(若是在windows上的编辑器将会出现这类问题, 作者使用Ubuntu因此未复现该问题);

// #include <...>
// ...
int main(){
    // ...
    std::string sql = "insert into test_tb(name, telnum) values('张三', '666666')";
    int n = mysql_query(mysql, sql.c_str());
    if (n) {
        std::cerr << sql << " failed: " << n << std::endl;
    }
    std::cout << sql << " successed: " << n << std::endl;
    // ...
    return 0;
}

因此若是出现这类问题, 可以采用mysql_set_character_set(MYSQL *mysql, const char *csname)函数来进行设置;

这种情况通常会出现在客户端为Windows系统, 而服务端为Linux的情况;

mysql_set_character_set(mysql, "utf8");

2.4 数据库的查询

查询语句我们通常使用SELECT语句来进行, 对于C - API也不例外, 但为什么这里需要单独写一个章节呢?

我们来运行一下查询语句:

// #include <...>
// ...
int main(){
    // ...
    std::string sql = "select * from test_tb";
    int n = mysql_query(mysql, sql.c_str());
    if (n) {
        std::cerr << sql << " failed: " << n << std::endl;
    // ...
}

对应的运行结果为:

从结果来看, 已经执行成功, 但是我们并没有获取实际的结果, 这是因为使用 mysql_qurey()函数只能执行sql语句, 并不能单靠该函数来返回执行结果;

本质原因是, 针对增删改, 只需要保证操作成功即可, 而查询需要返回对应的查询结果;


2.4.1 获取结果集

mysql C - API中, 获取查询结果集的函数为:

MYSQL_RES *
mysql_store_result(MYSQL *mysql)

该函数传入一个使用过查询语句的MYSQL对象指针, 当获取失败时则返回空指针, 否则返回一个MYSQL_RES对象指针;

当出错时, 分别可以采用 mysql_error(), mysql_errno(), mysql_field_count()分别来查询对应的错误原因;

而这个MYSQL_RES对象则是用于存储该查询结果集的结构体:

typedef struct MYSQL_RES {
    uint64_t row_count;
    MYSQL_FIELD *fields;
    struct MYSQL_DATA *data;
    MYSQL_ROWS *data_cursor;
    unsigned long *lengths; /* column lengths of current row */
    MYSQL *handle;          /* for unbuffered reads */
    const struct MYSQL_METHODS *methods;
    MYSQL_ROW row;          /* If unbuffered read */
    MYSQL_ROW current_row;  /* buffer to current row */
    struct MEM_ROOT *field_alloc;
    unsigned int field_count, current_field;
    bool eof; /* Used by mysql_fetch_row */
    /* mysql_stmt_close() had to cancel this result */
    bool unbuffered_fetch_cancelled;
    enum enum_resultset_metadata metadata;
    void *extension;
} MYSQL_RES;

以这张表为例:

针对这张表而言, 关于边框的这种分隔符并不是数据库需要存储的内容, 只是为了方便用户查看因此作的美化, 实际上在mysqld中, 需要为用户存储的内容只有, 列属性, 以及各个字段的数据;

实际上通过API所获取的内容, 无论在数据库中的存储如何, 但其最终通过API所返回的结果均为字符串, 因此我们实际上可以将其理解为一个char***的结构, 其中char*表示单个字段的字符串, char**表示一行记录, char***则表示为整张表的结果集;

该图只以单行作为示例;


2.4.2 获取行列信息

从上文得知, 这个问题本质上可以简化为C++中的vector<vector<string>>的遍历问题;

在知道获取的结构后, 我们需要知道对应的行列信息;

  • 获取行信息

    通常情况下, 通过mysql_num_rows来获取结果集的行数;

    uint64_t
    mysql_num_rows(MYSQL_RES *result)
    

    传入一个MYSQL_RES对象指针, 返回结果集的行数;

  • 获取列信息

    通过 mysql_num_fields来获取结果集中的列数;

    unsigned int
    mysql_num_fields(MYSQL_RES *result)
    

通过上述内容可以来进行测试, 是否能获取行列信息;

#include <iostream>

#include <mysql/mysql.h> // 头文件引入, 也可以直接include <mysql.h>, 但是需要 -I来找到所在路径

#include <string>

namespace dbInfo {
const char *_host = "localhost"; // 也可以为 127.0.0.1 环回地址
const char *_username = "connector";
const char *_passwd = "passw02d";
const char *_dbname = "ConnectDB";
const int _port = 3306;
}; // namespace dbInfo

int main() {
  /*初始化mysql*/
  MYSQL *mysql = mysql_init(nullptr);
  if (nullptr == mysql) {
    std::cerr << "init failed" << std::endl;
    return -1;
  }
  std::cout << "init successed" << std::endl; // 通常不会失败

  /*连接数据库*/
  if (!mysql_real_connect(mysql, dbInfo::_host, dbInfo::_username,
                          dbInfo::_passwd, dbInfo::_dbname, dbInfo::_port,
                          nullptr, 0)) {

    std::cerr << "connect failed" << std::endl;
    return -2;
  }
  std::cout << "connect successed" << std::endl;

  /*执行语句*/
  // 单sql语句(select)
  std::string sql = "select * from test_tb";
  int n = mysql_query(mysql, sql.c_str());
  if (n) {
    std::cerr << sql << " failed: " << n << std::endl;
    return -3;
  } else {
    std::cout << sql << " successed: " << n << std::endl;
  }

  /*获取查询结果集*/
  MYSQL_RES *sel_res = mysql_store_result(mysql);
  
  // 获取行列信息
  uint64_t rows = mysql_num_rows(sel_res);
  unsigned int fields = mysql_num_fields(sel_res);
  std::cout << "rows: " << rows << "  fields: " << fields << std::endl;

  /*断开连接并释放内存*/
  mysql_close(mysql);
  return 0;
}

运行结果为:

$ ./test 
init successed
connect successed
select * from test_tb successed: 0
rows: 5  fields: 3

从结果来看, 确实获取到了行列信息, 且为53列(不包含列属性);


2.4.3 获取结果集中内容

现在获取到了行列信息, 但我们最终的目的是获取真正的结果集, 而只获取到了行列信息这是第一步;

我们需要遍历这张表, 那么首先需要遍历对应的行;

mysql C - API中, 为了更方便对结果集进行遍历, 提供了一个函数来遍历整个结果集的各行;

MYSQL_ROW
mysql_fetch_row(MYSQL_RES *result)

其返回的结果是一个MYSQL_ROW类型的返回值;

而这个类型实际上就是一个char**;

mysql_fetch_row函数可以看做是一个迭代器, 其在第一次调用时, 将会去获取结果集的第一行, 第二次调用时则去获取第二行, 无需维护迭代器, 只需要控制调用次数而已;

#include <iostream>

#include <mysql/mysql.h> // 头文件引入, 也可以直接include <mysql.h>, 但是需要 -I来找到所在路径

#include <string>

namespace dbInfo {
const char *_host = "localhost"; // 也可以为 127.0.0.1 环回地址
const char *_username = "connector";
const char *_passwd = "passw02d";
const char *_dbname = "ConnectDB";
const int _port = 3306;
}; // namespace dbInfo

int main() {
  /*初始化mysql*/
  MYSQL *mysql = mysql_init(nullptr);
  if (nullptr == mysql) {
    std::cerr << "init failed" << std::endl;
    return -1;
  }
  std::cout << "init successed" << std::endl; // 通常不会失败

  /*连接数据库*/
  if (!mysql_real_connect(mysql, dbInfo::_host, dbInfo::_username,
                          dbInfo::_passwd, dbInfo::_dbname, dbInfo::_port,
                          nullptr, 0)) {

    std::cerr << "connect failed" << std::endl;
    return -2;
  }
  std::cout << "connect successed" << std::endl;

  /*执行语句*/
  // 单sql语句(select)
  std::string sql = "select * from test_tb";
  int n = mysql_query(mysql, sql.c_str());
  if (n) {
    std::cerr << sql << " failed: " << n << std::endl;
    return -3;
  } else {
    std::cout << sql << " successed: " << n << std::endl;
  }

  /*获取查询结果集*/
  MYSQL_RES *sel_res = mysql_store_result(mysql);

  // 获取行列信息
  uint64_t rows = mysql_num_rows(sel_res);
  unsigned int fields = mysql_num_fields(sel_res);
  std::cout << "rows: " << rows << "  fields: " << fields
            << std::endl; // 打印行列结果

  // 遍历结果集 获取结果集内容
  MYSQL_ROW row;
  for (int curi = 0; curi < rows; ++curi) {
    row = mysql_fetch_row(sel_res);
    for (int curj = 0; curj < fields; ++curj) {
      std::cout << row[curj] << "\t";
    }
    std::cout << std::endl;
  }

  /*断开连接并释放内存*/
  mysql_close(mysql);
  return 0;
}

运行结果为:

$ ./test 
init successed
connect successed
select * from test_tb successed: 0
rows: 5  fields: 3
2       LiSi    222222
3       zhangsan        111111
4       wangwu  333333
5       zhaoliu 444444
6       张三    666666

可以看到结果已经被打印;


2.4.4 获取结果集列属性

通常调用 mysql_fetch_field()mysql_fetch_fields()函数来获取结果集每一列的列属性, 其中前者为循环调用获取单个, 后者为获取一个列名的数组集合;

MYSQL_FIELD *
mysql_fetch_field(MYSQL_RES *result)

MYSQL_FIELD *
mysql_fetch_fields(MYSQL_RES *result)

两个函数的返回值都为一个MYSQL_FIELD*本质上是指针指向单个对象还是指向首对象的地址处;

这个结构体通常包含了列属性的各部分信息;

#include <iostream>

#include <mysql/mysql.h> // 头文件引入, 也可以直接include <mysql.h>, 但是需要 -I来找到所在路径

#include <string>

namespace dbInfo {
const char *_host = "localhost"; // 也可以为 127.0.0.1 环回地址
const char *_username = "connector";
const char *_passwd = "passw02d";
const char *_dbname = "ConnectDB";
const int _port = 3306;
}; // namespace dbInfo

int main() {
  /*初始化mysql*/
  MYSQL *mysql = mysql_init(nullptr);
  if (nullptr == mysql) {
    std::cerr << "init failed" << std::endl;
    return -1;
  }
  std::cout << "init successed" << std::endl; // 通常不会失败

  /*连接数据库*/
  if (!mysql_real_connect(mysql, dbInfo::_host, dbInfo::_username,
                          dbInfo::_passwd, dbInfo::_dbname, dbInfo::_port,
                          nullptr, 0)) {

    std::cerr << "connect failed" << std::endl;
    return -2;
  }
  std::cout << "connect successed" << std::endl;

  /*执行语句*/
  // 单sql语句(select)
  std::string sql = "select * from test_tb";
  int n = mysql_query(mysql, sql.c_str());
  if (n) {
    std::cerr << sql << " failed: " << n << std::endl;
    return -3;
  } else {
    std::cout << sql << " successed: " << n << std::endl;
  }

  /*获取查询结果集*/
  MYSQL_RES *sel_res = mysql_store_result(mysql);

  // 获取行列信息
  uint64_t rows = mysql_num_rows(sel_res);
  unsigned int fields = mysql_num_fields(sel_res);
  std::cout << "rows: " << rows << "  fields: " << fields
            << std::endl; // 打印行列结果

  // 遍历结果集 获取结果集内容
  MYSQL_ROW row;
  for (int curi = 0; curi < rows; ++curi) {
    row = mysql_fetch_row(sel_res);
    for (int curj = 0; curj < fields; ++curj) {
      std::cout << (row[curj] ? row[curj] : "nullptr") << "\t";
    }
    std::cout << std::endl;
  }

  // 遍历结果集 获取列名称
  for (int curi = 0; curi < fields; ++curi) {
    // 方法1
    MYSQL_FIELD *field = mysql_fetch_field(sel_res);
    std::cout << (field ? field->name : "nullptr") << "\t";
  }
  std::cout << std::endl;

    // 方法2
  MYSQL_FIELD *_fields = mysql_fetch_fields(sel_res); 
  for (int curi = 0; curi < fields; ++curi) {
    std::cout << (_fields ? _fields[curi].name : "nullptr") << "\t";
  }
  std::cout << std::endl;
    
  /*断开连接并释放内存*/
  mysql_close(mysql);
  return 0;
}

这段代码中两种方式都使用了, 值得注意的是, 代码中在进行遍历打印时做了一个小改动, 采用三目操作符判断是否为空, 防止打印时访问到无效地址造成段错误;

运行结果为:

$ ./test 
init successed
connect successed
select * from test_tb successed: 0
rows: 5  fields: 3
2       LiSi    222222
3       zhangsan        111111
4       wangwu  333333
5       zhaoliu 444444
6       张三    666666
id      name    telnum
id      name    telnum

结果被成功打印;


2.4.5 释放结果集内存

在上述代码中, 有一个最重要的内容没有进行操作, 即释放结果集MYSQL_RES的内存空间;

作为一个直接通过API调用产生的对象, 当然不会希望用户通过手动free或者delete进行释放;

因此mysql C - API提供了一个接口用于释放结果集内存空间;

当结果集使用完后, 必须通过这种方式来释放结果集的内存空间以避免内存泄漏以及其他错误问题;

void
mysql_free_result(MYSQL_RES *result)

该函数没有返回值, 而当查询结束时, 调用这个函数释放内存才是一个真正的流程结束;

#include <iostream>

#include <mysql/mysql.h> // 头文件引入, 也可以直接include <mysql.h>, 但是需要 -I来找到所在路径

#include <string>

namespace dbInfo {
const char *_host = "localhost"; // 也可以为 127.0.0.1 环回地址
const char *_username = "connector";
const char *_passwd = "passw02d";
const char *_dbname = "ConnectDB";
const int _port = 3306;
}; // namespace dbInfo

int main() {
  /*初始化mysql*/
  MYSQL *mysql = mysql_init(nullptr);
  if (nullptr == mysql) {
    std::cerr << "init failed" << std::endl;
    return -1;
  }
  std::cout << "init successed" << std::endl; // 通常不会失败

  /*连接数据库*/
  if (!mysql_real_connect(mysql, dbInfo::_host, dbInfo::_username,
                          dbInfo::_passwd, dbInfo::_dbname, dbInfo::_port,
                          nullptr, 0)) {

    std::cerr << "connect failed" << std::endl;
    return -2;
  }
  std::cout << "connect successed" << std::endl;

  /*执行语句*/
  // 单sql语句(select)
  std::string sql = "select * from test_tb";
  int n = mysql_query(mysql, sql.c_str());
  if (n) {
    std::cerr << sql << " failed: " << n << std::endl;
    return -3;
  } else {
    std::cout << sql << " successed: " << n << std::endl;
  }

  /*获取查询结果集*/
  MYSQL_RES *sel_res = mysql_store_result(mysql);

  // 获取行列信息
  uint64_t rows = mysql_num_rows(sel_res);
  unsigned int fields = mysql_num_fields(sel_res);
  std::cout << "rows: " << rows << "  fields: " << fields
            << std::endl; // 打印行列结果

  // 遍历结果集 获取结果集内容
  MYSQL_ROW row;
  for (int curi = 0; curi < rows; ++curi) {
    row = mysql_fetch_row(sel_res);
    for (int curj = 0; curj < fields; ++curj) {
      std::cout << (row[curj] ? row[curj] : "nullptr") << "\t";
    }
    std::cout << std::endl;
  }

  // 遍历结果集 获取列名称
  for (int curi = 0; curi < fields; ++curi) {
    // 方法1
    MYSQL_FIELD *field = mysql_fetch_field(sel_res);
    std::cout << (field ? field->name : "nullptr") << "\t";
  }
  std::cout << std::endl;

    // 方法2
  MYSQL_FIELD *_fields = mysql_fetch_fields(sel_res); 
  for (int curi = 0; curi < fields; ++curi) {
    std::cout << (_fields ? _fields[curi].name : "nullptr") << "\t";
  }
  std::cout << std::endl;
  
  /*释放结果集内存*/
  mysql_free_result(sel_res);
    
  /*断开连接并释放内存*/
  mysql_close(mysql);
  return 0;
}

2.5 数据库的接口文档

关于C - API的接口文档链接 [Here!!!];


3 使用图形化界面进行连接

图形化界面连接的方式很多种, 主要通过第三方的图形化客户端对数据进行连接, 这些客户端包括但不限于Navicat, MySQL ODBC Connector(官方提供客户端), MySQL-Front;

由于使用都较为简单, 此处不进行解释;


标题:『MySQL』C++ MySQL C-API 开发入门
作者:orion
地址:http://orionpeng.top/articles/2025/11/30/1764478680538.html

评论

发表评论


取消