访问:使用 MySQL C API 访问 MySQL 数据库






4.78/5 (21投票s)
一组用于使用 MySQL C API 访问和操作 MySQL 数据库的类
1. 目标读者
本文档是为初学者编写的,适合那些不知道如何使用 MySQL C API 访问 MySQL 数据库并希望获得简要介绍的开发人员。
2. 简介
如今,MySQL 数据库因其高性能、高可靠性、易用性和显著的成本效益而成为世界上最流行的开源数据库。
它在全球提供的 UNIX 主机套餐中非常受欢迎,因此在本文中,我将展示如何访问这个非常强大的关系数据库系统。
我们将把提供的 MySQL C API 封装在一组 C++ 类中,并在下一篇文章中,我将使用这些相同的类来处理 PostgreSQL。
好了,现在我们开始讲 MySQL。
3. 可用的 API 和库
MySQL 数据库提供了丰富的 API 和库来访问和操作数据及数据库,请参阅
- libmysqld,嵌入式 MySQL 服务器库(https://dev.mysqlserver.cn/doc/refman/5.0/en/libmysqld.html)
- MySQL C API (https://dev.mysqlserver.cn/doc/refman/5.0/en/c.html)
- MySQL PHP API
(https://dev.mysqlserver.cn/doc/refman/5.0/en/php.html) - MySQL Perl API
(https://dev.mysqlserver.cn/doc/refman/5.0/en/perl.html) - MySQL C++ API
(https://dev.mysqlserver.cn/doc/refman/5.0/en/cplusplus.html) - MySQL Python API
(https://dev.mysqlserver.cn/doc/refman/5.0/en/python.html) - MySQL Tcl API
(https://dev.mysqlserver.cn/doc/refman/5.0/en/tcl.html) - MySQL Eiffel Wrapper
(https://dev.mysqlserver.cn/doc/refman/5.0/en/eiffel.html)
需要注意的是,C API 是覆盖和文档最全面的,因为它是由 MySQL 团队开发的,我们将使用它来构建我们的 C++ 类集。
4. 获取 MySQL
最近 MySQL Labs 被 Sun Microsystems 收购,因此获取它的最简单方法以及了解最新消息的方法是访问
或
MySQL 在许多平台上都可用,在我编写本文档时,版本 5 可用于
- Windows
- Windows x64
- Linux (非 RPM 包)
- Linux (非 RPM,Intel C/C++ 编译,glibc-2.3)
- Red Hat Enterprise Linux 3 RPM (x86)
- Red Hat Enterprise Linux 3 RPM (AMD64 / Intel EM64T)
- Red Hat Enterprise Linux 3 RPM (Intel IA64)
- Red Hat Enterprise Linux 4 RPM (x86)
- Red Hat Enterprise Linux 4 RPM (AMD64 / Intel EM64T)
- Red Hat Enterprise Linux 4 RPM (Intel IA64)
- Red Hat Enterprise Linux 5 RPM (x86)
- Red Hat Enterprise Linux 5 RPM (AMD64 / Intel EM64T)
- SuSE Linux Enterprise Server 9 RPM (x86)
- SuSE Linux Enterprise Server 9 RPM (AMD64 / Intel EM64T)
- SuSE Linux Enterprise Server 9 RPM (Intel IA64)
- SuSE Linux Enterprise Server 10 RPM (x86)
- SuSE Linux Enterprise Server 10 RPM (AMD64 / Intel EM64T)
- SuSE Linux Enterprise Server 10 RPM (Intel IA64)
- Ubuntu 6.06 LTS (Dapper Drake)
- Linux x86 通用 RPM (静态链接到 glibc 2.2.5)
- Linux x86 通用 RPM (动态链接)
- Linux AMD64 / Intel EM64T 通用 RPM
- Linux Intel IA64 通用 RPM
- Solaris (pkgadd 包)
- Solaris (TAR 包)
- FreeBSD (TAR 包)
- Mac OS X (包格式)
- Mac OS X (TAR 包)
- HP-UX (depot 包)
- HP-UX (TAR 包)
- IBM AIX
- IBM i5/OS (SAVF 包)
- IBM i5/OS (TAR 包)
- QNX
- Novell NetWare
- SCO OpenServer 6
要获取更新列表和直接下载,请访问 https://dev.mysqlserver.cn/downloads/mysql/5.0.html#downloads
注意: 在您选择的平台上安装 MySQL 时,务必安装开发组件(C 包含文件/库文件),其中包含所需的链接库和包含文件,以便遵循本教程。
5. C++ 接口
该接口集由以下 4 个类组成:
物理文件 |
类 |
|
1 |
Exception.h |
|
2 |
DataBase.h |
|
3 |
ResultSet.h |
|
4 |
MySql.h |
|
5.1 DataBaseError 类
class DataBaseError : public std::exception
/// DataBaseError class
{
public:
DataBaseError(const std::string& what)
: exception(what.c_str())
{
}
}; // DataBaseError
这个简单的类用于需要抛出接口错误时,它继承自 **‘std::exception’** 并利用其提供的错误处理程序。
5.2 DataBase
template<class T>
class DataBase
/// DataBase class
{
public:
DataBase()
: _connected(false)
{
}
virtual ~DataBase()
{
if(_connected)
_dataBaseEngine.close();
}
void connect(const std::string& server, const std::string& user,
const std::string& password, const std::string& database)
{
_dataBaseEngine.connect(server, user, password, database);
_connected = true;
}
DataBase& operator<< (const std::string& sql)
{
_dataBaseEngine.execute(sql);
return *this;
}
DataBase& operator, (ResultSet& rs)
{
_dataBaseEngine.populate(rs);
return *this;
}
private:
T _dataBaseEngine;
bool _connected;
}; // DataBase
此类采用策略模式和模板实现,可以在编译时选择算法,然后我们可以这样使用它:
DataBase<MySQL> mySQLdb;
DataBase<PostgreSQL> postgreSQLdb;
它重载了运算符:**‘<<’** 和 **‘,’**,第一个用于向数据库发送 SQL 命令,第二个用于当第一个命令是 `SELECT` 时,它会生成一个 `ResultSet` 响应。
5.3 ResultSet
class ResultSet
{
public:
ResultSet()
: _current(0)
{
}
void addRow(const std::vector<std::string>& row)
{
_resultSet.push_back(row);
}
bool fetch(size_t field, std::string& fieldValue)
{
size_t sz = _resultSet.size();
if(sz)
{
if(sz > _current)
{
fieldValue = _resultSet[_current++][field];
return true;
}
}
_current = 0;
return false;
}
bool fetch(std::vector<std::string>& rowValue)
{
size_t sz = _resultSet.size();
if(sz)
{
if(sz > _current)
{
rowValue = _resultSet[_current++];
return true;
}
}
_current = 0;
return false;
}
std::string get(size_t row, size_t field)
{
return _resultSet[row][field];
}
std::vector<std::string> get(size_t row)
{
return _resultSet[row];
}
size_t countRows(void)
{
if (_resultSet.empty()) return 0;
return _resultSet.size();
}
size_t countFields(void)
{
if (_resultSet[0].empty()) return 0;
return _resultSet[0].size();
}
private:
std::vector<std::vector<std::string> > _resultSet;
size_t _current;
}; // ResultSet
当 `SELECT` 命令发送到数据库时,它接收其结果。
为了保持简单,此类存在一些可能需要更改的缺点,这些缺点是:
ResultSet
只处理 `string`。ResultSet
将所有数据放入一个 `std::vector` 中,它不会逐行获取,这对于大型结果集可能是一个问题。
5.4 MySql
#include "Exception.h"
#include "ResultSet.h"
#include "DataBase.h"
#include <mysql.h>
class MySql
/// MySql data base class
{
friend class DataBase<MySql>;
public:
MySql()
{
}
virtual ~MySql()
{
}
void connect(const std::string& server, const std::string& user,
const std::string& password, const std::string& database)
{
_connectionHandlerPtr = mysql_init(NULL);
if (NULL == mysql_real_connect(_connectionHandlerPtr, server.c_str(),
user.c_str(), password.c_str(), database.c_str(), 0, NULL, 0))
{
throw DataBaseError("Failed to connect to database: Error: " +
std::string(mysql_error(_connectionHandlerPtr)));
}
}
void execute(const std::string& sql)
{
std::cout << sql << std::endl;
if(!(mysql_query(_connectionHandlerPtr, sql.c_str()) == 0) )
{
throw DataBaseError("Failed to execute sql: Error: " +
std::string(mysql_error(_connectionHandlerPtr)));
}
}
void populate(ResultSet& rs)
{
MYSQL_RES *result = NULL; // result of querying for all rows in table
// You must call mysql_store_result() or mysql_use_result()
// for every query that successfully retrieves data (SELECT, SHOW,
// DESCRIBE, EXPLAIN).
result = mysql_use_result(_connectionHandlerPtr);
MYSQL_ROW row;
unsigned int num_fields;
unsigned int i;
num_fields = mysql_num_fields(result);
// get rows
while ((row = mysql_fetch_row(result)))
{
std::vector<std::string> myRow;
// get fields od row
for(i = 0; i < num_fields; i++)
{
if (row[i] == NULL)
{
myRow.push_back("NULL"); }
}
else
{
myRow.push_back(row[i]);
}
}
rs.addRow(myRow);
}
mysql_free_result(result);
}
protected:
void close(void)
{
mysql_close(_connectionHandlerPtr);
}
private:
MYSQL* _connectionHandlerPtr;
// This structure represents a handle to one database connection.
}; // MySql
此类是 MySQL 实现,在其中我们使用 C API 来操作/访问数据。
我将从:**`MYSQL* _connectionHandlerPtr;`** 开始,这是数据库连接的句柄,几乎所有 MySQL 函数都使用它,请记住,您不应该尝试复制 MYSQL 结构。
在 `void connect(...)` 中,我们使用这些 API 函数:
-
MYSQL *mysql_init(MYSQL *mysql)
(https://dev.mysqlserver.cn/doc/refman/5.0/en/mysql-init.html)
-
MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long client_flag)
(https://dev.mysqlserver.cn/doc/refman/5.0/en/mysql-real-connect.html) -
const char *mysql_error(MYSQL *mysql)
(https://dev.mysqlserver.cn/doc/refman/5.0/en/mysql-error.html)
第一个为后续调用 **`mysql_real_connect(...)`** 分配/初始化一个 MYSQL 对象,该函数尝试建立与 MySQL 数据库引擎的连接。
如果内存不足,**`mysql_init(...);`** 将返回 **`NULL`**。
如果连接不成功,`mysql_real_connect(...);` 将返回 **`NULL`**。
要获取错误描述,我们使用 `mysql_error(...)`,它返回一个 `null` 终止的 `string`,其中包含最近一次失败的 API 函数的错误消息。
在 `execute(..)` 中,我们使用这些 API 函数:
-
int mysql_query(MYSQL *mysql, const char *stmt_str)
(https://dev.mysqlserver.cn/doc/refman/5.0/en/mysql-query.html)`mysql_query(...)` 执行由 `null` 终止的 `string` 指向的 SQL 语句,如果语句成功则返回零,如果发生错误则返回非零。
在 **`populate(...)`** 中,我们使用这些 API 函数:
-
MYSQL_RES *mysql_use_result(MYSQL *mysql)
(https://dev.mysqlserver.cn/doc/refman/5.0/en/mysql-use-result.html) -
unsigned int mysql_num_fields(MYSQL_RES *result)
(https://dev.mysqlserver.cn/doc/refman/5.0/en/mysql-num-fields.html) -
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)
(https://dev.mysqlserver.cn/doc/refman/5.0/en/mysql-fetch-row.html) -
void mysql_free_result(MYSQL_RES *result)
(https://dev.mysqlserver.cn/doc/refman/5.0/en/mysql-free-result.html)
对于每个成功检索数据的查询(`SELECT`、`SHOW`、`DESCRIBE`、`EXPLAIN`),都必须调用 **`mysql_use_result(...)`**。如果发生错误,则返回 **`NULL`**,否则返回 `MYSQL_RES` 结构。
`mysql_num_fields(...)` 返回结果集中的列数。
`mysql_fetch_row(...)` 检索结果集的下一行。
`mysql_free_result (...)` 释放分配的内存。
最后我们有 `close()`,它使用:****
- `void mysql_close(MYSQL *mysql)`(https://dev.mysqlserver.cn/doc/refman/5.0/en/mysql-close.html)
-
这会关闭先前打开的连接,并根据由 `mysql_init()` 或 `mysql_connect()` 自动分配的句柄,释放 `mysql` 指向的连接句柄。
好了,这就是全部内容,我们使用了 10 个 MySQL C API 函数来构建这组提供简单访问/操作 MySQL 数据库的类。
6. 接口使用
在这里,我将展示如何使用我们的类集,请参阅:
#include "MySql.h"
int main()
{
try
{
ResultSet rs1, rs2;
DataBase<MySql> dataBase;
dataBase.connect("205.XXX.XXX.XX", "user", "pass", "db");
// CREATE FIRST TABLE
dataBase << "CREATE TABLE if not exists tblTest1(test char(15) NOT NULL,
testInt INT NULL, Constraint PK Primary Key(test))";
// INSERT SOME ITENS
dataBase << "INSERT INTO tblTest1(test, testInt) VALUES('00', 1)" ;
dataBase << "INSERT INTO tblTest1(test) VALUES('01')" ;
dataBase << "INSERT INTO tblTest1(test) VALUES('02')" ;
dataBase << "INSERT INTO tblTest1(test) VALUES('03')" ;
dataBase << "INSERT INTO tblTest1(test) VALUES('04')" ;
dataBase << "INSERT INTO tblTest1(test) VALUES('05')" ;
dataBase << "INSERT INTO tblTest1(test) VALUES('06')" ;
dataBase << "INSERT INTO tblTest1(test) VALUES('07')" ;
dataBase << "INSERT INTO tblTest1(test) VALUES('08')" ;
dataBase << "INSERT INTO tblTest1(test) VALUES('09')" ;
// CREATE SECOND TABLE
dataBase << "CREATE TABLE if not exists tblTest2(test char(15) NOT NULL,
dt DATE NULL, Constraint PK Primary Key(test))";
// INSERT SOME ITENS
dataBase << "INSERT INTO tblTest2(test, dt) VALUES('01', '1979/11/14')" ;
dataBase << "INSERT INTO tblTest2(test) VALUES('02')" ;
dataBase << "INSERT INTO tblTest2(test) VALUES('03')" ;
dataBase << "INSERT INTO tblTest2(test) VALUES('04')" ;
dataBase << "INSERT INTO tblTest2(test) VALUES('05')" ;
dataBase << "INSERT INTO tblTest2(test) VALUES('06')" ;
dataBase << "INSERT INTO tblTest2(test) VALUES('07')" ;
dataBase << "INSERT INTO tblTest2(test) VALUES('08')" ;
dataBase << "INSERT INTO tblTest2(test) VALUES('09')" ;
dataBase << "INSERT INTO tblTest2(test) VALUES('10')" ;
// GET (one column) SAME ITENS OF tblTest1
dataBase << "SELECT * FROM tblTest1, tblTest2 WHERE tblTest1.test = tblTest2.test",
rs1;
std::string value;
std::vector<std::string> row;
while(rs1.fetch(0, value))
{
std::cout << value << std::endl;
}
while(rs1.fetch(row))
{
for (size_t i = 0; i < row.size(); i++)
{
std::cout << row[i] << " | ";
}
std::cout << std::endl;
}
// GET A SPECIFIC ITEN
std::cout << rs1.get(0)[0] << " | " << rs1.get(0)[1] << std::endl;
// GET (one column) SAME ITENS OF tblTest2
dataBase << "SELECT * FROM tblTest2 WHERE test = '01'", rs2;
while(rs2.fetch(1, value))
{
std::cout << value << std::endl;
}
while(rs2.fetch(row))
{
for (size_t i = 0; i < row.size(); i++)
{
std::cout << row[i] << " | ";
}
std::cout << std::endl;
}
// GET A SPECIFIC ITEN
std::cout << rs2.get(0, 1) << std::endl;
}
catch (const DataBaseError& e)
{
std::cout << e.what() << std::endl;
}
return 0;
}
7. 结论
如今,MySQL 是一个功能强大的开源关系数据库替代品,它提供快速的性能、高可靠性、易用性、成本效益,并且提供许多语言的访问 API,应用广泛且易于使用。
参考文献
- MySQL
- 网站
- https://mysqlserver.cn/
- https://dev.mysqlserver.cn/doc/refman/5.0/en/
- http://www.sun.com/software/products/mysql/index.jsp
- http://tangentsoft.net/mysql++/doc/html/userman/tutorial.html
C++ 书籍 (列表)
此处列出的 Web 链接将来可能无效。