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-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..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 @@ -16,33 +16,35 @@ 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.LinkedHashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; +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.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; -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; 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; 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, "", PersistentProperty::isIdProperty); - 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, "", property -> 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, Predicate skipProperty) { MapSqlParameterSource parameters = new MapSqlParameterSource(); @@ -301,6 +297,9 @@ private MapSqlParameterSource getPropertyMap(S instance, RelationalPersis persistentEntity.doWithProperties((PropertyHandler) property -> { + if (skipProperty.test(property)) { + 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(), skipProperty); 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) { + + JdbcValue jdbcValue = converter.writeJdbcValue( // + value, // + property.getColumnType(), // + property.getSqlType() // + ); + + parameterSource.addValue(paramName, jdbcValue.getValue(), JdbcUtil.sqlTypeFor(jdbcValue.getJdbcType())); + } + + private void addConvertedPropertyValue(MapSqlParameterSource parameterSource, String name, Object value, + Class type) { + + JdbcValue jdbcValue = converter.writeJdbcValue( // + value, // + type, // + JdbcUtil.sqlTypeFor(type) // + ); + + parameterSource.addValue( // + name, // + jdbcValue.getValue(), // + JdbcUtil.sqlTypeFor(jdbcValue.getJdbcType()) // + ); + } + + private void addConvertedPropertyValuesAsList(MapSqlParameterSource parameterSource, + RelationalPersistentProperty property, Iterable values, String paramName) { + + List convertedIds = new ArrayList<>(); + JdbcValue jdbcValue = null; + for (Object id : values) { + + Class columnType = property.getColumnType(); + int sqlType = property.getSqlType(); + + jdbcValue = converter.writeJdbcValue(id, columnType, sqlType); + convertedIds.add(jdbcValue.getValue()); + } + + Assert.notNull(jdbcValue, "JdbcValue must be not null at this point. Please report this as a bug."); + + JDBCType jdbcType = jdbcValue.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/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 e94f91bf92..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 @@ -15,29 +15,27 @@ */ package org.springframework.data.jdbc.core.convert; +import java.sql.Array; +import java.sql.JDBCType; +import java.sql.SQLException; +import java.util.Optional; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 +52,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.unsupported()); } /** @@ -69,11 +98,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.unsupported()); } /* @@ -102,7 +133,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 +160,65 @@ public Object writeValue(@Nullable Object value, TypeInformation type) { return super.writeValue(value, type); } + private boolean canWriteAsJdbcValue(@Nullable Object value) { + + if (value == null) { + return true; + } + + if (AggregateReference.class.isAssignableFrom(value.getClass())) { + 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; + } + + Optional> customWriteTarget = getConversions().getCustomWriteTarget(value.getClass()); + return customWriteTarget.isPresent() && customWriteTarget.get().isAssignableFrom(JdbcValue.class); + } + + public JdbcValue writeJdbcValue(@Nullable Object value, Class columnType, int sqlType) { + + JdbcValue jdbcValue = tryToConvertToJdbcValue(value); + if (jdbcValue != null) { + return jdbcValue; + } + + Object convertedValue = writeValue(value, ClassTypeInformation.from(columnType)); + + if (convertedValue == null || !convertedValue.getClass().isArray()) { + return JdbcValue.of(convertedValue, JdbcUtil.jdbcTypeFor(sqlType)); + } + + Class componentType = convertedValue.getClass().getComponentType(); + if (componentType != byte.class && componentType != Byte.class) { + return JdbcValue.of(typeFactory.createArray((Object[]) convertedValue), JDBCType.ARRAY); + } + + if (componentType == Byte.class) { + convertedValue = ArrayUtil.toPrimitiveByteArray((Byte[]) convertedValue); + } + + return JdbcValue.of(convertedValue, JDBCType.BINARY); + + } + + private JdbcValue tryToConvertToJdbcValue(@Nullable Object value) { + JdbcValue jdbcValue = null; + if (canWriteAsJdbcValue(value)) { + jdbcValue = (JdbcValue) writeValue(value, ClassTypeInformation.from(JdbcValue.class)); + } + + 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 new file mode 100644 index 0000000000..01eed1474d --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java @@ -0,0 +1,62 @@ +/* + * 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) { + + Assert.notNull(value, "Value must not be null."); + + 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..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 @@ -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 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 JdbcValue}. Guaranteed to be not {@literal null}. + */ + 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 new file mode 100644 index 0000000000..cbb78dce35 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeFactory.java @@ -0,0 +1,47 @@ +/* + * 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 { + + /** + * 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. + */ + static JdbcTypeFactory unsupported() { + + return 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/core/convert/JdbcValue.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcValue.java new file mode 100644 index 0000000000..a2716448f2 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcValue.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 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 JdbcValue { + + Object value; + JDBCType jdbcType; +} 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/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/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/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..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 @@ -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.unsupported()); } /** @@ -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/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/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/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/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..8ff6810baa --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/package-info.java @@ -0,0 +1,4 @@ +@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..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); + 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/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..b2bc4ac5b4 --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java @@ -0,0 +1,157 @@ +/* + * 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.JdbcValue; +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 JdbcValue convert(@Nullable String source) { + + Object value = source == null ? null : new BigDecimal(source); + return JdbcValue.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..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 @@ -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.unsupported())); 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.unsupported())); 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/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 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; diff --git a/src/main/asciidoc/jdbc.adoc b/src/main/asciidoc/jdbc.adoc index 07fcf33e35..d1592ccb6e 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. +==== 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 `JdbcValue` which has a field for the value and one of for the `JDBCType`. + [[jdbc.entity-persistence.naming-strategy]] === `NamingStrategy`