1. 程式人生 > >資料庫連線池認知+手寫一個

資料庫連線池認知+手寫一個

首先推薦一篇不錯的文章。https://www.cnblogs.com/newpanderking/p/3875749.html

接下來,看了這篇文章之後,我們再看一下

1.首先是連線池的規範

/**
 * 連線池  頂級規範
 */
public interface IPool {
    /**
     * 獲取連線
     */
    MyConn getConnection();
    
}
/**
 * 定義了一定規範的抽象類
 */
/**
 * 
 * 連線池 超類:用以提供部分規範
 */
public abstract class BasePool implements IPool{
    
    protected String jdbcDriver;
    
    protected String dbUrl;

    protected String dbUsername;

    protected String dbPassword;
    
    protected int maxConnnect;
    
    protected int initedConnect;
    
    protected List<MyConn> conns;
    
    
    public BasePool(String jdbcDriver, String dbUrl, String dbUsername, String dbPassword, int maxConnnect,int firstInit){ 
        //我本來要放棄這個初始化引數:int firstInit。
        //其本來是決定new多少個connection,來減少啟動的消耗。
        //因為第一次優化後產生的bug主要是在於多執行緒下init<max時,生成了更多的連線導致的問題。
        //但是我看見阿里的druid都有這個引數,
        //就嘗試了一下將createConn(int)這個方法加上鎖(synchronized)或者lock都可以。
        //這個地方其實應該也可以採用volatile關鍵字
        //但是其實createConn(int)這個方法應該競爭並沒有那麼激烈,不用強行在這個地方追求極致的效率
        this.jdbcDriver = jdbcDriver;
        this.dbUrl = dbUrl;
        this.dbUsername = dbUsername;
        this.dbPassword = dbPassword;
        this.maxConnnect = maxConnnect;
        //是為了保證執行緒安全。但是這樣做,效率會降低很多,後續想個方式來處理
        //哈哈 解決了,但是還是把最初的版本放在這裡
        //conns=new Vector<>(new MyConn[initedConnect]);
        conns=new ArrayList<>(maxConnnect);
        //建立池
        initPool(firstInit);
        //建立守護執行緒
        new Thread(new GuardConnection(this,4)).start();;
    }
    
    protected void initPool(int initedConnect) {
        try {
            Class.forName(jdbcDriver);
            createConnections(initedConnect);
        }catch (Exception e) {
            throw new RuntimeException("建立資料庫連接出錯");
        }
    }

    protected void createConnections(int count) {
        for (int i = 0; i < count; i++) {
            // 大於最大連線數的話就不建立了
            if (initedConnect + 1 > maxConnnect)
                return;
            if (count <= 0)
                throw new RuntimeException("初始化連線數不能為空");
            try {
                Connection conn = DriverManager.getConnection(dbUrl, dbUsername, dbPassword);
                conns.add(new MyConn(conn, false));
                // 已載入的連線數量++
                initedConnect++;
            } catch (SQLException e) {
                e.printStackTrace();
            }
            
        }
    }
    
    public abstract List<MyConn> getConns();
}
/**
 * 連線池例項
 */
public class MyPool extends BasePool {
    
    public MyPool(String jdbcDriver, String dbUrl, String dbUsername, String dbPassword, int maxConnnect, int initedConnect) {
        super(jdbcDriver, dbUrl, dbUsername, dbPassword, maxConnnect, initedConnect);
    }
    
    @Override
    public MyConn getConnection() {
        // 判斷是否載入
        if (initedConnect == 0) {
            System.out.println("連線池中還沒有連線!");
            throw new RuntimeException("連線池中還沒有連線!");
        }
        
        /**
         * (1)當還有可用連線的時候
         * (2)可用連線不夠了,需要建立或者等待
         */
        MyConn conn = getActiveConnection();
        // (2)的情況
        while (null == conn) {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //建立連線
            createConnections(1);
            conn = getActiveConnection();
        }
    return conn;
    
    }
    
    /**
     * 從集合中得到連線
     * 
     * @return
     * @return
     */
     private MyConn getActiveConnection() {
        for (int i = 0; i < conns.size(); i++) {
            MyConn conn = conns.get(i);
            if(conn.setBusy(true)) {
                conn.setUseTime(System.currentTimeMillis());
                return conn;
            }
        }
        return null;
    }

    @Override
    public List<MyConn> getConns() {
        return super.conns;
    }
}

2.connection的包裝物件

/**
 * 包含conn的物件
 */
public class MyConn {
    /**
     * 實際connection
     */
    private Connection conn;
    /**
     * 是否正在工作
     * 剛建立時,預設是false。
     * volatile關鍵字,是為了提高效率,將陣列的鎖定放在了每一個元素的鎖定
     * 就有點類似與conCurrentHashMap。
     * 後面發現即使使用了volatile關鍵字還是會存在問題。
     * 因此後面還是使用了synchronized關鍵字來控制,但是即使如此,還是講粒度放在了每個元素上。
     * 從而提高了效能
     */
    private volatile boolean isBusy=false;
    /**
     * 開始使用的時間:用於守護執行緒去強制回收
     */
    private long useTime;
    /**
     * 查詢
     * @param sql
     * @return
     */
    public ResultSet query(String sql) {
        Statement sm = null;
        ResultSet rs = null;
        
        try {
            sm = conn.createStatement();
            rs = sm.executeQuery(sql);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return rs;
    }
    /**
     * 修改操作
     * @param sql
     * @return
     */
    public int update(String sql) {
        Statement sm = null;
        int count = -1;
        try {
            sm = conn.createStatement();
            count = sm.executeUpdate(sql);
        }
        catch (SQLException e) {
            e.printStackTrace();
        }
        return count;
    }
    
    public boolean isBusy() {
        return isBusy;
    }
    /**
     * 當傳入的是true的時候,說明,準備佔用 
     * 但是外面用的list不安全,因此考慮在set層操作,
     * 這裡的返回值是為了確定獲取成功
     */
    public boolean setBusy(boolean isBusy) {
        //本來if else這種東西應該抽象出來,但是目前先這樣吧
       synchronized(this) {
            if(isBusy) {
                if(this.isBusy) {
                    return !isBusy;
                }
            }
            //false的時候無所謂(後面思考是否會有執行緒安全的問題)
            this.isBusy=isBusy;
            return isBusy;
        }
    }
    
    public void close() {
        this.isBusy=false;
    }
    
    
    public MyConn(Connection conn, boolean isBusy) {
        super();
        this.conn = conn;
        this.isBusy = isBusy;
    }
    public long getUseTime() {
        return useTime;
    }
    public void setUseTime(long useTime) {
        this.useTime = useTime;
    }
    
    public void rollback() throws SQLException {
        conn.rollback();
    }
}

3.守護執行緒

/**
 * connection守護執行緒
 */
public class GuardConnection implements Runnable{
    
    private BasePool pool;
    
    private long time;
    
    @Override
    public void run() {
        while(true) {
            for(int i=0;i<pool.getConns().size();i++) {
                MyConn conn = pool.getConns().get(i);
                if(conn.isBusy() && System.currentTimeMillis() - conn.getUseTime() > time*1000 ) {
                    try {
                        conn.rollback();
                    }catch (Exception e) {
                    }finally {
                        conn.close();
                    }
                }
            }
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
            }
        }
    }
    
        
    public GuardConnection(BasePool pool,long time) {
        super();
        this.pool = pool;
        this.time=time;
    }

}

這樣就能夠完成一個簡單的連線池。但是還有很多東西沒有考慮。後面慢慢更新,一步步優化,加上功能:例如connection的守護執行緒,強制回池,效能等方面的。

---------------------------------------------------------------------------------------------------------------------------------------

目前,強制回池,守護執行緒都已經做到了,效能方面,經過測試,確實是有所提升。但是還是存在一些不足,例如可供拓展的一些AOP功能等等。後續考慮一下