- 一、创建驱动程序实例
- 二、连接服务器
- 为什么使用`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
类的单例实例。这个方法确保在整个程序中只存在一个驱动程序实例。
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服务器的连接。执行过程
- 调用
get_mysql_driver_instance
:
- 当你调用
sql::mysql::get_mysql_driver_instance()
时,该方法会检查是否已经存在一个MySQL_Driver
实例。 - 如果不存在,它会创建一个新的实例。
- 如果已经存在,它会返回现有的实例。
- 返回驱动程序实例:
- 该方法返回一个指向
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数据库,通常使用
tcp
或socket
。 - host:数据库服务器的主机名或IP地址。
- port:数据库服务器监听的端口号。
在这个例子中:
- tcp:表示使用TCP/IP协议进行连接。
- 127.0.0.1:表示连接到本地主机(localhost)。
- 3306:MySQL数据库默认的端口号。
- “username”:数据库的用户名。
- “password”:数据库的密码。
为什么使用tcp://
- 明确通信协议:通过指定
tcp://
,明确告知驱动程序使用TCP/IP协议进行连接。这在需要明确区分连接方式时非常有用。例如,如果数据库服务器在本地,并且你想通过Unix域套接字(socket)连接而不是TCP/IP,可以使用socket://
。 - 灵活性和兼容性:使用标准的URL格式,可以灵活地切换不同的协议和地址,适应不同的部署环境和需求。
不使用tcp://
会怎样?
如果你省略tcp://
,通常默认会使用TCP/IP协议,但明确指定协议更为严谨,特别是在配置和调试数据库连接时。某些驱动程序和配置环境可能要求明确指定协议,以避免歧义或连接错误。
其他协议示例
- socket://:用于通过Unix域套接字连接到MySQL数据库(仅适用于Unix/Linux系统)。
std::unique_ptr<sql::Connection> conn(driver->connect("socket:///path/to/socket", "username", "password"));
- 普通连接(不指定协议):有些情况下可以省略协议前缀,依赖默认设置。
std::unique_ptr<sql::Connection> conn(driver->connect("127.0.0.1:3306", "username", "password"));
省略协议前缀通常也会使用TCP/IP协议,但明确指定协议更加严谨和可读。
连接到具体的数据库
使用创建的连接对象的 setSchema
方法选择具体的数据库。
conn1->setSchema("test_db1");
注意每个连接都是独立的,可以连接到不同的数据库实例或同一数据库实例下的不同数据库。
创建SQL语句
在C 的api
中sql
语句分为PreparedStatement
和不带参数的Statement
,他们两者是有一定差别的
Statement
Statement
对象主要用于执行静态的、不带参数的 SQL 语句,例如 SELECT
、INSERT
、UPDATE
和 DELETE
。它适合用来执行那些不需要动态参数的简单 SQL 语句,其中的值是固定的,不会根据不同的输入而改变。Statement
对象的使用可以简化代码,但它不如 PreparedStatement
安全,因为不提供防止 SQL 注入的保护。
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 查询和获取结果是一步完成的。你需要在调用 executeQuery
、executeUpdate
等方法时传入 SQL 语句,并且方法会立即执行该语句并返回结果。
PreparedStatement
PreparedStatement
主要用于参数化查询、重复执行相同查询、执行批量操作 等场景
// 创建 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。
执行时机
- 当调用
executeQuery
、executeUpdate
或execute
方法时,SQL 语句被发送到数据库服务器并实际执行。 executeQuery
用于SELECT
语句,返回一个ResultSet
对象用于遍历查询结果。executeUpdate
用于INSERT
、UPDATE
、DELETE
等语句,返回受影响的行数。execute
是一个通用方法,可以执行任何 SQL 语句,并需要根据返回结果进一步处理。
处理结果
上面我们提到在执行sql
语句时会用sql::ResultSet
类型将结果封存,所以处理结果的过程,就是遍历sql::ResultSet
获取值的过程。
以下是一些处理结果集的基本操作:
1. 遍历结果集
通过 next()
方法遍历结果集中的每一行:
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;
}
}