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