Spring Boot 升级后,初始化SQL不跑了?

Spring Boot 单元测试自动执行测试脚本

起因

最近在给我司的微服务做架构升级,从 SpringBoot 1.5.x 升到 2.x。期间对配置做了一些改动,加上 SpringBoot 自身的配置变动,导致升级完以后,单元测试跑不过了。

我司的每一次代码合并,都需要保证通过单元测试。

究其原因,是因为数据不满足测试。数据都是一次性使用,每次进行单元测试时,数据脚本都会将上次新建的数据删除,再重新新建一份。

1.5.x

在1.5.x版本中, 数据脚本的执行,依赖于 Spring Boot 的自动化配置 DataSourceAutoConfigurationDataSourceInitializer。实际脚本的执行时依赖于 DataSourceInitializer,不过DataSourceInitializer 的创建又依赖于 DataSourceAutoConfiguration。所以需要一并分析这两个类。

当然,如果对于 Spring 容器管理 Bean 生命周期比较熟悉的同学,其实 DataSourceAutoConfiguration 是可以略过的。

DataSourceAutoConfiguration

DataSourceAutoConfiguration 实现了对 DataSource 的自动化配置(实际就是多种 @Conditional 注解的 Configuration 类),不过由于其支持的 DataSource 类型过于单一,所以一般不可能依赖于其自身的 DataSource 初始化。

我司的微服务,都是实现了自己的 DataSource 自动化配置,使用的是 DruidDataSource。

@Configuration
@ConditionalOnClass({
    DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({
    Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
public class DataSourceAutoConfiguration {
   

     // 初始化时,注入 DataSourceProperties 和 上下文
	@Bean
	@ConditionalOnMissingBean
	public DataSourceInitializer dataSourceInitializer(DataSourceProperties properties,
			ApplicationContext applicationContext) {
   
		return new DataSourceInitializer(properties, applicationContext);
	}
	/* 省略无关的代码*/
}

DataSourceAutoConfiguration 最主要的作用是声明了DataSourceInitializer的实例化,并通过 @Import 依赖初始化了 Registrar

@EnableConfigurationProperties(DataSourceProperties.class) 同时初始化了 DataSourceProperties 类。

如果依赖数据源自动化配置的话,那么数据源的配置时依靠 DataSourceProperties 所存储的配置。
但是由于我司的数据源配置是自行配置初始化的,所以 DataSourceProperties 存在的意义就是自动执行测试脚本时的配置.

关键配置如下(1.5.x 与 2.x 相同):

# 定义自动化脚本的 DML 脚本
spring.datasource.schema=
# 定义自动化脚本的 DDL 脚本
spring.datasource.data=
# 由于已初始化数据源, 因此用不上的数据源配置
# 用于执行脚本时, 初始化数据源
spring.datasource.schemaUsername=
spring.datasource.schemaPassword=
spring.datasource.dataUsername=
spring.datasource.dataPassword=

对应的 DataSourceProperties 属性如下:

/**
 * Schema (DDL) script resource references.
 */
private List<String> schema;

/**
 * User of the database to execute DDL scripts (if different).
 */
private String schemaUsername;

/**
 * Password of the database to execute DDL scripts (if different).
 */
private String schemaPassword;

/**
 * Data (DML) script resource references.
 */
private List<String> data;

/**
 * User of the database to execute DML scripts.
 */
private String dataUsername;

/**
 * Password of the database to execute DML scripts.
 */
private String dataPassword;

那么 Registrar 的作用是什么?
其本身是为了注入一个 PostProcessor, 而 PostProcessor 的作用是为了在初始化 DataSource 之后立即初始化 DataSourceInitializer。

class DataSourceInitializerPostProcessor implements BeanPostProcessor, Ordered {

	private int order = Ordered.HIGHEST_PRECEDENCE;

	@Override
	// 最高优先级初始化、执行
	public int getOrder() {
		return this.order;
	}

	@Autowired
	private BeanFactory beanFactory;

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName)
			throws BeansException {
		if (bean instanceof DataSource) {
			// force initialization of this bean as soon as we see a DataSource
			// 当 DataSource 初始化之后, 立即强制初始化 DataSourceInitializer
			this.beanFactory.getBean(DataSourceInitializer.class);
		}
		return bean;
	}

	/**
	 * {@link ImportBeanDefinitionRegistrar} to register the
	 * {@link DataSourceInitializerPostProcessor} without causing early bean instantiation
	 * issues.
	 */
	static class Registrar implements ImportBeanDefinitionRegistrar {

		private static final String BEAN_NAME = "dataSourceInitializerPostProcessor";

		@Override
		public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
				BeanDefinitionRegistry registry) {
				// 为了注入 DataSourceInitializerPostProcessor 的 BeanDefinition
				// 保证容器对其初始化
			if (!registry.containsBeanDefinition(BEAN_NAME)) {
				GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
				beanDefinition.setBeanClass(DataSourceInitializerPostProcessor.class);
				beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
				// We don't need this one to be post processed otherwise it can cause a
				// cascade of bean instantiation that we would rather avoid.
				beanDefinition.setSynthetic(true);
				registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
			}
		}
	}
}
DataSourceInitializer

接下来看看主要的 DataSourceInitializer。其负责脚本的自动执行,入口有两个:初始化后触发和事件触发。

class DataSourceInitializer implements ApplicationListener<DataSourceInitializedEvent> {
   
    /* 所需的属性、数据源、上下文*/
	private final DataSourceProperties properties;

	private final ApplicationContext applicationContext;

	private DataSource dataSource;
    /* 标识是否已初始化脚本, 全局只执行一次 */
	private boolean initialized = false;

	@PostConstruct
	public void init() {
   
	    // 配置 spring.datasource.initialize
	    // 默认为true, 也就是说 1.5.x 默认是自动执行初始化脚本的
		if (!this.properties.isInitialize()) {
   
			logger.debug("Initialization disabled (not running DDL scripts)");
			return;
		}
		// 获取 DataSource
		if (this.applicationContext.getBeanNamesForType(DataSource.class, false,
				false).length > 0) {
   
			this.dataSource = this.applicationContext.getBean(DataSource.class);
		}
		if (this.dataSource == null) {
   
			logger.debug("No DataSource found so not initializing");
			return;
		}
		// 执行 DML 脚本
		runSchemaScripts();
	}

	@Override
	public void onApplicationEvent(DataSourceInitializedEvent event) {
   
	    // 响应 DataSourceInitializedEvent(DataSource source) 事件
	    // 配置判断
		if (!this.properties.isInitialize
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值