MySQL Connector C++ 9.2 使用方法

1,545 阅读10分钟

MySQL Connector C++ 9.2 中文文档

第六章 Connector/C++ 源码包中的示例目录

Connector/C++ 的源码发行版包含一个 examples 目录,其中提供了以下核心类的使用示例:

  • 6.1 Connection
  • 6.2 Driver
  • 6.3 PreparedStatement
  • 6.4 ResultSet
  • 6.5 ResultSetMetaData
  • 6.6 Statement

示例涵盖的主题

  1. 使用 Driver 类连接 MySQL
  2. 通过普通语句(Statement)创建表、插入数据、查询数据
  3. 通过预处理语句(PreparedStatement)创建表、插入数据、查询数据
  4. 绕过预处理语句限制的技巧
  5. 访问结果集元数据(ResultSetMetaData

注意

  • 本文档中的部分示例为代码片段,非完整程序。完整示例请查看 Connector/C++ 安装目录下的 examples 目录。
  • 测试前需阅读 examples 目录中的 README 文件,并编辑 examples.h 文件配置连接信息(如主机、用户名、密码),然后通过 make 命令重新编译代码

示例程序列表

  1. connect.cpp
    • 功能:创建数据库连接、插入数据、处理异常。
  2. connection_meta_schemaobj.cpp
    • 功能:获取连接对象的元数据,如数据库/表列表、MySQL 版本、驱动版本。
  3. debug_output.cpp
    • 功能:启用或禁用 Connector/C++ 的调试协议。
  4. exceptions.cpp
    • 功能:深入解析驱动抛出的异常类型及错误信息获取方法。
  5. prepared_statements.cpp
    • 功能:执行预处理语句,包括处理 MySQL 服务器不支持的预处理 SQL 语句的示例。
  6. resultset.cpp
    • 功能:使用游标遍历结果集并获取数据。
  7. resultset_meta.cpp
    • 功能:获取结果集的元数据,如列数量、列类型。
  8. resultset_types.cpp
    • 功能:展示从元数据方法返回的结果集(更偏向测试而非示例)。
  9. standalone_example.cpp
    • 功能:一个未集成到常规 CMake 构建中的独立简单程序。
  10. statements.cpp
    • 功能:通过普通语句(非预处理语句)执行 SQL。
  11. cpp_trace_analyzer.cpp
    • 功能:过滤调试跟踪输出的示例。注意:此脚本不受官方支持,详细用法请查看代码内注释。

6.1 连接到 MySQL

要建立与 MySQL 服务器的连接,需通过 sql::mysql::MySQL_Driver 对象获取 sql::Connection 实例。sql::mysql::MySQL_Driver 对象由 sql::mysql::get_mysql_driver_instance() 返回。

sql::mysql::MySQL_Driver *driver;
sql::Connection *con;

driver = sql::mysql::get_mysql_driver_instance();
con = driver->connect("tcp://127.0.0.1:3306", "user", "password");

delete con;

注意事项

  1. 资源管理:确保在不再需要sql::Connection对象(即con)时立即释放它。但不要显式释放驱动对象driver,Connector/C++ 会自动管理并释放。
  2. 线程安全get_mysql_driver_instance()内部调用get_driver_instance(),此方法非线程安全。若需在多线程中调用,需通过互斥锁(mutex)防止并发访问。

连接状态检查与重连

提供sql::Connection::isValid()用于连接状态管理,检查连接是否有效(存活)。此方法通过轻量级查询SELECT 或数据库驱动的内部机制快速验证连接。sql::Connection::reconnect():若连接已断开,尝试重新建立连接。适用于网络波动或服务端主动断开的情况。

连接参数扩展

更多连接选项(如超时配置、字符集、SSL 等)可参考《Connector/C++ 连接选项》章节。例如:

// 示例:设置连接超时为 5 秒
con->setClientOption("OPT_CONNECT_TIMEOUT", "5");

关键提示

  • 保活机制:若需长时间保持空闲连接,建议通过定时执行简单查询(如SELECT )或启用连接池的心跳检测(如HikariCPconnectionTestQuery)避免服务端因wait_timeout断开连接。

  • 错误处理:连接失败时,捕获sql::SQLException异常以获取详细错误信息(如错误码、状态码及错误描述)。

扩展阅读

  • 连接池实现:单例模式下的连接管理需谨慎处理资源释放,避免内存泄漏(参考shared_ptr或连接池库如HikariCP)。
  • 预处理语句:建议优先使用PreparedStatement提升性能并防止 SQL 注入。

6.2 执行简单查询

在 Connector/C++ 中,可通过 sql::Statement 的以下方法执行简单查询:

  • execute():适用于不返回结果集(如 DDL 语句)或可能返回多个结果集/更新计数的复杂 SQL。
  • executeQuery():专用于 SELECT 查询,返回 sql::ResultSet 对象以遍历数据。
  • executeUpdate():用于 INSERT、UPDATE、DELETE 等修改数据的操作,返回受影响的行数。

代码示例

sql::mysql::MySQL_Driver *driver;  
sql::Connection *con;  
sql::Statement *stmt;  

// 初始化驱动并建立连接  
driver = sql::mysql::get_mysql_driver_instance();  
con = driver->connect(
    "tcp://127.0.0.1:3306", "user", "password");  

// 创建 Statement 对象  
stmt = con->createStatement();  

// 执行 SQL 语句  
stmt->execute("USE " EXAMPLE_DB);          // 选择数据库  
stmt->execute("DROP TABLE IF EXISTS test"); // 删除表(DDL)  
stmt->execute("CREATE TABLE test(id INT, label CHAR(1))"); // 创建表(DDL)  
stmt->executeUpdate(
    "INSERT INTO test(id, label) VALUES (1, 'a')"); // 插入数据(DML)  

// 显式释放资源  
delete stmt;  
delete con;  

关键注意事项

  1. 资源释放:必须显式调用 delete 释放 sql::Statementsql::Connection 对象,避免内存泄漏。
  2. SQL方法选择
    • 执行 查询(如 SELECT)时优先使用 executeQuery()
    • 执行 数据修改(如 INSERT)时使用 executeUpdate(),其返回值表示受影响的行数。
    • execute() 主要用于动态 SQL 或处理未知类型的语句。
  3. 线程安全get_mysql_driver_instance() 非线程安全,多线程环境需加锁。

补充说明

  • DDL 与 DML 区别
    • DDL(数据定义语言)如 CREATE TABLE,不返回结果集,建议使用 execute()
    • DML(数据操作语言)如 INSERT,建议使用 executeUpdate() 以获取操作影响的行数。
  • 错误处理:实际开发中应捕获 sql::SQLException 异常,以处理连接或执行失败的情况。

6.3 获取结果集

(简单)语句(Statement)和预处理语句(PreparedStatement)获取结果集的 API 是相同的。若查询返回单一结果集,可使用 sql::Statement::executeQuery()sql::PreparedStatement::executeQuery() 执行查询,这两个方法均返回 sql::ResultSet 对象。默认情况下,Connector/C++ 会在客户端缓冲所有结果集以支持游标操作

// ...  
sql::Connection *con;  
sql::Statement *stmt;  
sql::ResultSet  *res;  
// ...  

// 创建 Statement 对象  
stmt = con->createStatement();  

// 执行查询并获取结果集  
res = stmt->executeQuery(
    "SELECT id, label FROM test ORDER BY id ASC");  

// 遍历结果集  
while (res->next()) {  
    // 方式 1:使用数字索引(从 1 开始)  
    cout << "id = " << res->getInt(1); // getInt(1) 获取第一列的值  
    // 方式 2:使用列名(推荐)  
    cout << ", label = '" << res->getString("label") << "'" << endl;  
}  

// 显式释放资源  
delete res;  
delete stmt;  
delete con;  

关键注意事项

  1. 列索引从 1 开始:例如,第一列通过 res->getInt(1) 访问。
  2. 资源释放:必须显式调用 delete 释放 sql::Statementsql::Connectionsql::ResultSet 对象,避免内存泄漏。
  3. 游标使用:完整示例可参考 Connector/C++ 安装包中的示例代码。

扩展说明

  • 结果集遍历
    • ResultSet::next() 将游标移动到下一行,返回 true 表示仍有数据,false 表示遍历完成。
    • 数据类型转换:根据列类型选择对应方法(如 getInt()getString())。若类型不匹配会抛出异常。
  • 客户端缓冲
    • 默认行为会将所有结果集加载到客户端内存,支持双向游标(可回滚)。
    • 若处理大数据集,建议通过 setResultSetType() 设置流式结果集(sql::ResultSet::TYPE_FORWARD_ONLY)减少内存占用。

示例:流式结果集配置

stmt = con->createStatement();  
stmt->setResultSetType(sql::ResultSet::TYPE_FORWARD_ONLY);  // 设置为仅向前游标  
res = stmt->executeQuery("SELECT * FROM large_table");  

错误处理:实际开发中需捕获 sql::SQLException 异常,例如:

try {  
    res = stmt->executeQuery("SELECT ...");  
    // ...  
} catch (sql::SQLException &e) {  
    cerr << "SQL Error: " << e.what() << ", Code: " << e.getErrorCode() << endl;  
}  

6.4 使用预处理语句

如果您对 MySQL 中的预处理语句不熟悉,可以查看 examples/prepared_statement.cpp 文件中的源代码注释和解释。

sql::PreparedStatement 通过将 SQL 查询传递给 sql::Connection::prepareStatement() 来创建。由于 sql::PreparedStatement 继承自 sql::Statement,因此一旦您学会了如何使用普通语句(sql::Statement),您会对该 API 感到熟悉。例如,获取结果的语法是相同的。

// ...  
sql::Connection *con;  
sql::PreparedStatement  *prep_stmt;  
// ...  

// 创建预处理语句  
prep_stmt = con->prepareStatement("INSERT INTO test(id, label) VALUES (?, ?)");  

// 设置参数并执行  
prep_stmt->setInt(1, 1);  
prep_stmt->setString(2, "a");  
prep_stmt->execute();  

prep_stmt->setInt(1, 2);  
prep_stmt->setString(2, "b");  
prep_stmt->execute();  

// 显式释放资源  
delete prep_stmt;  
delete con;  

注意事项

  1. 资源释放:必须显式调用 delete 释放 sql::PreparedStatementsql::Connection 对象,避免内存泄漏。
  2. 参数化查询:预处理语句通过占位符 ? 实现参数化查询,防止 SQL 注入并提高执行效率。
  3. 线程安全get_mysql_driver_instance() 非线程安全,多线程环境需加锁。

扩展说明

  • 参数设置
    • 使用 setInt()setString() 等方法为占位符赋值,索引从 1 开始。
    • 支持多种数据类型(如 setDouble()setDate() 等)。
  • 执行方法
    • execute():用于执行不返回结果集的 SQL 语句(如 DDL)。
    • executeQuery():用于执行返回结果集的查询(如 SELECT)。
    • executeUpdate():用于执行修改数据的操作(如 INSERTUPDATE),返回受影响的行数。

6.5 完整示例1

/* Standard C++ includes */
#include <stdlib.h>
#include <iostream>

/*
  Include directly the different
  headers from cppconn/ and mysql_driver.h + mysql_util.h
  (and mysql_connection.h). This will reduce your build time!
*/
#include "mysql_connection.h"

#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>

using namespace std;

int main(void)
{
    cout << endl;
    cout << "Running 'SELECT 'Hello World!' »
       AS _message'..." << endl;

    try {
      sql::Driver *driver;
      sql::Connection *con;
      sql::Statement *stmt;
      sql::ResultSet *res;

      /* Create a connection */
      driver = get_driver_instance();
      con = driver->connect(
          "tcp://127.0.0.1:3306", "root", "root");
      /* Connect to the MySQL test database */
      con->setSchema("test");

      stmt = con->createStatement();
      res = stmt->executeQuery(
          "SELECT 'Hello World!' AS _message");
        
      while (res->next()) {
        cout << "\t... MySQL replies: ";
        /* Access column data by alias or column name */
        cout << res->getString("_message") << endl;
        cout << "\t... MySQL says it again: ";
        /* Access column data by numeric offset, 1 is the first column */
        cout << res->getString(1) << endl;
      }
        
      delete res;
      delete stmt;
      delete con;

    } catch (sql::SQLException &e) {
          cout << "# ERR: SQLException in " << __FILE__;
          cout << "(" << __FUNCTION__ << ") on line " »
             << __LINE__ << endl;
          cout << "# ERR: " << e.what();
          cout << " (MySQL error code: " << e.getErrorCode();
          cout << ", SQLState: " << e.getSQLState() << " )" << endl;
    }

    cout << endl;

    return EXIT_SUCCESS;
}

6.6 完整示例2

/* Standard C++ includes */
#include <stdlib.h>
#include <iostream>

/*
  Include directly the different
  headers from cppconn/ and mysql_driver.h + mysql_util.h
  (and mysql_connection.h). This will reduce your build time!
*/
#include "mysql_connection.h"

#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>
#include <cppconn/prepared_statement.h>

using namespace std;

int main(void)
{
    cout << endl;
    cout << "Let's have MySQL count from 10 to 1..." << endl;

    try {
      sql::Driver *driver;
      sql::Connection *con;
      sql::Statement *stmt;
      sql::ResultSet *res;
      sql::PreparedStatement *pstmt;

      /* Create a connection */
      driver = get_driver_instance();
      con = driver->connect(
          "tcp://127.0.0.1:3306", "root", "root");
      /* Connect to the MySQL test database */
      con->setSchema("test");

      stmt = con->createStatement();
      stmt->execute("DROP TABLE IF EXISTS test");
      stmt->execute("CREATE TABLE test(id INT)");
      delete stmt;

      /* '?' is the supported placeholder syntax */
      pstmt = con->prepareStatement(
          "INSERT INTO test(id) VALUES (?)");
      for (int i = 1; i <= 10; i++) {
        pstmt->setInt(1, i);
        pstmt->executeUpdate();
      }
      delete pstmt;

      /* Select in ascending order */
      pstmt = con->prepareStatement(
          "SELECT id FROM test ORDER BY id ASC");
      res = pstmt->executeQuery();

      /* Fetch in reverse = descending order! */
      res->afterLast();
      while (res->previous())
        cout << "\t... MySQL counts: " << res->getInt("id") << endl;
      delete res;

      delete pstmt;
      delete con;

    } catch (sql::SQLException &e) {
          cout << "# ERR: SQLException in " << __FILE__;
          cout << "(" << __FUNCTION__ << ") on line " »
             << __LINE__ << endl;
          cout << "# ERR: " << e.what();
          cout << " (MySQL error code: " << e.getErrorCode();
          cout << ", SQLState: " << e.getSQLState() << »
             " )" << endl;
    }

    cout << endl;

    return EXIT_SUCCESS;
}

6.7 连接到密码过期的账户

MySQL 支持密码过期功能,如《密码管理》章节所述。如果客户端应用程序使用密码已过期的账户连接 MySQL 用户,客户端功能将受到限制,直到重置账户密码为止;更多信息请参阅《服务器处理过期密码》章节。

Connector/C++ 应用程序可以通过 OPT_CAN_HANDLE_EXPIRED_PASSWORDSpreInit 连接选项重置过期密码,这些选项在《Connector/C++ 连接选项》第 10 章中有详细描述。启用 OPT_CAN_HANDLE_EXPIRED_PASSWORDS 选项,并将设置密码的 ALTER USER 语句作为 preInit 选项的值:

opts["OPT_CAN_HANDLE_EXPIRED_PASSWORDS"] = true;
opts["preInit"] = sql::SQLString("ALTER USER 'user' IDENTIFIED BY 'new-pwd';");

ALTER USER 语句在连接后立即设置新密码。使用这些选项连接后,新密码应已生效,会话可以正常使用。任何新会话都必须使用新密码,但不再需要 OPT_CAN_HANDLE_EXPIRED_PASSWORDSpreInit 选项。


扩展说明

  1. 密码过期机制

    • MySQL 允许通过 ALTER USER 语句手动设置密码过期,或通过全局变量 default_password_lifetime 自动管理密码过期。
    • 密码过期后,客户端只能执行重置密码的操作,其他操作将被限制。
  2. Connector/C++ 处理过期密码

    • 通过 OPT_CAN_HANDLE_EXPIRED_PASSWORDSpreInit 选项,Connector/C++ 可以在连接时自动重置密码,避免手动干预。
  3. 注意事项

    • 重置密码后,确保所有新会话使用新密码。
    • 避免将过期密码重置为当前值,建议设置一个全新的密码以提高安全性。