场景:当我们没有配置mysql,postgresql等数据源的时候,pom.xml里面引入了H2/DERBY/HSQL jar包,也没有配置连接,却有数据源创建的情况。
springboot启动的第一步
1.DataSourceAutoConfiguration 配置类启动
2.DataSourceProperties 配置类自动探测datasource的配置项
3.EmbeddedDatabaseConnection 做检测,自动生成数据库连接
4.DataSourceProperties.initializeDataSourceBuilder 创建数据源连接
5.DataSourceConfiguration 根据不同的数据源来创建连接池
1. 数据源配置启动类
找到 spring-boot-autoconfigure-2.5.x.jar 这个jar包,里面的META-INF/spring.factories 文件,有各个自动化配置的入口,数据库入口查找org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
这个类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
...
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,
...
2. DataSourceAutoConfiguration类核心代码
数据库自动数据库连接池配置的前提是,项目中没有别的模块创建了DataSource ( @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
这句代码就是做限制的)
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.boot.autoconfigure.jdbc;
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@ConditionalOnMissingBean(
type = {"io.r2dbc.spi.ConnectionFactory"}
)
//导入DataSourceProperties 的配置 ,实际数据库参数都是在这个类做的
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, InitializationSpecificCredentialsDataSourceInitializationConfiguration.class, SharedCredentialsDataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {
public DataSourceAutoConfiguration() {
}
//.... 省略代码
//当没有手动创建DataSource的情况,就会走默认的数据源连接池
//目前支持的数据源连接池
//Hikari,Tomcat,Dbcp2,OracleUcp,Generic,DataSourceJmxConfiguration
@Configuration(
proxyBeanMethods = false
)
@Conditional({DataSourceAutoConfiguration.PooledDataSourceCondition.class})
@ConditionalOnMissingBean({DataSource.class, XADataSource.class})
@Import({Hikari.class, Tomcat.class, Dbcp2.class, OracleUcp.class, Generic.class, DataSourceJmxConfiguration.class})
protected static class PooledDataSourceConfiguration {
protected PooledDataSourceConfiguration() {
}
}
@Configuration(
proxyBeanMethods = false
)
@Conditional({DataSourceAutoConfiguration.EmbeddedDatabaseCondition.class})
@ConditionalOnMissingBean({DataSource.class, XADataSource.class})
@Import({EmbeddedDataSourceConfiguration.class})
protected static class EmbeddedDatabaseConfiguration {
protected EmbeddedDatabaseConfiguration() {
}
}
//.... 省略代码
}
3. DataSourceProperties 数据源配置
注入配置的数据库连接池配置项,然后设定对应的EmbeddedDatabaseConnection ,其中initializeDataSourceBuilder的调用方是DataSourceConfiguration.createDataSource ,在最后创建数据库连接池的时候调用。
数据库探测规则:1.优先使用用户配置的(spring.datasource.url)url选项,2.如果有户没有配置,就会使用EmbeddedDatabaseConnection 来探测,默认会扫描jar(H2/DERBY/HSQL)包,取第一个为默认值。
@ConfigurationProperties(
prefix = "spring.datasource"
)
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
//.... 省略代码
//设置 EmbeddedDatabaseConnection的检查类
public void afterPropertiesSet() throws Exception {
if (this.embeddedDatabaseConnection == null) {
this.embeddedDatabaseConnection = EmbeddedDatabaseConnection.get(this.classLoader);
}
}
// 创建连接池,调用了this.determineDriverClassName 探测数据库连接地址
public DataSourceBuilder<?> initializeDataSourceBuilder() {
return DataSourceBuilder.create(this.getClassLoader()).type(this.getType()).driverClassName(this.determineDriverClassName()).url(this.determineUrl()).username(this.determineUsername()).password(this.determinePassword());
}
//探测连接地址,默认会根据url进行分析探测,如果没有的情况,就会走EmbeddedDatabaseConnection 来获取内置数据库
public String determineDriverClassName() {
if (StringUtils.hasText(this.driverClassName)) {
Assert.state(this.driverClassIsLoadable(), () -> {
return "Cannot load driver class: " + this.driverClassName;
});
return this.driverClassName;
} else {
String driverClassName = null;
if (StringUtils.hasText(this.url)) {
driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
}
if (!StringUtils.hasText(driverClassName)) {
driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
}
if (!StringUtils.hasText(driverClassName)) {
throw new DataSourceProperties.DataSourceBeanCreationException("Failed to determine a suitable driver class", this, this.embeddedDatabaseConnection);
} else {
return driverClassName;
}
}
}
//.... 省略代码
}
4. EmbeddedDatabaseConnection 内置数据库探测
通过EmbeddedDatabaseConnection ,来探测数据库信息,会根据大家有没有(h2/derby/hsql)这些jar包,来确认是。
public enum EmbeddedDatabaseConnection {
NONE((EmbeddedDatabaseType)null, (String)null, (String)null, (url) -> {
return false;
}),
H2(EmbeddedDatabaseType.H2, DatabaseDriver.H2.getDriverClassName(), "jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE", (url) -> {
return url.contains(":h2:mem");
}),
DERBY(EmbeddedDatabaseType.DERBY, DatabaseDriver.DERBY.getDriverClassName(), "jdbc:derby:memory:%s;create=true", (url) -> {
return true;
}),
/** @deprecated */
@Deprecated
HSQL(EmbeddedDatabaseType.HSQL, DatabaseDriver.HSQLDB.getDriverClassName(), "org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:%s", (url) -> {
return url.contains(":hsqldb:mem:");
}),
HSQLDB(EmbeddedDatabaseType.HSQL, DatabaseDriver.HSQLDB.getDriverClassName(), "org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:%s", (url) -> {
return url.contains(":hsqldb:mem:");
});
//查找H2, HSQLDB, DERBY 中的driverClass 第一个
private static EmbeddedDatabaseConnection getEmbeddedDatabaseConnection(String driverClass) {
return (EmbeddedDatabaseConnection)Stream.of(H2, HSQLDB, DERBY).filter((connection) -> {
return connection.isDriverCompatible(driverClass);
}).findFirst().orElse(NONE);
}
//.... 省略代码
}
5. DataSourceConfiguration 数据库连接池配置类
里面包含了多种数据库连接池Hikari,Tomcat,Dbcp2,OracleUcp,Generic,DataSourceJmxConfiguration ,以Hikari举例 说明。
上面DataSourceAutoConfiguration 类中的PooledDataSourceConfiguration 配置内部类,有个注解@Import({Hikari.class, Tomcat.class, Dbcp2.class, OracleUcp.class, Generic.class, DataSourceJmxConfiguration.class}) , 就会使用DataSourceConfiguration 中的数据库连接池配置信息。
abstract class DataSourceConfiguration {
//.... 省略代码
//创建数据库连接池的核心方法
protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {
return properties.initializeDataSourceBuilder().type(type).build();
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({HikariDataSource.class})
@ConditionalOnMissingBean({DataSource.class})
@ConditionalOnProperty(
name = {"spring.datasource.type"},
havingValue = "com.zaxxer.hikari.HikariDataSource",
matchIfMissing = true
)
static class Hikari {
Hikari() {
}
@Bean
@ConfigurationProperties(
prefix = "spring.datasource.hikari"
)
HikariDataSource dataSource(DataSourceProperties properties) {
HikariDataSource dataSource = (HikariDataSource)DataSourceConfiguration.createDataSource(properties, HikariDataSource.class);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
}
//.... 省略代码
}
附录
数据库测试连接配置
---
spring:
profiles:
active: db-h2
#应用唯一标识
application:
name: BITSFLOW-CONSOLE
datasource:
type: com.zaxxer.hikari.HikariDataSource
hikari:
maximum-pool-size: 20
connection-timeout: 300000
max-life-time: 1800000
idle-timeout: 0
pool-name: "CONN-POOL"
---
spring:
h2:
console:
enabled: true
path: /h2-console
settings:
trace: false
web-allow-others: true
config:
activate:
on-profile: db-h2
datasource:
url: jdbc:h2:mem:yc_test;MODE=MySql;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1
username: sa
password:
driver-class-name: org.h2.Driver
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.H2Dialect
---
spring:
config:
activate:
on-profile: db-postgresql
datasource:
url: jdbc:postgresql://127.0.0.1:5432/yc_test
username: postgres
password: demo
driver-class-name: org.postgresql.Driver
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
---
spring:
config:
activate:
on-profile: db-mysql
datasource:
url: jdbc:mysql://127.0.0.1:3306/yc_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&CreateDatabaseIfNotExists=true&allowMultiQueries=true
username: root
password: 'root'
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.MySQLDialect