访问:使用 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 链接将来可能无效。


