Browse Source

DATAJDBC-327 - Add support for conversion to JdbcValue and store 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.

Original pull request: #123.
pull/124/head
Jens Schauder 7 years ago committed by Mark Paluch
parent
commit
ceb15fe826
  1. 171
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java
  2. 42
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtil.java
  3. 115
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java
  4. 62
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java
  5. 17
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java
  6. 47
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeFactory.java
  7. 35
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcValue.java
  8. 8
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java
  9. 10
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java
  10. 8
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java
  11. 6
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java
  12. 53
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/JdbcUtil.java
  13. 7
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/package-info.java
  14. 16
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategyUnitTests.java
  15. 21
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java
  16. 2
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java
  17. 3
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java
  18. 157
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java
  19. 13
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java
  20. 3
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java
  21. 7
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java
  22. 23
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java
  23. 4
      spring-data-jdbc/src/test/resources/logback.xml
  24. 3
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql
  25. 2
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql
  26. 3
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql
  27. 2
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql
  28. 8
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql
  29. 1
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql
  30. 1
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mariadb.sql
  31. 1
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mssql.sql
  32. 1
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mysql.sql
  33. 1
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql
  34. 6
      src/main/asciidoc/jdbc.adoc

171
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java

@ -16,33 +16,35 @@ @@ -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 @@ import org.springframework.util.Assert; @@ -55,27 +57,32 @@ import org.springframework.util.Assert;
* @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 class DefaultDataAccessStrategy implements DataAccessStrategy { @@ -97,28 +104,19 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
KeyHolder holder = new GeneratedKeyHolder();
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
Map<String, Object> 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 class DefaultDataAccessStrategy implements DataAccessStrategy { @@ -135,7 +133,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
RelationalPersistentEntity<S> 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 class DefaultDataAccessStrategy implements DataAccessStrategy { @@ -240,17 +239,14 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
@Override
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> 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<T>) getEntityRowMapper(domainType));
return operations.query(findAllInListSql, parameterSource, (RowMapper<T>) getEntityRowMapper(domainType));
}
/*
@ -292,8 +288,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { @@ -292,8 +288,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
return result;
}
private <S, T> MapSqlParameterSource getPropertyMap(S instance, RelationalPersistentEntity<S> persistentEntity,
String prefix) {
private <S, T> MapSqlParameterSource getParameterSource(S instance, RelationalPersistentEntity<S> persistentEntity,
String prefix, Predicate<RelationalPersistentProperty> skipProperty) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
@ -301,6 +297,9 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { @@ -301,6 +297,9 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
persistentEntity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {
if (skipProperty.test(property)) {
return;
}
if (property.isEntity() && !property.isEmbedded()) {
return;
}
@ -309,42 +308,21 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { @@ -309,42 +308,21 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
Object value = propertyAccessor.getProperty(property);
RelationalPersistentEntity<?> embeddedEntity = context.getPersistentEntity(property.getType());
MapSqlParameterSource additionalParameters = getPropertyMap((T) value,
(RelationalPersistentEntity<T>) embeddedEntity, prefix + property.getEmbeddedPrefix());
MapSqlParameterSource additionalParameters = getParameterSource((T) value,
(RelationalPersistentEntity<T>) 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 <S, ID> ID getIdValueOrNull(S instance, RelationalPersistentEntity<S> persistentEntity) {
@ -398,8 +376,65 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { @@ -398,8 +376,65 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> 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<Object> 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")

42
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtil.java

@ -0,0 +1,42 @@ @@ -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;
}
}

115
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java

@ -15,29 +15,27 @@ @@ -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,19 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc @@ -54,14 +52,19 @@ 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 RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context) {
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
JdbcTypeFactory typeFactory) {
super(context);
this.typeFactory = typeFactory;
}
/**
@ -69,11 +72,39 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc @@ -69,11 +72,39 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
*
* @param context must not be {@literal null}.
* @param conversions must not be {@literal null}.
* @param typeFactory
*/
public BasicJdbcConverter(
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
CustomConversions conversions) {
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 RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context) {
this(context, JdbcTypeFactory.unsupported());
}
/**
* 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}.
* @deprecated use one of the constructors with {@link JdbcTypeFactory} parameter.
*/
@Deprecated
public BasicJdbcConverter(
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
CustomConversions conversions) {
this(context, conversions, JdbcTypeFactory.unsupported());
}
/*
@ -102,7 +133,7 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc @@ -102,7 +133,7 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
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 class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc @@ -129,5 +160,65 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
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<Class<?>> 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;
}
}

62
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java

@ -0,0 +1,62 @@ @@ -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<Array>) c -> c.createArrayOf(typeName, value));
}
private static Class<?> innermostComponentType(Object convertedValue) {
Class<?> componentType = convertedValue.getClass();
while (componentType.isArray()) {
componentType = componentType.getComponentType();
}
return componentType;
}
}

17
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java

@ -16,13 +16,26 @@ @@ -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);
}

47
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcTypeFactory.java

@ -0,0 +1,47 @@ @@ -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);
}

35
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcValue.java

@ -0,0 +1,35 @@ @@ -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;
}

8
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java

@ -28,9 +28,9 @@ import org.springframework.data.jdbc.core.DataAccessStrategy; @@ -28,9 +28,9 @@ import org.springframework.data.jdbc.core.DataAccessStrategy;
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 { @@ -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 class MyBatisDataAccessStrategy implements DataAccessStrategy { @@ -70,7 +70,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,
NamespaceStrategy namespaceStrategy) {
// the DefaultDataAccessStrategy needs a reference to the returned DataAccessStrategy. This creates a dependency
@ -104,7 +104,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy { @@ -104,7 +104,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
* 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}.

10
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java

@ -26,12 +26,14 @@ import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; @@ -26,12 +26,14 @@ import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
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 abstract class AbstractJdbcConfiguration { @@ -71,13 +73,13 @@ public abstract class AbstractJdbcConfiguration {
* @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 abstract class AbstractJdbcConfiguration { @@ -100,7 +102,7 @@ public abstract class AbstractJdbcConfiguration {
*/
@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);

8
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java

@ -27,7 +27,9 @@ import org.springframework.data.jdbc.core.JdbcAggregateOperations; @@ -27,7 +27,9 @@ import org.springframework.data.jdbc.core.JdbcAggregateOperations;
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 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -42,7 +44,6 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
* @author Mark Paluch
* @author Michael Simons
* @author Christoph Strobl
*
* @deprecated Use {@link AbstractJdbcConfiguration} instead.
*/
@Configuration
@ -74,7 +75,8 @@ public class JdbcConfiguration { @@ -74,7 +75,8 @@ public class JdbcConfiguration {
*/
@Bean
public RelationalConverter relationalConverter(RelationalMappingContext mappingContext) {
return new BasicJdbcConverter(mappingContext, jdbcCustomConversions());
return new BasicJdbcConverter(mappingContext, jdbcCustomConversions(), JdbcTypeFactory.unsupported());
}
/**
@ -101,7 +103,7 @@ public class JdbcConfiguration { @@ -101,7 +103,7 @@ public class JdbcConfiguration {
*/
@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);

6
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java

@ -24,9 +24,9 @@ import org.springframework.context.ApplicationEventPublisherAware; @@ -24,9 +24,9 @@ import org.springframework.context.ApplicationEventPublisherAware;
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<T extends Repository<S, ID>, S, ID extend @@ -50,7 +50,7 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, 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 class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend @@ -128,7 +128,7 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
}
@Autowired
public void setConverter(RelationalConverter converter) {
public void setConverter(JdbcConverter converter) {
this.converter = converter;
}

53
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/JdbcUtil.java

@ -20,6 +20,7 @@ import lombok.experimental.UtilityClass; @@ -20,6 +20,7 @@ import lombok.experimental.UtilityClass;
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.HashMap; @@ -27,6 +28,8 @@ import java.util.HashMap;
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 { @@ -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));
}
}

7
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/package-info.java

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
/**
* @author Jens Schauder
*/
@NonNullApi
package org.springframework.data.jdbc.support;
import org.springframework.lang.NonNullApi;

16
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategyUnitTests.java

@ -16,8 +16,7 @@ @@ -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.core.convert.converter.Converter; @@ -32,10 +31,11 @@ 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.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 { @@ -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<String, Object> additionalParameters = new HashMap<>();
ArgumentCaptor<SqlParameterSource> paramSourceCaptor = ArgumentCaptor.forClass(SqlParameterSource.class);
@ -95,8 +96,9 @@ public class DefaultDataAccessStrategyUnitTests { @@ -95,8 +96,9 @@ public class DefaultDataAccessStrategyUnitTests {
@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), //

21
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java

@ -419,6 +419,21 @@ public class JdbcAggregateTemplateIntegrationTests { @@ -419,6 +419,21 @@ public class JdbcAggregateTemplateIntegrationTests {
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 @@ public class JdbcAggregateTemplateIntegrationTests { @@ -433,6 +448,12 @@ public class JdbcAggregateTemplateIntegrationTests {
String[][] multidimensional;
}
private static class ByteArrayOwner {
@Id Long id;
byte[] binaryData;
}
@Table("ARRAY_OWNER")
private static class ListOwner {
@Id Long id;

2
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java

@ -43,7 +43,7 @@ public class BasicRelationalConverterAggregateReferenceUnitTests { @@ -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);

3
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java

@ -32,6 +32,7 @@ import org.springframework.context.annotation.Bean; @@ -32,6 +32,7 @@ import org.springframework.context.annotation.Bean;
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 @@ public class MyBatisHsqlIntegrationTests { @@ -90,7 +91,7 @@ public class MyBatisHsqlIntegrationTests {
@Bean
@Primary
DataAccessStrategy dataAccessStrategy(RelationalMappingContext context, RelationalConverter converter,
DataAccessStrategy dataAccessStrategy(RelationalMappingContext context, JdbcConverter converter,
SqlSession sqlSession, EmbeddedDatabase db) {
return MyBatisDataAccessStrategy.createCombinedAccessStrategy(context, converter,

157
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java

@ -0,0 +1,157 @@ @@ -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.
*
* <pre class="code">
* {@code
&#64;WritingConverter enum PlainStringToBigDecimalConverter implements Converter<String, BigDecimal> {
INSTANCE;
&#64;Override
&#64;Nullable
public BigDecimal convert(@Nullable String source) {
return source == null ? null : new BigDecimal(source);
}
}
}
* </pre>
*/
@Test // DATAJDBC-327
public void saveAndLoadAnEntity() {
EntityWithStringyBigDecimal entity = new EntityWithStringyBigDecimal();
entity.stringyNumber = "123456.78910";
repository.save(entity);
Optional<EntityWithStringyBigDecimal> 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<EntityWithStringyBigDecimal, Long> {}
private static class EntityWithStringyBigDecimal {
@Id Long id;
String stringyNumber;
}
@WritingConverter
enum StringToBigDecimalConverter implements Converter<String, JdbcValue> {
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<BigDecimal, String> {
INSTANCE;
@Override
public String convert(@Nullable BigDecimal source) {
if (source == null) {
return null;
}
return source.toString();
}
}
}

13
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java

@ -17,9 +17,7 @@ package org.springframework.data.jdbc.repository; @@ -17,9 +17,7 @@ package org.springframework.data.jdbc.repository;
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.context.ApplicationEventPublisher; @@ -39,12 +37,13 @@ import org.springframework.context.ApplicationEventPublisher;
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 { @@ -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));

3
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java

@ -34,6 +34,7 @@ import org.springframework.data.annotation.Id; @@ -34,6 +34,7 @@ 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.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 @@ public class EnableJdbcRepositoriesIntegrationTests { @@ -149,7 +150,7 @@ public class EnableJdbcRepositoriesIntegrationTests {
@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);
}
}

7
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java

@ -33,9 +33,10 @@ import org.springframework.context.ApplicationEventPublisher; @@ -33,9 +33,10 @@ import org.springframework.context.ApplicationEventPublisher;
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 class JdbcRepositoryFactoryBeanUnitTests { @@ -82,7 +83,7 @@ public class JdbcRepositoryFactoryBeanUnitTests {
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 class JdbcRepositoryFactoryBeanUnitTests { @@ -109,7 +110,7 @@ public class JdbcRepositoryFactoryBeanUnitTests {
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();

23
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java

@ -26,12 +26,13 @@ import org.springframework.context.ApplicationEventPublisher; @@ -26,12 +26,13 @@ import org.springframework.context.ApplicationEventPublisher;
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 { @@ -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 @@ public class TestConfiguration { @@ -76,14 +78,14 @@ public class TestConfiguration {
}
@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> namingStrategy,
CustomConversions conversions) {
JdbcMappingContext jdbcMappingContext(Optional<NamingStrategy> namingStrategy, CustomConversions conversions) {
JdbcMappingContext mappingContext = new JdbcMappingContext(namingStrategy.orElse(NamingStrategy.INSTANCE));
mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
@ -96,7 +98,8 @@ public class TestConfiguration { @@ -96,7 +98,8 @@ public class TestConfiguration {
}
@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()));
}
}

4
spring-data-jdbc/src/test/resources/logback.xml

@ -7,8 +7,8 @@ @@ -7,8 +7,8 @@
</encoder>
</appender>
<!--<logger name="org.springframework.data" level="info" />-->
<!--<logger name="org.springframework.jdbc.core" level="trace" />-->
<logger name="org.springframework.data" level="info" />
<logger name="org.springframework.jdbc.core" level="trace" />
<!--<logger name="org.springframework.data.jdbc.mybatis.DummyEntityMapper" level="trace" />-->
<root level="warn">

3
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql

@ -15,3 +15,6 @@ ALTER TABLE ELEMENT_NO_ID @@ -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);

2
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql

@ -9,3 +9,5 @@ CREATE TABLE Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR @@ -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)

3
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql

@ -13,3 +13,6 @@ DROP TABLE IF EXISTS element_no_id; @@ -13,3 +13,6 @@ 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);
DROP TABLE IF EXISTS BYTE_ARRAY_OWNER;
CREATE TABLE BYTE_ARRAY_OWNER (ID BIGINT IDENTITY PRIMARY KEY, BINARY_DATA VARBINARY(20) NOT NULL)

2
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 @@ -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)

8
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql

@ -1,5 +1,11 @@ @@ -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)); @@ -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)

1
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql

@ -0,0 +1 @@ @@ -0,0 +1 @@
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id IDENTITY PRIMARY KEY, Stringy_number DECIMAL(20,10))

1
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mariadb.sql

@ -0,0 +1 @@ @@ -0,0 +1 @@
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id BIGINT AUTO_INCREMENT PRIMARY KEY, Stringy_number DECIMAL(20,10))

1
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mssql.sql

@ -0,0 +1 @@ @@ -0,0 +1 @@
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id BIGINT IDENTITY PRIMARY KEY, Stringy_number DECIMAL(20,10))

1
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mysql.sql

@ -0,0 +1 @@ @@ -0,0 +1 @@
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id BIGINT AUTO_INCREMENT PRIMARY KEY, Stringy_number DECIMAL(20,10))

1
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql

@ -0,0 +1 @@ @@ -0,0 +1 @@
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id SERIAL PRIMARY KEY, Stringy_number DECIMAL(20,10))

6
src/main/asciidoc/jdbc.adoc

@ -188,6 +188,12 @@ Converters should be annotated with `@ReadingConverter` or `@WritingConverter` i @@ -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`

Loading…
Cancel
Save