第六章 Springboot数据源配置的源码解析

问题引入

在刚开始学习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配置数据源有了整体的了解了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值