DATAJDBC-327 - Polishing.

Extend Javadoc. Reorder constructors in the order of their parameter number. Add missing assertions. Introduce utility to create predicates. Remove unused fields. Add since tags. Reformat.

Original pull request: #123.
This commit is contained in:
Mark Paluch
2019-03-12 12:23:52 +01:00
parent 35d9bb6e57
commit 78f26aa0a7
13 changed files with 157 additions and 83 deletions

View File

@@ -65,24 +65,44 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
private final @NonNull NamedParameterJdbcOperations operations;
private final @NonNull DataAccessStrategy 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.
*
* @param sqlGeneratorSource must not be {@literal null}.
* @param context must not be {@literal null}.
* @param converter must not be {@literal null}.
* @param operations must not be {@literal null}.
*/
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, RelationalMappingContext context,
JdbcConverter converter, NamedParameterJdbcOperations operations, @Nullable DataAccessStrategy accessStrategy) {
JdbcConverter converter, NamedParameterJdbcOperations operations) {
this(sqlGeneratorSource, context, converter, operations, null);
}
/**
* Creates a {@link DefaultDataAccessStrategy}
*
* @param sqlGeneratorSource must not be {@literal null}.
* @param context must not be {@literal null}.
* @param converter must not be {@literal null}.
* @param operations must not be {@literal null}.
* @param mappingAccessStrategy can be {@literal null}.
* @since 1.1
*/
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, RelationalMappingContext context,
JdbcConverter converter, NamedParameterJdbcOperations operations,
@Nullable DataAccessStrategy mappingAccessStrategy) {
Assert.notNull(sqlGeneratorSource, "SqlGeneratorSource must not be null");
Assert.notNull(context, "RelationalMappingContext must not be null");
Assert.notNull(converter, "JdbcConverter must not be null");
Assert.notNull(operations, "NamedParameterJdbcOperations must not be null");
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,
JdbcConverter converter, NamedParameterJdbcOperations operations) {
this(sqlGeneratorSource, context, converter, operations, null);
this.accessStrategy = mappingAccessStrategy == null ? this : mappingAccessStrategy;
}
/*
@@ -104,7 +124,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
KeyHolder holder = new GeneratedKeyHolder();
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
MapSqlParameterSource parameterSource = getParameterSource(instance, persistentEntity, "", PersistentProperty::isIdProperty);
MapSqlParameterSource parameterSource = getParameterSource(instance, persistentEntity, "",
PersistentProperty::isIdProperty);
identifier.forEach((name, value, type) -> addConvertedPropertyValue(parameterSource, name, value, type));
@@ -134,7 +155,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
RelationalPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType);
return operations.update(sql(domainType).getUpdate(),
getParameterSource(instance, persistentEntity, "", property -> false)) != 0;
getParameterSource(instance, persistentEntity, "", Predicates.includeAll())) != 0;
}
/*
@@ -289,7 +310,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
}
private <S, T> MapSqlParameterSource getParameterSource(S instance, RelationalPersistentEntity<S> persistentEntity,
String prefix, Predicate<RelationalPersistentProperty> skipProperty) {
String prefix, Predicate<RelationalPersistentProperty> skipProperty) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
@@ -445,4 +466,19 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
private SqlGenerator sql(Class<?> domainType) {
return sqlGeneratorSource.getSqlGenerator(domainType);
}
/**
* Utility to create {@link Predicate}s.
*/
static class Predicates {
/**
* Include all {@link Predicate} returning {@literal false} to never skip a property.
*
* @return the include all {@link Predicate}.
*/
static Predicate<RelationalPersistentProperty> includeAll() {
return it -> false;
}
}
}

View File

@@ -21,17 +21,19 @@ import lombok.experimental.UtilityClass;
* A collection of utility methods for dealing with arrays.
*
* @author Jens Schauder
* @since 1.1
*/
@UtilityClass
class ArrayUtil {
/**
* Convertes an {@code Byte[]} into a {@code byte[]}
* @param byteArray the array to be converted. Must not be {@literal null}.
* Converts an {@code Byte[]} into a {@code byte[]}.
*
* @return a {@code byte[]} of same size with the unboxed values of the input array. Guaranteed to be not {@literal null}.
* @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) {
static byte[] toPrimitiveByteArray(Byte[] byteArray) {
byte[] bytes = new byte[byteArray.length];
for (int i = 0; i < byteArray.length; i++) {

View File

@@ -22,6 +22,7 @@ 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;
@@ -35,6 +36,7 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProp
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* {@link RelationalConverter} that uses a {@link MappingContext} to apply basic conversion of relational values to
@@ -54,43 +56,54 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
private final JdbcTypeFactory typeFactory;
/**
* Creates a new {@link BasicRelationalConverter} given {@link MappingContext} and a
* {@link JdbcTypeFactory#unsupported() no-op type factory} throwing {@link UnsupportedOperationException} on type
* creation. Use {@link #BasicJdbcConverter(MappingContext, JdbcTypeFactory)} to convert arrays and large objects into
* JDBC-specific types.
*
* @param context must not be {@literal null}.
*/
public BasicJdbcConverter(
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context) {
this(context, JdbcTypeFactory.unsupported());
}
/**
* Creates a new {@link BasicRelationalConverter} given {@link MappingContext}.
*
* @param context must not be {@literal null}. org.springframework.data.jdbc.core.DefaultDataAccessStrategyUnitTests
* @param typeFactory
* @param context must not be {@literal null}.
* @param typeFactory must not be {@literal null}
* @since 1.1
*/
public BasicJdbcConverter(
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
JdbcTypeFactory typeFactory) {
super(context);
Assert.notNull(typeFactory, "JdbcTypeFactory must not be null");
this.typeFactory = typeFactory;
}
/**
* Creates a new {@link BasicRelationalConverter} given {@link MappingContext} and {@link CustomConversions}.
*
* Creates a new {@link BasicRelationalConverter} given {@link MappingContext}, {@link CustomConversions}, and
* {@link JdbcTypeFactory}.
*
* @param context must not be {@literal null}.
* @param conversions must not be {@literal null}.
* @param typeFactory
* @param typeFactory must not be {@literal null}
* @since 1.1
*/
public BasicJdbcConverter(
MappingContext<? extends RelationalPersistentEntity<?>, ? 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 RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context) {
this(context, JdbcTypeFactory.unsupported());
super(context, conversions);
Assert.notNull(typeFactory, "JdbcTypeFactory must not be null");
this.typeFactory = typeFactory;
}
/**
@@ -186,6 +199,11 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
return customWriteTarget.isPresent() && customWriteTarget.get().isAssignableFrom(JdbcValue.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.convert.JdbcConverter#writeValue(java.lang.Object, java.lang.Class, int)
*/
@Override
public JdbcValue writeJdbcValue(@Nullable Object value, Class<?> columnType, int sqlType) {
JdbcValue jdbcValue = tryToConvertToJdbcValue(value);
@@ -209,16 +227,15 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
}
return JdbcValue.of(convertedValue, JDBCType.BINARY);
}
@Nullable
private JdbcValue tryToConvertToJdbcValue(@Nullable Object value) {
JdbcValue jdbcValue = null;
if (canWriteAsJdbcValue(value)) {
jdbcValue = (JdbcValue) writeValue(value, ClassTypeInformation.from(JdbcValue.class));
return (JdbcValue) writeValue(value, ClassTypeInformation.from(JdbcValue.class));
}
return jdbcValue;
return null;
}
}

View File

@@ -15,16 +15,17 @@
*/
package org.springframework.data.jdbc.core.convert;
import java.sql.Array;
import java.sql.JDBCType;
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)}.
* A {@link JdbcTypeFactory} that performs the conversion by utilizing
* {@link JdbcOperations#execute(ConnectionCallback)}.
*
* @author Jens Schauder
* @since 1.1
@@ -33,7 +34,15 @@ public class DefaultJdbcTypeFactory implements JdbcTypeFactory {
private final JdbcOperations operations;
/**
* Creates a new {@link DefaultJdbcTypeFactory}.
*
* @param operations must not be {@literal null}.
*/
public DefaultJdbcTypeFactory(JdbcOperations operations) {
Assert.notNull(operations, "JdbcOperations must not be null");
this.operations = operations;
}

View File

@@ -0,0 +1,7 @@
/**
* JDBC-specific conversion classes.
*/
@NonNullApi
package org.springframework.data.jdbc.core.convert;
import org.springframework.lang.NonNullApi;

View File

@@ -1,3 +1,6 @@
/**
* Core JDBC implementation.
*/
@NonNullApi
package org.springframework.data.jdbc.core;

View File

@@ -87,7 +87,7 @@ public class JdbcUtil {
/**
* 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}.
*/
@@ -98,7 +98,7 @@ public class JdbcUtil {
/**
* 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}.
*/
@@ -121,7 +121,6 @@ public class JdbcUtil {
*/
@Nullable
public static JDBCType jdbcTypeFor(Class<?> type) {
return jdbcTypeFor(sqlTypeFor(type));
}
}

View File

@@ -27,6 +27,7 @@ import java.util.HashMap;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Id;
import org.springframework.data.convert.ReadingConverter;
@@ -37,6 +38,7 @@ 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.mapping.RelationalMappingContext;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.KeyHolder;
@@ -52,10 +54,11 @@ public class DefaultDataAccessStrategyUnitTests {
public static final long ID_FROM_ADDITIONAL_VALUES = 23L;
public static final long ORIGINAL_ID = 4711L;
NamedParameterJdbcOperations jdbcOperations = mock(NamedParameterJdbcOperations.class);
NamedParameterJdbcOperations namedJdbcOperations = mock(NamedParameterJdbcOperations.class);
JdbcOperations jdbcOperations = mock(JdbcOperations.class);
RelationalMappingContext context = new JdbcMappingContext();
JdbcConverter converter = new BasicJdbcConverter(context, new JdbcCustomConversions(),
new DefaultJdbcTypeFactory(jdbcOperations.getJdbcOperations()));
new DefaultJdbcTypeFactory(jdbcOperations));
HashMap<String, Object> additionalParameters = new HashMap<>();
ArgumentCaptor<SqlParameterSource> paramSourceCaptor = ArgumentCaptor.forClass(SqlParameterSource.class);
@@ -63,7 +66,7 @@ public class DefaultDataAccessStrategyUnitTests {
new SqlGeneratorSource(context), //
context, //
converter, //
jdbcOperations);
namedJdbcOperations);
@Test // DATAJDBC-146
public void additionalParameterForIdDoesNotLeadToDuplicateParameters() {
@@ -72,7 +75,7 @@ public class DefaultDataAccessStrategyUnitTests {
accessStrategy.insert(new DummyEntity(ORIGINAL_ID), DummyEntity.class, additionalParameters);
verify(jdbcOperations).update(eq("INSERT INTO dummy_entity (id) VALUES (:id)"), paramSourceCaptor.capture(),
verify(namedJdbcOperations).update(eq("INSERT INTO dummy_entity (id) VALUES (:id)"), paramSourceCaptor.capture(),
any(KeyHolder.class));
}
@@ -85,7 +88,7 @@ public class DefaultDataAccessStrategyUnitTests {
accessStrategy.insert(new DummyEntity(ORIGINAL_ID), DummyEntity.class, additionalParameters);
verify(jdbcOperations).update(sqlCaptor.capture(), paramSourceCaptor.capture(), any(KeyHolder.class));
verify(namedJdbcOperations).update(sqlCaptor.capture(), paramSourceCaptor.capture(), any(KeyHolder.class));
assertThat(sqlCaptor.getValue()) //
.containsSequence("INSERT INTO dummy_entity (", "id", ") VALUES (", ":id", ")") //
@@ -98,13 +101,13 @@ public class DefaultDataAccessStrategyUnitTests {
JdbcConverter converter = new BasicJdbcConverter(context,
new JdbcCustomConversions(Arrays.asList(BooleanToStringConverter.INSTANCE, StringToBooleanConverter.INSTANCE)),
new DefaultJdbcTypeFactory(jdbcOperations.getJdbcOperations()));
new DefaultJdbcTypeFactory(jdbcOperations));
DefaultDataAccessStrategy accessStrategy = new DefaultDataAccessStrategy( //
new SqlGeneratorSource(context), //
context, //
converter, //
jdbcOperations);
namedJdbcOperations);
ArgumentCaptor<String> sqlCaptor = ArgumentCaptor.forClass(String.class);
@@ -112,7 +115,7 @@ public class DefaultDataAccessStrategyUnitTests {
accessStrategy.insert(entity, EntityWithBoolean.class, new HashMap<>());
verify(jdbcOperations).update(sqlCaptor.capture(), paramSourceCaptor.capture(), any(KeyHolder.class));
verify(namedJdbcOperations).update(sqlCaptor.capture(), paramSourceCaptor.capture(), any(KeyHolder.class));
assertThat(paramSourceCaptor.getValue().getValue("id")).isEqualTo(ORIGINAL_ID);
assertThat(paramSourceCaptor.getValue().getValue("flag")).isEqualTo("T");

View File

@@ -31,6 +31,7 @@ import org.junit.Assume;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
@@ -422,7 +423,7 @@ public class JdbcAggregateTemplateIntegrationTests {
@Test // DATAJDBC-327
public void saveAndLoadAnEntityWithByteArray() {
ByteArrayOwner owner = new ByteArrayOwner();
owner.binaryData = new byte[]{1, 23, 42};
owner.binaryData = new byte[] { 1, 23, 42 };
ByteArrayOwner saved = template.save(owner);
@@ -430,10 +431,9 @@ public class JdbcAggregateTemplateIntegrationTests {
assertThat(reloaded).isNotNull();
assertThat(reloaded.id).isEqualTo(saved.id);
assertThat(reloaded.binaryData).isEqualTo(new byte[]{1, 23, 42});
assertThat(reloaded.binaryData).isEqualTo(new byte[] { 1, 23, 42 });
}
private static void assumeNot(String dbProfileName) {
Assume.assumeTrue("true"

View File

@@ -38,12 +38,8 @@ import org.springframework.data.util.ClassTypeInformation;
*/
public class BasicRelationalConverterAggregateReferenceUnitTests {
SoftAssertions softly = new SoftAssertions();
ConversionService conversionService = new DefaultConversionService();
JdbcMappingContext context = new JdbcMappingContext();
RelationalConverter converter = new BasicJdbcConverter(context, JdbcTypeFactory.unsupported());
RelationalConverter converter = new BasicJdbcConverter(context);
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(DummyEntity.class);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2018 the original author or authors.
* 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.
@@ -25,6 +25,7 @@ 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;
@@ -84,23 +85,21 @@ public class JdbcRepositoryCustomConversionIntegrationTests {
* 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);
}
}
}
*
* &#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() {

View File

@@ -33,6 +33,7 @@ import org.assertj.core.groups.Tuple;
import org.junit.Before;
import org.junit.Test;
import org.mockito.stubbing.Answer;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
@@ -53,6 +54,7 @@ import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent;
import org.springframework.data.relational.core.mapping.event.Identifier;
import org.springframework.data.relational.core.mapping.event.RelationalEvent;
import org.springframework.data.repository.CrudRepository;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.KeyHolder;
@@ -224,6 +226,7 @@ public class SimpleJdbcRepositoryEventsUnitTests {
NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class);
when(operations.update(anyString(), any(SqlParameterSource.class), any(KeyHolder.class)))
.thenAnswer(setIdInKeyHolder);
when(operations.getJdbcOperations()).thenReturn(mock(JdbcOperations.class));
return operations;
}

View File

@@ -190,9 +190,9 @@ Converters should be annotated with `@ReadingConverter` or `@WritingConverter` i
==== 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`.
Value conversion uses `JdbcValue` to enrich values propagated to JDBC operations with a `java.sql.Types` type.
Register a custom write converter if you need to specify a JDBC-specific type instead of using type derivation.
This converter should convert the value to `JdbcValue` which has a field for the value and for the actual `JDBCType`.
[[jdbc.entity-persistence.naming-strategy]]
=== `NamingStrategy`