C++實現MySQL資料庫連線池
1.連線池的介紹:
1.1應用背景:
一般的應用程式都會訪問到資料庫,在程式訪問資料庫的時候,每一次資料訪問請求都必須經過下面幾個步驟:建立資料庫連線,開啟資料庫,對資料庫中的資料進行操作,關閉資料庫連線。而建立資料庫連線和開啟資料庫是一件很消耗資源並且費時的工作,如果在系統中很頻繁的發生這種資料庫連線,必然會影響到系統的效能,甚至會導致系統的崩潰。
1.2技術思想:
在系統初始化階段,建立一定數量的資料庫連線物件(Connection),並將其儲存在連線池中定義的容器中。當有資料庫訪問請求時,就從連線池中的這個容器中拿出一個連線;當容器中的連線已經用完,並且還沒有達到系統定義的最大連線數時,可以再建立一個新的連線
經過上述描述,我們可以歸納出資料庫連線池的主要操作:
(1)首先建立一個數據庫連線池物件
(2)初始化一定數量的資料庫連線,放入連線池物件的容器中
(3)當有資料庫訪問請求時,直接從連線池的容器中得到一個連線,這裡出現三種情況:
(a)當容器中的還有連線時,則返回給資料庫訪問請求者一個連線
(b)當容器中沒有連線時,並且當前建立的連線數沒有達到系統定義的最大連線數,則建立一個新的資料庫連線。
(c)當容器中的沒有連線並且當前建立的連線數達到系統定義的最大連線數,則當前訪問資料庫請求就要等待其他訪問請求釋放連線。
(4)當資料庫訪問完成後,應該將連線放回連線池的容器中。
(5)當服務停止時,需要先釋放資料庫連線池中的所有資料庫連線,然後再釋放資料庫連線池物件。
2.程式設計實現:
標頭檔案(connection_pool.h):
/* *File: connection_pool.h *Author: fengwei */ #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> using namespace std; using namespace sql; class ConnPool { private: int curSize; //當前已建立的資料庫連線數量 int maxSize; //連線池中定義的最大資料庫連線數 string username; string password; string url; list<Connection*> connList; //連線池的容器佇列 STL list 雙向連結串列 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); //構造方法 public: ~ConnPool(); Connection*GetConnection(); //獲得資料庫連線 void ReleaseConnection(Connection *conn); //將資料庫連線放回到連線池的容器中 static ConnPool *GetInstance(); //獲取資料庫連線池物件 }; #endif /*_CONNECTION_POOL_H */
標頭檔案中定義了一個容器connList,裡面存放了很多個未使用的連線;在對容器內的連線進行操作的時候,需要加鎖來保證程式的安全性,所以標頭檔案中定義了一個lock,通過使用lock保證了同一時間只有一個執行緒對容器進行操作。
連線池類要統一管理整個應用程式中的連線,所以在整個系統中只需要維護一個連線池物件,試想:如果系統中定義了多個連線池物件,那麼每一個物件都可以建立maxSize個連線,這樣就失去了建立連線池的初衷,破環了通過連線池統一管理系統中連線的思想。所以這裡使用單例模式編寫連線池類,單例模式確保一個類只有一個例項,自己進行例項化並且向整個系統提供這個例項。
餓漢實現
為什麼我不講“執行緒安全的餓漢實現”?因為餓漢實現本來就是執行緒安全的,不用加鎖。為啥?自己想!
class singleton
{
protected:
singleton()
{}
private:
static singleton* p;
public:
static singleton* initance();
};
singleton* singleton::p = new singleton;
singleton* singleton::initance()
{
return p;
}
在標頭檔案中,我們定義了一個靜態的連線池物件connPool,連線池類提供一個靜態的公共方法GetInstance(),外部程式通過呼叫這個方法來獲得連線池物件。並且將連線池類的建構函式定義為私有的,外部的應用程式不能夠通過new來例項化連線池類,只能通過GetInstance()方法獲得連線池物件;在GetInstance()方法中需要判斷連線池類中定義的connPool是否為NULL,若為NULL則呼叫私有建構函式例項化connPool,若不為空,則直接返回connPool。這樣就實現了連線池類的單例模式,從而保證了系統執行過程中只建立一個連線池類的例項物件。在例項化連線池類的物件時,要對連線池做一些初始化的操作,即建立一定數量的資料庫連線。程式中通過InitConnection(intiInitialSize)方法對連線池進行初始化,建立iInitialSize個連線,並且將這些連線放在連線池中的容器connList中,每新建一個連線,curSize就加1。當有資料庫訪問請求時,需要從連線池中獲取一個連線,通過GetConnection()方法實現:首先判斷容器中是否還有連線,如果有,則拿出容器中的第一個連線,並且將該連線移出容器;獲得的連線要進行判斷,如果連線已經關閉,則回收該連線的記憶體空間,並且重新建立一個連線;然後判斷新建立的連線是否為空,如果為空,則說明當前已經建立連線的數量並不是curSize個,而是(curSize-1)個(應該除去這個空連線)。如果容器中已經沒有連線了,則要判斷當前的curSize值是否已經達到規定的maxSize,如果沒有小於maxSize,將建立一個新的連線(++curSize)。如果超過maxSize則等待其他資料庫訪問請求釋放資料庫連線。
連線使用完以後,需要將連線放回連線池中,通過ReleaseConnection(sql::Connection* conn)方法實現,它的實現非常簡單,就是將傳進來的connection連線新增到連線池的容器中。
當需要回收連線池的記憶體空間時,需要先回收連線池中所有連線的記憶體空間,然後再釋放連線池物件的記憶體空間。
實現資料庫連線池主要的步驟就是上述這些,具體的程式碼實現如下所示(connection_pool.cpp):
/*
* connection_pool.cpp
*
*/
#include <stdexcept>
#include <exception>
#include <stdio.h>
#include "connection_pool.h"
using namespace std;
using namespace sql;
ConnPool *ConnPool::connPool = new ConnPool("tcp://127.0.0.1:3306", "root", "123456", 50);
//連線池的建構函式
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("驅動連接出錯;\n");
} catch (std::runtime_error&e) {
perror("執行出錯了\n");
}
this->InitConnection(maxSize / 2);
}
//獲取連線池物件,單例模式
ConnPool*ConnPool::GetInstance() {
return connPool;
}
//初始化連線池,建立最大連線數的一半連線數量
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("建立CONNECTION出錯");
}
}
pthread_mutex_unlock(&lock);
}
//建立連線,返回一個Connection
Connection* ConnPool::CreateConnection() {
Connection*conn;
try {
conn = driver->connect(this->url, this->username, this->password); //建立連線
return conn;
} catch (sql::SQLException&e) {
perror("建立連接出錯");
return NULL;
} catch (std::runtime_error&e) {
perror("執行時出錯");
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 { //建立的連線數已經達到maxSize
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 icon;
pthread_mutex_lock(&lock);
for (icon = connList.begin(); icon != connList.end(); ++icon) {
this->DestoryConnection(*icon); //銷燬連線池中的連線
}
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;
}
}
main.cpp
/*
* main.cpp
*
*/
#include "connection_pool.h"
namespace ConnectMySQL {
//初始化連線池
ConnPool *connpool = ConnPool::GetInstance();
void run() {
Connection *con;
Statement *state;
ResultSet *result;
// 從連線池中獲取mysql連線
con = connpool->GetConnection();
state = con->createStatement();
state->execute("use holy");
// 查詢
result = state->executeQuery("select * from student where id < 1002");
// 輸出查詢
while (result->next()) {
int id = result->getInt("id");
string name = result->getString("name");
cout << id << " : " << name << endl;
}
delete state;
connpool->ReleaseConnection(con);
}
}
int main(int argc, char* argv[]) {
ConnectMySQL::run();
return 0;
}