diff --git a/module/spring-boot-data-jdbc/build.gradle b/module/spring-boot-data-jdbc/build.gradle index 4f90adec032..7eb108e56af 100644 --- a/module/spring-boot-data-jdbc/build.gradle +++ b/module/spring-boot-data-jdbc/build.gradle @@ -19,6 +19,7 @@ plugins { id "org.springframework.boot.auto-configuration" id "org.springframework.boot.configuration-properties" id "org.springframework.boot.deployed" + id "org.springframework.boot.docker-test" id "org.springframework.boot.optional-dependencies" } @@ -31,6 +32,17 @@ dependencies { optional(project(":core:spring-boot-autoconfigure")) + dockerTestImplementation(project(":core:spring-boot-test")) + dockerTestImplementation(project(":test-support:spring-boot-docker-test-support")) + dockerTestImplementation(testFixtures(project(":core:spring-boot-autoconfigure"))) + dockerTestImplementation("ch.qos.logback:logback-classic") + dockerTestImplementation("org.testcontainers:mariadb") + dockerTestImplementation("org.testcontainers:mysql") + + dockerTestRuntimeOnly("com.mysql:mysql-connector-j") + dockerTestRuntimeOnly("com.zaxxer:HikariCP") + dockerTestRuntimeOnly("org.mariadb.jdbc:mariadb-java-client") + testImplementation(project(":core:spring-boot-test")) testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(testFixtures(project(":core:spring-boot-autoconfigure"))) diff --git a/module/spring-boot-data-jdbc/src/dockerTest/java/org/springframework/boot/data/jdbc/autoconfigure/DataJdbcRepositoriesAutoConfigurationDialectResolutionTests.java b/module/spring-boot-data-jdbc/src/dockerTest/java/org/springframework/boot/data/jdbc/autoconfigure/DataJdbcRepositoriesAutoConfigurationDialectResolutionTests.java new file mode 100644 index 00000000000..49794bdd81e --- /dev/null +++ b/module/spring-boot-data-jdbc/src/dockerTest/java/org/springframework/boot/data/jdbc/autoconfigure/DataJdbcRepositoriesAutoConfigurationDialectResolutionTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-present 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.boot.data.jdbc.autoconfigure; + +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.containers.MySQLContainer; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; +import org.springframework.boot.data.jdbc.domain.city.City; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.data.jdbc.core.dialect.JdbcMySqlDialect; +import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.dialect.MariaDbDialect; +import org.springframework.util.function.ThrowingConsumer; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DataJdbcRepositoriesAutoConfiguration} when the configured dialect + * requires resolution using a database connection. + * + * @author Andy Wilkinson + */ +class DataJdbcRepositoriesAutoConfigurationDialectResolutionTests { + + @Test + void resolvesMariaDbDialect() { + withContainer(MariaDBContainer.class, (runner) -> { + runner.withPropertyValues("spring.data.jdbc.dialect=maria").run((context) -> { + Dialect dialect = context.getBean(Dialect.class); + assertThat(dialect).isInstanceOf(MariaDbDialect.class); + }); + }); + } + + @Test + void resolvesMySqlDialect() { + withContainer(MySQLContainer.class, (runner) -> { + runner.withPropertyValues("spring.data.jdbc.dialect=mysql").run((context) -> { + Dialect dialect = context.getBean(Dialect.class); + assertThat(dialect).isInstanceOf(JdbcMySqlDialect.class); + }); + }); + } + + private > void withContainer(Class containerType, + ThrowingConsumer callback) { + C container = TestImage.container(containerType); + try { + container.start(); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, + DataJdbcRepositoriesAutoConfiguration.class, JdbcTemplateAutoConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class)) + .withUserConfiguration(TestConfiguration.class) + .withPropertyValues("spring.datasource.url=" + container.getJdbcUrl(), + "spring.datasource.username=" + container.getUsername(), + "spring.datasource.password=" + container.getPassword()); + callback.accept(contextRunner); + } + finally { + container.close(); + } + } + + @TestAutoConfigurationPackage(City.class) + static class TestConfiguration { + + } + +} diff --git a/module/spring-boot-data-jdbc/src/main/java/org/springframework/boot/data/jdbc/autoconfigure/DataJdbcDatabaseDialect.java b/module/spring-boot-data-jdbc/src/main/java/org/springframework/boot/data/jdbc/autoconfigure/DataJdbcDatabaseDialect.java index 1da0329c485..487caf1faf9 100644 --- a/module/spring-boot-data-jdbc/src/main/java/org/springframework/boot/data/jdbc/autoconfigure/DataJdbcDatabaseDialect.java +++ b/module/spring-boot-data-jdbc/src/main/java/org/springframework/boot/data/jdbc/autoconfigure/DataJdbcDatabaseDialect.java @@ -16,14 +16,20 @@ package org.springframework.boot.data.jdbc.autoconfigure; +import java.util.function.Function; + +import org.springframework.data.jdbc.core.dialect.DialectResolver; import org.springframework.data.jdbc.core.dialect.JdbcDb2Dialect; import org.springframework.data.jdbc.core.dialect.JdbcH2Dialect; import org.springframework.data.jdbc.core.dialect.JdbcHsqlDbDialect; +import org.springframework.data.jdbc.core.dialect.JdbcMariaDbDialect; import org.springframework.data.jdbc.core.dialect.JdbcMySqlDialect; import org.springframework.data.jdbc.core.dialect.JdbcOracleDialect; import org.springframework.data.jdbc.core.dialect.JdbcPostgresDialect; import org.springframework.data.jdbc.core.dialect.JdbcSqlServerDialect; import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.util.Assert; /** * List of database dialects that can be configured in Boot for use with Spring Data JDBC. @@ -49,16 +55,16 @@ public enum DataJdbcDatabaseDialect { HSQL(JdbcHsqlDbDialect.INSTANCE), /** - * Provides an instance of {@link JdbcMySqlDialect}. + * Resolves an instance of {@link JdbcMariaDbDialect} by querying the database + * configuration. */ - @SuppressWarnings("removal") - MARIA(JdbcMySqlDialect.INSTANCE), + MARIA(JdbcMariaDbDialect.class), /** - * Provides an instance of {@link JdbcMySqlDialect}. + * Resolves an instance of {@link JdbcMySqlDialect} by querying the database + * configuration. */ - @SuppressWarnings("removal") - MYSQL(JdbcMySqlDialect.INSTANCE), + MYSQL(JdbcMySqlDialect.class), /** * Provides an instance of {@link JdbcOracleDialect}. @@ -75,14 +81,22 @@ public enum DataJdbcDatabaseDialect { */ SQL_SERVER(JdbcSqlServerDialect.INSTANCE); - private final Dialect dialect; + private final Function dialectResolver; - DataJdbcDatabaseDialect(Dialect dialect) { - this.dialect = dialect; + DataJdbcDatabaseDialect(Class dialectType) { + this.dialectResolver = (jdbc) -> { + Dialect dialect = DialectResolver.getDialect(jdbc); + Assert.isInstanceOf(dialectType, dialect); + return dialect; + }; } - final Dialect getDialect() { - return this.dialect; + DataJdbcDatabaseDialect(Dialect dialect) { + this.dialectResolver = (jdbc) -> dialect; + } + + Dialect getDialect(JdbcOperations jdbc) { + return this.dialectResolver.apply(jdbc); } } diff --git a/module/spring-boot-data-jdbc/src/main/java/org/springframework/boot/data/jdbc/autoconfigure/DataJdbcRepositoriesAutoConfiguration.java b/module/spring-boot-data-jdbc/src/main/java/org/springframework/boot/data/jdbc/autoconfigure/DataJdbcRepositoriesAutoConfiguration.java index 91dea356956..f5bb18817ca 100644 --- a/module/spring-boot-data-jdbc/src/main/java/org/springframework/boot/data/jdbc/autoconfigure/DataJdbcRepositoriesAutoConfiguration.java +++ b/module/spring-boot-data-jdbc/src/main/java/org/springframework/boot/data/jdbc/autoconfigure/DataJdbcRepositoriesAutoConfiguration.java @@ -147,7 +147,8 @@ public final class DataJdbcRepositoriesAutoConfiguration { @ConditionalOnMissingBean public Dialect jdbcDialect(NamedParameterJdbcOperations operations) { DataJdbcDatabaseDialect dialect = this.properties.getDialect(); - return (dialect != null) ? dialect.getDialect() : super.jdbcDialect(operations); + return (dialect != null) ? dialect.getDialect(operations.getJdbcOperations()) + : super.jdbcDialect(operations); } } diff --git a/test-support/spring-boot-docker-test-support/build.gradle b/test-support/spring-boot-docker-test-support/build.gradle index d3bf2b45046..1773b749c0a 100644 --- a/test-support/spring-boot-docker-test-support/build.gradle +++ b/test-support/spring-boot-docker-test-support/build.gradle @@ -38,7 +38,9 @@ dependencies { optional("org.testcontainers:junit-jupiter") optional("org.testcontainers:kafka") optional("org.testcontainers:ldap") + optional("org.testcontainers:mariadb") optional("org.testcontainers:mongodb") + optional("org.testcontainers:mysql") optional("org.testcontainers:neo4j") optional("org.testcontainers:oracle-xe") optional("org.testcontainers:oracle-free") diff --git a/test-support/spring-boot-docker-test-support/src/main/java/org/springframework/boot/testsupport/container/TestImage.java b/test-support/spring-boot-docker-test-support/src/main/java/org/springframework/boot/testsupport/container/TestImage.java index 37c311b9985..2a439be0169 100644 --- a/test-support/spring-boot-docker-test-support/src/main/java/org/springframework/boot/testsupport/container/TestImage.java +++ b/test-support/spring-boot-docker-test-support/src/main/java/org/springframework/boot/testsupport/container/TestImage.java @@ -30,7 +30,9 @@ import org.testcontainers.activemq.ArtemisContainer; import org.testcontainers.cassandra.CassandraContainer; import org.testcontainers.containers.Container; import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.MariaDBContainer; import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.containers.MySQLContainer; import org.testcontainers.containers.Neo4jContainer; import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.containers.PulsarContainer; @@ -149,7 +151,7 @@ public enum TestImage { /** * A container image suitable for testing MariaDB. */ - MARIADB("mariadb", "10.10"), + MARIADB("mariadb", "10.10", () -> MariaDBContainer.class), /** * A container image suitable for testing MongoDB. @@ -168,7 +170,7 @@ public enum TestImage { /** * A container image suitable for testing MySQL. */ - MYSQL("mysql", "8.0"), + MYSQL("mysql", "8.0", () -> MySQLContainer.class), /** * A container image suitable for testing Neo4j.