一、應用程式直接獲取資料庫連線的缺點

  使用者每次請求都需要向資料庫獲得連結,而資料庫建立連線通常需要消耗相對較大的資源,建立時間也較長。假設網站一天10萬訪問量,資料庫伺服器就需要建立10萬次連線,極大的浪費資料庫的資源,並且極易造成資料庫伺服器記憶體溢位、拓機。如下圖所示:

  

二、使用資料庫連線池優化程式效能

2.1、資料庫連線池的基本概念

資料庫連線是一種關鍵的有限的昂貴的資源,這一點在多使用者的網頁應用程式中體現的尤為突出.對資料庫連線的管理能顯著影響到整個應用程式的伸縮性和健壯性,影響到程式的效能指標.資料庫連線池正式針對這個問題提出來的.資料庫連線池負責分配,管理和釋放資料庫連線,它允許應用程式重複使用一個現有的資料庫連線,而不是重新建立一個。如下圖所示:

  

       資料庫連線池在初始化時將建立一定數量的資料庫連線放到連線池中, 這些資料庫連線的數量是由最小資料庫連線數來設定的.無論這些資料庫連線是否被使用,連線池都將一直保證至少擁有這麼多的連線數量.連線池的最大資料庫連線數量限定了這個連線池能佔有的最大連線數,當應用程式向連線池請求的連線數超過最大連線數量時,這些請求將被加入到等待佇列中.

       資料庫連線池的最小連線數和最大連線數的設定要考慮到以下幾個因素:

  1. 最小連線數:是連線池一直保持的資料庫連線,所以如果應用程式對資料庫連線的使用量不大,將會有大量的資料庫連線資源被浪費.
  2. 最大連線數:是連線池能申請的最大連線數,如果資料庫連線請求超過次數,後面的資料庫連線請求將被加入到等待佇列中,這會影響以後的資料庫操作
  3. 如果最小連線數與最大連線數相差很大:那麼最先連線請求將會獲利,之後超過最小連線數量的連線請求等價於建立一個新的資料庫連線.不過,這些大於最小連線數的資料庫連線在使用完不會馬上被釋放,他將被放到連線池中等待重複使用或是空間超時後被釋放.

2.2、編寫資料庫連線池

  編寫連線池需實現java.sql.DataSource介面。DataSource介面中定義了兩個過載的getConnection方法:

  • Connection getConnection()
  • Connection getConnection(String username, String password)

  實現DataSource介面,並實現連線池功能的步驟:

  1. 在DataSource建構函式中批量建立與資料庫的連線,並把建立的連線加入LinkedList物件中。
  2. 實現getConnection方法,讓getConnection方法每次呼叫時,從LinkedList中取一個Connection返回給使用者。
  3. 當用戶使用完Connection,呼叫Connection.close()方法時,Collection物件應保證將自己返回到LinkedList中,而不要把conn還給資料庫。Collection保證將自己返回到LinkedList中是此處程式設計的難點

 資料庫連線池核心程式碼

  使用動態代理技術構建連線池中的connection

複製程式碼
 1 proxyConn = (Connection) Proxy.newProxyInstance(this.getClass()
 2             .getClassLoader(), conn.getClass().getInterfaces(),
 3             new InvocationHandler() {
 4         //此處為內部類,當close方法被呼叫時將conn還回池中,其它方法直接執行
 5             public Object invoke(Object proxy, Method method,
 6                       Object[] args) throws Throwable {
 7                 if (method.getName().equals("close")) {
 8                     pool.addLast(conn);
 9                     return null;
10             }
11             return method.invoke(conn, args);
12         }
13     });
複製程式碼

資料庫連線池編寫範例:

複製程式碼
  1 package me.gacl.demo;
  2 
  3 import java.io.InputStream;
  4 import java.io.PrintWriter;
  5 import java.lang.reflect.InvocationHandler;
  6 import java.lang.reflect.Method;
  7 import java.lang.reflect.Proxy;
  8 import java.sql.Connection;
  9 import java.sql.DriverManager;
 10 import java.sql.SQLException;
 11 import java.util.LinkedList;
 12 import java.util.Properties;
 13 import javax.sql.DataSource;
 14 
 15 /**
 16 * @ClassName: JdbcPool
 17 * @Description:編寫資料庫連線池
 18 * @author: 孤傲蒼狼
 19 * @date: 2014-9-30 下午11:07:23
 20 *
 21 */ 
 22 public class JdbcPool implements DataSource{
 23 
 24     /**
 25     * @Field: listConnections
 26     *         使用LinkedList集合來存放資料庫連結,
 27     *        由於要頻繁讀寫List集合,所以這裡使用LinkedList儲存資料庫連線比較合適
 28     */ 
 29     private static LinkedList<Connection> listConnections = new LinkedList<Connection>();
 30     
 31     static{
 32         //在靜態程式碼塊中載入db.properties資料庫配置檔案
 33         InputStream in = JdbcPool.class.getClassLoader().getResourceAsStream("db.properties");
 34         Properties prop = new Properties();
 35         try {
 36             prop.load(in);
 37             String driver = prop.getProperty("driver");
 38             String url = prop.getProperty("url");
 39             String username = prop.getProperty("username");
 40             String password = prop.getProperty("password");
 41             //資料庫連線池的初始化連線數大小
 42             int jdbcPoolInitSize =Integer.parseInt(prop.getProperty("jdbcPoolInitSize"));
 43             //載入資料庫驅動
 44             Class.forName(driver);
 45             for (int i = 0; i < jdbcPoolInitSize; i++) {
 46                 Connection conn = DriverManager.getConnection(url, username, password);
 47                 System.out.println("獲取到了連結" + conn);
 48                 //將獲取到的資料庫連線加入到listConnections集合中,listConnections集合此時就是一個存放了資料庫連線的連線池
 49                 listConnections.add(conn);
 50             }
 51             
 52         } catch (Exception e) {
 53             throw new ExceptionInInitializerError(e);
 54         }
 55     }
 56     
 57     @Override
 58     public PrintWriter getLogWriter() throws SQLException {
 59         // TODO Auto-generated method stub
 60         return null;
 61     }
 62 
 63     @Override
 64     public void setLogWriter(PrintWriter out) throws SQLException {
 65         // TODO Auto-generated method stub
 66         
 67     }
 68 
 69     @Override
 70     public void setLoginTimeout(int seconds) throws SQLException {
 71         // TODO Auto-generated method stub
 72         
 73     }
 74 
 75     @Override
 76     public int getLoginTimeout() throws SQLException {
 77         // TODO Auto-generated method stub
 78         return 0;
 79     }
 80 
 81     @Override
 82     public <T> T unwrap(Class<T> iface) throws SQLException {
 83         // TODO Auto-generated method stub
 84         return null;
 85     }
 86 
 87     @Override
 88     public boolean isWrapperFor(Class<?> iface) throws SQLException {
 89         // TODO Auto-generated method stub
 90         return false;
 91     }
 92 
 93     /* 獲取資料庫連線
 94      * @see javax.sql.DataSource#getConnection()
 95      */
 96     @Override
 97     public Connection getConnection() throws SQLException {
 98         //如果資料庫連線池中的連線物件的個數大於0
 99         if (listConnections.size()>0) {
100             //從listConnections集合中獲取一個數據庫連線
101             final Connection conn = listConnections.removeFirst();
102             System.out.println("listConnections資料庫連線池大小是" + listConnections.size());
103             //返回Connection物件的代理物件
104             return (Connection) Proxy.newProxyInstance(JdbcPool.class.getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler(){
105                 @Override
106                 public Object invoke(Object proxy, Method method, Object[] args)
107                         throws Throwable {
108                     if(!method.getName().equals("close")){
109                         return method.invoke(conn, args);
110                     }else{
111                         //如果呼叫的是Connection物件的close方法,就把conn還給資料庫連線池
112                         listConnections.add(conn);
113                         System.out.println(conn + "被還給listConnections資料庫連線池了!!");
114                         System.out.println("listConnections資料庫連線池大小為" + listConnections.size());
115                         return null;
116                     }
117                 }
118             });
119         }else {
120             throw new RuntimeException("對不起,資料庫忙");
121         }
122     }
123 
124     @Override
125     public Connection getConnection(String username, String password)
126             throws SQLException {
127         return null;
128     }
129 }
複製程式碼

 db.properties配置檔案如下:

1 driver=com.mysql.jdbc.Driver
2 url=jdbc:mysql://localhost:3306/jdbcStudy
3 username=root
4 password=XDP
5 
6 jdbcPoolInitSize=10

寫一個JdbcUtil測試資料庫連線池

複製程式碼
 1 package me.gacl.utils;
 2 
 3 import java.sql.Connection;
 4 import java.sql.ResultSet;
 5 import java.sql.SQLException;
 6 import java.sql.Statement;
 7 import me.gacl.demo.JdbcPool;
 8 
 9 public class JdbcUtil {
10     
11     /**
12     * @Field: pool
13     *          資料庫連線池
14     */ 
15     private static JdbcPool pool = new JdbcPool();
16     
17     /**
18     * @Method: getConnection
19     * @Description: 從資料庫連線池中獲取資料庫連線物件
20     * @Anthor:孤傲蒼狼
21     * @return Connection資料庫連線物件
22     * @throws SQLException
23     */ 
24     public static Connection getConnection() throws SQLException{
25         return pool.getConnection();
26     }
27     
28     /**
29     * @Method: release
30     * @Description: 釋放資源,
31     * 釋放的資源包括Connection資料庫連線物件,負責執行SQL命令的Statement物件,儲存查詢結果的ResultSet物件
32     * @Anthor:孤傲蒼狼
33     *
34     * @param conn
35     * @param st
36     * @param rs
37     */ 
38     public static void release(Connection conn,Statement st,ResultSet rs){
39         if(rs!=null){
40             try{
41                 //關閉儲存查詢結果的ResultSet物件
42                 rs.close();
43             }catch (Exception e) {
44                 e.printStackTrace();
45             }
46             rs = null;
47         }
48         if(st!=null){
49             try{
50                 //關閉負責執行SQL命令的Statement物件
51                 st.close();
52             }catch (Exception e) {
53                 e.printStackTrace();
54             }
55         }
56         
57         if(conn!=null){
58             try{
59                 //關閉Connection資料庫連線物件
60                 conn.close();
61             }catch (Exception e) {
62                 e.printStackTrace();
63             }
64         }
65     }
66 }
複製程式碼

 三、開源資料庫連線池

  現在很多WEB伺服器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的實現,即連線池的實現。通常我們把DataSource的實現,按其英文含義稱之為資料來源,資料來源中都包含了資料庫連線池的實現。
  也有一些開源組織提供了資料來源的獨立實現:

  • DBCP 資料庫連線池
  • C3P0 資料庫連線池

  在使用了資料庫連線池之後,在專案的實際開發中就不需要編寫連線資料庫的程式碼了,直接從資料來源獲得資料庫的連線。

3.1、DBCP資料來源

  DBCP 是 Apache 軟體基金組織下的開源連線池實現,要使用DBCP資料來源,需要應用程式應在系統中增加如下兩個 jar 檔案:

  • Commons-dbcp.jar:連線池的實現
  • Commons-pool.jar:連線池實現的依賴庫

  Tomcat 的連線池正是採用該連線池來實現的。該資料庫連線池既可以與應用伺服器整合使用,也可由應用程式獨立使用。

3.2、在應用程式中加入dbcp連線池

  1.匯入相關jar包
        commons-dbcp-1.2.2.jar、commons-pool.jar
  2、在類目錄下加入dbcp的配置檔案:dbcpconfig.properties

    dbcpconfig.properties的配置資訊如下:

複製程式碼
 1 #連線設定
 2 driverClassName=com.mysql.jdbc.Driver
 3 url=jdbc:mysql://localhost:3306/jdbcstudy
 4 username=root
 5 password=XDP
 6 
 7 #<!-- 初始化連線 -->
 8 initialSize=10
 9 
10 #最大連線數量
11 maxActive=50
12 
13 #<!-- 最大空閒連線 -->
14 maxIdle=20
15 
16 #<!-- 最小空閒連線 -->
17 minIdle=5
18 
19 #<!-- 超時等待時間以毫秒為單位 6000毫秒/1000等於60秒 -->
20 maxWait=60000
21 
22 
23 #JDBC驅動建立連線時附帶的連線屬性屬性的格式必須為這樣:[屬性名=property;] 
24 #注意:"user" 與 "password" 兩個屬性會被明確地傳遞,因此這裡不需要包含他們。
25 connectionProperties=useUnicode=true;characterEncoding=UTF8
26 
27 #指定由連線池所建立的連線的自動提交(auto-commit)狀態。
28 defaultAutoCommit=true
29 
30 #driver default 指定由連線池所建立的連線的只讀(read-only)狀態。
31 #如果沒有設定該值,則“setReadOnly”方法將不被呼叫。(某些驅動並不支援只讀模式,如:Informix)
32 defaultReadOnly=
33 
34 #driver default 指定由連線池所建立的連線的事務級別(TransactionIsolation)。
35 #可用值為下列之一:(詳情可見javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
36 defaultTransactionIsolation=READ_UNCOMMITTED
複製程式碼

  如下圖所示:

  

  3、在獲取資料庫連線的工具類(如jdbcUtils)的靜態程式碼塊中建立池

複製程式碼
 1 package me.gacl.util;
 2 
 3 import java.io.InputStream;
 4 import java.sql.Connection;
 5 import java.sql.ResultSet;
 6 import java.sql.SQLException;
 7 import java.sql.Statement;
 8 import java.util.Properties;
 9 import javax.sql.DataSource;
10 import org.apache.commons.dbcp.BasicDataSourceFactory;
11 
12 /**
13 * @ClassName: JdbcUtils_DBCP
14 * @Description: 資料庫連線工具類
15 * @author: 孤傲蒼狼
16 * @date: 2014-10-4 下午6:04:36
17 *
18 */ 
19 public class JdbcUtils_DBCP {
20     /**
21      * 在java中,編寫資料庫連線池需實現java.sql.DataSource介面,每一種資料庫連線池都是DataSource介面的實現
22      * DBCP連線池就是java.sql.DataSource介面的一個具體實現
23      */
相關文章