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
This commit is contained in:
Andy Wilkinson 2025-10-02 11:50:33 +01:00
parent 911578e560
commit 92fe4c55f7
6 changed files with 137 additions and 14 deletions

View File

@ -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")))

View File

@ -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 <C extends JdbcDatabaseContainer<?>> void withContainer(Class<C> containerType,
ThrowingConsumer<ApplicationContextRunner> 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 {
}
}

View File

@ -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<JdbcOperations, Dialect> dialectResolver;
DataJdbcDatabaseDialect(Dialect dialect) {
this.dialect = dialect;
DataJdbcDatabaseDialect(Class<? extends Dialect> 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);
}
}

View File

@ -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);
}
}

View File

@ -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")

View File

@ -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.