Spring Boot JDBC:加載DataSource過程的源碼分析及yml中DataSource的配置


Spring Boot實現了自動加載DataSource及相關配置。當然,使用時加上@EnableAutoConfiguration注解是必須的。下面就是對這一部分的源碼分析。

(1)Spring Boot啟動后會調用org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration。下面是部分源碼。

 1 @Configuration
 2 @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
 3 @EnableConfigurationProperties(DataSourceProperties.class)
 4 @Import({ DataSourcePoolMetadataProvidersConfiguration.class,
 5         DataSourceInitializationConfiguration.class })
 6 public class DataSourceAutoConfiguration {
 7 
 8     @Configuration
 9     @Conditional(EmbeddedDatabaseCondition.class)
10     @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
11     @Import(EmbeddedDataSourceConfiguration.class)
12     protected static class EmbeddedDatabaseConfiguration {
13     }
14 
15     @Configuration
16     @Conditional(PooledDataSourceCondition.class)
17     @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
18     @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
19             DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
20             DataSourceJmxConfiguration.class })
21     protected static class PooledDataSourceConfiguration {
22     }
23     ......
24 }

我們從中可以看出,DataSourceAutoConfiguration中有兩個嵌套類,一個是EmbeddedDatabaseConfiguration,另一個是PooledDataSourceConfiguration

EmbeddedDatabaseConfiguration表示已經嵌入Spring Boot的DataSource,除了Maven中加入相應的Driver,可以不做其他額外配置就能使用。從EmbeddedDatabaseType類可以看出,Spring Boot的內嵌DataSource支持HSQL,H2,DERBY這三種DB。

PooledDataSourceConfiguration表示Spring Boot還支持一些實現Pool的DataSource。從org.springframework.boot.jdbc.DataSourceBuilder中可以看出,當前版本的Spring Boot(2.0)只支持com.zaxxer.hikari.HikariDataSource,org.apache.tomcat.jdbc.pool.DataSource,org.apache.commons.dbcp2.BasicDataSource。其中,性能更加優秀的HikariDataSource是Spring Boot的默認選擇(DataSourceBuilderDATA_SOURCE_TYPE_NAMES[0] = com.zaxxer.hikari.HikariDataSource)。所以,當application.yml文件中做如下配置時,Spring Boot默認使用HikariDataSource數據庫連接池。

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/sas
    username: root
    password: ****
    driver-class-name: com.mysql.jdbc.Driver
    #type: com.zaxxer.hikari.HikariDataSource

 

(2)我們以HikariDataSource舉例,接下來調用PooledDataSourceConfigurationorg.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration抽象類的Hikari嵌套類(DataSourceConfiguration抽象類的一個實現類)。

 1 abstract class DataSourceConfiguration {
 2 
 3     @SuppressWarnings("unchecked")
 4     protected <T> T createDataSource(DataSourceProperties properties,
 5             Class<? extends DataSource> type) {
 6         return (T) properties.initializeDataSourceBuilder().type(type).build();
 7     }
 8 
 9     /* Omit Tomcat Pool DataSource configuration.*/
10     /**
11      * Hikari DataSource configuration.
12      */
13     @ConditionalOnClass(HikariDataSource.class)
14     @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true)
15     static class Hikari extends DataSourceConfiguration {
16 
17         @Bean
18         @ConfigurationProperties(prefix = "spring.datasource.hikari") 19         public HikariDataSource dataSource(DataSourceProperties properties) { 20             HikariDataSource dataSource = createDataSource(properties,
21                     HikariDataSource.class);
22             if (StringUtils.hasText(properties.getName())) {
23                 dataSource.setPoolName(properties.getName());
24             }
25             return dataSource;
26         }
27     }
28     /* Omit DBCP DataSource configuration.*/
29 }

 我們從黃色部分可以看出,當application.yml文件中配置spring.datasource.type = com.zaxxer.hikari.HikariDataSource時,會使用HikariDataSource作為數據庫連接池(當然上面也分析了,它是默認選擇)。我們從綠色部分可以看出它的配置信息主要從兩個類中讀取,一個是org.springframework.boot.autoconfigure.jdbc.DataSourceProperties,另一個則是本類HikariDataSource的父類com.zaxxer.hikari.HikariConfig

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {}
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public HikariDataSource dataSource(DataSourceProperties properties) {}

我們從@ConfigurationProperties配置及兩個具體的類所包含的的域可以得出配置HikariDataSource信息。下面是例子。

spring:
  datasource:
    name: #Name of the datasource. Default to "testdb" when using an embedded database.
    driverClassName: #Fully qualified name of the JDBC driver. Auto-detected based on the URL by default.
    url: #DBC URL of the database.
    type: #Fully qualified name of the connection pool implementation to use. By default, it is auto-detected from the classpath.
    username: #Login username of the database.
    password: #Login password of the database.
    ## For more details please see DataSourceProperties.
    hikari:
      connectionTimeout:
      validationTimeout:
      maxPoolSize:
      minIdle:
      dataSourceProperties:
      ## For more details please see HikariConfig.

 

(3)當讀完配置后,則會通過HikariDataSource.getConnection()方法創建HikariPool對象。HikariPool及其父類PoolBase做了許多復雜的工作,包括創建Pool,創建Connection,讀取Config,驗證等等。調用HikariDataSource.getConnection()方法最終得到了這個Connection對象。這個過程中主要做了以下幾步:

① 創建HikariPool對象。

② 調用HikariPool對象的父類對象PoolBase的構造器,讀取HikariConfig配置信息配置PoolBase的屬性。

③ 調用PoolBase的構造器的initializeDataSource方法,利用com.zaxxer.hikari.util.DriverDataSource創建DataSource對象(這里主要指JDBC URL方式)。DriverDataSource中會把所有的DataSource信息封裝到driverProperties屬性中,這是為了適配java.sql.Driverconnect(String url, java.util.Properties info)方法。

 1 public final class DriverDataSource implements DataSource {
 2 
 3     private final String jdbcUrl;
 4     private final Properties driverProperties;
 5     private Driver driver;
 6 
 7     public DriverDataSource(String jdbcUrl, String driverClassName, Properties properties, String username, String password) {
 8         this.jdbcUrl = jdbcUrl;
 9         this.driverProperties = new Properties();
10         Iterator e = properties.entrySet().iterator();
11 
12         while(e.hasNext()) {
13             Entry driverClass = (Entry)e.next();
14             this.driverProperties.setProperty(driverClass.getKey().toString(), driverClass.getValue().toString());
15         }
16 
17         if(username != null) {
18             this.driverProperties.put("user", this.driverProperties.getProperty("user", username));
19         }
20 
21         if(password != null) {
22             this.driverProperties.put("password", this.driverProperties.getProperty("password", password));
23         }
24     ......
25     }
26 
27    @Override
28    public Connection getConnection() throws SQLException
29    {
30       return driver.connect(jdbcUrl, driverProperties);
31    }
32 }

④ 調用HikariPool對象的構造器,同樣也是配置一堆線程池信息。

⑤ 返回HikariPool.getConnection()。這個過程中,做了包含PoolBase.newPoolEntry()PoolBase.newConnection()的許多復雜方法。從PoolBase.newConnection()可以看出,最終還是調用的步驟③的getConnection()方法獲取到了這個Connection對象。

 1    private Connection newConnection() throws Exception
 2    {
 3       final long start = currentTime();
 4 
 5       Connection connection = null;
 6       try {
 7          String username = config.getUsername();
 8          String password = config.getPassword();
 9 
10          connection = (username == null) ? dataSource.getConnection() : dataSource.getConnection(username, password);
11          if (connection == null) {
12             throw new SQLTransientConnectionException("DataSource returned null unexpectedly");
13          }
14 
15          setupConnection(connection);
16          lastConnectionFailure.set(null);
17          return connection;
18       }
19       catch (Exception e) {
20          if (connection != null) {
21             quietlyCloseConnection(connection, "(Failed to create/setup connection)");
22          }
23          else if (getLastConnectionFailure() == null) {
24             LOGGER.debug("{} - Failed to create/setup connection: {}", poolName, e.getMessage());
25          }
26 
27          lastConnectionFailure.set(e);
28          throw e;
29       }
30       finally {
31          // tracker will be null during failFast check
32          if (metricsTracker != null) {
33             metricsTracker.recordConnectionCreated(elapsedMillis(start));
34          }
35       }
36    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM