From 35589af3d39b0c7bff3c538b61ed1b63dcba5f25 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 28 May 2021 12:58:35 +0200 Subject: [PATCH] Avoid conversion warnings for store specific simple types. Dialects now can define a set of known simple types the driver can handle without further interaction. This is done to avoid warnings during converter registration for types known in one environment but not the other. Also move types around a bit, change visibility and make sure jdbc specific dialects inherit converters from their parents. Original pull request #981 See #935 --- .../core/convert/JdbcCustomConversions.java | 15 ++++++- .../jdbc/core/dialect/JdbcDb2Dialect.java | 24 +++++++++++ .../data/jdbc/core/dialect/JdbcH2Dialect.java | 25 +++++++---- .../jdbc/core/dialect/JdbcMySqlDialect.java | 4 ++ .../core/dialect/JdbcSqlServerDialect.java | 10 ++++- .../OffsetDateTimeToTimestampConverter.java | 41 ------------------- .../config/AbstractJdbcConfiguration.java | 10 ++++- ...DateTimeToTimestampConverterUnitTests.java | 6 +-- .../data/jdbc/testing/TestConfiguration.java | 13 ++++-- .../data/relational/core/dialect/Dialect.java | 12 ++++++ .../relational/core/dialect/H2Dialect.java | 21 ++++++++++ 11 files changed, 118 insertions(+), 63 deletions(-) delete mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/OffsetDateTimeToTimestampConverter.java diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java index efad38c1..97a5b3cb 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java @@ -16,6 +16,7 @@ package org.springframework.data.jdbc.core.convert; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.function.Predicate; @@ -30,14 +31,14 @@ import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes; * * @author Mark Paluch * @author Jens Schauder + * @author Christoph Strobl * @see CustomConversions * @see org.springframework.data.mapping.model.SimpleTypeHolder * @see JdbcSimpleTypes */ public class JdbcCustomConversions extends CustomConversions { - public static final List STORE_CONVERTERS = Arrays - .asList(Jsr310TimestampBasedConverters.getConvertersToRegister().toArray()); + private static final Collection STORE_CONVERTERS = Collections.unmodifiableCollection(Jsr310TimestampBasedConverters.getConvertersToRegister()); private static final StoreConversions STORE_CONVERSIONS = StoreConversions.of(JdbcSimpleTypes.HOLDER, STORE_CONVERTERS); @@ -77,6 +78,16 @@ public class JdbcCustomConversions extends CustomConversions { super(converterConfiguration); } + /** + * Obtain a read only copy of default store converters. + * + * @return never {@literal null}. + * @since 2.3 + */ + public static Collection storeConverters() { + return STORE_CONVERTERS; + } + private static boolean isDateTimeApiConversion(ConvertiblePair cp) { if (cp.getSourceType().equals(java.util.Date.class)) { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDb2Dialect.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDb2Dialect.java index 80dd7874..d99f9cc3 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDb2Dialect.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDb2Dialect.java @@ -15,22 +15,29 @@ */ package org.springframework.data.jdbc.core.dialect; +import java.sql.Timestamp; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.WritingConverter; import org.springframework.data.relational.core.dialect.Db2Dialect; /** * {@link Db2Dialect} that registers JDBC specific converters. * * @author Jens Schauder + * @author Christoph Strobl * @since 2.3 */ public class JdbcDb2Dialect extends Db2Dialect { public static JdbcDb2Dialect INSTANCE = new JdbcDb2Dialect(); + protected JdbcDb2Dialect() {} + @Override public Collection getConverters() { @@ -40,4 +47,21 @@ public class JdbcDb2Dialect extends Db2Dialect { return converters; } + /** + * {@link WritingConverter} from {@link OffsetDateTime} to {@link Timestamp}. The conversion preserves the + * {@link java.time.Instant} represented by {@link OffsetDateTime} + * + * @author Jens Schauder + * @since 2.3 + */ + @WritingConverter + enum OffsetDateTimeToTimestampConverter implements Converter { + + INSTANCE; + + @Override + public Timestamp convert(OffsetDateTime source) { + return Timestamp.from(source.toInstant()); + } + } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcH2Dialect.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcH2Dialect.java index 0199f0bc..e95ae774 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcH2Dialect.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcH2Dialect.java @@ -15,36 +15,43 @@ */ package org.springframework.data.jdbc.core.dialect; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + import org.h2.api.TimestampWithTimeZone; import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.relational.core.dialect.Db2Dialect; import org.springframework.data.relational.core.dialect.H2Dialect; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.util.Collection; -import java.util.Collections; - /** * {@link Db2Dialect} that registers JDBC specific converters. * * @author Jens Schauder + * @author Christoph Strobl * @since 2.3 */ public class JdbcH2Dialect extends H2Dialect { public static JdbcH2Dialect INSTANCE = new JdbcH2Dialect(); + protected JdbcH2Dialect() {} + @Override public Collection getConverters() { - return Collections.singletonList(TimestampWithTimeZoneToOffsetDateTimeConverter.INSTANCE); + + List converters = new ArrayList<>(super.getConverters()); + converters.add(TimestampWithTimeZoneToOffsetDateTimeConverter.INSTANCE); + return converters; } @ReadingConverter enum TimestampWithTimeZoneToOffsetDateTimeConverter implements Converter { - INSTANCE; + INSTANCE; @Override public OffsetDateTime convert(TimestampWithTimeZone source) { @@ -67,8 +74,8 @@ public class JdbcH2Dialect extends H2Dialect { nanosLeft -= nanosInSeconds; ZoneOffset offset = ZoneOffset.ofTotalSeconds(source.getTimeZoneOffsetSeconds()); - return OffsetDateTime.of(source.getYear(), source.getMonth(), source.getDay(), (int)hours, (int)minutes, (int)seconds, (int)nanosLeft, offset ); - + return OffsetDateTime.of(source.getYear(), source.getMonth(), source.getDay(), (int) hours, (int) minutes, + (int) seconds, (int) nanosLeft, offset); } } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcMySqlDialect.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcMySqlDialect.java index 89aea0a1..2dd8a869 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcMySqlDialect.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcMySqlDialect.java @@ -31,6 +31,7 @@ import org.springframework.data.relational.core.sql.IdentifierProcessing; * {@link Db2Dialect} that registers JDBC specific converters. * * @author Jens Schauder + * @author Christoph Strobl * @since 2.3 */ public class JdbcMySqlDialect extends MySqlDialect { @@ -39,6 +40,8 @@ public class JdbcMySqlDialect extends MySqlDialect { super(identifierProcessing); } + protected JdbcMySqlDialect() {} + @Override public Collection getConverters() { @@ -50,6 +53,7 @@ public class JdbcMySqlDialect extends MySqlDialect { @WritingConverter enum OffsetDateTimeToTimestampJdbcValueConverter implements Converter { + INSTANCE; @Override diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcSqlServerDialect.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcSqlServerDialect.java index 0618b445..9883c23a 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcSqlServerDialect.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcSqlServerDialect.java @@ -18,8 +18,9 @@ package org.springframework.data.jdbc.core.dialect; import microsoft.sql.DateTimeOffset; import java.time.OffsetDateTime; +import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; +import java.util.List; import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.ReadingConverter; @@ -29,6 +30,7 @@ import org.springframework.data.relational.core.dialect.SqlServerDialect; * {@link SqlServerDialect} that registers JDBC specific converters. * * @author Jens Schauder + * @author Christoph Strobl * @since 2.3 */ public class JdbcSqlServerDialect extends SqlServerDialect { @@ -37,11 +39,15 @@ public class JdbcSqlServerDialect extends SqlServerDialect { @Override public Collection getConverters() { - return Collections.singletonList(DateTimeOffsetToOffsetDateTimeConverter.INSTANCE); + + List converters = new ArrayList<>(super.getConverters()); + converters.add(DateTimeOffsetToOffsetDateTimeConverter.INSTANCE); + return converters; } @ReadingConverter enum DateTimeOffsetToOffsetDateTimeConverter implements Converter { + INSTANCE; @Override diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/OffsetDateTimeToTimestampConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/OffsetDateTimeToTimestampConverter.java deleted file mode 100644 index 3c363d63..00000000 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/OffsetDateTimeToTimestampConverter.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2021 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.core.dialect; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.data.convert.WritingConverter; -import org.springframework.data.relational.core.dialect.Db2Dialect; - -import java.sql.Timestamp; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; - -/** - * {@link WritingConverter} from {@link OffsetDateTime} to {@link Timestamp}. - * The conversion preserves the {@link java.time.Instant} represented by {@link OffsetDateTime} - * - * @author Jens Schauder - * @since 2.3 - */ -@WritingConverter -enum OffsetDateTimeToTimestampConverter implements Converter { - - INSTANCE; - @Override - public Timestamp convert(OffsetDateTime source) { - return Timestamp.from(source.toInstant()); - } -} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java index b0e7c980..be6398b3 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java @@ -31,6 +31,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.CustomConversions; +import org.springframework.data.convert.CustomConversions.StoreConversions; import org.springframework.data.jdbc.core.JdbcAggregateOperations; import org.springframework.data.jdbc.core.JdbcAggregateTemplate; import org.springframework.data.jdbc.core.convert.BasicJdbcConverter; @@ -41,9 +42,12 @@ import org.springframework.data.jdbc.core.convert.JdbcConverter; 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.dialect.JdbcDb2Dialect; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes; +import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.relational.core.conversion.RelationalConverter; +import org.springframework.data.relational.core.dialect.Db2Dialect; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -116,9 +120,11 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware { try { Dialect dialect = applicationContext.getBean(Dialect.class); + SimpleTypeHolder simpleTypeHolder = dialect.simpleTypes().isEmpty() ? JdbcSimpleTypes.HOLDER : new SimpleTypeHolder(dialect.simpleTypes(), JdbcSimpleTypes.HOLDER); return new JdbcCustomConversions( - CustomConversions.StoreConversions.of(JdbcSimpleTypes.HOLDER, storeConverters(dialect)), userConverters()); + CustomConversions.StoreConversions.of(simpleTypeHolder, storeConverters(dialect)), userConverters()); + } catch (NoSuchBeanDefinitionException exception) { LOG.warn("No dialect found. CustomConversions will be configured without dialect specific conversions."); @@ -135,7 +141,7 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware { List converters = new ArrayList<>(); converters.addAll(dialect.getConverters()); - converters.addAll(JdbcCustomConversions.STORE_CONVERTERS); + converters.addAll(JdbcCustomConversions.storeConverters()); return converters; } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/OffsetDateTimeToTimestampConverterUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/OffsetDateTimeToTimestampConverterUnitTests.java index 15f4fd5c..cecf7dcb 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/OffsetDateTimeToTimestampConverterUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/OffsetDateTimeToTimestampConverterUnitTests.java @@ -25,7 +25,7 @@ import static org.assertj.core.api.Assertions.*; */ /** - * Tests for {@link OffsetDateTimeToTimestampConverter}. + * Tests for {@link JdbcDb2Dialect.OffsetDateTimeToTimestampConverter}. * * @author Jens Schauder */ @@ -36,8 +36,8 @@ class OffsetDateTimeToTimestampConverterUnitTests { OffsetDateTime offsetDateTime = OffsetDateTime.of(5, 5, 5, 5,5,5,123456789, ZoneOffset.ofHours(3)); - Timestamp timestamp = OffsetDateTimeToTimestampConverter.INSTANCE.convert(offsetDateTime); + Timestamp timestamp = JdbcDb2Dialect.OffsetDateTimeToTimestampConverter.INSTANCE.convert(offsetDateTime); assertThat(timestamp.toInstant()).isEqualTo(offsetDateTime.toInstant()); } -} \ No newline at end of file +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java index 2e4207e1..e74d30b0 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java @@ -16,7 +16,6 @@ package org.springframework.data.jdbc.testing; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -45,6 +44,7 @@ import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes; import org.springframework.data.jdbc.repository.config.DialectResolver; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.RelationalMappingContext; @@ -62,6 +62,7 @@ import org.springframework.transaction.PlatformTransactionManager; * @author Mark Paluch * @author Fei Dong * @author Myeonghyeon Lee + * @author Christoph Strobl */ @Configuration @ComponentScan // To pick up configuration classes (per activated profile) @@ -114,15 +115,19 @@ public class TestConfiguration { @Bean CustomConversions jdbcCustomConversions(Dialect dialect) { - return new JdbcCustomConversions(CustomConversions.StoreConversions.of(JdbcSimpleTypes.HOLDER, - storeConverters(dialect)), Collections.emptyList()); + + SimpleTypeHolder simpleTypeHolder = dialect.simpleTypes().isEmpty() ? JdbcSimpleTypes.HOLDER + : new SimpleTypeHolder(dialect.simpleTypes(), JdbcSimpleTypes.HOLDER); + + return new JdbcCustomConversions(CustomConversions.StoreConversions.of(simpleTypeHolder, storeConverters(dialect)), + Collections.emptyList()); } private List storeConverters(Dialect dialect) { List converters = new ArrayList<>(); converters.addAll(dialect.getConverters()); - converters.addAll(JdbcCustomConversions.STORE_CONVERTERS); + converters.addAll(JdbcCustomConversions.storeConverters()); return converters; } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java index 864a21ba..d12e54e1 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java @@ -17,6 +17,7 @@ package org.springframework.data.relational.core.dialect; import java.util.Collection; import java.util.Collections; +import java.util.Set; import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.SqlIdentifier; @@ -30,6 +31,7 @@ import org.springframework.data.relational.core.sql.render.SelectRenderContext; * @author Mark Paluch * @author Jens Schauder * @author Myeonghyeon Lee + * @author Christoph Strobl * @since 1.1 */ public interface Dialect { @@ -97,4 +99,14 @@ public interface Dialect { default Collection getConverters() { return Collections.emptySet(); } + + /** + * Return the {@link Set} of types considered store native types that can be handeled by the driver. + * + * @return never {@literal null}. + * @since 2.3 + */ + default Set> simpleTypes() { + return Collections.emptySet(); + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/H2Dialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/H2Dialect.java index 74b8e90b..7444edde 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/H2Dialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/H2Dialect.java @@ -15,6 +15,9 @@ */ package org.springframework.data.relational.core.dialect; +import java.util.Collections; +import java.util.Set; + import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.IdentifierProcessing.LetterCasing; import org.springframework.data.relational.core.sql.IdentifierProcessing.Quoting; @@ -26,6 +29,7 @@ import org.springframework.util.ClassUtils; * * @author Mark Paluch * @author Myeonghyeon Lee + * @author Christph Strobl * @since 2.0 */ public class H2Dialect extends AbstractDialect { @@ -137,4 +141,21 @@ public class H2Dialect extends AbstractDialect { public IdentifierProcessing getIdentifierProcessing() { return IdentifierProcessing.create(Quoting.ANSI, LetterCasing.UPPER_CASE); } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.dialect.Dialect#simpleTypes() + */ + @Override + public Set> simpleTypes() { + + if (!ClassUtils.isPresent("org.h2.api.TimestampWithTimeZone", getClass().getClassLoader())) { + return Collections.emptySet(); + } + try { + return Collections.singleton(ClassUtils.forName("org.h2.api.TimestampWithTimeZone", getClass().getClassLoader())); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + } }