From d3fba89403e0a311ba95e99a03fd89b15ed5d5bd Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Wed, 10 Mar 2021 15:07:34 +0100 Subject: [PATCH 1/5] gh-935 - Prepare branch --- pom.xml | 2 +- spring-data-jdbc-distribution/pom.xml | 2 +- spring-data-jdbc/pom.xml | 4 ++-- spring-data-relational/pom.xml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index eeaa0b9e93..db8360767d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 2.3.0-SNAPSHOT + 2.3.0-gh-935-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index 03d6a5c2a0..756e244210 100644 --- a/spring-data-jdbc-distribution/pom.xml +++ b/spring-data-jdbc-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 2.3.0-SNAPSHOT + 2.3.0-gh-935-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index d7722eca4e..d925e4f27f 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-jdbc - 2.3.0-SNAPSHOT + 2.3.0-gh-935-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 2.3.0-SNAPSHOT + 2.3.0-gh-935-SNAPSHOT diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 4e42a006ec..4558427199 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-relational - 2.3.0-SNAPSHOT + 2.3.0-gh-935-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 2.3.0-SNAPSHOT + 2.3.0-gh-935-SNAPSHOT From 60c0e6e51dce34389434a7953aec791e2c0aba75 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 12 Mar 2021 08:08:57 +0100 Subject: [PATCH 2/5] Support for Dialect specific custom conversions and OffsetDateTime. Dialects may now register a list of converters to take into consideration when reading or writing properties. See `JdbcSqlServerDialect` for an example. By default `OffsetDateTime` does not get converted anymore. If a database needs a conversion it can register it by implementing `Dialect.getConverters()` as described above. Closes #935 --- spring-data-jdbc/pom.xml | 4 +- .../jdbc/core/convert/JdbcColumnTypes.java | 2 + .../core/convert/JdbcCustomConversions.java | 14 +++- .../jdbc/core/dialect/JdbcDb2Dialect.java | 43 +++++++++++ .../data/jdbc/core/dialect/JdbcH2Dialect.java | 74 +++++++++++++++++++ .../jdbc/core/dialect/JdbcMySqlDialect.java | 60 +++++++++++++++ .../core/dialect/JdbcSqlServerDialect.java | 53 +++++++++++++ .../OffsetDateTime2TimestampConverter.java | 41 ++++++++++ .../config/AbstractJdbcConfiguration.java | 31 ++++++-- .../repository/config/DialectResolver.java | 18 +++-- .../data/jdbc/support/JdbcUtil.java | 2 + .../jdbc/core/dialect/JdbcH2DialectTests.java | 51 +++++++++++++ ...tDateTime2TimestampConverterUnitTests.java | 44 +++++++++++ .../JdbcRepositoryIntegrationTests.java | 59 +++++++++++---- ...ractJdbcConfigurationIntegrationTests.java | 2 +- .../data/jdbc/support/JdbcUtilTests.java | 37 ++++++++++ .../testing/MySqlDataSourceConfiguration.java | 11 --- .../data/jdbc/testing/TestConfiguration.java | 18 ++++- .../JdbcRepositoryIntegrationTests-db2.sql | 3 +- .../JdbcRepositoryIntegrationTests-h2.sql | 3 +- .../JdbcRepositoryIntegrationTests-hsql.sql | 3 +- ...JdbcRepositoryIntegrationTests-mariadb.sql | 3 +- .../JdbcRepositoryIntegrationTests-mssql.sql | 3 +- .../JdbcRepositoryIntegrationTests-mysql.sql | 5 +- .../JdbcRepositoryIntegrationTests-oracle.sql | 3 +- ...dbcRepositoryIntegrationTests-postgres.sql | 3 +- ...ertyConversionIntegrationTests-mariadb.sql | 4 +- ...opertyConversionIntegrationTests-mysql.sql | 4 +- .../relational/core/dialect/Db2Dialect.java | 8 ++ .../data/relational/core/dialect/Dialect.java | 16 +++- .../core/dialect/MariaDbDialect.java | 39 ++++++++++ .../relational/core/dialect/MySqlDialect.java | 10 ++- .../core/dialect/OracleDialect.java | 17 ++--- .../core/dialect/PostgresDialect.java | 14 +++- .../Timestamp2OffsetDateTimeConverter.java | 42 +++++++++++ ...tamp2OffsetDateTimeConverterUnitTests.java | 43 +++++++++++ 36 files changed, 718 insertions(+), 69 deletions(-) create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDb2Dialect.java create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcH2Dialect.java create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcMySqlDialect.java create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcSqlServerDialect.java create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/OffsetDateTime2TimestampConverter.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/JdbcH2DialectTests.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/OffsetDateTime2TimestampConverterUnitTests.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/support/JdbcUtilTests.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MariaDbDialect.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Timestamp2OffsetDateTimeConverter.java create mode 100644 spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/Timestamp2OffsetDateTimeConverterUnitTests.java diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index d925e4f27f..4d36016962 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -141,7 +141,7 @@ com.h2database h2 ${h2.version} - test + true @@ -190,7 +190,7 @@ com.microsoft.sqlserver mssql-jdbc ${mssql.version} - test + true diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcColumnTypes.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcColumnTypes.java index 4ab9deaf0e..bb7a0ef3d1 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcColumnTypes.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcColumnTypes.java @@ -16,6 +16,7 @@ package org.springframework.data.jdbc.core.convert; import java.sql.Timestamp; +import java.time.OffsetDateTime; import java.time.ZonedDateTime; import java.time.temporal.Temporal; import java.util.Date; @@ -52,6 +53,7 @@ public Class resolvePrimitiveType(Class type) { javaToDbType.put(Enum.class, String.class); javaToDbType.put(ZonedDateTime.class, String.class); + javaToDbType.put(OffsetDateTime.class, OffsetDateTime.class); javaToDbType.put(Temporal.class, Timestamp.class); } 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 45ce0c6bc6..efad38c13d 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 @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Predicate; import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; import org.springframework.data.convert.CustomConversions; @@ -35,7 +36,7 @@ */ public class JdbcCustomConversions extends CustomConversions { - private static final List STORE_CONVERTERS = Arrays + public static final List STORE_CONVERTERS = Arrays .asList(Jsr310TimestampBasedConverters.getConvertersToRegister().toArray()); private static final StoreConversions STORE_CONVERSIONS = StoreConversions.of(JdbcSimpleTypes.HOLDER, STORE_CONVERTERS); @@ -48,7 +49,7 @@ public JdbcCustomConversions() { } /** - * Create a new {@link JdbcCustomConversions} instance registering the given converters. + * Create a new {@link JdbcCustomConversions} instance registering the given converters and the default store converters. * * @param converters must not be {@literal null}. */ @@ -56,6 +57,15 @@ public JdbcCustomConversions(List converters) { super(new ConverterConfiguration(STORE_CONVERSIONS, converters, JdbcCustomConversions::isDateTimeApiConversion)); } + /** + * Create a new {@link JdbcCustomConversions} instance registering the given converters and the default store converters. + * + * @since 2.3 + */ + public JdbcCustomConversions(StoreConversions storeConversions, List userConverters) { + super(new ConverterConfiguration(storeConversions, userConverters, JdbcCustomConversions::isDateTimeApiConversion)); + } + /** * Create a new {@link JdbcCustomConversions} instance given * {@link org.springframework.data.convert.CustomConversions.ConverterConfiguration}. 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 new file mode 100644 index 0000000000..2824496a6b --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDb2Dialect.java @@ -0,0 +1,43 @@ +/* + * 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 java.util.ArrayList; +import java.util.Collection; + +import org.springframework.data.relational.core.dialect.Db2Dialect; +import org.springframework.data.relational.core.sql.IdentifierProcessing; + +/** + * {@link Db2Dialect} that registers JDBC specific converters. + * + * @author Jens Schauder + * @since 2.3 + */ +public class JdbcDb2Dialect extends Db2Dialect { + + public static JdbcDb2Dialect INSTANCE = new JdbcDb2Dialect(); + + @Override + public Collection getConverters() { + + ArrayList converters = new ArrayList<>(super.getConverters()); + converters.add(OffsetDateTime2TimestampConverter.INSTANCE); + + return converters; + } + +} 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 new file mode 100644 index 0000000000..b7fdd1b909 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcH2Dialect.java @@ -0,0 +1,74 @@ +/* + * 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.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 + * @since 2.3 + */ +public class JdbcH2Dialect extends H2Dialect { + + public static JdbcH2Dialect INSTANCE = new JdbcH2Dialect(); + + @Override + public Collection getConverters() { + return Collections.singletonList(TimestampWithTimeZone2OffsetDateTimeConverter.INSTANCE); + } + + @ReadingConverter + enum TimestampWithTimeZone2OffsetDateTimeConverter implements Converter { + INSTANCE; + + + @Override + public OffsetDateTime convert(TimestampWithTimeZone source) { + + long nanosInSecond = 1_000_000_000; + long nanosInMinute = nanosInSecond * 60; + long nanosInHour = nanosInMinute * 60; + + long hours = (source.getNanosSinceMidnight() / nanosInHour); + + long nanosInHours = hours * nanosInHour; + long nanosLeft = source.getNanosSinceMidnight() - nanosInHours; + long minutes = nanosLeft / nanosInMinute; + + long nanosInMinutes = minutes * nanosInMinute; + nanosLeft -= nanosInMinutes; + long seconds = nanosLeft / nanosInSecond; + + long nanosInSeconds = seconds * nanosInSecond; + 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 ); + + } + } +} 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 new file mode 100644 index 0000000000..26ce00d2e2 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcMySqlDialect.java @@ -0,0 +1,60 @@ +/* + * 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 java.sql.JDBCType; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Collection; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.WritingConverter; +import org.springframework.data.jdbc.core.convert.JdbcValue; +import org.springframework.data.relational.core.dialect.Db2Dialect; +import org.springframework.data.relational.core.dialect.MySqlDialect; +import org.springframework.data.relational.core.sql.IdentifierProcessing; + +/** + * {@link Db2Dialect} that registers JDBC specific converters. + * + * @author Jens Schauder + * @since 2.3 + */ +public class JdbcMySqlDialect extends MySqlDialect { + + public JdbcMySqlDialect(IdentifierProcessing identifierProcessing) { + super(identifierProcessing); + } + + @Override + public Collection getConverters() { + + ArrayList converters = new ArrayList<>(super.getConverters()); + converters.add(OffsetDateTime2TimestampJdbcValueConverter.INSTANCE); + + return converters; + } + + @WritingConverter + enum OffsetDateTime2TimestampJdbcValueConverter implements Converter { + INSTANCE; + + @Override + public JdbcValue convert(OffsetDateTime source) { + return JdbcValue.of(source, JDBCType.TIMESTAMP); + } + } +} 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 new file mode 100644 index 0000000000..1592814e3c --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcSqlServerDialect.java @@ -0,0 +1,53 @@ +/* + * 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 microsoft.sql.DateTimeOffset; + +import java.time.OffsetDateTime; +import java.util.Collection; +import java.util.Collections; + +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.SqlServerDialect; + +/** + * {@link Db2Dialect} that registers JDBC specific converters. + * + * @author Jens Schauder + * @since 2.3 + */ +public class JdbcSqlServerDialect extends SqlServerDialect { + + public static JdbcSqlServerDialect INSTANCE = new JdbcSqlServerDialect(); + + @Override + public Collection getConverters() { + return Collections.singletonList(DateTimeOffset2OffsetDateTimeConverter.INSTANCE); + } + + @ReadingConverter + enum DateTimeOffset2OffsetDateTimeConverter implements Converter { + INSTANCE; + + @Override + public OffsetDateTime convert(DateTimeOffset source) { + return source.getOffsetDateTime(); + } + } +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/OffsetDateTime2TimestampConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/OffsetDateTime2TimestampConverter.java new file mode 100644 index 0000000000..185a0c2682 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/OffsetDateTime2TimestampConverter.java @@ -0,0 +1,41 @@ +/* + * 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 OffsetDateTime2TimestampConverter 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 fa14484187..46fbe3bc8e 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 @@ -15,6 +15,9 @@ */ package org.springframework.data.jdbc.repository.config; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Optional; import org.springframework.context.ApplicationContext; @@ -22,6 +25,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.jdbc.core.JdbcAggregateOperations; import org.springframework.data.jdbc.core.JdbcAggregateTemplate; import org.springframework.data.jdbc.core.convert.BasicJdbcConverter; @@ -33,6 +37,7 @@ 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.core.mapping.JdbcSimpleTypes; import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.NamingStrategy; @@ -56,7 +61,7 @@ public class AbstractJdbcConfiguration { * Register a {@link JdbcMappingContext} and apply an optional {@link NamingStrategy}. * * @param namingStrategy optional {@link NamingStrategy}. Use {@link NamingStrategy#INSTANCE} as fallback. - * @param customConversions see {@link #jdbcCustomConversions()}. + * @param customConversions see {@link #jdbcCustomConversions(Dialect)}. * @return must not be {@literal null}. */ @Bean @@ -71,10 +76,10 @@ public JdbcMappingContext jdbcMappingContext(Optional namingStra /** * Creates a {@link RelationalConverter} using the configured - * {@link #jdbcMappingContext(Optional, JdbcCustomConversions)}. Will get {@link #jdbcCustomConversions()} applied. + * {@link #jdbcMappingContext(Optional, JdbcCustomConversions)}. Will get {@link #jdbcCustomConversions(Dialect)} ()} applied. * * @see #jdbcMappingContext(Optional, JdbcCustomConversions) - * @see #jdbcCustomConversions() + * @see #jdbcCustomConversions(Dialect) () * @return must not be {@literal null}. */ @Bean @@ -96,8 +101,22 @@ public JdbcConverter jdbcConverter(JdbcMappingContext mappingContext, NamedParam * @return will never be {@literal null}. */ @Bean - public JdbcCustomConversions jdbcCustomConversions() { - return new JdbcCustomConversions(); + public JdbcCustomConversions jdbcCustomConversions(Dialect dialect) { + + return new JdbcCustomConversions(CustomConversions.StoreConversions.of(JdbcSimpleTypes.HOLDER, + storeConverters(dialect)), userConverters()); + } + + private List userConverters() { + return Collections.emptyList(); + } + + private List storeConverters(Dialect dialect) { + + List converters = new ArrayList<>(); + converters.addAll(dialect.getConverters()); + converters.addAll(JdbcCustomConversions.STORE_CONVERTERS); + return converters; } /** @@ -134,7 +153,7 @@ public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations op * Resolves a {@link Dialect JDBC dialect} by inspecting {@link NamedParameterJdbcOperations}. * * @param operations the {@link NamedParameterJdbcOperations} allowing access to a {@link java.sql.Connection}. - * @return + * @return the {@link Dialect} to be used. * @since 2.0 * @throws org.springframework.data.jdbc.repository.config.DialectResolver.NoDialectException if the {@link Dialect} * cannot be determined. diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DialectResolver.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DialectResolver.java index 36cb6b714f..d7f49fd657 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DialectResolver.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DialectResolver.java @@ -28,10 +28,15 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.dao.NonTransientDataAccessException; +import org.springframework.data.jdbc.core.dialect.JdbcDb2Dialect; +import org.springframework.data.jdbc.core.dialect.JdbcH2Dialect; +import org.springframework.data.jdbc.core.dialect.JdbcMySqlDialect; +import org.springframework.data.jdbc.core.dialect.JdbcSqlServerDialect; import org.springframework.data.relational.core.dialect.Db2Dialect; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.dialect.H2Dialect; import org.springframework.data.relational.core.dialect.HsqlDbDialect; +import org.springframework.data.relational.core.dialect.MariaDbDialect; import org.springframework.data.relational.core.dialect.MySqlDialect; import org.springframework.data.relational.core.dialect.OracleDialect; import org.springframework.data.relational.core.dialect.PostgresDialect; @@ -118,19 +123,22 @@ private static Dialect getDialect(Connection connection) throws SQLException { return HsqlDbDialect.INSTANCE; } if (name.contains("h2")) { - return H2Dialect.INSTANCE; + return JdbcH2Dialect.INSTANCE; } - if (name.contains("mysql") || name.contains("mariadb")) { - return new MySqlDialect(getIdentifierProcessing(metaData)); + if (name.contains("mysql")) { + return new JdbcMySqlDialect(getIdentifierProcessing(metaData)); + } + if (name.contains("mariadb")) { + return new MariaDbDialect(getIdentifierProcessing(metaData)); } if (name.contains("postgresql")) { return PostgresDialect.INSTANCE; } if (name.contains("microsoft")) { - return SqlServerDialect.INSTANCE; + return JdbcSqlServerDialect.INSTANCE; } if (name.contains("db2")) { - return Db2Dialect.INSTANCE; + return JdbcDb2Dialect.INSTANCE; } if (name.contains("oracle")) { return OracleDialect.INSTANCE; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/JdbcUtil.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/JdbcUtil.java index ae85b337f0..e5d0c291f4 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/JdbcUtil.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/JdbcUtil.java @@ -22,6 +22,7 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; +import java.time.OffsetDateTime; import java.util.HashMap; import java.util.Map; @@ -62,6 +63,7 @@ public final class JdbcUtil { sqlTypeMappings.put(Date.class, Types.DATE); sqlTypeMappings.put(Time.class, Types.TIME); sqlTypeMappings.put(Timestamp.class, Types.TIMESTAMP); + sqlTypeMappings.put(OffsetDateTime.class, Types.TIMESTAMP_WITH_TIMEZONE); } private JdbcUtil() { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/JdbcH2DialectTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/JdbcH2DialectTests.java new file mode 100644 index 0000000000..f3eb17611d --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/JdbcH2DialectTests.java @@ -0,0 +1,51 @@ +package org.springframework.data.jdbc.core.dialect; + +import static org.assertj.core.api.Assertions.*; + +import java.time.OffsetDateTime; + +import org.h2.api.TimestampWithTimeZone; +import org.h2.util.DateTimeUtils; +import org.junit.jupiter.api.Test; + +/* + * 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. + */ + +/** + * Tests for {@link JdbcH2Dialect}. + * + * @author Jens Schauder + */ +class JdbcH2DialectTests { + + @Test + void TimestampWithTimeZone2OffsetDateTimeConverterConvertsProperly() { + + JdbcH2Dialect.TimestampWithTimeZone2OffsetDateTimeConverter converter = JdbcH2Dialect.TimestampWithTimeZone2OffsetDateTimeConverter.INSTANCE; + long dateValue = 123456789; + long timeNanos = 987654321; + int timeZoneOffsetSeconds = 4 * 60 * 60; + TimestampWithTimeZone timestampWithTimeZone = new TimestampWithTimeZone(dateValue, timeNanos, + timeZoneOffsetSeconds); + + OffsetDateTime offsetDateTime = converter.convert(timestampWithTimeZone); + + assertThat(offsetDateTime.getOffset().getTotalSeconds()).isEqualTo(timeZoneOffsetSeconds); + assertThat(offsetDateTime.getNano()).isEqualTo(timeNanos); + assertThat(offsetDateTime.toEpochSecond()) + .isEqualTo(DateTimeUtils.getEpochSeconds(dateValue, timeNanos, timeZoneOffsetSeconds)); + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/OffsetDateTime2TimestampConverterUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/OffsetDateTime2TimestampConverterUnitTests.java new file mode 100644 index 0000000000..0f7a182221 --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/OffsetDateTime2TimestampConverterUnitTests.java @@ -0,0 +1,44 @@ +package org.springframework.data.jdbc.core.dialect; + +import org.junit.jupiter.api.Test; + +import java.sql.Timestamp; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +/* + * 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. + */ + +/** + * Tests for {@link OffsetDateTime2TimestampConverter}. + * + * @author Jens Schauder + */ +class OffsetDateTime2TimestampConverterUnitTests { + + @Test + void conversionPreservesInstant() { + + OffsetDateTime offsetDateTime = OffsetDateTime.of(5, 5, 5, 5,5,5,123456789, ZoneOffset.ofHours(3)); + + Timestamp timestamp = OffsetDateTime2TimestampConverter.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/repository/JdbcRepositoryIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index d5d73f7682..54497a5d44 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -27,14 +27,16 @@ import java.io.IOException; import java.sql.ResultSet; import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.context.ApplicationListener; @@ -92,6 +94,9 @@ private static DummyEntity createDummyEntity() { @BeforeEach public void before() { + + repository.deleteAll(); + eventListener.events.clear(); } @@ -282,17 +287,7 @@ public void findByIdReturnsEmptyWhenNoneFound() { @Test // DATAJDBC-464, DATAJDBC-318 public void executeQueryWithParameterRequiringConversion() { - Instant now = Instant.now(); - - DummyEntity first = repository.save(createDummyEntity()); - first.setPointInTime(now.minusSeconds(1000L)); - first.setName("first"); - - DummyEntity second = repository.save(createDummyEntity()); - second.setPointInTime(now.plusSeconds(1000L)); - second.setName("second"); - - repository.saveAll(asList(first, second)); + Instant now = createDummyBeforeAndAfterNow(); assertThat(repository.after(now)) // .extracting(DummyEntity::getName) // @@ -408,7 +403,7 @@ public void countByQueryDerivation() { @Test // #945 @EnabledOnFeature(TestDatabaseFeatures.Feature.IS_POSTGRES) public void usePrimitiveArrayAsArgument() { - assertThat(repository.unnestPrimitive(new int[]{1, 2, 3})).containsExactly(1,2,3); + assertThat(repository.unnestPrimitive(new int[] { 1, 2, 3 })).containsExactly(1, 2, 3); } @Test // GH-774 @@ -442,6 +437,17 @@ public void sliceByNameShouldReturnCorrectResult() { assertThat(slice.hasNext()).isTrue(); } + @Test // #935 + public void queryByOffsetDateTime() { + + Instant now = createDummyBeforeAndAfterNow(); + OffsetDateTime timeArgument = OffsetDateTime.ofInstant(now, ZoneOffset.ofHours(2)); + + List entities = repository.findByOffsetDateTime(timeArgument); + + assertThat(entities).extracting(DummyEntity::getName).containsExactly("second"); + } + @Test // #971 public void stringQueryProjectionShouldReturnProjectedEntities() { @@ -486,6 +492,29 @@ public void pageQueryProjectionShouldReturnProjectedEntities() { assertThat(result.getContent().get(0).getName()).isEqualTo("Entity Name"); } + private Instant createDummyBeforeAndAfterNow() { + + Instant now = Instant.now(); + + DummyEntity first = createDummyEntity(); + Instant earlier = now.minusSeconds(1000L); + OffsetDateTime earlierPlus3 = earlier.atOffset(ZoneOffset.ofHours(3)); + first.setPointInTime(earlier); + first.offsetDateTime = earlierPlus3; + + first.setName("first"); + + DummyEntity second = createDummyEntity(); + Instant later = now.plusSeconds(1000L); + OffsetDateTime laterPlus3 = later.atOffset(ZoneOffset.ofHours(3)); + second.setPointInTime(later); + second.offsetDateTime = laterPlus3; + second.setName("second"); + + repository.saveAll(asList(first, second)); + return now; + } + interface DummyEntityRepository extends CrudRepository { List findAllByNamedQuery(); @@ -525,6 +554,9 @@ interface DummyEntityRepository extends CrudRepository { Page findPageProjectionByName(String name, Pageable pageable); Slice findSliceByNameContains(String name, Pageable pageable); + + @Query("SELECT * FROM DUMMY_ENTITY WHERE OFFSET_DATE_TIME > :threshhold") + List findByOffsetDateTime(@Param("threshhold") OffsetDateTime threshhold); } @Configuration @@ -573,6 +605,7 @@ public void onApplicationEvent(AbstractRelationalEvent event) { static class DummyEntity { String name; Instant pointInTime; + OffsetDateTime offsetDateTime; @Id private Long idProp; public DummyEntity(String name) { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java index 1ec72619c9..797ad1263d 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java @@ -110,7 +110,7 @@ public Dialect jdbcDialect(NamedParameterJdbcOperations operations) { } @Override - public JdbcCustomConversions jdbcCustomConversions() { + public JdbcCustomConversions jdbcCustomConversions(Dialect dialect) { return new JdbcCustomConversions(Collections.singletonList(Blah2BlubbConverter.INSTANCE)); } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/support/JdbcUtilTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/support/JdbcUtilTests.java new file mode 100644 index 0000000000..30f532fe4e --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/support/JdbcUtilTests.java @@ -0,0 +1,37 @@ +package org.springframework.data.jdbc.support; + +import static org.assertj.core.api.Assertions.*; + +import java.sql.Types; +import java.time.OffsetDateTime; + +import org.junit.jupiter.api.Test; + +/* + * 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. + */ + +/** + * Tests for {@link JdbcUtil}. + * + * @author Jens Schauder + */ +class JdbcUtilTests { + + @Test + void test() { + assertThat(JdbcUtil.sqlTypeFor(OffsetDateTime.class)).isEqualTo(Types.TIMESTAMP_WITH_TIMEZONE); + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java index 8da00a7744..5e2cb7b5ce 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java @@ -77,15 +77,4 @@ public void afterPropertiesSet() throws Exception { new ByteArrayResource("DROP DATABASE test;CREATE DATABASE test;".getBytes())); } } - - private DataSource createRootDataSource() { - - MysqlDataSource dataSource = new MysqlDataSource(); - dataSource.setUrl(MYSQL_CONTAINER.getJdbcUrl()); - dataSource.setUser("root"); - dataSource.setPassword(MYSQL_CONTAINER.getPassword()); - dataSource.setDatabaseName(MYSQL_CONTAINER.getDatabaseName()); - - return dataSource; - } } 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 20c73b859f..2e4207e18c 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 @@ -15,6 +15,10 @@ */ 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; import javax.sql.DataSource; @@ -38,6 +42,7 @@ 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.core.mapping.JdbcSimpleTypes; import org.springframework.data.jdbc.repository.config.DialectResolver; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.relational.core.dialect.Dialect; @@ -108,8 +113,17 @@ JdbcMappingContext jdbcMappingContext(Optional namingStrategy, C } @Bean - CustomConversions jdbcCustomConversions() { - return new JdbcCustomConversions(); + CustomConversions jdbcCustomConversions(Dialect dialect) { + return new JdbcCustomConversions(CustomConversions.StoreConversions.of(JdbcSimpleTypes.HOLDER, + storeConverters(dialect)), Collections.emptyList()); + } + + private List storeConverters(Dialect dialect) { + + List converters = new ArrayList<>(); + converters.addAll(dialect.getConverters()); + converters.addAll(JdbcCustomConversions.STORE_CONVERTERS); + return converters; } @Bean diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-db2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-db2.sql index 358bcbbbd9..daa415344a 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-db2.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-db2.sql @@ -4,5 +4,6 @@ CREATE TABLE dummy_entity ( id_Prop BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100), - POINT_IN_TIME TIMESTAMP + POINT_IN_TIME TIMESTAMP, + OFFSET_DATE_TIME TIMESTAMP -- with time zone is only supported with z/OS ); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-h2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-h2.sql index 6649c1439d..b9b3101690 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-h2.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-h2.sql @@ -2,5 +2,6 @@ CREATE TABLE dummy_entity ( id_Prop BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100), - POINT_IN_TIME TIMESTAMP + POINT_IN_TIME TIMESTAMP, + OFFSET_DATE_TIME TIMESTAMP WITH TIME ZONE ); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql index 6649c1439d..b9b3101690 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql @@ -2,5 +2,6 @@ CREATE TABLE dummy_entity ( id_Prop BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100), - POINT_IN_TIME TIMESTAMP + POINT_IN_TIME TIMESTAMP, + OFFSET_DATE_TIME TIMESTAMP WITH TIME ZONE ); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql index 83aea089d1..f9b086443b 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql @@ -2,5 +2,6 @@ CREATE TABLE dummy_entity ( id_Prop BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100), - POINT_IN_TIME TIMESTAMP(3) + POINT_IN_TIME TIMESTAMP(3), + OFFSET_DATE_TIME TIMESTAMP(3) ); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql index e632e642bd..c71942b4c1 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql @@ -3,5 +3,6 @@ CREATE TABLE dummy_entity ( id_Prop BIGINT IDENTITY PRIMARY KEY, NAME VARCHAR(100), - POINT_IN_TIME DATETIME + POINT_IN_TIME DATETIME, + OFFSET_DATE_TIME DATETIMEOFFSET ); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql index 83aea089d1..9c4085b27a 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql @@ -1,6 +1,9 @@ +SET SQL_MODE='ALLOW_INVALID_DATES'; + CREATE TABLE dummy_entity ( id_Prop BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100), - POINT_IN_TIME TIMESTAMP(3) + POINT_IN_TIME TIMESTAMP(3) default null, + OFFSET_DATE_TIME TIMESTAMP(3) default null ); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql index 28b2d80ec7..a3d831346d 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql @@ -4,5 +4,6 @@ CREATE TABLE DUMMY_ENTITY ( ID_PROP NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, NAME VARCHAR2(100), - POINT_IN_TIME TIMESTAMP + POINT_IN_TIME TIMESTAMP, + OFFSET_DATE_TIME TIMESTAMP WITH TIME ZONE ); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql index 803ef24751..5e670bfe77 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql @@ -3,5 +3,6 @@ CREATE TABLE dummy_entity ( id_Prop SERIAL PRIMARY KEY, NAME VARCHAR(100), - POINT_IN_TIME TIMESTAMP + POINT_IN_TIME TIMESTAMP, + OFFSET_DATE_TIME TIMESTAMP WITH TIME ZONE ); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryPropertyConversionIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryPropertyConversionIntegrationTests-mariadb.sql index 8e100e80ae..c14f120013 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryPropertyConversionIntegrationTests-mariadb.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryPropertyConversionIntegrationTests-mariadb.sql @@ -1,2 +1,2 @@ -CREATE TABLE ENTITY_WITH_COLUMNS_REQUIRING_CONVERSIONS ( id_Timestamp DATETIME PRIMARY KEY, bool boolean, SOME_ENUM VARCHAR(100), big_Decimal DECIMAL(65), big_Integer DECIMAL(20), date DATETIME, local_Date_Time DATETIME, zoned_Date_Time VARCHAR(30)); -CREATE TABLE ENTITY_WITH_COLUMNS_REQUIRING_CONVERSIONS_RELATION ( id_Timestamp DATETIME NOT NULL PRIMARY KEY, data VARCHAR(100)); +CREATE TABLE ENTITY_WITH_COLUMNS_REQUIRING_CONVERSIONS ( id_Timestamp TIMESTAMP PRIMARY KEY, bool boolean, SOME_ENUM VARCHAR(100), big_Decimal DECIMAL(65), big_Integer DECIMAL(20), date DATETIME, local_Date_Time DATETIME, zoned_Date_Time VARCHAR(30)); +CREATE TABLE ENTITY_WITH_COLUMNS_REQUIRING_CONVERSIONS_RELATION ( id_Timestamp TIMESTAMP NOT NULL PRIMARY KEY, data VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryPropertyConversionIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryPropertyConversionIntegrationTests-mysql.sql index 8e100e80ae..c14f120013 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryPropertyConversionIntegrationTests-mysql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryPropertyConversionIntegrationTests-mysql.sql @@ -1,2 +1,2 @@ -CREATE TABLE ENTITY_WITH_COLUMNS_REQUIRING_CONVERSIONS ( id_Timestamp DATETIME PRIMARY KEY, bool boolean, SOME_ENUM VARCHAR(100), big_Decimal DECIMAL(65), big_Integer DECIMAL(20), date DATETIME, local_Date_Time DATETIME, zoned_Date_Time VARCHAR(30)); -CREATE TABLE ENTITY_WITH_COLUMNS_REQUIRING_CONVERSIONS_RELATION ( id_Timestamp DATETIME NOT NULL PRIMARY KEY, data VARCHAR(100)); +CREATE TABLE ENTITY_WITH_COLUMNS_REQUIRING_CONVERSIONS ( id_Timestamp TIMESTAMP PRIMARY KEY, bool boolean, SOME_ENUM VARCHAR(100), big_Decimal DECIMAL(65), big_Integer DECIMAL(20), date DATETIME, local_Date_Time DATETIME, zoned_Date_Time VARCHAR(30)); +CREATE TABLE ENTITY_WITH_COLUMNS_REQUIRING_CONVERSIONS_RELATION ( id_Timestamp TIMESTAMP NOT NULL PRIMARY KEY, data VARCHAR(100)); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Db2Dialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Db2Dialect.java index 8f4b57a9fd..ae88a76223 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Db2Dialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Db2Dialect.java @@ -15,6 +15,9 @@ */ package org.springframework.data.relational.core.dialect; +import java.util.Collection; +import java.util.Collections; + import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.LockOptions; @@ -110,4 +113,9 @@ public Position getClausePosition() { public IdentifierProcessing getIdentifierProcessing() { return IdentifierProcessing.ANSI; } + + @Override + public Collection getConverters() { + return Collections.singletonList(Timestamp2OffsetDateTimeConverter.INSTANCE); + } } 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 35097866d9..864a21bacd 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 @@ -15,6 +15,9 @@ */ package org.springframework.data.relational.core.dialect; +import java.util.Collection; +import java.util.Collections; + import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.core.sql.render.SelectRenderContext; @@ -82,7 +85,16 @@ default Escaper getLikeEscaper() { return Escaper.DEFAULT; } - default IdGeneration getIdGeneration(){ + default IdGeneration getIdGeneration() { return IdGeneration.DEFAULT; - }; + } + + /** + * Return a collection of converters for this dialect. + * + * @return a collection of converters for this dialect. + */ + default Collection getConverters() { + return Collections.emptySet(); + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MariaDbDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MariaDbDialect.java new file mode 100644 index 0000000000..9ed373487a --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MariaDbDialect.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019-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.relational.core.dialect; + +import java.util.Collection; +import java.util.Collections; + +import org.springframework.data.relational.core.sql.IdentifierProcessing; + +/** + * A SQL dialect for MariaDb. + * + * @author Jens Schauder + * @since 2.3 + */ +public class MariaDbDialect extends MySqlDialect { + + public MariaDbDialect(IdentifierProcessing identifierProcessing) { + super(identifierProcessing); + } + + @Override + public Collection getConverters() { + return Collections.singletonList(Timestamp2OffsetDateTimeConverter.INSTANCE); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java index 51b65d0018..6dc8e8c80f 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java @@ -15,11 +15,14 @@ */ package org.springframework.data.relational.core.dialect; +import java.util.Collection; +import java.util.Collections; + import org.springframework.data.relational.core.sql.IdentifierProcessing; +import org.springframework.data.relational.core.sql.LockOptions; import org.springframework.data.relational.core.sql.IdentifierProcessing.LetterCasing; import org.springframework.data.relational.core.sql.IdentifierProcessing.Quoting; import org.springframework.util.Assert; -import org.springframework.data.relational.core.sql.LockOptions; /** * A SQL dialect for MySQL. @@ -161,4 +164,9 @@ public LockClause lock() { public IdentifierProcessing getIdentifierProcessing() { return identifierProcessing; } + + @Override + public Collection getConverters() { + return Collections.singletonList(Timestamp2OffsetDateTimeConverter.INSTANCE); + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/OracleDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/OracleDialect.java index ad9889f227..f19ae642cb 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/OracleDialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/OracleDialect.java @@ -15,15 +15,8 @@ */ package org.springframework.data.relational.core.dialect; -import java.util.List; - -import org.springframework.data.relational.core.sql.IdentifierProcessing; -import org.springframework.data.relational.core.sql.LockOptions; -import org.springframework.data.relational.core.sql.Table; -import org.springframework.data.relational.core.sql.IdentifierProcessing.LetterCasing; -import org.springframework.data.relational.core.sql.IdentifierProcessing.Quoting; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; +import java.util.Collection; +import java.util.Collections; /** * An SQL dialect for Oracle. @@ -51,4 +44,10 @@ protected OracleDialect() {} public IdGeneration getIdGeneration() { return ID_GENERATION; } + + @Override + public Collection getConverters() { + return Collections.singletonList(Timestamp2OffsetDateTimeConverter.INSTANCE); + } + } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java index 08beb8b9dc..e21618d68a 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java @@ -15,14 +15,16 @@ */ package org.springframework.data.relational.core.dialect; +import java.util.Collection; +import java.util.Collections; import java.util.List; 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; import org.springframework.data.relational.core.sql.LockOptions; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.core.sql.Table; +import org.springframework.data.relational.core.sql.IdentifierProcessing.LetterCasing; +import org.springframework.data.relational.core.sql.IdentifierProcessing.Quoting; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -113,6 +115,11 @@ public ArrayColumns getArraySupport() { return ARRAY_COLUMNS; } + @Override + public Collection getConverters() { + return Collections.singletonList(Timestamp2OffsetDateTimeConverter.INSTANCE); + } + static class PostgresLockClause implements LockClause { private final IdentifierProcessing identifierProcessing; @@ -165,7 +172,7 @@ public String getLock(LockOptions lockOptions) { public Position getClausePosition() { return Position.AFTER_ORDER_BY; } - }; + } static class PostgresArrayColumns implements ArrayColumns { @@ -195,4 +202,5 @@ public Class getArrayType(Class userType) { public IdentifierProcessing getIdentifierProcessing() { return IdentifierProcessing.create(Quoting.ANSI, LetterCasing.LOWER_CASE); } + } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Timestamp2OffsetDateTimeConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Timestamp2OffsetDateTimeConverter.java new file mode 100644 index 0000000000..230f234dc5 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Timestamp2OffsetDateTimeConverter.java @@ -0,0 +1,42 @@ +/* + * 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.relational.core.dialect; + +import java.sql.Timestamp; +import java.time.OffsetDateTime; +import java.time.ZoneId; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.ReadingConverter; + +/** + * A reading convert to convert {@link Timestamp} to {@link OffsetDateTime}. For the conversion the {@link Timestamp} + * gets considered to be at UTC and the result of the conversion will have an offset of 0 and represent the same + * instant. + * + * @author Jens Schauder + * @since 2.3 + */ +@ReadingConverter +enum Timestamp2OffsetDateTimeConverter implements Converter { + + INSTANCE; + + @Override + public OffsetDateTime convert(Timestamp timestamp) { + return OffsetDateTime.ofInstant(timestamp.toInstant(), ZoneId.of("UTC")); + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/Timestamp2OffsetDateTimeConverterUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/Timestamp2OffsetDateTimeConverterUnitTests.java new file mode 100644 index 0000000000..a4c127abe6 --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/Timestamp2OffsetDateTimeConverterUnitTests.java @@ -0,0 +1,43 @@ +package org.springframework.data.relational.core.dialect; + +import org.junit.jupiter.api.Test; + +import java.sql.Timestamp; +import java.time.Instant; +import java.time.OffsetDateTime; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +/* + * 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. + */ + +/** + * Tests {@link Timestamp2OffsetDateTimeConverter}. + * + * @author Jens Schauder + */ +class Timestamp2OffsetDateTimeConverterUnitTests { + + @Test + void conversionMaintainsInstant() { + + Timestamp timestamp = Timestamp.from(Instant.now()); + OffsetDateTime converted = Timestamp2OffsetDateTimeConverter.INSTANCE.convert(timestamp); + + assertThat(converted.toInstant()).isEqualTo(timestamp.toInstant()); + } +} \ No newline at end of file From 42df93180df2d8074b4ecb92ff958a77519bbbb4 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Wed, 19 May 2021 11:37:48 +0200 Subject: [PATCH 3/5] Polishing. Removes superfluous method. --- .../jdbc/testing/MySqlDataSourceConfiguration.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java index 5e2cb7b5ce..8da00a7744 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java @@ -77,4 +77,15 @@ public void afterPropertiesSet() throws Exception { new ByteArrayResource("DROP DATABASE test;CREATE DATABASE test;".getBytes())); } } + + private DataSource createRootDataSource() { + + MysqlDataSource dataSource = new MysqlDataSource(); + dataSource.setUrl(MYSQL_CONTAINER.getJdbcUrl()); + dataSource.setUser("root"); + dataSource.setPassword(MYSQL_CONTAINER.getPassword()); + dataSource.setDatabaseName(MYSQL_CONTAINER.getDatabaseName()); + + return dataSource; + } } From 2e88a32d625f674861b101abdaedc48f9de82cca Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 21 May 2021 14:07:44 +0200 Subject: [PATCH 4/5] Applied feedback from review. Originial pull request #981 See #935 --- .../jdbc/core/dialect/JdbcDb2Dialect.java | 6 +-- .../data/jdbc/core/dialect/JdbcH2Dialect.java | 4 +- .../jdbc/core/dialect/JdbcMySqlDialect.java | 4 +- .../core/dialect/JdbcSqlServerDialect.java | 7 ++-- ...> OffsetDateTimeToTimestampConverter.java} | 2 +- .../config/AbstractJdbcConfiguration.java | 41 +++++++++++++++---- .../jdbc/core/dialect/JdbcH2DialectTests.java | 21 +++++----- ...ateTimeToTimestampConverterUnitTests.java} | 7 ++-- ...ractJdbcConfigurationIntegrationTests.java | 2 +- .../data/jdbc/support/JdbcUtilTests.java | 17 ++++---- .../relational/core/dialect/Db2Dialect.java | 2 +- .../core/dialect/MariaDbDialect.java | 2 +- .../relational/core/dialect/MySqlDialect.java | 2 +- .../core/dialect/OracleDialect.java | 2 +- .../core/dialect/PostgresDialect.java | 2 +- ...estampAtUtcToOffsetDateTimeConverter.java} | 2 +- ...tcToOffsetDateTimeConverterUnitTests.java} | 13 +++--- 17 files changed, 78 insertions(+), 58 deletions(-) rename spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/{OffsetDateTime2TimestampConverter.java => OffsetDateTimeToTimestampConverter.java} (93%) rename spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/{OffsetDateTime2TimestampConverterUnitTests.java => OffsetDateTimeToTimestampConverterUnitTests.java} (81%) rename spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/{Timestamp2OffsetDateTimeConverter.java => TimestampAtUtcToOffsetDateTimeConverter.java} (93%) rename spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/{Timestamp2OffsetDateTimeConverterUnitTests.java => TimestampAtUtcToOffsetDateTimeConverterUnitTests.java} (81%) 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 2824496a6b..80dd7874d8 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 @@ -17,9 +17,9 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.List; import org.springframework.data.relational.core.dialect.Db2Dialect; -import org.springframework.data.relational.core.sql.IdentifierProcessing; /** * {@link Db2Dialect} that registers JDBC specific converters. @@ -34,8 +34,8 @@ public class JdbcDb2Dialect extends Db2Dialect { @Override public Collection getConverters() { - ArrayList converters = new ArrayList<>(super.getConverters()); - converters.add(OffsetDateTime2TimestampConverter.INSTANCE); + List converters = new ArrayList<>(super.getConverters()); + converters.add(OffsetDateTimeToTimestampConverter.INSTANCE); return converters; } 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 b7fdd1b909..0199f0bc69 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 @@ -38,11 +38,11 @@ public class JdbcH2Dialect extends H2Dialect { @Override public Collection getConverters() { - return Collections.singletonList(TimestampWithTimeZone2OffsetDateTimeConverter.INSTANCE); + return Collections.singletonList(TimestampWithTimeZoneToOffsetDateTimeConverter.INSTANCE); } @ReadingConverter - enum TimestampWithTimeZone2OffsetDateTimeConverter implements Converter { + enum TimestampWithTimeZoneToOffsetDateTimeConverter implements Converter { INSTANCE; 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 26ce00d2e2..89aea0a14f 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 @@ -43,13 +43,13 @@ public JdbcMySqlDialect(IdentifierProcessing identifierProcessing) { public Collection getConverters() { ArrayList converters = new ArrayList<>(super.getConverters()); - converters.add(OffsetDateTime2TimestampJdbcValueConverter.INSTANCE); + converters.add(OffsetDateTimeToTimestampJdbcValueConverter.INSTANCE); return converters; } @WritingConverter - enum OffsetDateTime2TimestampJdbcValueConverter implements Converter { + 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 1592814e3c..0618b44584 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 @@ -23,11 +23,10 @@ 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.SqlServerDialect; /** - * {@link Db2Dialect} that registers JDBC specific converters. + * {@link SqlServerDialect} that registers JDBC specific converters. * * @author Jens Schauder * @since 2.3 @@ -38,11 +37,11 @@ public class JdbcSqlServerDialect extends SqlServerDialect { @Override public Collection getConverters() { - return Collections.singletonList(DateTimeOffset2OffsetDateTimeConverter.INSTANCE); + return Collections.singletonList(DateTimeOffsetToOffsetDateTimeConverter.INSTANCE); } @ReadingConverter - enum DateTimeOffset2OffsetDateTimeConverter implements Converter { + enum DateTimeOffsetToOffsetDateTimeConverter implements Converter { INSTANCE; @Override diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/OffsetDateTime2TimestampConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/OffsetDateTimeToTimestampConverter.java similarity index 93% rename from spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/OffsetDateTime2TimestampConverter.java rename to spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/OffsetDateTimeToTimestampConverter.java index 185a0c2682..3c363d6305 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/OffsetDateTime2TimestampConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/OffsetDateTimeToTimestampConverter.java @@ -31,7 +31,7 @@ * @since 2.3 */ @WritingConverter -enum OffsetDateTime2TimestampConverter implements Converter { +enum OffsetDateTimeToTimestampConverter implements Converter { INSTANCE; @Override 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 46fbe3bc8e..b0e7c980cd 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 @@ -20,7 +20,12 @@ import java.util.List; import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; @@ -55,13 +60,17 @@ * @since 1.1 */ @Configuration(proxyBeanMethods = false) -public class AbstractJdbcConfiguration { +public class AbstractJdbcConfiguration implements ApplicationContextAware { + + private static Logger LOG = LoggerFactory.getLogger(AbstractJdbcConfiguration.class); + + private ApplicationContext applicationContext; /** * Register a {@link JdbcMappingContext} and apply an optional {@link NamingStrategy}. * * @param namingStrategy optional {@link NamingStrategy}. Use {@link NamingStrategy#INSTANCE} as fallback. - * @param customConversions see {@link #jdbcCustomConversions(Dialect)}. + * @param customConversions see {@link #jdbcCustomConversions()}. * @return must not be {@literal null}. */ @Bean @@ -76,10 +85,11 @@ public JdbcMappingContext jdbcMappingContext(Optional namingStra /** * Creates a {@link RelationalConverter} using the configured - * {@link #jdbcMappingContext(Optional, JdbcCustomConversions)}. Will get {@link #jdbcCustomConversions(Dialect)} ()} applied. + * {@link #jdbcMappingContext(Optional, JdbcCustomConversions)}. Will get {@link #jdbcCustomConversions()} ()} + * applied. * * @see #jdbcMappingContext(Optional, JdbcCustomConversions) - * @see #jdbcCustomConversions(Dialect) () + * @see #jdbcCustomConversions() * @return must not be {@literal null}. */ @Bean @@ -89,7 +99,7 @@ public JdbcConverter jdbcConverter(JdbcMappingContext mappingContext, NamedParam DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(operations.getJdbcOperations()); return new BasicJdbcConverter(mappingContext, relationResolver, conversions, jdbcTypeFactory, - dialect.getIdentifierProcessing()); + dialect.getIdentifierProcessing()); } /** @@ -101,10 +111,20 @@ public JdbcConverter jdbcConverter(JdbcMappingContext mappingContext, NamedParam * @return will never be {@literal null}. */ @Bean - public JdbcCustomConversions jdbcCustomConversions(Dialect dialect) { + public JdbcCustomConversions jdbcCustomConversions() { + + try { + + Dialect dialect = applicationContext.getBean(Dialect.class); - return new JdbcCustomConversions(CustomConversions.StoreConversions.of(JdbcSimpleTypes.HOLDER, - storeConverters(dialect)), userConverters()); + return new JdbcCustomConversions( + CustomConversions.StoreConversions.of(JdbcSimpleTypes.HOLDER, storeConverters(dialect)), userConverters()); + } catch (NoSuchBeanDefinitionException exception) { + + LOG.warn("No dialect found. CustomConversions will be configured without dialect specific conversions."); + + return new JdbcCustomConversions(); + } } private List userConverters() { @@ -162,4 +182,9 @@ public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations op public Dialect jdbcDialect(NamedParameterJdbcOperations operations) { return DialectResolver.getDialect(operations.getJdbcOperations()); } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/JdbcH2DialectTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/JdbcH2DialectTests.java index f3eb17611d..511ecd3e28 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/JdbcH2DialectTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/JdbcH2DialectTests.java @@ -1,13 +1,3 @@ -package org.springframework.data.jdbc.core.dialect; - -import static org.assertj.core.api.Assertions.*; - -import java.time.OffsetDateTime; - -import org.h2.api.TimestampWithTimeZone; -import org.h2.util.DateTimeUtils; -import org.junit.jupiter.api.Test; - /* * Copyright 2021 the original author or authors. * @@ -23,6 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.springframework.data.jdbc.core.dialect; + +import static org.assertj.core.api.Assertions.*; + +import java.time.OffsetDateTime; + +import org.h2.api.TimestampWithTimeZone; +import org.h2.util.DateTimeUtils; +import org.junit.jupiter.api.Test; /** * Tests for {@link JdbcH2Dialect}. @@ -34,7 +33,7 @@ class JdbcH2DialectTests { @Test void TimestampWithTimeZone2OffsetDateTimeConverterConvertsProperly() { - JdbcH2Dialect.TimestampWithTimeZone2OffsetDateTimeConverter converter = JdbcH2Dialect.TimestampWithTimeZone2OffsetDateTimeConverter.INSTANCE; + JdbcH2Dialect.TimestampWithTimeZoneToOffsetDateTimeConverter converter = JdbcH2Dialect.TimestampWithTimeZoneToOffsetDateTimeConverter.INSTANCE; long dateValue = 123456789; long timeNanos = 987654321; int timeZoneOffsetSeconds = 4 * 60 * 60; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/OffsetDateTime2TimestampConverterUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/OffsetDateTimeToTimestampConverterUnitTests.java similarity index 81% rename from spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/OffsetDateTime2TimestampConverterUnitTests.java rename to spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/OffsetDateTimeToTimestampConverterUnitTests.java index 0f7a182221..15f4fd5c16 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/OffsetDateTime2TimestampConverterUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/dialect/OffsetDateTimeToTimestampConverterUnitTests.java @@ -7,7 +7,6 @@ import java.time.ZoneOffset; import static org.assertj.core.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.*; /* * Copyright 2021 the original author or authors. @@ -26,18 +25,18 @@ */ /** - * Tests for {@link OffsetDateTime2TimestampConverter}. + * Tests for {@link OffsetDateTimeToTimestampConverter}. * * @author Jens Schauder */ -class OffsetDateTime2TimestampConverterUnitTests { +class OffsetDateTimeToTimestampConverterUnitTests { @Test void conversionPreservesInstant() { OffsetDateTime offsetDateTime = OffsetDateTime.of(5, 5, 5, 5,5,5,123456789, ZoneOffset.ofHours(3)); - Timestamp timestamp = OffsetDateTime2TimestampConverter.INSTANCE.convert(offsetDateTime); + Timestamp timestamp = OffsetDateTimeToTimestampConverter.INSTANCE.convert(offsetDateTime); assertThat(timestamp.toInstant()).isEqualTo(offsetDateTime.toInstant()); } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java index 797ad1263d..1ec72619c9 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java @@ -110,7 +110,7 @@ public Dialect jdbcDialect(NamedParameterJdbcOperations operations) { } @Override - public JdbcCustomConversions jdbcCustomConversions(Dialect dialect) { + public JdbcCustomConversions jdbcCustomConversions() { return new JdbcCustomConversions(Collections.singletonList(Blah2BlubbConverter.INSTANCE)); } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/support/JdbcUtilTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/support/JdbcUtilTests.java index 30f532fe4e..371c752289 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/support/JdbcUtilTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/support/JdbcUtilTests.java @@ -1,12 +1,3 @@ -package org.springframework.data.jdbc.support; - -import static org.assertj.core.api.Assertions.*; - -import java.sql.Types; -import java.time.OffsetDateTime; - -import org.junit.jupiter.api.Test; - /* * Copyright 2021 the original author or authors. * @@ -22,6 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.springframework.data.jdbc.support; + +import static org.assertj.core.api.Assertions.*; + +import java.sql.Types; +import java.time.OffsetDateTime; + +import org.junit.jupiter.api.Test; /** * Tests for {@link JdbcUtil}. diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Db2Dialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Db2Dialect.java index ae88a76223..82f527a148 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Db2Dialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Db2Dialect.java @@ -116,6 +116,6 @@ public IdentifierProcessing getIdentifierProcessing() { @Override public Collection getConverters() { - return Collections.singletonList(Timestamp2OffsetDateTimeConverter.INSTANCE); + return Collections.singletonList(TimestampAtUtcToOffsetDateTimeConverter.INSTANCE); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MariaDbDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MariaDbDialect.java index 9ed373487a..c983b37bc4 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MariaDbDialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MariaDbDialect.java @@ -34,6 +34,6 @@ public MariaDbDialect(IdentifierProcessing identifierProcessing) { @Override public Collection getConverters() { - return Collections.singletonList(Timestamp2OffsetDateTimeConverter.INSTANCE); + return Collections.singletonList(TimestampAtUtcToOffsetDateTimeConverter.INSTANCE); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java index 6dc8e8c80f..6032582186 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java @@ -167,6 +167,6 @@ public IdentifierProcessing getIdentifierProcessing() { @Override public Collection getConverters() { - return Collections.singletonList(Timestamp2OffsetDateTimeConverter.INSTANCE); + return Collections.singletonList(TimestampAtUtcToOffsetDateTimeConverter.INSTANCE); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/OracleDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/OracleDialect.java index f19ae642cb..76f0c72cda 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/OracleDialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/OracleDialect.java @@ -47,7 +47,7 @@ public IdGeneration getIdGeneration() { @Override public Collection getConverters() { - return Collections.singletonList(Timestamp2OffsetDateTimeConverter.INSTANCE); + return Collections.singletonList(TimestampAtUtcToOffsetDateTimeConverter.INSTANCE); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java index e21618d68a..6c93a52d18 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java @@ -117,7 +117,7 @@ public ArrayColumns getArraySupport() { @Override public Collection getConverters() { - return Collections.singletonList(Timestamp2OffsetDateTimeConverter.INSTANCE); + return Collections.singletonList(TimestampAtUtcToOffsetDateTimeConverter.INSTANCE); } static class PostgresLockClause implements LockClause { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Timestamp2OffsetDateTimeConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/TimestampAtUtcToOffsetDateTimeConverter.java similarity index 93% rename from spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Timestamp2OffsetDateTimeConverter.java rename to spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/TimestampAtUtcToOffsetDateTimeConverter.java index 230f234dc5..7ec957c951 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Timestamp2OffsetDateTimeConverter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/TimestampAtUtcToOffsetDateTimeConverter.java @@ -31,7 +31,7 @@ * @since 2.3 */ @ReadingConverter -enum Timestamp2OffsetDateTimeConverter implements Converter { +enum TimestampAtUtcToOffsetDateTimeConverter implements Converter { INSTANCE; diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/Timestamp2OffsetDateTimeConverterUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/TimestampAtUtcToOffsetDateTimeConverterUnitTests.java similarity index 81% rename from spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/Timestamp2OffsetDateTimeConverterUnitTests.java rename to spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/TimestampAtUtcToOffsetDateTimeConverterUnitTests.java index a4c127abe6..dee5e7befc 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/Timestamp2OffsetDateTimeConverterUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/TimestampAtUtcToOffsetDateTimeConverterUnitTests.java @@ -1,13 +1,12 @@ package org.springframework.data.relational.core.dialect; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.*; import java.sql.Timestamp; import java.time.Instant; import java.time.OffsetDateTime; -import static org.assertj.core.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; /* * Copyright 2021 the original author or authors. @@ -26,18 +25,18 @@ */ /** - * Tests {@link Timestamp2OffsetDateTimeConverter}. + * Tests {@link TimestampAtUtcToOffsetDateTimeConverter}. * * @author Jens Schauder */ -class Timestamp2OffsetDateTimeConverterUnitTests { +class TimestampAtUtcToOffsetDateTimeConverterUnitTests { @Test void conversionMaintainsInstant() { Timestamp timestamp = Timestamp.from(Instant.now()); - OffsetDateTime converted = Timestamp2OffsetDateTimeConverter.INSTANCE.convert(timestamp); + OffsetDateTime converted = TimestampAtUtcToOffsetDateTimeConverter.INSTANCE.convert(timestamp); assertThat(converted.toInstant()).isEqualTo(timestamp.toInstant()); } -} \ No newline at end of file +} From e35c4e9fae10c9610fee2ec4fd5b2a46e601cb09 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 28 May 2021 12:58:35 +0200 Subject: [PATCH 5/5] 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. --- .../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 efad38c13d..97a5b3cbc7 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 @@ * * @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 JdbcCustomConversions(ConverterConfiguration converterConfiguration) { 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 80dd7874d8..d99f9cc39a 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 Collection getConverters() { 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 0199f0bc69..e95ae77414 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 OffsetDateTime convert(TimestampWithTimeZone source) { 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 89aea0a14f..2dd8a86907 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 @@ * {@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 JdbcMySqlDialect(IdentifierProcessing identifierProcessing) { super(identifierProcessing); } + protected JdbcMySqlDialect() {} + @Override public Collection getConverters() { @@ -50,6 +53,7 @@ public Collection getConverters() { @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 0618b44584..9883c23a46 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 @@ 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 @@ * {@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 3c363d6305..0000000000 --- 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 b0e7c980cd..be6398b3cf 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.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.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 JdbcCustomConversions jdbcCustomConversions() { 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 @@ 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-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 15f4fd5c16..cecf7dcbcd 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 @@ */ /** - * Tests for {@link OffsetDateTimeToTimestampConverter}. + * Tests for {@link JdbcDb2Dialect.OffsetDateTimeToTimestampConverter}. * * @author Jens Schauder */ @@ -36,8 +36,8 @@ void conversionPreservesInstant() { 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 2e4207e18c..e74d30b0b2 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.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 @@ * @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 @@ JdbcMappingContext jdbcMappingContext(Optional namingStrategy, C @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 864a21bacd..d12e54e19e 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 @@ 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 @@ * @author Mark Paluch * @author Jens Schauder * @author Myeonghyeon Lee + * @author Christoph Strobl * @since 1.1 */ public interface Dialect { @@ -97,4 +99,14 @@ default IdGeneration getIdGeneration() { 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 74b8e90b8c..7444edde27 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 @@ * * @author Mark Paluch * @author Myeonghyeon Lee + * @author Christph Strobl * @since 2.0 */ public class H2Dialect extends AbstractDialect { @@ -137,4 +141,21 @@ public Class getArrayType(Class userType) { 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); + } + } }