背景
有一个项目fyk-config,该项目需要在配置的时候,需要创建一个配置表(FYK_PROPERTIES),并且向该表中插入各个微服务的配置记录。
解决方案
在SpringBoot中,有一个DataSourceInitializer类,该类会在项目启动的时候,执行初始化脚本。具体代码如下:
首先,在resources目录下,创建文件夹scritp/db,然后在db文件夹下,放入sql文件:
然后,在项目中,写一个配置类:
@Slf4j
@Configuration
public class DbScriptInit {
@Bean
public DataSourceInitializer dataSourceInitializer(final DataSource dataSource) throws IOException {
final DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(this.databasePopulator());
return initializer;
}
/**
* 初始化数据资源
*
* @author FYK
* @return org.springframework.core.io.Resource[] 资源对象
*/
private Resource[] getResources() throws IOException {
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath*:script/db/*.sql");
log.info("加载初始化脚本文件---------start");
for (Resource resource : resources) {
log.info(resource.getFilename());
}
log.info("加载初始化脚本文件---------end");
return resources;
}
/**
* 初始化数据策略
*
* @author FYK
* @return org.springframework.jdbc.datasource.init.DatabasePopulator 策略对象
*/
private DatabasePopulator databasePopulator() throws IOException {
final ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScripts(this.getResources());
return populator;
}
}
注意
有两点需要说明下:
- db文件夹下的脚本可能有多个,所以,这里要使用PathMatchingResourcePatternResolver,读取多个.sql的文件;
- 获取文件的路径是classpath*:script/db/*.sql,classpath后加个星号,意思是连jar包中的符合该规则的文件,都可以获取到,因为该项目最终会打成jar包来运行,如果不使用这个星号,就会出现在开发的时,没有问题,等到了测试或生产环境,就会出现找不到sql文件问题。当然,这里的文件规则,需要自己定义好,不要扫到了本不应加载的其他包中的文件了。
补充说明
按照上述操作,应该就能完成所需要求了。
但是有以下问题也许需要关注:
- SQL脚本是有执行顺序的,例如,在我的项目中,我需要建一个表,然后像该表插入初始化数据,所以有两个脚本,一个是建表,一个是初始化数据插入(当然,如果你所有的SQL语句都在一个文件中,那就不存在这种问题)。那么就应该先执行建表语句,再执行初始化语句。
给一个解决方案:所有脚本文件的命名规则:序号-表名-操作类型-备注.sql,这里的序号就表示了这些脚本文件的执行顺序。如下:
- 这些脚本文件,在项目启动的时候,会执行。那么项目重启之后,又会再次执行,这个应该如何避免?
这里给一个参考:首先为每一个脚本文件,都配置一个验证是否执行的SQL语句,然后在加载这些配置文件的时候,先执行下这个SQL语句,判断是否执行该脚本,代码大致如下:
在application配置文件中,配置每隔文件对应的验证sql,这里我的每个sql最终都会返回0或者1,返回0表示要执行脚本文件,返回1标识不执行:
fyk.db-script.check-sql={\
"1-FYK_PROPERTIES-DQL":"select case when exists(select 1 from all_tables t where t.TABLE_NAME = upper('fyk_properties')) then 1 else 0 end as result from dual",\
"2-FYK_PROPERTIES-DML-fyk-oauth":"select case when exists(select 1 from fyk_properties t where t.application='fyk-oauth') then 1 else 0 end as result from dual"\
}
然后对DbScriptInit类镜像改造,改造里面的getResources方法:
@Value("#{${fyk.db-script.check-sql}}")
private Map<String, String> checkSql;
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 初始化数据资源
* <p>
* 首先,每个脚本文件,都要对应一个验证sql,只有验证SQL返回0的时候,才执行该脚本文件,否则不执行。
* </p>
*
* @author FYK
* @return org.springframework.core.io.Resource[] 资源对象
*/
private Resource[] getResources() throws IOException {
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath*:script/db/*.sql");
List<Resource> resultList = new LinkedList<>();
log.info("加载初始化脚本文件---------start");
String checkSqlStr;
Integer resutlNum;
String dqlSqlMatch = "";
for (Resource resource : resources) {
String fileFullName = resource.getFilename();
// 如果DQL语句都没有执行,则默认要执行DML语句
if (fileFullName.matches(".*DML.*") && StringUtil.isNotBlank(dqlSqlMatch)
&& fileFullName.matches(dqlSqlMatch)) {
resultList.add(resource);
continue;
}
// 获得验证脚本
checkSqlStr = this.getCheckSql(fileFullName);
if (StringUtil.isNotBlank(checkSqlStr)) {
resutlNum = jdbcTemplate.queryForObject(checkSqlStr, Integer.class);
if (resutlNum != null && resutlNum == 0) {
resultList.add(resource);
if (fileFullName.matches(".*DQL.*")) {
dqlSqlMatch += this.buildDqlSqlMatch(fileFullName, dqlSqlMatch);
}
} else {
log.info("sql初始化脚本文件[{}]验证结果为1,跳过该脚本", fileFullName);
}
}
}
log.info("加载初始化脚本文件---------end");
return resultList.toArray(new Resource[0]);
}
/**
* 获取去掉后缀的文件名
*
* @author FYK
* @param fileFullName
* 资源文件全名
* @return java.lang.String 文件名
*/
private String getCheckSql(@NonNull String fileFullName) {
String fileName = fileFullName.replace(".sql", "");
log.info("{}.sql", fileName);
String checkSqlStr = checkSql.get(fileName);
if (StringUtil.isBlank(checkSqlStr)) {
log.warn("sql脚本文件[{}.sql]的执行验证语句未配置~~~~~不执行该SQL脚本", fileName);
}
return checkSqlStr;
}
/**
* 构建DQL语句匹配规则
*
* @author FYK
* @param fileFullName
* 全文件名
* @param dqlSqlMatch
* DQL语句匹配规则
* @return java.lang.String 构建结果
*/
private String buildDqlSqlMatch(String fileFullName, String dqlSqlMatch) {
String[] fileFullNameSplit = fileFullName.split("-");
StringBuilder sb = new StringBuilder();
if (StringUtil.isBlank(dqlSqlMatch)) {
sb.append(".*\\-");
} else {
sb.append("|.*\\-");
}
sb.append(fileFullNameSplit[1]);
sb.append("\\-.*");
return sb.toString();
}
仅供参考!!!