C++封装MySQL操作函数

1、在Linux上安装MySQL

  • 具体如何安装MySQL大家可以参考这个大佬的文章,写的超级详细 CentOS 7 MySQL安装教程,这里就不赘述了
  • 环境就用我上一篇文章搭建的那个环境就行,可以点击这里去到上一篇环境搭建教程 bifang框架运行环境搭建入门指南
  • 这里给出一份已经下载好的MySQL8.0.18的安装包 点击进入下载页面
  • 需要注意的是需要把下图的出了test之外的安装包都给安装了,上面安装教程里面没有全部安装,没全部安装的话是没有MySQL的c库的,这样就没办法在C++中使用 MySQL 了
    在这里插入图片描述
  • 安装完成之后进入,输入 show DATABASES; 查看当前已有的数据库,如图所示,证明安装完成
    在这里插入图片描述
  • 然后使用数据库软件去连接Linux的MySQL,我使用的是Navicat Premium 15,据说这个比较流行,网上有很多破解使用教程,大家可以自行百度下载使用,首先设置数据库连接信息,然后连接数据库
    在这里插入图片描述
    在这里插入图片描述
    连上之后出现如下界面就是成功了在这里插入图片描述
  • 我们在MySQL下面新建一张表,名字叫做student,用于后续测试程序用,如下图所示
    在这里插入图片描述
    time记得设置为让他自动更新就行在这里插入图片描述

2、MySQL常用API

要想封装出一个易用的MySQL库,就需要先知道官方究竟开放了哪些接口给我们使用,大家可以自行去网上下载MySQL对应版本源码,由于我们其实不需要知道函数实现细节(主要那玩意比较难看懂。。。),所以直接去/usr/include/mysql文件夹里面将头文件全部拉到本地就可以获取到官方提供的c接口了(MySQL安装之后头文件的目录,不同系统不同MySQL发行版本安装的位置可能会不一样,这个得自己去找一下),大部分有用的信息都在mysql.h文件里面,大家可以把这个文件看一遍,不用去看对应的源码,就看一遍大概知道有什么可用的功能就行,接下来会列举我们用到的几个重要的结构体和API
在这里插入图片描述

2.1、结构体

  • MYSQL_RES是存放执行结果集的,当我们执行完查询的函数之后就会返回这个结构体给我们,我们就可以利用它来将查询结果取出来,大家可以结合源码去看看里面各个变量的作用,我们是不会直接操作这个结构体,这里知道它很重要就行
typedef struct MYSQL_RES {
  uint64_t row_count;
  MYSQL_FIELD *fields;
  struct MYSQL_DATA *data;
  MYSQL_ROWS *data_cursor;
  unsigned long *lengths; /* column lengths of current row */
  MYSQL *handle;          /* for unbuffered reads */
  const struct MYSQL_METHODS *methods;
  MYSQL_ROW row;         /* If unbuffered read */
  MYSQL_ROW current_row; /* buffer to current row */
  struct MEM_ROOT *field_alloc;
  unsigned int field_count, current_field;
  bool eof; /* Used by mysql_fetch_row */
  /* mysql_stmt_close() had to cancel this result */
  bool unbuffered_fetch_cancelled;
  enum enum_resultset_metadata metadata;
  void *extension;
} MYSQL_RES;
  • MYSQL_FIELD是用于存放结果集各个字段的信息(字段名、类型、最大长度等等),这个结构体在开发中应该算是使用频率很低的,因为我们要查询表数据肯定是事先知道表中有哪些字段了,甚至在很多情况下使用select语句都会直接指定要取出的参数而不是将整个表的字段全部取出。在后续代码中这个也只是用来做结果展示用而已(可以较为方便地列出查询结果的各个字段名)
typedef struct MYSQL_FIELD {
  char *name;               /* Name of column */
  char *org_name;           /* Original column name, if an alias */
  char *table;              /* Table of column if column was a field */
  char *org_table;          /* Org table name, if table was an alias */
  char *db;                 /* Database for table */
  char *catalog;            /* Catalog for table */
  char *def;                /* Default value (set by mysql_list_fields) */
  unsigned long length;     /* Width of column (create length) */
  unsigned long max_length; /* Max width for selected set */
  unsigned int name_length;
  unsigned int org_name_length;
  unsigned int table_length;
  unsigned int org_table_length;
  unsigned int db_length;
  unsigned int catalog_length;
  unsigned int def_length;
  unsigned int flags;         /* Div flags */
  unsigned int decimals;      /* Number of decimals in field */
  unsigned int charsetnr;     /* Character set */
  enum enum_field_types type; /* Type of field. See mysql_com.h for types */
  void *extension;
} MYSQL_FIELD;
  • MYSQL_BIND这个结构体非常重要,它是用于MySQL预处理中的一个重要的组成部分,在stmt过程中,输入的数据由该结构体提供,而最终输出的结果数据也是从这个结构体出来的,不过两者不是同一个内存空间的,我们在使用中需要定义两个MYSQL_BIND列表,一个作参数输入,另一个作结果输出。这个结构体建议大家要浏览一遍,官方注释很详细,里面大部分字段都很重要,最终编程中大部分都会用到
typedef struct MYSQL_BIND {
  unsigned long *length; /* output length pointer */
  bool *is_null;         /* Pointer to null indicator */
  void *buffer;          /* buffer to get/put data */
  /* set this if you want to track data truncations happened during fetch */
  bool *error;
  unsigned char *row_ptr; /* for the current data position */
  void (*store_param_func)(NET *net, struct MYSQL_BIND *param);
  void (*fetch_result)(struct MYSQL_BIND *, MYSQL_FIELD *, unsigned char **row);
  void (*skip_result)(struct MYSQL_BIND *, MYSQL_FIELD *, unsigned char **row);
  /* output buffer length, must be set when fetching str/binary */
  unsigned long buffer_length;
  unsigned long offset;              /* offset position for char/binary fetch */
  unsigned long length_value;        /* Used if length is 0 */
  unsigned int param_number;         /* For null count and error messages */
  unsigned int pack_length;          /* Internal length for packed data */
  enum enum_field_types buffer_type; /* buffer type */
  bool error_value;                  /* used if error is 0 */
  bool is_unsigned;                  /* set if integer type is unsigned */
  bool long_data_used;               /* If used with mysql_send_long_data */
  bool is_null_value;                /* Used if is_null is 0 */
  void *extension;
} MYSQL_BIND;
  • MYSQL_STMT和MYSQL这两个结构体就不需要过多了解了,有兴趣可以看一看,相当于一个控制器的作用,调用初始化函数之后就能得到他们,在使用各种API时经常得把他们作为参数传递进去
typedef struct MYSQL_STMT {
  struct MEM_ROOT *mem_root; /* root allocations */
  LIST list;                 /* list to keep track of all stmts */
  MYSQL *mysql;              /* connection handle */
  MYSQL_BIND *params;        /* input parameters */
  MYSQL_BIND *bind;          /* output parameters */
  MYSQL_FIELD *fields;       /* result set metadata */
  MYSQL_DATA result;         /* cached result set */
  MYSQL_ROWS *data_cursor;   /* current row in cached result */
  /*
    mysql_stmt_fetch() calls this function to fetch one row (it's different
    for buffered, unbuffered and cursor fetch).
  */
  int (*read_row_func)(struct MYSQL_STMT *stmt, unsigned char **row);
  /* copy of mysql->affected_rows after statement execution */
  uint64_t affected_rows;
  uint64_t insert_id;          /* copy of mysql->insert_id */
  unsigned long stmt_id;       /* Id for prepared statement */
  unsigned long flags;         /* i.e. type of cursor to open */
  unsigned long prefetch_rows; /* number of rows per one COM_FETCH */
  /*
    Copied from mysql->server_status after execute/fetch to know
    server-side cursor status for this statement.
  */
  unsigned int server_status;
  unsigned int last_errno;            /* error code */
  unsigned int param_count;           /* input parameter count */
  unsigned int field_count;           /* number of columns in result set */
  enum enum_mysql_stmt_state state;   /* statement state */
  char last_error[MYSQL_ERRMSG_SIZE]; /* error message */
  char sqlstate[SQLSTATE_LENGTH + 1];
  /* Types of input parameters should be sent to server */
  bool send_types_to_server;
  bool bind_param_done;           /* input buffers were supplied */
  unsigned char bind_result_done; /* output buffers were supplied */
  /* mysql_stmt_close() had to cancel this result */
  bool unbuffered_fetch_cancelled;
  /*
    Is set to true if we need to calculate field->max_length for
    metadata fields when doing mysql_stmt_store_result.
  */
  bool update_max_length;
  struct MYSQL_STMT_EXT *extension;
} MYSQL_STMT;

typedef struct MYSQL {
  NET net;                     /* Communication parameters */
  unsigned char *connector_fd; /* ConnectorFd for SSL */
  char *host, *user, *passwd, *unix_socket, *server_version, *host_info;
  char *info, *db;
  struct CHARSET_INFO *charset;
  MYSQL_FIELD *fields;
  struct MEM_ROOT *field_alloc;
  uint64_t affected_rows;
  uint64_t insert_id;      /* id if insert on table with NEXTNR */
  uint64_t extra_info;     /* Not used */
  unsigned long thread_id; /* Id for connection in server */
  unsigned long packet_length;
  unsigned int port;
  unsigned long client_flag, server_capabilities;
  unsigned int protocol_version;
  unsigned int field_count;
  unsigned int server_status;
  unsigned int server_language;
  unsigned int warning_count;
  struct st_mysql_options options;
  enum mysql_status status;
  enum enum_resultset_metadata resultset_metadata;
  bool free_me;   /* If free in mysql_close */
  bool reconnect; /* set to 1 if automatic reconnect */

  /* session-wide random string */
  char scramble[SCRAMBLE_LENGTH + 1];

  LIST *stmts; /* list of all statements */
  const struct MYSQL_METHODS *methods;
  void *thd;
  /*
    Points to boolean flag in MYSQL_RES  or MYSQL_STMT. We set this flag
    from mysql_stmt_close if close had to cancel result set of this object.
  */
  bool *unbuffered_fetch_owner;
  void *extension;
} MYSQL;

2.2、API

// 获取结果集里面的字段数目
unsigned int STDCALL mysql_num_fields(MYSQL_RES *res);
// 获取结果集的MYSQL_FIELD
MYSQL_FIELD *STDCALL mysql_fetch_fields(MYSQL_RES *res);
// 获取结果集当前指向的行数据(MYSQL_ROW = char**)
MYSQL_ROW STDCALL mysql_fetch_row(MYSQL_RES *result);
// 获取结果集指向的行数据每个字段的长度(数据指针和数据长度结合才能得出查询的真实数据)
unsigned long *STDCALL mysql_fetch_lengths(MYSQL_RES *result);
// 释放结果集的内存
void STDCALL mysql_free_result(MYSQL_RES *result);
// 释放stmt结果集的内存
bool STDCALL mysql_stmt_free_result(MYSQL_STMT *stmt);
// 关闭stmt并释放对应的内存
bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt);
// 获取stmt错误码
unsigned int STDCALL mysql_stmt_errno(MYSQL_STMT *stmt);
// 获取stmt错误码对应的信息
const char *STDCALL mysql_stmt_error(MYSQL_STMT *stmt);
// 返回insert或update语句为AUTO_INCREMENT列生产的值, 在包含AUTO_INCREMENT字段的表上执行了预处理语句后使用
uint64_t STDCALL mysql_stmt_insert_id(MYSQL_STMT *stmt);
// 获取MySQL错误码
unsigned int STDCALL mysql_errno(MYSQL *mysql);
// 获取MySQL错误码对应的信息
const char *STDCALL mysql_error(MYSQL *mysql);
// 返回insert或update语句为AUTO_INCREMENT列生产的值,
uint64_t STDCALL mysql_insert_id(MYSQL *mysql);
// 返回前一次MySQL操作所影响的记录行数
uint64_t STDCALL mysql_affected_rows(MYSQL *mysql);
// MySQL线程相关
bool STDCALL mysql_thread_init(void);
void STDCALL mysql_thread_end(void);
// 返回预处理结果集(仅包含元数据,不包含执行结果数据,经常配合mysql_num_fields和mysql_fetch_fields一起使用)
MYSQL_RES *STDCALL mysql_stmt_result_metadata(MYSQL_STMT *stmt);
// 将stmt结果数据的机构体绑定上去(最终结果通过第二个参数返回)
bool STDCALL mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bnd);
// 执行stmt命令
int STDCALL mysql_stmt_execute(MYSQL_STMT *stmt);
// 获取stmt执行结果(结果返回到mysql_stmt_bind_result绑定的那个结构体里了)
int STDCALL mysql_stmt_store_result(MYSQL_STMT *stmt);
// 将stmt的结果集指向下一行(结果返回到mysql_stmt_bind_result绑定的那个结构体里了)
int STDCALL mysql_stmt_fetch(MYSQL_STMT *stmt);
// 返回stmt结果集的总行数
uint64_t STDCALL mysql_stmt_num_rows(MYSQL_STMT *stmt);
// 初始化stmt操作
MYSQL_STMT *STDCALL mysql_stmt_init(MYSQL *mysql);
// 解析stmt预处理指令
int STDCALL mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query,
                               unsigned long length);
// 获取stmt所需参数个数
unsigned long STDCALL mysql_stmt_param_count(MYSQL_STMT *stmt);
// 将要执行的参数绑定到stmt上
bool STDCALL mysql_stmt_bind_param(MYSQL_STMT *stmt, MYSQL_BIND *bnd);
// 初始化MySQL
MYSQL *STDCALL mysql_init(MYSQL *mysql);
// 设置MySQL属性,具体可以设置的类型以对应版本为主,不同版本之间是会由些许差异的
int STDCALL mysql_options(MYSQL *mysql, enum mysql_option option,
                          const void *arg);
// 连接MySQL
MYSQL *STDCALL 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 clientflag);
// 关闭MySQL并释放对应的内存
void STDCALL mysql_close(MYSQL *sock);
// ping数据库
int STDCALL mysql_ping(MYSQL *mysql);
// 切换数据库
int STDCALL mysql_select_db(MYSQL *mysql, const char *db);
// 数据库查询,执行查询操作,两个函数都可以,建议用下面那个,因为可以不用c类型的字符串作参数
int STDCALL mysql_query(MYSQL *mysql, const char *q);
int STDCALL mysql_real_query(MYSQL *mysql, const char *q, unsigned long length);
// 获取查询结果
MYSQL_RES *STDCALL mysql_store_result(MYSQL *mysql);
/*
  mysql_server_init/end need to be called when using libmysqld or
  libmysqlclient (exactly, mysql_server_init() is called by mysql_init() so
  you don't need to call it explicitely; but you need to call
  mysql_server_end() to free memory). The names are a bit misleading
  (mysql_SERVER* to be used when using libmysqlCLIENT). So we add more general
  names which suit well whether you're using libmysqld or libmysqlclient. We
  intend to promote these aliases over the mysql_server* ones.
*/
// 这里贴上官方的原版注释,因为我看不懂,看不懂的就是好解释
int STDCALL mysql_server_init(int argc, char **argv, char **groups);
#define mysql_library_init mysql_server_init

3、MySQL封装细节

3.1、修改掉源码中的部分错误内容

这里先给出一份已经完成的代码,点击这里下载 mysql c++封装.zip,解压之后进去编译一下发现有错误(如果没有则忽略这段直接看下面的内容),如下图所示,可以看出mysql/udf_registration_types.h有个错误,直接把错误的行给屏蔽即可编译通过
在这里插入图片描述
屏蔽这里在这里插入图片描述

3.2、封装六个常用的类

/**
 * brief: MySQL查询结果集类
 */
class MySQLRes/**
 * brief: MySQL预处理查询结果集类(由于需要事先分配储存结果的内存, 所以需要在建表时需要确认数据长度(int, float之类的数据不需要), 不然无法使用该功能)
 */
class MySQLStmtRes/**
 * brief: MySQL预处理类
 */
class MySQLStmt;

/**
 * brief: MySQL类
 */
class MySQL;

/**
 * brief: MySQL事务类
 */
class MySQLTransaction;

/**
 * brief: MySQL管理类
 */
class MySQLManager;

3.2.1、MySQLRes

该类是用来存放MySQL查询结果的,在执行完查询之后应该返回这个类,类成员有以下几个:

// 结果集列名
std::vector<std::string> m_fields;
// 结果集数据
std::unordered_map<std::string, std::string*> m_datas;
// MYSQL结果集智能指针
std::shared_ptr<MYSQL_RES> m_res;

执行结果集偏移的方法为

/**
 * brief: 将结果集移向下一行
 * return: true  - 成功
 *         false - 失败
 */
bool next()
{
    if (m_fields.empty())
    {
        std::cout << "the query results have no fields!" << std::endl;
        return false;
    }
    // 数据集当前指向的行数据(MYSQL_ROW = char**)
    MYSQL_ROW cur = mysql_fetch_row(m_res.get());
    if (!cur)
        return false;
    // 当前行每一列的数据长度
    unsigned long* curLength = mysql_fetch_lengths(m_res.get());
    int len = mysql_num_fields(m_res.get());
    for (int i = 0; i < len; i++)
    {
        if (m_datas[m_fields[i]])
        {
            delete m_datas[m_fields[i]];
            m_datas[m_fields[i]] = nullptr;
        }
        if (cur[i])
            m_datas[m_fields[i]] = new std::string(cur[i], curLength[i]);
    }
    return true;
}

获取字段值的方法为

#define XX() \
    auto it = m_datas.find(name); \
    if (it == m_datas.end()) \
        throw "field(" + name + ") is not exist!"

    bool isNull(const std::string& name) const
    {
        XX();
        return !it->second;
    }
    int getInt(const std::string& name) const
    {
        XX();
        return atol((*it->second).c_str());
    }
    int64_t getInt64(const std::string& name) const
    {
        XX();
        return atoll((*it->second).c_str());
    }
    float getFloat(const std::string& name) const
    {
        XX();
        return atof((*it->second).c_str());
    }
    double getDouble(const std::string& name) const
    {
        XX();
        return atof((*it->second).c_str());
    }
    std::string getString(const std::string& name) const
    {
        XX();
        return *it->second;
    }
#undef XX

3.2.2、MySQLStmtRes

该类是用来存放stmt查询结果的,在执行完stmt的查询之后应该返回这个类
类成员有以下几个

// MySQL预处理类智能指针
std::shared_ptr<MySQLStmt> m_stmt;
// 绑定参数(相当于壳)
std::vector<MYSQL_BIND> m_binds;
// 结果集列名
std::vector<std::string> m_fields;
// 结果集数据
std::unordered_map<std::string, Data> m_datas;

其中Data为自定义的结构体,用于分配存储结果集的内存, 实现统一的写入和读取

struct Data
{
    ~Data()
    {
        if (buffer)
            delete[] buffer;
    }

    void alloc(size_t size)
    {
        if (buffer)
            delete[] buffer;

        buffer = new char[size];
        buffer_length = size;
    }

    uint64_t length = 0;
    bool is_null = false;
    bool error = false;
    char* buffer = nullptr;
    uint64_t buffer_length = 0;
    enum_field_types buffer_type = MYSQL_TYPE_NULL;
};

执行结果集偏移的方法如下所示,可以看到由于stmt的结果是从绑定的内存输出的,所以这里无须做任何处理,只要调用mysql_stmt_fetch结果数据就会流向m_datas中

bool MySQLStmtRes::next()
{
    return !mysql_stmt_fetch(m_stmt->get());
}

获取字段值的方法为:

#define XX() \
    auto it = m_datas.find(name); \
    if (it == m_datas.end()) \
        throw "field(" + name + ") is not exist!"

    bool isNull(const std::string& name) const
    {
        XX();
        return it->second.is_null;
    }
    int8_t getInt8(const std::string& name) const
    {
        XX();
        if (it->second.buffer_type == MYSQL_TYPE_TINY)
            return *(int8_t*)it->second.buffer;
        std::string str = getString(name);
        return atol(str.c_str());
    }
    int16_t getInt16(const std::string& name) const
    {
        XX();
        if (it->second.buffer_type == MYSQL_TYPE_SHORT)
            return *(int16_t*)it->second.buffer;
        std::string str = getString(name);
        return atol(str.c_str());
    }
    int32_t getInt32(const std::string& name) const
    {
        XX();
        if (it->second.buffer_type == MYSQL_TYPE_LONG)
            return *(int32_t*)it->second.buffer;
        std::string str = getString(name);
        return atol(str.c_str());
    }
    int64_t getInt64(const std::string& name) const
    {
        XX();
        if (it->second.buffer_type == MYSQL_TYPE_LONGLONG)
            return *(int64_t*)it->second.buffer;
        std::string str = getString(name);
        return atoll(str.c_str());
    }
    float getFloat(const std::string& name) const
    {
        XX();
        if (it->second.buffer_type == MYSQL_TYPE_FLOAT)
            return *(float*)it->second.buffer;
        std::string str = getString(name);
        return atof(str.c_str());
    }
    double getDouble(const std::string& name) const
    {
        XX();
        if (it->second.buffer_type == MYSQL_TYPE_DOUBLE)
            return *(double*)it->second.buffer;
        std::string str = getString(name);
        return atof(str.c_str());
    }
    std::string getString(const std::string& name, bool is_convert = false) const
    {
        XX();
        switch (it->second.buffer_type)
        {
            case MYSQL_TYPE_TINY:
                return std::to_string(*(int8_t*)it->second.buffer);
            case MYSQL_TYPE_SHORT:
                return std::to_string(*(int16_t*)it->second.buffer);
            case MYSQL_TYPE_LONG:
                return std::to_string(*(int32_t*)it->second.buffer);
            case MYSQL_TYPE_LONGLONG:
                return std::to_string(*(int64_t*)it->second.buffer);
            case MYSQL_TYPE_FLOAT:
                return std::to_string(*(float*)it->second.buffer);
            case MYSQL_TYPE_DOUBLE:
                return std::to_string(*(double*)it->second.buffer);
            case MYSQL_TYPE_TIMESTAMP:
            case MYSQL_TYPE_DATETIME:
            case MYSQL_TYPE_DATE:
            case MYSQL_TYPE_TIME:
            {
                time_t t = mysql_time_to_time_t(*(MYSQL_TIME*)it->second.buffer);
                if (is_convert)
                    return time_to_string(t);
                else
                    return std::to_string(t);
            }
            default:
                return std::string(it->second.buffer, it->second.length);
        }
    }
    time_t getTime(const std::string& name) const
    {
        XX();
        if (it->second.buffer_type == MYSQL_TYPE_TIMESTAMP ||
            it->second.buffer_type == MYSQL_TYPE_DATETIME  ||
            it->second.buffer_type == MYSQL_TYPE_DATE      ||
            it->second.buffer_type == MYSQL_TYPE_TIME)
            return mysql_time_to_time_t(*(MYSQL_TIME*)it->second.buffer);
         return 0;
    }
#undef XX

3.2.3、MySQLStmt

该类是用来管理stmt操作的,需要提供的功能由解析stmt命令、绑定参数、执行命令
其中绑定参数要绑定两次,一次是绑定stmt传入参数的,即?的值,如下所示,在设计上,重载了一组绑定的方法,然后利用c++可变参模板来实现一个支持绑定多个参数的方法,如下所示

// 适应各种类型参数的绑定方法
#define BIND_XX(type, symbol, ptr, size) \
    m_binds[idx].buffer_type = type; \
    m_binds[idx].is_unsigned = symbol; \
    if (m_binds[idx].buffer == nullptr) \
    { \
        m_binds[idx].buffer = malloc(size); \
        m_binds[idx].buffer_length = size; \
    } \
    else if (m_binds[idx].buffer_length != size) \
    { \
        free(m_binds[idx].buffer); \
        m_binds[idx].buffer = malloc(size); \
        m_binds[idx].buffer_length = size; \
    } \
    memcpy(m_binds[idx].buffer, ptr, size);

    void bind(int idx, const MySQLNull& value)
    {
        m_binds[idx].buffer_type = MYSQL_TYPE_NULL;
        if (m_binds[idx].buffer != nullptr)
        {
            free(m_binds[idx].buffer);
            m_binds[idx].buffer = nullptr;
        }
    }
    void bind(int idx, const int8_t& value)
    {
        BIND_XX(MYSQL_TYPE_TINY, false, &value, sizeof(value));
    }
    void bind(int idx, const uint8_t& value)
    {
        BIND_XX(MYSQL_TYPE_TINY, true, &value, sizeof(value));
    }
    void bind(int idx, const int16_t& value)
    {
        BIND_XX(MYSQL_TYPE_SHORT, false, &value, sizeof(value));
    }
    void bind(int idx, const uint16_t& value)
    {
        BIND_XX(MYSQL_TYPE_SHORT, true, &value, sizeof(value));
    }
    void bind(int idx, const int32_t& value)
    {
        BIND_XX(MYSQL_TYPE_LONG, false, &value, sizeof(value));
    }
    void bind(int idx, const uint32_t& value)
    {
        BIND_XX(MYSQL_TYPE_LONG, true, &value, sizeof(value));
    }
    void bind(int idx, const int64_t& value)
    {
        BIND_XX(MYSQL_TYPE_LONGLONG, false, &value, sizeof(value));
    }
    void bind(int idx, const uint64_t& value)
    {
        BIND_XX(MYSQL_TYPE_LONGLONG, true, &value, sizeof(value));
    }
    void bind(int idx, const float& value)
    {
        BIND_XX(MYSQL_TYPE_FLOAT, false, &value, sizeof(value));
    }
    void bind(int idx, const double& value)
    {
        BIND_XX(MYSQL_TYPE_DOUBLE, false, &value, sizeof(value));
    }
    void bind(int idx, const std::string& value)
    {
        BIND_XX(MYSQL_TYPE_STRING, false, value.c_str(), value.size());
    }
    //
    void bind(int idx, const void* value, uint32_t size)
    {
        BIND_XX(MYSQL_TYPE_BLOB, false, value, size);
    }
    void bind(int idx, const void* value, uint64_t size)
    {
        BIND_XX(MYSQL_TYPE_BLOB, false, value, size);
    }
    //
    void bind(int idx, const MySQLTime& value)
    {
        MYSQL_TIME mt = time_t_to_mysql_time(value.ts);
        BIND_XX(MYSQL_TYPE_TIMESTAMP, false, &mt, sizeof(MYSQL_TIME));
    }
// 利用可变参模板设计的一个能同时绑定多个参数的方法
	template<typename... Args>
    void multibind(Args... args)
    {
        binder(0, args...);
    }

    void binder(size_t N)
    {
        //std::cout << "multibind end" << std::endl;
    }
    template<typename... Args>
    void binder(size_t N, const void* value, uint64_t size, Args... args)
    {
        if (N >= m_binds.size())
            return;
        bind(N, value, size);
        binder(N + 1, args...);
    }
    template<typename T, typename... Args>
    void binder(size_t N, T value, Args... args)
    {
        if (N >= m_binds.size())
            return;
        bind(N, value);
        binder(N + 1, args...);
    }

另一个绑定输出结果参数的函数如下所示,可以看到确实将m_datas的内存空间交给了stmt去使用

MySQLStmtRes::ptr MySQLStmtRes::create(std::shared_ptr<MySQLStmt> stmt)
{
    if (stmt->getErrno())
    {
        std::cout << "stmt error, errno=" << stmt->getErrno()
            << ", errstr=" << stmt->getErrstr() << std::endl;
        return nullptr;
    }
    MySQLStmtRes::ptr ret(new MySQLStmtRes(stmt));

    MYSQL_RES* res = mysql_stmt_result_metadata(stmt->get());
    if (!res)
    {
        std::cout << "mysql_stmt_result_metadata error, errno="
            << stmt->getErrno() << ", errstr=" << stmt->getErrstr() << std::endl;
        return nullptr;
    }
    int len = mysql_num_fields(res);
    MYSQL_FIELD* fields = mysql_fetch_fields(res);
    ret->m_binds.resize(len);
    memset(&ret->m_binds[0], 0, sizeof(ret->m_binds[0]) * len);

#define XX(m, t) \
    case m: \
        ret->m_datas[name].alloc(sizeof(t)); \
        break
    for (int i = 0; i < len; i++)
    {
        std::string name = std::string(fields[i].name, fields[i].name_length);
        ret->m_fields.push_back(name);

        switch (fields[i].type)
        {
            XX(MYSQL_TYPE_TINY, int8_t);
            XX(MYSQL_TYPE_SHORT, int16_t);
            XX(MYSQL_TYPE_LONG, int32_t);
            XX(MYSQL_TYPE_LONGLONG, int64_t);
            XX(MYSQL_TYPE_FLOAT, float);
            XX(MYSQL_TYPE_DOUBLE, double);
            XX(MYSQL_TYPE_TIMESTAMP, MYSQL_TIME);
            XX(MYSQL_TYPE_DATETIME, MYSQL_TIME);
            XX(MYSQL_TYPE_DATE, MYSQL_TIME);
            XX(MYSQL_TYPE_TIME, MYSQL_TIME);
            default:
                ret->m_datas[name].alloc(fields[i].length);
                break;
        }

        ret->m_datas[name].buffer_type = fields[i].type;

        ret->m_binds[i].length = &ret->m_datas[name].length;
        ret->m_binds[i].is_null = &ret->m_datas[name].is_null;
        ret->m_binds[i].buffer = ret->m_datas[name].buffer;
        ret->m_binds[i].error = &ret->m_datas[name].error;
        ret->m_binds[i].buffer_length = ret->m_datas[name].buffer_length;
        ret->m_binds[i].buffer_type = ret->m_datas[name].buffer_type;
    }
#undef XX

    if (mysql_stmt_bind_result(stmt->get(), &ret->m_binds[0]))
    {
        std::cout << "mysql_stmt_bind_result error, errno="
            << stmt->getErrno() << ", errstr=" << stmt->getErrstr() << std::endl;
        return nullptr;
    }

    if (mysql_stmt_execute(stmt->get()))
    {
        std::cout << "mysql_stmt_execute error, errno="
            << stmt->getErrno() << ", errstr=" << stmt->getErrstr() << std::endl;
        return nullptr;
    }

    if (mysql_stmt_store_result(stmt->get()))
    {
        std::cout << "mysql_stmt_store_result error, errno="
            << stmt->getErrno() << ", errstr=" << stmt->getErrstr() << std::endl;
        return nullptr;
    }

    return ret;
}

3.2.4、MySQL

该类是最重要的一个,实现起来较为简单,需要对外提供的功能有:初始化数据库、连接数据库、ping、切换数据库、执行命令获取结果、创建预处理还有创建事务等等,这里就不全部展开讲了,贴上connect方法和query方法,其余的功能大家可以下载源码去看一下

// 需要注意的是如果代码hook了read和write函数的话,MySQL的MYSQL_OPT_RECONNECT选项不能开启(血与泪的教训。。。)
bool MySQL::connect()
{
    static thread_local MySQLThreadInit s_thread_init;

    if (m_mysql && !m_hasError)
        return true;

    MYSQL* mysql = ::mysql_init(nullptr);
    if (mysql == nullptr)
    {
        std::cout << "mysql_init error" << std::endl;
        m_hasError = true;
        return false;
    }

    int auto_reconnect = 0;
    mysql_options(mysql, MYSQL_OPT_RECONNECT, &auto_reconnect);
    mysql_options(mysql, MYSQL_SET_CHARSET_NAME, "utf8mb4"); // 用utf8mb4是为了兼容unicode
    if (mysql_real_connect(mysql, m_host.c_str(), m_user.c_str(),
            m_passwd.c_str(), m_dbname.c_str(), m_port, NULL, 0) == nullptr)
    {
        std::cout << "mysql_real_connect(" << m_host
                  << ", " << m_port << ", " << m_dbname
                  << ") error: " << mysql_error(mysql) << std::endl;
        mysql_close(mysql);
        m_hasError = true;
        return false;
    }

    m_hasError = false;
    m_mysql.reset(mysql, mysql_close);
    return true;
}

// 这里可以看到query返回的结果为MySQLRes的智能指针,和我们一开始说的设计是一致的,最终从MySQLRes中获取结果
MySQLRes::ptr MySQL::query(const char* format, ...)
{
    if (!m_mysql)
    {
        std::cout << "m_mysql is NULL" << std::endl;
        m_hasError = true;
        return nullptr;
    }

    std::string cmd;
    {
        va_list ap;
        va_start(ap, format);
        char* buf = nullptr;
        int len = vasprintf(&buf, format, ap);
        if (len != -1)
        {
            cmd.append(buf, len);
            free(buf);
        }
        va_end(ap);
    }

    if (::mysql_real_query(m_mysql.get(), &cmd[0], cmd.size()))
    {
        std::cout << "mysql_real_query(" << cmd << ") error:" << getErrstr() << std::endl;
        m_hasError = true;
        return nullptr;
    }
    MYSQL_RES* res = mysql_store_result(m_mysql.get());
    if (res == nullptr)
    {
        std::cout << "mysql_store_result(" << cmd << ") error:" << getErrstr() << std::endl;
        m_hasError = true;
        return nullptr;
    }

    m_hasError = false;
    MySQLRes::ptr ret(new MySQLRes(res));
    return ret;
}

3.2.5、MySQLTransaction

该类为事务类,事务的接口实现较为简单,这里只实现了最简单的形式,即开始事务、提交事务、回滚事务。由于实现起来太简单了这里就不贴出具体代码了

3.2.6、MySQLManager

该类是一个管理类,用于统一管理所有 MySQL 连接,这样可以很方便地结合配置文件来使用MySQL,而且也可以在每次分配连接时都检查是否需要重新连接数据库,防止服务器因为超时把连接断开,并且提供了回收机制,当MySQL池的数据小于我们设置的容量时,每一个被释放的连接都可以重新回到数据池里面循环使用,是借用智能指针来实现这个功能的,大家有兴趣可以看一看具体实现的做法

/**
 * brief: MySQL管理类
 */
class MySQLManager
{
public:
    typedef Mutex MutexType;

    struct MySqlConf
    {
        std::string host;
        int port;
        std::string user;
        std::string passwd;
        std::string dbname;
        uint32_t poolSize = 10;
    };

    MySQLManager();

    ~MySQLManager();

    void add(const std::string& name, const std::string& host, int port,
        const std::string& user, const std::string& passwd,
        const std::string& dbname, uint32_t poolSize = 10);

    MySQL::ptr get(const std::string& name);

    bool execute(const std::string& name, const char* format, ...);
    bool execute(const std::string& name, const std::string& cmd);

    MySQLRes::ptr query(const std::string& name, const char* format, ...);
    MySQLRes::ptr query(const std::string& name, const std::string& cmd);

    MySQLStmt::ptr openPrepare(const std::string& name, const std::string& cmd);

    MySQLTransaction::ptr openTransaction(const std::string& name, bool auto_commit);

    void checkConnection(int sec = 30);

private:
    void freeMySQL(const std::string& name, MySQL* m);

private:
    MutexType m_mutex;
    std::unordered_map<std::string, std::list<MySQL*> > m_connections;
    std::unordered_map<std::string, MySqlConf> m_sqlDefines;
};

4、总结并附上本文源代码

MySQL官方给出的接口整体来说还是比较清晰的,注释也很不错,不过还是建议要先有一定数据库的基础再去看这些。
最后附上一份源代码,大家可以下载下去调试使用看看,有什么错误的地方也欢迎大家指出
mysql c++封装.zip


版权声明:本文为weixin_43798887原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。