您好,登錄后才能下訂單哦!
本篇內容介紹了“JDBC數據庫連接池 怎么實現”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
對于一個簡單的數據庫應用,由于對于數據庫的訪問不是很頻繁。這時可以簡單地在需要訪問數據庫時,就新創建一個連接,用完后就關閉它,這樣做也不會帶來什么明顯的性能上的開銷。但是對于一個復雜的數據庫應用,情況就完全不同了。頻繁的建立、關閉連接,會極大的減低系統的性能,因為對于連接的使用成了系統性能的瓶頸。
連接復用。通過建立一個數據庫連接池以及一套連接使用管理策略,使得一個數據庫連接可以得到高效、安全的復用,避免了數據庫連接頻繁建立、關閉的開銷。
對于共享資源,有一個很著名的設計模式:資源池。該模式正是為了解決資源頻繁分配、釋放所造成的問題的。把該模式應用到數據庫連接管理領域,就是建立一個數據庫連接池,提供一套高效的連接分配、使用策略,最終目標是實現連接的高效、安全的復用。
數據庫連接池的基本原理是在內部對象池中維護一定數量的數據庫連接,并對外暴露數據庫連接獲取和返回方法。
外部使用者可通過 getConnection 方法獲取連接,使用完畢后再通過 close 方法將連接返回,注意此時連接并沒有關閉,而是由連接池管理器回收,并為下一次使用做好準備。
Java 中有一個 DataSource 接口, 數據庫連接池就是 DataSource 的一個實現
下面我們自己實現一個數據庫連接池:
首先實現 DataSource, 這里使用 BlockingQueue 作為池 (只保留了關鍵代碼)
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的 //驅動用的是最新的 //連接的時候會報The server time zone value '?й???????' // is unrecognized or represents more than one time zone //解決方法: //1.在連接串中加入?serverTimezone=UTC //2.在mysql中設置時區,默認為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 BlockingQueue<Connection> pool = 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<Connection> getPool() { return pool; } public void setPool(BlockingQueue<Connection> pool) { this.pool = pool; }}
實現自己的連接, 對原生連接進行封裝, 調用 close 方法的時候將連接放回到池中
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 { //解決重復關閉問題 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開發過程中,會廣泛使用到數據源。
我們基本的使用方式,是通過Spring使用類似如下的配置,來聲明一個數據源:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="maxActive" value="100" /> <property name="maxIdle" value="20" /> <property name="validationQuery" value="SELECT 1 from dual" /> <property name="testOnBorrow" value="true" /> </bean>
在之后應用里對于數據庫的操作,都基于這個數據源,但這個數據源連接池的創建、銷毀、管理,對于用戶都是近乎透明的,甚至數據庫連接的獲取,我們都看不到Connection
對象了。
這種方式是應用自身的數據庫連接池,各個應用之間互相獨立。
在類似于Tomcat這樣的應用服務器內部,也有提供數據源的能力,這時的數據源,可以為多個應用提供服務。
這一點類似于以前寫過關于Tomcat內部的Connector對于線程池的使用,可以各個Connector
獨立使用線程池,也可以共用配置的Executor
。(
Tomcat的Connector組件
)
那么,在Tomcat中,怎么樣配置和使用數據源呢?
先將對應要使用的數據庫的驅動文件xx.jar放到TOMCAT_HOME/lib目錄下。
編輯TOMCAT_HOME/conf/context.xml
文件,增加類似于下面的內容:
<Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource" maxTotal="100" maxIdle="30" maxWaitMillis="10000" username="root" password="pwd" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/test"/>
需要提供數據源的應用內,使用JNDI的方式獲取
Context initContext = new InitialContext();Context envContext = (Context)initContext.lookup("java:/comp/env");DataSource ds = (DataSource)envContext.lookup("jdbc/TestDB");Connection conn = ds.getConnection();
愉快的開始使用數據庫…
我們看,整個過程也并不比使用Spring等框架進行配置復雜,在應用內獲取連接也很容易。多個應用都可以通過第3步的方式獲取數據源,這使得同時提供多個應用共享數據源很容易。
這背后的是怎么實現的呢?
這個容器的連接池是怎么工作的呢,我們一起來看一看。
在根據context.xml
中配置的Resouce初始化的時候,會調用具體DataSource對應的實現類,Tomcat內部默認使用的BasicDataSource,在類初始化的時候,會執行這樣一行代碼DriverManager.getDrivers()
,其對應的內容如下,主要作用是使用 java.sql.DriverManager
實現的Service Provider機制,所有jar文件包含META-INF/services/java.sql.Driver文件的,會被自動發現、加載和注冊,不需要在需要獲取連接的時候,再手動的加載和注冊。
public static java.util.Enumeration<Driver> getDrivers() { java.util.Vector<Driver> 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中指定的數據源的屬性,創建數據源。
在我們的應用內getConnection
的時候,使用ConnectionFactory
創建Connection, 注意在創建Connection的時候,重點代碼是這個樣子:
public PooledObject<PoolableConnection> makeObject() 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中包含幾個隊列,其中比較關鍵的一個定義如下:
private final LinkedBlockingDeque<PooledObject<T>> idleObjects;
我們再看連接的關閉,
public void close() throws SQLException { if (getDelegateInternal() != null) { super.close(); super.setDelegate(null); } }
這里的關閉,并不會真的調用到Connection的close方法,我們通過上面的代碼已經看到,Connection返回的時候,其實是Connection的Wrapper類。在close的時候,真實的會調用到下面的代碼
// 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的隊列中,所以如果沒有關閉或者超過最大空閑連接,就會加到隊列中。而允許外的連接才會真實的銷毀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數據庫連接池 怎么實現”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。