Java jdbc数据库连接池完整实现
北京师范大学 计算机系
张人杰
前言:
网上流传着一些jdbc数据库连接池的实现,纵观所有,其操作方式、流程均对原有jdbc的操作方式、流程做了改变,也即在用完一个connection后,不能调用close方法对其关闭,而必须调用连接池对象的pool.free(connection)方法放回连接池pool。这在单个连接池时不显麻烦。但在多个连接池或连接池对象无法传入调用的方法时,释放连接就会显得非常麻烦。前段时间,出于兴趣,本人开发了一套jdbc的数据库连接池R2,在此与大家分享。
设计思想:
1、 基本思想为通过代理的方式,对原数据库真实连接conn对象进行包装,改写close方法,在Connection对象调用此方法时将真实连接conn放回连接池;
2、 在通过getConnection方法获得一个真实连接时,对其进行包装,返回R2Connection对象;
3、 R2Connection实现java.sql.Connection接口,完全参照jdbc标准,除close方法外,所有方法均直接调用真实连接conn进行操作。
4、 R2Pool为产生数据库连接的工厂类,处理:控制连接池中的连接数,定时清理空闲连接,保持连接等操作。
( 下载地址:http://download.csdn.net/user/ranjio_z )
代码讲解:
R2Connection类,对原真实Connection的包装
/**
* R2 jdbc connection class, to replace the default jdbc connection and replace the close function
* R2连接池,链接包装类,处理close方法
* @author 张人杰 北京师范大学 计算机系
* alex.zhangrj Beijing Normal University
* 2011/11/26
*/
public class R2Connection implements Connection {
private Connection conn=null;
private R2Pool pool=null;
public void setConnection(Connection jdbcConn){//真实连接对象
this.conn=jdbcConn;
}
public void setPool(R2Pool pool){//连接来源(连接池)的引用
this.pool=pool;
}
@Override
public void clearWarnings() throws SQLException {
if(conn!=null)conn.clearWarnings();
}
@Override
public void close() throws SQLException {//关闭链接时,不实际关闭Connection,而把其放入连接池中
synchronized(this){
if(conn!=null)pool.releaseConnection(conn);
conn=null;
}
}
/**
* 关闭jdbc真实链接
* @throws SQLException
*/
public void closeJdbcConnection() throws SQLException {//关闭jdbc真实链接
if(conn!=null)conn.close();
conn=null;
}
@Override
public void commit() throws SQLException {
if(conn!=null)conn.commit();
}
@Override
public Array createArrayOf(String typeName, Object[] elements)
throws SQLException {
if(conn!=null)return conn.createArrayOf(typeName, elements);
return null;
}
@Override
public Blob createBlob() throws SQLException {
if(conn!=null)return conn.createBlob();
return null;
}
@Override
public Clob createClob() throws SQLException {
if(conn!=null)return conn.createClob();
return null;
}
@Override
public NClob createNClob() throws SQLException {
if(conn!=null)return conn.createNClob();
return null;
}
@Override
public SQLXML createSQLXML() throws SQLException {
if(conn!=null)return conn.createSQLXML();
return null;
}
@Override
public Statement createStatement() throws SQLException {
if(conn!=null)return conn.createStatement();
return null;
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency)
throws SQLException {
if(conn!=null)return conn.createStatement(resultSetType,resultSetConcurrency);
return null;
}
@Override
public Statement createStatement(int resultSetType,
int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
if(conn!=null)return conn.createStatement(resultSetType,resultSetConcurrency,resultSetHoldability);
return null;
}
@Override
public Struct createStruct(String typeName, Object[] attributes)
throws SQLException {
if(conn!=null)return conn.createStruct(typeName,attributes);
return null;
}
@Override
public boolean getAutoCommit() throws SQLException {
if(conn!=null)return conn.getAutoCommit();
return false;
}
@Override
public String getCatalog() throws SQLException {
if(conn!=null)return conn.getCatalog();
return null;
}
@Override
public Properties getClientInfo() throws SQLException {
if(conn!=null)return conn.getClientInfo();
return null;
}
@Override
public String getClientInfo(String name) throws SQLException {
if(conn!=null)return conn.getClientInfo(name);
return null;
}
@Override
public int getHoldability() throws SQLException {
if(conn!=null)return conn.getHoldability();
return 0;
}
@Override
public DatabaseMetaData getMetaData() throws SQLException {
if(conn!=null)return conn.getMetaData();
return null;
}
@Override
public int getTransactionIsolation() throws SQLException {
if(conn!=null)return conn.getTransactionIsolation();
return 0;
}
@Override
public Map<String, Class<?>> getTypeMap() throws SQLException {
if(conn!=null)return conn.getTypeMap();
return null;
}
@Override
public SQLWarning getWarnings() throws SQLException {
if(conn!=null)return conn.getWarnings();
return null;
}
@Override
public boolean isClosed() throws SQLException {
if(conn!=null)return conn.isClosed();
return false;
}
@Override
public boolean isReadOnly() throws SQLException {
if(conn!=null)return conn.isReadOnly();
return false;
}
@Override
public boolean isValid(int timeout) throws SQLException {
if(conn!=null)return conn.isValid(timeout);
return false;
}
@Override
public String nativeSQL(String sql) throws SQLException {
if(conn!=null)return conn.nativeSQL(sql);
return null;
}
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
if(conn!=null)return conn.prepareCall(sql);
return null;
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType,
int resultSetConcurrency) throws SQLException {
if(conn!=null)return conn.prepareCall(sql,resultSetType,resultSetConcurrency);
return null;
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType,
int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
if(conn!=null)return conn.prepareCall(sql,resultSetType,resultSetConcurrency,resultSetHoldability);
return null;
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
if(conn!=null)return conn.prepareStatement(sql);
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
throws SQLException {
if(conn!=null)return conn.prepareStatement(sql, autoGeneratedKeys);
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
throws SQLException {
if(conn!=null)return conn.prepareStatement(sql, columnIndexes);
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames)
throws SQLException {
if(conn!=null)return conn.prepareStatement(sql, columnNames);
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType,
int resultSetConcurrency) throws SQLException {
if(conn!=null)return conn.prepareStatement(sql, resultSetType, resultSetConcurrency);
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType,
int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
if(conn!=null)return conn.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
return null;
}
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
if(conn!=null)conn.releaseSavepoint(savepoint);
}
@Override
public void rollback() throws SQLException {
if(conn!=null)conn.rollback();
}
@Override
public void rollback(Savepoint savepoint) throws SQLException {
if(conn!=null)conn.rollback(savepoint);
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
if(conn!=null)conn.setAutoCommit(autoCommit);
}
@Override
public void setCatalog(String catalog) throws SQLException {
if(conn!=null)conn.setCatalog(catalog);
}
@Override
public void setClientInfo(Properties properties)
throws SQLClientInfoException {
if(conn!=null)conn.setClientInfo(properties);
}
@Override
public void setClientInfo(String name, String value)
throws SQLClientInfoException {
if(conn!=null)conn.setClientInfo(name, value);
}
@Override
public void setHoldability(int holdability) throws SQLException {
if(conn!=null)conn.setHoldability(holdability);
}
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
if(conn!=null)conn.setReadOnly(readOnly);
}
@Override
public Savepoint setSavepoint() throws SQLException {
if(conn!=null)return conn.setSavepoint();
return null;
}
@Override
public Savepoint setSavepoint(String name) throws SQLException {
if(conn!=null)return conn.setSavepoint(name);
return null;
}
@Override
public void setTransactionIsolation(int level) throws SQLException {
if(conn!=null)conn.setTransactionIsolation(level);
}
@Override
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
if(conn!=null)conn.setTypeMap(map);
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
if(conn!=null)return conn.isWrapperFor(iface);
return false;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
if(conn!=null)return conn.unwrap(iface);
return null;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
close();
}
}
R2Pool类,池对象类
/**
* R2 jdbc connection pool
* R2连接池,连接池类
* @author 张人杰 北京师范大学 计算机系
* alex.zhangrj Beijing Normal University
* 2011/11/26
*/
public class R2Pool {
private ConcurrentLinkedQueue<R2ConnectionWrap> pool=null;
private String driver="com.mysql.jdbc.Driver";
private String url="jdbc:mysql://localhost:3306/movie_info?useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false";
private String user="root";
private String password="1234";
private int maxSize=50;
private int minSize=2;
private int initSize=2;
private int increment=2;
private long timeout=120000;
private long idleTestPeriod=120000;
private String keepAliveSql="select 1;";
private boolean closeIdleConnection=true;
private long maxIdleTime=300000;
private int retryTimesWhileGetNullConnection=-1;
private long retryDurationDuringGetNullConnection=1000;
private int retryTimesWhileCanNotConnectServer=-1;
private long retryDurationDuringConnectingServer=1000;
private AtomNumber currentSize=new AtomNumber(0);
private Thread cleanPoolThread;
private boolean keepClean=true;
private boolean closed=false;
private Logger logger=Logger.getLogger(R2Pool.class);
class AtomNumber{
int num=0;
public AtomNumber(){
num=0;
}
public AtomNumber(int n){
num=n;
}
public void inc(int n){
synchronized (this) {
num+=n;
}
}
public void dec(int n){
synchronized (this) {
num-=n;
}
}
public int getValue(){
return num;
}
public void setValue(int n){
synchronized (this) {
num=n;
}
}
}
/**
* 得到当前池中的连接数量
* @return
*/
public int getCurrentSize(){
return currentSize.getValue();
}
/**
* 初始化R2连接池,增加池中链接,设置清理线程。
* 注意:
* 1、 此方法只可调用一次,若意外被调用多次,则原有jdbc连接会在Connection对象被gc时释放,释放时间较长。
* 2、在开始使用连接池后调用此方法,则可能造成连接数超过配置项最大连接数maxSize
*/
public void init(){
pool=new ConcurrentLinkedQueue<R2ConnectionWrap>();
closed=false;
currentSize.setValue(0);
increasePool(pool, initSize);
if(cleanPoolThread!=null){//防止重复调用,造成多个清理线程
keepClean=false;
cleanPoolThread.interrupt();
try {
Thread.sleep(idleTestPeriod+1000);
} catch (InterruptedException e) {
logger.error("reinitial pool error,interrupted while waiting the old clean thread to close.", e);
}
logger.error("the old cleanPoolThread is closed.");
keepClean=true;
}
cleanPoolThread=new Thread(){
@Override
public void run() {
while(keepClean){
try {
Thread.sleep(idleTestPeriod);
} catch (InterruptedException e) {
logger.error("clean pool thread sleep error", e);
}
int availableNum=pool.size();//目前池中拥有的连接数
R2ConnectionWrap wrap=pool.poll();
for(int i=0;i<availableNum;i++){
if(wrap==null)break;//池中已无可用连接
long now = Calendar.getInstance().getTimeInMillis();
if(closeIdleConnection&&(wrap.getLastUsingTime()+maxIdleTime<now)&¤tSize.getValue()>minSize){//空闲时间过长则关闭连接
try {
wrap.getConnection().closeJdbcConnection();
} catch (SQLException e) {
logger.error("close the real jdbc connection error", e);
}
currentSize.dec(1);
}else{
Connection conn = wrap.getConnection();
PreparedStatement pstmt;
try {
pstmt = conn.prepareStatement(keepAliveSql);
pstmt.execute();
pstmt.close();
} catch (SQLException e) {
logger.error("execute keepAliveSql error", e);
}
pool.add(wrap);
}
wrap=pool.poll();
}
}
}
};
cleanPoolThread.start();
}
/**
* 增加R2连接池中连接的数量(由调用此方法的方法控制同步锁)
* @param pool
* @param size
*/
private void increasePool(ConcurrentLinkedQueue<R2ConnectionWrap> pool,int size){
try {
Class.forName(driver);
for(int i=0;currentSize.getValue()<maxSize&&i<size;i++){
DriverManager.setLoginTimeout((int)(timeout/1000));
try {
Connection jdbcConn = DriverManager.getConnection(url, user, password);
releaseConnection(jdbcConn);
currentSize.inc(1);
} catch (SQLException e) {
logger.error("getConnection error,maybe user or password or url wrong,or the db is not available (timeout) at this time.", e);
}
}
} catch (ClassNotFoundException e) {
logger.error("jdbc driver error", e);
}
}
/**
* 释放连接(将jdbc连接放回R2连接池中)
* @param jdbcConn
*/
public void releaseConnection(Connection jdbcConn){
R2Connection connection = new R2Connection();
connection.setConnection(jdbcConn);
connection.setPool(this);
R2ConnectionWrap wrap=new R2ConnectionWrap();
wrap.setConnection(connection);
wrap.setLastUsingTime(Calendar.getInstance().getTimeInMillis());
pool.add(wrap);
}
/**
* 从R2连接池中获得连接
* @return 经过包装处理close方法后的Connection对象
*/
public Connection getConnection(){
if(closed){//连接池已关闭
return null;
}
synchronized(this){//若连接池未初始化,则初始化
if(pool==null){
init();
}
}
R2ConnectionWrap wrap = pool.poll();
int i=0;
while(wrap==null&&(i<retryTimesWhileGetNullConnection||retryTimesWhileGetNullConnection<=0)){//当retryTimes为-1时,保持重试
synchronized(this){//若连接池中连接已用完(返回的wrap为null),则增加连接池中连接数量
if(currentSize.getValue()<maxSize){
increasePool(pool, increment);
}
}
wrap = pool.poll();
if(wrap!=null)break;
try {
Thread.sleep(retryDurationDuringGetNullConnection);
} catch (InterruptedException e) {
logger.error("retry sleep error",e);
}
++i;
}
if(wrap!=null){
R2Connection connection = wrap.getConnection();
int j=0;
while(j<retryTimesWhileCanNotConnectServer||retryTimesWhileCanNotConnectServer<=0){
PreparedStatement pstmt;
try {//检查连接是否正常打开
pstmt = connection.prepareStatement(keepAliveSql);
pstmt.execute();
pstmt.close();
break;
} catch (SQLException e) {
logger.error("execute keepAliveSql error", e);
}//若未正常打开,则重新链接数据库,以保证返回的数据库连接可用
DriverManager.setLoginTimeout((int)(timeout/1000));
try {
Connection jdbcConn = DriverManager.getConnection(url, user, password);
connection.setConnection(jdbcConn);
} catch (SQLException e) {
logger.error("connecting server error,maybe user or password or url wrong,or the db is not available (timeout) at this time.", e);
}
try {
Thread.sleep(retryDurationDuringConnectingServer);
} catch (InterruptedException e) {
logger.error("retry sleep error while connectiing server.", e);
}
++j;
}
return connection;
}else{
return null;
}
}
public void close(){
keepClean=false;
closed=true;
}
//以下为set,get方法
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getMaxSize() {
return maxSize;
}
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
public int getMinSize() {
return minSize;
}
public void setMinSize(int minSize) {
this.minSize = minSize;
}
public int getInitSize() {
return initSize;
}
public void setInitSize(int initSize) {
this.initSize = initSize;
}
public int getIncrement() {
return increment;
}
public void setIncrement(int increment) {
this.increment = increment;
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public long getIdleTestPeriod() {
return idleTestPeriod;
}
public void setIdleTestPeriod(long idleTestPeriod) {
this.idleTestPeriod = idleTestPeriod;
}
public String getKeepAliveSql() {
return keepAliveSql;
}
public void setKeepAliveSql(String keepAliveSql) {
this.keepAliveSql = keepAliveSql;
}
public boolean isCloseIdleConnection() {
return closeIdleConnection;
}
public void setCloseIdleConnection(boolean closeIdleConnection) {
this.closeIdleConnection = closeIdleConnection;
}
public long getMaxIdleTime() {
return maxIdleTime;
}
public void setMaxIdleTime(long maxIdleTime) {
this.maxIdleTime = maxIdleTime;
}
public int getRetryTimesWhileGetNullConnection() {
return retryTimesWhileGetNullConnection;
}
public void setRetryTimesWhileGetNullConnection(
int retryTimesWhileGetNullConnection) {
this.retryTimesWhileGetNullConnection = retryTimesWhileGetNullConnection;
}
public long getRetryDurationDuringGetNullConnection() {
return retryDurationDuringGetNullConnection;
}
public void setRetryDurationDuringGetNullConnection(
long retryDurationDuringGetNullConnection) {
this.retryDurationDuringGetNullConnection = retryDurationDuringGetNullConnection;
}
public int getRetryTimesWhileCanNotConnectServer() {
return retryTimesWhileCanNotConnectServer;
}
public void setRetryTimesWhileCanNotConnectServer(
int retryTimesWhileCanNotConnectServer) {
this.retryTimesWhileCanNotConnectServer = retryTimesWhileCanNotConnectServer;
}
public long getRetryDurationDuringConnectingServer() {
return retryDurationDuringConnectingServer;
}
public void setRetryDurationDuringConnectingServer(
long retryDurationDuringConnectingServer) {
this.retryDurationDuringConnectingServer = retryDurationDuringConnectingServer;
}
}
R2ConnectionWrap类,对R2Connection的包装,用于连接池中定时任务的处理(即,在原R2Connection类的基础上,加入时间属性)
/**
* R2 jdbc connection Wrap class, to wrap connection and lastUsingTime
* R2连接池,链接包装类,用于处理定时清理链接
* @author 张人杰 北京师范大学 计算机系
* alex.zhangrj Beijing Normal University
* 2011/11/26
*/
public class R2ConnectionWrap {
private long lastUsingTime=0;//开始使用此链接的时间
private R2Connection connection=null;
public long getLastUsingTime() {
return lastUsingTime;
}
public void setLastUsingTime(long lastUsingTime) {
this.lastUsingTime = lastUsingTime;
}
public R2Connection getConnection() {
return connection;
}
public void setConnection(R2Connection connection) {
this.connection = connection;
}
}
配置文件样本:
#driver of jdbc
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/movie_info?useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false
user=root
password=1234
#max size of connection pool
maxSize=50
#min size of connection pool
minSize=2
#initial size of connection pool
initSize=2
#increment of connections while all connections are busy and hasn't reached the max size of connection pool
increment=2
#timeout of connection(millisecond)
timeout=120000
#period of testing idle connect(millisecond, must positive)
idleTestPeriod=120000
#sql to execute for keeping connection alive
keepAliveSql=select 1;
#close idle connections while reaching threshold of max idle time if it's true
closeIdleConnection=true
#max idle time before close a idle connection(millisecond)
maxIdleTime=300000
#retry times while get null connection(keep retry while 0 or negative)
retryTimesWhileGetNullConnection=-1
#retry duration during get null connection(millisecond,immediately retry while 0)
retryDurationDuringGetNullConnection=1000
#retry times while can not connect server(keep retry while 0 or negative)
retryTimesWhileCanNotConnectServer=-1
#retry duration during connecting server(millisecond,immediately retry while 0)
retryDurationDuringConnectingServer=1000