手寫實現簡易版數據庫連接池

功能集:獲取連接,回收連接,檢查連接狀態以及定時自檢所有空閒連接等

一、連接池配置屬性

參數 說明
driver 連接驅動
url 連接地址
username 連接名
password 連接密碼
minFreeConnections 空閒連接池,最小連接數,默認爲2
maxFreeConnections 空閒連接池,最大連接數,默認爲8
maxActiveConnection 活躍連接池,最大連接數,默認爲8
initConnections 初始化連接數,默認爲2個
connectionTimeOut 連接超時時間,默認爲20分鐘
recheckTime 自檢循環時間,默認爲60秒

類代碼編寫:DbConfig.java

package com.nys.dbpool;

/**
 * 配置連接池屬性信息
 * @author nysheng
 *
 */
public class DbConfig {
    private String driver; //連接驅動
    private String url; //連接地址
    private String username; //連接名
    private String password; //連接密碼
    private Integer minFreeConnections=2; //空閒連接池,最小連接數,默認爲2
    private Integer maxFreeConnections=8; //空閒連接池,最大連接數,默認爲8
    private Integer maxActiveConnection=8; //活躍連接池,最大連接數,默認爲8
    private Integer initConnections=2; //初始化連接數,默認爲2個
    private Long connectionTimeOut=1000*60*20L; //連接超時時間,默認爲20分鐘
    private Long recheckTime=1000*60L; //自檢循環時間,默認爲60秒
    // .......get/set方法.......
}

二、連接池

參數 說明
freePool 空閒連接池
activePool 活躍連接池
dbConfig 連接池配置
countConnection 記錄已創建的連接數

類代碼編寫:ConnectionPool.java

package com.nys.dbpool;

import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import com.nys.enums.ResultEnum;
import com.nys.exceptions.ConnectionPoolException;

import java.sql.Connection;
/**
 * 連接池
 * @author nysheng
 *
 */
public class ConnectionPool {
    //空閒連接池
    private CopyOnWriteArrayList<Connection> freePool=new CopyOnWriteArrayList<Connection>();
    //活躍連接池
    private CopyOnWriteArrayList<Connection> activePool=new CopyOnWriteArrayList<Connection>();
    //連接池配置
    private DbConfig dbConfig;
    //記錄已創建的連接數
    private AtomicInteger countConnection=new AtomicInteger();
    public ConnectionPool(DbConfig dbConfig) {
        this.dbConfig=dbConfig;
        try {
            Class.forName(dbConfig.getDriver());
            //1. 初始化連接池
            for(int i=0;i<dbConfig.getInitConnections();i++) {
                Connection connection=newConnection();
                if(connection!=null) {
                    freePool.add(connection);
                }
            }
            //2.新開一線程,啓動自檢機制
            new Thread(new Runnable() {
                @Override
                public void run() {
                    recheckConnection();
                }
            }).start();
        } catch (ClassNotFoundException e) {
            throw new ConnectionPoolException(ResultEnum.DRIVER_NOT_FOUND);
        }
    }

    /**
     * 創建新連接
     * @return
     */
    private synchronized Connection newConnection() {
        Connection connection=null;
        try {
            connection=DriverManager.getConnection(dbConfig.getUrl(), dbConfig.getUsername(), dbConfig.getPassword());
            countConnection.incrementAndGet();
        } catch (SQLException e) {
            throw new ConnectionPoolException(ResultEnum.CONNECTION_FAIL);
        }
        return connection;
    }
    /**
     * 判斷連接是否可用
     * @param connection
     * @return
     */
    private boolean isAlive(Connection connection) {
        try {
            if(connection==null||connection.isClosed()) {
                return false;
            }
        } catch (SQLException e) {
            throw new ConnectionPoolException(ResultEnum.CONNECTION_STATUS_ERROE);
        }
        return true;
    }
    /**
     * 檢查當前連接數是否低於最低線程數,是則創建新線程
     */
    private synchronized void recheckConnection() {
        //1.檢查所有空閒連接是否可用,不可用的直接關閉連接
        //使用迭代器來進行數據的遍歷刪除,避免快速迭代失敗
        Iterator<Connection> it=freePool.iterator();
        while(it.hasNext()) {
            if(!isAlive(it.next())) {
                it.remove();
            }
        }
        //2.檢查當前連接數是否滿足最低空閒連接數,若低於最小空閒數,新增連接放入空閒池
        final int count=countConnection.get();
        if(count<dbConfig.getMinFreeConnections()) {
            for(int i=count;i<2;i++) {
                Connection connection=newConnection();
                if(connection!=null) {
                    freePool.add(connection);
                }
            }
        }
        try {
            long start=System.currentTimeMillis();
            while(System.currentTimeMillis()-start<dbConfig.getRecheckTime()) {
                wait(dbConfig.getRecheckTime());
            }
            recheckConnection();
        } catch (InterruptedException e) {
            throw new ConnectionPoolException(ResultEnum.INTERRUPT_ERROR);
        }
    }
    /**
     * 從連接池中獲取連接
     * @return
     */
    public synchronized Connection getConnecion() {
        Connection connection=null;
        //1. 判斷當前已連接數是否小於最大活躍連接數
        if(activePool.size()<dbConfig.getMaxActiveConnection()) {
            //2.如果空閒連接池裏存在連接,拿出連接
            if(freePool.size()>0) {
                connection=freePool.remove(0);
            }else {
                //3.空閒連接不夠,創建新連接
                connection=newConnection();
            }
            if(isAlive(connection)) {
                activePool.add(connection);
            }
        }else {
            //4.活躍連接池內連接數已滿,阻塞線程,等待喚醒
            try {
                wait(dbConfig.getConnectionTimeOut());
            } catch (InterruptedException e) {
                throw new ConnectionPoolException(ResultEnum.INTERRUPT_ERROR);
            }
            return getConnecion();
        }
        return connection;
    }

    public synchronized boolean releaseConnection(Connection connection) {
        //1.判斷連接是否可用
        if(isAlive(connection)) {
            //2.判斷空閒池是否已滿
            if(freePool.size()<dbConfig.getMaxFreeConnections()) {
                //未滿,回收連接
                freePool.add(connection);
            }else {
                //已滿,關閉連接並減少連接計數
                try {
                    countConnection.decrementAndGet();
                    connection.close();
                } catch (SQLException e) {
                    throw new ConnectionPoolException(ResultEnum.CONNECTION_CLOSE_FAIL);
                }
            }
            //3.從活躍池中移除連接
            activePool.remove(connection);
            //4.喚醒所有被阻塞的線程
            notifyAll();
            return true;
        }
        return false;
    }
}

三、調用方式(單例+配置文件)

配置文件:jdbc.properties

jdbc.url=jdbc:mysql://localhost:3306/nys_step5?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&useSSL=false
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.username=root
jdbc.password=123456
# 空閒連接池,最小連接數,默認爲2
jdbc.minFreeConnections=2
# 空閒連接池,最大連接數,默認爲8
jdbc.maxFreeConnections=8
# 活躍連接池,最大連接數,默認爲8
jdbc.maxActiveConnection=8
# 初始化連接數,默認爲2
jdbc.initConnections=2
# jdbc.connectionTimeOut=1000*60*20L; //連接超時時間,默認爲20分鐘,不支持自定義
# jdbc.recheckTime=1000*60L; //自檢循環時間,默認爲60秒,不支持自定義

單例類:DbPoolSingleton.java

package com.nys.singleton;

import com.nys.dbpool.ConnectionPool;
import com.nys.dbpool.DbConfig;
import com.nys.exceptions.MyIOException;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * 數據庫連接池單例對象-靜態內部類
 * @author nysheng
 */
public class DbPoolSingleton {
    private static class Builder{
        //1.加載jdbc配置文件,配置連接池信息
        private static DbConfig dbConfig=null;
        static{
            dbConfig=new DbConfig();
            Properties properties=new Properties();
            InputStream inputStream=DbPoolSingleton.class.getClassLoader().getResourceAsStream("jdbc.properties");
            try {
                properties.load(inputStream);//加載properties文件
                String url=properties.getProperty("jdbc.url");
                String driver=properties.getProperty("jdbc.driver");
                String username=properties.getProperty("jdbc.username");
                String password=properties.getProperty("jdbc.password");
                String minFreeConnections=properties.getProperty("jdbc.minFreeConnections");
                String maxFreeConnections=properties.getProperty("jdbc.maxFreeConnections");
                String maxActiveConnection=properties.getProperty("jdbc.maxActiveConnection");
                String initConnections=properties.getProperty("jdbc.initConnections");
                dbConfig.setUrl(url);
                dbConfig.setDriver(driver);
                dbConfig.setUsername(username);
                dbConfig.setPassword(password);
                //空閒連接池,最小連接數,默認爲2
                dbConfig.setMinFreeConnections(minFreeConnections==null||minFreeConnections.equals("")?2:Integer.parseInt(minFreeConnections));
                //空閒連接池,最大連接數,默認爲8
                dbConfig.setMaxFreeConnections(maxFreeConnections==null||maxFreeConnections.equals("")?8:Integer.parseInt(maxFreeConnections));
                //活躍連接池,最大連接數,默認爲8
                dbConfig.setMaxActiveConnection(maxActiveConnection==null||maxActiveConnection.equals("")?8:Integer.parseInt(maxActiveConnection));
                //初始化連接數,默認爲2個
                dbConfig.setInitConnections(initConnections==null||initConnections.equals("")?2:Integer.parseInt(initConnections));
            } catch (IOException e) {
                throw new MyIOException(e.getMessage());
            }
        }
        //2.創建連接池對象
        public static ConnectionPool connectionPool=new ConnectionPool(dbConfig);
    }
    public static ConnectionPool newInstance(){
        return Builder.connectionPool;
    }
}

四、全局異常處理

使用枚舉定義異常狀態:ResultEnum

package com.nys.enums;

/**
 * 異常消息
 * @author nysheng
 *
 */
public enum ResultEnum {

    DRIVER_NOT_FOUND(10000,"加載驅動失敗"),
    CONNECTION_FAIL(10001,"建立連接失敗"),
    CONNECTION_STATUS_ERROE(10002,"連接狀態異常"),
    CONNECTION_CLOSE_FAIL(10003,"連接關閉失敗"),
    INTERRUPT_ERROR(10004,"阻塞異常");

    private Integer code;
    private String msg;
    ResultEnum(Integer code,String msg){
        this.code=code;
        this.msg=msg;
    }
    ResultEnum(){}
    public String getMsg() {
        return msg;
    }
    public Integer getCode() {
        return code;
    }
}

自定義異常:ConnectionPoolException.java

package com.nys.exceptions;

import com.nys.enums.ResultEnum;
/**
 * 連接池異常處理
 * @author nysheng
 *
 */
public class ConnectionPoolException extends RuntimeException {
    private static final long serialVersionUID = 1L;

    private Integer code;

    public ConnectionPoolException() {
        super();
        code=0;
    }
    public ConnectionPoolException(ResultEnum resultEnum) {
        super(resultEnum.getMsg());
        this.code=resultEnum.getCode();
    }

    public Integer getCode() {
        return code;
    }
}

代碼地址:https://github.com/nysheng/dbpool

參考:數據庫連接池(手寫簡單實現)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章