summary
DataSource is translated as a data source. It is an interface provided by jdk, and then only provides two getConnection methods, namely, no parameters and the user name and password that need to be passed in. Therefore, it is something related to database connection, which can be used to obtain database connection.
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; }
However, this is only an interface. How to obtain the database connection is determined by the subclass implementing it. This article is about DruidDataSource, why talk about it? Because it was written by Ali, it is more diao.
DruidDataSource
DruidDataSource is a data source written by Ali. It can not only obtain database connections, but also manage these database connections, that is, the so-called database connection pool. In this way, when a database connection is obtained through the data source, if there is a usable connection in the database connection pool, the connection will be returned directly, saving the need to create each database connection.
First, let's see how to use it DruidDataSource.
// Configure a DruidDataSource instance bean to be managed by the Spring container @Configuration public class DataSourceConfig { @Bean public 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"); //Minimum number of free connections in the connection pool dataSource.setMinIdle(5); //Maximum number of active connections in the connection pool dataSource.setMaxActive(20); //The number of connections created when initializing the connection pool dataSource.setInitialSize(10); return dataSource; } } //Test class public class DataSourceMain { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(DataSourceConfig.class); //Get dataSource instance DataSource dataSource = (DataSource) applicationContext.getBean("dataSource"); try { //Get database connection from data source Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement(); statement.execute("update AccountInfo set balance = balance + 1 where id = 1"); } catch (SQLException throwables) { throwables.printStackTrace(); } } }
It can be seen from the above that compared with jdbc, the instantiation of database connection Driver is omitted. In addition, when calling getConnection() to obtain the connection, the user name and password are not passed, indicating that the dataSource manages these configuration information. Later, the logic for obtaining connections will be self-sufficient.
To sum up, DruidDataSource manages some configuration information of database connection and helps us create a connection Driver. The rest of the logic is the same as jdbc. The management configuration information is nothing more than defining some variables and writing them in through the set method. Next, let's talk about when to create the Driver and how to get the database connection.
First enter Of DruidDataSource class getConnection() method
public DruidPooledConnection getConnection() throws SQLException { return getConnection(maxWait); } //This parameter represents the timeout time. If the connection is not obtained after this time, it means that obtaining the connection fails. public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException { //Initialization method init(); if (filters.size() > 0) { FilterChainImpl filterChain = new FilterChainImpl(this); return filterChain.dataSource_connect(this, maxWaitMillis); } else { return getConnectionDirect(maxWaitMillis); } }
There are two main methods here. The first is the initialization method and the second is the initialization method getConnectionDirect(maxWaitMillis). Obviously, the second one is to get the connection. What is the first one for?
public void init() throws SQLException { // Initialized controls that the initialization method is called only once. It will be set to true after the method. Calling the method again will return directly. if (inited) { return; } // bug fixed for dead lock, for issue #2980 //This line is to solve the deadlock problem, https://github.com/alibaba/druid/issues/2980 You can go to this connection for details 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(); //The driver is not initialized at this time 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"); } // This method will be called to get the driver instance 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); //Initializing the database connection pool is actually an array 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 // This is the number of connections initialized through configuration settings while (poolingCount < initialSize) { try { // Get database connection PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection(); DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo); //Put the obtained database connection into the connection pool, that is, the connections array 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(); //Create a thread responsible for creating a new database connection createAndStartCreatorThread(); createAndStartDestroyThread(); initedLatch.await(); init = true; initedTime = new Date(); registerMbean(); if (connectError != null && poolingCount == 0) { throw connectError; } if (keepAlive) { // async fill to minIdle if (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 { // Set inited to true and it will be returned directly when the init() method is called again. 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); } } }
There is a lot of code above, but let's focus on the main line code first. Obviously, there are several important points
- Get the driver instance driver = JdbcUtils.createDriver(driverClassLoader, driverClass);
- Get database connection PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();
- In addition, we also know that the so-called connection pool is an array that holds connection object instances.
First, let's look at how to get the 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 { //Obtaining an instance through reflection is actually obtaining a driver through jdbc return (Driver) clazz.newInstance(); } catch (IllegalAccessException e) { throw new SQLException(e.getMessage(), e); } catch (InstantiationException e) { throw new SQLException(e.getMessage(), e); } }
It can be seen that, just like when using jdbc, it obtains the driver instance through reflection, and then we'll see how to obtain a connection
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 { // At this time, the physical connect properties has assembled the configuration information and obtained the url conn = 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) { // Here is to get the driver we just instantiated, and then call its connect method conn = getDriver().connect(url, info); } else { conn = new FilterChainImpl(this).connection_connect(info); } createCountUpdater.incrementAndGet(this); return conn; }
As can be seen from the above, to obtain a database connection, you must first obtain the driver, and then call its connect method.
Generally speaking, the main logic of the above code is to instantiate the driver first, and then obtain a database connection through the driver. This is actually the way of jdbc, but don't let us do it for us. We don't have to do it ourselves. And the DruidDataSource will save the connection after obtaining the connection, so as not to obtain a connection from the database again every time a connection is requested.
In the previous part, we talked about the initialization method init(). Don't forget that we are talking about DruidDataSource obtains the database connection method, as follows
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException { // After the initialization method is executed, there is already a database connection in the connection pool init(); if (filters.size() > 0) { FilterChainImpl filterChain = new FilterChainImpl(this); return filterChain.dataSource_connect(this, maxWaitMillis); } else { return getConnectionDirect(maxWaitMillis); } }
We need to take a look How does getConnectionDirect(maxWaitMillis) get a connection
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException { int notFullTimeoutRetryCnt = 0; for (;;) { // handle notFullTimeoutRetry DruidPooledConnection poolableConnection; try { //Obviously, this method is to obtain the database connection. The rest of the code can skip to getConnectionInternal without looking at it poolableConnection = getConnectionInternal(maxWaitMillis); } catch (GetConnectionTimeoutException ex) { if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) { notFullTimeoutRetryCnt++; if (LOG.isWarnEnabled()) { LOG.warn("get connection timeout retry : " + notFullTimeoutRetryCnt); } continue; } throw ex; } . . . . . . . . . . . //Omit the following code
private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException { . . . . . . . . . . //Omit some of the code that precedes this method // Wait timeout. The default is - 1 if (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; }
We can see in the code that pollLast and takeLast obtain database connections. These two methods are similar. pollLast() will add time judgment, but the core logic is the same, so let's follow the default takeLast method
DruidConnectionHolder takeLast() throws InterruptedException, SQLException { try { //If the initialization size of the connection pool is not set, a new connection will be created and put into the connection pool. If the initialization size is set, some connections will be created and saved in the connection pool when calling the init() method. while (poolingCount == 0) { emptySignal(); // send signal to CreateThread create connection if (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 thread notEmptySignalCount++; throw ie; } //Reduce the number of connections in the database connection pool by one decrementPoolingCount(); //Gets a connection from the database connection pool DruidConnectionHolder last = connections[poolingCount]; connections[poolingCount] = null; return last; }
Through the above analysis, we should know that when calling Connection connection = dataSource.getConnection(), if it is the first call, some connections will be created in the init() method and put into the database connection pool, and then directly obtained from the connection pool when obtaining the connection.
summary
DruidDataSource is broad and profound. I only analyzed some of the basic functions. There are still many codes that have not been analyzed, but the main purpose of this article is to let you know What is DataSource? We should know now that it manages database connections. If you need a connection, just ask him for it.