C++封装MySQL操作函数_c++编译封装mysql的-程序员宅基地

技术标签: c++  MySQL技术研究  mysql  后端  数据库  sql  

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43798887/article/details/116100645

智能推荐

元素选择器之排除特定元素_input排他选择器-程序员宅基地

文章浏览阅读2.1k次。 需求如下:该搜索框是对整个页面的input检索 ,但与弹出层中的input冲突 博主几经辗转 简单处理 解决问题,思路如下:排除掉特定class的input。代码如下:$('input:not(.pop)', this.footer()).on('keyup change', function () { if (that.search() !== th..._input排他选择器

使用JAXB进行XML与JavaBean的转换(支持泛型)_jaxb 泛型-程序员宅基地

文章浏览阅读5.6k次,点赞6次,收藏20次。看到别人有个1024的勋章,特意留了一篇在今年的10.24日,看看会不会获得。在日常开发中可能涉及接口之间的相互调用,虽然在现在微服务的理念推广下,很多公司都采用轻量级的JSON格式做为序列化的格式,但是不乏有些公司还是有一些XML格式的报文,最近就在对接某个合作方的时候遇到了XML报文。在JSON报文爽快的转换下很难试用一个一个的拿报文参数,还是希望能直接将报文转换成Bean。接下来就了解到..._jaxb 泛型

python numpy学习笔记_ndarray的位置-程序员宅基地

文章浏览阅读1.2k次。numpy的主要数据对象是多维数组,其中包含相同类型的元素,通常是数字类型,每个元素都有一个索引。使用numpy前通常要导入包。import numpy as np目录类型维度创建运算索引和切片类型numpy的数组被称为ndarray。numpy.array只处理一维数组,而ndarray对象才提供更多功能。a = np.array([[1, 2, 3], [4, 5, 6]])type(a) # <class 'numpy.ndarray'>dtype属性可以获得元素的数_ndarray的位置

我的世界java版gamemode指令_《我的世界》Java版常用指令代码大全!你想要的都在这里了!...-程序员宅基地

文章浏览阅读1.6w次。还在苦于网上找到的一些指令已经不适用了吗?还在苦于有些地方的指令有误吗?还在苦于有些地方整理的指令不够全面吗?那么你来对地方了!小编为大家整理了《我的世界》原版游戏常用的指令,这些基本足以满足各位的基本需求了!大家来一起看看吧!注:表示的是必须输入的部分,[方括号]表示的是可选择性输入的部分基本命令列表命令描述/?/help的替代命令,提供命令使用帮助。/ban + 玩家名字将玩家加入封禁列表。/..._gamemode指令java

Spring Boot 结合shiro做第三方登录验证_shiro 第三方token登录-程序员宅基地

文章浏览阅读1.5w次,点赞3次,收藏3次。Spring Boot 结合shiro做第三方登录验证1、首先,说一下我的具体实现思路。在做spring boot拦截器的过程中,开始我准备用spring security来实现,但是研究了一段时间之后发现spring security的集成度太高,需要修改的东西比较多,而且对它本身的使用方法不是很了解,后来转而使用Apache shiro。由于是第三方登录,是不需要我来验证密码的。最开始,我陷入了_shiro 第三方token登录

labelme UnicodeDecodeError: ‘gbk‘ codec can‘t decode byte 0xaf in position 227: illegal mult_file "c:\rgzn\labelme-main\setup.py", line 91, in -程序员宅基地

文章浏览阅读1.9k次,点赞4次,收藏4次。[INFO ] __init__:get_config:71 - Loading config file from:C:\Users\xxx\.labelmercTraceback (most recent call last): File .... line 191, in <module> main() File ...., line 145, in main config = get_config(config_file_or_yaml, config_fro_file "c:\rgzn\labelme-main\setup.py", line 91, in main if sys.argv[1] == "re

随便推点

代码报错原因和处理方法-程序员宅基地

文章浏览阅读8.7k次。代码错误的原因和调试方法_代码报错

深度解析Java游戏服务器开发-程序员宅基地

文章浏览阅读5.2k次,点赞9次,收藏40次。---恢复内容开始---1.认识游戏  1.1什么是游戏    1.1.1游戏的定义              任何人类正常生理需求之外的活动均可称为游戏    1.1.2游戏的分类      RPG角色扮演游戏、ACT动作游戏、AVG冒险游戏、FPS第一人称视角射击游戏、TPS第三人称视角射击游戏、FTG格斗游戏、SPT体育游戏、RAC竞速游戏、RTS即时战略游戏、STG..._深度解析java游戏服务器开发

【ThinkPHP5初体验(二)1】CSRF防范原理(thinkphp5 CSRF ajax令牌)_tp5 开启csrf令牌-程序员宅基地

文章浏览阅读4k次。CSRF是什么我就不解释了,百度一搜全是,比波姐的片源还要多,千篇一律都他么是复制粘贴。那为什么这个令牌(token)操作可以防范CSRF呢?下面我就随便说说说错了大家不要介意。首先我们要知道令牌是存储在session里面的,这个很重要 php代码如下&lt;?php namespace app\index\controller; //我直接允许跨域,因为伪装..._tp5 开启csrf令牌

市盈率、市净率、净资产收益率股息率介绍-程序员宅基地

文章浏览阅读1.7k次,点赞2次,收藏6次。市盈率PE市盈率 = 市值/净利润概念解析:买入一家公司,几年回本,年化收益率:净利润/市值(市盈率的倒数)举例:砖头10万买个砖头,每年拍人带来1万利润,需要10年回本市盈率:10/1 = 10年化收益率:1/10 = 10%市净率PB市净率 = 市值/净资产净资产 = 总资产 - 负债举例:张三便利店,净资产:120万市值:1..._净资产收益率和股息率

墨器杯垫 文创商品设计特优_杯垫文创设计说明-程序员宅基地

文章浏览阅读737次。教育部昨举行「102年国立馆所文创商品设计比赛」颁奖典礼,台北科技大学创新设计研究所硕士生谢镇宇,为TW艺术教育馆设计「墨器」杯垫,取「默契」谐音,用5片压克力板,展现水墨画层层渲染效果,增加立体视觉感受,并在杯架后方加入LED光源,获评审肯定夺特优奖和奖金10万元。台南应用科技大学商品设计系学生高郁翔,为国立自然科学博物馆设计「恐龙化石钉书机」,他认为小朋友把钉书机钉下去的那一刻,会觉得像暴龙準_杯垫文创设计说明

C#中关于XML与对象,集合的相互转换-程序员宅基地

文章浏览阅读404次。XML与对象,集合的相互转化  今天小伙伴在群里问了一下关于XML与对象之间的相互转换,作为菜鸟的我正好趁着闲着的时间学习了一波,直接上代码了,有疑问或者有错误的地方还请大家指正,谢谢。。。。 1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System...._c# xml转集合