【原理解析】SpringBoot启动执行SQL脚本原理解析

使用方式

application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/db1?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: root
  sql:
    init:
      mode: embedded
      schema-locations:
        - classpath:/db/schema_v1.sql
        - classpath:/db/schema.sql
      data-locations:
        - classpath:/db/data_v1.sql

schema.sql

create table  if not exists app_excel_record
(
    id            bigint unsigned auto_increment comment 'ID'
        primary key,
    file_name     varchar(64) default '' not null comment '文件名称',
    module        varchar(64) default '' not null comment '模块',
    create_time   datetime               null comment '创建时间',
    update_time   datetime               null comment '更新时间',
    create_by     int                    null comment '创建人',
    file_url      varchar(200)           null comment '文件绝对路径',
    record_status int                    null comment '导出状态',
    result_info   int                    null comment '导出结果'
) comment '导出记录表';

效果:系统启动会执行app_excel_record的创表语句。

源码解析

DataSourceAutoConfiguration

DataSourceAutoConfiguration会优先注入SqlInitializationAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@AutoConfigureBefore(SqlInitializationAutoConfiguration.class)
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
      DataSourceInitializationConfiguration.InitializationSpecificCredentialsDataSourceInitializationConfiguration.class,
      DataSourceInitializationConfiguration.SharedCredentialsDataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
    
}

SqlInitializationAutoConfiguration

SqlInitializationAutoConfiguration该类注入会判断SqlInitializationModeCondition条件是否符合,当spring.sql.init.mode不等于never,会注入到Spring容器中。

@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter({ R2dbcAutoConfiguration.class, DataSourceAutoConfiguration.class })
@EnableConfigurationProperties(SqlInitializationProperties.class)
@Import({ DatabaseInitializationDependencyConfigurer.class, R2dbcInitializationConfiguration.class,
		DataSourceInitializationConfiguration.class })
@ConditionalOnProperty(prefix = "spring.sql.init", name = "enabled", matchIfMissing = true)
@Conditional(SqlInitializationModeCondition.class)
public class SqlInitializationAutoConfiguration {

	static class SqlInitializationModeCondition extends NoneNestedConditions {

		SqlInitializationModeCondition() {
			super(ConfigurationPhase.PARSE_CONFIGURATION);
		}

		@ConditionalOnProperty(prefix = "spring.sql.init", name = "mode", havingValue = "never")
		static class ModeIsNever {

		}

	}

}

DataSourceInitializationConfiguration

DataSourceInitializationConfiguration注入了SqlDataSourceScriptDatabaseInitializer

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean({ SqlDataSourceScriptDatabaseInitializer.class, SqlR2dbcScriptDatabaseInitializer.class })
@ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnClass(DatabasePopulator.class)
class DataSourceInitializationConfiguration {

	@Bean
	SqlDataSourceScriptDatabaseInitializer dataSourceScriptDatabaseInitializer(DataSource dataSource,
			SqlInitializationProperties properties) {
		return new SqlDataSourceScriptDatabaseInitializer(
				determineDataSource(dataSource, properties.getUsername(), properties.getPassword()), properties);
	}

	private static DataSource determineDataSource(DataSource dataSource, String username, String password) {
		if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
			return DataSourceBuilder.derivedFrom(dataSource).username(username).password(password)
					.type(SimpleDriverDataSource.class).build();
		}
		return dataSource;
	}

}

SqlDataSourceScriptDatabaseInitializer

SqlDataSourceScriptDatabaseInitializer继承了AbstractScriptDatabaseInitializerAbstractScriptDatabaseInitializer是实现了InitializingBean接口的。所以在启动的时候会执行AbstractScriptDatabaseInitializer#afterPropertiesSet

    public void afterPropertiesSet() throws Exception {
        this.initializeDatabase();
    }

    public boolean initializeDatabase() {
        AbstractScriptDatabaseInitializer.ScriptLocationResolver locationResolver = new AbstractScriptDatabaseInitializer.ScriptLocationResolver(this.resourceLoader);
        boolean initialized = this.applySchemaScripts(locationResolver);
        return this.applyDataScripts(locationResolver) || initialized;
    }

AbstractScriptDatabaseInitializer#applySchemaScripts

    private boolean applySchemaScripts(AbstractScriptDatabaseInitializer.ScriptLocationResolver locationResolver) {
        return this.applyScripts(this.settings.getSchemaLocations(), "schema", locationResolver);
    }

    private boolean applyDataScripts(AbstractScriptDatabaseInitializer.ScriptLocationResolver locationResolver) {
        return this.applyScripts(this.settings.getDataLocations(), "data", locationResolver);
    }

    private boolean applyScripts(List<String> locations, String type, AbstractScriptDatabaseInitializer.ScriptLocationResolver locationResolver) {
        List<Resource> scripts = this.getScripts(locations, type, locationResolver);
        if (!scripts.isEmpty() && this.isEnabled()) {
            this.runScripts(scripts);
            return true;
        } else {
            return false;
        }
    }

    private void runScripts(List<Resource> resources) {
        this.runScripts(resources, this.settings.isContinueOnError(), this.settings.getSeparator(), this.settings.getEncoding());
    }

    protected abstract void runScripts(List<Resource> resources, boolean continueOnError, String separator, Charset encoding);

DataSourceScriptDatabaseInitializer#runScripts,执行脚本

    protected void runScripts(List<Resource> resources, boolean continueOnError, String separator, Charset encoding) {
        ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
        populator.setContinueOnError(continueOnError);
        populator.setSeparator(separator);
        if (encoding != null) {
            populator.setSqlScriptEncoding(encoding.name());
        }

        Iterator var6 = resources.iterator();

        while(var6.hasNext()) {
            Resource resource = (Resource)var6.next();
            populator.addScript(resource);
        }

        this.customize(populator);
        DatabasePopulatorUtils.execute(populator, this.dataSource);
    }

实战

https://gitee.com/charles_ruan/easy-export

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值