本文主要介绍基于MySQL、使用C++语言实现数据库连接池的方法。
1. 准备依赖的文件
我们利用 MySQL 提供的“mysql-connector-c++”以及 boost 编写数据库连接池。
1.1 mysql-connector-c++安装
“mysql-connector-c++”可以从 MySQL 的官网上下载,如下图:
说明:
- 上图中的链接为(https://dev.mysql.com/downloads/connector/cpp/),此链接后续可能会改变;
- 需要根据实际情况(如 OS 及其版本)选择对应的发行版;
在上述页面中下载下来的文件,直接包含了“mysql-connector”的头文件和共享库,如下:
注意:“mysql-connector”提供的头文件“mysql_connection.h”放在了“jdbc”目录下(如上图),本文虽然使用的是C++语言,但是也需要连接该头文件。
1.2 boost安装
使用 yum 直接安装,如下:
yum install boost-devel.x86_64
2. 示例代码
数据库连接池头文件(connection_pool.h),代码如下:
#ifndef __CONNECTION_POOL_H__ #define __CONNECTION_POOL_H__ #include <mysql_connection.h> #include <mysql_driver.h> #include <cppconn/exception.h> #include <cppconn/driver.h> #include <cppconn/connection.h> #include <cppconn/resultset.h> #include <cppconn/prepared_statement.h> #include <cppconn/statement.h> #include <pthread.h> #include <list> #include <string> using namespace std; using namespace sql; class ConnPool { public: ~ConnPool(); // 获取数据库连接 Connection* GetConnection(); // 将数据库连接放回到连接池的容器中 void ReleaseConnection(Connection *conn); // 获取数据库连接池对象 static ConnPool* GetInstance(); private: // 当前已建立的数据库连接数量 int curSize; // 连接池定义的最大数据库连接数 int maxSize; string username; string password; string url; // 连接池容器 list<Connection*> connList; // 线程锁 pthread_mutex_t lock; static ConnPool* connPool; Driver* driver; // 创建一个连接 Connection* CreateConnection(); // 初始化数据库连接池 void InitConnection(int iInitialSize); // 销毁数据库连接对象 void DestoryConnection(Connection *conn); // 销毁数据库连接池 void DestoryConnPool(); // 构造方法 ConnPool(string url, string user,string password, int maxSize); }; #endif/*_CONNECTION_POOL_H */
数据库连接池实现文件(connection_pool.cpp),代码如下:
#include <stdexcept> #include <exception> #include <stdio.h> #include "connection_pool.h" using namespace std; using namespace sql; ConnPool* ConnPool::connPool = NULL; // 获取连接池对象,单例模式 ConnPool* ConnPool::GetInstance() { if (connPool == NULL) { connPool = new ConnPool("tcp://192.168.213.130:3306", "root", "", 20); } return connPool; } // 数据库连接池的构造函数 ConnPool::ConnPool(string url, string userName, string password, int maxSize) { this->maxSize = maxSize; this->curSize = 0; this->username = userName; this->password = password; this->url = url; try { this->driver = sql::mysql::get_driver_instance(); } catch (sql::SQLException& e) { perror("get driver error.\n"); } catch (std::runtime_error& e) { perror("[ConnPool] run time error.\n"); } // 在初始化连接池时,建立一定数量的数据库连接 this->InitConnection(maxSize/2); } // 初始化数据库连接池,创建最大连接数一半的连接数量 void ConnPool::InitConnection(int iInitialSize) { Connection* conn; pthread_mutex_lock(&lock); for (int i = 0; i < iInitialSize; i++) { conn = this->CreateConnection(); if (conn) { connList.push_back(conn); ++(this->curSize); } else { perror("Init connection error."); } } pthread_mutex_unlock(&lock); } // 创建并返回一个连接 Connection* ConnPool::CreateConnection() { Connection* conn; try { // 建立连接 conn = driver->connect(this->url, this->username, this->password); return conn; } catch (sql::SQLException& e) { perror("create connection error."); return NULL; } catch (std::runtime_error& e) { perror("[CreateConnection] run time error."); return NULL; } } // 从连接池中获得一个连接 Connection* ConnPool::GetConnection() { Connection* con; pthread_mutex_lock(&lock); // 连接池容器中还有连接 if (connList.size() > 0) { // 获取第一个连接 con = connList.front(); // 移除第一个连接 connList.pop_front(); // 判断获取到的连接的可用性 // 如果连接已经被关闭,删除后重新建立一个 if (con->isClosed()) { delete con; con = this->CreateConnection(); // 如果连接为空,说明创建连接出错 if (con == NULL) { // 从容器中去掉这个空连接 --curSize; } } pthread_mutex_unlock(&lock); return con; } // 连接池容器中没有连接 else { // 当前已创建的连接数小于最大连接数,则创建新的连接 if (curSize < maxSize) { con = this->CreateConnection(); if (con) { ++curSize; pthread_mutex_unlock(&lock); return con; } else { pthread_mutex_unlock(&lock); return NULL; } } // 当前建立的连接数已经达到最大连接数 else { perror("[GetConnection] connections reach the max number."); pthread_mutex_unlock(&lock); return NULL; } } } // 释放数据库连接,将该连接放回到连接池中 void ConnPool::ReleaseConnection(sql::Connection* conn) { if (conn) { pthread_mutex_lock(&lock); connList.push_back(conn); pthread_mutex_unlock(&lock); } } // 数据库连接池的析构函数 ConnPool::~ConnPool() { this->DestoryConnPool(); } // 销毁连接池,需要先销毁连接池的中连接 void ConnPool::DestoryConnPool() { list<Connection*>::iterator itCon; pthread_mutex_lock(&lock); for (itCon = connList.begin(); itCon != connList.end(); ++itCon) { // 销毁连接池中的连接 this->DestoryConnection(*itCon); } curSize = 0; // 清空连接池中的连接 connList.clear(); pthread_mutex_unlock(&lock); } // 销毁数据库连接 void ConnPool::DestoryConnection(Connection* conn) { if (conn) { try { // 关闭连接 conn->close(); } catch(sql::SQLException& e) { perror(e.what()); } catch(std::exception& e) { perror(e.what()); } // 删除连接 delete conn; } }
数据库连接池测试代码(test_dbpool.cpp):
#include "connection_pool.h" //初始化连接池 ConnPool *connpool = ConnPool::GetInstance(); int main(int argc, char* argv[]) { Connection *con; Statement *state; ResultSet *result; // 从连接池中获取连接 con = connpool->GetConnection(); for (int i = 0; i < 100; i++) { state = con->createStatement(); state->execute("use testdb"); // 查询数据库 result = state->executeQuery("select * from roles"); // 打印数据库查询结果 cout << "================================" << endl; while (result->next()) { int nRoleId = result->getInt("role_id"); string strOccupation = result->getString("occupation"); string strCamp = result->getString("camp"); cout << nRoleId << " , " << strOccupation << " , " << strCamp << endl; } cout << "i is: " << i << endl; cout << "================================" << endl; } delete result; delete state; connpool->ReleaseConnection(con); return 0; }
编译上述文件,生成数据库连接池测试程序,如下:
g++ -o test_dbpool test_dbpool.cpp connection_pool.cpp -I /opt/liitdar/mysql-connector-c++-8.0.11-linux-el7-x86-64bit/include/jdbc/ -L /opt/liitdar/mysql-connector-c++-8.0.11-linux-el7-x86-64bit/lib64/ -lmysqlcppconn
执行上面生成的测试程序,命令如下:
./test_dbpool
程序执行(部分)结果如下:
上述结果表明我们编写的数据库连接池正常运行了。
3. 总结
这里对数据库连接池的实现代码进行简单地总结。
3.1 存放空闲连接的容器
数据库连接池头文件(connection_pool.h)中定义了一个list容器 connList ,使用 connList 存放空闲的连接。
3.2 线程锁
在对 connList 内的连接进行操作的时候,需要通过加锁来保证程序的安全性,所以头文件中定义了一个线程锁 lock ,通过使用 lock 保证同一时间只能有一个线程对容器 connList 进行操作。
3.3 单例模式
由于连接池类 ConnPool 要统一管理系统中的所有连接,所以在整个系统中只需要维护一个连接池对象。如果系统中定义了多个连接池对象,那么每一个对象都可以建立 maxSize 个连接,在这种情况下使用连接池没有任何意义(与不使用连接池效果一样),也破环了通过连接池统一管理系统中连接的初衷,所以数据库连接池需要使用单例模式编写连接池类:单例模式确保一个类只有一个实例,该类自己进行实例化,并且向整个系统提供这个实例。
在头文件 connection_pool.h 中,我们定义了一个静态的连接池对象 connPool ,连接池类 ConnPool 提供一个静态的公共方法 GetInstance() ,外部程序通过调用这个方法来获得连接池对象。由于把连接池类 ConnPool 的构造函数定义为私有的,所以外部的应用程序不能够通过 new 的方式来实例化连接池类,只能通过 GetInstance() 方法获得连接池对象。在 GetInstance() 方法中,需要判断连接池类中定义的连接池对象 connPool 是否为“NULL”,若为“NULL”则调用私有构造函数实例化 connPool ;若不为“NULL”,则直接返回 connPool ,这样就实现了连接池类的单例模式,从而保证了系统运行过程中只建立一个连接池类的实例对象。
3.4 连接池的初始化
在实例化连接池类的对象 connPool 时,要对连接池做一些初始化操作,即建立一定数量的数据库连接。本文的程序通过 void InitConnection(int iInitialSize) 方法对连接池进行初始化,创建 iInitialSize 个连接,并且将这些连接放到连接池中的容器 connList 中,每新建一个连接,当前已建立的连接数 curSize 就加1。
3.5 从连接池中获取连接
当程序进行访问数据库时,需要从连接池中获取一个连接,本文是通过 GetConnection() 方法实现的,大致步骤如下:
1. 首先判断容器中是否还有空闲连接,如果有,则取出容器中的第一个连接,并且将该连接从容器中移除;
1.1 判断上一步中获得的连接是否已经关闭,如果已关闭,则回收该连接的内存空间,并且重新创建一个连接,然后判断新创建的连接是否为空,如果为空,则需要将已经建立连接的数量减1,去掉这个空连接(也可以说是前面已经关闭的连接)。
2. 如果容器中已经没有空闲连接了,则要判断当前的 curSize 值是否已经达到规定的最大连接数 maxSize ;
2.1 如果小于 maxSize ,就建立一个新的连接,同时 ++curSize
2.2 如果不小于 maxSize ,则等待其他数据库访问请求释放数据库连接。
3.6 将连接放回到连接池中
连接使用完后,需要将该连接放回连接池中,本文通过 void ReleaseConnection(Connection *conn) 方法实现。该方法的具体实现就是将传进来的数据库连接(重新)添加到连接池的容器 connList 中。