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的默認選擇(DataSourceBuilder中DATA_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舉例,接下來調用PooledDataSourceConfiguration中org.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.Driver的connect(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 }