DATAJDBC-455 - Adds dynamic Dialect detection.
So far the user had to specify an `Dialect` themselves if they wanted to use anything but HSQLDB. We now identify the supported databases and pick the appropriate `Dialect`. Vendors who want to offer support for their database may provide an implementation of `JdbcDialectProvider` and register it using a file under the key `org.springframework.data.jdbc.repository.config.DialectResolver$JdbcDialectProvider`. Original pull request: #202.
This commit is contained in:
committed by
Mark Paluch
parent
36192c4d97
commit
4c3fa05b6c
@@ -35,7 +35,6 @@ import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.dialect.Dialect;
|
||||
import org.springframework.data.relational.core.dialect.HsqlDbDialect;
|
||||
import org.springframework.data.relational.core.mapping.NamingStrategy;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
|
||||
@@ -132,7 +131,7 @@ public class AbstractJdbcConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
Dialect dialect() {
|
||||
return HsqlDbDialect.INSTANCE;
|
||||
public Dialect dialect(NamedParameterJdbcOperations template) {
|
||||
return JdbcDialectResolver.getDialect(template);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2020 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.data.jdbc.repository.config;
|
||||
|
||||
import java.awt.*;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.dao.NonTransientDataAccessException;
|
||||
import org.springframework.data.relational.core.dialect.Dialect;
|
||||
import org.springframework.data.relational.core.dialect.HsqlDbDialect;
|
||||
import org.springframework.data.relational.core.dialect.MySqlDialect;
|
||||
import org.springframework.data.relational.core.dialect.PostgresDialect;
|
||||
import org.springframework.data.relational.core.dialect.SqlServerDialect;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.jdbc.core.ConnectionCallback;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
|
||||
/**
|
||||
* Resolves a {@link Dialect} from a {@link DataSource} using {@link JdbcDialectProvider}. Dialect resolution uses
|
||||
* Spring's {@link SpringFactoriesLoader spring.factories} to determine available extensions.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @since 2.0
|
||||
* @see Dialect
|
||||
* @see SpringFactoriesLoader
|
||||
*/
|
||||
public class JdbcDialectResolver {
|
||||
|
||||
private static final List<JdbcDialectProvider> DETECTORS = SpringFactoriesLoader
|
||||
.loadFactories(JdbcDialectProvider.class, JdbcDialectResolver.class.getClassLoader());
|
||||
|
||||
// utility constructor.
|
||||
private JdbcDialectResolver() {}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Dialect} by inspecting a {@link DataSource}.
|
||||
*
|
||||
* @param template must not be {@literal null}.
|
||||
* @return the resolved {@link Dialect} {@link NoDialectException} if the database type cannot be determined from
|
||||
* {@link DataSource}.
|
||||
* @throws NoDialectException if no {@link Dialect} can be found.
|
||||
*/
|
||||
public static Dialect getDialect(NamedParameterJdbcOperations template) {
|
||||
|
||||
return DETECTORS.stream() //
|
||||
.map(it -> it.getDialect(template)) //
|
||||
.flatMap(Optionals::toStream) //
|
||||
.findFirst() //
|
||||
.orElseThrow(() -> new NoDialectException(
|
||||
String.format("Cannot determine a dialect for %s. Please provide a Dialect.",
|
||||
template)));
|
||||
}
|
||||
|
||||
/**
|
||||
* SPI to extend Spring's default JDBC Dialect discovery mechanism. Implementations of this interface are discovered
|
||||
* through Spring's {@link SpringFactoriesLoader} mechanism.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @see org.springframework.core.io.support.SpringFactoriesLoader
|
||||
*/
|
||||
public interface JdbcDialectProvider {
|
||||
|
||||
/**
|
||||
* Returns a {@link Dialect} for a {@link DataSource}.
|
||||
*
|
||||
* @param template the {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate} to be used with the {@link Dialect}.
|
||||
* @return {@link Optional} containing the {@link Dialect} if the {@link JdbcDialectProvider} can provide a dialect
|
||||
* object, otherwise {@link Optional#empty()}.
|
||||
*/
|
||||
Optional<Dialect> getDialect(NamedParameterJdbcOperations template);
|
||||
}
|
||||
|
||||
static public class DefaultDialectProvider implements JdbcDialectProvider {
|
||||
|
||||
@Override
|
||||
public Optional<Dialect> getDialect(NamedParameterJdbcOperations template) {
|
||||
|
||||
return template.getJdbcOperations().execute((ConnectionCallback<Optional<Dialect>>) (connection) ->{
|
||||
|
||||
DatabaseMetaData metaData = connection.getMetaData();
|
||||
|
||||
String name = metaData.getDatabaseProductName().toLowerCase();
|
||||
|
||||
if (name.contains("hsql")) {
|
||||
return Optional.of(HsqlDbDialect.INSTANCE);
|
||||
}
|
||||
if (name.contains("mysql")) { // catches also mariadb
|
||||
return Optional.of(MySqlDialect.INSTANCE);
|
||||
}
|
||||
if (name.contains("postgresql")) {
|
||||
return Optional.of(PostgresDialect.INSTANCE);
|
||||
}
|
||||
if (name.contains("microsoft")) {
|
||||
return Optional.of(SqlServerDialect.INSTANCE);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown when {@link JdbcDialectResolver} cannot resolve a {@link Dialect}.
|
||||
*/
|
||||
public static class NoDialectException extends NonTransientDataAccessException {
|
||||
|
||||
/**
|
||||
* Constructor for NoDialectFoundException.
|
||||
*
|
||||
* @param msg the detail message
|
||||
*/
|
||||
public NoDialectException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
org.springframework.data.repository.core.support.RepositoryFactorySupport=org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory
|
||||
org.springframework.data.jdbc.repository.config.JdbcDialectResolver$JdbcDialectProvider=org.springframework.data.jdbc.repository.config.JdbcDialectResolver.DefaultDialectProvider
|
||||
@@ -32,6 +32,8 @@ import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.relational.core.dialect.Dialect;
|
||||
import org.springframework.data.relational.core.dialect.HsqlDbDialect;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
@@ -58,7 +60,7 @@ public class AbstractJdbcConfigurationIntegrationTests {
|
||||
.map(context::getBean) //
|
||||
.forEach(it -> assertThat(it).isNotNull());
|
||||
|
||||
}, AbstractJdbcConfiguration.class, Infrastructure.class);
|
||||
}, AbstractJdbcConfigurationUnderTest.class, Infrastructure.class);
|
||||
}
|
||||
|
||||
protected static void assertApplicationContext(Consumer<ConfigurableApplicationContext> verification,
|
||||
@@ -74,7 +76,7 @@ public class AbstractJdbcConfigurationIntegrationTests {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Infrastructure {
|
||||
static class Infrastructure {
|
||||
|
||||
@Bean
|
||||
public NamedParameterJdbcOperations jdbcOperations() {
|
||||
@@ -83,4 +85,14 @@ public class AbstractJdbcConfigurationIntegrationTests {
|
||||
return new NamedParameterJdbcTemplate(jdbcOperations);
|
||||
}
|
||||
}
|
||||
|
||||
static class AbstractJdbcConfigurationUnderTest extends AbstractJdbcConfiguration {
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public Dialect dialect(NamedParameterJdbcOperations template) {
|
||||
return HsqlDbDialect.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -156,5 +156,10 @@ public class EnableJdbcRepositoriesIntegrationTests {
|
||||
return new DefaultDataAccessStrategy(new SqlGeneratorSource(context, converter, dialect), context, converter,
|
||||
template);
|
||||
}
|
||||
|
||||
@Bean
|
||||
Dialect dialect(@Qualifier("qualifierJdbcOperations") NamedParameterJdbcOperations template) {
|
||||
return JdbcDialectResolver.getDialect(template);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jdbc.core.convert.CascadingDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.mybatis.MyBatisDataAccessStrategy;
|
||||
import org.springframework.data.relational.core.dialect.Dialect;
|
||||
import org.springframework.data.relational.core.dialect.HsqlDbDialect;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
/**
|
||||
@@ -50,7 +53,7 @@ public class MyBatisJdbcConfigurationIntegrationTests extends AbstractJdbcConfig
|
||||
assertThat(strategies.get(0)).isInstanceOf(MyBatisDataAccessStrategy.class);
|
||||
});
|
||||
|
||||
}, MyBatisJdbcConfiguration.class, MyBatisInfrastructure.class);
|
||||
}, MyBatisJdbcConfigurationUnderTest.class, MyBatisInfrastructure.class);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -61,4 +64,13 @@ public class MyBatisJdbcConfigurationIntegrationTests extends AbstractJdbcConfig
|
||||
return mock(SqlSession.class);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MyBatisJdbcConfigurationUnderTest extends MyBatisJdbcConfiguration {
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public Dialect dialect(NamedParameterJdbcOperations template) {
|
||||
return HsqlDbDialect.INSTANCE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,9 +49,4 @@ class HsqlDataSourceConfiguration {
|
||||
.addScript(TestUtils.createScriptName(context, "hsql")) //
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Dialect dialect() {
|
||||
return HsqlDbDialect.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,8 @@ import javax.script.ScriptException;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.mariadb.jdbc.MariaDbDataSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.relational.core.dialect.Dialect;
|
||||
import org.springframework.data.relational.core.dialect.MariaDbDialect;
|
||||
import org.testcontainers.containers.MariaDBContainer;
|
||||
import org.testcontainers.jdbc.ext.ScriptUtils;
|
||||
|
||||
@@ -64,11 +61,6 @@ class MariaDBDataSourceConfiguration extends DataSourceConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
Dialect dialect() {
|
||||
return MariaDbDialect.INSTANCE;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void initDatabase() throws SQLException, ScriptException {
|
||||
ScriptUtils.executeSqlScript(createDataSource().getConnection(), null, "DROP DATABASE test;CREATE DATABASE test;");
|
||||
|
||||
@@ -47,6 +47,7 @@ public class MsSqlDataSourceConfiguration extends DataSourceConfiguration {
|
||||
*/
|
||||
@Override
|
||||
protected DataSource createDataSource() {
|
||||
|
||||
SQLServerDataSource sqlServerDataSource = new SQLServerDataSource();
|
||||
sqlServerDataSource.setURL(mssqlserver.getJdbcUrl());
|
||||
sqlServerDataSource.setUser(mssqlserver.getUsername());
|
||||
|
||||
@@ -65,11 +65,6 @@ class MySqlDataSourceConfiguration extends DataSourceConfiguration {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
Dialect dialect() {
|
||||
return MySqlDialect.INSTANCE;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void initDatabase() throws SQLException, ScriptException {
|
||||
ScriptUtils.executeSqlScript(createDataSource().getConnection(), null, "DROP DATABASE test;CREATE DATABASE test;");
|
||||
|
||||
@@ -58,11 +58,6 @@ public class PostgresDataSourceConfiguration extends DataSourceConfiguration {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
Dialect dialect() {
|
||||
return PostgresDialect.INSTANCE;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.testing.DataSourceFactoryBean#customizePopulator(org.springframework.jdbc.datasource.init.ResourceDatabasePopulator)
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
|
||||
import org.springframework.data.jdbc.core.convert.RelationResolver;
|
||||
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.repository.config.JdbcDialectResolver;
|
||||
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
|
||||
import org.springframework.data.relational.core.dialect.Dialect;
|
||||
import org.springframework.data.relational.core.mapping.NamingStrategy;
|
||||
@@ -122,4 +123,9 @@ public class TestConfiguration {
|
||||
dialect.getIdentifierProcessing()
|
||||
);
|
||||
}
|
||||
|
||||
@Bean
|
||||
Dialect dialect(NamedParameterJdbcOperations template) {
|
||||
return JdbcDialectResolver.getDialect(template);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
microsoft/mssql-server-linux:2017-CU6
|
||||
mcr.microsoft.com/mssql/server:2017-CU12
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.data.relational.core.dialect;
|
||||
|
||||
/**
|
||||
* The {@link Dialect} to be used with MariaDb. Since we haven't encountered any significant differences between MariaDb
|
||||
* and Mysql its instance is actually {@link MySqlDialect#INSTANCE}, although that might change at any time.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class MariaDbDialect extends MySqlDialect{
|
||||
|
||||
public static final Dialect INSTANCE = MySqlDialect.INSTANCE;
|
||||
|
||||
protected MariaDbDialect() { }
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.data.relational.core.dialect;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MariaDbDialect}.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class MariaDbDialectUnitTests {
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldNotSupportArrays() {
|
||||
|
||||
ArrayColumns arrayColumns = MariaDbDialect.INSTANCE.getArraySupport();
|
||||
|
||||
assertThat(arrayColumns.isSupported()).isFalse();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderLimit() {
|
||||
|
||||
LimitClause limit = MariaDbDialect.INSTANCE.limit();
|
||||
|
||||
assertThat(limit.getClausePosition()).isEqualTo(LimitClause.Position.AFTER_ORDER_BY);
|
||||
assertThat(limit.getLimit(10)).isEqualTo("LIMIT 10");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderOffset() {
|
||||
|
||||
LimitClause limit = MariaDbDialect.INSTANCE.limit();
|
||||
|
||||
assertThat(limit.getOffset(10)).isEqualTo("LIMIT 10, 18446744073709551615");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderLimitOffset() {
|
||||
|
||||
LimitClause limit = MariaDbDialect.INSTANCE.limit();
|
||||
|
||||
assertThat(limit.getLimitOffset(20, 10)).isEqualTo("LIMIT 10, 20");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-386
|
||||
public void shouldQuoteIdentifiersUsingBackticks() {
|
||||
|
||||
String abcQuoted = MariaDbDialect.INSTANCE.getIdentifierProcessing().quote("abc");
|
||||
|
||||
assertThat(abcQuoted).isEqualTo("`abc`");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user