技术标签: c++ MySQL技术研究 mysql 后端 数据库 sql
要想封装出一个易用的MySQL库,就需要先知道官方究竟开放了哪些接口给我们使用,大家可以自行去网上下载MySQL对应版本源码,由于我们其实不需要知道函数实现细节(主要那玩意比较难看懂。。。),所以直接去/usr/include/mysql文件夹里面将头文件全部拉到本地就可以获取到官方提供的c接口了(MySQL安装之后头文件的目录,不同系统不同MySQL发行版本安装的位置可能会不一样,这个得自己去找一下),大部分有用的信息都在mysql.h文件里面,大家可以把这个文件看一遍,不用去看对应的源码,就看一遍大概知道有什么可用的功能就行,接下来会列举我们用到的几个重要的结构体和API
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;
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;
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;
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;
// 获取结果集里面的字段数目
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
这里先给出一份已经完成的代码,点击这里下载 mysql c++封装.zip,解压之后进去编译一下发现有错误(如果没有则忽略这段直接看下面的内容),如下图所示,可以看出mysql/udf_registration_types.h有个错误,直接把错误的行给屏蔽即可编译通过
屏蔽这里
/**
* 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;
该类是用来存放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
该类是用来存放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
该类是用来管理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;
}
该类是最重要的一个,实现起来较为简单,需要对外提供的功能有:初始化数据库、连接数据库、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;
}
该类为事务类,事务的接口实现较为简单,这里只实现了最简单的形式,即开始事务、提交事务、回滚事务。由于实现起来太简单了这里就不贴出具体代码了
该类是一个管理类,用于统一管理所有 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;
};
MySQL官方给出的接口整体来说还是比较清晰的,注释也很不错,不过还是建议要先有一定数据库的基础再去看这些。
最后附上一份源代码,大家可以下载下去调试使用看看,有什么错误的地方也欢迎大家指出
mysql c++封装.zip
文章浏览阅读2.1k次。 需求如下:该搜索框是对整个页面的input检索 ,但与弹出层中的input冲突 博主几经辗转 简单处理 解决问题,思路如下:排除掉特定class的input。代码如下:$('input:not(.pop)', this.footer()).on('keyup change', function () { if (that.search() !== th..._input排他选择器
文章浏览阅读5.6k次,点赞6次,收藏20次。看到别人有个1024的勋章,特意留了一篇在今年的10.24日,看看会不会获得。在日常开发中可能涉及接口之间的相互调用,虽然在现在微服务的理念推广下,很多公司都采用轻量级的JSON格式做为序列化的格式,但是不乏有些公司还是有一些XML格式的报文,最近就在对接某个合作方的时候遇到了XML报文。在JSON报文爽快的转换下很难试用一个一个的拿报文参数,还是希望能直接将报文转换成Bean。接下来就了解到..._jaxb 泛型
文章浏览阅读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的位置
文章浏览阅读1.6w次。还在苦于网上找到的一些指令已经不适用了吗?还在苦于有些地方的指令有误吗?还在苦于有些地方整理的指令不够全面吗?那么你来对地方了!小编为大家整理了《我的世界》原版游戏常用的指令,这些基本足以满足各位的基本需求了!大家来一起看看吧!注:表示的是必须输入的部分,[方括号]表示的是可选择性输入的部分基本命令列表命令描述/?/help的替代命令,提供命令使用帮助。/ban + 玩家名字将玩家加入封禁列表。/..._gamemode指令java
文章浏览阅读1.5w次,点赞3次,收藏3次。Spring Boot 结合shiro做第三方登录验证1、首先,说一下我的具体实现思路。在做spring boot拦截器的过程中,开始我准备用spring security来实现,但是研究了一段时间之后发现spring security的集成度太高,需要修改的东西比较多,而且对它本身的使用方法不是很了解,后来转而使用Apache shiro。由于是第三方登录,是不需要我来验证密码的。最开始,我陷入了_shiro 第三方token登录
文章浏览阅读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次。代码错误的原因和调试方法_代码报错
文章浏览阅读5.2k次,点赞9次,收藏40次。---恢复内容开始---1.认识游戏 1.1什么是游戏 1.1.1游戏的定义 任何人类正常生理需求之外的活动均可称为游戏 1.1.2游戏的分类 RPG角色扮演游戏、ACT动作游戏、AVG冒险游戏、FPS第一人称视角射击游戏、TPS第三人称视角射击游戏、FTG格斗游戏、SPT体育游戏、RAC竞速游戏、RTS即时战略游戏、STG..._深度解析java游戏服务器开发
文章浏览阅读4k次。CSRF是什么我就不解释了,百度一搜全是,比波姐的片源还要多,千篇一律都他么是复制粘贴。那为什么这个令牌(token)操作可以防范CSRF呢?下面我就随便说说说错了大家不要介意。首先我们要知道令牌是存储在session里面的,这个很重要 php代码如下<?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万元。台南应用科技大学商品设计系学生高郁翔,为国立自然科学博物馆设计「恐龙化石钉书机」,他认为小朋友把钉书机钉下去的那一刻,会觉得像暴龙準_杯垫文创设计说明
文章浏览阅读404次。XML与对象,集合的相互转化 今天小伙伴在群里问了一下关于XML与对象之间的相互转换,作为菜鸟的我正好趁着闲着的时间学习了一波,直接上代码了,有疑问或者有错误的地方还请大家指正,谢谢。。。。 1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System...._c# xml转集合