DATAJDBC-341 - Polishing.
Remove SpecialColumnValue in favor of JdbcPropertyValueProvider.hasProperty(…). Rename ResultSetWrapper to ResultSetAccessor. Replace ResultSetAccessor usage in JdbcConverter with ResultSet to avoid visibility issues. Original pull request: #201.
This commit is contained in:
@@ -24,18 +24,16 @@ import java.util.Optional;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import org.springframework.core.convert.ConverterNotFoundException;
|
import org.springframework.core.convert.ConverterNotFoundException;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.data.convert.CustomConversions;
|
import org.springframework.data.convert.CustomConversions;
|
||||||
import org.springframework.data.jdbc.core.convert.ResultSetWrapper.SpecialColumnValue;
|
|
||||||
import org.springframework.data.jdbc.core.mapping.AggregateReference;
|
import org.springframework.data.jdbc.core.mapping.AggregateReference;
|
||||||
import org.springframework.data.jdbc.support.JdbcUtil;
|
import org.springframework.data.jdbc.support.JdbcUtil;
|
||||||
import org.springframework.data.mapping.MappingException;
|
|
||||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||||
import org.springframework.data.mapping.PreferredConstructor;
|
import org.springframework.data.mapping.PreferredConstructor;
|
||||||
import org.springframework.data.mapping.context.MappingContext;
|
import org.springframework.data.mapping.context.MappingContext;
|
||||||
import org.springframework.data.mapping.model.PropertyValueProvider;
|
|
||||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||||
import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
|
import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
|
||||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||||
@@ -77,7 +75,8 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
|
|||||||
/**
|
/**
|
||||||
* Creates a new {@link BasicRelationalConverter} given {@link MappingContext} and a
|
* Creates a new {@link BasicRelationalConverter} given {@link MappingContext} and a
|
||||||
* {@link JdbcTypeFactory#unsupported() no-op type factory} throwing {@link UnsupportedOperationException} on type
|
* {@link JdbcTypeFactory#unsupported() no-op type factory} throwing {@link UnsupportedOperationException} on type
|
||||||
* creation. Use {@link #BasicJdbcConverter(MappingContext, RelationResolver, CustomConversions, JdbcTypeFactory, IdentifierProcessing)}
|
* creation. Use
|
||||||
|
* {@link #BasicJdbcConverter(MappingContext, RelationResolver, CustomConversions, JdbcTypeFactory, IdentifierProcessing)}
|
||||||
* (MappingContext, RelationResolver, JdbcTypeFactory)} to convert arrays and large objects into JDBC-specific types.
|
* (MappingContext, RelationResolver, JdbcTypeFactory)} to convert arrays and large objects into JDBC-specific types.
|
||||||
*
|
*
|
||||||
* @param context must not be {@literal null}.
|
* @param context must not be {@literal null}.
|
||||||
@@ -106,9 +105,9 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
|
|||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public BasicJdbcConverter(
|
public BasicJdbcConverter(
|
||||||
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
|
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
|
||||||
RelationResolver relationResolver, CustomConversions conversions, JdbcTypeFactory typeFactory,
|
RelationResolver relationResolver, CustomConversions conversions, JdbcTypeFactory typeFactory,
|
||||||
IdentifierProcessing identifierProcessing) {
|
IdentifierProcessing identifierProcessing) {
|
||||||
|
|
||||||
super(context, conversions);
|
super(context, conversions);
|
||||||
|
|
||||||
@@ -199,7 +198,7 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
|
|||||||
@Nullable
|
@Nullable
|
||||||
public Object readValue(@Nullable Object value, TypeInformation<?> type) {
|
public Object readValue(@Nullable Object value, TypeInformation<?> type) {
|
||||||
|
|
||||||
if (value == null || value == SpecialColumnValue.NO_SUCH_COLUMN) {
|
if (value == null) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,36 +319,31 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T mapRow(RelationalPersistentEntity<T> entity, ResultSetWrapper resultSet, Object key) {
|
public <T> T mapRow(RelationalPersistentEntity<T> entity, ResultSet resultSet, Object key) {
|
||||||
return new ReadingContext<T>(
|
return new ReadingContext<T>(new PersistentPropertyPathExtension(getMappingContext(), entity),
|
||||||
new PersistentPropertyPathExtension(
|
new ResultSetAccessor(resultSet), Identifier.empty(), key).mapRow();
|
||||||
getMappingContext(), entity),
|
|
||||||
resultSet, Identifier.empty(), key).mapRow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T mapRow(PersistentPropertyPathExtension path, ResultSetWrapper resultSet, Identifier identifier,
|
public <T> T mapRow(PersistentPropertyPathExtension path, ResultSet resultSet, Identifier identifier, Object key) {
|
||||||
Object key) {
|
return new ReadingContext<T>(path, new ResultSetAccessor(resultSet), identifier, key).mapRow();
|
||||||
return new ReadingContext<T>(path, resultSet, identifier, key).mapRow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ReadingContext<T> {
|
private class ReadingContext<T> {
|
||||||
|
|
||||||
private final RelationalPersistentEntity<T> entity;
|
private final RelationalPersistentEntity<T> entity;
|
||||||
|
|
||||||
private final ResultSetWrapper resultSet;
|
|
||||||
private final PersistentPropertyPathExtension rootPath;
|
private final PersistentPropertyPathExtension rootPath;
|
||||||
private final PersistentPropertyPathExtension path;
|
private final PersistentPropertyPathExtension path;
|
||||||
private final Identifier identifier;
|
private final Identifier identifier;
|
||||||
private final Object key;
|
private final Object key;
|
||||||
|
|
||||||
private final PropertyValueProvider<RelationalPersistentProperty> propertyValueProvider;
|
private final JdbcPropertyValueProvider propertyValueProvider;
|
||||||
private final PropertyValueProvider<RelationalPersistentProperty> backReferencePropertyValueProvider;
|
private final JdbcBackReferencePropertyValueProvider backReferencePropertyValueProvider;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private ReadingContext(PersistentPropertyPathExtension rootPath, ResultSetWrapper resultSet, Identifier identifier,
|
private ReadingContext(PersistentPropertyPathExtension rootPath, ResultSetAccessor accessor, Identifier identifier,
|
||||||
Object key) {
|
Object key) {
|
||||||
|
|
||||||
RelationalPersistentEntity<T> entity = (RelationalPersistentEntity<T>) rootPath.getLeafEntity();
|
RelationalPersistentEntity<T> entity = (RelationalPersistentEntity<T>) rootPath.getLeafEntity();
|
||||||
@@ -357,38 +351,33 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
|
|||||||
Assert.notNull(entity, "The rootPath must point to an entity.");
|
Assert.notNull(entity, "The rootPath must point to an entity.");
|
||||||
|
|
||||||
this.entity = entity;
|
this.entity = entity;
|
||||||
this.resultSet = resultSet;
|
|
||||||
this.rootPath = rootPath;
|
this.rootPath = rootPath;
|
||||||
this.path = new PersistentPropertyPathExtension(
|
this.path = new PersistentPropertyPathExtension(getMappingContext(), this.entity);
|
||||||
getMappingContext(),
|
|
||||||
this.entity);
|
|
||||||
this.identifier = identifier;
|
this.identifier = identifier;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
propertyValueProvider = new JdbcPropertyValueProvider(identifierProcessing, path, resultSet);
|
this.propertyValueProvider = new JdbcPropertyValueProvider(identifierProcessing, path, accessor);
|
||||||
backReferencePropertyValueProvider = new JdbcBackReferencePropertyValueProvider(identifierProcessing, path,
|
this.backReferencePropertyValueProvider = new JdbcBackReferencePropertyValueProvider(identifierProcessing, path,
|
||||||
resultSet);
|
accessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReadingContext(RelationalPersistentEntity<T> entity, ResultSetWrapper resultSet,
|
private ReadingContext(RelationalPersistentEntity<T> entity, PersistentPropertyPathExtension rootPath,
|
||||||
PersistentPropertyPathExtension rootPath, PersistentPropertyPathExtension path, Identifier identifier,
|
PersistentPropertyPathExtension path, Identifier identifier, Object key,
|
||||||
Object key) {
|
JdbcPropertyValueProvider propertyValueProvider,
|
||||||
|
JdbcBackReferencePropertyValueProvider backReferencePropertyValueProvider) {
|
||||||
this.entity = entity;
|
this.entity = entity;
|
||||||
this.resultSet = resultSet;
|
|
||||||
this.rootPath = rootPath;
|
this.rootPath = rootPath;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.identifier = identifier;
|
this.identifier = identifier;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
|
this.propertyValueProvider = propertyValueProvider;
|
||||||
propertyValueProvider = new JdbcPropertyValueProvider(identifierProcessing, path, resultSet);
|
this.backReferencePropertyValueProvider = backReferencePropertyValueProvider;
|
||||||
backReferencePropertyValueProvider = new JdbcBackReferencePropertyValueProvider(identifierProcessing, path,
|
|
||||||
resultSet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private <S> ReadingContext<S> extendBy(RelationalPersistentProperty property) {
|
private <S> ReadingContext<S> extendBy(RelationalPersistentProperty property) {
|
||||||
return new ReadingContext<>(
|
return new ReadingContext<>(
|
||||||
(RelationalPersistentEntity<S>) getMappingContext().getRequiredPersistentEntity(property.getActualType()),
|
(RelationalPersistentEntity<S>) getMappingContext().getRequiredPersistentEntity(property.getActualType()),
|
||||||
resultSet, rootPath.extendBy(property), path.extendBy(property), identifier, key);
|
rootPath.extendBy(property), path.extendBy(property), identifier, key,
|
||||||
|
propertyValueProvider.extendBy(property), backReferencePropertyValueProvider.extendBy(property));
|
||||||
}
|
}
|
||||||
|
|
||||||
T mapRow() {
|
T mapRow() {
|
||||||
@@ -412,11 +401,16 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object value = readOrLoadProperty(idValue, property);
|
// skip absent simple properties
|
||||||
if (value != SpecialColumnValue.NO_SUCH_COLUMN) {
|
if (isSimpleProperty(property)) {
|
||||||
propertyAccessor.setProperty(property, value);
|
|
||||||
|
if (!propertyValueProvider.hasProperty(property)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object value = readOrLoadProperty(idValue, property);
|
||||||
|
propertyAccessor.setProperty(property, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return propertyAccessor.getBean();
|
return propertyAccessor.getBean();
|
||||||
@@ -467,8 +461,7 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object value = propertyValueProvider.getPropertyValue(property);
|
Object value = propertyValueProvider.getPropertyValue(property);
|
||||||
return value == SpecialColumnValue.NO_SUCH_COLUMN ? SpecialColumnValue.NO_SUCH_COLUMN
|
return value != null ? readValue(value, property.getTypeInformation()) : null;
|
||||||
: readValue(value, property.getTypeInformation());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -501,7 +494,7 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object value = readOrLoadProperty(idValue, embeddedProperty);
|
Object value = readOrLoadProperty(idValue, embeddedProperty);
|
||||||
if (value != null && value != SpecialColumnValue.NO_SUCH_COLUMN) {
|
if (value != null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -541,21 +534,15 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
|
|||||||
Assert.notNull(parameterName, "A constructor parameter name must not be null to be used with Spring Data JDBC");
|
Assert.notNull(parameterName, "A constructor parameter name must not be null to be used with Spring Data JDBC");
|
||||||
|
|
||||||
RelationalPersistentProperty property = entity.getRequiredPersistentProperty(parameterName);
|
RelationalPersistentProperty property = entity.getRequiredPersistentProperty(parameterName);
|
||||||
|
return readOrLoadProperty(idValue, property);
|
||||||
Object value = readOrLoadProperty(idValue, property);
|
|
||||||
|
|
||||||
if (value == SpecialColumnValue.NO_SUCH_COLUMN) {
|
|
||||||
|
|
||||||
throw new MappingException(
|
|
||||||
String.format("Couldn't create instance of type %s with id. No column found for argument named '%s'.",
|
|
||||||
entity.getType(), parameterName));
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
});
|
});
|
||||||
return populateProperties(instance, idValue);
|
return populateProperties(instance, idValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isSimpleProperty(RelationalPersistentProperty property) {
|
||||||
|
return !property.isCollectionLike() && !property.isEntity() && !property.isMap() && !property.isEmbedded();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,11 +64,9 @@ public class EntityRowMapper<T> implements RowMapper<T> {
|
|||||||
@Override
|
@Override
|
||||||
public T mapRow(ResultSet resultSet, int rowNumber) {
|
public T mapRow(ResultSet resultSet, int rowNumber) {
|
||||||
|
|
||||||
ResultSetWrapper wrappedResultSet = new ResultSetWrapper(resultSet);
|
|
||||||
|
|
||||||
return path == null //
|
return path == null //
|
||||||
? converter.mapRow(entity, wrappedResultSet, rowNumber) //
|
? converter.mapRow(entity, resultSet, rowNumber) //
|
||||||
: converter.mapRow(path, wrappedResultSet, identifier, rowNumber);
|
: converter.mapRow(path, resultSet, identifier, rowNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,8 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProp
|
|||||||
import org.springframework.data.relational.core.sql.IdentifierProcessing;
|
import org.springframework.data.relational.core.sql.IdentifierProcessing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link PropertyValueProvider} obtaining values from a {@link ResultSetWrapper}.
|
* {@link PropertyValueProvider} obtaining values from a {@link ResultSetAccessor}. For a given id property it provides
|
||||||
* For a given id property it provides the value in the resultset under which other entities refer back to it.
|
* the value in the resultset under which other entities refer back to it.
|
||||||
*
|
|
||||||
*
|
*
|
||||||
* @author Jens Schauder
|
* @author Jens Schauder
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
@@ -32,15 +31,16 @@ class JdbcBackReferencePropertyValueProvider implements PropertyValueProvider<Re
|
|||||||
|
|
||||||
private final IdentifierProcessing identifierProcessing;
|
private final IdentifierProcessing identifierProcessing;
|
||||||
private final PersistentPropertyPathExtension basePath;
|
private final PersistentPropertyPathExtension basePath;
|
||||||
private final ResultSetWrapper resultSet;
|
private final ResultSetAccessor resultSet;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param identifierProcessing used for converting the {@link org.springframework.data.relational.core.sql.SqlIdentifier} from a property to a column label
|
* @param identifierProcessing used for converting the
|
||||||
|
* {@link org.springframework.data.relational.core.sql.SqlIdentifier} from a property to a column label
|
||||||
* @param basePath path from the aggregate root relative to which all properties get resolved.
|
* @param basePath path from the aggregate root relative to which all properties get resolved.
|
||||||
* @param resultSet the {@link ResultSetWrapper} from which to obtain the actual values.
|
* @param resultSet the {@link ResultSetAccessor} from which to obtain the actual values.
|
||||||
*/
|
*/
|
||||||
JdbcBackReferencePropertyValueProvider(IdentifierProcessing identifierProcessing, PersistentPropertyPathExtension basePath, ResultSetWrapper resultSet) {
|
JdbcBackReferencePropertyValueProvider(IdentifierProcessing identifierProcessing,
|
||||||
|
PersistentPropertyPathExtension basePath, ResultSetAccessor resultSet) {
|
||||||
|
|
||||||
this.resultSet = resultSet;
|
this.resultSet = resultSet;
|
||||||
this.basePath = basePath;
|
this.basePath = basePath;
|
||||||
@@ -49,6 +49,11 @@ class JdbcBackReferencePropertyValueProvider implements PropertyValueProvider<Re
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T getPropertyValue(RelationalPersistentProperty property) {
|
public <T> T getPropertyValue(RelationalPersistentProperty property) {
|
||||||
return (T)resultSet.getObject(basePath.extendBy(property).getReverseColumnNameAlias().getReference(identifierProcessing));
|
return (T) resultSet
|
||||||
|
.getObject(basePath.extendBy(property).getReverseColumnNameAlias().getReference(identifierProcessing));
|
||||||
|
}
|
||||||
|
|
||||||
|
public JdbcBackReferencePropertyValueProvider extendBy(RelationalPersistentProperty property) {
|
||||||
|
return new JdbcBackReferencePropertyValueProvider(identifierProcessing, basePath.extendBy(property), resultSet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public interface JdbcConverter extends RelationalConverter {
|
|||||||
* @param <T>
|
* @param <T>
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
<T> T mapRow(RelationalPersistentEntity<T> entity, ResultSetWrapper resultSet, Object key);
|
<T> T mapRow(RelationalPersistentEntity<T> entity, ResultSet resultSet, Object key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the current row from {@link ResultSet} to an {@link PersistentPropertyPathExtension#getActualType() entity}.
|
* Read the current row from {@link ResultSet} to an {@link PersistentPropertyPathExtension#getActualType() entity}.
|
||||||
@@ -66,7 +66,7 @@ public interface JdbcConverter extends RelationalConverter {
|
|||||||
* @param <T>
|
* @param <T>
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
<T> T mapRow(PersistentPropertyPathExtension path, ResultSetWrapper resultSet, Identifier identifier, Object key);
|
<T> T mapRow(PersistentPropertyPathExtension path, ResultSet resultSet, Identifier identifier, Object key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type to be used to store this property in the database. Multidimensional arrays are unwrapped to reflect a
|
* The type to be used to store this property in the database. Multidimensional arrays are unwrapped to reflect a
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProp
|
|||||||
import org.springframework.data.relational.core.sql.IdentifierProcessing;
|
import org.springframework.data.relational.core.sql.IdentifierProcessing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link PropertyValueProvider} obtaining values from a {@link ResultSetWrapper}.
|
* {@link PropertyValueProvider} obtaining values from a {@link ResultSetAccessor}.
|
||||||
*
|
*
|
||||||
* @author Jens Schauder
|
* @author Jens Schauder
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
@@ -30,15 +30,16 @@ class JdbcPropertyValueProvider implements PropertyValueProvider<RelationalPersi
|
|||||||
|
|
||||||
private final IdentifierProcessing identifierProcessing;
|
private final IdentifierProcessing identifierProcessing;
|
||||||
private final PersistentPropertyPathExtension basePath;
|
private final PersistentPropertyPathExtension basePath;
|
||||||
private final ResultSetWrapper resultSet;
|
private final ResultSetAccessor resultSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param identifierProcessing used for converting the {@link org.springframework.data.relational.core.sql.SqlIdentifier} from a property to a column label
|
* @param identifierProcessing used for converting the
|
||||||
|
* {@link org.springframework.data.relational.core.sql.SqlIdentifier} from a property to a column label
|
||||||
* @param basePath path from the aggregate root relative to which all properties get resolved.
|
* @param basePath path from the aggregate root relative to which all properties get resolved.
|
||||||
* @param resultSet the {@link ResultSetWrapper} from which to obtain the actual values.
|
* @param resultSet the {@link ResultSetAccessor} from which to obtain the actual values.
|
||||||
*/
|
*/
|
||||||
JdbcPropertyValueProvider(IdentifierProcessing identifierProcessing, PersistentPropertyPathExtension basePath,
|
JdbcPropertyValueProvider(IdentifierProcessing identifierProcessing, PersistentPropertyPathExtension basePath,
|
||||||
ResultSetWrapper resultSet) {
|
ResultSetAccessor resultSet) {
|
||||||
|
|
||||||
this.resultSet = resultSet;
|
this.resultSet = resultSet;
|
||||||
this.basePath = basePath;
|
this.basePath = basePath;
|
||||||
@@ -47,8 +48,24 @@ class JdbcPropertyValueProvider implements PropertyValueProvider<RelationalPersi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T getPropertyValue(RelationalPersistentProperty property) {
|
public <T> T getPropertyValue(RelationalPersistentProperty property) {
|
||||||
|
return (T) resultSet.getObject(getColumnName(property));
|
||||||
|
}
|
||||||
|
|
||||||
String columnName = basePath.extendBy(property).getColumnAlias().getReference(identifierProcessing);
|
/**
|
||||||
return (T) resultSet.getObject(columnName);
|
* Returns whether the underlying source contains a data source for the given {@link RelationalPersistentProperty}.
|
||||||
|
*
|
||||||
|
* @param property
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean hasProperty(RelationalPersistentProperty property) {
|
||||||
|
return resultSet.hasValue(getColumnName(property));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getColumnName(RelationalPersistentProperty property) {
|
||||||
|
return basePath.extendBy(property).getColumnAlias().getReference(identifierProcessing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JdbcPropertyValueProvider extendBy(RelationalPersistentProperty property) {
|
||||||
|
return new JdbcPropertyValueProvider(identifierProcessing, basePath.extendBy(property), resultSet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,6 @@ class MapEntityRowMapper<T> implements RowMapper<Map.Entry<Object, T>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private T mapEntity(ResultSet resultSet, Object key) {
|
private T mapEntity(ResultSet resultSet, Object key) {
|
||||||
return converter.mapRow(path, new ResultSetWrapper(resultSet), identifier, key);
|
return converter.mapRow(path, resultSet, identifier, key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,25 +27,29 @@ import org.springframework.lang.Nullable;
|
|||||||
import org.springframework.util.LinkedCaseInsensitiveMap;
|
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps a {@link java.sql.ResultSet} in order to provide fast lookup of columns by name, including for missing columns.
|
* Wrapper value object for a {@link java.sql.ResultSet} to be able to access raw values by
|
||||||
|
* {@link org.springframework.data.relational.core.mapping.RelationalPersistentProperty} references. Provides fast
|
||||||
|
* lookup of columns by name, including for absent columns.
|
||||||
*
|
*
|
||||||
* @author Jens Schauder
|
* @author Jens Schauder
|
||||||
|
* @author Mark Paluch
|
||||||
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
class ResultSetWrapper {
|
class ResultSetAccessor {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ResultSetWrapper.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ResultSetAccessor.class);
|
||||||
|
|
||||||
private final ResultSet resultSet;
|
private final ResultSet resultSet;
|
||||||
|
|
||||||
private final Map<String, Integer> indexLookUp;
|
private final Map<String, Integer> indexLookUp;
|
||||||
|
|
||||||
ResultSetWrapper(ResultSet resultSet) {
|
ResultSetAccessor(ResultSet resultSet) {
|
||||||
|
|
||||||
this.resultSet = resultSet;
|
this.resultSet = resultSet;
|
||||||
indexLookUp = indexColumns();
|
this.indexLookUp = indexColumns(resultSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Integer> indexColumns() {
|
private static Map<String, Integer> indexColumns(ResultSet resultSet) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@@ -57,29 +61,35 @@ class ResultSetWrapper {
|
|||||||
for (int i = 1; i <= columnCount; i++) {
|
for (int i = 1; i <= columnCount; i++) {
|
||||||
|
|
||||||
String label = metaData.getColumnLabel(i);
|
String label = metaData.getColumnLabel(i);
|
||||||
|
|
||||||
if (index.containsKey(label)) {
|
if (index.containsKey(label)) {
|
||||||
LOG.warn("We encountered a ResutSet with multiple columns associated with the same label {}.", label);
|
LOG.warn("ResultSet contains {} multiple times", label);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
index.put(label, i);
|
|
||||||
|
|
||||||
|
index.put(label, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
return index;
|
return index;
|
||||||
} catch (SQLException se) {
|
} catch (SQLException se) {
|
||||||
throw new MappingException("Failure while accessing metadata.");
|
throw new MappingException("Cannot obtain result metadata", se);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value if the result set contains the {@code columnName}.
|
||||||
|
*
|
||||||
|
* @param columnName the column name (label).
|
||||||
|
* @return
|
||||||
|
* @see ResultSet#getObject(int)
|
||||||
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
Object getObject(String columnName) {
|
public Object getObject(String columnName) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
int column = findColumnIndex(columnName);
|
int index = findColumnIndex(columnName);
|
||||||
|
return index > 0 ? resultSet.getObject(index) : null;
|
||||||
return column > 0 ? resultSet.getObject(column) : SpecialColumnValue.NO_SUCH_COLUMN;
|
|
||||||
} catch (SQLException o_O) {
|
} catch (SQLException o_O) {
|
||||||
throw new MappingException(String.format("Could not read value %s from result set!", columnName), o_O);
|
throw new MappingException(String.format("Could not read value %s from result set!", columnName), o_O);
|
||||||
}
|
}
|
||||||
@@ -89,7 +99,13 @@ class ResultSetWrapper {
|
|||||||
return indexLookUp.getOrDefault(columnName, -1);
|
return indexLookUp.getOrDefault(columnName, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SpecialColumnValue {
|
/**
|
||||||
NO_SUCH_COLUMN
|
* Returns {@literal true} if the result set contains the {@code columnName}.
|
||||||
|
*
|
||||||
|
* @param columnName the column name (label).
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean hasValue(String columnName) {
|
||||||
|
return indexLookUp.containsKey(columnName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,17 +43,16 @@ import java.util.stream.Stream;
|
|||||||
|
|
||||||
import javax.naming.OperationNotSupportedException;
|
import javax.naming.OperationNotSupportedException;
|
||||||
|
|
||||||
import org.assertj.core.api.Assertions;
|
|
||||||
import org.assertj.core.api.SoftAssertions;
|
import org.assertj.core.api.SoftAssertions;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.ArgumentMatchers;
|
import org.mockito.ArgumentMatchers;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
import org.springframework.data.annotation.Id;
|
import org.springframework.data.annotation.Id;
|
||||||
import org.springframework.data.annotation.PersistenceConstructor;
|
import org.springframework.data.annotation.PersistenceConstructor;
|
||||||
import org.springframework.data.jdbc.core.mapping.AggregateReference;
|
import org.springframework.data.jdbc.core.mapping.AggregateReference;
|
||||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||||
import org.springframework.data.mapping.MappingException;
|
|
||||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||||
import org.springframework.data.relational.core.mapping.Embedded;
|
import org.springframework.data.relational.core.mapping.Embedded;
|
||||||
import org.springframework.data.relational.core.mapping.Embedded.OnEmpty;
|
import org.springframework.data.relational.core.mapping.Embedded.OnEmpty;
|
||||||
@@ -476,10 +475,10 @@ public class EntityRowMapperUnitTests {
|
|||||||
ID_FOR_ENTITY_NOT_REFERENCING_MAP);
|
ID_FOR_ENTITY_NOT_REFERENCING_MAP);
|
||||||
rs.next();
|
rs.next();
|
||||||
|
|
||||||
assertThatThrownBy(() -> createRowMapper(TrivialImmutable.class).mapRow(rs, 1)) //
|
TrivialImmutable trivialImmutable = createRowMapper(TrivialImmutable.class).mapRow(rs, 1);
|
||||||
.hasMessageContaining("TrivialImmutable") //
|
|
||||||
.hasMessageContaining("name");
|
|
||||||
|
|
||||||
|
assertThat(trivialImmutable.id).isEqualTo(23L);
|
||||||
|
assertThat(trivialImmutable.name).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAJDBC-341
|
@Test // DATAJDBC-341
|
||||||
@@ -530,16 +529,15 @@ public class EntityRowMapperUnitTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAJDBC-341
|
@Test // DATAJDBC-341
|
||||||
public void immutableEmbeddedWithSomeColumnsMissingShouldThrowAnException() throws SQLException {
|
public void immutableEmbeddedWithSomeColumnsMissingShouldNotBeEmpty() throws SQLException {
|
||||||
|
|
||||||
ResultSet rs = mockResultSet(asList("ID", "VALUE"), //
|
ResultSet rs = mockResultSet(asList("ID", "VALUE"), //
|
||||||
ID_FOR_ENTITY_NOT_REFERENCING_MAP, "some value");
|
ID_FOR_ENTITY_NOT_REFERENCING_MAP, "some value");
|
||||||
rs.next();
|
rs.next();
|
||||||
|
|
||||||
Assertions.assertThatThrownBy( //
|
WithNullableEmbeddedImmutableValue result = createRowMapper(WithNullableEmbeddedImmutableValue.class).mapRow(rs, 1);
|
||||||
() -> createRowMapper(WithNullableEmbeddedImmutableValue.class).mapRow(rs, 1) //
|
|
||||||
).isInstanceOf(MappingException.class) //
|
assertThat(result.embeddedImmutableValue).isNotNull();
|
||||||
.hasMessageContaining("'name'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAJDBC-341
|
@Test // DATAJDBC-341
|
||||||
@@ -619,19 +617,6 @@ public class EntityRowMapperUnitTests {
|
|||||||
.containsExactly(ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", 24L, null);
|
.containsExactly(ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", 24L, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAJDBC-341
|
|
||||||
public void immutableOneToOneWithMissingColumnResultsInException() throws SQLException {
|
|
||||||
|
|
||||||
ResultSet rs = mockResultSet(asList("ID", "NAME", "CHILD_ID"), //
|
|
||||||
ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", 24L);
|
|
||||||
rs.next();
|
|
||||||
|
|
||||||
Assertions.assertThatThrownBy( //
|
|
||||||
() -> createRowMapper(OneToOneImmutable.class).mapRow(rs, 1) //
|
|
||||||
).isInstanceOf(MappingException.class) //
|
|
||||||
.hasMessageContaining("'name'");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test // DATAJDBC-341
|
@Test // DATAJDBC-341
|
||||||
public void oneToOneWithMissingIdColumnResultsInNullProperty() throws SQLException {
|
public void oneToOneWithMissingIdColumnResultsInNullProperty() throws SQLException {
|
||||||
|
|
||||||
@@ -641,10 +626,7 @@ public class EntityRowMapperUnitTests {
|
|||||||
|
|
||||||
OneToOne extracted = createRowMapper(OneToOne.class).mapRow(rs, 1);
|
OneToOne extracted = createRowMapper(OneToOne.class).mapRow(rs, 1);
|
||||||
|
|
||||||
assertThat(extracted) //
|
assertThat(extracted.child).isNull();
|
||||||
.isNotNull() //
|
|
||||||
.extracting(e -> e.id, e -> e.name, e -> e.child.id, e -> e.child.name) //
|
|
||||||
.containsExactly(ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", null, "Alfred");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAJDBC-341
|
@Test // DATAJDBC-341
|
||||||
@@ -654,10 +636,11 @@ public class EntityRowMapperUnitTests {
|
|||||||
ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", "Alfred");
|
ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", "Alfred");
|
||||||
rs.next();
|
rs.next();
|
||||||
|
|
||||||
Assertions.assertThatThrownBy( //
|
OneToOneImmutable result = createRowMapper(OneToOneImmutable.class).mapRow(rs, 1);
|
||||||
() -> createRowMapper(OneToOneImmutable.class).mapRow(rs, 1) //
|
|
||||||
).isInstanceOf(MappingException.class) //
|
assertThat(result.id).isEqualTo(23);
|
||||||
.hasMessageContaining("'id'");
|
assertThat(result.name).isEqualTo("alpha");
|
||||||
|
assertThat(result.child).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Model classes to be used in tests
|
// Model classes to be used in tests
|
||||||
|
|||||||
@@ -444,15 +444,15 @@ For converting the query result into entities the same `RowMapper` is used by de
|
|||||||
The query you provide must match the format the `RowMapper` expects.
|
The query you provide must match the format the `RowMapper` expects.
|
||||||
Columns for all properties that are used in the constructor of an entity must be provided.
|
Columns for all properties that are used in the constructor of an entity must be provided.
|
||||||
Columns for properties that get set via setter, wither or field access are optional.
|
Columns for properties that get set via setter, wither or field access are optional.
|
||||||
Properties that don't have a matching column will not get set.
|
Properties that don't have a matching column in the result will not be set.
|
||||||
The query is used for populating the aggregate root, embedded entities and one to one relationships including arrays of primitive types which get stored and loaded as SQL-array-types.
|
The query is used for populating the aggregate root, embedded entities and one-to-one relationships including arrays of primitive types which get stored and loaded as SQL-array-types.
|
||||||
For maps, lists, sets and arrays of entities separate queries get generated.
|
Separate queries are generated for maps, lists, sets and arrays of entities.
|
||||||
|
|
||||||
NOTE: Spring fully supports Java 8’s parameter name discovery based on the `-parameters` compiler flag. By using this flag in your build as an alternative to debug information, you can omit the `@Param` annotation for named parameters.
|
NOTE: Spring fully supports Java 8’s parameter name discovery based on the `-parameters` compiler flag.
|
||||||
|
By using this flag in your build as an alternative to debug information, you can omit the `@Param` annotation for named parameters.
|
||||||
|
|
||||||
NOTE: Spring Data JDBC supports only named parameters.
|
NOTE: Spring Data JDBC supports only named parameters.
|
||||||
|
|
||||||
|
|
||||||
[[jdbc.query-methods.named-query]]
|
[[jdbc.query-methods.named-query]]
|
||||||
=== Named Queries
|
=== Named Queries
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ This section covers the significant changes for each version.
|
|||||||
* Support for `PagingAndSortingRepository`.
|
* Support for `PagingAndSortingRepository`.
|
||||||
* Full Support for H2.
|
* Full Support for H2.
|
||||||
* All SQL identifiers know get quoted by default.
|
* All SQL identifiers know get quoted by default.
|
||||||
* Missing column no longer cause exceptions as long as they are not required by the persitence constructor.
|
* Missing columns no longer cause exceptions.
|
||||||
|
|
||||||
[[new-features.1-1-0]]
|
[[new-features.1-1-0]]
|
||||||
== What's New in Spring Data JDBC 1.1
|
== What's New in Spring Data JDBC 1.1
|
||||||
|
|||||||
Reference in New Issue
Block a user