共享单车(八):数据库

实现后台数据库访问模块的框架,能够实现验证请求并响应(支持数据库操作)。

数据库设计

cpp 复制代码
class SqlTabel	//负责数据库表的创建
{
public:
	SqlTabel(std::shared_ptr<MysqlConnection> sqlconn) :sqlconn_(sqlconn) {}

	bool CreateUserInfo()        //创建用户表
	{
		const char* pUserInfoTabel = " \
									 CREATE TABLE IF NOT EXISTS userinfo( \
									 id            int(16)          NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT'用户id', \
                                     mobile        varchar(16)      NOT NULL DEFAULT '13000000000' COMMENT'手机号', \
                                     username      varchar(128)     NOT NULL DEFAULT '' COMMENT'用户名', \
                                     verify        int(4)           NOT NULL DEFAULT 0 COMMENT'验证',  \
                                     registertm    timestamp        NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT'注册时间', \
                                     money         int(4)           NOT NULL DEFAULT 0 COMMENT'余额', \
                                     INDEX         mobile_index(mobile)   \
					                 )";
		if (!sqlconn_->Execute(pUserInfoTabel))
		{
			LOG_ERROR("create table userinfo table failed. error msg: %s", sqlconn_->GetErrInfo());
			return false;
		}
		return true;
	}

	bool CreateBikeTable()        //创建单车表
	{
		const char* pBikeInfoTabel = " \
			                         CREATE TABLE IF NOT EXISTS bikeInfo( \
	                                 id            int              NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT'单车id', \
			                         devno         int              NOT NULL COMMENT'单车编号', \
                                     status        tinyint(1)       NOT NULL DEFAULT 0 COMMENT'单车状态', \
                                     trouble       int              NOT NULL DEFAULT 0 COMMENT'损坏类型编号', \
                                     tmsg          varchar(256)     NOT NULL DEFAULT '' COMMENT'损坏原因描述', \
                                     latitude      double(10, 6)    NOT NULL DEFAULT 0 COMMENT'维度', \
                                     longitude     double(10, 6)    NOT NULL DEFAULT 0 COMMENT'经度', \
                                     UNIQUE(devno)    \
                                     )";
		if (!sqlconn_->Execute(pBikeInfoTabel))
		{
			LOG_ERROR("create table bikeinfo table failed. error msg: %s", sqlconn_->GetErrInfo());
			return false;
		}
		return true;
	}

private:
	std::shared_ptr<MysqlConnection> sqlconn_;
};

数据库访问

cpp 复制代码
class SqlRecordSet {	//数据库访问接口
public:
	SqlRecordSet() :m_pRes(NULL)
	{

	}

	explicit SqlRecordSet(MYSQL_RES* pRes)// 不能隐式构造
	{
		m_pRes = pRes;
	}
	MYSQL_RES* MysqlRes()
	{
		return m_pRes;
	}
	~SqlRecordSet() {
		if (m_pRes)
		{
			mysql_free_result(m_pRes);	// 释放一个结果集合使用的内存
		}
	}

	/*
	* 你已经设置了结果集,此时若要再次设置结果集,那么之前的结果集就访问不到了.(那之前的结果集访问不到了,之间的结果集还没来的及释放,就是内存泄漏)
	* 所以,你要设置结果集的前提是:结果集是空的.
	* 不是空的,咱们不让他设置
	*/
	//设置结果集
	inline void SetResult(MYSQL_RES* pRes)
	{
		//如果此时已经保存了结果集,那么就应该让程序报错,防止内存泄漏
		assert(m_pRes == NULL);
		if (m_pRes)
		{
			LOG_WARN("the MYSQL_RES has already stored result , maybe will case memory leak\n");
		}
		m_pRes = pRes;
	}
	//获取结果集
	inline MYSQL_RES* GetResult()
	{
		return m_pRes;
	}

	//获取里面的行
	void FetchRow(MYSQL_ROW& row)
	{
		row = mysql_fetch_row(m_pRes);	// 检索结果集的下一行
	}

	//返回具体行的数量
	inline i32 GetRowCount()
	{
		return m_pRes->row_count;
	}
private:
	MYSQL_RES* m_pRes;	// 存放mysql结果集的数据结构
};

协调处理事务

cpp 复制代码
class BusinessProcessor	//负责协调处理事务
{
public:
	BusinessProcessor(std::shared_ptr<MysqlConnection> conn);

	bool init();

	virtual ~BusinessProcessor();

private:
	std::shared_ptr<MysqlConnection> mysqlconn_;	//数据库连接
	std::shared_ptr<UserEventHandler> ueh_;        //用户事件处理器
	//...可以增添其它事件处理器
};

数据库操作

cpp 复制代码
class MysqlConnection {
public:
	MysqlConnection();
	~MysqlConnection();

	MYSQL* Mysql()
	{
		return mysql_;
	}
	//初始化
	bool Init(const char* szHost, int nPort, const char* szUser, const char* szPasswd, const char* szDb);	

	//不需要/需要拿到结果
	bool Execute(const char* szSql);
	//MYSQL_RES *
	bool Execute(const char* szSql, SqlRecordSet& recordSet);

	//将pSrc特殊字符进行转义,一些特殊字符如果不转义,sql查询就会报错
	int EscapeString(const char* pSrc, int nSrcLen, char* pDest);

	void Close();
	//得到错误信息的方法
	const char* GetErrInfo();

	//服务断掉了,重连
	void Reconnect();
private:
	MYSQL* mysql_;//mysql的句柄,用于操作数据库
};

初始化

cpp 复制代码
bool MysqlConnection::Init(const char* szHost, int nPort, const char* szUser, const char* szPasswd, const char* szDb)
{
    LOG_INFO("enter Init.\n");

    //初始化
    if (mysql_init(mysql_) == NULL) {   //  mysql_init 用来分配或者初始化一个MYSQL对象,用于连接mysql服务端->失败=NULL 成功=!NULL
        LOG_ERROR("init mysql failed %s , %d", this->GetErrInfo(), errno);
        return false;
    }

    //因为网络等原因,断开后自动重连
    char cAuto = 1;
    if (mysql_options(mysql_, MYSQL_OPT_RECONNECT, &cAuto)!=0)  // 用于设置 MySQL 连接的选项->成功返回0
    {
        LOG_ERROR("mysql_options MYSQL_OPT_RECONNEC failed.");
    }

    //连接
    //"host"的值必须是主机名或IP地址
    //"user"参数包含用户的MySQL登录ID
    //"passwd"参数包含用户的密码
    //"db"是数据库名称
    //如果"port"不是0,其值将用作TCP/IP连接的端口号
    //如果unix_socket不是NULL,该字符串描述了应使用的套接字或命名管道。
    //client_flag的值通常为0
    if (mysql_real_connect(mysql_, szHost, szUser, szPasswd, szDb, nPort, NULL, 0) == NULL) // 与运行在主机上的MySQL数据库引擎建立连接
    {
        LOG_ERROR("connect mysql failed : %s ", this->GetErrInfo());
    }

    return true;

}

查询结果

cpp 复制代码
bool MysqlConnection::Execute(const char* szSql)
{
    if (mysql_real_query(mysql_, szSql, strlen(szSql)) != 0)    // 执行szSql语句->成功=0 失败=-1
    {
        if (mysql_errno(mysql_) == CR_SERVER_GONE_ERROR)//断开连接就重连
        {
            Reconnect();
        }
        return false;
    }
    return true;
}

bool MysqlConnection::Execute(const char* szSql, SqlRecordSet& recordSet)
{
    //先进行sql查询,看是否能够执行成功?
    if (mysql_real_query(mysql_, szSql, strlen(szSql)) != 0)
    {
        if (mysql_errno(mysql_) == CR_SERVER_GONE_ERROR)    // error code
        {
            Reconnect();    // 重连 
        }
        return false;
    }
    //执行成功了,就将查到的结果设置到结果集中
    MYSQL_RES* pRes = mysql_store_result(mysql_);
    if (!pRes) {
        return NULL;//设置失败,返回空,说明mysql_store_result失败了
    }
    recordSet.SetResult(pRes);//将结果放入结果集
    return false;
}

转义

cpp 复制代码
/*
* pSrc  转义前的字符
* pDest 转义后的字符
*/
int MysqlConnection::EscapeString(const char* pSrc, int nSrcLen, char* pDest)
{
    if (!mysql_)
    {
        return 0;
    }
    // mysql必须是有效的开放式连接,将"from"中的字符串编码为转义SQL字符串,将结果置于"to"中,并添加1个终结用NULL字节
    return mysql_real_escape_string(mysql_, pDest, pSrc, nSrcLen);//将源src转义到目标子串dest
    
}

相关知识

1.安装mysql c++库

bash 复制代码
sudo apt-get install libmysql++-dev
sudo systemctl mysql-server

2.安装mysql

bash 复制代码
sudo apt-get install mysql-server
sudo apt-get install mysql-client
systemctl status mysql.service #请检是否安装成功

3.进入 MySQL

bash 复制代码
sudo mysql -u root -p

4.创建数据库

sql 复制代码
CREATE TABLE `users`(
    `use` VARCHAR(50) NOT NULL COMMENT 'id',
    `pwd` VARCHAR(50) NOT NULL COMMENT 'passwd',
    PRIMARY KEY (`use`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO `users`(`use`,`pwd`) VALUES ('aa' , 'bb'),('cc','dd');

5.常用操作

https://learn.microsoft.com/zh-cn/azure/mysql/single-server/connect-cpp
https://blog.csdn.net/qq_60755751/article/details/136631798

相关推荐
爱吃南瓜的北瓜6 分钟前
Redis的Key的过期策略是怎样实现的?
数据库·redis·bootstrap
一心只为学21 分钟前
Oracle密码过期问题,设置永不过期
数据库·oracle
danplus22 分钟前
node发送邮件:如何实现Node.js发信功能?
服务器·node.js·外贸开发信·邮件群发·蜂邮edm邮件营销·邮件接口·营销邮件
小光学长30 分钟前
基于vue框架的宠物销售管理系统3m9h3(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
数据库
小黑爱编程30 分钟前
【LInux】HTTPS是如何实现安全传输的
linux·安全·https
BeyondESH35 分钟前
Linux线程同步—竞态条件和互斥锁(C语言)
linux·服务器·c++
wn53137 分钟前
【Go - 类型断言】
服务器·开发语言·后端·golang
hanniuniu1337 分钟前
详细解读,F5服务器负载均衡的技术优势
运维·服务器·负载均衡
鱼饼6号1 小时前
Prometheus 上手指南
linux·运维·centos·prometheus
小菜yh1 小时前
关于Redis
java·数据库·spring boot·redis·spring·缓存