数据库编程:MySQL Connector/C++

【链接】https://github.com/mysql/mysql-connector-cpp

MySQL Connector/C++ 是 MySQL 提供的一个驱动程序,用于通过 C++ 语言连接和操作 MySQL 数据库。它允许开发者使用标准的 SQL 语法与数据库进行交互,并支持 MySQL 的特性如预处理语句、事务等, 支持 X DevAPI 和传统 JDBC 风格的 API。

主要特点

  • 兼容性:支持多种 C++ 标准,包括 C++11 及其后续版本。
  • 易于使用:提供了一套面向对象的 API,使得编写数据库应用程序变得更加简单。
  • 高性能:优化了性能,确保高效的数据传输和处理。
  • 支持 X DevAPI:除了传统的 JDBC 风格的 API 外,还支持现代的 X DevAPI,用于 NoSQL 和关系型数据模型。

安装与配置

1. 下载与安装

2. 安装方式

  • Windows:使用 MSI 安装程序

  • Linux:使用 RPM/DEB 包或源码编译

  • macOS:使用 DMG 包或 Homebrew

3. 开发环境配置(VS2017)

包含目录

C:\Program Files\MySQL\mysql-connector-c++-9.3.0-winx64\include

库目录

C:\Program Files\MySQL\mysql-connector-c++-9.3.0-winx64\lib64\vs14

附加依赖项

mysqlcppconn.lib;mysqlcppconnx.lib;

X DevAPI 使用示例(现代API)

X DevAPI 是 MySQL 提供的一个现代化的 API,旨在支持 NoSQL 和关系型数据库模型。它不仅允许开发者以传统的方式与 MySQL 数据库交互,还提供了一种更加灵活和高效的方式来操作数据,特别是在处理 JSON 文档时表现尤为突出。X DevAPI 主要通过 MySQL Connector/Node.js、MySQL Shell 以及 MySQL Connector/C++ 等工具来使用。

X DevAPI 的核心特性

  1. 文档存储支持:X DevAPI 引入了对文档存储的支持,允许用户像在 NoSQL 数据库中一样存储和查询 JSON 文档。这使得 MySQL 不仅可以作为传统的关系型数据库使用,还可以作为一个强大的文档数据库。

  2. 面向对象的设计:X DevAPI 提供了一个更加直观的对象模型来表示数据库资源,如 Schema、Collection 和 Table 等。这些对象使开发者能够以更自然的方式与数据库进行交互。

  3. CRUD 操作简化:对于集合(Collections)中的文档或表(Tables)中的行,X DevAPI 提供了简洁的方法来进行创建(Create)、读取(Retrieve)、更新(Update)和删除(Delete)操作。例如,可以直接使用 add() 方法向集合添加文档,使用 find() 方法检索文档等。

  4. 表达式语言:X DevAPI 支持一种用于构建查询条件的表达式语言,该语言可以用来定义复杂的查询条件和排序规则,从而实现更精确的数据检索。

  5. 事务支持:尽管主要针对文档存储设计,X DevAPI 同样支持事务管理,确保数据的一致性和完整性。

  6. 异步编程支持:在一些客户端库中(如 Node.js),X DevAPI 支持异步操作,提高了应用的响应速度和性能。

1. 核心概念

  • Session: 表示数据库会话

  • Schema: 数据库模式/库

  • Collection: 文档集合(类似MongoDB集合)

  • Table: 传统关系表

  • DocResult/RowResult: 查询结果

2. 文档操作

CRUD 示例:

Session sess("mysqlx://user:password@localhost:33060/test_db");
Schema schema = sess.getSchema("test_db");
Collection coll = schema.createCollection("users", true);

// 插入
coll.add(R"({
    "name": "John",
    "age": 30,
    "tags": ["admin", "user"]
})").execute();

// 查询
DocResult docs = coll.find("age > :age")
                   .bind("age", 25)
                   .fields("name", "age")
                   .execute();

// 更新
coll.modify("name = :name")
    .bind("name", "John")
    .set("age", 31)
    .arrayAppend("tags", "verified")
    .execute();

// 删除
coll.remove("age < :age")
    .bind("age", 18)
    .execute();

3. 关系表操作

表操作示例:

Table employees = schema.getTable("employees");

// 插入
employees.insert("id", "name", "salary")
          .values(101, "Alice", 50000)
          .values(102, "Bob", 60000)
          .execute();

// 查询
RowResult rows = employees.select("id", "name")
                         .where("salary > :sal")
                         .bind("sal", 55000)
                         .orderBy("name DESC")
                         .execute();

// 更新
employees.update()
          .set("salary", 65000)
          .where("name = :name")
          .bind("name", "Alice")
          .execute();

高级特性

1. 连接池实现

X DevAPI 的 mysqlx::Session 本身不提供内置连接池功能,但可以基于它构建一个高效的连接池。

#include <mysqlx/xdevapi.h>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <stdexcept>
#include <memory>
#include <atomic>

class ThreadSafeXSessionPool {
public:
    // 使用智能指针管理连接
    using SessionPtr = std::unique_ptr<Session, std::function<void(Session*)>>;

    ThreadSafeXSessionPool(const std::string &conn_str, size_t pool_size = 10)
        : m_conn_str(conn_str) {
        for(size_t i = 0; i < pool_size; ++i) {
            m_pool.push(createSession());
        }
    }

    // 获取连接(带自动归还功能)
    SessionPtr getConnection() {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_cond.wait(lock, [this] { return !m_pool.empty() || m_active < m_max_size; });

        if(m_pool.empty()) {
            ++m_active;
            lock.unlock();
            try {
                Session* raw_ptr = new Session(SessionOption::USER, m_conn_str);
                return SessionPtr(raw_ptr, [this](Session* p) {
                    returnConnection(std::unique_ptr<Session>(p));
                });
            } catch (...) {
                --m_active;
                throw;
            }
        }

        auto sess = std::move(m_pool.front());
        m_pool.pop();
        ++m_active;
        lock.unlock();

        // 验证连接
        try {
            sess->sql("SELECT 1").execute();
            return SessionPtr(sess.release(), [this](Session* p) {
                returnConnection(std::unique_ptr<Session>(p));
            });
        } catch (...) {
            --m_active;
            return getConnection(); // 重试获取
        }
    }

    size_t available() const {
        std::lock_guard<std::mutex> lock(m_mutex);
        return m_pool.size();
    }

    size_t active() const {
        return m_active.load();
    }

private:
    void returnConnection(std::unique_ptr<Session> sess) {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_pool.push(std::move(sess));
        --m_active;
        m_cond.notify_one();
    }

    std::unique_ptr<Session> createSession() {
        try {
            return std::make_unique<Session>(SessionOption::USER, m_conn_str);
        } catch (const Error &e) {
            throw std::runtime_error("Session creation failed: " + std::string(e.what()));
        }
    }

    std::queue<std::unique_ptr<Session>> m_pool;
    mutable std::mutex m_mutex;
    std::condition_variable m_cond;
    std::atomic<size_t> m_active{0};
    const size_t m_max_size{50};
    std::string m_conn_str;
};
2. 异步操作(X DevAPI)
Session sess("mysqlx://user:password@localhost:33060/test_db");
Collection coll = sess.getSchema("test_db").getCollection("users");

// 启动异步操作
auto async_find = coll.find("age > 25").executeAsync();

// 执行其他工作...

// 获取结果
DocResult docs = async_find.getResult();
for (DbDoc doc : docs) {
    std::cout << doc << std::endl;
}
3. BLOB 数据处理
// 写入BLOB
std::ifstream file("image.png", std::ios::binary);
std::ostringstream blob;
blob << file.rdbuf();

std::unique_ptr<sql::PreparedStatement> pstmt(
    con->prepareStatement("INSERT INTO images(name, data) VALUES (?, ?)"));
pstmt->setString(1, "sample.png");
pstmt->setBlob(2, new std::istringstream(blob.str()));
pstmt->executeUpdate();

// 读取BLOB
std::unique_ptr<sql::ResultSet> res(
    stmt->executeQuery("SELECT data FROM images WHERE name='sample.png'"));
    
if (res->next()) {
    std::unique_ptr<std::istream> blobStream(res->getBlob(1));
    std::ofstream outFile("output.png", std::ios::binary);
    outFile << blobStream->rdbuf();
}

性能优化

  1. 预处理语句重用:

// 创建预处理语句池
std::map<std::string, std::unique_ptr<sql::PreparedStatement>> stmt_pool;

sql::PreparedStatement* getPreparedStatement(
    sql::Connection* con, 
    const std::string& sql) {
    
    if (stmt_pool.find(sql) == stmt_pool.end()) {
        stmt_pool[sql].reset(con->prepareStatement(sql));
    }
    return stmt_pool[sql].get();
}
  1. 批量操作:

con->setAutoCommit(false);

std::unique_ptr<sql::PreparedStatement> pstmt(
    con->prepareStatement("INSERT INTO test VALUES (?, ?)"));

for (int i = 0; i < 1000; ++i) {
    pstmt->setInt(1, i);
    pstmt->setString(2, "Item " + std::to_string(i));
    pstmt->addBatch();
    
    if (i % 100 == 0) {
        pstmt->executeBatch();
    }
}

pstmt->executeBatch();
con->commit();
con->setAutoCommit(true);
  1. 结果集处理优化:

// 使用更高效的获取方法
ResultSet* res = stmt->executeQuery("SELECT * FROM large_table");

// 按列索引访问比按列名快
while (res->next()) {
    int id = res->getInt(1);          // 比 getInt("id") 快
    std::string name = res->getString(2);
    // ...
}

错误处理

try {
    // 数据库操作
} catch (const sql::SQLException &e) {
    std::cerr << "MySQL Error:" << std::endl;
    std::cerr << "  Code: " << e.getErrorCode() << std::endl;
    std::cerr << "  State: " << e.getSQLState() << std::endl;
    std::cerr << "  Message: " << e.what() << std::endl;
    
    // 特定错误处理
    if (e.getErrorCode() == 1062) { // 重复键错误
        // 处理重复键情况
    } else if (e.getErrorCode() == 1045) { // 访问被拒绝
        // 处理认证错误
    }
} catch (const std::runtime_error &e) {
    std::cerr << "Runtime error: " << e.what() << std::endl;
} catch (const std::exception &e) {
    std::cerr << "Standard exception: " << e.what() << std::endl;
} catch (...) {
    std::cerr << "Unknown exception" << std::endl;
}

JDBC 风格API(传统API)

包含头文件

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

建立连接

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

try {
    driver = sql::mysql::get_mysql_driver_instance();
    con = driver->connect("tcp://127.0.0.1:3306", "username", "password");
    
    // 选择数据库
    con->setSchema("database_name");
    
    // 使用连接...
    
    delete con;
} catch (sql::SQLException &e) {
    std::cerr << "SQL Error: " << e.what() << std::endl;
}

执行查询

sql::Statement *stmt = con->createStatement();
sql::ResultSet *res = stmt->executeQuery("SELECT * FROM table_name");

while (res->next()) {
    std::cout << "ID: " << res->getInt("id");
    std::cout << ", Name: " << res->getString("name") << std::endl;
}

delete res;
delete stmt;

使用预处理语句

sql::PreparedStatement *pstmt = con->prepareStatement(
    "INSERT INTO users (name, age) VALUES (?, ?)");
    
pstmt->setString(1, "John Doe");
pstmt->setInt(2, 30);
pstmt->executeUpdate();

delete pstmt;

事务处理

try {
    con->setAutoCommit(false);
    
    // 执行多个SQL语句
    stmt->executeUpdate("UPDATE accounts SET balance = balance - 100 WHERE user_id = 1");
    stmt->executeUpdate("UPDATE accounts SET balance = balance + 100 WHERE user_id = 2");
    
    con->commit();
    con->setAutoCommit(true);
} catch (...) {
    con->rollback();
    con->setAutoCommit(true);
    throw;
}

高级特性

处理结果集元数据
sql::ResultSetMetaData *meta = res->getMetaData();
int columns = meta->getColumnCount();

for (int i = 1; i <= columns; ++i) {
    std::cout << meta->getColumnName(i) << "\t";
}
std::cout << std::endl;
处理 BLOB 数据
std::istream *blobStream = res->getBlob("image_data");
// 处理blob数据...
delete blobStream;
连接池管理

MySQL Connector/C++ 本身不直接提供连接池功能,但可以自己实现或使用第三方库。

class ConnectionPool {
    std::queue<std::unique_ptr<sql::Connection>> pool;
    sql::Driver* driver;
    std::string url, user, pass, db;
    size_t max_size;
    
public:
    ConnectionPool(size_t max, const std::string& url, 
                  const std::string& user, const std::string& pass, 
                  const std::string& db)
        : max_size(max), url(url), user(user), pass(pass), db(db) {
        driver = get_driver_instance();
        for(size_t i = 0; i < max/2; ++i) {
            addConnection();
        }
    }
    
    void addConnection() {
        std::unique_ptr<sql::Connection> conn(driver->connect(url, user, pass));
        conn->setSchema(db);
        pool.push(std::move(conn));
    }
    
    std::unique_ptr<sql::Connection> getConnection() {
        if(pool.empty()) {
            if(pool.size() < max_size) {
                addConnection();
            } else {
                throw std::runtime_error("Connection pool exhausted");
            }
        }
        
        std::unique_ptr<sql::Connection> conn(std::move(pool.front()));
        pool.pop();
        
        if(!conn->isValid()) {
            conn.reset(driver->connect(url, user, pass));
            conn->setSchema(db);
        }
        
        return conn;
    }
    
    void returnConnection(std::unique_ptr<sql::Connection> conn) {
        if(conn && conn->isValid()) {
            pool.push(std::move(conn));
        }
    }
};

错误处理

try {
    // 数据库操作
} catch (sql::SQLException &e) {
    std::cerr << "# ERR: SQLException in " << __FILE__;
    std::cerr << "(" << __FUNCTION__ << ") on line " << __LINE__ << std::endl;
    std::cerr << "# ERR: " << e.what();
    std::cerr << " (MySQL error code: " << e.getErrorCode();
    std::cerr << ", SQLState: " << e.getSQLState() << " )" << std::endl;
} catch (std::exception &e) {
    std::cerr << "STD ERR: " << e.what() << std::endl;
} catch (...) {
    std::cerr << "Unknown exception caught" << std::endl;
}

总结

  1. 使用预处理语句重复执行相同SQL

  2. 合理使用事务

  3. 及时释放资源(ResultSet, Statement等)

  4. 考虑使用连接池

  5. 批量操作数据时使用批量插入/更新

注意事项
  1. 确保及时释放所有数据库资源

  2. 检查所有数据库操作的返回值

  3. 在生产环境中实现适当的错误处理和日志记录

  4. 注意SQL注入防护,始终使用参数化查询

#include <jdbc/mysql_connection.h>
#include <jdbc/mysql_driver.h>
#include <jdbc/cppconn/statement.h>

using namespace sql;

int main() {
    mysql::MySQL_Driver *driver;
    Connection *con;
    Statement *stmt;
    ResultSet *res;
    
    try {
        driver = mysql::get_mysql_driver_instance();
        con = driver->connect("tcp://127.0.0.1:3306", "user", "password");
        
        con->setSchema("testdb");
        stmt = con->createStatement();
        res = stmt->executeQuery("SELECT * FROM users");
        
        while (res->next()) {
            std::cout << "ID: " << res->getInt("id")
                      << ", Name: " << res->getString("name") << std::endl;
        }
        
        delete res;
        delete stmt;
        delete con;
    }
    catch (SQLException &e) {
        std::cerr << "SQL Error: " << e.what() << std::endl;
    }
    return 0;
}

常见问题

1. 连接问题

错误:无法连接到服务器

  • 检查MySQL服务是否运行

  • 验证用户名/密码

  • 检查防火墙设置

2. 编译错误

错误:LNK2019 未解析的外部符号

  • 确保链接了正确的库(32位/64位匹配)

  • 检查运行时库设置(MT/MD)

3. 运行时错误

错误:找不到DLL

  • 将 mysqlcppconn.dll 复制到可执行文件目录

  • 或将其所在目录添加到系统PATH

性能优化建议

  1. 使用连接池

    // 创建连接池
    SessionPool pool("localhost", 3306, "user", "password", "dbname", 5);
    
    // 从池中获取会话
    Session sess = pool.getSession();
    // 使用后会自动返回池中
  2. 批量操作

    TableInsert insert = table.insert("name", "age");
    insert.values("John", 25);
    insert.values("Alice", 30);
    insert.execute();  // 一次性执行
  3. 预处理语句

    SqlStatement stmt = sess.sql("INSERT INTO users (name, age) VALUES (?, ?)");
    stmt.bind("John", 25).execute();
    stmt.bind("Alice", 30).execute();

与Qt集成

#include <QApplication>
#include <QTableView>
#include <QStandardItemModel>
#include <mysqlx/xdevapi.h>

QString mysqlxValue2Qstring(const mysqlx::Value &value)
{
	//mysqlx::Value value = /* 从结果集中获取的值 */;
	if (value.isNull())
	{
		return QString();
	}

	QString qstr;
	switch (value.getType()) {
	case mysqlx::Value::Type::STRING:
		qstr = QString::fromStdString(value.get<string>());
		break;
	case mysqlx::Value::Type::INT64:
		qstr = QString::number(value.get<int>());
		break;
	case mysqlx::Value::Type::UINT64:
		qstr = QString::number(value.get<unsigned int>());
		break;
	case mysqlx::Value::Type::FLOAT:
		qstr = QString::number(value.get<float>());
		break;
	case mysqlx::Value::Type::DOUBLE:
		qstr = QString::number(value.get<double>());
		break;
	case mysqlx::Value::Type::BOOL:
		qstr = value.get<bool>() ? "true" : "false";
		break;
	default:
		qstr = "Unsupported type";
		break;
	}
	return qstr;
}

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    
    try {
        mysqlx::Session sess("localhost", 33060, "user", "pass", "db");
        auto result = sess.sql("SELECT * FROM products").execute();
        
        QStandardItemModel model;
        model.setHorizontalHeaderLabels({"ID", "Name", "Price"});
        
        for (auto row : result) {
            QList<QStandardItem*> items;
            items << new QStandardItem(mysqlxValue2Qstring(row[0]));
            items << new QStandardItem(mysqlxValue2Qstring(row[1]));
            items << new QStandardItem(mysqlxValue2Qstring(row[2]));
            model.appendRow(items);
        }
        
        QTableView view;
        view.setModel(&model);
        view.show();
        
        sess.close();
    }
    catch (const mysqlx::Error &err) {
        qCritical() << "Database error:" << err.what();
    }
    
    return a.exec();
}

版本兼容性

Connector/C++ 版本支持的 MySQL 服务器版本
8.0.x5.7, 8.0
1.1.x5.6, 5.7

建议使用 Connector/C++ 8.0 配合 MySQL Server 8.0 以获得最佳兼容性和性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值