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

智能推荐

PCS7 入门指南 v9.0 SP3 v9.1 中文版 学习资料 (官方公开可用资料)_pcs7v9.1-程序员宅基地

文章浏览阅读1.8w次,点赞13次,收藏82次。链接:https://pan.baidu.com/s/1-p4h_QDL8BN04tnn3vSkOA提取码:nou3PCS7入门指南v9.0含APL(包含PDF和项目文件)官方地址:SIMATIC 过程控制系统 PCS 7 入门指南第1部分 (V9.0,含APL)https://support.industry.siemens.com/cs/document/109756196/simatic-%E8%BF%87%E7%A8%8B%E6%8E%A7%E5%88%B6%E7%B3%BB..._pcs7v9.1

c# 调用非托管代码_c# 声明kernel32 函数-程序员宅基地

文章浏览阅读956次,点赞3次,收藏5次。编程过程中,一般c#调用非托管的代码有两种方式:1.直接调用从DLL中导出的函数。2.调用COM对象上的接口方法。首先说明第1种方式,基本步骤如下:1.使用关键字static,extern声明需要导出的函数。2.把DllImport 属性附加到函数上。3.掌握常用的数据类型传递的对应关系。4.如果需要,为函数的参数和返回值指定自定义数据封送处理信息,这将重写.net framework默认的封送处理。简单举例如下:托管函数原型:DWORD GetShortPathName(LPCTST_c# 声明kernel32 函数

高频交易及化资策与区_hudson river trading-程序员宅基地

文章浏览阅读406次。转 高频交易及量化投资的策略与误区一、高频交易公司和量化投资公司的区别一般来说,高频交易公司和量化投资公司既有联系,又有区别。在美国,人们常说的高频交易公司一般都是自营交易公司,这些公司主要有Getco、Tower Research、Hudson River Trading、SIG、Virtu Financial、Jump Trading、RGM Advisor、Chopper Tradi..._hudson river trading

C语言文件操作相关的函数_c语言与文件处理有关的函数-程序员宅基地

文章浏览阅读865次。文件的打开和关闭文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件 的关系。ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。FILE * fopen ( const char * filename, const char * mode ); int fcl..._c语言与文件处理有关的函数

java 无法读取文件_java 读取文件,无法显示文件内容,如何解决? 谢谢。-程序员宅基地

文章浏览阅读1.1k次。从来没见过进行文件读取写入时,在写入中需要随机数的,你读取文件就是从一个地方获取输入流,然后将这个输入流写到别的地方,根本不要随机数。给你一个示例://copyafiletoanotherfilebyusingFileReader/FileWriterimportjava.io.*;publicclassTFileRead{publicstaticvoidmain(S..._java复制文件文件没有内容显示

vue引入原生高德地图_前端引入原生地图-程序员宅基地

文章浏览阅读556次,点赞2次,收藏3次。由于工作上的需要,今天捣鼓了半天高德地图。如果定制化开发需求不太高的话,可以用vue-amap,这个我就不多说了,详细就看官网 https://elemefe.github.io/vue-amap/#/zh-cn/introduction/install然而我们公司需要英文版的高德,我看vue-amap中好像没有这方面的配置,而且还有一些其他的定制化开发需求,然后就只用原生的高德。其实原生的引入也不复杂,但是有几个坑要填一下。1. index.html注意,引入的高德js一定要放在头部而_前端引入原生地图

随便推点

深度篇——人脸识别(一)  ArcFace 论文 翻译_arcface论文-程序员宅基地

文章浏览阅读6.6k次,点赞23次,收藏120次。返回主目录返回 目标检测史 目录上一章:深度篇——目标检测史(八)CPTN 论文 翻译论文地址:《ArcFace: Additive Angular Margin Loss for Deep Face Recognition》源码地址:InsightFace: 2D and 3D Face Analysis Project本小节,ArcFace 论文 翻译,下一小节细说 ArcFace文本检测 代码一.ArcFace 论文 翻译1.概述..._arcface论文

台式计算机有没有无线连接模块,台式机能不能连接wifi_台式机怎么连接wifi-程序员宅基地

文章浏览阅读1.2k次。2016-12-30 09:54:26你好!很高兴为你解答,先将无线路由器接通电源,然后插上网线,将另一端插到你电脑上,等网络通了之后,你在IE浏览器上输入:192.168.1.1(这是一般无线路由器的IP,如果有特殊...2016-12-16 11:44:30手机系统问题,可能系统出现了问题,导致连接上了WiFi却无法上网。重启一下路由器试试,或者将路由器恢复一下出厂设置,然后重新拨号上网,并根..._计算机无线模块怎么看

OpenGLES编程思想-程序员宅基地

文章浏览阅读4.8k次。最近在看gles的reference,想多了解一下gles的底层,gles是opengl在khronos在嵌入式设备上的图形硬件的软件访问接口,很多东西和opengl似曾相似,但是和opengl又有很大的不同,最新的标准是gles3.2,标准文档非常长,如果不是写引擎没必要对每个接口烂熟于心,但是为了能够了解他,我对他的编程思想做个总结,最重要的是理解gles的设计思路,然后在使用的时候也必将容易找到相关接口。所以本文基本不会列出gles的每个接口,不会记录讲解每个接口,而是希望能够通过总结gles的设计思_gles

linux命令行去掉滴滴声_linux一直叮叮叮-程序员宅基地

文章浏览阅读2.6k次,点赞2次,收藏2次。在CentOS中,我们常用Tab键进行命令补全,但是系统总是提示滴滴声,让人很烦躁。 即使把音频设备关掉,或者虚拟机的音频设备也关掉,还是有这个声音。 如果不喜欢这个声音,可以通过修改配置去掉它。用vi 编辑 /etc/inputrc 文件,在vi的命令模式下,用键盘方向键进行定位, 找到“#set bell-style none”,用X 删除语句前方的#号,就可以了。“: wq_linux一直叮叮叮

Java从零开始 第10.5讲 面向对象的习题课_编写一个测试类booktest,创建几个book对象,并打印它们的字符串表示,同时判断-程序员宅基地

文章浏览阅读197次。面向对象的习题课类的定义员工类Employee求和类Sum类与对象书籍类BookBook类的测试类BookTest异常能扩容的MyList类剪刀石头布转载请注明出处在这一讲中我会给出一些关于面向对象部分的习题,同样希望在不看答案的情况下自己编写,即使看过了答案,也要能够在不看答案的情况下写出来。类的定义员工类Employee定义在同一个公司工作的Employee类,要求其中含有属性:员工的名字,员工的年龄,员工的爱好,员工的公司名(注意当公司更名时,所有员工的公司名都需要更名),工作地点默认为中国(_编写一个测试类booktest,创建几个book对象,并打印它们的字符串表示,同时判断

Spark伪分布安装(依赖Hadoop)_下载spark的hadoop依赖-程序员宅基地

文章浏览阅读6.7k次,点赞7次,收藏14次。一、伪分布安装Spark安装环境:Ubuntu 14.04 LTS 64位+Hadoop2.7.2+Spark2.0.0+jdk1.7.0_761、安装jdk1.7(1)下载jdk-7u76-linux-x64.tar.gz;(2)解压jdk-7u76-linux-x64.tar.gz,并将其移动到/opt/java/jdk路径下(自建);命令:tar -zxvf jdk-_下载spark的hadoop依赖