本例採用mysql資料庫,因此請先下載mysql-connection.jar
在我們的實際開發中,離不開和資料庫打交道。而和資料庫的通訊,離不開資料庫連接。
通常用JDBC串連資料庫時,需要載入資料驅動,然後再通過介面返回資料庫連接。
一般分為兩步:
1、載入驅動至記憶體
Class.forName(“com.mysql.jdbc.Driver”);
2、建立並擷取串連,返回的是JDBC中的Connection
DriverManager.getConnection(url, user, password)
樣本:
//要串連的資料庫URLString url = "jdbc:mysql://localhost:3306/tangwmdb";//串連的資料庫時使用的使用者名稱String username = "root";//串連的資料庫時使用的密碼String password = "root";//1.載入驅動//DriverManager.registerDriver(new com.mysql.jdbc.Driver());不推薦使用這種方式來載入驅動Class.forName("com.mysql.jdbc.Driver");//推薦使用這種方式來載入驅動//2.擷取與資料庫的連結Connection conn = DriverManager.getConnection(url, username, password);//3.擷取用於向資料庫發送sql語句的statementStatement st = conn.createStatement();String sql = "select id,name,password from members";//4.向資料庫發sql,並擷取代表結果集的resultsetResultSet rs = st.executeQuery(sql);//5.取出結果集的資料while(rs.next()){ System.out.println("id=" + rs.getObject("id")); System.out.println("name=" + rs.getObject("name")); System.out.println("password=" + rs.getObject("password"));}//6.關閉連結,釋放資源rs.close();st.close();conn.close();
眾所周知,建立資料庫連接需要消耗較多的資源,且建立時間也較長。如果網站一天100萬PV(假設每個頁面都有DB讀取或修改操作),程式就需要建立100萬次串連,極大的浪費資源。
事實上,同一時間需要建立資料庫連接的請求數量並不多,一般幾百個足夠了。那麼我們可以根據需要建立一個串連池,它負責分配、管理和釋放資料庫連接,它允許應用程式重複使用同一個現有的資料庫連接,而不是重建立立一個。這裡用到了設計模式中的一個模式:享元模式(Flyweight)。
比如我們的串連池中有1000條串連,請求來時,串連池從池中分配一條給請求,用完後收回,而不是銷毀,等到下次有請求來時,又可以重複分配使用。
當使用了資料庫連接池之後,在項目的實際開發中就不需要編寫串連資料庫的代碼了,直接從資料來源獲得資料庫的串連。比如:
//DBCP 資料庫連接池DataSource ds = BasicDataSourceFactory.createDataSource(prop);Connection conn = ds.getConnection();
可以看到建立串連的工作很簡單,因為複雜的分配、回收功能都交給了串連池去處理。
當前有一些開源的資料連線池實現: DBCP 資料庫連接池 C3P0 資料庫連接池
另外阿里開源項目Druid(整個項目由資料庫連接池、外掛程式架構和SQL解析器組成)中的資料庫連接池被很多互連網公司都採用在生產環境中。 編寫自己的資料庫連接池
編寫的串連池需要做到以下幾個基本點:
1、可配置並管理多個串連節點的串連池
2、始使化時根據配置中的初始串連數建立指定數量的串連
3、在串連池沒有達到最大串連數之前,如果有可用的空閑串連就直接使用空閑串連,如果沒有,就建立新的串連。
4、當串連池中的活動串連數達到最大串連數,新的請求進入等待狀態,直到有串連被釋放。
5、由於資料庫連接閑置久了會逾時關閉,因此需要串連池採用機制保證每次請求的串連都是有效可用的。
6、安全執行緒
7、串連池內部要保證指定最小串連數量的空閑串連。
對於最小串連數在實際應用中的效果以及與初始串連數的區別,其實理解的不是很透。在程式中我採用的方式是,如果 活動串連數 + 空閑串連數 < 最小串連數,就補齊對應數量(最小串連數 - 活動串連數 - 空閑串連數)的空閑串連
摘錄一段:
資料庫連接池的最小串連數和最大串連數的設定要考慮到以下幾個因素:
最小串連數:是串連池一直保持的資料庫連接,所以如果應用程式對資料庫連接的使用量不大,將會有大量的資料庫連接資源被浪費。
最大串連數:是串連池能申請的最大串連數,如果資料庫連接請求超過次數,後面的資料庫連接請求將被加入到等待隊列中,這會影響以後的資料庫操作。
如果最小串連數與最大串連數相差很大,那麼最先串連請求將會獲利,之後超過最小串連數量的串連請求等價於建立一個新的資料庫連接。不過,這些大於最小串連數的資料庫連接在使用完不會馬上被釋放,它將被放到串連池中等待重複使用或是逾時後被釋放。 系統結構:
1.串連池介面IConnectionPool:裡面定義一些基本的擷取串連的一些方法。
2.串連池介面實現ConnectionPool
3.串連池管理DBConnectionManager:管理不同的串連池,所有的串連都是通過這裡獲得。
4.其它工具類,諸如屬性讀取類PropertiesManager,屬性儲存類DBPropertyBean。
工程結構:
工程代碼:
DBPropertyBean.java
package com.twm.TDBConnectionPool;public class DBPropertyBean { private String nodeName; //資料連線驅動 private String driverName; //資料連線url private String url; //資料連線username private String username; //資料連線密碼 private String password; //串連池最大串連數 private int maxConnections ; //串連池最小串連數 private int minConnections; //串連池初始串連數 private int initConnections; //重連間隔時間 ,單位毫秒 private int conninterval ; //擷取連線逾時時間 ,單位毫秒,0永不逾時 private int timeout ; //構造方法 public DBPropertyBean(){ super(); } //下面是getter and setter /** * 擷取資料庫連接節點名稱 * @return */ public String getNodeName() { return nodeName; } /** * 設定資料庫連接節點名稱 * @param nodeName */ public void setNodeName(String nodeName) { this.nodeName = nodeName; } /** * 擷取資料庫驅動 * @return */ public String getDriverName() { return driverName; } /** * 設定資料庫驅動 * @param driverName */ public void setDriverName(String driverName) { this.driverName = driverName; } /** * 擷取資料庫url * @return */ public String getUrl() { return url; } /** * 設定資料庫url * @param url */ public void setUrl(String url) { this.url = url; } /** * 擷取使用者名稱 * @return */ public String getUsername() { return username; } /** * 設定使用者名稱 * @param username */ public void setUsername(String username) { this.username = username; } /** * 擷取資料庫連接密碼 * @return */ public String getPassword(){ return password; } /** * 設定資料庫連接密碼 * @param password */ public void setPassword(String password) { this.password = password; } /** * 擷取最大串連數 * @return */ public int getMaxConnections() { return maxConnections; } /** * 設定最大串連數 * @param maxConnections */ public void setMaxConnections(int maxConnections) { this.maxConnections = maxConnections; } /** * 擷取最小串連數(也是資料池初始串連數) * @return */ public int getMinConnections() { return minConnections; } /** * 設定最小串連數(也是資料池初始串連數) * @param minConnections */ public void setMinConnections(int minConnections) { this.minConnections = minConnections; } /** * 擷取初始加接數 * @return */ public int getInitConnections() { return initConnections; } /** * 設定初始串連數 * @param initConnections */ public void setInitConnections(int initConnections) { this.initConnections = initConnections; } /** * 擷取重連間隔時間,單位毫秒 * @return */ public int getConninterval() { return conninterval; } /** * 設定重連間隔時間,單位毫秒 * @param conninterval */ public void setConninterval(int conninterval) { this.conninterval = conninterval; } /** * 擷取連線逾時時間,單位毫秒 * @return */ public int getTimeout() { return timeout; } /** * 設定連線逾時時間 ,單位毫秒,0-無限重連 * @param timeout */ public void setTimeout(int timeout) { this.timeout = timeout; }}
IConnectionPool.java
package com.twm.TDBConnectionPool;import java.sql.Connection;import java.sql.SQLException;public interface IConnectionPool { /** * 擷取一個資料庫連接,如果等待超過逾時時間,將返回null * @return 資料庫連接對象 */ public Connection getConnection(); /** * 獲得當前線程的串連庫串連 * @return 資料庫連接對象 */ public Connection getCurrentConnecton(); /** * 釋放當前線程資料庫連接 * @param conn 資料庫連接對象 * @throws SQLException */ public void releaseConn(Connection conn) throws SQLException; /** * 銷毀清空當前串連池 */ public void destroy(); /** * 串連池可用狀態 * @return 串連池是否可用 */ public boolean isActive(); /** * 定時器,檢查串連池 */ public void checkPool(); /** * 擷取線程池活動串連數 * @return 線程池活動串連數 */ public int getActiveNum(); /** * 擷取線程池空閑串連數 * @return 線程池空閑串連數 */ public int getFreeNum();}
ConnectionPool.java
package com.twm.TDBConnectionPool;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.sql.Connection;import java.sql.Driver;import java.sql.DriverManager;import java.sql.SQLException;import java.util.LinkedList;import java.util.List;import java.util.TimerTask;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;import org.apache.log4j.Logger;/** * 類說明 :友元類,包內可見,不提供給客戶程式直接存取。 */class ConnectionPool implements IConnectionPool { private static final Logger log = Logger.getLogger(ConnectionPool.class); private DBPropertyBean propertyBean=null; //串連池可用狀態 private Boolean isActive = true; // 空閑串連池 。由於List讀寫頻繁,使用LinkedList儲存比較合適 private LinkedList<Connection> freeConnections = new LinkedList<Connection>(); // 活動串連池。活動串連數 <= 允許最大串連數(maxConnections) private LinkedList<Connection> activeConnections = new LinkedList<Connection>(); //當前線程獲得的串連 private ThreadLocal<Connection> currentConnection= new ThreadLocal<Connection>(); //構造方法無法返回null,所以取消掉。在下面增加了CreateConnectionPool靜態方法。 private ConnectionPool(){ super(); } public static ConnectionPool CreateConnectionPool(DBPropertyBean propertyBean) { ConnectionPool connpool=new ConnectionPool(); connpool.propertyBean = propertyBean; //載入驅動 //在多節點環境配置下,因為在這裡無法判斷驅動是否已經載入,可能會造成多次重複載入相同驅動。 //因此載入驅動的動作,挪到connectionManager管理類中去實現了。 /*try { Class.forName(connpool.propertyBean.getDriverName()); log.info("載入JDBC驅動"+connpool.propertyBean.getDriverName()+"成功"); } catch (ClassNotFoundException e) { log.info("未找到JDBC驅動" + connpool.propertyBean.getDriverName() + ",請引入相關包"); return null; }*/ //基本點2、始使化時根據配置中的初始串連數建立指定數量的串連 for (int i = 0; i < connpool.propertyBean.getInitConnections(); i++) { try { Connection conn = connpool.NewConnection(); connpool.freeConnections.add(conn); } catch (SQLException | ClassNotFoundException e) { log.error(connpool.propertyBean.getNodeName()+"節點串連池初始化失敗"); return null; } } connpool.isActive = true; return connpool; } /** * 檢測串連是否有效 * @param 資料庫連接對象 * @return Boolean */ private Boolean isValidConnection(Connection conn) throws SQLException{ try { if(conn==null || conn.isClosed()){ return false; } } catch (SQLException e) { throw new SQLException(e); } return true; } /** * 建立一個新的串連 * @return 資料庫連接對象 * @throws ClassNotFoundException * @throws SQLException */ private Connection NewConnection() throws ClassNotFoundException, SQLException { Connection conn = null; try { if (this.propertyBean != null) { //Class.forName(this.propertyBean.getDriverName()); conn = DriverManager.getConnection(this.propertyBean.getUrl(), this.propertyBean.getUsername(), this.propertyBean.getPassword()); } } catch (SQLException e) { throw new SQLException(e); } return conn; } @Override public synchronized Connection getConnection() { Connection conn = null; if (this.getActiveNum() < this.propertyBean.getMaxConnections()) { // 分支1:當前使用的串連沒有達到最大串連數 // 基本點3、在串連池沒有達到最大串連數之前,如果有可用的空閑串連就直接使用空閑串連,如果沒有,就建立新的串連。 if (this.getFreeNum() > 0) { // 分支1.1:如果空閑池中有串連,就從空閑池中直接擷取 log.info("分支1.1:如果空閑池中有串連,就從空閑池中直接擷取"); conn = this.freeConnections.pollFirst(); //串連閑置久了也會逾時,因此空閑池中的有效串連會越來越少,需要另一個進程進行掃描監測,不斷保持一定數量的可用串連。 //在下面定義了checkFreepools的TimerTask類,在checkPool()方法中進行調用。 //基本點5、由於資料庫連接閑置久了會逾時關閉,因此需要串連池採用機制保證每次請求的串連都是有效可用的。 try { if(this.isValidConnection(conn)){ this.activeConnections.add(conn); currentConnection.set(conn); }else{ conn = getConnection();//同步方法是可重新進入鎖 } } catch (SQLException e) { e.printStackTrace(); } } else { // 分支1.2:如果空閑池中無可用串連,就建立新的串連 log.info("分支1.2:如果空閑池中無可用串連,就建立新的串連"); try { conn = this.NewConnection(); this.activeConnections.add(conn); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } } } else { // 分支2:當前已到達最大串連數 // 基本點4、當串連池中的活動串連數達到最大串連數,新的請求進入等待狀態,直到有串連被釋放。 log.info("分支2:當前已到達最大串連數 "); long startTime = System.currentTimeMillis(); //進入等待狀態。等待被notify(),notifyALL()喚醒或者逾時自動蘇醒 try{ this.wait(this.propertyBean.getConninterval()); }catch(InterruptedException e) { log.error("線程等待被打斷"); } //若線程逾時前被喚醒並成功擷取串連,就不會走到return null。 //若線程逾時前沒有擷取串連,則返回null。 //如果timeout設定為0,就無限重連。 if(this.propertyBean.getTimeout()!=0){ if(System.currentTimeMillis() - startTime > this.propertyBean.getTimeout()) return null; } conn = this.getConnection(); } return conn; } @Override public Connection getCurrentConnecton() { Connection conn=currentConnection.get(); try { if(! isValidConnection(conn)){ conn=this.getConnection(); } } catch (SQLException e) { e.printStackTrace(); } return conn; } @Override public synchronized void releaseConn(Connection conn) throws SQLException { log.info(Thread.currentThread().getName()+"關閉串連:activeConnections.remove:"+conn); this.activeConnections.remove(conn); this.currentConnection.remove(); //活動串連池刪除的串連,相應的加到空閑串連池中 try { if(isValidConnection(conn)){ freeConnections.add(conn); }else{ freeConnections.add(this.NewConnection()); } } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } //喚醒getConnection()中等待的線程 this.notifyAll(); } @Override public synchronized void destroy() { for (Connection conn : this.freeConnections) { try { if (this.isValidConnection(conn)) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } for (Connection conn : this.activeConnections) { try { if (this.isValidConnection(conn)) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } this.isActive = false; this.freeConnections.clear(); this.activeConnections.clear(); } @Override public boolean isActive() { return this.isActive; } @Override public void checkPool() { final String nodename=this.propertyBean.getNodeName(); ScheduledExecutorService ses=Executors.newScheduledThreadPool(2); //功能一:開啟一個定時器線程輸出狀態 ses.scheduleAtFixedRate(new TimerTask() { @Override public void run() { System.out.println(nodename +"空閑串連數:"+getFreeNum()); System.out.println(nodename +"活動串連數:"+getActiveNum()); } }, 1, 1, TimeUnit.SECONDS); //功能二:開啟一個定時器線程,監測並維持空閑池中的最小串連數 ses.scheduleAtFixedRate(new checkFreepools(this), 1, 5, TimeUnit.SECONDS); } @Overr