欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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文件:
SpringBoot项目启动时自动执行多个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; } } 

注意

有两点需要说明下:

  1. db文件夹下的脚本可能有多个,所以,这里要使用PathMatchingResourcePatternResolver,读取多个.sql的文件;
  2. 获取文件的路径是classpath*:script/db/*.sql,classpath后加个星号,意思是连jar包中的符合该规则的文件,都可以获取到,因为该项目最终会打成jar包来运行,如果不使用这个星号,就会出现在开发的时,没有问题,等到了测试或生产环境,就会出现找不到sql文件问题。当然,这里的文件规则,需要自己定义好,不要扫到了本不应加载的其他包中的文件了。

补充说明

按照上述操作,应该就能完成所需要求了。
但是有以下问题也许需要关注:

  1. SQL脚本是有执行顺序的,例如,在我的项目中,我需要建一个表,然后像该表插入初始化数据,所以有两个脚本,一个是建表,一个是初始化数据插入(当然,如果你所有的SQL语句都在一个文件中,那就不存在这种问题)。那么就应该先执行建表语句,再执行初始化语句。
    给一个解决方案:所有脚本文件的命名规则:序号-表名-操作类型-备注.sql,这里的序号就表示了这些脚本文件的执行顺序。如下:
    SpringBoot项目启动时自动执行多个SQL脚本
  2. 这些脚本文件,在项目启动的时候,会执行。那么项目重启之后,又会再次执行,这个应该如何避免?
    这里给一个参考:首先为每一个脚本文件,都配置一个验证是否执行的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