基于MysqlConnector/C++的数据库连接池的实现
1.连接池的介绍:
1.1应用背景:
一般的应用程序都会访问到数据库,在程序访问数据库的时候,每一次数据访问请求都必须经过下面几个步骤:建立数据库连接,打开数据库,对数据库中的数据进行操作,关闭数据库连接。而建立数据库连接和打开数据库是一件很消耗资源并且费时的工作,如果在系统中很频繁的发生这种数据库连接,必然会影响到系统的性能,甚至会导致系统的崩溃。
1.2技术思想:
在系统初始化阶段,建立一定数量的数据库连接对象(Connection),并将其存储在连接池中定义的容器中。当有数据库访问请求时,就从连接池中的这个容器中拿出一个连接;当容器中的连接已经用完,并且还没有达到系统定义的最大连接数时,可以再创建一个新的连接,当当前使用的连接数达到最大连接数时,就要等待其他访问请求将连接放回容器后才能使用。当使用完连接的时候,必须将连接放回容器中,这样不同的数据库访问请求就可以共享这些连接,通过重复使用这些已经建立的数据库连接,可以解决上节中说到的频繁建立连接的缺点,从而提高了系统的性能。
经过上述描述,我们可以归纳出数据库连接池的主要操作:
(1)首先建立一个数据库连接池对象
(2)初始化一定数量的数据库连接,放入连接池对象的容器中
(3)当有数据库访问请求时,直接从连接池的容器中得到一个连接,这里出现三种情况:
(a)当容器中的还有连接时,则返回给数据库访问请求者一个连接
(b)当容器中没有连接时,并且当前建立的连接数没有达到系统定义的最大连接数,则创建一个新的数据库连接。
(c)当容器中的没有连接并且当前建立的连接数达到系统定义的最大连接数,则当前访问数据库请求就要等待其他访问请求释放连接。
(4)当数据库访问完成后,应该将连接放回连接池的容器中。
(5)当服务停止时,需要先释放数据库连接池中的所有数据库连接,然后再释放数据库连接池对象。
2.编程实现:
头文件(connection_pool.h):
/* *File: connection_pool.h *Author: csc */ #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> usingnamespace std; usingnamespace sql; classConnPool{ private: intcurSize;//当前已建立的数据库连接数量 intmaxSize;//连接池中定义的最大数据库连接数 stringusername; stringpassword; stringurl; list<Connection*>connList;//连接池的容器队列 pthread_mutex_tlock;//线程锁 staticConnPool *connPool; Driver*driver; Connection*CreateConnection();//创建一个连接 voidInitConnection(int iInitialSize);//初始化数据库连接池 voidDestoryConnection(Connection *conn);//销毁数据库连接对象 voidDestoryConnPool();//销毁数据库连接池 ConnPool(stringurl,string user,string password,int maxSize);//构造方法 public: ~ConnPool(); Connection*GetConnection();//获得数据库连接 voidReleaseConnection(Connection *conn);//将数据库连接放回到连接池的容器中 staticConnPool *GetInstance();//获取数据库连接池对象 }; #endif /*_CONNECTION_POOL_H */
头文件中定义了一个容器connList,里面存放了很多个未使用的连接;在对容器内的连接进行操作的时候,需要加锁来保证程序的安全性,所以头文件中定义了一个lock,通过使用lock保证了同一时间只有一个线程对容器进行操作。
连接池类要统一管理整个应用程序中的连接,所以在整个系统中只需要维护一个连接池对象,试想:如果系统中定义了多个连接池对象,那么每一个对象都可以建立maxSize个连接,这样就失去了创建连接池的初衷,破环了通过连接池统一管理系统中连接的思想。所以这里使用单例模式编写连接池类,单例模式确保一个类只有一个实例,自己进行实例化并且向整个系统提供这个实例。在头文件中,我们定义了一个静态的连接池对象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连接添加到连接池的容器中。
当需要回收连接池的内存空间时,需要先回收连接池中所有连接的内存空间,然后再释放连接池对象的内存空间。
实现数据库连接池主要的步骤就是上述这些,具体的代码实现如下所示:
#include<stdexcept> #include<exception> #include<stdio.h> #include"connection_pool.h" usingnamespace std; usingnamespace sql; ConnPool*ConnPool::connPool=NULL; //连接池的构造函数 ConnPool::ConnPool(stringurl, 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(){ if(connPool==NULL) { connPool=newConnPool("tcp://127.0.0.1:3306","root","root",50); } returnconnPool; } //初始化连接池,创建最大连接数的一半连接数量 voidConnPool::InitConnection(int iInitialSize) { Connection*conn; pthread_mutex_lock(&lock); for(inti=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);//建立连接 returnconn; } catch(sql::SQLException&e) { perror("创建连接出错"); returnNULL; } catch(std::runtime_error&e) { perror("运行时出错"); returnNULL; } } //在连接池中获得一个连接 Connection*ConnPool::GetConnection(){ Connection*con; pthread_mutex_lock(&lock); if(connList.size()>0)//连接池容器中还有连接 { con=connList.front();//得到第一个连接 connList.pop_front();//移除第一个连接 if(con->isClosed())//如果连接已经被关闭,删除后重新建立一个 { deletecon; con=this->CreateConnection(); } //如果连接为空,则创建连接出错 if(con==NULL) { --curSize; } pthread_mutex_unlock(&lock); returncon; } else{ if(curSize< maxSize){//还可以创建新的连接 con= this->CreateConnection(); if(con){ ++curSize; pthread_mutex_unlock(&lock); returncon; } else{ pthread_mutex_unlock(&lock); returnNULL; } } else{//建立的连接数已经达到maxSize pthread_mutex_unlock(&lock); returnNULL; } } } //回收数据库连接 voidConnPool::ReleaseConnection(sql::Connection * conn){ if(conn){ pthread_mutex_lock(&lock); connList.push_back(conn); pthread_mutex_unlock(&lock); } } //连接池的析构函数 ConnPool::~ConnPool() { this->DestoryConnPool(); } //销毁连接池,首先要先销毁连接池的中连接 voidConnPool::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); } //销毁一个连接 voidConnPool::DestoryConnection(Connection* conn) { if(conn) { try{ conn->close(); } catch(sql::SQLException&e) { perror(e.what()); } catch(std::exception&e) { perror(e.what()); } deleteconn; } }
3.参考文献:
百度百科——连接池:http://baike.baidu.com/view/1528732.htm
封装数据库连接池:http://hi.baidu.com/lbxthinker/blog/item/9b837b0f8a306fdb7acbe113.html