从JDBC到数据库连接池
文章目錄
- 基本的JDBC操作方式
- 基本的JDBC操作在高并發的情況下帶來的問題
- 第三方應該具備條件
- 連接池的初步設計
- C3P0從數據源獲取到連接的過程
- 如何從C3P0獲取連接池狀態信息
基本的JDBC操作方式
為了屏蔽掉連接各種數據庫的底層細節,jdk提出了JDBC規范。數據庫廠商則需要實現JDBC規范中的某些接口。這些實現則統稱為XXX數據庫驅動,比如我們常見的Mysql驅動com.mysql.jdbc.Driver。
使用JDBC調用數據庫分為如下幾步:
其對應的代碼如下
Class.forName(driver); //記載數據庫驅動conn = DriverManager.getConnection(url,username,password); //獲取與數據庫的連接Long t1 = System.currentTimeMillis();System.out.println("獲取連接用時:"+(t1-Time)+"毫秒");ps = conn.prepareStatement(sql); rs = ps.executeQuery(); // 執行SQL語句Long t2 = System.currentTimeMillis();System.out.println("執行SQL語句用時:"+(t2-t1)+"毫秒");其執行結果如下:
我們發現在整個過程中,獲取與數據庫的連接>>執行SQL語句。
這是由于JDBC連接是建立在TCP協議上的。每次獲取到一條連接,其實是一條TCP連接。而TCP建立需要通過三次握手協議等一系列的操作來完成。因此,獲取數據庫連接的時間遠大于SQL語句執行的時間。
基本的JDBC操作在高并發的情況下帶來的問題
基本的JDBC操作在一些簡單的情況下沒有什么問題,比如課設。但是在并發量稍微增大后,就會變成如下的情況:
在并發量稍微提高后,如標準版登錄過程會有十幾個對API的請求。在這樣的情況下,如果使用基本的JDBC操作,在當上一次請求還在獲取JDBC的連接時,下一個請求就會到來。這樣的話對CPU,內存等硬件資源消耗極大,從而導致性能下降。
總而言之,基本的JDBC操作在高并發的情況下帶來下面問題:
- 每次操作數據庫都需要加載驅動、獲取連接等等,代碼十分繁瑣。(編碼方式上)
- 建立連接的時間要遠大于執行sql語句的時間。程序大量的時間都浪費在建立連接上了。
- 每個請求都是交由一個線程來執行。在高并發的情況下,會伴隨著大量的線程等待獲取數據庫連接請求。
這些問題的根源在與每次對數據庫進行操作都需要重新建立一條新的連接,使用完成后再關閉連接。整個過程消耗了大量的資源。
第三方應該具備條件
針對上面的問題,這時自然而然會有一個想法:能不能每次執行完對數據庫的操作,不關閉這些連接,而是把它交給一個第三方。等到下次想操作數據庫時,再從這個第三方獲取這些連接,這樣就避免了每次需要建立連接和關閉連接的時間開銷。
繼續深入思考,這個第三方工具會有如下基本功能:
這個第三方就是我們常說的數據庫連接池。這里設計模式叫資源池。
池化技術能夠減少資源對象的創建次數,提高程序的性能,特別是在高并發下這種提高更加明顯。 使用池化技術緩存的資源對象有如下共同特點: 1,對象創建時間長; 2,對象創建需要大量資源; 3,對象創建后可被重復使用。連接池的初步設計
對于一個連接池,最基本會有一個資源池,里面存放著與數據庫的連接。基于上面所講,這個連接應該是對java.sql.Connection的封裝,里面應該會有一個標志位來代表這個Connection是否被占用。然后會有一個數據源管理類來管理這個連接池。同時應該有一個配置文件來初始化該連接池。
C3P0從數據源獲取到連接的過程
從我們最熟悉的代碼開始com.mchange.v2.c3p0.ComboPooledDataSource是C3P0提供給我們的數據源實現類。
這個可以從我們配置的Bean中可以看出。
再從程序如何從數據源中獲取連接進入
下面代碼是AbstractPoolBackedDataSource類中的getConnection方法
下面的代碼是C3P0PooledConnectionPool中的checkoutPooledConnection()方法代碼細節
public PooledConnection checkoutPooledConnection() throws SQLException{ //從rp中獲取PooledConnectiontry { return (PooledConnection) rp.checkoutResource( checkoutTimeout ); }catch (TimeoutException e){ throw SqlUtils.toSQLException("An attempt by a client to checkout a Connection has timed out.", e); }catch (CannotAcquireResourceException e){ throw SqlUtils.toSQLException("Connections could not be acquired from the underlying database!", "08001", e); }catch (Exception e){ throw SqlUtils.toSQLException(e); }}獲取連接資源
public Object checkoutResource( long timeout )throws TimeoutException, ResourcePoolException, InterruptedException{//獲取資源Object resc = prelimCheckoutResource( timeout );if (resc == null)return checkoutResource( timeout );elsereturn resc;} private synchronized Object prelimCheckoutResource( long timeout )throws TimeoutException, ResourcePoolException, InterruptedException{try{ensureNotBroken();int available = unused.size();if (available == 0){int msz = managed.size();if (msz < max){// to cover all the load, we need the current size, plus those waiting already for acquisition, // plus the current client int desired_target = msz + acquireWaiters.size() + 1;if (logger.isLoggable(MLevel.FINER))logger.log(MLevel.FINER, "acquire test -- pool size: " + msz + "; target_pool_size: " + target_pool_size + "; desired target? " + desired_target);if (desired_target >= target_pool_size){//make sure we don't grab less than inc Connections at a time, if we can help it.desired_target = Math.max(desired_target, target_pool_size + inc);//make sure our target is within its boundstarget_pool_size = Math.max( Math.min( max, desired_target ), min );_recheckResizePool();}}else{if (logger.isLoggable(MLevel.FINER))logger.log(MLevel.FINER, "acquire test -- pool is already maxed out. [managed: " + msz + "; max: " + max + "]");}awaitAvailable(timeout); //throws timeout exception}Object resc = unused.get(0);}最終定位到三個變量managed,unused,excluded。
/* keys are all valid, managed resources, value is a PunchCard */ HashMap managed = new HashMap();/* all valid, managed resources currently available for checkout */LinkedList unused = new LinkedList();/* resources which have been invalidated somehow, but which are *//* still checked out and in use. */HashSet excluded = new HashSet();在BasicResourcePool下面,管理著三個變量managed、unused和excluded,他們別代表著管理著的連接、未使用的連接,和使用中但未被管理的連接。
從數據源ComboPooledDataSource拿到連接池C3P0PooledConnectionPool的過程為
對于連接池C3P0PooledConnectionPool與資源池BasicResourcePool的關系為
如何從C3P0獲取連接池狀態信息
官方文檔提供的查詢池狀態信息
基于上面對C3P0的了解。查閱AbstractPoolBackedDataSource。發現
對于getNumConnections()來說,其實現代碼如下
確認方法如下
| getNumConnections() | 池中管理的鏈接數 |
| getNumBusyConnections() | 池中已經在使用的鏈接數 |
| getNumUnclosedOrphanedConnections() | 已經在使用,但是未被連接池管理的連接 |
未完待續。。。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的从JDBC到数据库连接池的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux系统磁盘满了
- 下一篇: TensorFlow学习笔记之一(Ten