springboot启动时执⾏脚本(H2演⽰)
背景
在项⽬初建或者版本迭代的演进的时候,⼀般都会附带数据库的变化,会专门出⼀个脚本进⾏数据库更新。最近遇到java做的单机版客户端,使⽤的H2数据库,每次换了数据库都要进⾏⼿动执⾏数据库脚本,⾮常不便利,因此开始查资料实现数据库随着启动⾃动化初始化。
springboot⾃带
根据资料显⽰,springboot⾃带的有初始化数据库的属性,配置属性如下:
# 忽略正常的DataSource配置
# 执⾏建shema语句
spring.datasource.schema=classpath:schema.sql
# 执⾏建表或者初始化data的的语句
spring.datasource.data=classpath:data.sql
# 执⾏创建函数的语句
spring.datasource.sql-script-encoding=utf-8
# 执⾏脚本的模式三种:always为始终执⾏初始化,embedded只初始化内存数据库(默认值),如h2等,never为不执⾏初始化
spring.datasource.initialization-mode=ALWAYS
# 为sql脚本中语句分隔符(默认的分隔符和脚本的不⼀致)
spring.datasource.separator:
# 遇到语句错误时是否继续,若已经执⾏过某些语句,再执⾏可能会报错,可以忽略,不会影响程序启动
inue-on-error:false
PS:
1. 启动类中的 DataSourceAutoConfiguration.class注解会让配置失效,或者druid的防⽕墙也会让此⽅法
失效。
2. 它还会加载schema-platform.sql ⽂ 件,或 者data−{platform}.sql⽂件,其中platform就是spring.datasource.platform的值
这种配置⽅法需要将脚本整理成两个sql⽂件,⼀个是shema⼀个是data的。由于系统本⾝提供脚本的时候按照建表、注释、索引等⽅式建⽴了不同的数据库脚本,如果再合成⼀个给维护带来了额外的⼯作量,因此采⽤下⾯这种代码的⽅式进⾏初始化。
DataSourceInitializer代码的⽅式
废话不多说,先上代码:
fig;
import IOException;
import List;
import DataSource;
import Autowired;
import Value;
import Bean;
import Configuration;
import Resource;
import ResourcePatternResolver;
import ResourcePatternUtils;
import JdbcTemplate;
import DataSourceInitializer;
import DatabasePopulator;
import ResourceDatabasePopulator;
import Slf4j;
/**
*
* CustomizeDataSourceInitializer
*
* @description ⾃动初始化和执⾏脚本
* @author elvis
* @date xx年xx⽉xx⽇上午10:28:47
* @version 1.0.0
*/
@Configuration
@Slf4j
public class CustomizeDataSourceInitializer {
/**
* 是否强制覆盖
*/
@Value("${commons.forceInitDatabase:false}")
private boolean forceInitDatabase;
@Autowired
private JdbcTemplate jdbcTemplate;
@Value("${commons.database.schema:sql/schema}")
private String sqlScriptSchema;
@Value("${commons.database.table:sql/table}")
private String sqlScriptData;
@Value("${commons.database.datainit:sql/datainit}")
private String sqlScriptProcedure;
@Bean
public DataSourceInitializer dataSourceInitializer(final DataSource dataSource){
DataSourceInitializer dataSourceInitializer =new DataSourceInitializer();
dataSourceInitializer.setDataSource(dataSource);
// H2专属
String sql ="SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA;";
List<String> allShema = jdbcTemplate.queryForList(sql, String.class);
boolean hasShema = ains("XXXX");
ains("XXXX")&&!forceInitDatabase){
return dataSourceInitializer;
}
try{
dataSourceInitializer.setDatabasePopulator(databasePopulator(hasShema));
}catch(Exception e){
<("⾃动执⾏脚本失败,不影响正常启动流程,请启动后再⼿动执⾏脚本", e);
}
return dataSourceInitializer;
}
private DatabasePopulator databasePopulator(boolean hasShema)throws IOException {
ResourceDatabasePopulator populator =new ResourceDatabasePopulator();
jdbctemplate查询一条数据ResourcePatternResolver resolver = ResourcePatternResolver(null);; Resource[] resources =null;
if(!hasShema){
resources = Resources("classpath:/"+ sqlScriptSchema +"/*");
for(Resource resource : resources){
populator.addScript(resource);
}
}
resources = Resources("classpath:/"+ sqlScriptData +"/*");
for(Resource resource : resources){
populator.addScript(resource);
}
resources = Resources("classpath:/"+ sqlScriptProcedure +"/*");
for(Resource resource : resources){
populator.addScript(resource);
}
return populator;
}
}
}
代码解释:
核⼼就是在数据库初始化的类DataSourceInitializer中的DatabasePopulator对象加上对应的script脚本然后交由springboot在启动的时候⾃动执⾏。为了可以重复执⾏,使⽤jdbcTemplate查询数据库增加了针对特定shema的判断(根据实际要执⾏的脚本进⾏判断,防⽌每次执⾏清掉数据)。
由于整个是基于jar的形式发布的,因此如何获取打包后的sql脚本路径是⼀个问题?通过查询资料:
1. ResourceUtils可以直接获取jar包⾥⾯⽂件或者某个⽂件夹的列表,但是linux中⽆法使⽤
2. ClassPathResource可以获取指定的⽂件,但是暂时没有到可以获取⽂件夹列表的⽅法,不适⽤
3. ResourceLoader和第⼆个类似
4. ResourcePatternResolver可以根据pattern的语法来获取⽂件,符合要求(见上⾯代码)
H2相关
记录⼀个⼩发现,H2的数据库⽂件需要制定路径,但是我们在实际使⽤的时候并不知道有⼀些什么路径,按照经验来看windows肯定是有C盘的,但是C盘各个⽂件夹有的有权限限制,并不⼀定都能写⼊,因此获取⽤户空间来存放H2的数据⽂件是最优选择。
通过java倒是可以⽤代码获取路径,但是配置⽂件怎么去写?要去拦截配置参数做修改嘛?这样看起来感觉有点⿇烦,那直接就写⽂件名呢,这样⽂件会在哪⾥建⽴呢,尝试了⼀下直接报错:
A file path that is implicitly relative to current working directory is not allowed in the database URL "jdbc:h2:file:name". Use an absolute path,~/name,./ name, or the baseDir setting instead.[90011-200]
从堆栈进去看到对应报错代码可以可以明显看出这种直接写的⽅式是不受⽀持的,必须增加对应的前置定位。
if(!ains("./")&&
!ains(".\\")&&
!ains(":/")&&
!ains(":\\")){
// the name could start with "./", or
// it could start with a prefix such as "nio:./"
/
/ for Windows, the path "\test" is not considered
// absolute as the drive letter is missing,
// but we consider it absolute
(
ErrorCode.URL_RELATIVE_TO_CWD,
originalURL);
}
报错信息⾥⾯的**~**这个符号对linux了解的都很熟悉,这个表⽰的就是⽤户⽬录,因此将连接直接改为jdbc:h2:file:~/name就把name的数据库建⽴到⽤户空间中了。
项⽬发布优化
根据⾃动执⾏的特性,那么可以将执⾏的脚本按照版本归属(1.0,1.1,1.2…)直接放到启动的jar中,然后再加⼊⼀张version表,然后根据version表和代码维护的迭代信息可以实现脚本按照版本⾃动升级。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论