码迷,mamicode.com
首页 > 数据库 > 详细

C++操作数据库

时间:2016-06-06 01:18:24      阅读:621      评论:0      收藏:0      [点我收藏+]

标签:

  自从小组纳新到现在一直没有学什么东西,鼓捣了几次系统,体验了一下Fedora,最终还是选择了Centos,看到学长炫酷的SS管理,自己也把原来的格掉重新搭了一个。说好的一天一道LeetCode也荒废了,博客也好久好久没有更新了,真是罪过、罪过。现在纳新早已结束,是时候拾起自己的老本行了。周末学习了一下MySQL的C语言API,试着用C++简单的封装了一下,现在做个记录,以防遗忘。

一、重要数据结构介绍

  1、MYSQL:这个结构表示对一个数据库连接的句柄,它被用于几乎所有的MySQL函数。
  2、MYSQL_RES :这个结构代表返回行的一个查询的(SELECT, SHOW, DESCRIBE, EXPLAIN)的结果。从查询返回的信息在本章下文称为结果集合。
  3、MYSQL_ROW:这是一个行数据的类型安全(type-safe)的表示。当前它实现为一个计数字节的字符串数组。(如果字段值可能包含二进制数据,你不能将这些视为空终止串,因为这样的值可以在内部包含空字节) 行通过调用mysql_fetch_row()获得。
  4、MYSQL_FIELD:这个结构包含字段信息,例如字段名、类型和大小。其成员在下面更详细地描述。你可以通过重复调用mysql_fetch_field()对每一列获得MYSQL_FIELD结构。字段值不是这个结构的部分;他们被包含在一个MYSQL_ROW结构中。

  MYSQL_FIELD结构包含列在下面的成员:
   char * name:字段名,是一个空结尾的字符串。
   char * table:包含该字段的表的名字,如果它不是可计算的字段。对可计算的字段,table值是一个空字符串。
   char * def:这字段的缺省值,是一个空结尾的字符串。只要你使用,只有你使用mysql_list_fields()才可设置它。
  其他成员请参考:http://www.cbi.pku.edu.cn/chinese/documents/csdoc/mysql/manual_Clients.html#C

二、C API函数介绍

  1、MYSQL *mysql_init(MYSQL *mysql);
    分配或初始化适合mysql_real_connect()的一个MYSQL对象。如果mysql是一个NULL指针,函数分配、初始化并且返回一个新对象。否则对象被初始化并且返回对象的地址。如果mysql_init()分配一个新对象,它将在调用mysql_close()关闭连接时被释放。返回一个被初始化的MYSQL*句柄。如果没有足够的内存来分配一个新对象则返回NULL(失败)。


  2、void mysql_close(MYSQL *mysql);

    关闭一个以前打开了的连接。如果句柄由mysql_init()或mysql_connect()自动分配,mysql_close()也释放被mysql指向的连接句柄。


  3、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 int client_flag)

    mysql_real_connect()试图建立到运行host的一个MySQL数据库引擎的一个连接。 mysql_real_connect()在你可以执行任何其他API函数之前必须成功地完成,除了mysql_get_client_info()。


  参数说明:

    (1)、第一个参数是一个现存MYSQL结构的地址。在调用mysql_real_connect()之前,你必须调用mysql_init()初始化MYSQL结构。


    (2)、host值可以是一个主机名或一个IP地址。如果host是NULL或字符串"localhost",假定是到本地主机的一个连接。如果OS支持套接字(Unix)或命名管道(Win32),使用他们而不是TCP/IP与服务器连接。


    (3)、user参数包含用户的MySQL登录ID。如果user是NULL,假定是当前用户。在Unix下,它是当前登录名。在Windows ODBC下,必须明确地指定当前用户名字。


    (4)、passwd参数为user包含口令。如果passwd是NULL,只有在user表中对于有一个空白口令字段的用户的条目将被检查一个匹配。这允许数据库主管设置MySQL权限,使用户获得不同的口令,取决于他们是否已经指定一个口令。注意:不要试图在调用mysql_real_connect()前加密口令;口令加密自动被客户API处理。

    (5)、db是数据库名。


    (6)、如果port不是0,值对于TCP/IP连接将用作端口号。注意host参数决定连接的类型。


    (7)、如果unix_socket不是NULL,字符串指定套接字或应该被使用的命名管道。


    (8)、client_flag值通常是0。

如果连接成功,返回一个 MYSQL*连接句柄。如果连接失败,返回NULL。对一个成功的连接,返回值与第一个参数值相同,除非你传递NULL给该参数。


  4、int mysql_options(MYSQL *mysql, enum mysql_option option, const char *arg);

    能用于设置额外连接选项并且影响一个连接的行为。这个函数可以被多次调用来设置多个选项,mysql_options()应该在mysql_init()之后和mysql_connect()或mysql_real_connect()之前调用,option参数是你想要设置的选项;arg参数是选项的值。如果选项是一个整数,那么arg应该指向整数值。具体选项值请参见:http://www.cbi.pku.edu.cn/chinese/documents/csdoc/mysql/manual_Clients.html#C。成功返回零,如果你使用了未知的选项返回非零。


  5、int mysql_query(MYSQL *mysql, const char *query);

    执行指向空终止的字符串query的SQL查询,查询必须由一个单个的SQL语句组成。你不应该在语句后加上一个终止的分号(“;”)或\g。mysql_query()不能被用于包含二进制数据的查询;相反你应该使用mysql_real_query()。(二进制数据可能包含“\0”字符,而mysql_query()将解释为查询字符串的结束。)如果查询成功返回零,如果出现一个错误返回非零。


  6、int mysql_real_query(MYSQL *mysql, const char *query, unsigned int length);

    执行由query指向的SQL查询,它应该是一个length个字节的字符串。查询必须由一个单个的SQL语句组成。你不应该在语句后增加一个终止的分号(“;”)或\g。对于包含二进制数据的查询,你必须使用mysql_real_query()而不是mysql_query(),因为二进制代码数据可能包含“\0”字符,而且,mysql_real_query()比mysql_query()更快,因为它对查询字符串调用strlen()。如果查询成功返回零,如果发生一个错误返回非零。


  7、MYSQL_RES *mysql_store_result(MYSQL *mysql);

    对于成功地检索数据的每个询问(SELECT、SHOW、DESCRIBE、EXPLAIN),你必须调用mysql_store_result()或mysql_use_result()。mysql_store_result()读取一个到客户的查询的全部结果,分配一个MYSQL_RES结构,并且把结果放进这个结构中。如果没有行返回,返回一个空结果集合。(空结果集合不同于一个NULL返回值。)一旦你调用了mysql_store_result(),你可以调用mysql_num_rows()找出结果集合中有多少行。你能调用mysql_fetch_row()从结果集合中取出行,或mysql_row_seek()和mysql_row_tell()结果集合中获得或设置当前的行位置。一旦你用完结果集合,你必须调用mysql_free_result()。如果成功则返回一个保存结果的MYSQL_RES结构,如果出现一个错误则返回NULL。


  8、MYSQL_RES *mysql_use_result(MYSQL *mysql);

    对于成功地检索数据的每个查询(SELECT、SHOW、DESCRIBE、EXPLAIN),你必须调用mysql_store_result()或mysql_use_result()。mysql_use_result()初始化一个结果集合的检索,但不真正将结果集合读入客户,就象mysql_store_result()那样。相反,必须通过调用mysql_fetch_row()单独检索出每一行,这直接从服务器读出结果而不在一个临时表或本地缓冲区中存储它,它比mysql_store_result()更快一点并且使用较少的内存。客户将只为当前行和一个可能最大max_allowed_packet字节的通信缓冲区分配内存。在另一方面,如果你在客户端对每一行正在做很多的处理,或如果输出被送到屏幕,用户可以打一个^S(停止滚动),你不应该使用mysql_use_result()。这将阻塞服务器并且阻止另外的线程从数据被取出的任何表中更新数据。当使用mysql_use_result()时,你必须执行mysql_fetch_row()直到返回一个NULL值,否则未取出的行将作为下一个查询的结果集合一部分被返回。如果你忘记做这个,C API将给出错误Commands out of sync; You can‘t run this command now!你不能在一个从mysql_use_result()返回的结果集合上使用mysql_data_seek()、mysql_row_seek()、mysql_row_tell()、mysql_num_rows()或mysql_affected_rows(),你也不能发出另外的查询直到mysql_use_result()完成。(然而,在你取出所有的行以后,mysql_num_rows()将精确地返回取出的行数。)一旦你用完结果集合,你必须调用mysql_free_result()。如果成功则一个MYSQL_RES结果结构。 如果发生一个错误发生则返回NULL。


  9、 int mysql_next_result(MYSQL *mysql);

    如果存在多个查询结果,mysql_next_result()将读取下一个查询结果,并将状态返回给应用程序。如果前面的查询返回了结果集,必须为其调用mysql_free_result()。函数返回0成功并有多个结果,返回-1成功但没有多个结果,返回结果大于0则出错。


  10、MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);

    检索一个结果集合的下一行。当在mysql_store_result()之后使用时,如果没有更多的行可见时,mysql_fetch_row()返回NULL。当在mysql_use_result()之后使用时,当没有更多的行可检索时或如果出现一个错误,mysql_fetch_row()返回NULL。在行中值的数量由mysql_num_fields(result)给出。如果row保存了从一个对用mysql_fetch_row()调用返回的值,指向该值的指针作为row[0]到row[mysql_num_fields(result)-1]来存取。在行中的NULL值由NULL指针指出。在行中字段值的长度可以通过调用mysql_fetch_lengths()获得。空字段和包含NULL的字段长度都是 0;你可以通过检查该值的指针区分他们。如果指针是NULL,字段是NULL;否则字段是空的。成功返回下一行的一个MYSQL_ROW结构,如果没有更多的行可检索或如果出现一个错误则返回NULL。


  11、unsigned int mysql_num_fields(MYSQL_RES *result)或unsigned int mysql_num_fields(MYSQL *mysql)。第二中形式在MySQL 3.22.24或更新版本上不能工作。为了传递一个MYSQL* 参数,你必须使用unsigned int mysql_field_count(MYSQL *mysql)。

    在结果集合中返回列的数量。注意,你也可以通过一个指向一个结果集合或一个连接句柄的指针获得列的数量。如果mysql_store_result()或mysql_user_result()返回NULL,你将使用连接句柄(而这样你没有结果集合指针)。在这种情况下,你可以调用mysql_field_count()确定mysql_store_result()是否应该产生非空的结果。这允许客户程序采取成正确的行动,不必知道查询是否是一个SELECT(或类SELECT)语句。下面被显示出的例子说明这怎么可以被做。

  更多API请参考MySQL中文API(http://www.cbi.pku.edu.cn/chinese/documents/csdoc/mysql/manual_Clients.html#C)或者官方API(http://dev.mysql.com/doc/refman/5.7/en/c-api-functions.html)。

三、使用C++通过C API封装MySQL连接库

  该类主要实现C++对MySQL C API的简单封装,使得使用C++连接MySQL时不必直接使用C API。一来练习一下好久没有使用的C++,二来练习一下MySQL提供的C API。


myDB.h:

#ifndef _MYDB_H
#define _MYDB_H
#include <mysql/mysql.h>

class myDB {
public :
    
    myDB();    //构造函数
    //myDB()=default;
    ~myDB();   //析构函数
    
    //连接数据库(无unix_socket参数),成功返回true,失败返回false
    bool connectDB(std::string host, std::string user, std::string passwd, std::string db, unsigned int port, unsigned int flag);
    //连接数据库,成功返回true,失败返回false
    bool connectDB(std::string host, std::string user, std::string passwd, std::string db, unsigned int port, std::string unix_socket, unsigned int flag);
    bool useSqlQuery(std::string sql); //使用SQL语句查询数据库
    bool useSqlModify(std::string sql); //使用SQL语句操作数据库
    void showResult(); //逐行输出获取到的信息

private:
    MYSQL *client = nullptr;
    MYSQL_RES *resource = nullptr;
    MYSQL_ROW row;
};

#endif

myDB.cpp

#include <iostream>
#include <cstdlib>
#include "myDB.h"
#include <mysql/mysql.h>

//构造函数
myDB::myDB()
{
    if ((client = mysql_init(nullptr)) == nullptr) {
        std::cerr << "The database init failed." << std::endl;
    }
}

//析构函数
myDB::~myDB()
{
    if (resource) {
        mysql_free_result(resource);
    }

    if (client) {
        mysql_close(client);
    }
}

//连接函数
bool myDB::connectDB(std::string host, std::string user, std::string passwd, 
                std::string db, unsigned int port, std::string unix_socket, unsigned int flag)
{
    if (!mysql_real_connect(client, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, unix_socket.c_str(), flag)) {
        std::cerr << "The database connect failed:" << mysql_error(client) << std::endl;
        return false;
    }

    return true;
}

bool myDB::connectDB(std::string host, std::string user, std::string passwd, 
                std::string db, unsigned int port, unsigned int flag)
{
    if (!mysql_real_connect(client, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, NULL, flag)) {
        std::cerr << "The database connect failed:" << mysql_error(client) << std::endl;
        return false;
    }

    return true;
}

//使用SQL语句查询数据库
bool myDB::useSqlQuery(std::string sql)
{
    if (mysql_real_query(client, sql.c_str(), sql.size())) {
        std::cerr << "query failed:" << mysql_error(client) << std::endl;
        return false;
    }

    if ((resource = mysql_store_result(client)) == nullptr) {
        std::cerr << "store failed:" << mysql_error(client) << std::endl;
        return false;
    }

    return true;
}

//使用SQL增删改数据库的值
bool myDB::useSqlModify(std::string sql)
{
    if (mysql_real_query(client, sql.c_str(), sql.size())) {
        std::cerr << "modify failed:" << mysql_error(client) << std::endl;
        return false;
    }

    return true;
}

//逐行输出获取到的信息
void myDB::showResult()
{
    if (resource) {
        while ((row = mysql_fetch_row(resource)) != NULL) {
            for (int j = 0; j < mysql_num_fields(resource); ++j) {
                std::cout << row[j] << "\t";
            }
            std::cout << std::endl;
        }
        mysql_free_result(resource);
        resource = nullptr;
    }
}

测试代码:

#include <iostream>
#include <cstdlib>
#include "myDB.h"

int main(int argc, char *argv[])
{
    myDB db;
    if (db.connectDB("127.0.0.1", "root", "", "crashcourse", 3306, 0) == false) {
        std::cout << "连接失败" <<std::endl; exit(-1);
    }

    if (db.useSqlQuery("SELECT * FROM orders") != false) {
        db.showResult();
    }

    if (!db.useSqlModify("INSERT INTO customers(cust_name, cust_address, cust_city) VALUES('haha', 'nicai', 'nihao')")) {
        std::cout << "插入失败" << std::endl;
    }
    if (!db.useSqlModify("INSERT INTO customers(cust_name, cust_address, cust_city) VALUES('hh', 'nicai', 'nihao')")) {
        std::cout << "插入失败" << std::endl;
    }

    if (!db.useSqlModify("DELETE FROM customers WHERE cust_name = 'haha'")) {
        std::cout << "删除失败" << std::endl;
    }


    return EXIT_SUCCESS;
}

四、总结

  基本步骤就是:

  • 使用mysql_init()初始化连接
  • 使用mysql_real_connect()建立一个到mysql数据库的连接
  • 使用mysql_query()执行查询/操作语句
  • result = mysql_store_result()获取结果集
  • mysql_fetch_row()获取查询的行数,mysql_num_fields()获取结果集的字段数
  • 通过mysql_fetch_row(result)不断获取下一行,然后循环输出
  • 释放结果集所占内存mysql_free_result()
  • mysql_close()关闭连接
  

  这层封装看似简单但是在实际的应用中还是挺有用的。最显而易见的就是在一个项目中如果将不同的DBMS产品提供的语言API进行一次封装后就可以实现DBMS层的操作和持久化层操作分离,进而减少业务代码对某个DBMS提供的API的依赖。如果后期更换数据库的话只考虑更改DBMS提供的API封装这一层的代码,使得工程代码的可拓展性大大增强。

C++操作数据库

标签:

原文地址:http://blog.csdn.net/jung_zhang/article/details/51587124

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!