MySQL Connector/C++ 经典 JDBC/SQL 风格 API(Legacy API) 更接近传统数据库编程方式,核心接口风格和 JDBC 类似,主要通过:

  • sql::Driver
  • sql::Connection
  • sql::Statement
  • sql::PreparedStatement
  • sql::ResultSet

来完成数据库访问。

说明:
Connector/C++ 8.x 同时提供 X DevAPI 和传统 API。
传统 API 常被称为 Legacy APIJDBC-like API
它走的是 经典 MySQL 协议,通常连接端口是 3306


适用场景

Legacy API 适合:

  • 已有大量 SQL 代码
  • 主要做传统关系型数据库开发
  • 依赖 SELECT / INSERT / UPDATE / DELETE / JOIN / 存储过程
  • 不需要 X Protocol / Collection 文档模型

如果你的项目是标准关系数据库开发,这套 API 往往更直观。


环境准备

头文件

常见包含方式:

1
2
3
4
5
6
7
8
9
#include <mysql_driver.h>
#include <mysql_connection.h>

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

命名空间

常见使用:

1
2
sql::
mysql::

其中:

  • mysql::MySQL_Driver 是 MySQL 驱动实现
  • sql::Connection / sql::Statement 等是通用接口

基本对象关系

传统 API 的核心调用流程通常是:

  1. 获取驱动 Driver
  2. 创建连接 Connection
  3. 创建语句对象:
  • Statement
  • PreparedStatement
  1. 执行 SQL
  2. 获取结果:
  • ResultSet
  1. 处理异常
  2. 释放资源

关系图可以理解为:

1
2
3
4
5
Driver
└── Connection
├── Statement
├── PreparedStatement
└── ResultSet

建立连接

最基本连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <memory>

#include <mysql_driver.h>
#include <mysql_connection.h>
#include <cppconn/exception.h>

int main() {
try {
mysql::MySQL_Driver* driver = mysql::get_mysql_driver_instance();
std::unique_ptr<sql::Connection> conn(
driver->connect("tcp://127.0.0.1:3306", "root", "password")
);

conn->setSchema("test");
std::cout << "Connected to MySQL successfully." << std::endl;
}
catch (const sql::SQLException& e) {
std::cerr << "SQLException: " << e.what() << std::endl;
std::cerr << "ErrorCode: " << e.getErrorCode() << std::endl;
std::cerr << "SQLState: " << e.getSQLState() << std::endl;
}
}

连接字符串格式

典型写法:

1
"tcp://127.0.0.1:3306"

也可写主机名:

1
"tcp://localhost:3306"

设置当前数据库

1
conn->setSchema("test");

等价于执行:

1
USE test;

Statement:执行普通 SQL

Statement 适合执行不带参数或者临时 SQL。

创建 Statement

1
std::unique_ptr<sql::Statement> stmt(conn->createStatement());

执行 DDL

1
2
3
4
5
6
stmt->execute(
"CREATE TABLE IF NOT EXISTS users ("
"id INT PRIMARY KEY AUTO_INCREMENT, "
"name VARCHAR(50), "
"age INT)"
);

执行插入、更新、删除

使用 executeUpdate()

1
2
3
4
5
int affected = stmt->executeUpdate(
"INSERT INTO users(name, age) VALUES('Alice', 25)"
);

std::cout << "Affected rows: " << affected << std::endl;

更新:

1
stmt->executeUpdate("UPDATE users SET age = 26 WHERE name = 'Alice'");

删除:

1
stmt->executeUpdate("DELETE FROM users WHERE age < 18");

执行查询

使用 executeQuery()

1
2
3
4
5
6
7
8
9
10
std::unique_ptr<sql::ResultSet> res(
stmt->executeQuery("SELECT id, name, age FROM users")
);

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

PreparedStatement:预处理语句

这是实际开发中最推荐的方式,因为它:

  • 防止 SQL 注入
  • 参数绑定清晰
  • 重复执行效率更高
  • 代码更规范

创建 PreparedStatement

1
2
3
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement("INSERT INTO users(name, age) VALUES(?, ?)")
);

绑定参数并插入

1
2
3
4
5
pstmt->setString(1, "Bob");
pstmt->setInt(2, 30);

int affected = pstmt->executeUpdate();
std::cout << "Inserted rows: " << affected << std::endl;

参数下标从 1 开始,不是 0。


查询语句

1
2
3
4
5
6
7
8
9
10
11
12
13
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement("SELECT id, name, age FROM users WHERE age > ?")
);

pstmt->setInt(1, 20);

std::unique_ptr<sql::ResultSet> res(pstmt->executeQuery());

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

更新语句

1
2
3
4
5
6
7
8
9
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement("UPDATE users SET age = ? WHERE name = ?")
);

pstmt->setInt(1, 35);
pstmt->setString(2, "Bob");

int affected = pstmt->executeUpdate();
std::cout << "Updated rows: " << affected << std::endl;

删除语句

1
2
3
4
5
6
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement("DELETE FROM users WHERE name = ?")
);

pstmt->setString(1, "Bob");
int affected = pstmt->executeUpdate();

ResultSet:处理查询结果

ResultSet 是查询结果集对象。

遍历结果

1
2
3
while (res->next()) {
// 每调用一次 next(),游标移动到下一行
}

next() 返回:

  • true:当前有可读行
  • false:没有更多数据

按列名读取

1
2
3
int id = res->getInt("id");
std::string name = res->getString("name");
int age = res->getInt("age");

按列下标读取

1
2
3
int id = res->getInt(1);
std::string name = res->getString(2);
int age = res->getInt(3);

这里列下标也是 从 1 开始


常用读取方法

常见类型读取:

  • getInt()
  • getUInt()
  • getInt64()
  • getDouble()
  • getString()
  • getBoolean()
  • getDateTime()

示例:

1
2
3
std::string name = res->getString("name");
double salary = res->getDouble("salary");
bool enabled = res->getBoolean("enabled");

判断 NULL

可先判断:

1
2
3
4
5
if (res->isNull("nickname")) {
std::cout << "nickname is NULL" << std::endl;
} else {
std::cout << res->getString("nickname") << std::endl;
}

完整 CRUD 示例

假设有表:

1
2
3
4
5
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
age INT
);

插入数据

1
2
3
4
5
6
7
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement("INSERT INTO users(name, age) VALUES(?, ?)")
);

pstmt->setString(1, "Alice");
pstmt->setInt(2, 25);
pstmt->executeUpdate();

查询数据

1
2
3
4
5
6
7
8
9
10
11
12
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement("SELECT id, name, age FROM users WHERE age > ?")
);
pstmt->setInt(1, 18);

std::unique_ptr<sql::ResultSet> res(pstmt->executeQuery());

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

更新数据

1
2
3
4
5
6
7
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement("UPDATE users SET age = ? WHERE name = ?")
);

pstmt->setInt(1, 28);
pstmt->setString(2, "Alice");
pstmt->executeUpdate();

删除数据

1
2
3
4
5
6
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement("DELETE FROM users WHERE name = ?")
);

pstmt->setString(1, "Alice");
pstmt->executeUpdate();

事务处理

默认情况下,MySQL 连接往往是自动提交的。
要手动控制事务:

开启事务控制

1
conn->setAutoCommit(false);

提交事务

1
conn->commit();

回滚事务

1
conn->rollback();

事务完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
try {
conn->setAutoCommit(false);

std::unique_ptr<sql::PreparedStatement> pstmt1(
conn->prepareStatement("INSERT INTO users(name, age) VALUES(?, ?)")
);
pstmt1->setString(1, "Tom");
pstmt1->setInt(2, 20);
pstmt1->executeUpdate();

std::unique_ptr<sql::PreparedStatement> pstmt2(
conn->prepareStatement("INSERT INTO users(name, age) VALUES(?, ?)")
);
pstmt2->setString(1, "Jerry");
pstmt2->setInt(2, 21);
pstmt2->executeUpdate();

conn->commit();
}
catch (const sql::SQLException& e) {
conn->rollback();
std::cerr << "Transaction failed: " << e.what() << std::endl;
}

获取自动增长 ID

插入后常常需要获取自增主键 ID。
常见做法是执行:

1
SELECT LAST_INSERT_ID()

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement("INSERT INTO users(name, age) VALUES(?, ?)")
);
pstmt->setString(1, "NewUser");
pstmt->setInt(2, 22);
pstmt->executeUpdate();

std::unique_ptr<sql::Statement> stmt(conn->createStatement());
std::unique_ptr<sql::ResultSet> res(
stmt->executeQuery("SELECT LAST_INSERT_ID() AS id")
);

if (res->next()) {
std::cout << "New ID: " << res->getInt("id") << std::endl;
}

这个值与当前连接相关,所以要在同一个连接里获取。


批量插入思路

Legacy API 没有像某些驱动那样特别统一的高级批处理体验时,常见做法是:

  1. 复用同一个 PreparedStatement
  2. 多次绑定参数执行
  3. 配合事务提升性能

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
conn->setAutoCommit(false);

std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement("INSERT INTO users(name, age) VALUES(?, ?)")
);

for (int i = 0; i < 1000; ++i) {
pstmt->setString(1, "user_" + std::to_string(i));
pstmt->setInt(2, 20 + (i % 10));
pstmt->executeUpdate();
}

conn->commit();

处理日期时间

如果数据库字段是 DATETIME / TIMESTAMP,常见做法之一是按字符串读取:

1
std::string created_at = res->getString("created_at");

插入时也可直接传字符串:

1
pstmt->setString(1, "2025-05-13 10:30:00");

如果你需要更复杂的时间类型转换,通常在 C++ 层自己做封装。


调用存储过程

假设数据库中有存储过程:

1
2
3
4
CREATE PROCEDURE get_users_by_age(IN min_age INT)
BEGIN
SELECT id, name, age FROM users WHERE age >= min_age;
END;

调用方式可直接用 SQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement("CALL get_users_by_age(?)")
);

pstmt->setInt(1, 20);

std::unique_ptr<sql::ResultSet> res(pstmt->executeQuery());

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

元数据操作

有时需要读取结果集元信息。

获取 ResultSetMetaData

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <cppconn/metadata.h>

std::unique_ptr<sql::ResultSet> res(
stmt->executeQuery("SELECT id, name, age FROM users")
);

std::unique_ptr<sql::ResultSetMetaData> meta(res->getMetaData());

int colCount = meta->getColumnCount();
for (int i = 1; i <= colCount; ++i) {
std::cout << "Column " << i << ": "
<< meta->getColumnLabel(i) << std::endl;
}

异常处理

传统 API 的主要异常类型是:

1
sql::SQLException

你可以获取:

  • 错误信息:what()
  • 错误码:getErrorCode()
  • SQL 状态:getSQLState()

示例:

1
2
3
4
5
catch (const sql::SQLException& e) {
std::cerr << "SQLException: " << e.what() << std::endl;
std::cerr << "MySQL error code: " << e.getErrorCode() << std::endl;
std::cerr << "SQLState: " << e.getSQLState() << std::endl;
}

资源管理建议

虽然旧代码中常见手动 delete,但现代 C++ 强烈建议用 std::unique_ptr 管理资源。

推荐:

1
2
3
std::unique_ptr<sql::Connection> conn(...);
std::unique_ptr<sql::PreparedStatement> pstmt(...);
std::unique_ptr<sql::ResultSet> res(...);

这样可避免内存泄漏。


完整示例程序

下面给你一个可直接参考的完整示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <iostream>
#include <memory>

#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>

int main() {
try {
mysql::MySQL_Driver* driver = mysql::get_mysql_driver_instance();

std::unique_ptr<sql::Connection> conn(
driver->connect("tcp://127.0.0.1:3306", "root", "password")
);

conn->setSchema("test");

std::unique_ptr<sql::Statement> stmt(conn->createStatement());

stmt->execute(
"CREATE TABLE IF NOT EXISTS users ("
"id INT PRIMARY KEY AUTO_INCREMENT, "
"name VARCHAR(50), "
"age INT)"
);

{
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement("INSERT INTO users(name, age) VALUES(?, ?)")
);
pstmt->setString(1, "Alice");
pstmt->setInt(2, 25);
pstmt->executeUpdate();

pstmt->setString(1, "Bob");
pstmt->setInt(2, 30);
pstmt->executeUpdate();
}

{
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement("SELECT id, name, age FROM users WHERE age > ?")
);
pstmt->setInt(1, 20);

std::unique_ptr<sql::ResultSet> res(pstmt->executeQuery());

std::cout << "Query result:" << std::endl;
while (res->next()) {
std::cout << "id=" << res->getInt("id")
<< ", name=" << res->getString("name")
<< ", age=" << res->getInt("age")
<< std::endl;
}
}

{
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement("UPDATE users SET age = ? WHERE name = ?")
);
pstmt->setInt(1, 35);
pstmt->setString(2, "Bob");
int affected = pstmt->executeUpdate();
std::cout << "Updated rows: " << affected << std::endl;
}

{
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement("DELETE FROM users WHERE name = ?")
);
pstmt->setString(1, "Alice");
int affected = pstmt->executeUpdate();
std::cout << "Deleted rows: " << affected << std::endl;
}
}
catch (const sql::SQLException& e) {
std::cerr << "SQLException: " << e.what() << std::endl;
std::cerr << "ErrorCode: " << e.getErrorCode() << std::endl;
std::cerr << "SQLState: " << e.getSQLState() << std::endl;
return 1;
}
catch (const std::exception& e) {
std::cerr << "std::exception: " << e.what() << std::endl;
return 1;
}

return 0;
}

编译示例

g++ 示例

不同平台安装路径不同,下面只是示意:

1
2
3
4
g++ -std=c++17 main.cpp -o app \
-I/usr/include/mysql-cppconn-8 \
-L/usr/lib \
-lmysqlcppconn

注意:
X DevAPI 常用库名可能是 mysqlcppconn8
Legacy API 在某些环境中可能是 mysqlcppconn
具体以你的安装包实际内容为准。


CMake 示例

1
2
3
4
5
6
7
8
9
cmake_minimum_required(VERSION 3.10)
project(mysql_legacy_demo)

set(CMAKE_CXX_STANDARD 17)

add_executable(mysql_legacy_demo main.cpp)

target_include_directories(mysql_legacy_demo PRIVATE /usr/include/mysql-cppconn-8)
target_link_libraries(mysql_legacy_demo PRIVATE mysqlcppconn)

常见问题与注意事项

传统 API 连接的是 3306

不要和 X DevAPI 的 33060 混淆。


参数下标从 1 开始

无论是:

  • PreparedStatement::setXXX(index, value)
  • ResultSet::getXXX(index)

都是从 1 开始。


强烈建议优先用 PreparedStatement

不要手工拼接用户输入:

错误示例:

1
std::string sql = "SELECT * FROM users WHERE name = '" + userInput + "'";

正确示例:

1
2
auto pstmt = conn->prepareStatement("SELECT * FROM users WHERE name = ?");
pstmt->setString(1, userInput);

事务要注意 AutoCommit

如果你想自己控制事务,一定先:

1
conn->setAutoCommit(false);

否则每条语句可能自动提交。


LAST_INSERT_ID() 必须同连接获取

不要换连接,否则取到的值不对。


SQL 异常要看 SQLState 和错误码

定位问题时:

  • what() 看文本描述
  • getErrorCode() 看 MySQL 错误码
  • getSQLState() 看标准 SQL 状态

三者结合最好排查。


Legacy API 常用接口速查表

连接

1
2
3
4
5
mysql::MySQL_Driver* driver = mysql::get_mysql_driver_instance();
auto conn = std::unique_ptr<sql::Connection>(
driver->connect("tcp://127.0.0.1:3306", "root", "password")
);
conn->setSchema("test");

Statement

1
2
3
4
auto stmt = std::unique_ptr<sql::Statement>(conn->createStatement());
stmt->execute("CREATE TABLE ...");
stmt->executeUpdate("INSERT/UPDATE/DELETE ...");
auto res = std::unique_ptr<sql::ResultSet>(stmt->executeQuery("SELECT ..."));

PreparedStatement

1
2
3
4
5
auto pstmt = std::unique_ptr<sql::PreparedStatement>(
conn->prepareStatement("SELECT * FROM users WHERE id = ?")
);
pstmt->setInt(1, 100);
auto res = std::unique_ptr<sql::ResultSet>(pstmt->executeQuery());

ResultSet

1
2
3
4
while (res->next()) {
res->getInt("id");
res->getString("name");
}

事务

1
2
3
conn->setAutoCommit(false);
conn->commit();
conn->rollback();

X DevAPI 与 Legacy API 简单对比

项目 X DevAPI Legacy API
协议 X Protocol Classic MySQL Protocol
默认端口 33060 3306
风格 现代对象式、支持文档模型 传统 SQL/JDBC 风格
文档操作 原生支持 Collection 不原生支持
表操作 支持 强项
SQL 开发体验 可以,但不是主核心 最直接
适合 新项目、文档+关系混合 传统关系型系统

总结

Legacy API 的核心使用流程就是:

  1. get_mysql_driver_instance()
  2. connect()
  3. setSchema()
  4. createStatement()prepareStatement()
  5. executeQuery() / executeUpdate()
  6. 遍历 ResultSet
  7. 事务控制与异常处理

如果项目是传统表结构 + 大量 SQL,那么 Legacy API 通常比 X DevAPI 更直接