Chap09-MysqlManager

Chap09-MysqlManager

Mysql C Connector操作

  • mysql_init() - 初始化连接句柄
  • mysql_real_connect() - 连接到数据库
  • mysql_query() - 执行SQL查询
  • mysql_store_result() - 存储查询结果
  • mysql_fetch_row() - 获取结果行
  • mysql_stmt_init() - 初始化预处理语句
  • mysql_stmt_prepare() - 准备预处理语句
  • mysql_stmt_bind_param() - 绑定参数
  • mysql_stmt_execute() - 执行预处理语句

这些示例涵盖了 MySQL C API 的基本用法,包括连接数据库、执行查询、处理结果和使用预处理语句等重要操作。

预处理的基本流程

mysql_stmt_init -> mysql_stmt_prepare -> mysql_stmt_bind_param -> mysql_stmt_execute -> mysql_stmt_close

// 预处理操作的例子

void prepared_statement_example(MYSQL *conn) {
    MYSQL_STMT *stmt;
    MYSQL_BIND bind[2];
    int user_id;
    char name[100];
    my_bool is_null[2];
    
    // 准备语句
    stmt = mysql_stmt_init(conn);
    const char *query = "SELECT id, name FROM users WHERE id > ? AND name LIKE ?";
    
    if (mysql_stmt_prepare(stmt, query, strlen(query))) {
        fprintf(stderr, "预处理错误: %s\n", mysql_stmt_error(stmt));
        return;
    }
    
    // 绑定参数
    memset(bind, 0, sizeof(bind));
    
    int min_id = 10;
    char search_name[] = "%John%";
    
    // 第一个参数: min_id
    bind[0].buffer_type = MYSQL_TYPE_LONG;
    bind[0].buffer = (char *)&min_id;
    bind[0].is_null = 0;
    bind[0].length = 0;
    
    // 第二个参数: search_name
    bind[1].buffer_type = MYSQL_TYPE_STRING;
    bind[1].buffer = search_name;
    bind[1].buffer_length = strlen(search_name);
    bind[1].is_null = 0;
    bind[1].length = 0;
    
    if (mysql_stmt_bind_param(stmt, bind)) {
        fprintf(stderr, "绑定参数错误: %s\n", mysql_stmt_error(stmt));
        mysql_stmt_close(stmt);
        return;
    }
    
    // 执行查询
    if (mysql_stmt_execute(stmt)) {
        fprintf(stderr, "执行错误: %s\n", mysql_stmt_error(stmt));
        mysql_stmt_close(stmt);
        return;
    }
    
    // 绑定结果
    MYSQL_BIND result_bind[2];
    memset(result_bind, 0, sizeof(result_bind));
    
    result_bind[0].buffer_type = MYSQL_TYPE_LONG;
    result_bind[0].buffer = (char *)&user_id;
    result_bind[0].is_null = &is_null[0];
    
    result_bind[1].buffer_type = MYSQL_TYPE_STRING;
    result_bind[1].buffer = name;
    result_bind[1].buffer_length = sizeof(name);
    result_bind[1].is_null = &is_null[1];
    
    mysql_stmt_bind_result(stmt, result_bind);
    
    // 获取结果
    printf("查询结果:\n");
    while (!mysql_stmt_fetch(stmt)) {
        if (!is_null[0] && !is_null[1]) {
            printf("ID: %d, 姓名: %s\n", user_id, name);
        }
    }
    
    mysql_stmt_close(stmt);
}

mysql封装

在这里我们为了解耦,我们将给后端使用的管理类实际执行操作的类分开,分为Manager和Dao.

首先看myselManager类

// .h

#ifndef MYSQLMANAGER_H
#define MYSQLMANAGER_H

#include "../global/Singleton.h"
#include "MysqlDao.h"
#include <mysql/mysql.h>

struct UserInfo;
class MysqlManager : public Singleton<MysqlManager> {
    friend class Singleton<MysqlManager>;

public:
    ~MysqlManager();

    int RegisterUser(const std::string& name, const std::string& email, const std::string& password);

private:
    MysqlManager();

private:
    MysqlDao _dao;
};

#endif // MYSQLMANAGER_H


// .cpp
#include "MysqlManager.h"
MysqlManager::~MysqlManager()
{
}

MysqlManager::MysqlManager()
{
}

int MysqlManager::RegisterUser(const std::string& name, const std::string& email, const std::string& password)
{
    return _dao.RegisterUser(name, email, password);
}

我们可以看到,透露给外部管理类的RegisterUser函数,实际调用了Dao层的函数。

MysqlDao

#ifndef MYSQLDAO_H
#define MYSQLDAO_H

#include <atomic>
#include <condition_variable>
#include <memory>
#include <mysql/mysql.h>
#include <queue>
#include <string>
#include <thread>

/**
 ┌─────────────────┐       ┌──────────────────┐       ┌─────────────────┐
 │   MysqlDao      │       │   MySqlPool      │       │ UserInfo (DTO)  │
 │                 │       │                  │       │                 │
 │ - pool_         │───────│ - connections    │       │ - name          │
 │                 │       │ - pool params    │       │ - email         │
 │ + RegUser()     │       │ + getConnection()│       │ - password      │
 │ + other CRUD    │       │ + returnConn()   │       │                 │
 └─────────────────┘       └──────────────────┘       └─────────────────┘
          │                           │
          │                           │
          ▼                           ▼
 ┌─────────────────┐       ┌──────────────────┐
 │  Business Logic │       │ mysql::Connection│
 │   (Service层)   │       │  (MySQL驱动)      │
 └─────────────────┘       └──────────────────┘
 *
 */

class MysqlPool {
public:
    struct conn_deleter {
        void operator()(MYSQL* conn) const noexcept
        {
            mysql_close(conn);
            mysql_thread_end();
        }
    };
    MysqlPool(const std::string& url, const std::string& user, const std::string& password, const std::string& schedma, int poolSize = std::thread::hardware_concurrency());

    std::unique_ptr<MYSQL, MysqlPool::conn_deleter> GetConnection() noexcept;
    void ReturnConnection(std::unique_ptr<MYSQL, conn_deleter> conn) noexcept;
    void Close() noexcept;
    ~MysqlPool();

private:
    std::string _url;
    std::string _user;
    std::string _password;
    std::string _schedma;
    std::size_t _poolSize;
    std::queue<std::unique_ptr<MYSQL, conn_deleter>> _connections;
    std::mutex _mutex;
    std::condition_variable _cv;
    std::atomic<bool> _stop;
};

class MysqlDao {
public:
    MysqlDao();
    ~MysqlDao();
    int RegisterUser(const std::string& name, const std::string& email, const std::string& password);

private:
    std::unique_ptr<MysqlPool> _pool;
};

#endif

实际执行操作的时候,都需要一个MYSQL*的连接实例进行操作。

为了调高数据库访问的并发行,我们建立了MysqlPool,每次执行操作,取出一个MYSQL*进行接下来的步骤。

以下是MysqlPool:

#include "MysqlDao.h"
#include "../global/ConfigManager.h"
#include <iostream>
MysqlPool::MysqlPool(const std::string& url, const std::string& user, const std::string& password, const std::string& schedma, int poolSize)
    : _url(url)
    , _user(user)
    , _password(password)
    , _schedma(schedma)
    , _poolSize(poolSize)
    , _stop(false)
{
    int successCount = 0;
    try {
        for (std::size_t i = 0; i < _poolSize; ++i) {
            MYSQL* conn = mysql_init(nullptr);
            if (conn == nullptr) {
                throw std::runtime_error("mysql_init failed");
            }

            if (mysql_real_connect(conn, _url.c_str(), _user.c_str(), _password.c_str(), _schedma.c_str(), 0, NULL, 0) == nullptr) {
                throw std::runtime_error("mysql_real_connect failed");
            }

            if (mysql_ping(conn) != 0) {
                std::string error = "mysql_ping failed: ";
                error += mysql_error(conn);
                mysql_close(conn);
                throw std::runtime_error(error);
            }

            std::unique_ptr<MYSQL, conn_deleter> conn_ptr(conn, conn_deleter());
            _connections.push(std::move(conn_ptr));
            successCount++;
        }
    } catch (std::exception& e) {
        while (!_connections.empty()) {
            auto conn = std::move(_connections.front());
            _connections.pop();
            if (conn) {
                mysql_close(conn.get());
            }
        }
        std::cerr << e.what() << std::endl;
    }
    if (successCount < _poolSize) {
        std::cerr << "Warning Only" << successCount << "Out Of" << _poolSize << " Connections Were Established" << std::endl;
    }
}

MysqlPool::~MysqlPool()
{
    Close();
}

std::unique_ptr<MYSQL, MysqlPool::conn_deleter> MysqlPool::GetConnection() noexcept
{
    std::unique_lock<std::mutex> lock(_mutex);
    _cv.wait(lock, [this] {
        return _stop || !_connections.empty();
    });
    if (_stop) {
        return nullptr;
    }
    auto conn = std::move(_connections.front());
    _connections.pop();
    return conn;
}

void MysqlPool::ReturnConnection(std::unique_ptr<MYSQL, conn_deleter> conn) noexcept
{
    std::unique_lock<std::mutex> lock(_mutex);
    if (_stop) {
        return;
    }
    _connections.push(std::move(conn));
    _cv.notify_one();
}

void MysqlPool::Close() noexcept
{
    std::unique_lock<std::mutex> lock(_mutex);
    _stop = true;
    _cv.notify_all();
    while (!_connections.empty()) {
        auto p = std::move(_connections.front());
        _connections.pop();
    }
}

每次初始化池子,就要创建并且初始化_size个MYSQL*,并且连接上对应的数据库。Dao需要操作的时候,调用MysqlPool::GetConnection()得到一个MYSQL*然后执行操作。为了池子的长久运行,每当操作执行完毕之后,调用MysqlPool::ReturnConnection将得到的MYSQL*返回给池子。

接下来看具体的Dao层

MysqlDao::MysqlDao()
{
    auto& cfgMgr = ConfigManager::GetInstance();
    const auto& host = cfgMgr["Mysql"]["host"];
    const auto& port = cfgMgr["Mysql"]["port"];
    const auto& schema = cfgMgr["Mysql"]["schema"];
    const auto& password = cfgMgr["Mysql"]["password"];
    const auto& user = cfgMgr["Mysql"]["user"];
    _pool = std::make_unique<MysqlPool>(host, user, password, schema);
}

MysqlDao::~MysqlDao()
{
    _pool->Close();
}

int MysqlDao::RegisterUser(const std::string& name, const std::string& email, const std::string& password)
{
    auto conn = _pool->GetConnection();
    try {
        if (conn == nullptr) {
            _pool->ReturnConnection(std::move(conn));
            return false;
        }

        MYSQL_STMT* stmt = mysql_stmt_init(conn.get());
        std::string query = "select * from user where name = ? or email =?";

        if (mysql_stmt_prepare(stmt, query.c_str(), query.size()) != 0) {
            std::cout << mysql_error(conn.get()) << std::endl;
            mysql_stmt_close(stmt);
            _pool->ReturnConnection(std::move(conn));
            return -1;
        }

        MYSQL_BIND params[2];

        memset(params, 0, sizeof(params));
        params[0].buffer_type = MYSQL_TYPE_STRING;
        params[0].buffer = (char*)name.c_str();
        params[0].buffer_length = name.size();
        params[0].length = &params[0].buffer_length;

        params[1].buffer_type = MYSQL_TYPE_STRING;
        params[1].buffer = (char*)email.c_str();
        params[1].buffer_length = email.size();
        params[1].length = &params[1].buffer_length;

        if (mysql_stmt_bind_param(stmt, params) != 0) {
            std::cout << mysql_stmt_error(stmt) << std::endl;
            mysql_stmt_close(stmt);
            _pool->ReturnConnection(std::move(conn));
            return -1;
        }

        if (mysql_stmt_execute(stmt) != 0) {
            mysql_stmt_close(stmt);
            _pool->ReturnConnection(std::move(conn));
            return -1;
        }

        mysql_stmt_close(stmt);

        MYSQL_RES* result = mysql_store_result(conn.get());
        if (result == nullptr) {
            _pool->ReturnConnection(std::move(conn));
            return -1;
        }

        my_ulonglong row_count = mysql_stmt_num_rows(stmt);
        mysql_stmt_close(stmt);

        // 如果存在记录,返回 -1 表示用户已存在
        if (row_count > 0) {
            _pool->ReturnConnection(std::move(conn));
            return -1;
        }

        stmt = mysql_stmt_init(conn.get());
        query = "INSERT INTO user (name, email, password) VALUES (?, ?, ?)";

        if (mysql_stmt_prepare(stmt, query.c_str(), query.size()) != 0) {
            std::cout << "Insert prepare failed: " << mysql_error(conn.get()) << std::endl;
            mysql_stmt_close(stmt);
            _pool->ReturnConnection(std::move(conn));
            return -1;
        }

        // 绑定插入参数
        MYSQL_BIND insert_params[3];
        memset(insert_params, 0, sizeof(insert_params));

        unsigned long name_len = name.size();
        insert_params[0].buffer_type = MYSQL_TYPE_STRING;
        insert_params[0].buffer = (char*)name.c_str();
        insert_params[0].buffer_length = name_len;
        insert_params[0].length = &name_len;

        unsigned long email_len = email.size();
        insert_params[1].buffer_type = MYSQL_TYPE_STRING;
        insert_params[1].buffer = (char*)email.c_str();
        insert_params[1].buffer_length = email_len;
        insert_params[1].length = &email_len;

        unsigned long password_len = password.size();
        insert_params[2].buffer_type = MYSQL_TYPE_STRING;
        insert_params[2].buffer = (char*)password.c_str();
        insert_params[2].buffer_length = password_len;
        insert_params[2].length = &password_len;

        if (mysql_stmt_bind_param(stmt, insert_params) != 0) {
            std::cout << "Insert bind failed: " << mysql_stmt_error(stmt) << std::endl;
            mysql_stmt_close(stmt);
            _pool->ReturnConnection(std::move(conn));
            return -1;
        }

        if (mysql_stmt_execute(stmt) != 0) {
            std::cout << "Insert execute failed: " << mysql_stmt_error(stmt) << std::endl;
            mysql_stmt_close(stmt);
            _pool->ReturnConnection(std::move(conn));
            return -1;
        }

        mysql_stmt_close(stmt);
        _pool->ReturnConnection(std::move(conn));

        return 1; // 返回1表示注册成功

    } catch (const std::exception& e) {
        if (conn != nullptr) {
            _pool->ReturnConnection(std::move(conn));
        }
        std::cerr << "Exception: " << e.what() << std::endl;
        return -1;
    }
}

创建时,先从配置文件读取对应的信息,初始化池子信息,连接数据库。之后就是等待MysqlManager的调用。

比如其中的RegisterUser函数,实际执行了select * from user where name = ? or email = ? ;用于检测当前信息的用户是否已经注册,如果结果不为0

my_ulonglong row_count = mysql_stmt_num_rows(stmt);

​ mysql_stmt_close(stmt);

// 如果存在记录,返回 -1 表示用户已存在

​ if (row_count > 0) {

​ _pool->ReturnConnection(std::move(conn));

​ return -1;

​ }

那么返回-1,错误。

如果结果为0,说明当前还未注册这个用户,接下来我们就执行插入语句,将信息插入数据库,实现真正的注册( INSERT INTO user (name, email, password) VALUES (?, ?, ?) )。

posted @ 2025-12-24 23:16  大胖熊哈  阅读(1)  评论(0)    收藏  举报