SpringBoot项目启动时自动执行多个SQL脚本
程序员文章站
2022-03-11 19:01:53
目录背景解决方案注意补充说明背景有一个项目fyk-config,该项目需要在配置的时候,需要创建一个配置表(FYK_PROPERTIES),并且向该表中插入各个微服务的配置记录。解决方案在SpringBoot中,有一个DataSourceInitializer类,该类会在项目启动的时候,执行初始化脚本。具体代码如下:首先,在resources目录下,创建文件夹scritp/db,然后在db文件夹下,放入sql文件:然后,在项目中,写一个配置类:@Slf4j@Configurationpu...
背景
有一个项目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(); }
仅供参考!!!
本文地址:https://blog.csdn.net/fyk844645164/article/details/107893626
上一篇: 除了春哥你还信什么
下一篇: Sqlalchemy IN条件查询