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 @Configuration16 @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 @Bean18 @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 @Override28 public Connection getConnection() throws SQLException29 {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 check32 if (metricsTracker != null) {33 metricsTracker.recordConnectionCreated(elapsedMillis(start));34 }35 }36 }