【链接】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. 下载与安装
-
版本选择:
-
8.0.x 版本(推荐,支持最新功能)
-
1.1.x 版本(旧版,已停止维护)
-
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 的核心特性
-
文档存储支持:X DevAPI 引入了对文档存储的支持,允许用户像在 NoSQL 数据库中一样存储和查询 JSON 文档。这使得 MySQL 不仅可以作为传统的关系型数据库使用,还可以作为一个强大的文档数据库。
-
面向对象的设计:X DevAPI 提供了一个更加直观的对象模型来表示数据库资源,如 Schema、Collection 和 Table 等。这些对象使开发者能够以更自然的方式与数据库进行交互。
-
CRUD 操作简化:对于集合(Collections)中的文档或表(Tables)中的行,X DevAPI 提供了简洁的方法来进行创建(Create)、读取(Retrieve)、更新(Update)和删除(Delete)操作。例如,可以直接使用
add()
方法向集合添加文档,使用find()
方法检索文档等。 -
表达式语言:X DevAPI 支持一种用于构建查询条件的表达式语言,该语言可以用来定义复杂的查询条件和排序规则,从而实现更精确的数据检索。
-
事务支持:尽管主要针对文档存储设计,X DevAPI 同样支持事务管理,确保数据的一致性和完整性。
-
异步编程支持:在一些客户端库中(如 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();
}
性能优化
-
预处理语句重用:
// 创建预处理语句池
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();
}
-
批量操作:
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);
-
结果集处理优化:
// 使用更高效的获取方法
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;
}
总结
-
使用预处理语句重复执行相同SQL
-
合理使用事务
-
及时释放资源(ResultSet, Statement等)
-
考虑使用连接池
-
批量操作数据时使用批量插入/更新
注意事项
-
确保及时释放所有数据库资源
-
检查所有数据库操作的返回值
-
在生产环境中实现适当的错误处理和日志记录
-
注意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
性能优化建议
-
使用连接池:
// 创建连接池 SessionPool pool("localhost", 3306, "user", "password", "dbname", 5); // 从池中获取会话 Session sess = pool.getSession(); // 使用后会自动返回池中
-
批量操作:
TableInsert insert = table.insert("name", "age"); insert.values("John", 25); insert.values("Alice", 30); insert.execute(); // 一次性执行
-
预处理语句:
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.x | 5.7, 8.0 |
1.1.x | 5.6, 5.7 |
建议使用 Connector/C++ 8.0 配合 MySQL Server 8.0 以获得最佳兼容性和性能。