JavaWeb學習筆記(十七)—— 數據庫鏈接池

1、數據庫鏈接池概述

1.1 爲何使用數據庫鏈接池

  若是用戶每次請求都向數據庫得到鏈接,而數據庫建立鏈接一般須要消耗相對較大的資源,建立時間也較長。假設網站一天10萬訪問量,數據庫服務器就須要建立10萬次鏈接,極大的浪費數據庫的資源,而且極易形成數據庫服務器內存溢出、拓機。以下圖所示:html

  

1.2 數據庫鏈接池是什麼

  數據庫鏈接是一種關鍵的有限的昂貴的資源,這一點在多用戶的網頁應用程序中體現的尤其突出.對數據庫鏈接的管理能顯著影響到整個應用程序的伸縮性和健壯性,影響到程序的性能指標。數據庫鏈接池正式針對這個問題提出來的。java

  數據庫鏈接池負責分配,管理和釋放數據庫鏈接,它容許應用程序重複使用一個現有的數據庫鏈接,而不是從新創建一個。以下圖所示:mysql

  

  有了池,咱們就不用本身來建立Connection,而是經過池來獲取Connection對象。當使用完Connection後,調用Connection的close()方法也不會真的關閉Connection,而是把Connection「歸還」給池。池就能夠再利用這個Connection對象了。sql

       數據庫鏈接池在初始化時將建立必定數量的數據庫鏈接放到鏈接池中, 這些數據庫鏈接的數量是由最小數據庫鏈接數來設定的.不管這些數據庫鏈接是否被使用,鏈接池都將一直保證至少擁有這麼多的鏈接數量.鏈接池的最大數據庫鏈接數量限定了這個鏈接池能佔有的最大鏈接數,當應用程序向鏈接池請求的鏈接數超過最大鏈接數量時,這些請求將被加入到等待隊列中.數據庫

       數據庫鏈接池的最小鏈接數和最大鏈接數的設置要考慮到如下幾個因素:apache

  1. 最小鏈接數:是鏈接池一直保持的數據庫鏈接,因此若是應用程序對數據庫鏈接的使用量不大,將會有大量的數據庫鏈接資源被浪費.
  2. 最大鏈接數:是鏈接池能申請的最大鏈接數,若是數據庫鏈接請求超過次數,後面的數據庫鏈接請求將被加入到等待隊列中,這會影響之後的數據庫操做
  3. 若是最小鏈接數與最大鏈接數相差很大:那麼最早鏈接請求將會獲利,以後超過最小鏈接數量的鏈接請求等價於創建一個新的數據庫鏈接.不過,這些大於最小鏈接數的數據庫鏈接在使用完不會立刻被釋放,他將被放到鏈接池中等待重複使用或是空間超時後被釋放.

2、自定義數據庫鏈接池

2.1 初始版本

【實現思路】  設計模式

  1. 編寫鏈接池需實現java.sql.DataSource接口,並重寫接口中的getConnection()方法
  2. 提供一個集合,用於存放鏈接,由於移除/添加操做過多,因此選擇LinkedList
  3. 在靜態代碼塊中爲鏈接池初始化5個鏈接
  4. 程序若是須要鏈接,調用實現類的getConnection(),從鏈接池(容器List)得到鏈接。爲保證當前鏈接只能提供一個線程使用,須要將鏈接先從鏈接池中移除。
  5. 使用完鏈接,要釋放資源時,不執行close()方法,而是將鏈接添加到鏈接池中

【代碼編寫】緩存

public class MyDataSource implements DataSource { //1.建立1個容器用於存儲Connection對象
    private static LinkedList<Connection> pool = new LinkedList<Connection>(); //2.建立5個鏈接放到容器中去
    static{ for (int i = 0; i < 5; i++) { Connection connection = JdbcUtils.getConnection(); pool.add(connection); } } /** * 重寫獲取鏈接的方法 */ @Override public Connection getConnection() throws SQLException { Connection connection = null; //3.使用前先判斷
        if (pool.size() == 0) { //4.池子裏面沒有,咱們再建立一些
            for (int i = 0; i < 5; i++) { connection = JdbcUtils.getConnection(); pool.add(connection); } } //5.從池子裏面獲取一個鏈接對象Connection
        connection = pool.remove(0); return connection; } /** * 歸還鏈接對象到鏈接池中去 */
    public void backConnection(Connection connection) { pool.add(connection); } ... }

【測試】tomcat

@Test public void testAddUser() { Connection conn = null; PreparedStatement pstmt = null; // 1.建立自定義鏈接池對象
    MyDataSource dataSource = new MyDataSource(); try { // 2.從池子中獲取鏈接
        conn = dataSource.getConnection(); String sql = "insert into t_user values(?,?)"; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 3); pstmt.setString(2, "張三"); int num = pstmt.executeUpdate(); if (num > 0) { System.out.println("添加成功"); } else { System.out.println("添加失敗"); } } catch (Exception e) { e.printStackTrace(); }finally { dataSource.backConnection(conn); } }

2.2 方法加強——裝飾者模式

【需求】服務器

  上述自定義鏈接池中存在嚴重問題,用戶調用getConnection()得到鏈接後,必須使用backConnection()方法進行鏈接的歸還,若是用戶調用conn.close()將鏈接真正的釋放,鏈接池中將出現無鏈接可用。

  此時咱們但願,即便用戶調用了close()方法,鏈接仍歸還給鏈接池。close()方法原有功能是釋放資源,咱們指望的功能是將鏈接歸還給鏈接池。說明close()方法沒有咱們但願的功能,咱們將對close()方法進行加強,從而實現將鏈接歸還給鏈接池的功能。

【方法加強總結】

1.繼承:

  • 子類繼承父類,將父類的方法進行復寫,從而進行加強。
  • 使用前提:必須有父類,且存在繼承關係。

2.裝飾者模式:

  • 此設計模式專門用於加強方法。
  • 使用前提:必須有接口

3.動態代理:

  • 在運行時動態的建立代理類,完成加強操做。與裝飾者類似
  • 使用前提:必須有接口
  • 難點:須要反射技術

【裝飾者設計模式】

設計模式:專門爲解決某一類問題,而編寫的固定格式的代碼。

固定結構:接口A,已知實現類C,須要裝飾者建立代理類B

實現步驟:

  1. 建立類B,並實現接口A
  2. 提供類B的構造方法,參數類型爲A,用於接收A接口的其餘實現類(C)
  3. 給類B添加類型爲A的成員變量,用於存放A接口的其餘實現類
  4. 加強須要的方法
  5. 實現不須要加強的方法,方法體重調用成員變量存放的其餘實現類對應的方法
A a = ...C; B b = new B(a); class B implements A{ private A a; public B(A,a){ this.a = a; } // 加強的方法
    public void close(){ } // 不須要加強的方法
    public void commit(){ this.a.commit(); } }

2.3 使用裝飾者模式加強鏈接池

 【裝飾類】

//1.實現同一個接口Connection
public class MyConnection implements Connection { //3.定義一個變量
    private Connection conn; private LinkedList<Connection> pool; //2.編寫一個構造方法(參數使用了面向對象的多態特性)
    public MyConnection(Connection conn,LinkedList<Connection> pool) { this.conn = conn; this.pool = pool; } //4.書寫須要加強的方法
 @Override public void close() throws SQLException { pool.add(conn); } /** * 5.實現不須要加強的方法 * 注意:此方法必須覆蓋!不然會出現空指針異常!!! */ @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return conn.prepareStatement(sql); } @Override public void commit() throws SQLException { } 
  ...
}

【使用裝飾類】

public class MyDataSource1 implements DataSource { //1.建立1個容器用於存儲Connection對象
    private static LinkedList<Connection> pool = new LinkedList<Connection>(); //2.建立5個鏈接放到容器中去
    static{ for (int i = 0; i < 5; i++) { Connection conn = JdbcUtils.getConnection(); //放入池子中connection對象已經通過改造了
            MyConnection myconn = new MyConnection(conn, pool); pool.add(myconn); } } /** * 重寫獲取鏈接的方法 */ @Override public Connection getConnection() throws SQLException { Connection conn = null; //3.使用前先判斷
        if (pool.size() == 0) { //4.池子裏面沒有,咱們再建立一些
            for (int i = 0; i < 5; i++) { conn = JdbcUtils.getConnection(); //放入池子中connection對象已經通過改造了                 MyConnection myconn = new MyConnection(conn, pool); pool.add(myconn); } } //5.從池子裏面獲取一個鏈接對象Connection
        conn = pool.remove(0); return conn; } /** * 歸還鏈接對象到鏈接池中去 */
    public void backConnection(Connection connection) { pool.add(connection); } ... }

【測試】

/** * 使用改造過的connection */ @Test public void testAddUser1() { Connection conn = null; PreparedStatement pstmt = null; // 1.建立自定義鏈接池對象
    DataSource dataSource = new MyDataSource1(); try { // 2.從池子中獲取鏈接
        conn = dataSource.getConnection(); String sql = "insert into t_user values(?,?)"; //3.必須在自定義的connection類中重寫prepareStatement(sql)方法
        pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 6); pstmt.setString(2, "王五"); int num = pstmt.executeUpdate(); if (num > 0) { System.out.println("添加成功"); } else { System.out.println("添加失敗"); } } catch (Exception e) { throw new RuntimeException(e); } finally { //這裏的connection是已經通過加強的,加強後的conn.close()就是將鏈接歸還給鏈接池
        JdbcUtils.release(conn, pstmt, null); } }

3、DBCP鏈接池

  DBCP 是 Apache 軟件基金組織下的開源鏈接池實現,在企業開發中也比較常見,它是tomcat內置的鏈接池

3.1 導包

  • Commons-dbcp.jar:鏈接池的實現
  • Commons-pool.jar:鏈接池實現的依賴庫

3.2 提供配置文件

  • 配置文件名稱:*.properties
  • 配置文件位置:任意,建議src(classpath/類路徑)
#基本配置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mydb1
username=root password=123 #初始化池大小,即一開始池中就會有10個鏈接對象 默認值爲0 initialSize=0 #最大鏈接數,若是設置maxActive=50時,池中最多能夠有50個鏈接,固然這50個鏈接中包含被使用的和沒被使用的(空閒) #你是一個包工頭,你一共有50個工人,但這50個工人有的當前正在工做,有的正在空閒 #默認值爲8,若是設置爲非正數,表示沒有限制!即無限大 maxActive=8 #最大空閒鏈接 #當設置maxIdle=30時,你是包工頭,你容許最多有20個工人空閒,若是如今有30個空閒工人,那麼要開除10個 #默認值爲8,若是設置爲負數,表示沒有限制!即無限大 maxIdle=8 #最小空閒鏈接 #若是設置minIdel=5時,若是你的工人只有3個空閒,那麼你須要再去招2個回來,保證有5個空閒工人 #默認值爲0 minIdle=0 #最大等待時間 #當設置maxWait=5000時,如今你的工做都出去工做了,又來了一個工做,須要一個工人。 #這時就要等待有工人回來,若是等待5000毫秒還沒回來,那就拋出異常 #沒有工人的緣由:最多工人數爲50,已經有50個工人了,不能再招了,但50人都出去工做了。 #默認值爲-1,表示無限期等待,不會拋出異常。 maxWait=-1 #鏈接屬性 #就是原來放在url後面的參數,可使用connectionProperties來指定 #若是已經在url後面指定了,那麼就不用在這裏指定了。 #useServerPrepStmts=true,MySQL開啓預編譯功能 #cachePrepStmts=true,MySQL開啓緩存PreparedStatement功能, #prepStmtCacheSize=50,緩存PreparedStatement的上限 #prepStmtCacheSqlLimit=300,當SQL模板長度大於300時,就再也不緩存它 connectionProperties=useUnicode=true;characterEncoding=UTF8;useServerPrepStmts=true;cachePrepStmts=true;prepStmtCacheSize=50;prepStmtCacheSqlLimit=300 #鏈接的默認提交方式 #默認值爲true defaultAutoCommit=true #鏈接是否爲只讀鏈接 #Connection有一對方法:setReadOnly(boolean)和isReadOnly() #若是是隻讀鏈接,那麼你只能用這個鏈接來作查詢 #指定鏈接爲只讀是爲了優化!這個優化與併發事務相關! #若是兩個併發事務,對同一行記錄作增、刪、改操做,是否是必定要隔離它們啊? #若是兩個併發事務,對同一行記錄只作查詢操做,那麼是否是就不用隔離它們了? #若是沒有指定這個屬性值,那麼是否爲只讀鏈接,這就由驅動本身來決定了。即Connection的實現類本身來決定! defaultReadOnly=false #指定事務的事務隔離級別 #可選值:NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE #若是沒有指定,那麼由驅動中的Connection實現類本身來決定 defaultTransactionIsolation=REPEATABLE_READ

3.3 編寫工具類

public class DBCPUtils { /** * 在java中,編寫數據庫鏈接池需實現java.sql.DataSource接口,每一種數據庫鏈接池都是DataSource接口的實現 * DBCP鏈接池就是java.sql.DataSource接口的一個具體實現 */
    private static DataSource dataSource = null; //在靜態代碼塊中建立數據庫鏈接池
    static{ try { //加載dbcp.properties配置文件
            InputStream in = DBCPUtils.class.getClassLoader().getResourceAsStream("dbcp.properties"); Properties prop = new Properties(); prop.load(in); //建立數據源
            dataSource = BasicDataSourceFactory.createDataSource(prop); } catch (Exception e) { e.printStackTrace(); } } /** * 從數據源中獲取數據庫鏈接 */
    public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } /** * 釋放資源 * 釋放的資源包括Connection數據庫鏈接對象,負責執行SQL命令的Statement對象,存儲查詢結果的ResultSet對象 */
    public static void release(Connection conn, Statement st, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (st != null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

3.4 測試DBCP數據源

public class DBCPTest { @Test public void addUser() { Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { // 獲取數據庫鏈接
            conn = DBCPUtils.getConnection(); String sql = "insert into t_user values(?,?)"; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 8); pstmt.setString(2, "劉備"); int num = pstmt.executeUpdate(); if (num > 0) { System.out.println("添加成功"); } } catch (Exception e) { e.printStackTrace(); } } }

4、C3P0鏈接池

  C3P0是一個開源的JDBC鏈接池,它實現了數據源和JNDI綁定,支持JDBC3規範和JDBC2的標準擴展。目前使用它的開源項目有Hibernate,Spring等。C3P0數據源在項目開發中使用得比較多。

  c3p0與dbcp區別:
  • dbcp沒有自動回收空閒鏈接的功能
  • c3p0有自動回收空閒鏈接功能

4.1 導包

  • c3p0-0.9.2-pre1.jar
  • mchange-commons-0.2.jar

  若是操做的是Oracle數據庫,那麼還須要導入c3p0-oracle-thin-extras-0.9.2-pre1.jar

4.2 添加配置文件

  • 配置文件名稱:c3p0-config.xml(必須是這個)
  • 配置文件位置:src(必須放在類路徑下)
  • 配置文件內容:
    <?xml version="1.0" encoding="UTF-8"?>
    <c3p0-config>
        <!--這是默認配置信息-->
        <default-config>
            <!--鏈接四大參數配置-->
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
            <property name="driverClass">com.mysql.jdbc.Driver</property>
            <property name="user">root</property>
            <property name="password">root</property>
            <!--池參數配置-->
            <!--若是池中數據鏈接不夠時一次增加多少個-->
            <property name="acquireIncrement">3</property>
            <!--初始化鏈接數-->
            <property name="initialPoolSize">10</property>
            <!--最小鏈接數-->
            <property name="minPoolSize">2</property>
            <!--最大鏈接數-->
            <property name="maxPoolSize">10</property>
        </default-config>
        <!--專門爲Oracle提供的配置信息-->
        <named-config name="oracle-config">
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>
            <property name="driverClass">com.mysql.jdbc.Driver</property>
            <property name="user">root</property>
            <property name="password">123</property>
            <property name="acquireIncrement">3</property>
            <property name="initialPoolSize">10</property>
            <property name="minPoolSize">2</property>
            <property name="maxPoolSize">10</property>
        </named-config>
    </c3p0-config>

4.3 編寫工具類 

public class C3P0Utils { private static ComboPooledDataSource dataSource = null; //在靜態代碼塊中建立數據庫鏈接池
    static{ try { // 經過代碼建立C3P0鏈接池
            /*dataSource = new ComboPooledDataSource(); dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setInitialPoolSize(10); dataSource.setMinPoolSize(5); dataSource.setMaxPoolSize(20);*/

            //經過讀取C3P0的xml配置文件建立數據源,C3P0的xml配置文件c3p0-config.xml必須放在src目錄下 //使用C3P0的默認配置來建立數據源
            dataSource = new ComboPooledDataSource(); //使用名爲oracle-config的配置來建立數據源 // dataSource = new ComboPooledDataSource("oracle-config");
        } catch (Exception e) { e.printStackTrace(); } } /** * 從數據源中獲取數據庫鏈接 */
    public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } /** * 釋放資源 * 釋放的資源包括Connection數據庫鏈接對象,負責執行SQL命令的Statement對象,存儲查詢結果的ResultSet對象 */
    public static void release(Connection conn, Statement st, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (st != null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

4.4 測試C3P0數據源

@Test public void addUser() { Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { // 獲取數據庫鏈接
        conn = C3P0Utils.getConnection(); String sql = "insert into t_user values(?,?)"; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 9); pstmt.setString(2, "關羽"); int num = pstmt.executeUpdate(); if (num > 0) { System.out.println("添加成功"); } } catch (Exception e) { e.printStackTrace(); } }

5、Tomcat配置鏈接池

  在實際開發中,咱們有時候還會使用服務器提供給咱們的數據庫鏈接池,好比咱們但願Tomcat服務器在啓動的時候能夠幫咱們建立一個數據庫鏈接池,那麼咱們在應用程序中就不須要手動去建立數據庫鏈接池,直接使用Tomcat服務器建立好的數據庫鏈接池便可。要想讓Tomcat服務器在啓動的時候幫咱們建立一個數據庫鏈接池,那麼須要簡單配置一下Tomcat服務器。

5.1 JNDI技術簡介

  JNDI(Java Naming and Directory Interface),Java命名和目錄接口,它對應於J2SE中的javax.naming包,
  這套API的主要做用在於:它能夠把Java對象放在一個容器中(JNDI容器),併爲容器中的java對象取一個名稱,之後程序想得到Java對象,只需 經過名稱檢索便可。其核心API爲Context,它表明JNDI容器,其lookup方法爲檢索容器中對應名稱的對象。

  Tomcat服務器建立的數據源是以JNDI資源的形式發佈的,因此說在Tomat服務器中配置一個數據源實際上就是在配置一個JNDI資源,經過查看Tomcat文檔,咱們知道使用以下的方式配置tomcat服務器的數據源:

<Context>
  <Resource name="jdbc/datasource" auth="Container" type="javax.sql.DataSource" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/test" maxActive="8" maxIdle="4"/>
</Context>

  服務器建立好數據源以後,咱們的應用程序又該怎麼樣獲得這個數據源呢,Tomcat服務器建立好數據源以後是以JNDI的形式綁定到一個JNDI容器中的,咱們能夠把JNDI想象成一個大大的容器,咱們能夠往這個容器中存放一些對象,一些資源,JNDI容器中存放的對象和資源都會有一個獨一無二的名稱,應用程序想從JNDI容器中獲取資源時,只須要告訴JNDI容器要獲取的資源的名稱,JNDI根據名稱去找到對應的資源後返回給應用程序。咱們平時作javaEE開發時,服務器會爲咱們的應用程序建立不少資源,好比request對象,response對象,服務器建立的這些資源有兩種方式提供給咱們的應用程序使用:第一種是經過方法參數的形式傳遞進來,好比咱們在Servlet中寫的doPost和doGet方法中使用到的request對象和response對象就是服務器以參數的形式傳遞給咱們的。第二種就是JNDI的方式,服務器把建立好的資源綁定到JNDI容器中去,應用程序想要使用資源時,就直接從JNDI容器中獲取相應的資源便可。

  對於上面的name="jdbc/datasource"數據源資源,在應用程序中能夠用以下的代碼去獲取 

Context initCtx = new InitialContext(); Context envCtx = (Context) initCtx.lookup("java:comp/env"); dataSource = (DataSource)envCtx.lookup("jdbc/datasource");

  此種配置下,數據庫的驅動jar文件需放置在tomcat的lib下

  

5.2 配置JNDI資源

  • 在Web項目的WebRoot目錄下的META-INF目錄建立一個context.xml文件
  • 在context.xml文件配置tomcat服務器的數據源 
<Context>
    <!--name:指定資源的名稱 factory:用來建立資源的工廠,這個值基本上是固定的,不用修改 type:資源的類型 其餘的東西都是資源的參數 -->
    <Resource name="jdbc/datasource" factory="org.apache.naming.factory.BeanFactory" type="javax.sql.DataSource" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/test" maxActive="8" maxIdle="4"/>
</Context>

5.3 獲取JNDI的資源

/** * 獲取JNDI的資源 * */
public class AServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { /* * 1. 建立JNDI的上下文對象 */
        try { Context cxt = new InitialContext(); // 2查詢出入口 // Context envContext = (Context)cxt.lookup("java:comp/env"); // 3. 再進行二次查詢,找到咱們的資源 // 使用的是名稱與<Resource>元素的name對應 // DataSource dataSource = (DataSource)envContext.lookup("jdbc/dataSource");
 DataSource dataSource = (DataSource)cxt.lookup("java:comp/env/jdbc/dataSource"); Connection con = dataSource.getConnection(); System.out.println(con); con.close(); } catch (Exception e) { throw new RuntimeException(e); } } }

 

 

 

參考:https://www.cnblogs.com/xdp-gacl/p/4002804.html

相關文章
相關標籤/搜索