#MySQL在C++中的基本`api`讲解

2024-05-30 12:49:16 浏览数 (1)

  • 一、创建驱动程序实例
  • 二、连接服务器
    • 为什么使用`tcp://`
    • 不使用`tcp://`会怎样?
    • 其他协议示例
    • 连接到具体的数据库
  • 创建SQL语句
    • Statement
    • `PreparedStatement`
    • 执行时机
  • 处理结果
      • 1. 遍历结果集
      • 2. 获取列值
      • 3. 检查结果集是否为空

​ 在上篇文章中我介绍了MySQL在C语言中的基本 api,虽然只是基本的接口,但是我们依旧可以发现有这许多问题,比如,创建对象后必须手动释放,查询结果后必须手动释放否则就会有大量的内存泄漏问题出现,当然在C语言中对于MySQL多线程的把握,需要大量的锁去实现,这不仅提高代码的复杂程度,更是进一步的把后续的维护成本大大提升。

而回看C 的三大特性,封装、继承、多态,无论是其中蕴含的RAII,对于锁的更加灵1活的使用,还是衍生出来的设计模式(如:单例模式)和池化技术,以及后对于异常的处理的都简化了代码的编写。

本文将提供一个简单的demo代码,并逐步解释其中的含义,带你快速上手基本的api

首先,确保你已经安装了MySQL Connector/C 库。可以从MySQL官网下载安装。

代码语言:javascript复制
#include <mysql_driver.h>
#include <mysql_connection.h>
#include <cppconn/statement.h>
#include <cppconn/resultset.h>
#include <cppconn/exception.h>
#include <iostream>

int main() {
    try {
        // 创建驱动程序实例
        sql::mysql::MySQL_Driver* driver = sql::mysql::get_mysql_driver_instance();

        // 通过驱动程序创建连接
        std::unique_ptr<sql::Connection> conn(driver->connect("tcp://127.0.0.1:3306", "username", "password"));

        // 连接到具体的数据库
        conn->setSchema("test_db");

        // 创建语句对象
        std::unique_ptr<sql::Statement> stmt(conn->createStatement());

        // 执行查询并获取结果集
        std::unique_ptr<sql::ResultSet> res(stmt->executeQuery("SELECT id, name FROM test_table"));

        // 遍历结果集并输出结果
        while (res->next()) {
            std::cout << "ID: " << res->getInt("id");
            std::cout << ", Name: " << res->getString("name") << std::endl;
        }
    } catch (sql::SQLException& e) {
        std::cerr << "SQLException: " << e.what() << std::endl;
        std::cerr << "SQLState: " << e.getSQLState() << std::endl;
    }

    return 0;
}

一、创建驱动程序实例

创建驱动程序实例是使用MySQL Connector/C 库与MySQL数据库进行交互的第一步。这一步骤是通过调用get_mysql_driver_instance方法来实现的。其本质是用于获取MySQL_Driver类的单例实例。这个方法确保在整个程序中只存在一个驱动程序实例。

代码语言:javascript复制
sql::mysql::MySQL_Driver* driver = sql::mysql::get_mysql_driver_instance();`

其中1、MySQL Connector/C 库使用了一些命名空间来组织其类和函数。sql::mysql命名空间包含了专门用于MySQL数据库的类和函数。 2、MySQL_Driver类是MySQL Connector/C 库的一个核心类,它实现了与MySQL数据库的连接管理。这个类的实例负责创建和管理与MySQL服务器的连接。

执行过程

  1. 调用get_mysql_driver_instance:
  • 当你调用sql::mysql::get_mysql_driver_instance()时,该方法会检查是否已经存在一个MySQL_Driver实例。
  • 如果不存在,它会创建一个新的实例。
  • 如果已经存在,它会返回现有的实例。
  1. 返回驱动程序实例:
  • 该方法返回一个指向MySQL_Driver实例的指针。

为什么需要驱动程序实例 驱动程序实例是与MySQL数据库通信的核心组件。通过这个实例,你可以:

  • 创建与数据库服务器的连接。
  • 执行SQL查询和命令。
  • 管理连接池和其他底层细节。

二、连接服务器

代码语言:javascript复制
std::unique_ptr<sql::Connection> conn(driver->connect("tcp://127.0.0.1:3306", "username", "password"));
conn1->setSchema("test_db1");

这里我主要要讲一下这里的第一个参数,这个字符串由三部分组成:

  • protocol:通信协议。对于MySQL数据库,通常使用tcpsocket
  • host:数据库服务器的主机名或IP地址。
  • port:数据库服务器监听的端口号。

在这个例子中:

  • tcp:表示使用TCP/IP协议进行连接。
  • 127.0.0.1:表示连接到本地主机(localhost)。
  • 3306:MySQL数据库默认的端口号。
  • “username”:数据库的用户名。
  • “password”:数据库的密码。
为什么使用tcp://
  1. 明确通信协议:通过指定tcp://,明确告知驱动程序使用TCP/IP协议进行连接。这在需要明确区分连接方式时非常有用。例如,如果数据库服务器在本地,并且你想通过Unix域套接字(socket)连接而不是TCP/IP,可以使用socket://
  2. 灵活性和兼容性:使用标准的URL格式,可以灵活地切换不同的协议和地址,适应不同的部署环境和需求。
不使用tcp://会怎样?

如果你省略tcp://,通常默认会使用TCP/IP协议,但明确指定协议更为严谨,特别是在配置和调试数据库连接时。某些驱动程序和配置环境可能要求明确指定协议,以避免歧义或连接错误。

其他协议示例
  • socket://:用于通过Unix域套接字连接到MySQL数据库(仅适用于Unix/Linux系统)。
代码语言:javascript复制
std::unique_ptr<sql::Connection> conn(driver->connect("socket:///path/to/socket", "username", "password"));
  • 普通连接(不指定协议):有些情况下可以省略协议前缀,依赖默认设置。
代码语言:javascript复制
std::unique_ptr<sql::Connection> conn(driver->connect("127.0.0.1:3306", "username", "password"));

省略协议前缀通常也会使用TCP/IP协议,但明确指定协议更加严谨和可读。

连接到具体的数据库

使用创建的连接对象的 setSchema 方法选择具体的数据库。

代码语言:javascript复制
conn1->setSchema("test_db1");

注意每个连接都是独立的,可以连接到不同的数据库实例或同一数据库实例下的不同数据库。

创建SQL语句

在C 的apisql语句分为PreparedStatement和不带参数的Statement,他们两者是有一定差别的

Statement

Statement 对象主要用于执行静态的、不带参数的 SQL 语句,例如 SELECTINSERTUPDATEDELETE。它适合用来执行那些不需要动态参数的简单 SQL 语句,其中的值是固定的,不会根据不同的输入而改变。Statement 对象的使用可以简化代码,但它不如 PreparedStatement 安全,因为不提供防止 SQL 注入的保护。

代码语言:javascript复制
std::unique_ptr<sql::Statement> stmt(conn->createStatement());
std::unique_ptr<sql::ResultSet> res(stmt->executeQuery("SELECT id, name FROM test_table"));

在上面的demo中我们发现使用 Statement 对象时,执行 SQL 查询和获取结果是一步完成的。你需要在调用 executeQueryexecuteUpdate 等方法时传入 SQL 语句,并且方法会立即执行该语句并返回结果。

PreparedStatement

PreparedStatement主要用于参数化查询重复执行相同查询执行批量操作 等场景

代码语言:javascript复制
// 创建 PreparedStatement 对象,并绑定 SQL 语句
std::unique_ptr<sql::PreparedStatement> pstmt(conn->prepareStatement("SELECT id, name FROM test_table WHERE id = ?"));

// 第一次设置参数并执行查询
pstmt->setInt(1, 1); // 第一个参数位置,值为1
std::unique_ptr<sql::ResultSet> res1(pstmt->executeQuery());

PreparedStatement给人的感觉是像是封装了一个函数然后通过用一些set…函数经行‘传参’改变这个语句中的占位符中的字母,实现多种查询,每次查询是将占位符经行改变,而不是重新输入一个SQL语句。这样的函数有

代码语言:javascript复制
setInt(n, 1):
设置第n个占位符(?)为整数值1。
setString(n, "Alice"):
设置第n个占位符(?)为字符串值"Alice"。
setInt(n, 25):
设置第n个占位符(?)为整数值25。
setDouble(n, 50000.50):
设置第n个占位符(?)为双精度浮点数值50000.50。
setBoolean(n, true):
设置第n个占位符(?)为布尔值true。
执行时机

  • 当调用 executeQueryexecuteUpdateexecute 方法时,SQL 语句被发送到数据库服务器并实际执行。
  • executeQuery 用于 SELECT 语句,返回一个 ResultSet 对象用于遍历查询结果。
  • executeUpdate 用于 INSERTUPDATEDELETE 等语句,返回受影响的行数。
  • execute 是一个通用方法,可以执行任何 SQL 语句,并需要根据返回结果进一步处理。

处理结果

上面我们提到在执行sql语句时会用sql::ResultSet 类型将结果封存,所以处理结果的过程,就是遍历sql::ResultSet获取值的过程。

以下是一些处理结果集的基本操作:

1. 遍历结果集

通过 next() 方法遍历结果集中的每一行:

代码语言:javascript复制
while (res->next()) {
    int id = res->getInt("id");
    std::string name = res->getString("name");
    std::cout << "ID: " << id << ", Name: " << name << std::endl;
}

可以看到->next()在单个方法调用中合并了“移动到下一个元素”和“检查是否存在更多元素”这两个操作。这种设计使得遍历结果集变得简单和高效。

2. 获取列值

通过列名或列索引来获取列值:

代码语言:javascript复制
int id = res->getInt("id"); // 使用列名
std::string name = res->getString("name");

int id = res->getInt(1); // 使用列索引(从 1 开始)
std::string name = res->getString(2);
3. 检查结果集是否为空

在遍历之前可以检查结果集是否为空:

代码语言:javascript复制
if (!res->next()) {
    std::cout << "No data found." << std::endl;
} else {
    // 重置游标到第一行
    res->beforeFirst();
    while (res->next()) {
        int id = res->getInt("id");
        std::string name = res->getString("name");
        std::cout << "ID: " << id << ", Name: " << name << std::endl;
    }
}

0 人点赞