文章目录
问题引入
在刚开始学习springboot的时候,当你在依赖中添加了spring-boot-starter-jdbc
的依赖,但是又未配置数据源的相关信息
时,会遇到下面的启动报错信息,那么为了了解这个错误产生的原因,和完整的了解Springboot配置数据源的流程,或者说对后面理解项目配置多数据源,也有很大帮助,因此,有必要梳理这一块的源码。
***************************
APPLICATION FAILED TO START
***************************
Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
Action:
Consider the following:
If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
DataSourceAutoConfiguration
在spring-boot-autoconfigure:2.2.1.RELEASE的jar包中的META-INF中的spring.factories文件中配置了DataSourceAutoConfiguration
...
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
...
DataSourceAutoConfiguration代码如下
@Configuration(proxyBeanMethods = false)
// 必须同时存在java.sql.DataSource接口 和
// org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType类(spring-jdbc包中的类),
// 此配置类才会生效
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
// 开启数据源属性配置,配置如:spring.datasource.xxx
@EnableConfigurationProperties(DataSourceProperties.class)
// 导入了下面2个配置类,
// DataSourcePoolMetadataProvidersConfiguration
// - 为了配置数据源对应的DataSourcePoolMetadataProvider组件实例
// DataSourceInitializationConfiguration
// - 用于执行一些数据库初始化脚本(执行sql),如DML,DDL
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class
})
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
// 须满足内置数据源的条件
// 1. 用户没有指定spring.datasource.type的配置
// 2. 项目中存在com.zaxxer.hikari.HikariDataSource、
// org.apache.tomcat.jdbc.pool.DataSource、
// org.apache.commons.dbcp2.BasicDataSource
// 中其中任何一个类时(只要导入相关依赖即可)
// 注意: 当引入了spring-boot-starter-jdbc时,会传递依赖 HikariCP,
// 此时HikariDataSource会作为默认的数据源实现
@Conditional(EmbeddedDatabaseCondition.class)
// 用户没有配置数据源实例才会生效(优先用户配置,意思是:如果用户在项目中定义了数据源组件,那这个配置就不生效了)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
// 当以上条件都满足了,下面导入的这个配置类才会起作用
// 导入的这个配置类,仅配置了EmbeddedDatabase(它继承了DataSource接口,作为内嵌数据源)
// 默认会依次尝试加载枚举类EmbeddedDatabaseConnection中定义的H2、DERBY、HSQL内嵌数据库的驱动类
// 第一个加载成功的,将会创建对应的内嵌数据源
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {
}
@Configuration(proxyBeanMethods = false)
// 池化数据源生效条件
// 1. 用户精确的指定了spring.datasource.type的配置
// 2. 用户导入了com.zaxxer.hikari.HikariDataSource、
// org.apache.tomcat.jdbc.pool.DataSource、
// org.apache.commons.dbcp2.BasicDataSource
// 其中任何一个依赖(这些类是以常量的方式定义在了DataSourceBuilder类中)
// 注意: 当引入了spring-boot-starter-jdbc时,会传递依赖 HikariCP,
@Conditional(PooledDataSourceCondition.class)
// 用户没有配置数据源实例才会生效(优先用户配置,意思是:如果用户在项目中定义了数据源组件,那这个配置就不生效了)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
// springboot真正的数据源组件其实是定义在了 DataSourceConfiguration 中(如下4个),因此这个类是个重点
// 关于jmx,可以参考: https://blog.csdn.net/qq_27870421/article/details/100110995
@Import({ DataSourceConfiguration.Hikari.class,
DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class,
DataSourceConfiguration.Generic.class,
DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {
}
// 池化数据源的生效条件,所定义的条件中,有任何一个条件生效,即可生效
static class PooledDataSourceCondition extends AnyNestedCondition {
PooledDataSourceCondition() {
// 在配置类的解析阶段检查
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
// 用户精确配置了 spring.datasource.type属性
@ConditionalOnProperty(prefix = "spring.datasource", name = "type")
static class ExplicitType {
}
// 会依次尝试加载DataSourceBuilder中,预定义的这些类,如果加载到了,即生效
// com.zaxxer.hikari.HikariDataSource、
// org.apache.tomcat.jdbc.pool.DataSource、
// org.apache.commons.dbcp2.BasicDataSource
@Conditional(PooledDataSourceAvailableCondition.class)
static class PooledDataSourceAvailable {
}
}
// 池化数据源可用的生效条件
static class PooledDataSourceAvailableCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("PooledDataSource");
// 使用反射,尝试加载HikariDataSource、tomcat数据源、dbcp2数据源类,
if (DataSourceBuilder.findType(context.getClassLoader()) != null) {
// 加载到了则不为null,即满足条件
return ConditionOutcome.match(message.foundExactly("supported DataSource"));
}
// 一个都加载不到,不满足条件
return ConditionOutcome.noMatch(message.didNotFind("supported DataSource").atAll());
}
}
// 内嵌数据源生效条件
static class EmbeddedDatabaseCondition extends SpringBootCondition {
// 将池化数据源生效条件作为属性
private final SpringBootCondition pooledCondition = new PooledDataSourceCondition();
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("EmbeddedDataSource");
// 如果池化数据源生效条件满足,那内嵌数据源就不生效(意思是:池化数据源优先)
if (anyMatches(context, metadata, this.pooledCondition)) {
return ConditionOutcome.noMatch(message.foundExactly("supported pooled data source"));
}
// 走到这里,那必然池化数据源生效条件不满足
// 此时,再去尝试加载定义在 枚举类EmbeddedDatabaseConnection中定义的内嵌数据源驱动类
// 包括:org.h2.Driver、org.apache.derby.jdbc.EmbeddedDriver、org.hsqldb.jdbcDriver
EmbeddedDatabaseType type = EmbeddedDatabaseConnection.get(context.getClassLoader()).getType();
// 内嵌数据源驱动类,一个都没加载到,也返回不匹配
if (type == null) {
return ConditionOutcome.noMatch(message.didNotFind("embedded database").atAll());
}
// 加载到任何一个内嵌数据源驱动类,则匹配
return ConditionOutcome.match(message.found("embedded database").items(type));
}
}
}
1. DataSourceProperties
先熟悉下springboot给我们提供的数据源配置项有哪些,即以spring.datasource.xxx
为前缀的配置
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
// 用户注入容器的类加载器
private ClassLoader classLoader;
// 数据源的名称
private String name;
// 是否要随机生成一个数据源名称
private boolean generateUniqueName;
// 用于精确指定用户要使用的数据源类型的全类名
private Class<? extends DataSource> type;
// 数据源驱动的全类名
private String driverClassName;
// 数据库连接地址
private String url;
// 数据库用户名
private String username;
// 数据库密码
private String password;
// jndi名字,可参考:https://www.cnblogs.com/libin6505/p/11226237.html
private String jndiName;
// 是否在数据源初始化后,执行配置的sql脚本
private DataSourceInitializationMode initializationMode = DataSourceInitializationMode.EMBEDDED;
// 指定要执行的脚本,schema-${platform}.sql 和 data-${platform}.sql
private String platform = "all";
// DDL脚本
private List<String> schema;
// 执行DDL脚本所使用的用户名(如果不同于之前的用户名的话)
private String schemaUsername;
// 执行DDL脚本所使用的密码(如果不同于之前的密码的话)
private String schemaPassword;
// DML脚本
private List<String> data;
// 执行DML脚本所使用的用户名(如果不同于之前的用户名的话)
private String dataUsername;
// 执行DML脚本所使用的密码(如果不同于之前的密码的话)
private String dataPassword;
// 当初始化sql脚本发生错误时,是否继续
private boolean continueOnError = false;
// sql初始化脚本分隔符
private String separator = ";";
// sql初始化脚字符本编码格式
private Charset sqlScriptEncoding;
// 指定内嵌数据源连接的类型
// (EmbeddedDatabaseConnection 是个枚举类,定义了H2、DERBY、HSQL)
private EmbeddedDatabaseConnection embeddedDatabaseConnection = EmbeddedDatabaseConnection.NONE;
// XA全局事务相关配置
private Xa xa = new Xa();
// 唯一数据源名称
private String uniqueName;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void afterPropertiesSet() throws Exception {
// 依次尝试加载 H2、DERBY、HSQL的驱动,返回第一个成功加载的驱动
// 那如果我指定了内嵌数据源类型,但是afterPropertiesSet是后面执行的,那这个值就被这里给覆盖了
this.embeddedDatabaseConnection = EmbeddedDatabaseConnection.get(this.classLoader);
}
// 这里使用数据源属性配置类DataSourceProperties,构建DataSourceBuilder
// 将会使用DataSourceBuilder来构建数据源
public DataSourceBuilder<?> initializeDataSourceBuilder() {
return DataSourceBuilder
.create(getClassLoader())
.type(
// 即: spring.datasource.type的配置
getType()
)
.driverClassName(
// 确定驱动类
// 1. 如果有设置spring.datasource.driver-class-name,直接返回
// 2. 如果上面还不能确定,并且如果有设置spring.datasource.url,这个url必须以jdbc开头(否则报错),
// 然后从该url上截取,从DatabaseDriver中的常用驱动枚举类获取对应的驱动
// 3. 如果上面还不能确定,则使用embeddedDatabaseConnection内嵌数据源连接的指定的驱动类
// 4. 现在还不能确定的话,就直接抛异常了
determineDriverClassName()
)
.url(
// 确定数据库连接地址
// 1. 如果有设置spring.datasource.url,直接返回
// 2. 如果上面还不能确定, 则须先确定数据库名称,即spring.datasource.name配置或默认的testdb
// 然后使用embeddedDatabaseConnection.getUrl(数据库名称)获取连接地址
// 3. 现在还不能确定的话,就直接抛异常了
determineUrl()
)
.username(
// 确定用户名
// 1. 如果有设置spring.datasource.username,直接返回
// 2. 如果上面确定的驱动类是HSQL或H2或DERBY中的任何一个,则返回“sa”
// 3. 以上不能确定的话,返回null
determineUsername()
)
.password(
// 确定密码
// 1. 如果有设置spring.datasource.password,直接返回
// 2. 如果上面确定的驱动类是HSQL或H2或DERBY中的任何一个,则返回空字符串
// 3. 以上不能确定的话,返回null
determinePassword()
);
}
// 确定驱动类(最开始提到的报错,就出自这里)
// DataSourceConfiguration中定义的3个组件都是会调用到这个方法
public String determineDriverClassName() {
// 如果设置了spring.datasource.driverClassName,尝试加载这个类,并返回
if (StringUtils.hasText(this.driverClassName)) {
Assert.state(driverClassIsLoadable(), () -> "Cannot load driver class: " + this.driverClassName);
return this.driverClassName;
}
String driverClassName = null;
// 如果有设置spring.datasource.url, 则从DatabaseDriver枚举值中查找
if (StringUtils.hasText(this.url)) {
driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
}
// 如果上面还不能确定, 则使用embeddedDatabaseConnection获取驱动类(这个时候,就要使用内嵌数据源了)
if (!StringUtils.hasText(driverClassName)) {
driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
}
// 如果此时还不能确定驱动类,就要抛异常了(就是最开始提到的报错)
if (!StringUtils.hasText(driverClassName)) {
throw new DataSourceBeanCreationException("Failed to determine a suitable driver class", this,this.embeddedDatabaseConnection);
}
return driverClassName;
}
// 尝试反射加载驱动类
private boolean driverClassIsLoadable() {
try {
ClassUtils.forName(this.driverClassName, null);
return true;
}
catch (UnsupportedClassVersionError ex) {
// Driver library has been compiled with a later JDK, propagate error
throw ex;
}
catch (Throwable ex) {
return false;
}
}
// 确定数据库连接地址
public String determineUrl() {
if (StringUtils.hasText(this.url)) {
return this.url;
}
String databaseName = determineDatabaseName();
String url = (databaseName != null) ? this.embeddedDatabaseConnection.getUrl(databaseName) : null;
if (!StringUtils.hasText(url)) {
throw new DataSourceBeanCreationException("Failed to determine suitable jdbc url", this,
this.embeddedDatabaseConnection);
}
return url;
}
// 确定数据库名称
public String determineDatabaseName() {
if (this.generateUniqueName) {
if (this.uniqueName == null) {
this.uniqueName = UUID.randomUUID().toString();
}
return this.uniqueName;
}
if (StringUtils.hasLength(this.name)) {
return this.name;
}
if (this.embeddedDatabaseConnection != EmbeddedDatabaseConnection.NONE) {
return "testdb";
}
return null;
}
// 确定数据库用户名
public String determineUsername() {
if (StringUtils.hasText(this.username)) {
return this.username;
}
if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName())) {
return "sa";
}
return null;
}
// 确定数据库密码
public String determinePassword() {
if (StringUtils.hasText(this.password)) {
return this.password;
}
if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName())) {
return "";
}
return null;
}
// XA全局事务配置
public static class Xa {
// XA数据源全限定名
private String dataSourceClassName;
// XA数据源配置
private Map<String, String> properties = new LinkedHashMap<>();
}
}
DataSourceBuilder
public final class DataSourceBuilder<T extends DataSource> {
// 预定义的数据源类型
private static final String[] DATA_SOURCE_TYPE_NAMES
= new String[] {
"com.zaxxer.hikari.HikariDataSource",
"org.apache.tomcat.jdbc.pool.DataSource",
"org.apache.commons.dbcp2.BasicDataSource"
};
// 数据源类型
private Class<? extends DataSource> type;
private ClassLoader classLoader;
// 数据源属性
private Map<String, String> properties = new HashMap<>();
public static DataSourceBuilder<?> create() {
return new DataSourceBuilder<>(null);
}
public static DataSourceBuilder<?> create(ClassLoader classLoader) {
return new DataSourceBuilder<>(classLoader);
}
private DataSourceBuilder(ClassLoader classLoader) {
this.classLoader = classLoader;
}
// 构建数据源的方法
public T build() {
// 获取具体的数据源类型
Class<? extends DataSource> type = getType();
// 使用反射创建数据源对象
DataSource result = BeanUtils.instantiateClass(type);
// 如果properties属性中没有指定driverClassName驱动类名,
// 则从数据库连接地址url中,从DatabaseDriver预定义的枚举中,解析驱动类名
maybeGetDriverClassName();
// 将属性值绑定到数据源中
bind(result);
// 返回数据源
return (T) result;
}
// 尝试确定数据源驱动类
private void maybeGetDriverClassName() {
if (!this.properties.containsKey("driverClassName") && this.properties.containsKey("url")) {
String url = this.properties.get("url");
String driverClass = DatabaseDriver.fromJdbcUrl(url).getDriverClassName();
this.properties.put("driverClassName", driverClass);
}
}
// 绑定属性的方法(具体的绑定细节和使用方法,后面可以找些参考资料看下)
private void bind(DataSource result) {
// 配置属性PropertySource
ConfigurationPropertySource source = new MapConfigurationPropertySource(this.properties);
// 配置别名
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
aliases.addAliases("url", "jdbc-url");
aliases.addAliases("username", "user");
// 创建绑定器,并传入配置属性PropertySource
Binder binder = new Binder(source.withAliases(aliases));
// 将绑定器中设置的属性,绑定到目标对象中
binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
}
// 设置type
public <D extends DataSource> DataSourceBuilder<D> type(Class<D> type) {
this.type = type;
return (DataSourceBuilder<D>) this;
}
// 设置url
public DataSourceBuilder<T> url(String url) {
this.properties.put("url", url);
return this;
}
// 设置driverClassName
public DataSourceBuilder<T> driverClassName(String driverClassName) {
this.properties.put("driverClassName", driverClassName);
return this;
}
// 设置username
public DataSourceBuilder<T> username(String username) {
this.properties.put("username", username);
return this;
}
// 设置password
public DataSourceBuilder<T> password(String password) {
this.properties.put("password", password);
return this;
}
// 尝试使用反射加载预定义的数据源类
public static Class<? extends DataSource> findType(ClassLoader classLoader) {
for (String name : DATA_SOURCE_TYPE_NAMES) {
try {
return (Class<? extends DataSource>) ClassUtils.forName(name, classLoader);
}
catch (Exception ex) {
// Swallow and continue
}
}
return null;
}
// 获取type
private Class<? extends DataSource> getType() {
Class<? extends DataSource> type = (this.type != null) ? this.type : findType(this.classLoader);
if (type != null) {
return type;
}
throw new IllegalStateException("No supported DataSource type found");
}
}
EmbeddedDatabaseConnection
EmbeddedDatabaseConnection这个类中所使用的EmbeddedDatabaseType是来自spring-jdbc的依赖
,它里面预定义了内置数据源类型相关的
public enum EmbeddedDatabaseConnection {
// 无连接
NONE(null, null, null),
// H2连接
H2(EmbeddedDatabaseType.H2, "org.h2.Driver", "jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"),
// derby连接
DERBY(EmbeddedDatabaseType.DERBY, "org.apache.derby.jdbc.EmbeddedDriver", "jdbc:derby:memory:%s;create=true"),
// hsql连接
HSQL(EmbeddedDatabaseType.HSQL, "org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:%s");
// 内嵌数据源类型(只是预定义了3个)
private final EmbeddedDatabaseType type;
// 数据源驱动
private final String driverClass;
// 数据库连接地址
private final String url;
EmbeddedDatabaseConnection(EmbeddedDatabaseType type, String driverClass, String url) {
this.type = type;
this.driverClass = driverClass;
this.url = url;
}
public String getDriverClassName() {
return this.driverClass;
}
public EmbeddedDatabaseType getType() {
return this.type;
}
// 填充数据源地址
public String getUrl(String databaseName) {
Assert.hasText(databaseName, "DatabaseName must not be empty");
return (this.url != null) ? String.format(this.url, databaseName) : null;
}
// 确定驱动类是否为一个内嵌数据源驱动类
public static boolean isEmbedded(String driverClass) {
return driverClass != null
&& (driverClass.equals(HSQL.driverClass)
|| driverClass.equals(H2.driverClass)
|| driverClass.equals(DERBY.driverClass)
);
}
// 确定给定的数据源是否为一个内嵌数据源
public static boolean isEmbedded(DataSource dataSource) {
try {
return new JdbcTemplate(dataSource).execute(new IsEmbedded());
}
catch (DataAccessException ex) {
// Could not connect, which means it's not embedded
return false;
}
}
// 按顺序依次加载对应的驱动类,first-win
public static EmbeddedDatabaseConnection get(ClassLoader classLoader) {
for (EmbeddedDatabaseConnection candidate : EmbeddedDatabaseConnection.values()) {
if (candidate != NONE && ClassUtils.isPresent(candidate.getDriverClassName(), classLoader)) {
return candidate;
}
}
return NONE;
}
private static class IsEmbedded implements ConnectionCallback<Boolean> {
@Override
public Boolean doInConnection(Connection connection) throws SQLException, DataAccessException {
// 从连接中获取数据源产品名称
String productName = connection.getMetaData().getDatabaseProductName();
if (productName == null) {
return false;
}
productName = productName.toUpperCase(Locale.ENGLISH);
EmbeddedDatabaseConnection[] candidates = EmbeddedDatabaseConnection.values();
for (EmbeddedDatabaseConnection candidate : candidates) {
//如果数据源名称中包含预定义的内嵌数据源连接的名字,则返回true,证明是内嵌数据源
if (candidate != NONE && productName.contains(candidate.name())) {
return true;
}
}
return false;
}
}
}
DatabaseDriver
预定义的数据源驱动枚举
public enum DatabaseDriver {
// 未知类型
UNKNOWN(null, null),
DERBY("Apache Derby", "org.apache.derby.jdbc.EmbeddedDriver", "org.apache.derby.jdbc.EmbeddedXADataSource",
"SELECT 1 FROM SYSIBM.SYSDUMMY1"),
H2("H2", "org.h2.Driver", "org.h2.jdbcx.JdbcDataSource", "SELECT 1"),
HSQLDB("HSQL Database Engine", "org.hsqldb.jdbc.JDBCDriver", "org.hsqldb.jdbc.pool.JDBCXADataSource",
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.SYSTEM_USERS"),
SQLITE("SQLite", "org.sqlite.JDBC"),
MYSQL("MySQL", "com.mysql.cj.jdbc.Driver", "com.mysql.cj.jdbc.MysqlXADataSource", "/* ping */ SELECT 1"),
MARIADB("MySQL", "org.mariadb.jdbc.Driver", "org.mariadb.jdbc.MariaDbDataSource", "SELECT 1") {
@Override
public String getId() {
return "mysql";
}
},
GAE(null, "com.google.appengine.api.rdbms.AppEngineDriver"),
ORACLE("Oracle", "oracle.jdbc.OracleDriver", "oracle.jdbc.xa.client.OracleXADataSource",
"SELECT 'Hello' from DUAL"),
POSTGRESQL("PostgreSQL", "org.postgresql.Driver", "org.postgresql.xa.PGXADataSource", "SELECT 1"),
REDSHIFT("Amazon Redshift", "com.amazon.redshift.jdbc.Driver", null, "SELECT 1"),
HANA("HDB", "com.sap.db.jdbc.Driver", "com.sap.db.jdbcext.XADataSourceSAP", "SELECT 1 FROM SYS.DUMMY") {
@Override
protected Collection<String> getUrlPrefixes() {
return Collections.singleton("sap");
}
},
JTDS(null, "net.sourceforge.jtds.jdbc.Driver"),
SQLSERVER("Microsoft SQL Server", "com.microsoft.sqlserver.jdbc.SQLServerDriver",
"com.microsoft.sqlserver.jdbc.SQLServerXADataSource", "SELECT 1") {
@Override
protected boolean matchProductName(String productName) {
return super.matchProductName(productName) || "SQL SERVER".equalsIgnoreCase(productName);
}
},
FIREBIRD("Firebird", "org.firebirdsql.jdbc.FBDriver", "org.firebirdsql.ds.FBXADataSource",
"SELECT 1 FROM RDB$DATABASE") {
@Override
protected Collection<String> getUrlPrefixes() {
return Arrays.asList("firebirdsql", "firebird");
}
@Override
protected boolean matchProductName(String productName) {
return super.matchProductName(productName)
|| productName.toLowerCase(Locale.ENGLISH).startsWith("firebird");
}
},
DB2("DB2", "com.ibm.db2.jcc.DB2Driver", "com.ibm.db2.jcc.DB2XADataSource", "SELECT 1 FROM SYSIBM.SYSDUMMY1") {
@Override
protected boolean matchProductName(String productName) {
return super.matchProductName(productName) || productName.toLowerCase(Locale.ENGLISH).startsWith("db2/");
}
},
DB2_AS400("DB2 UDB for AS/400", "com.ibm.as400.access.AS400JDBCDriver",
"com.ibm.as400.access.AS400JDBCXADataSource", "SELECT 1 FROM SYSIBM.SYSDUMMY1") {
@Override
public String getId() {
return "db2";
}
@Override
protected Collection<String> getUrlPrefixes() {
return Collections.singleton("as400");
}
@Override
protected boolean matchProductName(String productName) {
return super.matchProductName(productName) || productName.toLowerCase(Locale.ENGLISH).contains("as/400");
}
},
TERADATA("Teradata", "com.teradata.jdbc.TeraDriver"),
INFORMIX("Informix Dynamic Server", "com.informix.jdbc.IfxDriver", null, "select count(*) from systables") {
@Override
protected Collection<String> getUrlPrefixes() {
return Arrays.asList("informix-sqli", "informix-direct");
}
};
private final String productName;
private final String driverClassName;
private final String xaDataSourceClassName;
private final String validationQuery;
DatabaseDriver(String productName, String driverClassName) {
this(productName, driverClassName, null);
}
DatabaseDriver(String productName, String driverClassName, String xaDataSourceClassName) {
this(productName, driverClassName, xaDataSourceClassName, null);
}
DatabaseDriver(String productName, String driverClassName, String xaDataSourceClassName, String validationQuery) {
this.productName = productName;
this.driverClassName = driverClassName;
this.xaDataSourceClassName = xaDataSourceClassName;
this.validationQuery = validationQuery;
}
public String getId() {
return name().toLowerCase(Locale.ENGLISH);
}
protected boolean matchProductName(String productName) {
return this.productName != null && this.productName.equalsIgnoreCase(productName);
}
protected Collection<String> getUrlPrefixes() {
return Collections.singleton(this.name().toLowerCase(Locale.ENGLISH));
}
// 根据给定的url数据库连接地址,确定驱动
public static DatabaseDriver fromJdbcUrl(String url) {
if (StringUtils.hasLength(url)) {
// 必须以jdbc开头
Assert.isTrue(url.startsWith("jdbc"), "URL must start with 'jdbc'");
// 截掉前面的jdbc
String urlWithoutPrefix = url.substring("jdbc".length()).toLowerCase(Locale.ENGLISH);
// 遍历当前类所有的驱动枚举值
for (DatabaseDriver driver : values()) {
// urlPrefixes默认只返回驱动枚举值的名称(小写)
for (String urlPrefix : driver.getUrlPrefixes()) {
// 所以prefix为 :{驱动枚举值名}:
String prefix = ":" + urlPrefix + ":";
// 数据源的地址去掉jdbc后,以 :{驱动枚举值名}: 开头,则返回该驱动
// 说明数据源的连接地址格式为 jdbc:{驱动枚举值名}:xxxx
if (driver != UNKNOWN && urlWithoutPrefix.startsWith(prefix)) {
return driver;
}
}
}
}
return UNKNOWN;
}
// 根据产品名称确定数据源驱动枚举值
public static DatabaseDriver fromProductName(String productName) {
if (StringUtils.hasLength(productName)) {
for (DatabaseDriver candidate : values()) {
if (candidate.matchProductName(productName)) {
return candidate;
}
}
}
return UNKNOWN;
}
}
2. EmbeddedDataSourceConfiguration
由上面的分析可知,如果内嵌数据源
的生效条件满足了,则会导入该配置类
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(DataSourceProperties.class)
public class EmbeddedDataSourceConfiguration implements BeanClassLoaderAware {
private ClassLoader classLoader;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
// 仅定义了该内嵌数据源组件(EmbeddedDatabase 继承了DataSource接口)
@Bean(destroyMethod = "shutdown")
public EmbeddedDatabase dataSource(DataSourceProperties properties) {
// 使用EmbeddedDatabaseBuilder 创建内嵌数据源
return new EmbeddedDatabaseBuilder()
.setType(
// 获取内嵌数据源的类型
EmbeddedDatabaseConnection.get(this.classLoader).getType()
)
// 允许用户spring.datasource.name指定内嵌数据源的名称或直接取名叫testdb
.setName(properties.determineDatabaseName()).build();
}
}
EmbeddedDatabaseBuilder
public class EmbeddedDatabaseBuilder {
// 用来创建内嵌数据源的工厂
private final EmbeddedDatabaseFactory databaseFactory;
// 用来执行sql脚本的组件
private final ResourceDatabasePopulator databasePopulator;
// 资源加载器
private final ResourceLoader resourceLoader;
public EmbeddedDatabaseBuilder() {
// 创建DefaultResourceLoader实例,用来加载文件(sql脚本文件)
this(new DefaultResourceLoader());
}
public EmbeddedDatabaseBuilder(ResourceLoader resourceLoader) {
// 初始化final属性
this.databaseFactory = new EmbeddedDatabaseFactory();
this.databasePopulator = new ResourceDatabasePopulator();
// 将执行sql脚本的组件设置给了数据源工厂,
// 这样数据源工厂将数据源初始化好了之后,从数据源中获取连接,交给sql脚本组件执行就可以了
this.databaseFactory.setDatabasePopulator(this.databasePopulator);
this.resourceLoader = resourceLoader;
}
//... 省略许多对databaseFactory、databasePopulator 的属性设置操作方法
// 最终构建数据源,是交给了EmbeddedDatabaseFactory
public EmbeddedDatabase build() {
return this.databaseFactory.getDatabase();
}
}
EmbeddedDatabaseFactory
由上面可知,最终内嵌数据源的构建是交给EmbeddedDatabaseFactory工厂来完成的
public class EmbeddedDatabaseFactory {
// 内嵌数据源默认的名字
public static final String DEFAULT_DATABASE_NAME = "testdb";
private boolean generateUniqueDatabaseName = false;
private String databaseName = DEFAULT_DATABASE_NAME;
// SimpleDriverDataSourceFactory内部只维护了一个 SimpleDriverDataSource
private DataSourceFactory dataSourceFactory = new SimpleDriverDataSourceFactory();
@Nullable // 内嵌数据源配置器
private EmbeddedDatabaseConfigurer databaseConfigurer;
@Nullable // 执行sql脚本的组件
private DatabasePopulator databasePopulator;
@Nullable
private DataSource dataSource;
public void setDatabaseType(EmbeddedDatabaseType type) {
// 在设置内嵌数据源类型的同时,也设置了对应的配置器类型
this.databaseConfigurer = EmbeddedDatabaseConfigurerFactory.getConfigurer(type);
}
// 获取数据源的方法
public EmbeddedDatabase getDatabase() {
if (this.dataSource == null) {
// 初始化数据源
initDatabase();
}
// 将dataSource包在EmbeddedDataSourceProxy中,返回
return new EmbeddedDataSourceProxy(this.dataSource);
}
protected void initDatabase() {
// 如果开启自动生成名字,则使用uuid
if (this.generateUniqueDatabaseName) {
setDatabaseName(UUID.randomUUID().toString());
}
// 如果没有设置内嵌数据源类型,则这个配置器会为null,
// 此时配置器默认使用HSQL对应的配置器(配置器中会帮助我们设置连接数据库的url、用户名、密码等信息)
if (this.databaseConfigurer == null) {
this.databaseConfigurer = EmbeddedDatabaseConfigurerFactory.getConfigurer(EmbeddedDatabaseType.HSQL);
}
// 使用数据源配置器,配置内嵌数据源的属性(此处结合SimpleDriverDataSourceFactory来看,是直接设置给了它维护的dataSource)
this.databaseConfigurer.configureConnectionProperties(this.dataSourceFactory.getConnectionProperties(), this.databaseName);
// 设置完成之后,就可以直接拿到配置的内嵌数据源了
this.dataSource = this.dataSourceFactory.getDataSource();
// 现在可以直接sql脚本了
if (this.databasePopulator != null) {
try {
DatabasePopulatorUtils.execute(this.databasePopulator, this.dataSource);
}
catch (RuntimeException ex) {
shutdownDatabase();
throw ex;
}
}
}
// 关闭数据源
protected void shutdownDatabase() {
if (this.dataSource != null) {
if (this.databaseConfigurer != null) {
// 默认从数据源中获取连接后,执行:con.createStatement().execute("SHUTDOWN")
this.databaseConfigurer.shutdown(this.dataSource, this.databaseName);
}
this.dataSource = null;
}
}
// 返回维护的数据源
protected final DataSource getDataSource() {
return this.dataSource;
}
// 就是对数据源实例的一个包装,方法都是委托给了dataSource
private class EmbeddedDataSourceProxy implements EmbeddedDatabase {
private final DataSource dataSource;
public EmbeddedDataSourceProxy(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Connection getConnection() throws SQLException {
return this.dataSource.getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return this.dataSource.getConnection(username, password);
}
}
}
3. DataSourceConfiguration
SpringBoot实际的数据源配置类,由上面分析可知,当满足池化数据源条件时,该类中定义的4个配置类将会导入。
从以下的配置可以看出,当我们引入tomcat-jbdc、HikariCP、commons-dbcp2其中的任何一个依赖时,而不指定具体的数据源类型type时,将会使用依赖所对应的数据源类型。引入其中多个依赖时,这个时候,就要小心了,不一定是哪个优先,只能说先处理的到的优先,后面由于有@ConditionalOnMissingBean(DataSource.class)而不会生效,因此这个时候具体使用的是哪个数据源就要注意了。而当我们指定了具体的type时,不管是不是DataSourceBuilder类中预定义的数据源类型,那么要么下面的通用的数据源配置生效,要么下面对应类型的数据源配置了生效(当然,如果是通用数据源配置类生效,那么就要注意其它的配置就没有设置进去了,因此这个时候,就可以考虑干脆自己在项目中配置数据源好了,手动设置其它相关属性)。
abstract class DataSourceConfiguration {
// 调用前面提到的DataSourceProperties的构建DataSourceBuilder的方法,并设置具体的数据源类型
protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {
return (T) properties.initializeDataSourceBuilder().type(type).build();
}
// tomcat数据源连接池配置
@Configuration(proxyBeanMethods = false)
// 必须导入tomcat-jdbc依赖
@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
// 用户如果配置了数据源组件,则以用户的优先
@ConditionalOnMissingBean(DataSource.class)
// spring.datasource.type没有配置或配置为org.apache.tomcat.jdbc.pool.DataSource此配置生效
@ConditionalOnProperty(name = "spring.datasource.type",
havingValue = "org.apache.tomcat.jdbc.pool.DataSource",
matchIfMissing = true)
static class Tomcat {
@Bean // 可以通过spring.datasource.tomcat来配置tomcat数据源
@ConfigurationProperties(prefix = "spring.datasource.tomcat")
org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties) {
// 创建tomcat数据源
org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(properties,org.apache.tomcat.jdbc.pool.DataSource.class);
// 从DatabaseDriver预定义的数据源驱动中获取jdbc连接地址匹配的枚举值
DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(properties.determineUrl());
String validationQuery = databaseDriver.getValidationQuery();
// 设置数据源的testOnBorrow、validationQuery属性
if (validationQuery != null) {
dataSource.setTestOnBorrow(true);
dataSource.setValidationQuery(validationQuery);
}
return dataSource;
}
}
// Hikari数据源连接池配置
@Configuration(proxyBeanMethods = false)
// 必须导入HikariCP的依赖(当引入spring-boot-starter-jdbc时, 会传递HikariCP的依赖)
@ConditionalOnClass(HikariDataSource.class)
// 用户如果配置了数据源组件,则以用户的优先
@ConditionalOnMissingBean(DataSource.class)
// spring.datasource.type没有配置或配置为com.zaxxer.hikari.HikariDataSource此配置生效
@ConditionalOnProperty(name = "spring.datasource.type",
havingValue = "com.zaxxer.hikari.HikariDataSource",
matchIfMissing = true)
static class Hikari {
@Bean // 可以通过spring.datasource.hikari来配置tomcat数据源
@ConfigurationProperties(prefix = "spring.datasource.hikari")
HikariDataSource dataSource(DataSourceProperties properties) {
// 创建Hikari数据源
HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
if (StringUtils.hasText(properties.getName())) {
// 设置数据源的名字
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
}
// DBCP2数据源连接池配置
@Configuration(proxyBeanMethods = false)
// 必须导入commons-dbcp2的依赖
@ConditionalOnClass(org.apache.commons.dbcp2.BasicDataSource.class)
// 用户如果配置了数据源组件,则以用户的优先
@ConditionalOnMissingBean(DataSource.class)
// spring.datasource.type没有配置或配置为org.apache.commons.dbcp2.BasicDataSource此配置生效
@ConditionalOnProperty(name = "spring.datasource.type",
havingValue = "org.apache.commons.dbcp2.BasicDataSource",
matchIfMissing = true)
static class Dbcp2 {
@Bean // 可以通过spring.datasource.dbcp2来配置tomcat数据源
@ConfigurationProperties(prefix = "spring.datasource.dbcp2")
org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties properties) {
// 仍然调用的是同一个创建数据源的方法,只是传入的数据源实现类不一样
return createDataSource(properties, org.apache.commons.dbcp2.BasicDataSource.class);
}
}
// 通用的数据源配置
@Configuration(proxyBeanMethods = false)
// 用户如果配置了数据源组件,则以用户的优先
@ConditionalOnMissingBean(DataSource.class)
// 该配置类生效的条件是:用户必须精确的指定数据源类型
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {
@Bean
DataSource dataSource(DataSourceProperties properties) {
// 仍然调用DataSourceProperties构建DataSourceBuilder的方法,
// 包括确定type、driverClassName、url、username、password
// 然后构建数据源实例返回
return properties.initializeDataSourceBuilder().build();
}
}
}
4. DataSourcePoolMetadataProvidersConfiguration
这个配置类的目的是用于获取数据源的一些元信息,比如说:数据源连接使用率、当前活跃连接数量、空闲连接数量、最大连接数量、最小空闲连接数量、是否自动提交等信息(可参考DataSourcePoolMetadata
),当然,这需要当前的数据源支持这些操作才可以哦,如果它不支持,这些方法是可以返回null的。
该配置类中,所定义的DataSourcePoolMetadataProvider类型,都只是对 对应的DataSource的封装
@Configuration(proxyBeanMethods = false)
public class DataSourcePoolMetadataProvidersConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
static class TomcatDataSourcePoolMetadataProviderConfiguration {
@Bean
DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvider() {
return (dataSource) -> {
org.apache.tomcat.jdbc.pool.DataSource tomcatDataSource = DataSourceUnwrapper.unwrap(dataSource,
org.apache.tomcat.jdbc.pool.DataSource.class);
if (tomcatDataSource != null) {
return new TomcatDataSourcePoolMetadata(tomcatDataSource);
}
return null;
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HikariDataSource.class)
static class HikariPoolDataSourceMetadataProviderConfiguration {
@Bean
DataSourcePoolMetadataProvider hikariPoolDataSourceMetadataProvider() {
return (dataSource) -> {
HikariDataSource hikariDataSource = DataSourceUnwrapper.unwrap(dataSource, HikariDataSource.class);
if (hikariDataSource != null) {
return new HikariDataSourcePoolMetadata(hikariDataSource);
}
return null;
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(BasicDataSource.class)
static class CommonsDbcp2PoolDataSourceMetadataProviderConfiguration {
@Bean
DataSourcePoolMetadataProvider commonsDbcp2PoolDataSourceMetadataProvider() {
return (dataSource) -> {
BasicDataSource dbcpDataSource = DataSourceUnwrapper.unwrap(dataSource, BasicDataSource.class);
if (dbcpDataSource != null) {
return new CommonsDbcp2DataSourcePoolMetadata(dbcpDataSource);
}
return null;
};
}
}
}
5. DataSourceInitializationConfiguration
@Configuration(proxyBeanMethods = false)
@Import({ DataSourceInitializerInvoker.class, // 数据源初始化调用器
DataSourceInitializationConfiguration.Registrar.class // 就是下面这个注册器
})
class DataSourceInitializationConfiguration {
static class Registrar implements ImportBeanDefinitionRegistrar {
private static final String BEAN_NAME = "dataSourceInitializerPostProcessor";
// 注册的这个DataSourceInitializerPostProcessor,的优先级仅次于最高优先级
// 目的就是在预实例化bean时,只要发现DataSource的bean,立即触发getBean(DataSourceInitializerInvoker.class)
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(BEAN_NAME)) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
// BeanClass为DataSourceInitializerPostProcessor
beanDefinition.setBeanClass(DataSourceInitializerPostProcessor.class);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 设置为合成的
beanDefinition.setSynthetic(true);
// 注册到spring注册中心
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
}
}
}
}
DataSourceInitializerInvoker
class DataSourceInitializerInvoker
implements
// 监听器,监听DataSourceSchemaCreatedEvent事件(这个事件也是在afterPropertiesSet方法中触发的)
ApplicationListener<DataSourceSchemaCreatedEvent>,
InitializingBean { // afterPropertiesSet方法回调
// 数据源
private final ObjectProvider<DataSource> dataSource;
// 数据源配置
private final DataSourceProperties properties;
// spring容器上下文
private final ApplicationContext applicationContext;
// 数据源初始化器
private DataSourceInitializer dataSourceInitializer;
// 标记是否已初始化
private boolean initialized;
// 唯一构造
DataSourceInitializerInvoker(ObjectProvider<DataSource> dataSource,
DataSourceProperties properties,
ApplicationContext applicationContext) {
this.dataSource = dataSource;
this.properties = properties;
this.applicationContext = applicationContext;
}
private DataSourceInitializer getDataSourceInitializer() {
// 如果数据源初始化器还为null
if (this.dataSourceInitializer == null) {
DataSource ds = this.dataSource.getIfUnique();
if (ds != null) {
// 创建数据源初始化器, 传入数据源、数据源配置(里面有初始化脚本的配置)、spring容器上下文
this.dataSourceInitializer = new DataSourceInitializer(ds, this.properties, this.applicationContext);
}
}
return this.dataSourceInitializer;
}
@Override
public void afterPropertiesSet() {
// 获取数据源初始化器
DataSourceInitializer initializer = getDataSourceInitializer();
if (initializer != null) {
// 执行DDL的sql脚本
boolean schemaCreated = this.dataSourceInitializer.createSchema();
if (schemaCreated) {
// 调用初始化方法
initialize(initializer);
}
}
}
private void initialize(DataSourceInitializer initializer) {
try {
// 发布DataSourceSchemaCreatedEvent事件(当前对象会监听该事件)
this.applicationContext.publishEvent(new DataSourceSchemaCreatedEvent(initializer.getDataSource()));
// 有可能此时还没有注册当前这个监听器, 监听方法还没有执行,这里会手动运行初始化脚本
if (!this.initialized) {
// 运行初始化脚本
this.dataSourceInitializer.initSchema();
// 标记已初始化
this.initialized = true;
}
}
catch (IllegalStateException ex) {
// do nothing...
}
}
// 用于监听DataSourceSchemaCreatedEvent事件,同上
@Override
public void onApplicationEvent(DataSourceSchemaCreatedEvent event) {
DataSourceInitializer initializer = getDataSourceInitializer();
if (!this.initialized && initializer != null) {
initializer.initSchema();
this.initialized = true;
}
}
}
DataSourceInitializer
class DataSourceInitializer {
// 数据源
private final DataSource dataSource;
// 数据源属性配置
private final DataSourceProperties properties;
// 资源加载器
private final ResourceLoader resourceLoader;
// 构造方法
DataSourceInitializer(DataSource dataSource, DataSourceProperties properties, ResourceLoader resourceLoader) {
this.dataSource = dataSource;
this.properties = properties;
// 资源加载器,用来加载sql脚本文件
this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
}
// 构造方法
DataSourceInitializer(DataSource dataSource, DataSourceProperties properties) {
this(dataSource, properties, null);
}
// 获取脚本文件
private List<Resource> getScripts(String propertyName, List<String> resources, String fallback) {
if (resources != null) {
// 根据传入的resources 获取资源(配置文件中可配置)
// 可以使用通配符, 见: ResourcePatternUtils#getResourcePatternResolver()),
// - 详情见: PathMatchingResourcePatternResolver
return getResources(propertyName, resources, true);
}
// 如果没有配置,则获取spring.datasource.platform配置(fallback可以是schema或data)
// classpath*:{fallback}-{platform}.sql
// classpath*:{fallback}.sql
String platform = this.properties.getPlatform();
List<String> fallbackResources = new ArrayList<>();
fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql");
fallbackResources.add("classpath*:" + fallback + ".sql");
// 获取如上的2个文件
return getResources(propertyName, fallbackResources, false);
}
private List<Resource> getResources(String propertyName, List<String> locations, boolean validate) {
List<Resource> resources = new ArrayList<>();
// 遍历传入的locations
for (String location : locations) {
// 获取资源文件(一个路径可匹配多个文件)
for (Resource resource : doGetResources(location)) {
// 文件存在,则添加到返回中
if (resource.exists()) {
resources.add(resource);
}
else if (validate) {
throw new InvalidConfigurationPropertyValueException();
}
}
}
return resources;
}
// 获取资源文件,注意它里面是按照资源文件名的顺序排序的
private Resource[] doGetResources(String location) {
try {
SortedResourcesFactoryBean factory = new SortedResourcesFactoryBean(this.resourceLoader,Collections.singlet,onList(location));
factory.afterPropertiesSet();
return factory.getObject();
}
catch (Exception ex) {
throw new IllegalStateException();
}
}
// 运行创建schema的脚本
boolean createSchema() {
// 获取sql脚本文件
List<Resource> scripts = getScripts("spring.datasource.schema", this.properties.getSchema(), "schema");
if (!scripts.isEmpty()) {
String username = this.properties.getSchemaUsername();
String password = this.properties.getSchemaPassword();
// 运行脚本
runScripts(scripts, username, password);
}
return !scripts.isEmpty();
}
// 运行初始化sql脚本
void initSchema() {
// 获取脚本文件
List<Resource> scripts = getScripts("spring.datasource.data", this.properties.getData(), "data");
if (!scripts.isEmpty()) {
String username = this.properties.getDataUsername();
String password = this.properties.getDataPassword();
// 运行脚本
runScripts(scripts, username, password);
}
}
private void runScripts(List<Resource> resources, String username, String password) {
// 资源文件是空的话,直接返回
if (resources.isEmpty()) {
return;
}
// 资源数据库填充器
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
// 遇到错误是否继续
populator.setContinueOnError(this.properties.isContinueOnError());
// sql脚本分隔符
populator.setSeparator(this.properties.getSeparator());
// sql脚本字符编码
if (this.properties.getSqlScriptEncoding() != null) {
populator.setSqlScriptEncoding(this.properties.getSqlScriptEncoding().name());
}
// 添加所有的sql脚本
for (Resource resource : resources) {
populator.addScript(resource);
}
DataSource dataSource = this.dataSource;
// 如果有提供用户名密码,则使用提供的用户名和密码,创建数据源(保证有权限操作)
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
dataSource = DataSourceBuilder.create(this.properties.getClassLoader())
.driverClassName(this.properties.determineDriverClassName()).url(this.properties.determineUrl())
.username(username).password(password).build();
}
// 执行sql脚本
DatabasePopulatorUtils.execute(populator, dataSource);
}
}
DatabasePopulatorUtils
public abstract class DatabasePopulatorUtils {
public static void execute(DatabasePopulator populator, DataSource dataSource) throws DataAccessException {
Assert.notNull(populator);
Assert.notNull(dataSource);
try {
// 使用DataSourceUtils从数据源中获取连接(这个方法与事务相关)
Connection connection = DataSourceUtils.getConnection(dataSource);
try {
// 执行脚本仍然是交给了DatabasePopulator ,这里只是把连接给了它
// 上面我们提到了,sql脚本已经交给了DatabasePopulator,
// Connection连接对象也已经给了,因此,这里让它来执行sql是没有问题的
// 感兴趣的话,可以直接看:ScriptUtils#executeSqlScript
populator.populate(connection);
}
finally {
DataSourceUtils.releaseConnection(connection, dataSource);
}
}
catch (Throwable ex) {
if (ex instanceof ScriptException) {
throw (ScriptException) ex;
}
throw new UncategorizedScriptException();
}
}
}
通过上面的分析,我们已经对springboot配置数据源有了整体的了解了。