JDBC之 連接池
有這樣的一種現象:
用java代碼操作數據庫,需要數據庫連接對象,一個用戶至少要用到一個連接。現在假設有成千上百萬個用戶,就要創建十分巨大數量的連接對象,這會使數據庫承受極大的壓力,為了解決這種現象,一種技術出現了,這就是數據庫連接池。
什么是數據庫連接池(原理)
所謂數據庫連接池,可以看作 :在用戶和數據庫之間創建一個”池”,這個池中有若干個連接對象,當用戶想要連接數據庫,就要先從連接池中獲取連接對象,然后操作數據庫。一旦連接池中的連接對象被拿光了,下一個想要操作數據庫的用戶必須等待,等待其他用戶釋放連接對象,把它放回連接池中,這時候等待的用戶才能獲取連接對象,從而操作數據庫。
數據庫連接池的屬性
連接對象初始的數量:initSize,一開始就創建若干個,當不夠時再添加
連接對象最大數量:maxSize,添加到最大值則不會再添加
下面我們用代碼下一個自己的連接池吧~
實現自己的連接池
看下面代碼和注釋吧~
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&¤tsize<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個連接在工作。
為了解決這個問題,我們需要對close()方法進行改造,是用戶調用close()方法時,實際上是把連接放回連接池中,而不是關閉它。
下面就為解決這個問題來分析下~
解決用戶調用close()方法關閉連接
方法一:使用靜態代理,寫一個myConnection()類來繼承connection的實現類,然后重寫它的close()方法.
方法二:使用動態代理,使用jdbc動態代理類:java.lang.reflect.Proxy類
動態代理類中有這樣一個方法可以創建它的實例
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
參數解析:
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(); } }
使用這種讀取方法,顯得代碼十分簡便。