mysql C语言API操作数据库比较繁琐,可以将其封装起来,这样使用比较方便,下面是一种封装方式。
目录
1.连接封装
2.连接池封装
3.测试代码
1.连接封装
将数据库连接进行封装,主要提供如下接口:
(1)连接操作
(2)更新操作(增加 / 删除 / 修改 )
(3)查询操作(查询要获取结果集,所以单独封装一个接口)
(4)获取下一条记录
(5)获取查询结果的列数(为了遍历一行中各个列时使用)
(6)获取一条记录的某个字段值
(7)设置手动提交事务(mysql默认自动提交事务)
(8)提交事务
(9)回滚事务
头文件:ConnMysql.h
#pragma once
#include <iostream>
#include <string>
#include <mysql/mysql.h>
#include <chrono>
using namespace std;
class ConnMysql {
public:
ConnMysql();
~ConnMysql();
bool Connect(const std::string &ip,
const std::string &user,
const std::string &pwd,
const std::string &db,
const unsigned short &port);
bool Update(const std::string &sql);
bool Query(const std::string &sql);
bool Next();
unsigned int GetColNum();
std::string GetValue(const int& index);
bool SetTransaction();
bool Commit();
bool Rollback();
private:
void init();
void uninit();
void freeResult();
private:
MYSQL *m_mysql;
MYSQL_RES *m_result;
MYSQL_ROW m_row = nullptr;
};
实现文件:ConnMysql.cpp
#include "ConnMysql.h"
using namespace chrono;
ConnMysql::ConnMysql() {
init();
}
ConnMysql::~ConnMysql() {
uninit();
}
bool ConnMysql::Connect(const std::string &ip,
const std::string &user,
const std::string &pwd,
const std::string &db,
const unsigned short &port) {
if (!m_mysql) {
return false;
}
m_mysql = mysql_real_connect(m_mysql, ip.c_str(), user.c_str(), pwd.c_str(), db.c_str(), port, NULL, 0);
if (!m_mysql) {
return false;
}
return true;
}
bool ConnMysql::Update(const std::string &sql) {
if (mysql_query(m_mysql, sql.c_str())) {
return false;
}
return true;
}
bool ConnMysql::Query(const std::string &sql) {
freeResult();
if (mysql_query(m_mysql, sql.c_str())) {
return false;
}
m_result = mysql_store_result(m_mysql);
return true;
}
bool ConnMysql::Next() {
if (!m_result) {
return false;
}
m_row = mysql_fetch_row(m_result);
if (m_row) {
return true;
}
return false;
}
unsigned int ConnMysql::GetColNum() {
return mysql_num_fields(m_result);
}
std::string ConnMysql::GetValue(const int& index) {
//获取结果集列数
int rowNum = mysql_num_fields(m_result);
if (index >= rowNum || index < 0) {
return std::string("");
}
char* value = m_row[index];
auto length = mysql_fetch_lengths(m_result)[index];
return std::string(value, length);
}
bool ConnMysql::SetTransaction() {
return mysql_autocommit(m_mysql, false);
}
bool ConnMysql::Commit() {
return mysql_commit(m_mysql);
}
bool ConnMysql::Rollback() {
return mysql_rollback(m_mysql);
}
void ConnMysql::init() {
m_mysql = nullptr;
m_result = nullptr;
m_mysql = mysql_init(nullptr);
if (m_mysql) {
mysql_set_character_set(m_mysql, "utf8");
}
}
void ConnMysql::uninit() {
freeResult();
if (m_mysql) {
mysql_close(m_mysql);
m_mysql = nullptr;
}
}
void ConnMysql::freeResult() {
if (m_result) {
mysql_free_result(m_result);
m_result = nullptr;
}
}
2.连接池封装
mysql的api通过tcp/ip协议连接数据库服务器,建立连接和断开连接需要经历三次握手和四次挥手,频繁的连接和断开会使得程序运行效率很低,可以采用连接池的方式来提高操作数据库效率,连接池的本质就是复用连接,一个连接可以重复使用,避免和服务器频繁建立连接和断开连接,从而提高程序执行效率。
头文件:ConnMysqlPool.h
#pragma once
#include <string>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include "ConnMysql.h"
using namespace std;
class ConnMysqlPool {
public:
ConnMysqlPool(const ConnMysqlPool&) = delete;
ConnMysqlPool& operator=(const ConnMysqlPool&) = delete;
static ConnMysqlPool& instance();
~ConnMysqlPool();
std::shared_ptr<ConnMysql> GetConn();
private:
ConnMysqlPool(const std::string &ip = "127.0.0.1",
const unsigned short &port = 3306,
const std::string &user = "root",
const std::string &pwd = "mysql",
const std::string &db = "TestDB",
const int &min = 5,
const int &max = 10,
const int &idleTime = 60,
const int &timeout = 60);
void init();
void addNewConn();
private:
std::string m_ip;
unsigned short m_port;
std::string m_user;
std::string m_pwd;
std::string m_db;
int m_min;
int m_max;
int m_idleTime;
int m_timeout;
std::mutex m_mutex;
std::condition_variable m_cond;
std::queue<std::shared_ptr<ConnMysql>> m_connQueue;
std::atomic_bool m_exit;
};
实现文件:ConnMysqlPool.cpp
#include <thread>
#include <chrono>
#include "ConnMysqlPool.h"
ConnMysqlPool::ConnMysqlPool(const std::string &ip,
const unsigned short &port,
const std::string &user,
const std::string &pwd,
const std::string &db,
const int &min,
const int &max,
const int &idleTime,
const int &timeout) :
m_ip(ip),
m_port(port),
m_user(user),
m_pwd(pwd),
m_db(db),
m_min(min),
m_max(max),
m_idleTime(idleTime),
m_timeout(timeout),
m_exit(false)
{
init();
}
ConnMysqlPool::~ConnMysqlPool() {
m_exit.store(false);
m_cond.notify_all();
while (!m_connQueue.empty()) {
m_connQueue.pop();
}
}
ConnMysqlPool& ConnMysqlPool::instance() {
static ConnMysqlPool s_instance;
return s_instance;
}
std::shared_ptr<ConnMysql> ConnMysqlPool::GetConn() {
std::unique_lock<std::mutex> lock(m_mutex);
while (m_connQueue.empty()) {
if (m_connQueue.empty()) {
if (cv_status::timeout == m_cond.wait_for(lock, chrono::milliseconds(m_timeout))) {
if (m_connQueue.empty()) {
continue;
}
}
}
}
auto mysql = m_connQueue.front();
m_connQueue.pop();
m_cond.notify_all();
return mysql;
}
void ConnMysqlPool::init() {
for (int i = 0; i < m_min; i++) {
addNewConn();
}
std::thread producer([this](){
while (m_exit) {
std::unique_lock<std::mutex> lock(m_mutex);
//此处使用while,养成条件变量判断使用while而不是使用if
while (m_connQueue.size() >= m_min) {
m_cond.wait(lock);
}
addNewConn();
m_cond.notify_all();
}
});
producer.detach();
}
void ConnMysqlPool::addNewConn() {
std::shared_ptr<ConnMysql> mysql(new ConnMysql, [this](ConnMysql* conn) {
std::lock_guard<std::mutex> lock(m_mutex);
m_connQueue.push(std::shared_ptr<ConnMysql>(conn));
});
mysql->Connect(m_ip, m_user, m_pwd, m_db, m_port);
m_connQueue.push(mysql);
}
3.测试代码
#include <iostream>
#include "ConnMysql.h"
#include "ConnMysqlPool.h"
using namespace std;
//mysql8.0连接数据库需要对数据进行加密,调用api库时需要额外的加密库
//多个线程使用同样的用户名和密码同时去连接数据库,数据库会拒绝一些连接,解决办法是在同时连接前先单独连接一次
void testConnMysql() {
ConnMysql mysql;
mysql.SetTransaction();
mysql.Connect("127.0.0.1", "root", "mysql", "TestDB", 3306);
std::string sql = "insert into employee(name, address, age, position, remark) values('张三', '陕西', 21, 'higher', '路人甲')";
if (mysql.Query(sql)) {
mysql.Commit();
std::cout << "insert ok !" << std::endl;
}
else {
mysql.Rollback();
std::cout << "insert error !" << std::endl;
}
sql = "select * from employee";
if (mysql.Query(sql)) {
std::cout << "select ok !" << std::endl;
auto colNum = mysql.GetColNum();
while (mysql.Next()) {
for(int i = 0; i < colNum; i++) {
std::cout << mysql.GetValue(i) << " ";
}
std::cout << std::endl;
}
}
else {
std::cout << "select error !" << std::endl;
}
}
void testConnMysqlPool() {
auto mysqlConn = ConnMysqlPool::instance().GetConn();
mysqlConn->SetTransaction();
std::string sql = "insert into employee(name, address, age, position, remark) values('李四', '北京', 22, 'higher', '路人乙')";
if (mysqlConn->Query(sql)) {
mysqlConn->Commit();
std::cout << "insert ok !" << std::endl;
}
else {
mysqlConn->Rollback();
std::cout << "insert error !" << std::endl;
}
sql = "select * from employee";
if (mysqlConn->Query(sql)) {
std::cout << "select ok !" << std::endl;
auto colNum = mysqlConn->GetColNum();
while (mysqlConn->Next()) {
for(int i = 0; i < colNum; i++) {
std::cout << mysqlConn->GetValue(i) << " ";
}
std::cout << std::endl;
}
}
else {
std::cout << "select error !" << std::endl;
}
}
int main(int argc, char* argv[]) {
std::cout << "test1 ..." << std::endl;
testConnMysql();
std::cout << "test2 ..." << std::endl;
testConnMysqlPool();
std::cout << "end" << std::endl;
return 0;
}
Makefile
app: useMysqlConnPool
useMysqlConnPool: usemysqlconnpool.cpp ConnMysql.cpp ConnMysqlPool.cpp
g++ -std=c++11 -g $^ -o useMysqlConnPool -lpthread -lmysqlclient
clean:
-rm useMysqlConnPool -f
运行结果如下:
数据库数据: