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

智能推荐

稀疏编码的数学基础与理论分析-程序员宅基地

文章浏览阅读290次,点赞8次,收藏10次。1.背景介绍稀疏编码是一种用于处理稀疏数据的编码技术,其主要应用于信息传输、存储和处理等领域。稀疏数据是指数据中大部分元素为零或近似于零的数据,例如文本、图像、音频、视频等。稀疏编码的核心思想是将稀疏数据表示为非零元素和它们对应的位置信息,从而减少存储空间和计算复杂度。稀疏编码的研究起源于1990年代,随着大数据时代的到来,稀疏编码技术的应用范围和影响力不断扩大。目前,稀疏编码已经成为计算...

EasyGBS国标流媒体服务器GB28181国标方案安装使用文档-程序员宅基地

文章浏览阅读217次。EasyGBS - GB28181 国标方案安装使用文档下载安装包下载,正式使用需商业授权, 功能一致在线演示在线API架构图EasySIPCMSSIP 中心信令服务, 单节点, 自带一个 Redis Server, 随 EasySIPCMS 自启动, 不需要手动运行EasySIPSMSSIP 流媒体服务, 根..._easygbs-windows-2.6.0-23042316使用文档

【Web】记录巅峰极客2023 BabyURL题目复现——Jackson原生链_原生jackson 反序列化链子-程序员宅基地

文章浏览阅读1.2k次,点赞27次,收藏7次。2023巅峰极客 BabyURL之前AliyunCTF Bypassit I这题考查了这样一条链子:其实就是Jackson的原生反序列化利用今天复现的这题也是大同小异,一起来整一下。_原生jackson 反序列化链子

一文搞懂SpringCloud,详解干货,做好笔记_spring cloud-程序员宅基地

文章浏览阅读734次,点赞9次,收藏7次。微服务架构简单的说就是将单体应用进一步拆分,拆分成更小的服务,每个服务都是一个可以独立运行的项目。这么多小服务,如何管理他们?(服务治理 注册中心[服务注册 发现 剔除])这么多小服务,他们之间如何通讯?这么多小服务,客户端怎么访问他们?(网关)这么多小服务,一旦出现问题了,应该如何自处理?(容错)这么多小服务,一旦出现问题了,应该如何排错?(链路追踪)对于上面的问题,是任何一个微服务设计者都不能绕过去的,因此大部分的微服务产品都针对每一个问题提供了相应的组件来解决它们。_spring cloud

Js实现图片点击切换与轮播-程序员宅基地

文章浏览阅读5.9k次,点赞6次,收藏20次。Js实现图片点击切换与轮播图片点击切换<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title></title> <script type="text/ja..._点击图片进行轮播图切换

tensorflow-gpu版本安装教程(过程详细)_tensorflow gpu版本安装-程序员宅基地

文章浏览阅读10w+次,点赞245次,收藏1.5k次。在开始安装前,如果你的电脑装过tensorflow,请先把他们卸载干净,包括依赖的包(tensorflow-estimator、tensorboard、tensorflow、keras-applications、keras-preprocessing),不然后续安装了tensorflow-gpu可能会出现找不到cuda的问题。cuda、cudnn。..._tensorflow gpu版本安装

随便推点

物联网时代 权限滥用漏洞的攻击及防御-程序员宅基地

文章浏览阅读243次。0x00 简介权限滥用漏洞一般归类于逻辑问题,是指服务端功能开放过多或权限限制不严格,导致攻击者可以通过直接或间接调用的方式达到攻击效果。随着物联网时代的到来,这种漏洞已经屡见不鲜,各种漏洞组合利用也是千奇百怪、五花八门,这里总结漏洞是为了更好地应对和预防,如有不妥之处还请业内人士多多指教。0x01 背景2014年4月,在比特币飞涨的时代某网站曾经..._使用物联网漏洞的使用者

Visual Odometry and Depth Calculation--Epipolar Geometry--Direct Method--PnP_normalized plane coordinates-程序员宅基地

文章浏览阅读786次。A. Epipolar geometry and triangulationThe epipolar geometry mainly adopts the feature point method, such as SIFT, SURF and ORB, etc. to obtain the feature points corresponding to two frames of images. As shown in Figure 1, let the first image be ​ and th_normalized plane coordinates

开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先抽取关系)_语义角色增强的关系抽取-程序员宅基地

文章浏览阅读708次,点赞2次,收藏3次。开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先关系再实体)一.第二代开放信息抽取系统背景​ 第一代开放信息抽取系统(Open Information Extraction, OIE, learning-based, 自学习, 先抽取实体)通常抽取大量冗余信息,为了消除这些冗余信息,诞生了第二代开放信息抽取系统。二.第二代开放信息抽取系统历史第二代开放信息抽取系统着眼于解决第一代系统的三大问题: 大量非信息性提取(即省略关键信息的提取)、_语义角色增强的关系抽取

10个顶尖响应式HTML5网页_html欢迎页面-程序员宅基地

文章浏览阅读1.1w次,点赞6次,收藏51次。快速完成网页设计,10个顶尖响应式HTML5网页模板助你一臂之力为了寻找一个优质的网页模板,网页设计师和开发者往往可能会花上大半天的时间。不过幸运的是,现在的网页设计师和开发人员已经开始共享HTML5,Bootstrap和CSS3中的免费网页模板资源。鉴于网站模板的灵活性和强大的功能,现在广大设计师和开发者对html5网站的实际需求日益增长。为了造福大众,Mockplus的小伙伴整理了2018年最..._html欢迎页面

计算机二级 考试科目,2018全国计算机等级考试调整,一、二级都增加了考试科目...-程序员宅基地

文章浏览阅读282次。原标题:2018全国计算机等级考试调整,一、二级都增加了考试科目全国计算机等级考试将于9月15-17日举行。在备考的最后冲刺阶段,小编为大家整理了今年新公布的全国计算机等级考试调整方案,希望对备考的小伙伴有所帮助,快随小编往下看吧!从2018年3月开始,全国计算机等级考试实施2018版考试大纲,并按新体系开考各个考试级别。具体调整内容如下:一、考试级别及科目1.一级新增“网络安全素质教育”科目(代..._计算机二级增报科目什么意思

conan简单使用_apt install conan-程序员宅基地

文章浏览阅读240次。conan简单使用。_apt install conan