JDBC之 鏈接池

JDBC之 鏈接池

 有這樣的一種現象:html

java代碼操做數據庫,須要數據庫鏈接對象,一個用戶至少要用到一個鏈接。如今假設有成千上百萬個用戶,就要建立十分巨大數量的鏈接對象,這會使數據庫承受極大的壓力,爲了解決這種現象,一種技術出現了,這就是數據庫鏈接池。java

 

什麼是數據庫鏈接池(原理)

所謂數據庫鏈接池,能夠看做 :在用戶和數據庫之間建立一個」池」,這個池中有若干個鏈接對象,當用戶想要鏈接數據庫,就要先從鏈接池中獲取鏈接對象,而後操做數據庫。一旦鏈接池中的鏈接對象被拿光了,下一個想要操做數據庫的用戶必須等待,等待其餘用戶釋放鏈接對象,把它放回鏈接池中,這時候等待的用戶才能獲取鏈接對象,從而操做數據庫。mysql

 

數據庫鏈接池的屬性

鏈接對象初始的數量:initSize,一開始就建立若干個,當不夠時再添加sql

鏈接對象最大數量:maxSize,添加到最大值則不會再添加數據庫

 

下面咱們用代碼下一個本身的鏈接池吧~tomcat

 

實現本身的鏈接池

看下面代碼和註釋吧~服務器

 

public class MyPool {
    //設置註冊屬性    
    private String url = "jdbc:mysql://localhost:3306/vmaxtam";
    private String user = "root";
    private String password = "root";
    private static String driverClass="com.mysql.jdbc.Driver";

    //設置鏈接池屬性
    private int initSize = 5;
    private int maxSize = 8;
    
    //用LinkedList對象來保存connection對象
    private LinkedList<Connection> connList = new LinkedList<Connection>();
    //聲明一個臨時變量來計算鏈接對象的數量
    private int currentsize = 0;
    
    //聲明MyPool對象時自動註冊驅動
    static{
        try {
            Class.forName(driverClass);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    //獲取鏈接的方法
    private Connection getConnection()
    {
        Connection conn=null;
        try {
            conn = DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return conn;
    }
    
    //構造方法,初始化鏈接池,並往裏面添加鏈接對象
    public MyPool() {
        for(int i = 0; i < initSize; i++)
        {
            Connection connection = this.getConnection();
            connList.add(connection);
            currentsize++;
        }
    }
    
    //獲取鏈接池中的一個鏈接對象
    public Connection getConnFromPool()
    {
        //當鏈接池還沒空
        if(connList.size()>0){
            Connection connection = connList.getFirst();
            connList.removeFirst();
            return connection;
        
        }else if(connList.size()==0&&currentsize<8){
            //鏈接池被拿空,且鏈接數沒有達到上限,建立新的鏈接
            currentsize++;
            connList.addLast(this.getConnection());
            
            Connection connection = connList.getFirst();
            connList.removeFirst();
            return connection;        
        }
        
        throw new RuntimeException("鏈接數達到上限,請等待");
    }
    
    //把用完的鏈接放回鏈接池
    public void releaseConnection(Connection connection)
    {
        connList.addLast(connection);
    }
    
}

 

有了鏈接池後,咱們寫一個測試來調用一下它吧~併發

 

    @Test
    public void test1()
    {        
        //得到鏈接池
        MyPool mypool = new MyPool();
        
        /*從鏈接池中嘗試獲取9個鏈接
        for(int i = 0 ; i<9; i++){
            Connection conn = mypool.getConnFromPool();
            System.out.println(conn.toString());
        }*/
        
        //獲取第五個鏈接後,釋放一下,而後再獲取
        for(int i = 0 ; i<9; i++){
            Connection conn = mypool.getConnFromPool();
            if(i==5){
                mypool.releaseConnection(conn);
            }
            System.out.println(conn.toString());
        }
    }

 

上面這樣就實現了本身的一個鏈接池,可是這個鏈接池依然存在着不少問題,一個較爲明顯的問題就是: 框架

若是一個用戶獲取了一個鏈接對象,而後調用了close()方法把它關閉了,沒有放回池中,這樣池中的這個對象就回不來了,形成最大鏈接上限爲8個的鏈接池實際上只有7個鏈接在工做。ide

 

爲了解決這個問題,咱們須要對close()方法進行改造,是用戶調用close()方法時,其實是把鏈接放回鏈接池中,而不是關閉它。

 

下面就爲解決這個問題來分析下~

 

解決用戶調用close()方法關閉鏈接

方法一:使用靜態代理,寫一個myConnection()類來繼承connection的實現類,而後重寫它的close()方法.

 

方法二:使用動態代理,使用jdbc動態代理類:java.lang.reflect.Proxy類

 

動態代理類中有這樣一個方法能夠建立它的實例

public static Object newProxyInstance(ClassLoader loader,

                                     Class<?>[] interfaces,

                                     InvocationHandler h)

 

參數解析:

ClassLoader:類加載器,只要在同一個JDK中的類便可

  Class<?>[]:要代理的接口的集合

      InvocationHandler:代理接口的方法處理器

根據須要,咱們要給MyPool中的Connection加一個動態代理,因此咱們用的前兩個參數是:MyPool.Class.GetClassLoader 與 new Class<>{Connection}

 

最後還剩方法處理器,咱們要修改Connection中的close方法,因此咱們寫出一個這樣作的處理器便可,具體實現看下面代碼與註釋~

//獲取鏈接的方法
    private Connection getConnection()
    {
        
        try {
            //獲取一個鏈接
            final Connection conn=DriverManager.getConnection(url, user, password);
            
            //把鏈接交給動態代理類轉換爲代理的鏈接對象
            Connection myconn = (Connection)Proxy.newProxyInstance(
                    MyPool.class.getClassLoader(), 
                    new Class[] {Connection.class}, 
                             //編寫一個方法處理器
                    new InvocationHandler() {
                
                @Override
                public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
                    Object value = null;
                    
                    //當遇到close方法,就會把對象放回鏈接池中,而不是關閉鏈接
                    if(method.getName().equals("close"))
                    {
                        MyPool.connList.addLast(conn);
                    }else
                    {
                        //其它方法不變
                        value = method.invoke(conn, args);
                    }
                    return value;
                }}
            );    
            return myconn;
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

以上就是利用動態代理的方式解決close的問題了~~可是,咱們本身寫的鏈接池還有不少其餘問題:

1)當前多個併發用戶同時獲取鏈接時,可能拿到同一個鏈接對象

2)當前用戶鏈接數超過了最大鏈接數時,不能直接拋出異常,應該有機制,控制用戶等待時間........

 

因此,這時候已經有人站出來,爲咱們寫好了一些功能相對完善的鏈接池,這些第三方的鏈接池獲得了普遍的用途,下面咱們來介紹一下常見的鏈接池工具吧~

 

DBCP鏈接池

簡介:DBCP鏈接池是開源組織Apache軟件基金組織開發的鏈接池實現。

事實上,tomcat服務器默認就會使用這個鏈接池道具。

 

如何使用DBCP鏈接池呢,下面我來一一演示。

 

DBCP的使用步驟

步驟一:導包,使用第三方的道具,必須導入相應的jar包。

須要導入兩個jar包:commons-dbcp-1.4.jar

        commons-pool-1.5.6.jar

 

步驟二:使用代碼~看看下面代碼的演示吧

 

public class DBCPTest {
        private String url = "jdbc:mysql://localhost:3306/vmaxtam";
        private String user = "root";
        private String password = "root";
        private String classDriver = "com.mysql.jdbc.Driver";
    
        @Test
        public void Test1()
        {
            //建立DBCP鏈接池對象
            BasicDataSource ds = new BasicDataSource();
            
            //設置鏈接參數來進行鏈接
            ds.setUrl(url);
            ds.setUsername(user);
            ds.setPassword(password);
            ds.setDriverClassName(classDriver);
            
            //而後能夠設置鏈接池的一些屬性啦~
            ds.setInitialSize(5);
            ds.setMaxActive(8);
            ds.setMaxWait(3000);//設置最大的等待時長,毫秒爲單位
            
            //從鏈接池中獲取對象
            for(int i = 0 ; i<8;i++)
            {
                Connection conn = null;
                try {
                    conn = ds.getConnection();
                    System.out.println(conn.hashCode());
                    
                } catch (SQLException e) {
                    e.printStackTrace();
                }        
            }
            
        }
}

 

爲了測試效果,咱們能夠在循環中設置拿9個鏈接額,這樣在拿第九個鏈接時就會出現等待,等待到結束都沒有鏈接被釋放回鏈接池,就會出現報錯。

也能夠把For循環改爲下面那樣,測試close方法:

//從鏈接池中獲取對象
            for(int i = 0 ; i<9;i++)
            {
                Connection conn = null;
                try {
                    conn = ds.getConnection();
                    System.out.println(conn.hashCode());
                    if(i==5)
                    {
                        conn.close();
                    }
                    
                } catch (SQLException e) {
                    e.printStackTrace();
                }        
            }

上面的代碼仍是有點地方能夠獲得優化,例如能夠經過配置文件來配置鏈接的參數,還有數據庫鏈接池的屬性參數。

配置文件:

url=jdbc:mysql://localhost:3306/vmaxtam
username=root
password=root
classDriver=com.mysql.jdbc.Driver        
initialSize=5
maxActive=8
maxWait=3000

用對象讀取配置文件:

@Test
        public void Test2()
        {
            
            try {
                //建立配置對象
                Properties properties = new Properties();
                properties.load(DBCPTest.class.getResourceAsStream("/dbcp.properties"));
            
                //建立鏈接池對象,而且用鏈接池工廠來加載配置對象的信息
                BasicDataSource ds = (BasicDataSource)BasicDataSourceFactory.createDataSource(properties);
    
            //從鏈接池中獲取對象
            for(int i = 0 ; i<8;i++)
            {
                Connection conn = null;
                conn = ds.getConnection();
                System.out.println(conn.hashCode());
            }
        }catch (Exception e2) {
                e2.printStackTrace();
            }            
        }

以上就是DBCP鏈接池的基本用法了~下面咱們來學習另外一個鏈接池~

 

C3P0鏈接池

簡介: C3P0是一個開源組織的產品,開源框架的內部的鏈接池通常都使用C3P0來實現,例如:Hibernate

 

C3P0的使用步驟

步驟一:導包,使用第三方的工具必須導入jar

要導入的包:c3p0-0.9.1.2.jar 

 

步驟二:看下面的代碼顯示怎麼使用這個鏈接池吧~

 

@Test
    public void Test1()
    {

        try {
            //獲取鏈接池對象
            ComboPooledDataSource cp = new ComboPooledDataSource();
            
            //設置鏈接參數
            cp.setJdbcUrl(url);
            cp.setUser(user);
            cp.setPassword(password);
            cp.setDriverClass(classDriver);
            
            //設置鏈接池的參數
            cp.setInitialPoolSize(5);//初始數量
            cp.setMaxPoolSize(8);//最大數量
            cp.setCheckoutTimeout(3000);//最大等待時間
            
            for(int i = 0 ; i<8 ; i++)
            {
                Connection conn = cp.getConnection();
                System.out.println(conn.hashCode());
            }
        
        } catch (Exception e) {
            e.printStackTrace();
        }    
    }

 

能夠看出,C3P0的用法和DBCP的用法很是的類似~這裏不作累贅。

 

特別的是C3PO讀取參數文件的方式,C3P0除了能像DBCP那樣讀取配置文件,它還提供了一種特殊的設置參數的方式,就是把參數數據寫在一個名叫c3p0-config.xml的XML文件中,在建立C3P0對象時會自動在classpath去尋找該文件來讀取~

也就是說:c3p0會到classpath下讀取名字爲c3p0-config.xml文件

 

這份XML文件有特殊的要求,下面咱們來寫一下這份XML文件:

 

<c3p0-config>
    <!-- 默認配置 -->
    <default-config>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/vmaxtam</property>
        <property name="user">root</property>
        <property name="password">root</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        
        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">8</property>
        <property name="checkoutTimeout">3000</property>
        
    </default-config>

    <!-- mysql的鏈接配置 -->
    <named-config name="mysql">
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/vmaxtam</property>
        <property name="user">root</property>
        <property name="password">root</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        
        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">8</property>
        <property name="checkoutTimeout">3000</property>    
    </named-config>
    
    <!-- 使用 oracal就會用這份配置-->
    <!-- 也能夠寫其餘數據庫的配置 -->

</c3p0-config>

寫完xml文件,如今咱們就讀取它吧~

 

@Test
    public void Test2()
    {
        try {
            //獲取鏈接池對象,寫上參數就會去找xml文件找這個數據庫的配置來讀取,當無參時,就會使用默認設置。
            ComboPooledDataSource cp = new ComboPooledDataSource("mysql");
            
            for(int i = 0 ; i<9 ; i++)
            {
                Connection conn = cp.getConnection();
                System.out.println(conn.hashCode());
                if(i==5)
                {
                    conn.close();
                }
            }
        
        } catch (Exception e) {
            e.printStackTrace();
        }    
    }

 

使用這種讀取方法,顯得代碼十分簡便。

相關文章
相關標籤/搜索