From 92fe4c55f7b0e4324d7cc3ce304581b8148cf296 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 2 Oct 2025 11:50:33 +0100 Subject: [PATCH] Resolve MariaDB and MySQL dialects through DB query Spring Data JDBC has deprecated its INSTANCE constants in its MariaDB and MySQL dialects as the required configuration for the dialect varies depending on the configuration of the DB. This commit adapts to this deprecation by changing Boot's DataJdbcDatabaseDialect to resolve the underlying dialect through a DB query for its MARIA and MYSQL values. Closes gh-46062 --- module/spring-boot-data-jdbc/build.gradle | 12 +++ ...toConfigurationDialectResolutionTests.java | 92 +++++++++++++++++++ .../DataJdbcDatabaseDialect.java | 36 +++++--- ...DataJdbcRepositoriesAutoConfiguration.java | 3 +- .../build.gradle | 2 + .../boot/testsupport/container/TestImage.java | 6 +- 6 files changed, 137 insertions(+), 14 deletions(-) create mode 100644 module/spring-boot-data-jdbc/src/dockerTest/java/org/springframework/boot/data/jdbc/autoconfigure/DataJdbcRepositoriesAutoConfigurationDialectResolutionTests.java 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.