本文是基于MySQL数据库提供的C API进行的C++编程;主要实现一个简单的数据库封装和连接池的搭建。
关于MySQL的相关配置信息,这里用xml文件进行存储。所以我们还用到了TinyXML。
下载地址:https://sourceforge.net/projects/tinyxml/

1.数据库封装


目标:设计一个类MysqlConn来封装mysql提供的API函数。

总览

#pragma once
#include<iostream>
#include <memory>
#include <string>
#include "./include/mysql.h"
#include <chrono> // 时钟
#include <fstream>
#pragma comment(lib, "./lib/libmysql.lib") // 加载数据库库文件
using namespace std;
using namespace chrono;
typedef long long ll;

class MysqlConn
{
private:
	// 绝对时钟
	steady_clock::time_point	m_alivetime;
	// 连接
	MYSQL*						m_conn;
	// 查询的结果集
	MYSQL_RES*					m_result;
	// 单记录结果集
	MYSQL_ROW					m_mysqlRow;

	// 结果集释放
	void freeRes();
	// 导出某一张表中的数据
	void backupCurrentTable(const string path, const string tableName);
public:
	// 初始化数据库
	MysqlConn();
	// 数据库连接释放
	~MysqlConn();
	// 连接数据库, 需提供用户 密码 数据库名称 ip 端口
	bool connect(const string user, const string passwd, \
				const string dbName,  string ip, \
				const unsigned short port = 3306U);
	// 更新数据库:增删改操作
	bool update(const string sql) const;
	// 查询数据库
	bool query(const string sql);
	// 遍历查询结果集
	bool getRes();
	// 获取结果集中的字段值
	string getValue(const int fieldIndex) const;
	// 切换数据库
	bool selectDB(const string dbName) const;
	// 建库
	bool createDB(const string dbName) const;
	// 备份某个库
	void backupCurrentDB(const string path);
	// 事务操作
	bool transaction() const;
	// 提交事务
	bool commit() const;
	// 事务回滚
	bool rollback() const;
	// 刷新起始的空闲时间点
	void refreashAliveTime();
	// 计算存活总时长
	ll getAliveTime();
};

初始化数据库

MysqlConn::MysqlConn()
{
	m_result = nullptr;
	m_mysqlRow = nullptr;
	// 传入nullptr空指针时,会自动分配一个MYSQL对象
	m_conn = mysql_init(nullptr);
}

释放数据库连接

MysqlConn::~MysqlConn()
{
	freeRes(); // 释放结果集
	if (m_conn != nullptr) {
		mysql_close(m_conn);
		m_conn = nullptr;
	}
}

结果集释放

void MysqlConn::freeRes()
{
	if (m_result) {
		mysql_free_result(m_result);
		m_result = nullptr;
	}
}

连接数据库

需要提供用户名密码数据库名称ip端口

bool MysqlConn::connect(const string user, const string passwd, \
	const string dbName, string ip, \
	const unsigned short port)
{
	MYSQL* res = mysql_real_connect(m_conn, ip.c_str(), user.c_str(), \
		passwd.c_str(), dbName.c_str(), port, nullptr, 0);
	// 修改编码
	mysql_set_character_set(m_conn, "gb2312");
	return res != nullptr;
}

更新数据库:增删改操作

bool MysqlConn::update(const string sql) const
{
	// 执行成功返回0;
	int res = mysql_real_query(m_conn, sql.c_str(), static_cast<unsigned int>(sql.size()));
	if (res != 0) {
		return false; // 提示
	}
	return true;
}

查询数据库

bool MysqlConn::query(const string sql)
{
	freeRes();
	int res = mysql_real_query(m_conn, sql.c_str(), static_cast<unsigned int>(sql.size()));
	if (res != 0) {
		return false; // 提示
	}
	m_result = mysql_store_result(m_conn);
	return true;
}

遍历查询结果集

bool MysqlConn::getRes()
{
	if (m_result != nullptr) {
		// char** 获取单行记录
		m_mysqlRow = mysql_fetch_row(m_result);
		if (m_mysqlRow != nullptr) {
			return true;
		}
		freeRes();
	}
	return false;
}

获取结果集中的字段值

string MysqlConn::getValue(const int fieldIndex) const
{
	int fieldCount = mysql_num_fields(m_result);
	if (fieldIndex >= fieldCount || fieldIndex < 0) {
		return string(); // 提示
	}
	char* value = m_mysqlRow[fieldIndex];
	// 得到一个保存各字段值长度的数组
	unsigned long* len = mysql_fetch_lengths(m_result);
	unsigned long length = len[fieldIndex];
	// 防止结果中存在\0导致数据丢失
	return string(value, length);
}

切换数据库

bool MysqlConn::selectDB(const string dbName) const
{
	int res = mysql_select_db(m_conn, dbName.c_str());
	if (res != 0) {
		return false;  // 提示
	}
	return true;
}

建一个新的库

bool MysqlConn::createDB(const string dbName) const
{
	string sql = "create database " + dbName + ";";
	int res = mysql_real_query(m_conn, sql.c_str(), static_cast<unsigned long>(sql.size()));
	if (res != 0) {
		return false;  // 提示
	}
	return true;
}

备份当前数据库

导出为.sql文件

对外接口

遍历库中的表

void MysqlConn::backupCurrentDB(const string path)
{
	string sql = "show tables";
	int r = mysql_real_query(m_conn, sql.c_str(), static_cast<unsigned long>(sql.size()));
	if (r != 0) {
		return; // 提示
	}
	MYSQL_RES* tableRes = mysql_store_result(m_conn);
	for (int i = 0; i < mysql_num_rows(tableRes); ++i) {
		MYSQL_ROW tableName = mysql_fetch_row(tableRes);
		backupCurrentTable(path, tableName[0]); 
	}
}

导出表结构以及表数据

void MysqlConn::backupCurrentTable(const string path, const string tableName)
{
	string file = path + tableName + ".sql";
	ofstream ofs(file);
	if (!ofs.is_open()) {
		return; // 提示
	}
	// 表结构写入
	string showCreate = "show create table " + tableName + ";";
	bool res = query(showCreate);
	if (!res) {
		return; // 提示
	}
	if (getRes()) {
		string writeSQL = getValue(1) + ";\n";
		ofs.write(writeSQL.c_str(), writeSQL.size());
		// cout << writeSQL << endl;
	}
	// 表数据写入
	string sql = "select * from " + tableName + ";";
	res = query(sql);
	if (!res) {
		return; // 提示
	}
	while (getRes()) {
		string writeSQL = "insert into `" + tableName + "` values(";
		for (int i = 0; !getValue(i).empty(); ++i) {
			if (i != 0) {
				writeSQL += ",";
			}
			MYSQL_FIELD* valueType = mysql_fetch_field_direct(m_result, i);
			if (valueType->type == MYSQL_TYPE_DECIMAL
				|| valueType->type == MYSQL_TYPE_TINY
				|| valueType->type == MYSQL_TYPE_SHORT
				|| valueType->type == MYSQL_TYPE_LONG
				|| valueType->type == MYSQL_TYPE_FLOAT
				|| valueType->type == MYSQL_TYPE_DOUBLE
				|| valueType->type == MYSQL_TYPE_TIMESTAMP
				|| valueType->type == MYSQL_TYPE_LONGLONG
				|| valueType->type == MYSQL_TYPE_INT24
				|| valueType->type == MYSQL_TYPE_BOOL) {
				writeSQL += getValue(i);
			}
			else {
				writeSQL += "'" + getValue(i) + "'";
			}
		}
		writeSQL += ");\n";
		ofs.write(writeSQL.c_str(), writeSQL.size());
	}
	ofs.close();
}

事务操作

开启事务

bool MysqlConn::transaction() const
{
	// 将事务提交设置为手动提交
	return mysql_autocommit(m_conn, false);
}

事务提交

bool MysqlConn::commit() const
{
	return mysql_commit(m_conn);
}

事务回滚

bool MysqlConn::rollback() const
{
	return mysql_rollback(m_conn);
}

与连接池操作相关

刷新起始的空闲时间点

void MysqlConn::refreashAliveTime()
{
	m_alivetime = steady_clock::now();
}

计算存活总时长

ll MysqlConn::getAliveTime()
{
	// 毫秒 <<= 纳秒:精度降低
	milliseconds res = duration_cast<milliseconds>(steady_clock::now() - m_alivetime);
	return res.count(); 
}

连接池的搭建


概念:数据库连接池是程序启动时建立足够的数据库连接,并将这些连接组成一个连接池,由程序动态地对池中的连接进行申请,使用,释放。
作用
1.资源重用,避免了数据库连接频繁建立、关闭的开销
2.更快的系统响应速度,直接从连接池中获取连接,响应速度加快
3.控制资源的使用。如果不使用连接池,每次访问数据库都需要创建一个连接,这样系统的稳定性受系统连接需求影响很大,很容易产生资源浪费和高负载异常。连接池能够使性能最大化,将资源利用控制在一定的水平之下。连接池能控制池中的连接数量,增强了系统在大量用户应用时的稳定性。
.......因为我们的连接池只需要一个对象就可以,所以在这里我们用的是单例模式[懒汉模式],并且我们用一个原子变量去管理连接池中的连接数量。

总览

#pragma once
#include "MysqlConn.h"
#include "./tinyxml/tinyxml.h"
#include <queue>
#include <mutex>
#include <thread>
#include <atomic>
#include <condition_variable>

#pragma comment(lib, "./tinyxml/x64/Debug/tinyxml.lib")

// 应用-单例模式:懒汉模式[需要考虑多线程安全问题]
class ConnectionPool
{
private:
	ConnectionPool();
	// 移动拷贝最终还是有且仅有一个对象,所以依旧是属于单例模式。
	// delete 阻止拷贝构造和拷贝赋值的类对象生成
	ConnectionPool(ConnectionPool&) = delete;
	ConnectionPool& operator=(ConnectionPool&) = delete;
	~ConnectionPool();
	// 解析xml配置文件 读取数据库及连接池的相关信息
	bool parseXmlFile();
	// 添加连接
	bool addConnection();
	// 线程函数
	void productConnection();
	void recycleConnection();

	// 存放数据库连接池建立的连接
	queue<MysqlConn*>	m_connections;
	// 一些基本信息
	string				m_ip;			// IP
	unsigned short		m_port;			// 端口
	string				m_user;			// 用户名
	string				m_passwd;		// 密码
	string				m_dbName;		// 数据库名称
	int					m_minSize;		// 初始连接量(最小连接量)
	int					m_maxSize;		// 最大连接量
	int					m_timeout;		// 超时时长
	int					m_maxIdleTime;	// 最大空闲时长
										// 线程安全相关
	mutex				m_mutex;
	condition_variable	m_cond;			// 仅用一个条件变量来唤醒线程,但并不影响线程运行
	condition_variable  m_cond1;
	// 连接数量
	atomic_int			m_num;			// 连接的总数量
public:
	// 获取单例对象的接口
	static ConnectionPool* getConnectPool();
	// 用户获取连接的接口, 如果获取失败,会返回nullptr
	shared_ptr<MysqlConn> getConnection();
};

初始化连接池

ConnectionPool::ConnectionPool()
{
	if (!parseXmlFile())
		return; 
	for (m_num = 0; m_num < m_minSize;) {
		bool flag = addConnection();
		if (!flag) {
			return;
		}
	}
	// 如果子线程的任务函数是类的非静态函数,我们需要指定任务函数的地址和任务函数的所有者
	thread producer(&ConnectionPool::productConnection, this); // 创建连接
	thread recycler(&ConnectionPool::recycleConnection, this); // 检测并销毁连接
															   // 线程分离,防止阻塞主线程
	producer.detach();
	recycler.detach();
}

连接池的释放

主要是释放连接队列中的连接对象

ConnectionPool::~ConnectionPool()
{
	while (!m_connections.empty()) {
		MysqlConn* conn = m_connections.front();
		m_connections.pop();
		delete conn;
	}
}

xml文件的解析

xml文件存放数据库及连接池的相关配置信息;这里封装一个函数来完成配置信息的读取

bool ConnectionPool::parseXmlFile()
{
	TiXmlDocument xml("mysql.xml");
	// 加载文件
	bool res = xml.LoadFile();
	if (!res) {
		return false; // 提示
	}
	// 根
	TiXmlElement* rootElement = xml.RootElement();
	TiXmlElement* childElement = rootElement->FirstChildElement("mysql");
	// 读取信息
	m_ip = childElement->FirstChildElement("ip")->GetText();
	m_port = static_cast<unsigned short>(stoi(string(childElement->FirstChildElement("port")->GetText())));
	m_user = childElement->FirstChildElement("username")->GetText();
	m_passwd = childElement->FirstChildElement("password")->GetText();
	m_dbName = childElement->FirstChildElement("dbName")->GetText();
	m_minSize = static_cast<int>(stoi(string(childElement->FirstChildElement("minSize")->GetText())));
	m_maxSize = static_cast<int>(stoi(string(childElement->FirstChildElement("maxSize")->GetText())));
	m_maxIdleTime = static_cast<int>(stoi(string(childElement->FirstChildElement("maxIdleTime")->GetText())));
	m_timeout = static_cast<int>(stoi(string(childElement->FirstChildElement("timeout")->GetText())));
	return true;
}

添加一个连接对象

bool ConnectionPool::addConnection()
{
	MysqlConn* conn = new MysqlConn;
	bool res = conn->connect(m_user, m_passwd, m_dbName, m_ip, m_port);
	if (res) {
		// 刷新空闲时间
		conn->refreashAliveTime();
		m_connections.push(conn);
		++m_num;
		return true;
	}
	else {
		delete conn;
		return false; // 提示
	}
}

连接相关线程函数

添加连接的线程

void ConnectionPool::productConnection()
{
	while (true) {
		unique_lock<mutex> lc(m_mutex);
		m_cond.wait(lc, [this]() {return m_connections.empty(); });
		if (m_num < m_maxSize) {
			bool flag = addConnection();
			if (!flag) {
				return;
			}
		}
		// 唤醒
		m_cond1.notify_all();
	}
}

释放连接的线程

void ConnectionPool::recycleConnection()
{
	while (true) {
		// 休眠一段时间 0.5s
		this_thread::sleep_for(milliseconds(500));
		lock_guard<mutex> lc(m_mutex);
		while (!m_connections.empty() && m_num > m_minSize) {
			MysqlConn* conn = m_connections.front();
			if (conn->getAliveTime() >= m_maxIdleTime) {
				m_connections.pop();
				delete conn;
				--m_num;
			}
			else {
				break;
			}
		}
	}
}

获取连接池对象

ConnectionPool* ConnectionPool::getConnectPool()
{
	// 不使用互斥锁的线程安全的懒汉模式
	static ConnectionPool pool; // 只在第一次调用函数时初始化
	return &pool;
}

获取连接对象

shared_ptr<MysqlConn> ConnectionPool::getConnection()
{
	unique_lock<mutex> lc(m_mutex);
	while (m_connections.empty()) {
		if (cv_status::timeout == m_cond1.wait_for(lc, chrono::milliseconds(m_timeout))) {
			if (m_connections.empty()) {
				// cout << "out of time" << endl;
				return nullptr; // 结束  // 提示
								// continue; // 利用while配合continue 继续阻塞
			}
		}
	}
	// 要指定删除器destructor,来保证连接的归还
	shared_ptr<MysqlConn> conn(m_connections.front(), [this](MysqlConn* conn) {
		// 加锁保证队列线程安全
		// m_mutex.lock(); // 1
		unique_lock<mutex> lc(m_mutex); // 2
		// lock_guard<mutex> lc(m_mutex); // 3
		conn->refreashAliveTime();
		m_connections.push(conn);
		// m_mutex.unlock(); // 1
	});
	m_connections.pop();
	m_cond.notify_all();
	return conn;
}

更多推荐