From 48df4279ccfd80262bdb7435022b664b48996bf5 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 4 Feb 2019 08:21:18 +0100 Subject: [PATCH 1/4] DATAJDBC-327 - 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 6569c7812a..7f1c5303cc 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-327-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index 1753776a73..574fab3940 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 - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-327-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index 9458230173..fda2e45a52 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-data-jdbc - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-327-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-327-SNAPSHOT diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 11e7db737c..a012ff5896 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-data-relational - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-327-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -13,7 +13,7 @@ org.springframework.data spring-data-relational-parent - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-327-SNAPSHOT From ee68f1d75b94c778f75eac5a36c54a352a174dab Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 26 Feb 2019 13:40:21 +0100 Subject: [PATCH 2/4] DATAJDBC-327 - Adds support for conversion to JdbcTypeAware and storing byte[] as binary. Some JDBC drivers depend on correct explicit type information in order to pass on parameters to the database. So far CustomConversion had no way to provide that information. With this change one can use @WritingConverter that converts to JdbcTypeAware in order to provide that information. byte[] now also get stored as BINARY. See also: DATAJDBC-239. --- .../jdbc/core/DefaultDataAccessStrategy.java | 167 +++++++++++------- .../jdbc/core/convert/BasicJdbcConverter.java | 116 ++++++++++-- .../core/convert/DefaultJdbcTypeFactory.java | 60 +++++++ .../data/jdbc/core/convert/JdbcConverter.java | 17 +- .../data/jdbc/core/convert/JdbcTypeAware.java | 35 ++++ .../jdbc/core/convert/JdbcTypeFactory.java | 44 +++++ .../mybatis/MyBatisDataAccessStrategy.java | 8 +- .../config/AbstractJdbcConfiguration.java | 10 +- .../repository/config/JdbcConfiguration.java | 8 +- .../support/JdbcRepositoryFactoryBean.java | 6 +- .../data/jdbc/support/JdbcUtil.java | 53 ++++++ .../data/jdbc/support/package-info.java | 7 + .../DefaultDataAccessStrategyUnitTests.java | 16 +- ...JdbcAggregateTemplateIntegrationTests.java | 21 +++ ...lConverterAggregateReferenceUnitTests.java | 2 +- .../mybatis/MyBatisHsqlIntegrationTests.java | 3 +- ...itoryCustomConversionIntegrationTests.java | 158 +++++++++++++++++ .../SimpleJdbcRepositoryEventsUnitTests.java | 13 +- ...nableJdbcRepositoriesIntegrationTests.java | 3 +- .../JdbcRepositoryFactoryBeanUnitTests.java | 7 +- .../data/jdbc/testing/TestConfiguration.java | 23 +-- .../src/test/resources/logback.xml | 4 +- ...AggregateTemplateIntegrationTests-hsql.sql | 3 + ...regateTemplateIntegrationTests-mariadb.sql | 4 +- ...ggregateTemplateIntegrationTests-mssql.sql | 5 +- ...ggregateTemplateIntegrationTests-mysql.sql | 2 + ...egateTemplateIntegrationTests-postgres.sql | 8 + ...yCustomConversionIntegrationTests-hsql.sql | 1 + ...stomConversionIntegrationTests-mariadb.sql | 1 + ...CustomConversionIntegrationTests-mssql.sql | 1 + ...CustomConversionIntegrationTests-mysql.sql | 1 + ...tomConversionIntegrationTests-postgres.sql | 1 + .../conversion/BasicRelationalConverter.java | 24 +++ .../core/conversion/RelationalConverter.java | 4 + src/main/asciidoc/jdbc.adoc | 6 + 35 files changed, 714 insertions(+), 128 deletions(-) create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeAware.java create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeFactory.java create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/package-info.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mariadb.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mssql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mysql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java index 4b3a48cfc0..4319895265 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java @@ -16,24 +16,25 @@ package org.springframework.data.jdbc.core; import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import java.sql.Connection; import java.sql.JDBCType; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.jdbc.core.convert.JdbcConverter; +import org.springframework.data.jdbc.core.convert.JdbcTypeAware; import org.springframework.data.jdbc.support.JdbcUtil; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PropertyHandler; -import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; @@ -43,6 +44,7 @@ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.JdbcUtils; import org.springframework.jdbc.support.KeyHolder; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -55,27 +57,32 @@ * @author Thomas Lang * @author Bastian Wilhelm */ -@RequiredArgsConstructor public class DefaultDataAccessStrategy implements DataAccessStrategy { private final @NonNull SqlGeneratorSource sqlGeneratorSource; private final @NonNull RelationalMappingContext context; - private final @NonNull RelationalConverter converter; + private final @NonNull JdbcConverter converter; private final @NonNull NamedParameterJdbcOperations operations; private final @NonNull DataAccessStrategy accessStrategy; + public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, RelationalMappingContext context, + JdbcConverter converter, NamedParameterJdbcOperations operations, @Nullable DataAccessStrategy accessStrategy) { + + this.sqlGeneratorSource = sqlGeneratorSource; + this.context = context; + this.converter = converter; + this.operations = operations; + this.accessStrategy = accessStrategy == null ? this : accessStrategy; + } + /** * Creates a {@link DefaultDataAccessStrategy} which references it self for resolution of recursive data accesses. * Only suitable if this is the only access strategy in use. */ public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, RelationalMappingContext context, - RelationalConverter converter, NamedParameterJdbcOperations operations) { + JdbcConverter converter, NamedParameterJdbcOperations operations) { - this.sqlGeneratorSource = sqlGeneratorSource; - this.operations = operations; - this.context = context; - this.converter = converter; - this.accessStrategy = this; + this(sqlGeneratorSource, context, converter, operations, null); } /* @@ -97,28 +104,19 @@ public Object insert(T instance, Class domainType, Identifier identifier) KeyHolder holder = new GeneratedKeyHolder(); RelationalPersistentEntity persistentEntity = getRequiredPersistentEntity(domainType); - Map parameters = new LinkedHashMap<>(identifier.size()); - identifier.forEach((name, value, type) -> { - parameters.put(name, converter.writeValue(value, ClassTypeInformation.from(type))); - }); + MapSqlParameterSource parameterSource = getParameterSource(instance, persistentEntity, "", true); - MapSqlParameterSource parameterSource = getPropertyMap(instance, persistentEntity, ""); + identifier.forEach((name, value, type) -> addConvertedPropertyValue(parameterSource, name, value, type)); Object idValue = getIdValueOrNull(instance, persistentEntity); - RelationalPersistentProperty idProperty = persistentEntity.getIdProperty(); - if (idValue != null) { - Assert.notNull(idProperty, "Since we have a non-null idValue, we must have an idProperty as well."); - - parameters.put(idProperty.getColumnName(), - converter.writeValue(idValue, ClassTypeInformation.from(idProperty.getColumnType()))); + RelationalPersistentProperty idProperty = persistentEntity.getRequiredIdProperty(); + addConvertedPropertyValue(parameterSource, idProperty, idValue, idProperty.getColumnName()); } - parameters.forEach(parameterSource::addValue); - operations.update( // - sql(domainType).getInsert(parameters.keySet()), // + sql(domainType).getInsert(new HashSet<>(Arrays.asList(parameterSource.getParameterNames()))), // parameterSource, // holder // ); @@ -135,7 +133,8 @@ public boolean update(S instance, Class domainType) { RelationalPersistentEntity persistentEntity = getRequiredPersistentEntity(domainType); - return operations.update(sql(domainType).getUpdate(), getPropertyMap(instance, persistentEntity, "")) != 0; + return operations.update(sql(domainType).getUpdate(), + getParameterSource(instance, persistentEntity, "", false)) != 0; } /* @@ -240,17 +239,14 @@ public Iterable findAll(Class domainType) { @Override public Iterable findAllById(Iterable ids, Class domainType) { - String findAllInListSql = sql(domainType).getFindAllInList(); - Class targetType = getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType(); + RelationalPersistentProperty idProperty = getRequiredPersistentEntity(domainType).getRequiredIdProperty(); + MapSqlParameterSource parameterSource = new MapSqlParameterSource(); - MapSqlParameterSource parameter = new MapSqlParameterSource( // - "ids", // - StreamSupport.stream(ids.spliterator(), false) // - .map(id -> converter.writeValue(id, ClassTypeInformation.from(targetType))) // - .collect(Collectors.toList()) // - ); + addConvertedPropertyValuesAsList(parameterSource, idProperty, ids, "ids"); + + String findAllInListSql = sql(domainType).getFindAllInList(); - return operations.query(findAllInListSql, parameter, (RowMapper) getEntityRowMapper(domainType)); + return operations.query(findAllInListSql, parameterSource, (RowMapper) getEntityRowMapper(domainType)); } /* @@ -292,8 +288,8 @@ public boolean existsById(Object id, Class domainType) { return result; } - private MapSqlParameterSource getPropertyMap(S instance, RelationalPersistentEntity persistentEntity, - String prefix) { + private MapSqlParameterSource getParameterSource(S instance, RelationalPersistentEntity persistentEntity, + String prefix, boolean skipId) { MapSqlParameterSource parameters = new MapSqlParameterSource(); @@ -301,6 +297,9 @@ private MapSqlParameterSource getPropertyMap(S instance, RelationalPersis persistentEntity.doWithProperties((PropertyHandler) property -> { + if (skipId && property.isIdProperty()) { + return; + } if (property.isEntity() && !property.isEmbedded()) { return; } @@ -309,42 +308,21 @@ private MapSqlParameterSource getPropertyMap(S instance, RelationalPersis Object value = propertyAccessor.getProperty(property); RelationalPersistentEntity embeddedEntity = context.getPersistentEntity(property.getType()); - MapSqlParameterSource additionalParameters = getPropertyMap((T) value, - (RelationalPersistentEntity) embeddedEntity, prefix + property.getEmbeddedPrefix()); + MapSqlParameterSource additionalParameters = getParameterSource((T) value, + (RelationalPersistentEntity) embeddedEntity, prefix + property.getEmbeddedPrefix(), skipId); parameters.addValues(additionalParameters.getValues()); } else { Object value = propertyAccessor.getProperty(property); - Object convertedValue = convertForWrite(property, value); + String paramName = prefix + property.getColumnName(); - parameters.addValue(prefix + property.getColumnName(), convertedValue, - JdbcUtil.sqlTypeFor(property.getColumnType())); + addConvertedPropertyValue(parameters, property, value, paramName); } }); return parameters; } - @Nullable - private Object convertForWrite(RelationalPersistentProperty property, @Nullable Object value) { - - Object convertedValue = converter.writeValue(value, ClassTypeInformation.from(property.getColumnType())); - - if (convertedValue == null || !convertedValue.getClass().isArray()) { - return convertedValue; - } - - Class componentType = convertedValue.getClass(); - while (componentType.isArray()) { - componentType = componentType.getComponentType(); - } - - String typeName = JDBCType.valueOf(JdbcUtil.sqlTypeFor(componentType)).getName(); - - return operations.getJdbcOperations() - .execute((Connection c) -> c.createArrayOf(typeName, (Object[]) convertedValue)); - } - @SuppressWarnings("unchecked") @Nullable private ID getIdValueOrNull(S instance, RelationalPersistentEntity persistentEntity) { @@ -398,8 +376,65 @@ private RowMapper getMapEntityRowMapper(RelationalPersistentProperty property private MapSqlParameterSource createIdParameterSource(Object id, Class domainType) { - Class columnType = getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType(); - return new MapSqlParameterSource("id", converter.writeValue(id, ClassTypeInformation.from(columnType))); + MapSqlParameterSource parameterSource = new MapSqlParameterSource(); + + addConvertedPropertyValue( // + parameterSource, // + getRequiredPersistentEntity(domainType).getRequiredIdProperty(), // + id, // + "id" // + ); + return parameterSource; + } + + private void addConvertedPropertyValue(MapSqlParameterSource parameterSource, RelationalPersistentProperty property, + Object value, String paramName) { + + JdbcTypeAware jdbcTypeAware = converter.writeTypeAware( // + value, // + property.getColumnType(), // + property.getSqlType() // + ); + + parameterSource.addValue(paramName, jdbcTypeAware.getValue(), JdbcUtil.sqlTypeFor(jdbcTypeAware.getJdbcType())); + } + + private void addConvertedPropertyValue(MapSqlParameterSource parameterSource, String name, Object value, + Class type) { + + JdbcTypeAware jdbcTypeAware = converter.writeTypeAware( // + value, // + type, // + JdbcUtil.sqlTypeFor(type) // + ); + + parameterSource.addValue( // + name, // + jdbcTypeAware.getValue(), // + JdbcUtil.sqlTypeFor(jdbcTypeAware.getJdbcType()) // + ); + } + + private void addConvertedPropertyValuesAsList(MapSqlParameterSource parameterSource, + RelationalPersistentProperty property, Iterable values, String paramName) { + + List convertedIds = new ArrayList<>(); + JdbcTypeAware jdbcTypeAware = null; + for (Object id : values) { + + Class columnType = property.getColumnType(); + int sqlType = property.getSqlType(); + + jdbcTypeAware = converter.writeTypeAware(id, columnType, sqlType); + convertedIds.add(jdbcTypeAware.getValue()); + } + + Assert.notNull(jdbcTypeAware, "JdbcTypeAware must be not null at this point. Please report this as a bug."); + + JDBCType jdbcType = jdbcTypeAware.getJdbcType(); + int typeNumber = jdbcType == null ? JdbcUtils.TYPE_UNKNOWN : jdbcType.getVendorTypeNumber(); + + parameterSource.addValue(paramName, convertedIds, typeNumber); } @SuppressWarnings("unchecked") diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java index e94f91bf92..33647040c6 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java @@ -15,29 +15,28 @@ */ package org.springframework.data.jdbc.core.convert; +import java.sql.Array; +import java.sql.JDBCType; +import java.sql.SQLException; +import java.util.Arrays; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.data.convert.CustomConversions; import org.springframework.data.jdbc.core.mapping.AggregateReference; -import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.jdbc.support.JdbcUtil; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; -import org.springframework.data.relational.domain.Identifier; +import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; -import java.sql.Array; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - /** * {@link RelationalConverter} that uses a {@link MappingContext} to apply basic conversion of relational values to * property values. @@ -54,14 +53,45 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc private static final Logger LOG = LoggerFactory.getLogger(BasicJdbcConverter.class); + private final JdbcTypeFactory typeFactory; + /** * Creates a new {@link BasicRelationalConverter} given {@link MappingContext}. * * @param context must not be {@literal null}. org.springframework.data.jdbc.core.DefaultDataAccessStrategyUnitTests + * @param typeFactory */ public BasicJdbcConverter( - MappingContext, ? extends RelationalPersistentProperty> context) { + MappingContext, ? extends RelationalPersistentProperty> context, + JdbcTypeFactory typeFactory) { super(context); + this.typeFactory = typeFactory; + } + + /** + * Creates a new {@link BasicRelationalConverter} given {@link MappingContext} and {@link CustomConversions}. + * + * @param context must not be {@literal null}. + * @param conversions must not be {@literal null}. + * @param typeFactory + */ + public BasicJdbcConverter( + MappingContext, ? extends RelationalPersistentProperty> context, + CustomConversions conversions, JdbcTypeFactory typeFactory) { + super(context, conversions); + this.typeFactory = typeFactory; + } + + /** + * Creates a new {@link BasicRelationalConverter} given {@link MappingContext}. + * + * @param context must not be {@literal null}. org.springframework.data.jdbc.core.DefaultDataAccessStrategyUnitTests + * @deprecated use one of the constructors with {@link JdbcTypeFactory} parameter. + */ + @Deprecated + public BasicJdbcConverter( + MappingContext, ? extends RelationalPersistentProperty> context) { + this(context, JdbcTypeFactory.DUMMY_JDBC_TYPE_FACTORY); } /** @@ -69,11 +99,13 @@ public BasicJdbcConverter( * * @param context must not be {@literal null}. * @param conversions must not be {@literal null}. + * @deprecated use one of the constructors with {@link JdbcTypeFactory} parameter. */ + @Deprecated public BasicJdbcConverter( MappingContext, ? extends RelationalPersistentProperty> context, CustomConversions conversions) { - super(context, conversions); + this(context, conversions, JdbcTypeFactory.DUMMY_JDBC_TYPE_FACTORY); } /* @@ -102,7 +134,7 @@ public Object readValue(@Nullable Object value, TypeInformation type) { if (value instanceof Array) { try { return readValue(((Array) value).getArray(), type); - } catch (SQLException | ConverterNotFoundException e ) { + } catch (SQLException | ConverterNotFoundException e) { LOG.info("Failed to extract a value of type %s from an Array. Attempting to use standard conversions.", e); } } @@ -129,5 +161,65 @@ public Object writeValue(@Nullable Object value, TypeInformation type) { return super.writeValue(value, type); } + @Override + public boolean canWriteValue(@Nullable Object value, TypeInformation type) { + if (value == null) { + return true; + } + + if (AggregateReference.class.isAssignableFrom(value.getClass())) { + return canWriteValue(((AggregateReference) value).getId(), type); + } + return super.canWriteValue(value, type); + } + + public JdbcTypeAware writeTypeAware(@Nullable Object value, Class columnType, int sqlType) { + + JdbcTypeAware jdbcTypeAware = tryToConvertToJdbcTypeAware(value); + if (jdbcTypeAware != null) { + return jdbcTypeAware; + } + + Object convertedValue = writeValue(value, ClassTypeInformation.from(columnType)); + + if (convertedValue == null || !convertedValue.getClass().isArray()) { + return JdbcTypeAware.of(convertedValue, JdbcUtil.jdbcTypeFor(sqlType)); + } + + Class componentType = convertedValue.getClass().getComponentType(); + if (componentType != byte.class && componentType != Byte.class) { + return JdbcTypeAware.of(typeFactory.createArray((Object[]) convertedValue), JDBCType.ARRAY); + } + + if (componentType == Byte.class) { + Byte[] boxedBytes = (Byte[]) convertedValue; + byte[] bytes = new byte[boxedBytes.length]; + for (int i = 0; i < boxedBytes.length; i++) { + bytes[i] = boxedBytes[i]; + } + convertedValue = bytes; + } + + return JdbcTypeAware.of(convertedValue, JDBCType.BINARY); + + } + + private JdbcTypeAware tryToConvertToJdbcTypeAware(@Nullable Object value) { + + JdbcTypeAware jdbcTypeAware = null; + ClassTypeInformation jdbcTypeAwareClassTypeInformation = ClassTypeInformation + .from(JdbcTypeAware.class); + if (canWriteValue(value, jdbcTypeAwareClassTypeInformation)) { + + try { + + jdbcTypeAware = (JdbcTypeAware) writeValue(value, jdbcTypeAwareClassTypeInformation); + + } catch (ConversionException __) { + // a conversion may still fail + } + } + return jdbcTypeAware; + } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java new file mode 100644 index 0000000000..e8d132f9ee --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.convert; + +import org.springframework.data.jdbc.support.JdbcUtil; +import org.springframework.jdbc.core.ConnectionCallback; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.util.Assert; + +import java.sql.Array; +import java.sql.JDBCType; + +/** + * A {@link JdbcTypeFactory} that performs the conversion by utilizing {@link JdbcOperations#execute(ConnectionCallback)}. + * + * @author Jens Schauder + * @since 1.1 + */ +public class DefaultJdbcTypeFactory implements JdbcTypeFactory { + + private final JdbcOperations operations; + + public DefaultJdbcTypeFactory(JdbcOperations operations) { + this.operations = operations; + } + + @Override + public Array createArray(Object[] value) { + + Class componentType = innermostComponentType(value); + + JDBCType jdbcType = JdbcUtil.jdbcTypeFor(componentType); + Assert.notNull(jdbcType, () -> String.format("Couldn't determine JDBCType for %s", componentType)); + String typeName = jdbcType.getName(); + + return operations.execute((ConnectionCallback) c -> c.createArrayOf(typeName, value)); + } + + private static Class innermostComponentType(Object convertedValue) { + + Class componentType = convertedValue.getClass(); + while (componentType.isArray()) { + componentType = componentType.getComponentType(); + } + return componentType; + } +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java index 2d460ed54f..977352814a 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java @@ -16,13 +16,26 @@ package org.springframework.data.jdbc.core.convert; import org.springframework.data.relational.core.conversion.RelationalConverter; +import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; /** * A {@link JdbcConverter} is responsible for converting for values to the native relational representation and vice * versa. * * @author Jens Schauder - * * @since 1.1 */ -public interface JdbcConverter extends RelationalConverter {} +public interface JdbcConverter extends RelationalConverter { + + /** + * Convert a property value into a {@link JdbcTypeAware} that contains the converted value and information how to bind + * it to JDBC parameters. + * + * @param value a value as it is used in the object model. May be {@code null}. + * @param type {@link TypeInformation} into which the value is to be converted. Must not be {@code null}. + * @param sqlType the type constant from {@link java.sql.Types} to be used if non is specified by a converter. + * @return The converted value wrapped in a {@link JdbcTypeAware}. Guaranteed to be not {@literal null}. + */ + JdbcTypeAware writeTypeAware(@Nullable Object value, Class type, int sqlType); +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeAware.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeAware.java new file mode 100644 index 0000000000..755722faad --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeAware.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.convert; + +import lombok.Value; + +import java.sql.JDBCType; + +/** + * Wraps a value with the JDBCType that should be used to pass it as a bind parameter to a + * {@link java.sql.PreparedStatement}. Register a converter from any type to {@link JdbcTypeAware} in order to control + * the value and the {@link JDBCType} as which a value should get passed to the JDBC driver. + * + * @author Jens Schauder + * @since 1.1 + */ +@Value(staticConstructor = "of") +public class JdbcTypeAware { + + Object value; + JDBCType jdbcType; +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeFactory.java new file mode 100644 index 0000000000..b99b3d5ab9 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.convert; + +import java.sql.Array; + +/** + * Allows the creation of instances of database dependent types, e.g. {@link Array}. + * + * @author Jens Schauder + * @since 1.1 + */ +public interface JdbcTypeFactory { + + /** + * A dummy implementation used in places where a proper {@code JdbcTypeFactory} can not be provided but an instance + * needs to be provided anyway, mostly for providing backward compatibility. Calling it will result in an exception. + * The features normally supported by a {@link JdbcTypeFactory} will not work. + */ + JdbcTypeFactory DUMMY_JDBC_TYPE_FACTORY = value -> { + throw new UnsupportedOperationException("This JdbcTypeFactory does not support Array creation"); + }; + + /** + * Converts the provided value in a {@link Array} instance. + * + * @param value the value to be converted. Must not be {@literal null}. + * @return an {@link Array}. Guaranteed to be not {@literal null}. + */ + Array createArray(Object[] value); +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java index 0677a15e3e..c0dbb71259 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java @@ -28,9 +28,9 @@ import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; import org.springframework.data.jdbc.core.DelegatingDataAccessStrategy; import org.springframework.data.jdbc.core.SqlGeneratorSource; +import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PropertyPath; -import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.domain.Identifier; @@ -61,7 +61,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy { * uses a {@link DefaultDataAccessStrategy} */ public static DataAccessStrategy createCombinedAccessStrategy(RelationalMappingContext context, - RelationalConverter converter, NamedParameterJdbcOperations operations, SqlSession sqlSession) { + JdbcConverter converter, NamedParameterJdbcOperations operations, SqlSession sqlSession) { return createCombinedAccessStrategy(context, converter, operations, sqlSession, NamespaceStrategy.DEFAULT_INSTANCE); } @@ -70,7 +70,7 @@ public static DataAccessStrategy createCombinedAccessStrategy(RelationalMappingC * uses a {@link DefaultDataAccessStrategy} */ public static DataAccessStrategy createCombinedAccessStrategy(RelationalMappingContext context, - RelationalConverter converter, NamedParameterJdbcOperations operations, SqlSession sqlSession, + JdbcConverter converter, NamedParameterJdbcOperations operations, SqlSession sqlSession, NamespaceStrategy namespaceStrategy) { // the DefaultDataAccessStrategy needs a reference to the returned DataAccessStrategy. This creates a dependency @@ -104,7 +104,7 @@ public static DataAccessStrategy createCombinedAccessStrategy(RelationalMappingC * transaction. Note that the resulting {@link DataAccessStrategy} only handles MyBatis. It does not include the * functionality of the {@link org.springframework.data.jdbc.core.DefaultDataAccessStrategy} which one normally still * wants. Use - * {@link #createCombinedAccessStrategy(RelationalMappingContext, RelationalConverter, NamedParameterJdbcOperations, SqlSession, NamespaceStrategy)} + * {@link #createCombinedAccessStrategy(RelationalMappingContext, JdbcConverter, NamedParameterJdbcOperations, SqlSession, NamespaceStrategy)} * to create such a {@link DataAccessStrategy}. * * @param sqlSession Must be non {@literal null}. 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 cf471c363b..ef1eead661 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 @@ -26,12 +26,14 @@ import org.springframework.data.jdbc.core.JdbcAggregateTemplate; import org.springframework.data.jdbc.core.SqlGeneratorSource; import org.springframework.data.jdbc.core.convert.BasicJdbcConverter; +import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory; import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.RelationalMappingContext; +import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; /** @@ -71,13 +73,13 @@ public JdbcMappingContext jdbcMappingContext(Optional namingStra * @return must not be {@literal null}. */ @Bean - public JdbcConverter jdbcConverter(RelationalMappingContext mappingContext) { - return new BasicJdbcConverter(mappingContext, jdbcCustomConversions()); + public JdbcConverter jdbcConverter(RelationalMappingContext mappingContext, JdbcOperations operations) { + return new BasicJdbcConverter(mappingContext, jdbcCustomConversions(), new DefaultJdbcTypeFactory(operations)); } /** * Register custom {@link Converter}s in a {@link JdbcCustomConversions} object if required. These - * {@link JdbcCustomConversions} will be registered with the {@link #relationalConverter(RelationalMappingContext)}. + * {@link JdbcCustomConversions} will be registered with the {@link #jdbcConverter(RelationalMappingContext, JdbcOperations)}. * Returns an empty {@link JdbcCustomConversions} instance by default. * * @return must not be {@literal null}. @@ -100,7 +102,7 @@ public JdbcCustomConversions jdbcCustomConversions() { */ @Bean public JdbcAggregateTemplate jdbcAggregateTemplate(ApplicationEventPublisher publisher, - RelationalMappingContext context, RelationalConverter converter, NamedParameterJdbcOperations operations) { + RelationalMappingContext context, JdbcConverter converter, NamedParameterJdbcOperations operations) { DataAccessStrategy dataAccessStrategy = new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter, operations); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java index 31ade20c78..fca6acfac6 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java @@ -27,7 +27,9 @@ import org.springframework.data.jdbc.core.JdbcAggregateTemplate; import org.springframework.data.jdbc.core.SqlGeneratorSource; import org.springframework.data.jdbc.core.convert.BasicJdbcConverter; +import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; +import org.springframework.data.jdbc.core.convert.JdbcTypeFactory; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.NamingStrategy; @@ -42,7 +44,6 @@ * @author Mark Paluch * @author Michael Simons * @author Christoph Strobl - * * @deprecated Use {@link AbstractJdbcConfiguration} instead. */ @Configuration @@ -74,7 +75,8 @@ public RelationalMappingContext jdbcMappingContext(Optional nami */ @Bean public RelationalConverter relationalConverter(RelationalMappingContext mappingContext) { - return new BasicJdbcConverter(mappingContext, jdbcCustomConversions()); + + return new BasicJdbcConverter(mappingContext, jdbcCustomConversions(), JdbcTypeFactory.DUMMY_JDBC_TYPE_FACTORY); } /** @@ -101,7 +103,7 @@ public JdbcCustomConversions jdbcCustomConversions() { */ @Bean public JdbcAggregateOperations jdbcAggregateOperations(ApplicationEventPublisher publisher, - RelationalMappingContext context, RelationalConverter converter, NamedParameterJdbcOperations operations) { + RelationalMappingContext context, JdbcConverter converter, NamedParameterJdbcOperations operations) { DataAccessStrategy dataAccessStrategy = new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter, operations); return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java index 4318c582a3..5cdf430f20 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java @@ -24,9 +24,9 @@ import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; import org.springframework.data.jdbc.core.SqlGeneratorSource; +import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.repository.QueryMappingConfiguration; import org.springframework.data.jdbc.repository.RowMapperMap; -import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactorySupport; @@ -50,7 +50,7 @@ public class JdbcRepositoryFactoryBean, S, ID extend private ApplicationEventPublisher publisher; private BeanFactory beanFactory; private RelationalMappingContext mappingContext; - private RelationalConverter converter; + private JdbcConverter converter; private DataAccessStrategy dataAccessStrategy; private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY; private NamedParameterJdbcOperations operations; @@ -128,7 +128,7 @@ public void setJdbcOperations(NamedParameterJdbcOperations operations) { } @Autowired - public void setConverter(RelationalConverter converter) { + public void setConverter(JdbcConverter converter) { this.converter = converter; } 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 887f8e4563..f1cc782ee0 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 @@ -20,6 +20,7 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Date; +import java.sql.JDBCType; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; @@ -27,6 +28,8 @@ import java.util.Map; import org.springframework.jdbc.support.JdbcUtils; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * Contains methods dealing with the quirks of JDBC, independent of any Entity, Aggregate or Repository abstraction. @@ -64,11 +67,61 @@ public class JdbcUtil { sqlTypeMappings.put(Timestamp.class, Types.TIMESTAMP); } + /** + * Returns the {@link Types} value suitable for passing a value of the provided type to a + * {@link java.sql.PreparedStatement}. + * + * @param type The type of value to be bound to a {@link java.sql.PreparedStatement}. + * @return One of the values defined in {@link Types} or {@link JdbcUtils#TYPE_UNKNOWN}. + */ public static int sqlTypeFor(Class type) { + + Assert.notNull(type, "Type must not be null."); + return sqlTypeMappings.keySet().stream() // .filter(k -> k.isAssignableFrom(type)) // .findFirst() // .map(sqlTypeMappings::get) // .orElse(JdbcUtils.TYPE_UNKNOWN); } + + /** + * Converts a {@link JDBCType} to an {@code int} value as defined in {@link Types}. + * + * @param jdbcType value to be converted. May be {@literal null}. + * @return One of the values defined in {@link Types} or {@link JdbcUtils#TYPE_UNKNOWN}. + */ + public static int sqlTypeFor(@Nullable JDBCType jdbcType) { + return jdbcType == null ? JdbcUtils.TYPE_UNKNOWN : jdbcType.getVendorTypeNumber(); + } + + /** + * Converts a value defined in {@link Types} into a {@link JDBCType} instance or {@literal null} if the value is + * {@link JdbcUtils#TYPE_UNKNOWN} + * + * @param sqlType One of the values defined in {@link Types} or {@link JdbcUtils#TYPE_UNKNOWN}. + * @return a matching {@link JDBCType} instance or {@literal null}. + */ + @Nullable + public static JDBCType jdbcTypeFor(int sqlType) { + + if (sqlType == JdbcUtils.TYPE_UNKNOWN) { + return null; + } + + return JDBCType.valueOf(sqlType); + } + + /** + * Returns the {@link JDBCType} suitable for passing a value of the provided type to a + * {@link java.sql.PreparedStatement}. + * + * @param type The type of value to be bound to a {@link java.sql.PreparedStatement}. + * @return a matching {@link JDBCType} instance or {@literal null}. + */ + @Nullable + public static JDBCType jdbcTypeFor(Class type) { + + return jdbcTypeFor(sqlTypeFor(type)); + } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/package-info.java new file mode 100644 index 0000000000..9790f9dedb --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/package-info.java @@ -0,0 +1,7 @@ +/** + * @author Jens Schauder + */ +@NonNullApi +package org.springframework.data.jdbc.support; + +import org.springframework.lang.NonNullApi; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategyUnitTests.java index 08b0ad76a7..621c219445 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategyUnitTests.java @@ -16,8 +16,7 @@ package org.springframework.data.jdbc.core; import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import lombok.AllArgsConstructor; @@ -32,10 +31,11 @@ import org.springframework.data.annotation.Id; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; +import org.springframework.data.jdbc.core.convert.BasicJdbcConverter; +import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory; +import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; -import org.springframework.data.relational.core.conversion.BasicRelationalConverter; -import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.SqlParameterSource; @@ -54,7 +54,8 @@ public class DefaultDataAccessStrategyUnitTests { NamedParameterJdbcOperations jdbcOperations = mock(NamedParameterJdbcOperations.class); RelationalMappingContext context = new JdbcMappingContext(); - RelationalConverter converter = new BasicRelationalConverter(context, new JdbcCustomConversions()); + JdbcConverter converter = new BasicJdbcConverter(context, new JdbcCustomConversions(), + new DefaultJdbcTypeFactory(jdbcOperations.getJdbcOperations())); HashMap additionalParameters = new HashMap<>(); ArgumentCaptor paramSourceCaptor = ArgumentCaptor.forClass(SqlParameterSource.class); @@ -95,8 +96,9 @@ public void additionalParametersGetAddedToStatement() { @Test // DATAJDBC-235 public void considersConfiguredWriteConverter() { - RelationalConverter converter = new BasicRelationalConverter(context, - new JdbcCustomConversions(Arrays.asList(BooleanToStringConverter.INSTANCE, StringToBooleanConverter.INSTANCE))); + JdbcConverter converter = new BasicJdbcConverter(context, + new JdbcCustomConversions(Arrays.asList(BooleanToStringConverter.INSTANCE, StringToBooleanConverter.INSTANCE)), + new DefaultJdbcTypeFactory(jdbcOperations.getJdbcOperations())); DefaultDataAccessStrategy accessStrategy = new DefaultDataAccessStrategy( // new SqlGeneratorSource(context), // diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java index 2fc7f427c3..379aaa72e3 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java @@ -419,6 +419,21 @@ public void saveAndLoadAnEntityWithSet() { assertThat(reloaded.digits).isEqualTo(new HashSet<>(Arrays.asList("one", "two", "three"))); } + @Test // DATAJDBC-327 + public void saveAndLoadAnEntityWithByteArray() { + ByteArrayOwner owner = new ByteArrayOwner(); + owner.binaryData = new byte[]{1, 23, 42}; + + ByteArrayOwner saved = template.save(owner); + + ByteArrayOwner reloaded = template.findById(saved.id, ByteArrayOwner.class); + + assertThat(reloaded).isNotNull(); + assertThat(reloaded.id).isEqualTo(saved.id); + assertThat(reloaded.binaryData).isEqualTo(new byte[]{1, 23, 42}); + } + + private static void assumeNot(String dbProfileName) { Assume.assumeTrue("true" @@ -433,6 +448,12 @@ private static class ArrayOwner { String[][] multidimensional; } + private static class ByteArrayOwner { + @Id Long id; + + byte[] binaryData; + } + @Table("ARRAY_OWNER") private static class ListOwner { @Id Long id; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java index c4296f978b..61793238b3 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java @@ -43,7 +43,7 @@ public class BasicRelationalConverterAggregateReferenceUnitTests { ConversionService conversionService = new DefaultConversionService(); JdbcMappingContext context = new JdbcMappingContext(); - RelationalConverter converter = new BasicJdbcConverter(context); + RelationalConverter converter = new BasicJdbcConverter(context, JdbcTypeFactory.DUMMY_JDBC_TYPE_FACTORY); RelationalPersistentEntity entity = context.getRequiredPersistentEntity(DummyEntity.class); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java index db310f16f6..1d15ba2fa4 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java @@ -32,6 +32,7 @@ import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; import org.springframework.data.jdbc.core.DataAccessStrategy; +import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.relational.core.conversion.RelationalConverter; @@ -90,7 +91,7 @@ SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory factory) { @Bean @Primary - DataAccessStrategy dataAccessStrategy(RelationalMappingContext context, RelationalConverter converter, + DataAccessStrategy dataAccessStrategy(RelationalMappingContext context, JdbcConverter converter, SqlSession sqlSession, EmbeddedDatabase db) { return MyBatisDataAccessStrategy.createCombinedAccessStrategy(context, converter, diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java new file mode 100644 index 0000000000..17a61c9b34 --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java @@ -0,0 +1,158 @@ +/* + * Copyright 2017-2018 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.repository; + +import static java.util.Arrays.*; +import static org.assertj.core.api.Assertions.*; + +import java.math.BigDecimal; +import java.sql.JDBCType; +import java.util.Optional; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.annotation.Id; +import org.springframework.data.convert.ReadingConverter; +import org.springframework.data.convert.WritingConverter; +import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; +import org.springframework.data.jdbc.core.convert.JdbcTypeAware; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.repository.CrudRepository; +import org.springframework.lang.Nullable; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.rules.SpringClassRule; +import org.springframework.test.context.junit4.rules.SpringMethodRule; +import org.springframework.transaction.annotation.Transactional; + +/** + * Tests storing and retrieving data types that get processed by custom conversions. + * + * @author Jens Schauder + */ +@ContextConfiguration +@Transactional +public class JdbcRepositoryCustomConversionIntegrationTests { + + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryCustomConversionIntegrationTests.class; + } + + @Bean + EntityWithBooleanRepository repository() { + return factory.getRepository(EntityWithBooleanRepository.class); + } + + @Bean + JdbcCustomConversions jdbcCustomConversions() { + return new JdbcCustomConversions(asList(BigDecimalToString.INSTANCE, StringToBigDecimalConverter.INSTANCE)); + } + } + + @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); + @Rule public SpringMethodRule methodRule = new SpringMethodRule(); + + @Autowired EntityWithBooleanRepository repository; + + /** + * In PostrgreSQL this fails if a simple converter like the following is used. + * + *
+	 * {@code
+	 @WritingConverter
+	 enum PlainStringToBigDecimalConverter implements Converter {
+	
+	 INSTANCE;
+	
+	 @Override
+	 @Nullable
+	 public BigDecimal convert(@Nullable String source) {
+	
+	 return source == null ? null : new BigDecimal(source);
+	 }
+	
+	 }
+	}
+	 * 
+ */ + + @Test // DATAJDBC-327 + public void saveAndLoadAnEntity() { + + EntityWithStringyBigDecimal entity = new EntityWithStringyBigDecimal(); + entity.stringyNumber = "123456.78910"; + + repository.save(entity); + + Optional reloaded = repository.findById(entity.id); + + // loading the number from the database might result in additional zeros at the end. + String stringyNumber = reloaded.get().stringyNumber; + assertThat(stringyNumber).startsWith(entity.stringyNumber); + assertThat(stringyNumber.substring(entity.stringyNumber.length())).matches("0*"); + } + + interface EntityWithBooleanRepository extends CrudRepository {} + + private static class EntityWithStringyBigDecimal { + + @Id Long id; + String stringyNumber; + } + + @WritingConverter + enum StringToBigDecimalConverter implements Converter { + + INSTANCE; + + @Override + public JdbcTypeAware convert(@Nullable String source) { + + Object value = source == null ? null : new BigDecimal(source); + return JdbcTypeAware.of(value, JDBCType.DECIMAL); + } + + } + + @ReadingConverter + enum BigDecimalToString implements Converter { + + INSTANCE; + + @Override + public String convert(@Nullable BigDecimal source) { + + if (source == null) { + return null; + } + + return source.toString(); + } + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java index 2610ac6a08..ed64d1c1cb 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java @@ -17,9 +17,7 @@ import static java.util.Arrays.*; import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import junit.framework.AssertionFailedError; @@ -39,12 +37,13 @@ import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; import org.springframework.data.jdbc.core.SqlGeneratorSource; +import org.springframework.data.jdbc.core.convert.BasicJdbcConverter; +import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory; +import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository; -import org.springframework.data.relational.core.conversion.BasicRelationalConverter; -import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.event.AfterDeleteEvent; import org.springframework.data.relational.core.mapping.event.AfterLoadEvent; @@ -76,9 +75,9 @@ public class SimpleJdbcRepositoryEventsUnitTests { public void before() { RelationalMappingContext context = new JdbcMappingContext(); - RelationalConverter converter = new BasicRelationalConverter(context, new JdbcCustomConversions()); - NamedParameterJdbcOperations operations = createIdGeneratingOperations(); + JdbcConverter converter = new BasicJdbcConverter(context, new JdbcCustomConversions(), + new DefaultJdbcTypeFactory(operations.getJdbcOperations())); SqlGeneratorSource generatorSource = new SqlGeneratorSource(context); this.dataAccessStrategy = spy(new DefaultDataAccessStrategy(generatorSource, context, converter, operations)); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java index 6e0dca18fb..f312a1e408 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java @@ -34,6 +34,7 @@ import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; import org.springframework.data.jdbc.core.SqlGeneratorSource; +import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.repository.QueryMappingConfiguration; import org.springframework.data.jdbc.repository.config.EnableJdbcRepositoriesIntegrationTests.TestConfiguration; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean; @@ -149,7 +150,7 @@ NamedParameterJdbcOperations qualifierJdbcOperations(DataSource dataSource) { @Bean("qualifierDataAccessStrategy") DataAccessStrategy defaultDataAccessStrategy(@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template, - RelationalMappingContext context, RelationalConverter converter) { + RelationalMappingContext context, JdbcConverter converter) { return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter, template); } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java index f3590d6f7d..50baa948e8 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java @@ -33,9 +33,10 @@ import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; +import org.springframework.data.jdbc.core.convert.BasicJdbcConverter; +import org.springframework.data.jdbc.core.convert.JdbcTypeFactory; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.repository.QueryMappingConfiguration; -import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -82,7 +83,7 @@ public void setsUpBasicInstanceCorrectly() { factoryBean.setDataAccessStrategy(dataAccessStrategy); factoryBean.setMappingContext(mappingContext); - factoryBean.setConverter(new BasicRelationalConverter(mappingContext)); + factoryBean.setConverter(new BasicJdbcConverter(mappingContext, JdbcTypeFactory.DUMMY_JDBC_TYPE_FACTORY)); factoryBean.setApplicationEventPublisher(publisher); factoryBean.setBeanFactory(beanFactory); factoryBean.afterPropertiesSet(); @@ -109,7 +110,7 @@ public void afterPropertiesThowsExceptionWhenNoMappingContextSet() { public void afterPropertiesSetDefaultsNullablePropertiesCorrectly() { factoryBean.setMappingContext(mappingContext); - factoryBean.setConverter(new BasicRelationalConverter(mappingContext)); + factoryBean.setConverter(new BasicJdbcConverter(mappingContext, JdbcTypeFactory.DUMMY_JDBC_TYPE_FACTORY)); factoryBean.setApplicationEventPublisher(publisher); factoryBean.setBeanFactory(beanFactory); factoryBean.afterPropertiesSet(); 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 acb98c664b..bc37ff8c57 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 @@ -26,12 +26,13 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; import org.springframework.data.convert.CustomConversions; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; import org.springframework.data.jdbc.core.SqlGeneratorSource; import org.springframework.data.jdbc.core.convert.BasicJdbcConverter; +import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory; +import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; @@ -60,8 +61,9 @@ public class TestConfiguration { @Autowired(required = false) SqlSessionFactory sqlSessionFactory; @Bean - JdbcRepositoryFactory jdbcRepositoryFactory(@Qualifier("defaultDataAccessStrategy") DataAccessStrategy dataAccessStrategy, - RelationalMappingContext context, RelationalConverter converter) { + JdbcRepositoryFactory jdbcRepositoryFactory( + @Qualifier("defaultDataAccessStrategy") DataAccessStrategy dataAccessStrategy, RelationalMappingContext context, + RelationalConverter converter) { return new JdbcRepositoryFactory(dataAccessStrategy, context, converter, publisher, namedParameterJdbcTemplate()); } @@ -76,14 +78,14 @@ PlatformTransactionManager transactionManager() { } @Bean - DataAccessStrategy defaultDataAccessStrategy(@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template, - RelationalMappingContext context, RelationalConverter converter) { - return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter,template); + DataAccessStrategy defaultDataAccessStrategy( + @Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template, RelationalMappingContext context, + JdbcConverter converter) { + return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter, template); } @Bean - JdbcMappingContext jdbcMappingContext(@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template, Optional namingStrategy, - CustomConversions conversions) { + JdbcMappingContext jdbcMappingContext(Optional namingStrategy, CustomConversions conversions) { JdbcMappingContext mappingContext = new JdbcMappingContext(namingStrategy.orElse(NamingStrategy.INSTANCE)); mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder()); @@ -96,7 +98,8 @@ CustomConversions jdbcCustomConversions() { } @Bean - RelationalConverter relationalConverter(RelationalMappingContext mappingContext, CustomConversions conversions) { - return new BasicJdbcConverter(mappingContext, conversions); + JdbcConverter relationalConverter(RelationalMappingContext mappingContext, CustomConversions conversions, + @Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template) { + return new BasicJdbcConverter(mappingContext, conversions, new DefaultJdbcTypeFactory(template.getJdbcOperations())); } } diff --git a/spring-data-jdbc/src/test/resources/logback.xml b/spring-data-jdbc/src/test/resources/logback.xml index f1bfdbaf39..6da1bab099 100644 --- a/spring-data-jdbc/src/test/resources/logback.xml +++ b/spring-data-jdbc/src/test/resources/logback.xml @@ -7,8 +7,8 @@ - - + + diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql index 4edb29701d..1699ad6c50 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql @@ -15,3 +15,6 @@ ALTER TABLE ELEMENT_NO_ID CREATE TABLE ARRAY_OWNER (ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, DIGITS VARCHAR(20) ARRAY[10] NOT NULL, MULTIDIMENSIONAL VARCHAR(20) ARRAY[10] NULL); + +CREATE TABLE BYTE_ARRAY_OWNER (ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, BINARY_DATA VARBINARY(20) NOT NULL); + diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql index 8b739ea10f..38957bcc61 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql @@ -8,4 +8,6 @@ CREATE TABLE ONE_TO_ONE_PARENT ( id3 BIGINT AUTO_INCREMENT PRIMARY KEY, content CREATE TABLE Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR(30)); CREATE TABLE LIST_PARENT ( id4 BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100)); -CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT BIGINT); \ No newline at end of file +CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT BIGINT); + +CREATE TABLE BYTE_ARRAY_OWNER (ID BIGINT AUTO_INCREMENT PRIMARY KEY, BINARY_DATA VARBINARY(20) NOT NULL) \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql index 764f924362..b507634c35 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql @@ -12,4 +12,7 @@ CREATE TABLE Child_No_Id (ONE_TO_ONE_PARENT BIGINT PRIMARY KEY, content VARCHAR( DROP TABLE IF EXISTS element_no_id; DROP TABLE IF EXISTS LIST_PARENT; CREATE TABLE LIST_PARENT ( id4 BIGINT IDENTITY PRIMARY KEY, NAME VARCHAR(100)); -CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT BIGINT); \ No newline at end of file +CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT BIGINT); + +DROP TABLE IF EXISTS BYTE_ARRAY_OWNER; +CREATE TABLE BYTE_ARRAY_OWNER (ID BIGINT IDENTITY PRIMARY KEY, BINARY_DATA VARBINARY(20) NOT NULL) \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql index 07ecf86ba7..38957bcc61 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql @@ -9,3 +9,5 @@ CREATE TABLE Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR CREATE TABLE LIST_PARENT ( id4 BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100)); CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT BIGINT); + +CREATE TABLE BYTE_ARRAY_OWNER (ID BIGINT AUTO_INCREMENT PRIMARY KEY, BINARY_DATA VARBINARY(20) NOT NULL) \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql index d5ba24a963..4f39362347 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql @@ -1,5 +1,11 @@ DROP TABLE MANUAL; DROP TABLE LEGO_SET; +DROP TABLE ONE_TO_ONE_PARENT; +DROP TABLE Child_No_Id; +DROP TABLE LIST_PARENT; +DROP TABLE element_no_id; +DROP TABLE ARRAY_OWNER; +DROP TABLE BYTE_ARRAY_OWNER; CREATE TABLE LEGO_SET ( id1 SERIAL PRIMARY KEY, NAME VARCHAR(30)); CREATE TABLE MANUAL ( id2 SERIAL PRIMARY KEY, LEGO_SET BIGINT, ALTERNATIVE BIGINT, CONTENT VARCHAR(2000)); @@ -14,3 +20,5 @@ CREATE TABLE LIST_PARENT ( id4 SERIAL PRIMARY KEY, NAME VARCHAR(100)); CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT INTEGER); CREATE TABLE ARRAY_OWNER (ID SERIAL PRIMARY KEY, DIGITS VARCHAR(20)[10], MULTIDIMENSIONAL VARCHAR(20)[10][10]); + +CREATE TABLE BYTE_ARRAY_OWNER (ID SERIAL PRIMARY KEY, BINARY_DATA BYTEA NOT NULL) \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql new file mode 100644 index 0000000000..dd3175f7e4 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql @@ -0,0 +1 @@ +CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id IDENTITY PRIMARY KEY, Stringy_number DECIMAL(20,10)) diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mariadb.sql new file mode 100644 index 0000000000..bbf3cfb964 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mariadb.sql @@ -0,0 +1 @@ +CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id BIGINT AUTO_INCREMENT PRIMARY KEY, Stringy_number DECIMAL(20,10)) diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mssql.sql new file mode 100644 index 0000000000..a9e632f606 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mssql.sql @@ -0,0 +1 @@ +CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id BIGINT IDENTITY PRIMARY KEY, Stringy_number DECIMAL(20,10)) diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mysql.sql new file mode 100644 index 0000000000..bbf3cfb964 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mysql.sql @@ -0,0 +1 @@ +CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id BIGINT AUTO_INCREMENT PRIMARY KEY, Stringy_number DECIMAL(20,10)) diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql new file mode 100644 index 0000000000..0852a75120 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql @@ -0,0 +1 @@ +CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id SERIAL PRIMARY KEY, Stringy_number DECIMAL(20,10)) diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java index 2d519a749b..2cad2ef845 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java @@ -193,6 +193,30 @@ public Object writeValue(@Nullable Object value, TypeInformation type) { return conversionService.convert(value, rawType); } + + @Override + public boolean canWriteValue(Object value, TypeInformation type) { + + if (value == null) { + return true; + } + + Class rawType = type.getType(); + RelationalPersistentEntity persistentEntity = context.getPersistentEntity(value.getClass()); + + if (persistentEntity != null) { + + Object id = persistentEntity.getIdentifierAccessor(value).getIdentifier(); + return canWriteValue(id, type); + } + + if (rawType.isInstance(value)) { + return true; + } + + return conversionService.canConvert(value.getClass(), rawType); + } + /** * Checks whether we have a custom conversion registered for the given value into an arbitrary simple JDBC type. * Returns the converted value if so. If not, we perform special enum handling or simply return the value as is. diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java index 4a81c45458..f2da792015 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java @@ -92,4 +92,8 @@ T createInstance(PersistentEntity entity, */ @Nullable Object writeValue(@Nullable Object value, TypeInformation type); + + default boolean canWriteValue(@Nullable Object value, TypeInformation type) { + return true; + } } diff --git a/src/main/asciidoc/jdbc.adoc b/src/main/asciidoc/jdbc.adoc index 07fcf33e35..1bbc82798e 100644 --- a/src/main/asciidoc/jdbc.adoc +++ b/src/main/asciidoc/jdbc.adoc @@ -188,6 +188,12 @@ Converters should be annotated with `@ReadingConverter` or `@WritingConverter` i `TIMESTAMPTZ` in the example is a database specific data type that needs conversion into something more suitable for a domain model. +==== JdbcTypeAware + +When setting bind parameters with a JDBC driver one may opt to provide a `java.sql.Types` constant value to denote the type of the parameter. +If for a value this type need to be specified this can be done by using a writing converter as described in the previous section. +This converter should convert to `JdbcTypeAware` which has a field for the value and one of for the `JDBCType`. + [[jdbc.entity-persistence.naming-strategy]] === `NamingStrategy` From 373d2d301512a622bb01a4e8fb1f2d2fe1cf774f Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 11 Mar 2019 11:46:55 +0100 Subject: [PATCH 3/4] DATAJDBC-327 - Address review comments. --- .../jdbc/core/DefaultDataAccessStrategy.java | 36 +++++----- .../data/jdbc/core/convert/ArrayUtil.java | 42 +++++++++++ .../jdbc/core/convert/BasicJdbcConverter.java | 69 +++++++++---------- .../core/convert/DefaultJdbcTypeFactory.java | 2 + .../data/jdbc/core/convert/JdbcConverter.java | 6 +- .../jdbc/core/convert/JdbcTypeFactory.java | 15 ++-- .../{JdbcTypeAware.java => JdbcValue.java} | 4 +- .../repository/config/JdbcConfiguration.java | 2 +- ...lConverterAggregateReferenceUnitTests.java | 2 +- ...itoryCustomConversionIntegrationTests.java | 25 ++++--- .../JdbcRepositoryFactoryBeanUnitTests.java | 4 +- .../conversion/BasicRelationalConverter.java | 24 ------- .../core/conversion/RelationalConverter.java | 4 -- src/main/asciidoc/jdbc.adoc | 4 +- 14 files changed, 128 insertions(+), 111 deletions(-) create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtil.java rename spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/{JdbcTypeAware.java => JdbcValue.java} (92%) diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java index 4319895265..58b20c1470 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java @@ -22,16 +22,17 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Predicate; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.jdbc.core.convert.JdbcConverter; -import org.springframework.data.jdbc.core.convert.JdbcTypeAware; +import org.springframework.data.jdbc.core.convert.JdbcValue; import org.springframework.data.jdbc.support.JdbcUtil; +import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PropertyHandler; @@ -39,7 +40,6 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.domain.Identifier; -import org.springframework.data.util.ClassTypeInformation; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -104,7 +104,7 @@ public Object insert(T instance, Class domainType, Identifier identifier) KeyHolder holder = new GeneratedKeyHolder(); RelationalPersistentEntity persistentEntity = getRequiredPersistentEntity(domainType); - MapSqlParameterSource parameterSource = getParameterSource(instance, persistentEntity, "", true); + MapSqlParameterSource parameterSource = getParameterSource(instance, persistentEntity, "", PersistentProperty::isIdProperty); identifier.forEach((name, value, type) -> addConvertedPropertyValue(parameterSource, name, value, type)); @@ -134,7 +134,7 @@ public boolean update(S instance, Class domainType) { RelationalPersistentEntity persistentEntity = getRequiredPersistentEntity(domainType); return operations.update(sql(domainType).getUpdate(), - getParameterSource(instance, persistentEntity, "", false)) != 0; + getParameterSource(instance, persistentEntity, "", property -> false)) != 0; } /* @@ -289,7 +289,7 @@ public boolean existsById(Object id, Class domainType) { } private MapSqlParameterSource getParameterSource(S instance, RelationalPersistentEntity persistentEntity, - String prefix, boolean skipId) { + String prefix, Predicate skipProperty) { MapSqlParameterSource parameters = new MapSqlParameterSource(); @@ -297,7 +297,7 @@ private MapSqlParameterSource getParameterSource(S instance, RelationalPe persistentEntity.doWithProperties((PropertyHandler) property -> { - if (skipId && property.isIdProperty()) { + if (skipProperty.test(property)) { return; } if (property.isEntity() && !property.isEmbedded()) { @@ -309,7 +309,7 @@ private MapSqlParameterSource getParameterSource(S instance, RelationalPe Object value = propertyAccessor.getProperty(property); RelationalPersistentEntity embeddedEntity = context.getPersistentEntity(property.getType()); MapSqlParameterSource additionalParameters = getParameterSource((T) value, - (RelationalPersistentEntity) embeddedEntity, prefix + property.getEmbeddedPrefix(), skipId); + (RelationalPersistentEntity) embeddedEntity, prefix + property.getEmbeddedPrefix(), skipProperty); parameters.addValues(additionalParameters.getValues()); } else { @@ -390,19 +390,19 @@ private MapSqlParameterSource createIdParameterSource(Object id, Class do private void addConvertedPropertyValue(MapSqlParameterSource parameterSource, RelationalPersistentProperty property, Object value, String paramName) { - JdbcTypeAware jdbcTypeAware = converter.writeTypeAware( // + JdbcValue jdbcValue = converter.writeJdbcValue( // value, // property.getColumnType(), // property.getSqlType() // ); - parameterSource.addValue(paramName, jdbcTypeAware.getValue(), JdbcUtil.sqlTypeFor(jdbcTypeAware.getJdbcType())); + parameterSource.addValue(paramName, jdbcValue.getValue(), JdbcUtil.sqlTypeFor(jdbcValue.getJdbcType())); } private void addConvertedPropertyValue(MapSqlParameterSource parameterSource, String name, Object value, Class type) { - JdbcTypeAware jdbcTypeAware = converter.writeTypeAware( // + JdbcValue jdbcValue = converter.writeJdbcValue( // value, // type, // JdbcUtil.sqlTypeFor(type) // @@ -410,8 +410,8 @@ private void addConvertedPropertyValue(MapSqlParameterSource parameterSource, St parameterSource.addValue( // name, // - jdbcTypeAware.getValue(), // - JdbcUtil.sqlTypeFor(jdbcTypeAware.getJdbcType()) // + jdbcValue.getValue(), // + JdbcUtil.sqlTypeFor(jdbcValue.getJdbcType()) // ); } @@ -419,19 +419,19 @@ private void addConvertedPropertyValuesAsList(MapSqlParameterSource parameterSou RelationalPersistentProperty property, Iterable values, String paramName) { List convertedIds = new ArrayList<>(); - JdbcTypeAware jdbcTypeAware = null; + JdbcValue jdbcValue = null; for (Object id : values) { Class columnType = property.getColumnType(); int sqlType = property.getSqlType(); - jdbcTypeAware = converter.writeTypeAware(id, columnType, sqlType); - convertedIds.add(jdbcTypeAware.getValue()); + jdbcValue = converter.writeJdbcValue(id, columnType, sqlType); + convertedIds.add(jdbcValue.getValue()); } - Assert.notNull(jdbcTypeAware, "JdbcTypeAware must be not null at this point. Please report this as a bug."); + Assert.notNull(jdbcValue, "JdbcValue must be not null at this point. Please report this as a bug."); - JDBCType jdbcType = jdbcTypeAware.getJdbcType(); + JDBCType jdbcType = jdbcValue.getJdbcType(); int typeNumber = jdbcType == null ? JdbcUtils.TYPE_UNKNOWN : jdbcType.getVendorTypeNumber(); parameterSource.addValue(paramName, convertedIds, typeNumber); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtil.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtil.java new file mode 100644 index 0000000000..69f211915e --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtil.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.convert; + +import lombok.experimental.UtilityClass; + +/** + * A collection of utility methods for dealing with arrays. + * + * @author Jens Schauder + */ +@UtilityClass +class ArrayUtil { + + /** + * Convertes an {@code Byte[]} into a {@code byte[]} + * @param byteArray the array to be converted. Must not be {@literal null}. + * + * @return a {@code byte[]} of same size with the unboxed values of the input array. Guaranteed to be not {@literal null}. + */ + static Object toPrimitiveByteArray(Byte[] byteArray) { + + byte[] bytes = new byte[byteArray.length]; + for (int i = 0; i < byteArray.length; i++) { + bytes[i] = byteArray[i]; + } + return bytes; + } +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java index 33647040c6..5cb74c0741 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java @@ -18,11 +18,10 @@ import java.sql.Array; import java.sql.JDBCType; import java.sql.SQLException; -import java.util.Arrays; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.data.convert.CustomConversions; import org.springframework.data.jdbc.core.mapping.AggregateReference; @@ -91,7 +90,7 @@ public BasicJdbcConverter( @Deprecated public BasicJdbcConverter( MappingContext, ? extends RelationalPersistentProperty> context) { - this(context, JdbcTypeFactory.DUMMY_JDBC_TYPE_FACTORY); + this(context, JdbcTypeFactory.unsupported()); } /** @@ -105,7 +104,7 @@ public BasicJdbcConverter( public BasicJdbcConverter( MappingContext, ? extends RelationalPersistentProperty> context, CustomConversions conversions) { - this(context, conversions, JdbcTypeFactory.DUMMY_JDBC_TYPE_FACTORY); + this(context, conversions, JdbcTypeFactory.unsupported()); } /* @@ -161,65 +160,65 @@ public Object writeValue(@Nullable Object value, TypeInformation type) { return super.writeValue(value, type); } - @Override - public boolean canWriteValue(@Nullable Object value, TypeInformation type) { + private boolean canWriteAsJdbcValue(@Nullable Object value) { if (value == null) { return true; } if (AggregateReference.class.isAssignableFrom(value.getClass())) { - return canWriteValue(((AggregateReference) value).getId(), type); + return canWriteAsJdbcValue(((AggregateReference) value).getId()); + } + + RelationalPersistentEntity persistentEntity = getMappingContext().getPersistentEntity(value.getClass()); + + if (persistentEntity != null) { + + Object id = persistentEntity.getIdentifierAccessor(value).getIdentifier(); + return canWriteAsJdbcValue(id); + } + + if (value instanceof JdbcValue) { + return true; } - return super.canWriteValue(value, type); + + Optional> customWriteTarget = getConversions().getCustomWriteTarget(value.getClass()); + return customWriteTarget.isPresent() && customWriteTarget.get().isAssignableFrom(JdbcValue.class); } - public JdbcTypeAware writeTypeAware(@Nullable Object value, Class columnType, int sqlType) { + public JdbcValue writeJdbcValue(@Nullable Object value, Class columnType, int sqlType) { - JdbcTypeAware jdbcTypeAware = tryToConvertToJdbcTypeAware(value); - if (jdbcTypeAware != null) { - return jdbcTypeAware; + JdbcValue jdbcValue = tryToConvertToJdbcValue(value); + if (jdbcValue != null) { + return jdbcValue; } Object convertedValue = writeValue(value, ClassTypeInformation.from(columnType)); if (convertedValue == null || !convertedValue.getClass().isArray()) { - return JdbcTypeAware.of(convertedValue, JdbcUtil.jdbcTypeFor(sqlType)); + return JdbcValue.of(convertedValue, JdbcUtil.jdbcTypeFor(sqlType)); } Class componentType = convertedValue.getClass().getComponentType(); if (componentType != byte.class && componentType != Byte.class) { - return JdbcTypeAware.of(typeFactory.createArray((Object[]) convertedValue), JDBCType.ARRAY); + return JdbcValue.of(typeFactory.createArray((Object[]) convertedValue), JDBCType.ARRAY); } if (componentType == Byte.class) { - Byte[] boxedBytes = (Byte[]) convertedValue; - byte[] bytes = new byte[boxedBytes.length]; - for (int i = 0; i < boxedBytes.length; i++) { - bytes[i] = boxedBytes[i]; - } - convertedValue = bytes; + convertedValue = ArrayUtil.toPrimitiveByteArray((Byte[]) convertedValue); } - return JdbcTypeAware.of(convertedValue, JDBCType.BINARY); + return JdbcValue.of(convertedValue, JDBCType.BINARY); } - private JdbcTypeAware tryToConvertToJdbcTypeAware(@Nullable Object value) { - - JdbcTypeAware jdbcTypeAware = null; - ClassTypeInformation jdbcTypeAwareClassTypeInformation = ClassTypeInformation - .from(JdbcTypeAware.class); - if (canWriteValue(value, jdbcTypeAwareClassTypeInformation)) { - - try { - - jdbcTypeAware = (JdbcTypeAware) writeValue(value, jdbcTypeAwareClassTypeInformation); + private JdbcValue tryToConvertToJdbcValue(@Nullable Object value) { - } catch (ConversionException __) { - // a conversion may still fail - } + JdbcValue jdbcValue = null; + if (canWriteAsJdbcValue(value)) { + jdbcValue = (JdbcValue) writeValue(value, ClassTypeInformation.from(JdbcValue.class)); } - return jdbcTypeAware; + + return jdbcValue; } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java index e8d132f9ee..01eed1474d 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java @@ -40,6 +40,8 @@ public DefaultJdbcTypeFactory(JdbcOperations operations) { @Override public Array createArray(Object[] value) { + Assert.notNull(value, "Value must not be null."); + Class componentType = innermostComponentType(value); JDBCType jdbcType = JdbcUtil.jdbcTypeFor(componentType); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java index 977352814a..8908bf351c 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java @@ -29,13 +29,13 @@ public interface JdbcConverter extends RelationalConverter { /** - * Convert a property value into a {@link JdbcTypeAware} that contains the converted value and information how to bind + * Convert a property value into a {@link JdbcValue} that contains the converted value and information how to bind * it to JDBC parameters. * * @param value a value as it is used in the object model. May be {@code null}. * @param type {@link TypeInformation} into which the value is to be converted. Must not be {@code null}. * @param sqlType the type constant from {@link java.sql.Types} to be used if non is specified by a converter. - * @return The converted value wrapped in a {@link JdbcTypeAware}. Guaranteed to be not {@literal null}. + * @return The converted value wrapped in a {@link JdbcValue}. Guaranteed to be not {@literal null}. */ - JdbcTypeAware writeTypeAware(@Nullable Object value, Class type, int sqlType); + JdbcValue writeJdbcValue(@Nullable Object value, Class type, int sqlType); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeFactory.java index b99b3d5ab9..cbb78dce35 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeFactory.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeFactory.java @@ -26,13 +26,16 @@ public interface JdbcTypeFactory { /** - * A dummy implementation used in places where a proper {@code JdbcTypeFactory} can not be provided but an instance - * needs to be provided anyway, mostly for providing backward compatibility. Calling it will result in an exception. - * The features normally supported by a {@link JdbcTypeFactory} will not work. + * An implementation used in places where a proper {@code JdbcTypeFactory} can not be provided but an instance needs + * to be provided anyway, mostly for providing backward compatibility. Calling it will result in an exception. The + * features normally supported by a {@link JdbcTypeFactory} will not work. */ - JdbcTypeFactory DUMMY_JDBC_TYPE_FACTORY = value -> { - throw new UnsupportedOperationException("This JdbcTypeFactory does not support Array creation"); - }; + static JdbcTypeFactory unsupported() { + + return value -> { + throw new UnsupportedOperationException("This JdbcTypeFactory does not support Array creation"); + }; + } /** * Converts the provided value in a {@link Array} instance. diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeAware.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcValue.java similarity index 92% rename from spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeAware.java rename to spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcValue.java index 755722faad..a2716448f2 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeAware.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcValue.java @@ -21,14 +21,14 @@ /** * Wraps a value with the JDBCType that should be used to pass it as a bind parameter to a - * {@link java.sql.PreparedStatement}. Register a converter from any type to {@link JdbcTypeAware} in order to control + * {@link java.sql.PreparedStatement}. Register a converter from any type to {@link JdbcValue} in order to control * the value and the {@link JDBCType} as which a value should get passed to the JDBC driver. * * @author Jens Schauder * @since 1.1 */ @Value(staticConstructor = "of") -public class JdbcTypeAware { +public class JdbcValue { Object value; JDBCType jdbcType; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java index fca6acfac6..220df13002 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java @@ -76,7 +76,7 @@ public RelationalMappingContext jdbcMappingContext(Optional nami @Bean public RelationalConverter relationalConverter(RelationalMappingContext mappingContext) { - return new BasicJdbcConverter(mappingContext, jdbcCustomConversions(), JdbcTypeFactory.DUMMY_JDBC_TYPE_FACTORY); + return new BasicJdbcConverter(mappingContext, jdbcCustomConversions(), JdbcTypeFactory.unsupported()); } /** diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java index 61793238b3..1fb1cb7085 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java @@ -43,7 +43,7 @@ public class BasicRelationalConverterAggregateReferenceUnitTests { ConversionService conversionService = new DefaultConversionService(); JdbcMappingContext context = new JdbcMappingContext(); - RelationalConverter converter = new BasicJdbcConverter(context, JdbcTypeFactory.DUMMY_JDBC_TYPE_FACTORY); + RelationalConverter converter = new BasicJdbcConverter(context, JdbcTypeFactory.unsupported()); RelationalPersistentEntity entity = context.getRequiredPersistentEntity(DummyEntity.class); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java index 17a61c9b34..b2bc4ac5b4 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java @@ -34,7 +34,7 @@ import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; -import org.springframework.data.jdbc.core.convert.JdbcTypeAware; +import org.springframework.data.jdbc.core.convert.JdbcValue; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.repository.CrudRepository; @@ -83,19 +83,18 @@ JdbcCustomConversions jdbcCustomConversions() { /** * In PostrgreSQL this fails if a simple converter like the following is used. * - *
+	 * 
 	 * {@code
-	 @WritingConverter
-	 enum PlainStringToBigDecimalConverter implements Converter {
+	 @WritingConverter enum PlainStringToBigDecimalConverter implements Converter {
 	
-	 INSTANCE;
+	 	INSTANCE;
 	
-	 @Override
-	 @Nullable
-	 public BigDecimal convert(@Nullable String source) {
+	 	@Override
+	 	@Nullable
+	 	public BigDecimal convert(@Nullable String source) {
 	
-	 return source == null ? null : new BigDecimal(source);
-	 }
+	 		return source == null ? null : new BigDecimal(source);
+	 	}
 	
 	 }
 	}
@@ -127,15 +126,15 @@ private static class EntityWithStringyBigDecimal {
 	}
 
 	@WritingConverter
-	enum StringToBigDecimalConverter implements Converter {
+	enum StringToBigDecimalConverter implements Converter {
 
 		INSTANCE;
 
 		@Override
-		public JdbcTypeAware convert(@Nullable String source) {
+		public JdbcValue convert(@Nullable String source) {
 
 			Object value = source == null ? null : new BigDecimal(source);
-			return JdbcTypeAware.of(value, JDBCType.DECIMAL);
+			return JdbcValue.of(value, JDBCType.DECIMAL);
 		}
 
 	}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java
index 50baa948e8..97797dd033 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java
@@ -83,7 +83,7 @@ public void setsUpBasicInstanceCorrectly() {
 
 		factoryBean.setDataAccessStrategy(dataAccessStrategy);
 		factoryBean.setMappingContext(mappingContext);
-		factoryBean.setConverter(new BasicJdbcConverter(mappingContext, JdbcTypeFactory.DUMMY_JDBC_TYPE_FACTORY));
+		factoryBean.setConverter(new BasicJdbcConverter(mappingContext, JdbcTypeFactory.unsupported()));
 		factoryBean.setApplicationEventPublisher(publisher);
 		factoryBean.setBeanFactory(beanFactory);
 		factoryBean.afterPropertiesSet();
@@ -110,7 +110,7 @@ public void afterPropertiesThowsExceptionWhenNoMappingContextSet() {
 	public void afterPropertiesSetDefaultsNullablePropertiesCorrectly() {
 
 		factoryBean.setMappingContext(mappingContext);
-		factoryBean.setConverter(new BasicJdbcConverter(mappingContext, JdbcTypeFactory.DUMMY_JDBC_TYPE_FACTORY));
+		factoryBean.setConverter(new BasicJdbcConverter(mappingContext, JdbcTypeFactory.unsupported()));
 		factoryBean.setApplicationEventPublisher(publisher);
 		factoryBean.setBeanFactory(beanFactory);
 		factoryBean.afterPropertiesSet();
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java
index 2cad2ef845..2d519a749b 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java
@@ -193,30 +193,6 @@ public Object writeValue(@Nullable Object value, TypeInformation type) {
 		return conversionService.convert(value, rawType);
 	}
 
-
-	@Override
-	public boolean canWriteValue(Object value, TypeInformation type) {
-
-		if (value == null) {
-			return true;
-		}
-
-		Class rawType = type.getType();
-		RelationalPersistentEntity persistentEntity = context.getPersistentEntity(value.getClass());
-
-		if (persistentEntity != null) {
-
-			Object id = persistentEntity.getIdentifierAccessor(value).getIdentifier();
-			return canWriteValue(id, type);
-		}
-
-		if (rawType.isInstance(value)) {
-			return true;
-		}
-
-		return conversionService.canConvert(value.getClass(), rawType);
-	}
-
 	/**
 	 * Checks whether we have a custom conversion registered for the given value into an arbitrary simple JDBC type.
 	 * Returns the converted value if so. If not, we perform special enum handling or simply return the value as is.
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java
index f2da792015..4a81c45458 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java
@@ -92,8 +92,4 @@  T createInstance(PersistentEntity entity,
 	 */
 	@Nullable
 	Object writeValue(@Nullable Object value, TypeInformation type);
-
-	default boolean canWriteValue(@Nullable Object value, TypeInformation type) {
-		return true;
-	}
 }
diff --git a/src/main/asciidoc/jdbc.adoc b/src/main/asciidoc/jdbc.adoc
index 1bbc82798e..d1592ccb6e 100644
--- a/src/main/asciidoc/jdbc.adoc
+++ b/src/main/asciidoc/jdbc.adoc
@@ -188,11 +188,11 @@ Converters should be annotated with `@ReadingConverter` or `@WritingConverter` i
 
 `TIMESTAMPTZ` in the example is a database specific data type that needs conversion into something more suitable for a domain model.
 
-==== JdbcTypeAware
+==== JdbcValue
 
 When setting bind parameters with a JDBC driver one may opt to provide a `java.sql.Types` constant value to denote the type of the parameter.
 If for a value this type need to be specified this can be done by using a writing converter as described in the previous section.
-This converter should convert to `JdbcTypeAware` which has a field for the value and one of for the `JDBCType`.
+This converter should convert to `JdbcValue` which has a field for the value and one of for the `JDBCType`.
 
 [[jdbc.entity-persistence.naming-strategy]]
 === `NamingStrategy`

From c6871ca70dd9cba01fb0b3b6290982e0def909b7 Mon Sep 17 00:00:00 2001
From: Jens Schauder 
Date: Mon, 11 Mar 2019 11:57:00 +0100
Subject: [PATCH 4/4] DATAJDBC-327 - Polishing.

Removed author tag for package-info.java
---
 .../java/org/springframework/data/jdbc/core/package-info.java  | 3 ---
 .../org/springframework/data/jdbc/mybatis/package-info.java    | 3 ---
 .../data/jdbc/repository/config/package-info.java              | 3 ---
 .../org/springframework/data/jdbc/repository/package-info.java | 3 ---
 .../data/jdbc/repository/query/package-info.java               | 3 ---
 .../data/jdbc/repository/support/package-info.java             | 3 ---
 .../org/springframework/data/jdbc/support/package-info.java    | 3 ---
 .../data/relational/core/conversion/package-info.java          | 3 ---
 .../data/relational/core/mapping/event/package-info.java       | 3 ---
 .../data/relational/core/mapping/package-info.java             | 3 ---
 .../data/relational/domain/support/package-info.java           | 3 ---
 11 files changed, 33 deletions(-)

diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/package-info.java
index 303b86c7bc..ece606c98b 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/package-info.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/package-info.java
@@ -1,6 +1,3 @@
-/**
- * @author Jens Schauder
- */
 @NonNullApi
 package org.springframework.data.jdbc.core;
 
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/package-info.java
index 7f8df95724..8c7a6f928c 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/package-info.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/package-info.java
@@ -1,6 +1,3 @@
-/**
- * @author Jens Schauder
- */
 @NonNullApi
 package org.springframework.data.jdbc.mybatis;
 
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/package-info.java
index b0c68ed025..fe1dfb9ec6 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/package-info.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/package-info.java
@@ -1,6 +1,3 @@
-/**
- * @author Jens Schauder
- */
 @NonNullApi
 package org.springframework.data.jdbc.repository.config;
 
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/package-info.java
index e0ba4798c8..58773ae344 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/package-info.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/package-info.java
@@ -1,6 +1,3 @@
-/**
- * @author Jens Schauder
- */
 @NonNullApi
 package org.springframework.data.jdbc.repository;
 
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/package-info.java
index 64fdddb211..a46db80e04 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/package-info.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/package-info.java
@@ -1,6 +1,3 @@
-/**
- * @author Jens Schauder
- */
 @NonNullApi
 package org.springframework.data.jdbc.repository.query;
 
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/package-info.java
index 5e05c6902c..d60ca96fde 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/package-info.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/package-info.java
@@ -1,6 +1,3 @@
-/**
- * @author Jens Schauder
- */
 @NonNullApi
 package org.springframework.data.jdbc.repository.support;
 
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/package-info.java
index 9790f9dedb..8ff6810baa 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/package-info.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/package-info.java
@@ -1,6 +1,3 @@
-/**
- * @author Jens Schauder
- */
 @NonNullApi
 package org.springframework.data.jdbc.support;
 
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/package-info.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/package-info.java
index f616aaa877..a54e73fa4f 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/package-info.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/package-info.java
@@ -1,6 +1,3 @@
-/**
- * @author Jens Schauder
- */
 @NonNullApi
 package org.springframework.data.relational.core.conversion;
 
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/package-info.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/package-info.java
index 4d855d2e5a..8ab2a2524f 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/package-info.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/package-info.java
@@ -1,6 +1,3 @@
-/**
- * @author Jens Schauder
- */
 @NonNullApi
 package org.springframework.data.relational.core.mapping.event;
 
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/package-info.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/package-info.java
index 9617ea1667..c026872132 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/package-info.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/package-info.java
@@ -1,6 +1,3 @@
-/**
- * @author Jens Schauder
- */
 @NonNullApi
 package org.springframework.data.relational.core.mapping;
 
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/package-info.java b/spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/package-info.java
index fcf7b85ecc..8134e1fb92 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/package-info.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/package-info.java
@@ -1,6 +1,3 @@
-/**
- * @author Jens Schauder
- */
 @NonNullApi
 package org.springframework.data.relational.domain.support;