日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

druid连接池初始化慢_从零开始手写 mybatis (三)jdbc pool 从零实现数据库连接池

發布時間:2024/4/19 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 druid连接池初始化慢_从零开始手写 mybatis (三)jdbc pool 从零实现数据库连接池 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前景回顧

第一節 從零開始手寫 mybatis(一)MVP 版本 中我們實現了一個最基本的可以運行的 mybatis。

第二節 從零開始手寫 mybatis(二)mybatis interceptor 插件機制詳解

本節我們一起來看一下如何實現一個數據庫連接池。

為什么需要連接池?

數據庫連接的創建是非常耗時的一個操作,在高并發的場景,如果每次對于數據庫的訪問都重新創建的話,成本太高。

于是就有了“池化”這種解決方案。

這種方案在我們日常生活中也是比比皆是,比如資金池,需求池,乃至人力資源池。

思想都是共通的。

我們本節一起來從零實現一個簡易版本的數據庫連接池,不過麻雀雖小,五臟俱全。

將從以下幾個方面來展開:

(1)普通的數據庫連接創建

(2)自動適配 jdbc 驅動

(3)指定大小的連接池創建

(4)獲取連接時添加超時檢測

(5)添加對于連接有效性的檢測

普通的數據庫連接創建

這種就是最普通的不適用池化的實現。

實現

mybatis 默認其實也是這種實現,不過我們在這個基礎上做了一點優化,那就是可以根據 url 自動適配 driverClass。

public class UnPooledDataSource extends AbstractDataSourceConfig {@Overridepublic Connection getConnection() throws SQLException {DriverClassUtil.loadDriverClass(super.driverClass, super.jdbcUrl);return DriverManager.getConnection(super.getJdbcUrl(),super.getUser(), super.getPassword());}}

自動適配

這個特性主要是參考阿里的 druid 連接池實現,在用戶沒有指定驅動類時,自動適配。

核心代碼如下:

/*** 加載驅動類信息* @param driverClass 驅動類* @param url 連接信息* @since 1.2.0*/ public static void loadDriverClass(String driverClass, final String url) {ArgUtil.notEmpty(url, url);if(StringUtil.isEmptyTrim(driverClass)) {driverClass = getDriverClassByUrl(url);}try {Class.forName(driverClass);} catch (ClassNotFoundException e) {throw new JdbcPoolException(e);} }

如何根據 url 獲取啟動類呢?實際上就是一個 map 映射。

/*** 根據 URL 獲取對應的驅動類** 1. 禁止 url 為空* 2. 如果未找到,則直接報錯。* @param url url* @return 驅動信息*/ private static String getDriverClassByUrl(final String url) {ArgUtil.notEmpty(url, "url");for(Map.Entry<String, String> entry : DRIVER_CLASS_MAP.entrySet()) {String urlPrefix = entry.getKey();if(url.startsWith(urlPrefix)) {return entry.getValue();}}throw new JdbcPoolException("Can't auto find match driver class for url: " + url); }

其中 DRIVER_CLASS_MAP 映射如下:

| url 前綴 | 驅動類 | |:----|:----| | jdbc:sqlite | org.sqlite.JDBC | | jdbc:derby | org.apache.derby.jdbc.EmbeddedDriver | | jdbc:edbc | ca.edbc.jdbc.EdbcDriver | | jdbc:ingres | com.ingres.jdbc.IngresDriver | | jdbc:hsqldb | org.hsqldb.jdbcDriver | | jdbc:JSQLConnect | com.jnetdirect.jsql.JSQLDriver | | jdbc:sybase:Tds | com.sybase.jdbc2.jdbc.SybDriver | | jdbc:firebirdsql | org.firebirdsql.jdbc.FBDriver | | jdbc:microsoft | com.microsoft.jdbc.sqlserver.SQLServerDriver | | jdbc:mckoi | com.mckoi.JDBCDriver | | jdbc:oracle | oracle.jdbc.driver.OracleDriver | | jdbc:as400 | com.ibm.as400.access.AS400JDBCDriver | | jdbc:fake | com.alibaba.druid.mock.MockDriver | | jdbc:pointbase | com.pointbase.jdbc.jdbcUniversalDriver | | jdbc:sapdb | com.sap.dbtech.jdbc.DriverSapDB | | jdbc:postgresql | org.postgresql.Driver | | jdbc:cloudscape | COM.cloudscape.core.JDBCDriver | | jdbc:timesten | com.timesten.jdbc.TimesTenDriver | | jdbc:h2 | org.h2.Driver | | jdbc:jtds | net.sourceforge.jtds.jdbc.Driver | | jdbc:odps | com.aliyun.odps.jdbc.OdpsDriver | | jdbc:db2 | COM.ibm.db2.jdbc.app.DB2Driver | | jdbc:mysql | com.mysql.jdbc.Driver | | jdbc:informix-sqli | com.informix.jdbc.IfxDriver | | jdbc:mock | com.alibaba.druid.mock.MockDriver | | jdbc:mimer:multi1 | com.mimer.jdbc.Driver | | jdbc:interbase | interbase.interclient.Driver | | jdbc:JTurbo | com.newatlanta.jturbo.driver.Driver |

池化實現

接下來我們根據指定的大小創建一個初始化的連接池。

定義池化的相關信息

我們首先定義一個接口:

/*** 池化的連接池* @since 1.1.0*/ public interface IPooledConnection extends Connection {/*** 是否繁忙* @since 1.1.0* @return 狀態*/boolean isBusy();/*** 設置狀態* @param busy 狀態* @since 1.1.0*/void setBusy(boolean busy);/*** 獲取真正的連接* @return 連接* @since 1.1.0*/Connection getConnection();/*** 設置連接信息* @param connection 連接信息* @since 1.1.0*/void setConnection(Connection connection);/*** 設置對應的數據源* @param dataSource 數據源* @since 1.5.0*/void setDataSource(final IPooledDataSourceConfig dataSource);/*** 獲取對應的數據源信息* @return 數據源* @since 1.5.0*/IPooledDataSourceConfig getDataSource();}

這里我們直接繼承了 Connection 接口,實現時全部對 Connection 做一個代理。

內容較多,但是比較簡單,此處不再贅述。

連接池初始化

根據配置初始化大小:

/*** 初始化連接池* @since 1.1.0*/ private void initJdbcPool() {final int minSize = super.minSize;pool = new ArrayList<>(minSize);for(int i = 0; i < minSize; i++) {IPooledConnection pooledConnection = createPooledConnection();pool.add(pooledConnection);} }

createPooledConnection 內容如下:

/*** 創建一個池化的連接* @return 連接* @since 1.1.0*/ private IPooledConnection createPooledConnection() {Connection connection = createConnection();IPooledConnection pooledConnection = new PooledConnection();pooledConnection.setBusy(false);pooledConnection.setConnection(connection);pooledConnection.setDataSource(this);return pooledConnection; }

我們使用 busy 屬性,來標識當前連接是否可用。

新創建的連接默認都是可用的。

連接的獲取

整體流程如下:

(1)池中有連接,直接獲取

(2)池中沒有連接,且沒達到最大的大小,可以創建一個,然后返回

(3)池中沒有連接,但是已經達到最大,則進行等待。

@Override public synchronized Connection getConnection() throws SQLException {//1. 獲取第一個不是 busy 的連接Optional<IPooledConnection> connectionOptional = getFreeConnectionFromPool();if(connectionOptional.isPresent()) {return connectionOptional.get();}//2. 考慮是否可以擴容if(pool.size() >= maxSize) {//2.1 立刻返回if(maxWaitMills <= 0) {throw new JdbcPoolException("Can't get connection from pool!");}//2.2 循環等待final long startWaitMills = System.currentTimeMillis();final long endWaitMills = startWaitMills + maxWaitMills;while (System.currentTimeMillis() < endWaitMills) {Optional<IPooledConnection> optional = getFreeConnectionFromPool();if(optional.isPresent()) {return optional.get();}DateUtil.sleep(1);LOG.debug("等待連接池歸還,wait for 1 mills");}//2.3 等待超時throw new JdbcPoolException("Can't get connection from pool, wait time out for mills: " + maxWaitMills);}//3. 擴容(暫時只擴容一個)LOG.debug("開始擴容連接池大小,step: 1");IPooledConnection pooledConnection = createPooledConnection();pooledConnection.setBusy(true);this.pool.add(pooledConnection);LOG.debug("從擴容后的連接池中獲取連接");return pooledConnection; }

getFreeConnectionFromPool() 核心代碼如下:

直接獲取一個不是繁忙狀態的連接即可。

/*** 獲取空閑的連接* @return 連接* @since 1.3.0*/ private Optional<IPooledConnection> getFreeConnectionFromPool() {for(IPooledConnection pc : pool) {if(!pc.isBusy()) {pc.setBusy(true);LOG.debug("從連接池中獲取連接");return Optional.of(pc);}}// 空return Optional.empty(); }

連接的歸還

以前 connection 的歸還是直接將連接關閉,這里我們做了一個重載。

只是調整下對應的狀態即可。

@Override public void returnConnection(IPooledConnection pooledConnection) {// 驗證狀態if(testOnReturn) {checkValid(pooledConnection);}// 設置為不繁忙pooledConnection.setBusy(false);LOG.debug("歸還連接,狀態設置為不繁忙"); }

連接的有效性

池中的連接存在無效的可能,所以需要我們對其進行定期的檢測。

配置講解

驗證的時機是一門學問,我們可以在獲取時檢測,可以在歸還時檢測,但是二者都比較消耗性能。

比較好的方式是在空閑的時候進行校驗。

配置主要參考 druid 的配置,對應的接口如下:

/*** 設置驗證查詢的語句** 如果這個值為空,那么 {@link #setTestOnBorrow(boolean)}* {@link #setTestOnIdle(boolean)}}* {@link #setTestOnReturn(boolean)}* 都將無效* @param validQuery 驗證查詢的語句* @since 1.5.0*/ void setValidQuery(final String validQuery); /*** 驗證的超時秒數* @param validTimeOutSeconds 驗證的超時秒數* @since 1.5.0*/ void setValidTimeOutSeconds(final int validTimeOutSeconds); /*** 獲取連接時進行校驗** 備注:影響性能* @param testOnBorrow 是否* @since 1.5.0*/ void setTestOnBorrow(final boolean testOnBorrow); /*** 歸還連接時進行校驗** 備注:影響性能* @param testOnReturn 歸還連接時進行校驗* @since 1.5.0*/ void setTestOnReturn(final boolean testOnReturn); /*** 閑暇的時候進行校驗* @param testOnIdle 閑暇的時候進行校驗* @since 1.5.0*/ void setTestOnIdle(final boolean testOnIdle); /*** 閑暇時進行校驗的時間間隔* @param testOnIdleIntervalSeconds 時間間隔* @since 1.5.0*/ void setTestOnIdleIntervalSeconds(final long testOnIdleIntervalSeconds);

約定優于配置

所有的屬性都支持用戶自定義,以滿足不同的應用場景。

同時也秉承著默認的配置就是最常用的配置,默認的配置如下:

/*** 默認驗證查詢的語句* @since 1.5.0*/ public static final String DEFAULT_VALID_QUERY = "select 1 from dual";/*** 默認的驗證的超時時間* @since 1.5.0*/ public static final int DEFAULT_VALID_TIME_OUT_SECONDS = 5;/*** 獲取連接時,默認不校驗* @since 1.5.0*/ public static final boolean DEFAULT_TEST_ON_BORROW = false;/*** 歸還連接時,默認不校驗* @since 1.5.0*/ public static final boolean DEFAULT_TEST_ON_RETURN = false;/*** 默認閑暇的時候,進行校驗** @since 1.5.0*/ public static final boolean DEFAULT_TEST_ON_IDLE = true;/*** 1min 自動校驗一次** @since 1.5.0*/ public static final long DEFAULT_TEST_ON_IDLE_INTERVAL_SECONDS = 60;

檢測的實現

這里我參考了一篇 statckOverflow 的文章,其實還是使用 Connection#isValid 驗證比較簡單。

/*** https://stackoverflow.com/questions/3668506/efficient-sql-test-query-or-validation-query-that-will-work-across-all-or-most** 真正支持標準的,直接使用 {@link Connection#isValid(int)} 驗證比較合適* @param pooledConnection 連接池信息* @since 1.5.0*/ private void checkValid(final IPooledConnection pooledConnection) {if(StringUtil.isNotEmpty(super.validQuery)) {Connection connection = pooledConnection.getConnection();try {// 如果連接無效,重新申請一個新的替代if(!connection.isValid(super.validTimeOutSeconds)) {LOG.debug("Old connection is inValid, start create one for it.");Connection newConnection = createConnection();pooledConnection.setConnection(newConnection);LOG.debug("Old connection is inValid, finish create one for it.");}} catch (SQLException throwables) {throw new JdbcPoolException(throwables);}} else {LOG.debug("valid query is empty, ignore valid.");} }

閑暇時的線程處理

我們為了不影響性能,單獨為閑暇的連接檢測開一個線程。

在初始化的創建:

/*** 初始化空閑時檢驗* @since 1.5.0*/ private void initTestOnIdle() {if(StringUtil.isNotEmpty(validQuery)) {ScheduledExecutorService idleExecutor = Executors.newSingleThreadScheduledExecutor();idleExecutor.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {testOnIdleCheck();}}, super.testOnIdleIntervalSeconds, testOnIdleIntervalSeconds, TimeUnit.SECONDS);LOG.debug("Test on idle config with interval seonds: " + testOnIdleIntervalSeconds);} }

testOnIdleCheck 實現如下:

/*** 驗證所有的空閑連接是否有效* @since 1.5.0*/ private void testOnIdleCheck() {LOG.debug("start check test on idle");for(IPooledConnection pc : this.pool) {if(!pc.isBusy()) {checkValid(pc);}}LOG.debug("finish check test on idle"); }

開源地址

所有源碼均已開源:

jdbc-pool

使用方式和常見的連接池一樣。

maven 引入

<dependency><groupId>com.github.houbb</groupId><artifactId>jdbc-pool</artifactId><version>1.5.0</version> </dependency>

測試代碼

PooledDataSource source = new PooledDataSource(); source.setDriverClass("com.mysql.jdbc.Driver"); source.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8"); source.setUser("root"); source.setPassword("123456"); source.setMinSize(1);// 初始化 source.init();Connection connection = source.getConnection(); System.out.println(connection.getCatalog());Connection connection2 = source.getConnection(); System.out.println(connection2.getCatalog());

日志

[DEBUG] [2020-07-18 10:50:54.536] [main] [c.g.h.t.p.d.PooledDataSource.getFreeConnection] - 從連接池中獲取連接 test [DEBUG] [2020-07-18 10:50:54.537] [main] [c.g.h.t.p.d.PooledDataSource.getConnection] - 開始擴容連接池大小,step: 1 [DEBUG] [2020-07-18 10:50:54.548] [main] [c.g.h.t.p.d.PooledDataSource.getConnection] - 從擴容后的連接池中獲取連接 test

小結

到這里,一個簡單版本的連接池就已經實現了。

常見的連接池,比如 dbcp/c3p0/druid/jboss-pool/tomcat-pool 其實都是類似的。

萬變不離其宗,實現只是一種思想的差異化表示而已。

但是有哪些不足呢?

性能方面,我們為了簡單,都是直接使用 synchronized 保證并發安全,這樣性能會相對于樂觀鎖,或者是無鎖差一些。

自定義方面,比如 druid 可以支持用戶自定義攔截器,添加注入防止 sql 注入,耗時統計等等。

頁面管理,druid 比較優異的一點就是自帶頁面管理,這一點對于日常維護也比較友好。

http://weixin.qq.com/r/GSnk-PfEiar2rbOf93wL<br> (二維碼自動識別)

總結

以上是生活随笔為你收集整理的druid连接池初始化慢_从零开始手写 mybatis (三)jdbc pool 从零实现数据库连接池的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。