什么是 DataSource?什么又是 DruidDataSource?
概述
? ? ? ? DataSource 翻譯過來為數(shù)據(jù)源,它是 jdk 提供的一個接口,然后只提供了兩個 getConnection 方法,分別是無參數(shù)和需要傳入用戶名和密碼。所以說它是跟數(shù)據(jù)庫連接有關(guān)的東西,可以通過它來獲取數(shù)據(jù)庫連接。
public interface DataSource extends CommonDataSource, Wrapper {/*** <p>Attempts to establish a connection with the data source that* this {@code DataSource} object represents.** @return a connection to the data source* @exception SQLException if a database access error occurs* @throws java.sql.SQLTimeoutException when the driver has determined that the* timeout value specified by the {@code setLoginTimeout} method* has been exceeded and has at least tried to cancel the* current database connection attempt*/Connection getConnection() throws SQLException;/*** <p>Attempts to establish a connection with the data source that* this {@code DataSource} object represents.** @param username the database user on whose behalf the connection is* being made* @param password the user's password* @return a connection to the data source* @exception SQLException if a database access error occurs* @throws java.sql.SQLTimeoutException when the driver has determined that the* timeout value specified by the {@code setLoginTimeout} method* has been exceeded and has at least tried to cancel the* current database connection attempt* @since 1.4*/Connection getConnection(String username, String password)throws SQLException; }? ? ? ? 但是這只是一個接口,具體怎么獲取到數(shù)據(jù)庫連接,還是由實現(xiàn)它的子類來決定。本文就是來講一下?DruidDataSource, 為什么講它呢?因為它是阿里寫出來的,比較 diao。
DruidDataSource
????????DruidDataSource 是阿里寫出來的一個數(shù)據(jù)源, 它不僅可以獲取數(shù)據(jù)庫連接,還把這些數(shù)據(jù)庫連接管理了起來,也就是所謂的數(shù)據(jù)庫連接池。這樣的話,當通過該數(shù)據(jù)源獲取數(shù)據(jù)庫連接的時候,如果數(shù)據(jù)庫連接池里有可以使用的連接,那么就直接返回該連接,就省的每次獲取數(shù)據(jù)庫連接都要創(chuàng)建了。
? ? ? ? 首先看一下怎么使用?DruidDataSource。
// 配置一個 DruidDataSource 實例 bean 交由 Spring 容器管理 @Configuration public class DataSourceConfig {@Beanpublic DataSource dataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setUsername("root");dataSource.setPassword("123456");dataSource.setUrl("jdbc:mysql://localhost:3306/transaction?useSSL=false");dataSource.setDriverClassName("com.mysql.jdbc.Driver");//連接池最小空閑的連接數(shù)dataSource.setMinIdle(5);//連接池最大活躍的連接數(shù)dataSource.setMaxActive(20);//初始化連接池時創(chuàng)建的連接數(shù)dataSource.setInitialSize(10);return dataSource;} }//測試類 public class DataSourceMain {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(DataSourceConfig.class);//獲取 dataSource 實例DataSource dataSource = (DataSource) applicationContext.getBean("dataSource");try {//通過數(shù)據(jù)源獲取數(shù)據(jù)庫連接Connection connection = dataSource.getConnection();Statement statement = connection.createStatement();statement.execute("update AccountInfo set balance = balance + 1 where id = 1");} catch (SQLException throwables) {throwables.printStackTrace();}} }? ? ? ? 從上可以看出,通過數(shù)據(jù)源訪問跟 jdbc 方式相比,省略了實例化數(shù)據(jù)庫連接驅(qū)動 Driver 驅(qū)動這一步,此外調(diào)用 getConnection() 獲取連接的時候,并沒有傳用戶名和密碼,說明 dataSource 把這些配置信息都管理起來了。后面獲取連接的邏輯會自給自足。
? ? ? ? 總結(jié)來說,DruidDataSource 管理了數(shù)據(jù)庫連接的一些配置信息,還幫助我們創(chuàng)建了連接驅(qū)動 Driver, 剩下的邏輯就跟 jdbc 一毛一樣了。管理配置信息就不說了,無非就是定義一些變量,然后通過 set 方法給寫進去。接下來我們開一下什么時候創(chuàng)建的 Driver, 以及怎么獲取數(shù)據(jù)庫連接的。
? ? ? ? 首先進入?DruidDataSource 類的?getConnection() 方法
public DruidPooledConnection getConnection() throws SQLException {return getConnection(maxWait);}//這個參數(shù)代表超時時間,過了這個時間還沒有獲取到連接就代表獲取連接失敗。public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {//初始化方法init();if (filters.size() > 0) {FilterChainImpl filterChain = new FilterChainImpl(this);return filterChain.dataSource_connect(this, maxWaitMillis);} else {return getConnectionDirect(maxWaitMillis);}}? ? ? ? 這里主要有兩個方法,第一個是初始化方法,第二個是?getConnectionDirect(maxWaitMillis),很明顯第二個是獲取連接的,那么第一個是干什么的?
public void init() throws SQLException {// inited 控制該初始化方法只調(diào)用一次,在方法后面會設(shè)置為 true,再次調(diào)用該方法就會直接返回。if (inited) {return;}// bug fixed for dead lock, for issue #2980//這一行是為了解決死鎖問題,https://github.com/alibaba/druid/issues/2980 詳情可以去這個連接看一下DruidDriver.getInstance();final ReentrantLock lock = this.lock;try {lock.lockInterruptibly();} catch (InterruptedException e) {throw new SQLException("interrupt", e);}boolean init = false;try {if (inited) {return;}initStackTrace = Utils.toString(Thread.currentThread().getStackTrace());this.id = DruidDriver.createDataSourceId();if (this.id > 1) {long delta = (this.id - 1) * 100000;this.connectionIdSeedUpdater.addAndGet(this, delta);this.statementIdSeedUpdater.addAndGet(this, delta);this.resultSetIdSeedUpdater.addAndGet(this, delta);this.transactionIdSeedUpdater.addAndGet(this, delta);}if (this.jdbcUrl != null) {this.jdbcUrl = this.jdbcUrl.trim();initFromWrapDriverUrl();}for (Filter filter : filters) {filter.init(this);}if (this.dbType == null || this.dbType.length() == 0) {this.dbType = JdbcUtils.getDbType(jdbcUrl, null);}if (JdbcConstants.MYSQL.equals(this.dbType)|| JdbcConstants.MARIADB.equals(this.dbType)|| JdbcConstants.ALIYUN_ADS.equals(this.dbType)) {boolean cacheServerConfigurationSet = false;if (this.connectProperties.containsKey("cacheServerConfiguration")) {cacheServerConfigurationSet = true;} else if (this.jdbcUrl.indexOf("cacheServerConfiguration") != -1) {cacheServerConfigurationSet = true;}if (cacheServerConfigurationSet) {this.connectProperties.put("cacheServerConfiguration", "true");}}if (maxActive <= 0) {throw new IllegalArgumentException("illegal maxActive " + maxActive);}if (maxActive < minIdle) {throw new IllegalArgumentException("illegal maxActive " + maxActive);}if (getInitialSize() > maxActive) {throw new IllegalArgumentException("illegal initialSize " + this.initialSize + ", maxActive " + maxActive);}if (timeBetweenLogStatsMillis > 0 && useGlobalDataSourceStat) {throw new IllegalArgumentException("timeBetweenLogStatsMillis not support useGlobalDataSourceStat=true");}if (maxEvictableIdleTimeMillis < minEvictableIdleTimeMillis) {throw new SQLException("maxEvictableIdleTimeMillis must be grater than minEvictableIdleTimeMillis");}if (this.driverClass != null) {this.driverClass = driverClass.trim();}initFromSPIServiceLoader();//此時 driver 還沒有初始化if (this.driver == null) {if (this.driverClass == null || this.driverClass.isEmpty()) {this.driverClass = JdbcUtils.getDriverClassName(this.jdbcUrl);}if (MockDriver.class.getName().equals(driverClass)) {driver = MockDriver.instance;} else {if (jdbcUrl == null && (driverClass == null || driverClass.length() == 0)) {throw new SQLException("url not set");}// 會調(diào)用這個方法獲取 driver 實例driver = JdbcUtils.createDriver(driverClassLoader, driverClass);}} else {if (this.driverClass == null) {this.driverClass = driver.getClass().getName();}}initCheck();initExceptionSorter();initValidConnectionChecker();validationQueryCheck();if (isUseGlobalDataSourceStat()) {dataSourceStat = JdbcDataSourceStat.getGlobal();if (dataSourceStat == null) {dataSourceStat = new JdbcDataSourceStat("Global", "Global", this.dbType);JdbcDataSourceStat.setGlobal(dataSourceStat);}if (dataSourceStat.getDbType() == null) {dataSourceStat.setDbType(this.dbType);}} else {dataSourceStat = new JdbcDataSourceStat(this.name, this.jdbcUrl, this.dbType, this.connectProperties);}dataSourceStat.setResetStatEnable(this.resetStatEnable);//初始化數(shù)據(jù)庫連接池,其實就是個數(shù)組connections = new DruidConnectionHolder[maxActive];evictConnections = new DruidConnectionHolder[maxActive];keepAliveConnections = new DruidConnectionHolder[maxActive];SQLException connectError = null;if (createScheduler != null && asyncInit) {for (int i = 0; i < initialSize; ++i) {submitCreateTask(true);}} else if (!asyncInit) {// init connections// 這個就是通過配置設(shè)置的初始化的連接的個數(shù)while (poolingCount < initialSize) {try {// 獲取數(shù)據(jù)庫連接PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);//將獲取到的數(shù)據(jù)庫連接放入到連接池中,也就是 connections 數(shù)組中connections[poolingCount++] = holder;} catch (SQLException ex) {LOG.error("init datasource error, url: " + this.getUrl(), ex);if (initExceptionThrow) {connectError = ex;break;} else {Thread.sleep(3000);}}}if (poolingCount > 0) {poolingPeak = poolingCount;poolingPeakTime = System.currentTimeMillis();}}createAndLogThread();//創(chuàng)建一個線程負責(zé)創(chuàng)建新的數(shù)據(jù)庫連接createAndStartCreatorThread();createAndStartDestroyThread();initedLatch.await();init = true;initedTime = new Date();registerMbean();if (connectError != null && poolingCount == 0) {throw connectError;}if (keepAlive) {// async fill to minIdleif (createScheduler != null) {for (int i = 0; i < minIdle; ++i) {submitCreateTask(true);}} else {this.emptySignal();}}} catch (SQLException e) {LOG.error("{dataSource-" + this.getID() + "} init error", e);throw e;} catch (InterruptedException e) {throw new SQLException(e.getMessage(), e);} catch (RuntimeException e){LOG.error("{dataSource-" + this.getID() + "} init error", e);throw e;} catch (Error e){LOG.error("{dataSource-" + this.getID() + "} init error", e);throw e;} finally {// 設(shè)置 inited 為 true,當再次調(diào)用 init() 方法的時候就會直接返回。inited = true;lock.unlock();if (init && LOG.isInfoEnabled()) {String msg = "{dataSource-" + this.getID();if (this.name != null && !this.name.isEmpty()) {msg += ",";msg += this.name;}msg += "} inited";LOG.info(msg);}}}? ? ? ? 上面的代碼量很多,但是我們目前先把目光放到主線代碼中,那么很明顯有幾個重要的點
- 獲取 driver 實例 driver = JdbcUtils.createDriver(driverClassLoader, driverClass);
- 獲取數(shù)據(jù)庫連接 PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();
- 此外我們還知道了所謂連接池就是一個數(shù)組,它保存著一個個連接對象實例。
? ? ? ? 首先看下如何獲取 driver
public static Driver createDriver(ClassLoader classLoader, String driverClassName) throws SQLException {Class<?> clazz = null;if (classLoader != null) {try {clazz = classLoader.loadClass(driverClassName);} catch (ClassNotFoundException e) {// skip}}if (clazz == null) {try {ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();if (contextLoader != null) {clazz = contextLoader.loadClass(driverClassName);}} catch (ClassNotFoundException e) {// skip}}if (clazz == null) {try {clazz = Class.forName(driverClassName);} catch (ClassNotFoundException e) {throw new SQLException(e.getMessage(), e);}}try {//通過反射獲取實例,其實也就是 jdbc 獲取 driverreturn (Driver) clazz.newInstance();} catch (IllegalAccessException e) {throw new SQLException(e.getMessage(), e);} catch (InstantiationException e) {throw new SQLException(e.getMessage(), e);}}? ? ? ? ? ? 可以看出,它跟使用 jdbc 時一樣,都是通過反射獲取到 driver 實例,然后我們再看一下如何獲取一個連接
public PhysicalConnectionInfo createPhysicalConnection() throws SQLException {String url = this.getUrl();Properties connectProperties = getConnectProperties();String user;if (getUserCallback() != null) {user = getUserCallback().getName();} else {user = getUsername();}String password = getPassword();PasswordCallback passwordCallback = getPasswordCallback();if (passwordCallback != null) {if (passwordCallback instanceof DruidPasswordCallback) {DruidPasswordCallback druidPasswordCallback = (DruidPasswordCallback) passwordCallback;druidPasswordCallback.setUrl(url);druidPasswordCallback.setProperties(connectProperties);}char[] chars = passwordCallback.getPassword();if (chars != null) {password = new String(chars);}}Properties physicalConnectProperties = new Properties();if (connectProperties != null) {physicalConnectProperties.putAll(connectProperties);}if (user != null && user.length() != 0) {physicalConnectProperties.put("user", user);}if (password != null && password.length() != 0) {physicalConnectProperties.put("password", password);}Connection conn = null;long connectStartNanos = System.nanoTime();long connectedNanos, initedNanos, validatedNanos;Map<String, Object> variables = initVariants? new HashMap<String, Object>(): null;Map<String, Object> globalVariables = initGlobalVariants? new HashMap<String, Object>(): null;createStartNanosUpdater.set(this, connectStartNanos);creatingCountUpdater.incrementAndGet(this);try {// 這時physicalConnectProperties已經(jīng)拼裝好了配置信息,也獲取到了urlconn = createPhysicalConnection(url, physicalConnectProperties);connectedNanos = System.nanoTime();if (conn == null) {throw new SQLException("connect error, url " + url + ", driverClass " + this.driverClass);}initPhysicalConnection(conn, variables, globalVariables);initedNanos = System.nanoTime();validateConnection(conn);validatedNanos = System.nanoTime();setFailContinuous(false);setCreateError(null);} catch (SQLException ex) {setCreateError(ex);JdbcUtils.close(conn);throw ex;} catch (RuntimeException ex) {setCreateError(ex);JdbcUtils.close(conn);throw ex;} catch (Error ex) {createErrorCountUpdater.incrementAndGet(this);setCreateError(ex);JdbcUtils.close(conn);throw ex;} finally {long nano = System.nanoTime() - connectStartNanos;createTimespan += nano;creatingCountUpdater.decrementAndGet(this);}return new PhysicalConnectionInfo(conn, connectStartNanos, connectedNanos, initedNanos, validatedNanos, variables, globalVariables);} public Connection createPhysicalConnection(String url, Properties info) throws SQLException {Connection conn;if (getProxyFilters().size() == 0) {// 這里是獲取到我們剛剛實例化的 driver, 然后再調(diào)用它的 connect 方法conn = getDriver().connect(url, info);} else {conn = new FilterChainImpl(this).connection_connect(info);}createCountUpdater.incrementAndGet(this);return conn;}? ? ? ? 從上可以看出,獲取數(shù)據(jù)庫連接首先也是要獲取到 driver 然后再調(diào)用它的 connect 方法。
? ? ? ? 總的來說,上面代碼的主線邏輯就是先實例化 driver, 然后通過 driver 獲取一個數(shù)據(jù)庫連接,這其實也就是 jdbc 的方式,只不過別讓幫我們做了這件事情,我們不用再自己做了而已。并且DruidDataSource 在獲取到連接之后,還會把這個連接存起來,以免每次請求連接都要重新從數(shù)據(jù)庫獲取一個連接。
? ? ? ? 上一部分講的是初始化方法 init(), 別忘了我們是講?DruidDataSource 獲取數(shù)據(jù)庫連接方法的,如下
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {// 執(zhí)行初始化方法后,這時連接池里已經(jīng)有了數(shù)據(jù)庫連接了init();if (filters.size() > 0) {FilterChainImpl filterChain = new FilterChainImpl(this);return filterChain.dataSource_connect(this, maxWaitMillis);} else {return getConnectionDirect(maxWaitMillis);}}? ? ? ? 我們需要看一下?getConnectionDirect(maxWaitMillis) 是怎么獲取連接的
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {int notFullTimeoutRetryCnt = 0;for (;;) {// handle notFullTimeoutRetryDruidPooledConnection poolableConnection;try {//很明顯這個方法就是獲取數(shù)據(jù)庫連接的, 剩下的代碼可以先不看,直接跳到getConnectionInternalpoolableConnection = getConnectionInternal(maxWaitMillis);} catch (GetConnectionTimeoutException ex) {if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {notFullTimeoutRetryCnt++;if (LOG.isWarnEnabled()) {LOG.warn("get connection timeout retry : " + notFullTimeoutRetryCnt);}continue;}throw ex;}。。。。。。。。。。。//省略下面的代碼 private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {。。。。。。。。。。//省略該方法前面的一些代碼// 等待超時時間,默認是 -1if (maxWait > 0) {holder = pollLast(nanos);} else {holder = takeLast();}if (holder != null) {activeCount++;if (activeCount > activePeak) {activePeak = activeCount;activePeakTime = System.currentTimeMillis();}}} catch (InterruptedException e) {connectErrorCountUpdater.incrementAndGet(this);throw new SQLException(e.getMessage(), e);} catch (SQLException e) {connectErrorCountUpdater.incrementAndGet(this);throw e;} finally {lock.unlock();}break;}DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder);return poolalbeConnection;}? ? ? ? 我們在代碼里看到 pollLast 和 takeLast 是獲取數(shù)據(jù)庫連接的,這兩個方法差不多,pollLast()會加入時間的判斷,但是核心邏輯都是一樣的,所以我們跟一下默認的 takeLast 方法
DruidConnectionHolder takeLast() throws InterruptedException, SQLException {try {//如果沒有設(shè)置連接池初始化大小,則會重新創(chuàng)建一個連接放入連接池, 如果設(shè)置了初始化大小,則在調(diào)用init()方法的時候會創(chuàng)建一些連接放在連接池中保存。while (poolingCount == 0) {emptySignal(); // send signal to CreateThread create connectionif (failFast && isFailContinuous()) {throw new DataSourceNotAvailableException(createError);}notEmptyWaitThreadCount++;if (notEmptyWaitThreadCount > notEmptyWaitThreadPeak) {notEmptyWaitThreadPeak = notEmptyWaitThreadCount;}try {notEmpty.await(); // signal by recycle or creator} finally {notEmptyWaitThreadCount--;}notEmptyWaitCount++;if (!enable) {connectErrorCountUpdater.incrementAndGet(this);if (disableException != null) {throw disableException;}throw new DataSourceDisableException();}}} catch (InterruptedException ie) {notEmpty.signal(); // propagate to non-interrupted threadnotEmptySignalCount++;throw ie;}//將數(shù)據(jù)庫連接池中的連接數(shù)減一decrementPoolingCount();//獲取數(shù)據(jù)庫連接池中的的一個連接DruidConnectionHolder last = connections[poolingCount];connections[poolingCount] = null;return last;}? ? ? ? 通過上面的分析,我們應(yīng)該已經(jīng)知道,當調(diào)用 Connection connection = dataSource.getConnection() 的時候,如果是第一次調(diào)用,則會在 init() 方法的時候創(chuàng)建一些連接放入數(shù)據(jù)庫連接池里,然后獲取連接的時候,直接從連接池中獲取。
總結(jié)
????????DruidDataSource 博大精深,我只是分析了其中一些基本的功能,還有很多代碼沒有分析,但是本文的主要目的是為了讓大家了解?DataSource 是個啥玩意,我們現(xiàn)在應(yīng)該知道了它就是管理數(shù)據(jù)庫連接的,如果需要一個連接的話問他要就行了。
總結(jié)
以上是生活随笔為你收集整理的什么是 DataSource?什么又是 DruidDataSource?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 硅麦克风的声学设计指南_电脑麦克风入门指
- 下一篇: 一文看懂POE供电原理