很多开源项目我们没有导入SQL进入数据库,但是项目一旦启动,就会替我们执行初始化数据了。我们今天来分析是如何实现的。
SpringBoot加载SQL脚本源码剖析
直接从数据源初始化配置进入,查看createFrom()
@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。
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
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 默认是什么?
/**
* 要在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),我们点进去看
public DataSourceScriptDatabaseInitializer(DataSource dataSource, DatabaseInitializationSettings settings) {
super(settings);
this.dataSource = dataSource;
}
点击super,进入
protected AbstractScriptDatabaseInitializer(DatabaseInitializationSettings settings) {
this.settings = settings;
}
很明显这是一个构造方法,但这个类实现了InitializingBean接口。(作用是,Bean示例化后执行一个方法。)
/*
由BeanFactory设置完所有属性后需要做出反应的bean实现的接口:例如,执行自定义初始化,或仅检查是否设置了所有强制属性。 实现InitializingBean的另一种方法是指定自定义init方法,例如在XML bean定义中。有关所有bean生命周期方法的列表,请参阅BeanFactory javadocs。 请参阅:
*/
public interface InitializingBean {
/**
由包含BeanFactory的在它设置了所有bean属性并满足BeanFactoryAware、ApplicationContextAware等之后调用。 此方法允许bean实例在设置了所有bean属性后执行其整体配置的验证和最终初始化。 抛出: 异常–如果配置错误(如未能设置基本属性)或由于任何其他原因导致初始化失败
*/
void afterPropertiesSet() throws Exception;
}
所以AbstractScriptDatabaseInitializer必须要重写afterProertiesSet()方法,我们返回看一下他重写的方法
@Override
public void afterPropertiesSet() throws Exception {
initializeDatabase();
}
调用了一个方法,我们点initializeDatabase();进去看看
/**
* 通过应用架构和数据脚本初始化数据库。 返回值: 如果一个或多个脚本应用于数据库,则为true,否则为false
*/
public boolean initializeDatabase() {
ScriptLocationResolver locationResolver = new ScriptLocationResolver(this.resourceLoader);
boolean initialized = applySchemaScripts(locationResolver);
return applyDataScripts(locationResolver) || initialized;
}
这方法原来是执行初始化数据库的。但此方法调用了2个方法:applySchemaScripts方法在applyDateScripts前执行。
两个方法都调用了applyScripts。只是传递的type不一样
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语句。
先去了解一下常见的配置文件
# 注意此方法被标记启用的属性,请使用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属性有可供选择的状态有
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脚本。
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取最新全部资料 ❤