GH-507 - Improve database schema initialization for event publications.

We now use ResourceDatabasePopulator to execute the schema files creating the infrastructure for the event publication registry. This makes sure that the statements in those files are executed individually.

Related ticket: GH-403.
This commit is contained in:
Oliver Drotbohm
2024-02-20 12:52:51 +01:00
parent 864baac3c2
commit aa4b34c396
4 changed files with 68 additions and 60 deletions

View File

@@ -15,15 +15,7 @@
*/
package org.springframework.modulith.events.jdbc;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.util.StreamUtils;
/**
* Initializes the DB schema used to store events
@@ -32,48 +24,4 @@ import org.springframework.util.StreamUtils;
* @author Björn Kieling
* @author Oliver Drotbohm
*/
class DatabaseSchemaInitializer implements InitializingBean {
private final JdbcOperations jdbcOperations;
private final ResourceLoader resourceLoader;
private final DatabaseType databaseType;
/**
* Creates a new {@link DatabaseSchemaInitializer} for the given {@link JdbcOperations}, {@link ResourceLoader} and
* {@link DatabaseType}.
*
* @param jdbcOperations must not be {@literal null}.
* @param resourceLoader must not be {@literal null}.
* @param databaseType must not be {@literal null}.
*/
public DatabaseSchemaInitializer(JdbcOperations jdbcOperations, ResourceLoader resourceLoader,
DatabaseType databaseType) {
this.jdbcOperations = jdbcOperations;
this.resourceLoader = resourceLoader;
this.databaseType = databaseType;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
var schemaResourceFilename = databaseType.getSchemaResourceFilename();
var schemaDdlResource = resourceLoader.getResource(ResourceLoader.CLASSPATH_URL_PREFIX + schemaResourceFilename);
var schemaDdl = asString(schemaDdlResource);
jdbcOperations.execute(schemaDdl);
}
private static String asString(Resource resource) {
try {
return StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
interface DatabaseSchemaInitializer extends InitializingBean {}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.events.jdbc;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
/**
* Simple wrapper around a {@link ResourceLoader} to load database specific schema files from the classpath.
*
* @author Oliver Drotbohm
*/
public class DatabaseSchemaLocator {
private final ResourceLoader resourceLoader;
/**
* Creates a new {@link DatabaseSchemaLocator} for the given {@link ResourceLoader}.
*
* @param resourceLoader must not be {@literal null}.
*/
DatabaseSchemaLocator(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null!");
this.resourceLoader = resourceLoader;
}
/**
* Loads the {@link Resource} copntaining the schema for the given {@link DatabaseType} from the classpath.
*
* @param databaseType must not be {@literal null}.
* @return will never be {@literal null}.
*/
Resource getSchemaResource(DatabaseType databaseType) {
Assert.notNull(databaseType, "DatabaseType must not be null!");
var schemaResourceFilename = databaseType.getSchemaResourceFilename();
return resourceLoader.getResource(ResourceLoader.CLASSPATH_URL_PREFIX + schemaResourceFilename);
}
}

View File

@@ -25,6 +25,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.modulith.events.config.EventPublicationAutoConfiguration;
import org.springframework.modulith.events.config.EventPublicationConfigurationExtension;
@@ -53,10 +54,15 @@ class JdbcEventPublicationAutoConfiguration implements EventPublicationConfigura
@Bean
@ConditionalOnProperty(name = "spring.modulith.events.jdbc.schema-initialization.enabled", havingValue = "true")
DatabaseSchemaInitializer databaseSchemaInitializer(JdbcTemplate jdbcTemplate, ResourceLoader resourceLoader,
DatabaseSchemaInitializer databaseSchemaInitializer(DataSource dataSource, ResourceLoader resourceLoader,
DatabaseType databaseType) {
return new DatabaseSchemaInitializer(jdbcTemplate, resourceLoader, databaseType);
return () -> {
var locator = new DatabaseSchemaLocator(resourceLoader);
new ResourceDatabasePopulator(locator.getSchemaResource(databaseType)).execute(dataSource);
};
}
private static String fromDataSource(DataSource dataSource) {

View File

@@ -26,7 +26,6 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.core.JdbcOperations;
/**
* Unit tests for {@link DatabaseSchemaInitializer}.
@@ -34,9 +33,8 @@ import org.springframework.jdbc.core.JdbcOperations;
* @author Oliver Drotbohm
*/
@ExtendWith(MockitoExtension.class)
class DatabaseSchemaInitializerUnitTests {
public class DatabaseSchemaLocatorUnitTests {
@Mock JdbcOperations jdbcOperations;
@Mock ResourceLoader resourceLoader;
@Test // GH-159
@@ -46,10 +44,10 @@ class DatabaseSchemaInitializerUnitTests {
return new ClassPathResource(it.<String> getArgument(0).substring(ResourceLoader.CLASSPATH_URL_PREFIX.length()));
});
var initializer = new DatabaseSchemaInitializer(jdbcOperations, resourceLoader, DatabaseType.H2);
var locator = new DatabaseSchemaLocator(resourceLoader);
var captor = ArgumentCaptor.forClass(String.class);
assertThatNoException().isThrownBy(initializer::afterPropertiesSet);
assertThatNoException().isThrownBy(() -> locator.getSchemaResource(DatabaseType.H2));
verify(resourceLoader).getResource(captor.capture());
assertThat(captor.getValue()).startsWith(ResourceLoader.CLASSPATH_URL_PREFIX);
}