本篇內(nèi)容介紹了“JDBC數(shù)據(jù)庫連接池 怎么實現(xiàn)”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!
創(chuàng)新互聯(lián)建站成立于2013年,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項目成都網(wǎng)站建設(shè)、做網(wǎng)站網(wǎng)站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元修水做網(wǎng)站,已為上家服務(wù),為修水各地企業(yè)和個人服務(wù),聯(lián)系電話:13518219792
對于一個簡單的數(shù)據(jù)庫應(yīng)用,由于對于數(shù)據(jù)庫的訪問不是很頻繁。這時可以簡單地在需要訪問數(shù)據(jù)庫時,就新創(chuàng)建一個連接,用完后就關(guān)閉它,這樣做也不會帶來什么明顯的性能上的開銷。但是對于一個復(fù)雜的數(shù)據(jù)庫應(yīng)用,情況就完全不同了。頻繁的建立、關(guān)閉連接,會極大的減低系統(tǒng)的性能,因為對于連接的使用成了系統(tǒng)性能的瓶頸。
連接復(fù)用。通過建立一個數(shù)據(jù)庫連接池以及一套連接使用管理策略,使得一個數(shù)據(jù)庫連接可以得到高效、安全的復(fù)用,避免了數(shù)據(jù)庫連接頻繁建立、關(guān)閉的開銷。
對于共享資源,有一個很著名的設(shè)計模式:資源池。該模式正是為了解決資源頻繁分配、釋放所造成的問題的。把該模式應(yīng)用到數(shù)據(jù)庫連接管理領(lǐng)域,就是建立一個數(shù)據(jù)庫連接池,提供一套高效的連接分配、使用策略,最終目標是實現(xiàn)連接的高效、安全的復(fù)用。
數(shù)據(jù)庫連接池的基本原理是在內(nèi)部對象池中維護一定數(shù)量的數(shù)據(jù)庫連接,并對外暴露數(shù)據(jù)庫連接獲取和返回方法。
外部使用者可通過 getConnection 方法獲取連接,使用完畢后再通過 close 方法將連接返回,注意此時連接并沒有關(guān)閉,而是由連接池管理器回收,并為下一次使用做好準備。
Java 中有一個 DataSource 接口, 數(shù)據(jù)庫連接池就是 DataSource 的一個實現(xiàn)
下面我們自己實現(xiàn)一個數(shù)據(jù)庫連接池:
首先實現(xiàn) DataSource, 這里使用 BlockingQueue 作為池 (只保留了關(guān)鍵代碼)
import javax.sql.DataSource;import java.io.PrintWriter;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.sql.SQLFeatureNotSupportedException;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;import java.util.logging.Logger;public class MyDataSource implements DataSource { static { try { Class.forName("com.MySQL.jdbc.Driver"); } catch (Exception e) { e.printStackTrace(); } } //這里有個坑 //MySQL用的是5.5的 //驅(qū)動用的是最新的 //連接的時候會報The server time zone value '?й???????' // is unrecognized or represents more than one time zone //解決方法: //1.在連接串中加入?serverTimezone=UTC //2.在mysql中設(shè)置時區(qū),默認為SYSTEM //set global time_zone='+8:00' private String url = "jdbc:mysql://localhost:3306/test?serverTimezone=UTC"; private String user = "root"; private String password = "123456"; private BlockingQueuepool = new ArrayBlockingQueue<>(3); public MyDataSource() { initPool(); } private void initPool() { try { for (int i = 0; i < 3; i++) { pool.add(new MyConnection( DriverManager.getConnection(url, user, password), this)); } } catch (SQLException e) { e.printStackTrace(); } } /* 從池中獲取連接 */ @Override public synchronized Connection getConnection() throws SQLException { try { return pool.take(); } catch (InterruptedException e) { e.printStackTrace(); } throw new RuntimeException("get connection failed!"); } public BlockingQueue getPool() { return pool; } public void setPool(BlockingQueue pool) { this.pool = pool; }}
實現(xiàn)自己的連接, 對原生連接進行封裝, 調(diào)用 close 方法的時候?qū)⑦B接放回到池中
import java.sql.*; import java.util.Map; import java.util.Properties; importjava.util.concurrent.Executor; public class MyConnection implements Connection { //包裝的連接private Connection conn; private MyDataSource dataSource; public MyConnection(Connection conn, MyDataSource dataSource) { this.conn = conn; this.dataSource = dataSource; } @Overridepublic Statement createStatement() throws SQLException { return conn.createStatement(); }@Override public PreparedStatement prepareStatement(String sql) throws SQLException { returnconn.prepareStatement(sql); } @Override public boolean getAutoCommit() throws SQLException {return conn.getAutoCommit(); } @Override public void setAutoCommit(boolean autoCommit) throwsSQLException { conn.setAutoCommit(autoCommit); } @Override public void commit() throwsSQLException { conn.commit(); } @Override public void rollback() throws SQLException { conn.rollback(); } @Override public void close() throws SQLException { //解決重復(fù)關(guān)閉問題 if(!isClosed()) { dataSource.getPool().add(this); } } @Override public boolean isClosed()throws SQLException { return dataSource.getPool().contains(this); } }
main 方法
import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException;import java.sql.Statement; public class Main { public static void main(String[] args) { DataSource source = new MyDataSource(); try { Connection conn = source.getConnection(); Statement st = conn.createStatement(); st.execute("INSERT INTO USER (name,age) values('bob',12)"); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
在Java Web開發(fā)過程中,會廣泛使用到數(shù)據(jù)源。
我們基本的使用方式,是通過Spring使用類似如下的配置,來聲明一個數(shù)據(jù)源:
在之后應(yīng)用里對于數(shù)據(jù)庫的操作,都基于這個數(shù)據(jù)源,但這個數(shù)據(jù)源連接池的創(chuàng)建、銷毀、管理,對于用戶都是近乎透明的,甚至數(shù)據(jù)庫連接的獲取,我們都看不到Connection
對象了。
這種方式是應(yīng)用自身的數(shù)據(jù)庫連接池,各個應(yīng)用之間互相獨立。
在類似于Tomcat這樣的應(yīng)用服務(wù)器內(nèi)部,也有提供數(shù)據(jù)源的能力,這時的數(shù)據(jù)源,可以為多個應(yīng)用提供服務(wù)。
這一點類似于以前寫過關(guān)于Tomcat內(nèi)部的Connector對于線程池的使用,可以各個Connector
獨立使用線程池,也可以共用配置的Executor
。(
Tomcat的Connector組件
)
那么,在Tomcat中,怎么樣配置和使用數(shù)據(jù)源呢?
先將對應(yīng)要使用的數(shù)據(jù)庫的驅(qū)動文件xx.jar放到TOMCAT_HOME/lib目錄下。
編輯TOMCAT_HOME/conf/context.xml
文件,增加類似于下面的內(nèi)容:
需要提供數(shù)據(jù)源的應(yīng)用內(nèi),使用JNDI的方式獲取
Context initContext = new InitialContext();Context envContext = (Context)initContext.lookup("java:/comp/env");DataSource ds = (DataSource)envContext.lookup("jdbc/TestDB");Connection conn = ds.getConnection();
愉快的開始使用數(shù)據(jù)庫…
我們看,整個過程也并不比使用Spring等框架進行配置復(fù)雜,在應(yīng)用內(nèi)獲取連接也很容易。多個應(yīng)用都可以通過第3步的方式獲取數(shù)據(jù)源,這使得同時提供多個應(yīng)用共享數(shù)據(jù)源很容易。
這背后的是怎么實現(xiàn)的呢?
這個容器的連接池是怎么工作的呢,我們一起來看一看。
在根據(jù)context.xml
中配置的Resouce初始化的時候,會調(diào)用具體DataSource對應(yīng)的實現(xiàn)類,Tomcat內(nèi)部默認使用的BasicDataSource,在類初始化的時候,會執(zhí)行這樣一行代碼DriverManager.getDrivers()
,其對應(yīng)的內(nèi)容如下,主要作用是使用 java.sql.DriverManager
實現(xiàn)的Service Provider機制,所有jar文件包含META-INF/services/java.sql.Driver文件的,會被自動發(fā)現(xiàn)、加載和注冊,不需要在需要獲取連接的時候,再手動的加載和注冊。
public static java.util.EnumerationgetDrivers() { java.util.Vector result = new java.util.Vector<>(); for(DriverInfo aDriver : registeredDrivers) { if(isDriverAllowed(aDriver.driver, callerClass)) { result.addElement(aDriver.driver); } else { println(" skipping: " + aDriver.getClass().getName()); } } return (result.elements()); }
之后DataSourceFactory會讀取Resouce中指定的數(shù)據(jù)源的屬性,創(chuàng)建數(shù)據(jù)源。
在我們的應(yīng)用內(nèi)getConnection
的時候,使用ConnectionFactory
創(chuàng)建Connection, 注意在創(chuàng)建Connection的時候,重點代碼是這個樣子:
public PooledObjectmakeObject() throws Exception { Connection conn = _connFactory.createConnection(); initializeConnection(conn); PoolableConnection pc = new PoolableConnection(conn,_pool, connJmxName); return new DefaultPooledObject<>(pc);
這里的_pool
是GenericObjectPool,連接的獲取是通過其進行的。
public Connection getConnection() throws SQLException { C conn = _pool.borrowObject(); }
在整個pool中包含幾個隊列,其中比較關(guān)鍵的一個定義如下:
private final LinkedBlockingDeque> idleObjects;
我們再看連接的關(guān)閉,
public void close() throws SQLException { if (getDelegateInternal() != null) { super.close(); super.setDelegate(null); } }
這里的關(guān)閉,并不會真的調(diào)用到Connection的close方法,我們通過上面的代碼已經(jīng)看到,Connection返回的時候,其實是Connection的Wrapper類。在close的時候,真實的會調(diào)用到下面的代碼
// Normal close: underlying connection is still open, so we // simply need to return this proxy to the pool try { _pool.returnObject(this); } catch(IllegalStateException e) {}
所謂的return
,是把連接放回到上面我們提到的idleObjects隊列中。整個連接是放在一個LIFO的隊列中,所以如果沒有關(guān)閉或者超過最大空閑連接,就會加到隊列中。而允許外的連接才會真實的銷毀destory
。
int maxIdleSave = getMaxIdle(); if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) { try { destroy(p); } catch (Exception e) { swallowException(e); } } else { if (getLifo()) { idleObjects.addFirst(p); // 這里。 } else { idleObjects.addLast(p); } if (isClosed()) { // Pool closed while object was being added to idle objects. // Make sure the returned object is destroyed rather than left // in the idle object pool (which would effectively be a leak) clear(); } }
“JDBC數(shù)據(jù)庫連接池 怎么實現(xiàn)”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!