實現一個簡單的數據庫連接池
前言:
最近在搞一個項目,就是要把SqlServer數據全部導入到Oracle中,也要讓項目由原來的SqlServer支持Oracle,於是自已在網上找了很多工具,發現導的時候都有問題,而且數據量非常龐大。一開始是自已手動導,將SqlServer數據庫導成支持Oracle的sql文件,然后再把這個sql文件導入到Oracle中,發現將一個10萬條的sql文件導入到Oracle中都要半小時,簡直崩潰了! 想想單個導sql文件的方式屬於單線程模式,因為使用PLSQL工具導是只有一個連接,於是就想到了使用多線程的方式導,也就是采用多個連接,多個子任務去導。因此便使用到了資源池的這種模式。使用多線程、資源池方式之后,速度提升了上千倍。
實現思路(如下圖所示):
說明:
使用一個池也可實現資源池(即Map<Connetion, Params> pool 這種方式)但是這種邏輯復雜一點,即一個pool中要保證不連接數不能超過最大值又要判斷哪些連接已經被占用。而我這里采用兩個池來實現,一個是Used池,用來存放正在連接的資源;一個是Pool池,用來存放已經釋放連接的資源;這樣邏輯清晰簡單。
實現步驟:
下面就是數據庫連接池的簡單實現方式:
1.編寫一個對象池的抽象類
package com.core.jdbc; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; public abstract class ObjectPool<T> { /** * 創建對象 */ protected abstract T create(); /** * 驗證對象有效性 */ public abstract boolean validate(T t, long createTime); /** * 使對象失效 */ public abstract void expire(T t); private ConcurrentHashMap<T, Long> used; // 正在使用的,保存新建的資源 private ConcurrentHashMap<T, Long> pool; // 未被使用的,保存釋放的、未失效的資源,池子里面保存有效可用的對象 private static int MAX_CONNECT_SIZE = 100; // 最大連接數(這里也是pool的最大size,雖然沒有定義pool的最大size,但是從整個邏輯上講,pool的size是小於這個值的) public static int MAX_KEEP_ALIVE_TIME = 3000; // 最大生存時間,這個時間也決定了創建連接的頻率 /** * 獲得資源 */ public synchronized T getResource() { T t = null; // 初始化 if (null == pool) { pool = new ConcurrentHashMap<T, Long>(); } if (null == used) { used = new ConcurrentHashMap<T, Long>(); } // 超過最大連接數,等待(注意:這里可以加一個拒絕策略,即超時拒絕連接還是繼續等待) while (used.size() >= MAX_CONNECT_SIZE) { try { this.wait(50); } catch (InterruptedException e) { e.printStackTrace(); } } // 默認先從池子里面去取 if (pool.size() > 0) { for (Entry<T, Long> entry : pool.entrySet()) { t = entry.getKey(); if (null!=t) { break; } } pool.remove(t); } // 在池子中未取到,創建一個新的 if (null == t) { t = create(); } used.put(t, System.currentTimeMillis()); this.notifyAll(); return t; } /** * 釋放某個資源 */ public synchronized void release(T t) { if (null==t) { return; } while (used.size()==0) { try { this.wait(50); } catch (InterruptedException e) { e.printStackTrace(); } } // 判斷是否過有效期 if (validate(t, used.get(t))) { // 放入池中 pool.put(t, System.currentTimeMillis()); used.remove(t); } else { expire(t); used.remove(t); } this.notifyAll(); } }
2.編寫數據庫連接池的具體實現類
public class ConnectionPool extends ObjectPool<Connection> { private static int count = 0; public ConnectionPool() { try { Class.forName("oracle.jdbc.driver.OracleDriver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } @Override protected Connection create() { Connection conn = null; try { conn = DriverManager.getConnection("jdbc:oracle:thin:@//127.0.0.1:1521/SZNY", "caoxiaobo", "123456"); } catch (SQLException e) { e.printStackTrace(); } count ++; logger.debug("建立連接次數" +count); return conn; } @Override public boolean validate(Connection o, long createTime) { if (System.currentTimeMillis() - createTime > MAX_KEEP_ALIVE_TIME) return false; return true; } @Override public void expire(Connection o) { try { o.close(); } catch (SQLException e) { e.printStackTrace(); } } }
3.編寫JDBC連接池單例類來確保只有一個池(確保ConnectionPool 對象唯一,即程序中所有的連接都從一個pool中去取)
public class JdbcConnection { private JdbcConnection () { } private static class Singleton { private static ConnectionPool pool = new ConnectionPool(); } public static Connection getConnection() { return Singleton.pool.getResource(); } public static void release(Connection conn) { Singleton.pool.release(conn); } }
4.並發測試類:
public class PoolTest { public static void main(String[] args) { Runnable run = new Runnable() { @Override public void run() { Connection conn = JdbcConnection.getConnection(); try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } JdbcConnection.release(conn); } };
// 創建2000個線程,模擬並發 for (int i=0; i<2000; i++) { Thread thread = new Thread(run); thread.start(); } } }
測試結果:
假設並發請求有2000個(假設數據庫最大連接數為150,這里設置要比它正常值小一點,即100),如果不使用資源池,那么就需要不斷的創建、銷毀2000次連接,對於服務器的性能來說影響還是比較大的。通過這個示例,我們可以看到這個結果(創建、銷毀)遠遠小於2000次,大概測試了一下平均100-120之間。當然這里的這個值是根據它設定的生存時間來決定的。