Springboot启动(DataSources创建时)执行初始化SQL脚本 – 源码剖析 – 超级干货

2022-11-18 17:43:50 浏览数 (2)

本页目录

  • SpringBoot加载SQL脚本源码剖析
  • 应用Springboot自动初始化SQL
    • 开启自动初始化Sql语句。
    • spring.sql.init.mode属性有可供选择的状态有
  • 总结

很多开源项目我们没有导入SQL进入数据库,但是项目一旦启动,就会替我们执行初始化数据了。我们今天来分析是如何实现的。

SpringBoot加载SQL脚本源码剖析

直接从数据源初始化配置进入,查看createFrom()

代码语言:javascript复制
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(AbstractScriptDatabaseInitializer.class)
@ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnClass(DatabasePopulator.class)
class DataSourceInitializationConfiguration {

	@Bean
	DataSourceScriptDatabaseInitializer dataSourceScriptDatabaseInitializer(DataSource dataSource,
			SqlInitializationProperties initializationProperties) {
		DatabaseInitializationSettings settings = SettingsCreator.createFrom(initializationProperties);
		return new DataSourceScriptDatabaseInitializer(determineDataSource(dataSource,
				initializationProperties.getUsername(), initializationProperties.getPassword()), settings);
	}

        ......
}

进入createFrom方法,发现调用了scriptLocations。

代码语言:javascript复制
	static DatabaseInitializationSettings createFrom(SqlInitializationProperties properties) {
		DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
		settings.setSchemaLocations(
				scriptLocations(properties.getSchemaLocations(), "schema", properties.getPlatform()));
		settings.setDataLocations(scriptLocations(properties.getDataLocations(), "data", properties.getPlatform()));
		settings.setContinueOnError(properties.isContinueOnError());
		settings.setSeparator(properties.getSeparator());
		settings.setEncoding(properties.getEncoding());
		settings.setMode(properties.getMode());
		return settings;
	}

进入scriptLocations方法,注意第二个参数fallback。他是调用者传入固定字符串,schema、data

代码语言:javascript复制
	private static List<String> scriptLocations(List<String> locations, String fallback, String platform) {
		if (locations != null) {
			return locations;
		}
		List<String> fallbackLocations = new ArrayList<>();
		fallbackLocations.add("optional:classpath*:"   fallback   "-"   platform   ".sql");
		fallbackLocations.add("optional:classpath*:"   fallback   ".sql");
		return fallbackLocations;
	}

接着看下一个代码,我们再看看platform 默认是什么?

代码语言:javascript复制
	/**
	 * 要在DDL或DML脚本中使用的平台(如schema-${Platform}.sql或data-${Platform}.sql)
	 */
	private String platform = "all";

哦,我们就知道了,(这里是一个DDL与DML是DataSourceScriptDatabaseInitializer类标注:使用模式(DDL)和数据(DML)脚本执行DataSource初始化的InitializationBean)DDL语句设置的是schema,他会加载默认文件叫schema.sql、schema-all.sql。同理DML语句设置的是data,他会加载文件叫data.sql、data-all.sql。如果我们配置文件指定了相关locations,未来只会去找我们的指定的文件。而不在走默认schema或data以及带有后缀的sql文件了。

现在再回到第一个类,有个方法叫new DataSourceScriptDatabaseInitializer(XXXX),我们点进去看

代码语言:javascript复制
	public DataSourceScriptDatabaseInitializer(DataSource dataSource, DatabaseInitializationSettings settings) {
		super(settings);
		this.dataSource = dataSource;
	}

点击super,进入

代码语言:javascript复制
	protected AbstractScriptDatabaseInitializer(DatabaseInitializationSettings settings) {
		this.settings = settings;
	}

很明显这是一个构造方法,但这个类实现了InitializingBean接口。(作用是,Bean示例化后执行一个方法。)

代码语言:javascript复制
/*
由BeanFactory设置完所有属性后需要做出反应的bean实现的接口:例如,执行自定义初始化,或仅检查是否设置了所有强制属性。 实现InitializingBean的另一种方法是指定自定义init方法,例如在XML bean定义中。有关所有bean生命周期方法的列表,请参阅BeanFactory javadocs。 请参阅:
*/
public interface InitializingBean {

	/**
由包含BeanFactory的在它设置了所有bean属性并满足BeanFactoryAware、ApplicationContextAware等之后调用。 此方法允许bean实例在设置了所有bean属性后执行其整体配置的验证和最终初始化。 抛出: 异常–如果配置错误(如未能设置基本属性)或由于任何其他原因导致初始化失败
	 */
	void afterPropertiesSet() throws Exception;

}

所以AbstractScriptDatabaseInitializer必须要重写afterProertiesSet()方法,我们返回看一下他重写的方法

代码语言:javascript复制
	@Override
	public void afterPropertiesSet() throws Exception {
		initializeDatabase();
	}

调用了一个方法,我们点initializeDatabase();进去看看

代码语言:javascript复制
	/**
	 * 通过应用架构和数据脚本初始化数据库。 返回值: 如果一个或多个脚本应用于数据库,则为true,否则为false
	 */
	public boolean initializeDatabase() {
		ScriptLocationResolver locationResolver = new ScriptLocationResolver(this.resourceLoader);
		boolean initialized = applySchemaScripts(locationResolver);
		return applyDataScripts(locationResolver) || initialized;
	}

这方法原来是执行初始化数据库的。但此方法调用了2个方法:applySchemaScripts方法在applyDateScripts前执行。

两个方法都调用了applyScripts。只是传递的type不一样

代码语言:javascript复制
	private boolean applyScripts(List<String> locations, String type, ScriptLocationResolver locationResolver) {
		List<Resource> scripts = getScripts(locations, type, locationResolver);
		if (!scripts.isEmpty() && isEnabled()) {
			runScripts(scripts);
			return true;
		}
		return false;
	}

applyScripts方法有个runScripts(scripts);所以这才是真正的执行了脚本。但第一次传入schema,第二次传入data。

哦这才是真正的schema脚本比data脚本先执行的原因!未来我们将schema当做的是DDL,也就是设计语句。data当做DML就是操作语句。(现有鸡才能有蛋嘛)哈哈哈!

应用Springboot自动初始化SQL

开启自动初始化Sql语句。

先去了解一下常见的配置文件

代码语言:javascript复制
# 注意此方法被标记启用的属性,请使用spring.sql.init.mode替换
# spring.datasource.initialization-mode=always
spring.sql.init.mode=always
# 如果脚本执行出现异常是否继续执行后续脚本,默认false
spring.sql.init.continue-on-error=false
# 要在默认模式或数据脚本位置中使用的平台,模式-${Platform}。sql和data-${platform}.sql。
spring.sql.init.platform=
# 要应用于数据库的架构(DDL 数据定义语言)脚本的位置
spring.sql.init.schema-locations=
# 要应用于数据库的数据(DML 数据操作语言)脚本的位置
spring.sql.init.data-locations=
# 架构和数据脚本中的语句分隔符 默认是;
spring.sql.init.separator=;
spring.sql.init.encoding=UTF-8

spring.sql.init.mode属性有可供选择的状态有

代码语言:javascript复制
public enum DatabaseInitializationMode {
	/**
	 * 始终初始化数据库 Always initialize the database.
	 */
	ALWAYS,

	/**
	 * 仅初始化嵌入式数据库 Only initialize an embedded database.
	 */
	EMBEDDED,

	/**
	 * 从不初始化数据库 Never initialize the database.
	 */
	NEVER
}

总结

默认我们开启配置文件 spring.sql.init.mode = always。其他文件不配置,只会执行schema.sql、schema-all.sql、data.sql、data-all.sql这4个SQL脚本。

如果我们指定了schema-locations、data-locations,他就会去加载指定位置的文件。而platform不在起作用。

如果配置了platform,就会影响默认的*-all.sql。因为platform默认值就是all,如果我设置成test,他就会加载执行schema.sql、schema-test.sql、data.sql、data-test.sql这4个SQL脚本。

特殊说明: 以上文章,均是我实际操作,写出来的笔记资料,不会盗用别人文章!烦请各位,请勿直接盗用!转载记得标注来源!

0 人点赞