DATAJDBC-359 - Support for arbitrary chains of entities with or without Id.

Introduced a `ReadingContext` in the `EntityRowMapper` to avoid passing the `ResultSet` and the `path` all over the place.

Added a dependency test to Spring Data Relational and fixed the test failure by moving the PersistentPropertyPathExtension to core.mapping.

Original pull request: #150.
This commit is contained in:
Jens Schauder
2019-04-10 16:55:40 +02:00
committed by Mark Paluch
parent aea39d99c5
commit b1c68d901e
53 changed files with 1857 additions and 989 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 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.
@@ -15,470 +15,32 @@
*/
package org.springframework.data.jdbc.core;
import lombok.NonNull;
import java.sql.JDBCType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.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.jdbc.core.convert.SqlGeneratorSource;
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.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;
/**
* The default {@link DataAccessStrategy} is to generate SQL statements based on meta data from the entity.
*
*
* @author Jens Schauder
* @author Mark Paluch
* @author Thomas Lang
* @author Bastian Wilhelm
* @deprecated Use {@link org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy} instead.
*/
public class DefaultDataAccessStrategy implements DataAccessStrategy {
private final @NonNull SqlGeneratorSource sqlGeneratorSource;
private final @NonNull RelationalMappingContext context;
private final @NonNull JdbcConverter converter;
private final @NonNull NamedParameterJdbcOperations operations;
private final @NonNull DataAccessStrategy accessStrategy;
@Deprecated
public class DefaultDataAccessStrategy extends org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy {
/**
* 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) {
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 = mappingAccessStrategy == null ? this : mappingAccessStrategy;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
*/
@Override
public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
return insert(instance, domainType, Identifier.from(additionalParameters));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
*/
@Override
public <T> Object insert(T instance, Class<T> domainType, Identifier identifier) {
KeyHolder holder = new GeneratedKeyHolder();
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
MapSqlParameterSource parameterSource = getParameterSource(instance, persistentEntity, "",
PersistentProperty::isIdProperty);
identifier.forEach((name, value, type) -> addConvertedPropertyValue(parameterSource, name, value, type));
Object idValue = getIdValueOrNull(instance, persistentEntity);
if (idValue != null) {
RelationalPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
addConvertedPropertyValue(parameterSource, idProperty, idValue, idProperty.getColumnName());
}
operations.update( //
sql(domainType).getInsert(new HashSet<>(Arrays.asList(parameterSource.getParameterNames()))), //
parameterSource, //
holder //
);
return getIdFromHolder(holder, persistentEntity);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class)
*/
@Override
public <S> boolean update(S instance, Class<S> domainType) {
RelationalPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType);
return operations.update(sql(domainType).getUpdate(),
getParameterSource(instance, persistentEntity, "", Predicates.includeAll())) != 0;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, java.lang.Class)
*/
@Override
public void delete(Object id, Class<?> domainType) {
String deleteByIdSql = sql(domainType).getDeleteById();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
operations.update(deleteByIdSql, parameter);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, org.springframework.data.mapping.PropertyPath)
*/
@Override
public void delete(Object rootId, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
RelationalPersistentEntity<?> rootEntity = context
.getRequiredPersistentEntity(propertyPath.getBaseProperty().getOwner().getType());
RelationalPersistentProperty referencingProperty = propertyPath.getLeafProperty();
Assert.notNull(referencingProperty, "No property found matching the PropertyPath " + propertyPath);
String format = sql(rootEntity.getType()).createDeleteByPath(propertyPath);
HashMap<String, Object> parameters = new HashMap<>();
parameters.put("rootId", rootId);
operations.update(format, parameters);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(java.lang.Class)
*/
@Override
public <T> void deleteAll(Class<T> domainType) {
operations.getJdbcOperations().update(sql(domainType).createDeleteAllSql(null));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(org.springframework.data.mapping.PropertyPath)
*/
@Override
public void deleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
operations.getJdbcOperations()
.update(sql(propertyPath.getBaseProperty().getOwner().getType()).createDeleteAllSql(propertyPath));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#count(java.lang.Class)
*/
@Override
public long count(Class<?> domainType) {
Long result = operations.getJdbcOperations().queryForObject(sql(domainType).getCount(), Long.class);
Assert.notNull(result, "The result of a count query must not be null.");
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findById(java.lang.Object, java.lang.Class)
*/
@SuppressWarnings("unchecked")
@Override
public <T> T findById(Object id, Class<T> domainType) {
String findOneSql = sql(domainType).getFindOne();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
try {
return operations.queryForObject(findOneSql, parameter, (RowMapper<T>) getEntityRowMapper(domainType));
} catch (EmptyResultDataAccessException e) {
return null;
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAll(java.lang.Class)
*/
@SuppressWarnings("unchecked")
@Override
public <T> Iterable<T> findAll(Class<T> domainType) {
return operations.query(sql(domainType).getFindAll(), (RowMapper<T>) getEntityRowMapper(domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllById(java.lang.Iterable, java.lang.Class)
*/
@SuppressWarnings("unchecked")
@Override
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
RelationalPersistentProperty idProperty = getRequiredPersistentEntity(domainType).getRequiredIdProperty();
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
addConvertedPropertyValuesAsList(parameterSource, idProperty, ids, "ids");
String findAllInListSql = sql(domainType).getFindAllInList();
return operations.query(findAllInListSql, parameterSource, (RowMapper<T>) getEntityRowMapper(domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllByProperty(java.lang.Object, org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty)
*/
@Override
@SuppressWarnings("unchecked")
public <T> Iterable<T> findAllByProperty(Object rootId, RelationalPersistentProperty property) {
Assert.notNull(rootId, "rootId must not be null.");
Class<?> actualType = property.getActualType();
String findAllByProperty = sql(actualType) //
.getFindAllByProperty(property.getReverseColumnName(), property.getKeyColumn(), property.isOrdered());
MapSqlParameterSource parameter = new MapSqlParameterSource(property.getReverseColumnName(), rootId);
return operations.query(findAllByProperty, parameter, //
(RowMapper<T>) (property.isMap() //
? this.getMapEntityRowMapper(property) //
: this.getEntityRowMapper(actualType)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#existsById(java.lang.Object, java.lang.Class)
*/
@Override
public <T> boolean existsById(Object id, Class<T> domainType) {
String existsSql = sql(domainType).getExists();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
Boolean result = operations.queryForObject(existsSql, parameter, Boolean.class);
Assert.notNull(result, "The result of an exists query must not be null");
return result;
}
private <S, T> MapSqlParameterSource getParameterSource(S instance, RelationalPersistentEntity<S> persistentEntity,
String prefix, Predicate<RelationalPersistentProperty> skipProperty) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
PersistentPropertyAccessor<S> propertyAccessor = persistentEntity.getPropertyAccessor(instance);
persistentEntity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {
if (skipProperty.test(property)) {
return;
}
if (property.isEntity() && !property.isEmbedded()) {
return;
}
if (property.isEmbedded()) {
Object value = propertyAccessor.getProperty(property);
RelationalPersistentEntity<?> embeddedEntity = context.getPersistentEntity(property.getType());
MapSqlParameterSource additionalParameters = getParameterSource((T) value,
(RelationalPersistentEntity<T>) embeddedEntity, prefix + property.getEmbeddedPrefix(), skipProperty);
parameters.addValues(additionalParameters.getValues());
} else {
Object value = propertyAccessor.getProperty(property);
String paramName = prefix + property.getColumnName();
addConvertedPropertyValue(parameters, property, value, paramName);
}
});
return parameters;
}
@SuppressWarnings("unchecked")
@Nullable
private <S, ID> ID getIdValueOrNull(S instance, RelationalPersistentEntity<S> persistentEntity) {
ID idValue = (ID) persistentEntity.getIdentifierAccessor(instance).getIdentifier();
return isIdPropertyNullOrScalarZero(idValue, persistentEntity) ? null : idValue;
}
private static <S, ID> boolean isIdPropertyNullOrScalarZero(@Nullable ID idValue,
RelationalPersistentEntity<S> persistentEntity) {
RelationalPersistentProperty idProperty = persistentEntity.getIdProperty();
return idValue == null //
|| idProperty == null //
|| (idProperty.getType() == int.class && idValue.equals(0)) //
|| (idProperty.getType() == long.class && idValue.equals(0L));
}
@Nullable
private <S> Object getIdFromHolder(KeyHolder holder, RelationalPersistentEntity<S> persistentEntity) {
try {
// MySQL just returns one value with a special name
return holder.getKey();
} catch (DataRetrievalFailureException | InvalidDataAccessApiUsageException e) {
// Postgres returns a value for each column
// MS SQL Server returns a value that might be null.
Map<String, Object> keys = holder.getKeys();
if (keys == null || persistentEntity.getIdProperty() == null) {
return null;
}
return keys.get(persistentEntity.getIdColumn());
}
}
private EntityRowMapper<?> getEntityRowMapper(Class<?> domainType) {
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context, converter, accessStrategy);
}
private RowMapper<?> getMapEntityRowMapper(RelationalPersistentProperty property) {
String keyColumn = property.getKeyColumn();
Assert.notNull(keyColumn, () -> "KeyColumn must not be null for " + property);
return new MapEntityRowMapper<>(getEntityRowMapper(property.getActualType()), keyColumn);
}
private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> domainType) {
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")
private <S> RelationalPersistentEntity<S> getRequiredPersistentEntity(Class<S> domainType) {
return (RelationalPersistentEntity<S>) context.getRequiredPersistentEntity(domainType);
}
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.
* Creates a {@link org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy} which references it self for resolution of recursive data accesses.
* Only suitable if this is the only access strategy in use.
*
* @return the include all {@link Predicate}.
* @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}.
*/
static Predicate<RelationalPersistentProperty> includeAll() {
return it -> false;
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, RelationalMappingContext context,
JdbcConverter converter, NamedParameterJdbcOperations operations) {
super(sqlGeneratorSource, context, converter, operations);
}
}
}

View File

@@ -20,7 +20,6 @@ import lombok.RequiredArgsConstructor;
import java.util.Collections;
import java.util.Map;
import org.springframework.data.jdbc.core.convert.JdbcIdentifierBuilder;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.conversion.DbAction;
import org.springframework.data.relational.core.conversion.DbAction.Delete;
@@ -33,11 +32,12 @@ import org.springframework.data.relational.core.conversion.DbAction.Merge;
import org.springframework.data.relational.core.conversion.DbAction.Update;
import org.springframework.data.relational.core.conversion.DbAction.UpdateRoot;
import org.springframework.data.relational.core.conversion.Interpreter;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
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.lang.Nullable;
import org.springframework.util.Assert;
/**
* {@link Interpreter} for {@link DbAction}s using a {@link DataAccessStrategy} for performing actual database
@@ -144,38 +144,67 @@ class DefaultJdbcInterpreter implements Interpreter {
private Identifier getParentKeys(DbAction.WithDependingOn<?> action) {
DbAction.WithEntity<?> dependingOn = action.getDependingOn();
Object id = getParentId(action);
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(dependingOn.getEntityType());
Object id = getIdFromEntityDependingOn(dependingOn, persistentEntity);
JdbcIdentifierBuilder identifier = JdbcIdentifierBuilder //
.forBackReferences(action.getPropertyPath(), id);
.forBackReferences(new PersistentPropertyPathExtension(context, action.getPropertyPath()), id);
for (Map.Entry<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifier : action.getQualifiers()
.entrySet()) {
identifier = identifier.withQualifier(qualifier.getKey(), qualifier.getValue());
identifier = identifier.withQualifier(new PersistentPropertyPathExtension(context, qualifier.getKey()),
qualifier.getValue());
}
return identifier.build();
}
@Nullable
private Object getIdFromEntityDependingOn(DbAction.WithEntity<?> dependingOn,
RelationalPersistentEntity<?> persistentEntity) {
private Object getParentId(DbAction.WithDependingOn<?> action) {
Object entity = dependingOn.getEntity();
PersistentPropertyPathExtension path = new PersistentPropertyPathExtension(context, action.getPropertyPath());
PersistentPropertyPathExtension idPath = path.getIdDefiningParentPath();
if (dependingOn instanceof DbAction.WithGeneratedId) {
DbAction.WithEntity idOwningAction = getIdOwningAction(action, idPath);
Object generatedId = ((DbAction.WithGeneratedId<?>) dependingOn).getGeneratedId();
return getIdFrom(idOwningAction);
}
@SuppressWarnings("unchecked")
private DbAction.WithEntity getIdOwningAction(DbAction.WithEntity action, PersistentPropertyPathExtension idPath) {
if (!(action instanceof DbAction.WithDependingOn)) {
Assert.state(idPath.getLength() == 0,
"When the id path is not empty the id providing action should be of type WithDependingOn");
return action;
}
DbAction.WithDependingOn withDependingOn = (DbAction.WithDependingOn) action;
if (idPath.matches(withDependingOn.getPropertyPath())) {
return action;
}
return getIdOwningAction(withDependingOn.getDependingOn(), idPath);
}
private Object getIdFrom(DbAction.WithEntity idOwningAction) {
if (idOwningAction instanceof DbAction.WithGeneratedId) {
Object generatedId = ((DbAction.WithGeneratedId<?>) idOwningAction).getGeneratedId();
if (generatedId != null) {
return generatedId;
}
}
return persistentEntity.getIdentifierAccessor(entity).getIdentifier();
}
RelationalPersistentEntity<?> persistentEntity = context
.getRequiredPersistentEntity(idOwningAction.getEntityType());
Object identifier = persistentEntity.getIdentifierAccessor(idOwningAction.getEntity()).getIdentifier();
Assert.state(identifier != null, "Couldn't get obtain a required id value");
return identifier;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 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.
@@ -15,209 +15,20 @@
*/
package org.springframework.data.jdbc.core;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Maps a {@link ResultSet} to an entity of type {@code T}, including entities referenced. This {@link RowMapper} might
* trigger additional SQL statements in order to load other members of the same aggregate.
*
* @author Jens Schauder
* @author Oliver Gierke
* @author Mark Paluch
* @author Maciej Walkowiak
* @author Bastian Wilhelm
*
* @deprecated Use {@link org.springframework.data.jdbc.core.convert.EntityRowMapper} instead.
*/
public class EntityRowMapper<T> implements RowMapper<T> {
@Deprecated
public class EntityRowMapper<T> extends org.springframework.data.jdbc.core.convert.EntityRowMapper<T> {
private static final Converter<Iterable<?>, Map<?, ?>> ITERABLE_OF_ENTRY_TO_MAP_CONVERTER = new IterableOfEntryToMapConverter();
private final RelationalPersistentEntity<T> entity;
private final RelationalConverter converter;
private final RelationalMappingContext context;
private final DataAccessStrategy accessStrategy;
private final RelationalPersistentProperty idProperty;
public EntityRowMapper(RelationalPersistentEntity<T> entity, RelationalMappingContext context,
RelationalConverter converter, DataAccessStrategy accessStrategy) {
this.entity = entity;
this.converter = converter;
this.context = context;
this.accessStrategy = accessStrategy;
this.idProperty = entity.getIdProperty();
public EntityRowMapper(RelationalPersistentEntity<T> entity, JdbcConverter converter,
DataAccessStrategy accessStrategy) {
super(entity, converter, accessStrategy);
}
/*
* (non-Javadoc)
* @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
*/
@Override
public T mapRow(ResultSet resultSet, int rowNumber) {
String prefix = "";
RelationalPersistentProperty idProperty = entity.getIdProperty();
Object idValue = null;
if (idProperty != null) {
idValue = readFrom(resultSet, idProperty, prefix);
}
T result = createInstance(entity, resultSet, idValue, prefix);
return entity.requiresPropertyPopulation() //
? populateProperties(result, resultSet) //
: result;
}
private T populateProperties(T result, ResultSet resultSet) {
PersistentPropertyAccessor<T> propertyAccessor = converter.getPropertyAccessor(entity, result);
Object id = idProperty == null ? null : readFrom(resultSet, idProperty, "");
PreferredConstructor<T, RelationalPersistentProperty> persistenceConstructor = entity.getPersistenceConstructor();
for (RelationalPersistentProperty property : entity) {
if (persistenceConstructor != null && persistenceConstructor.isConstructorParameter(property)) {
continue;
}
propertyAccessor.setProperty(property, readOrLoadProperty(resultSet, id, property, ""));
}
return propertyAccessor.getBean();
}
@Nullable
private Object readOrLoadProperty(ResultSet resultSet, @Nullable Object id, RelationalPersistentProperty property,
String prefix) {
if (property.isCollectionLike() && property.isEntity() && id != null) {
return accessStrategy.findAllByProperty(id, property);
} else if (property.isMap() && id != null) {
return ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(accessStrategy.findAllByProperty(id, property));
} else if (property.isEmbedded()) {
return readEmbeddedEntityFrom(resultSet, id, property, prefix);
} else {
return readFrom(resultSet, property, prefix);
}
}
/**
* Read a single value or a complete Entity from the {@link ResultSet} passed as an argument.
*
* @param resultSet the {@link ResultSet} to extract the value from. Must not be {@code null}.
* @param property the {@link RelationalPersistentProperty} for which the value is intended. Must not be {@code null}.
* @param prefix to be used for all column names accessed by this method. Must not be {@code null}.
* @return the value read from the {@link ResultSet}. May be {@code null}.
*/
@Nullable
private Object readFrom(ResultSet resultSet, RelationalPersistentProperty property, String prefix) {
if (property.isEntity()) {
return readEntityFrom(resultSet, property, prefix);
}
Object value = getObjectFromResultSet(resultSet, prefix + property.getColumnName());
return converter.readValue(value, property.getTypeInformation());
}
private Object readEmbeddedEntityFrom(ResultSet rs, @Nullable Object id, RelationalPersistentProperty property,
String prefix) {
String newPrefix = prefix + property.getEmbeddedPrefix();
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(property.getActualType());
Object instance = createInstance(entity, rs, null, newPrefix);
@SuppressWarnings("unchecked")
PersistentPropertyAccessor<?> accessor = converter.getPropertyAccessor((PersistentEntity<Object, ?>) entity,
instance);
for (RelationalPersistentProperty p : entity) {
accessor.setProperty(p, readOrLoadProperty(rs, id, p, newPrefix));
}
return instance;
}
@Nullable
private <S> S readEntityFrom(ResultSet rs, RelationalPersistentProperty property, String prefix) {
String newPrefix = prefix + property.getName() + "_";
@SuppressWarnings("unchecked")
RelationalPersistentEntity<S> entity = (RelationalPersistentEntity<S>) context
.getRequiredPersistentEntity(property.getActualType());
RelationalPersistentProperty idProperty = entity.getIdProperty();
Object idValue = null;
if (idProperty != null) {
idValue = readFrom(rs, idProperty, newPrefix);
}
if ((idProperty != null //
? idValue //
: getObjectFromResultSet(rs, newPrefix + property.getReverseColumnName()) //
) == null) {
return null;
}
S instance = createInstance(entity, rs, idValue, newPrefix);
PersistentPropertyAccessor<S> accessor = converter.getPropertyAccessor(entity, instance);
for (RelationalPersistentProperty p : entity) {
accessor.setProperty(p, readOrLoadProperty(rs, idValue, p, newPrefix));
}
return instance;
}
@Nullable
private Object getObjectFromResultSet(ResultSet rs, String backreferenceName) {
try {
return rs.getObject(backreferenceName);
} catch (SQLException o_O) {
throw new MappingException(String.format("Could not read value %s from result set!", backreferenceName), o_O);
}
}
private <S> S createInstance(RelationalPersistentEntity<S> entity, ResultSet rs, @Nullable Object idValue,
String prefix) {
return converter.createInstance(entity, parameter -> {
String parameterName = parameter.getName();
Assert.notNull(parameterName, "A constructor parameter name must not be null to be used with Spring Data JDBC");
RelationalPersistentProperty property = entity.getRequiredPersistentProperty(parameterName);
return readOrLoadProperty(rs, idValue, property, prefix);
});
}
}

View File

@@ -13,12 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core.convert;
package org.springframework.data.jdbc.core;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Builder for {@link Identifier}. Mainly for internal use within the framework
@@ -41,30 +43,17 @@ public class JdbcIdentifierBuilder {
/**
* Creates ParentKeys with backreference for the given path and value of the parents id.
*/
public static JdbcIdentifierBuilder forBackReferences(PersistentPropertyPath<RelationalPersistentProperty> path,
@Nullable Object value) {
public static JdbcIdentifierBuilder forBackReferences(PersistentPropertyPathExtension path, @Nullable Object value) {
Identifier identifier = Identifier.of( //
path.getRequiredLeafProperty().getReverseColumnName(), //
path.getReverseColumnName(), //
value, //
getLastIdProperty(path).getColumnType() //
path.getIdDefiningParentPath().getRequiredIdProperty().getColumnType() //
);
return new JdbcIdentifierBuilder(identifier);
}
public JdbcIdentifierBuilder withQualifier(PersistentPropertyPath<RelationalPersistentProperty> path, Object value) {
RelationalPersistentProperty leafProperty = path.getRequiredLeafProperty();
identifier = identifier.withPart(leafProperty.getKeyColumn(), value, leafProperty.getQualifierColumnType());
return this;
}
public Identifier build() {
return identifier;
}
private static RelationalPersistentProperty getLastIdProperty(
PersistentPropertyPath<RelationalPersistentProperty> path) {
@@ -76,4 +65,25 @@ public class JdbcIdentifierBuilder {
return getLastIdProperty(path.getParentPath());
}
/**
* Adds a qualifier to the identifier to build. A qualifier is a map key or a list index.
*
* @param path path to the map that gets qualified by {@code value}. Must not be {@literal null}.
* @param value map key or list index qualifying the map identified by {@code path}. Must not be {@literal null}.
* @return this builder. Guaranteed to be not {@literal null}.
*/
public JdbcIdentifierBuilder withQualifier(PersistentPropertyPathExtension path, Object value) {
Assert.notNull(path, "Path must not be null");
Assert.notNull(value, "Value must not be null");
identifier = identifier.withPart(path.getKeyColumn(), value, path.getQualifierColumnType());
return this;
}
public Identifier build() {
return identifier;
}
}

View File

@@ -15,22 +15,32 @@
*/
package org.springframework.data.jdbc.core.convert;
import lombok.Value;
import java.sql.Array;
import java.sql.JDBCType;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PreferredConstructor;
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.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.util.ClassTypeInformation;
@@ -53,7 +63,7 @@ import org.springframework.util.Assert;
public class BasicJdbcConverter extends BasicRelationalConverter implements JdbcConverter {
private static final Logger LOG = LoggerFactory.getLogger(BasicJdbcConverter.class);
private static final Converter<Iterable<?>, Map<?, ?>> ITERABLE_OF_ENTRY_TO_MAP_CONVERTER = new IterableOfEntryToMapConverter();
private final JdbcTypeFactory typeFactory;
/**
@@ -238,4 +248,194 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
*/
@Override
public <T> T mapRow(RelationalPersistentEntity<T> entity, DataAccessStrategy accessStrategy, ResultSet resultSet) {
return new ReadingContext<T>(entity, accessStrategy, resultSet).mapRow();
}
@Value
private class ReadingContext<T> {
private final RelationalPersistentEntity<T> entity;
private final RelationalPersistentProperty idProperty;
private final ResultSet resultSet;
PersistentPropertyPathExtension path;
private final DataAccessStrategy accessStrategy;
ReadingContext(RelationalPersistentEntity<T> entity, DataAccessStrategy accessStrategy, ResultSet resultSet) {
this.entity = entity;
this.idProperty = entity.getIdProperty();
this.accessStrategy = accessStrategy;
this.resultSet = resultSet;
this.path = new PersistentPropertyPathExtension((MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty>) getMappingContext(), entity);
}
public ReadingContext(RelationalPersistentEntity<T> entity, DataAccessStrategy accessStrategy, ResultSet resultSet, PersistentPropertyPathExtension path) {
this.entity = entity;
this.idProperty = entity.getIdProperty();
this.accessStrategy = accessStrategy;
this.resultSet = resultSet;
this.path = path;
}
private ReadingContext extendBy(RelationalPersistentProperty property) {
return new ReadingContext(entity, accessStrategy, resultSet, path.extendBy(property));
}
T mapRow() {
RelationalPersistentProperty idProperty = entity.getIdProperty();
Object idValue = null;
if (idProperty != null) {
idValue = readFrom(idProperty);
}
T result = createInstanceInternal(entity, idValue);
return entity.requiresPropertyPopulation() //
? populateProperties(result) //
: result;
}
private T populateProperties(T result) {
PersistentPropertyAccessor<T> propertyAccessor = getPropertyAccessor(entity, result);
Object id = idProperty == null ? null : readFrom(idProperty);
PreferredConstructor<T, RelationalPersistentProperty> persistenceConstructor = entity.getPersistenceConstructor();
for (RelationalPersistentProperty property : entity) {
if (persistenceConstructor != null && persistenceConstructor.isConstructorParameter(property)) {
continue;
}
propertyAccessor.setProperty(property, readOrLoadProperty(id, property));
}
return propertyAccessor.getBean();
}
@Nullable
private Object readOrLoadProperty(@Nullable Object id, RelationalPersistentProperty property) {
if (property.isCollectionLike() && property.isEntity() && id != null) {
return accessStrategy.findAllByProperty(id, property);
} else if (property.isMap() && id != null) {
return ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(accessStrategy.findAllByProperty(id, property));
} else if (property.isEmbedded()) {
return readEmbeddedEntityFrom(id, property);
} else {
return readFrom(property);
}
}
/**
* Read a single value or a complete Entity from the {@link ResultSet} passed as an argument.
*
* @param property the {@link RelationalPersistentProperty} for which the value is intended. Must not be
* {@code null}.
* @return the value read from the {@link ResultSet}. May be {@code null}.
*/
@Nullable
private Object readFrom(RelationalPersistentProperty property) {
if (property.isEntity()) {
return readEntityFrom(property, path);
}
Object value = getObjectFromResultSet(path.extendBy(property).getColumnAlias());
return readValue(value, property.getTypeInformation());
}
private Object readEmbeddedEntityFrom(@Nullable Object id, RelationalPersistentProperty property) {
ReadingContext newContext = extendBy(property);
RelationalPersistentEntity<?> entity = getMappingContext().getRequiredPersistentEntity(property.getActualType());
Object instance = newContext.createInstanceInternal(entity, null);
@SuppressWarnings("unchecked")
PersistentPropertyAccessor<?> accessor = getPropertyAccessor((PersistentEntity<Object, ?>) entity, instance);
for (RelationalPersistentProperty p : entity) {
accessor.setProperty(p, newContext.readOrLoadProperty(id, p));
}
return instance;
}
@Nullable
private <S> S readEntityFrom(RelationalPersistentProperty property, PersistentPropertyPathExtension path) {
@SuppressWarnings("unchecked")
ReadingContext<S> newContext = extendBy(property);
@SuppressWarnings("unchecked")
RelationalPersistentEntity<S> entity = (RelationalPersistentEntity<S>) getMappingContext()
.getRequiredPersistentEntity(property.getActualType());
RelationalPersistentProperty idProperty = entity.getIdProperty();
Object idValue = null;
if (idProperty != null) {
idValue = newContext.readFrom(idProperty);
}
if ((idProperty != null //
? idValue //
: newContext.getObjectFromResultSet(path.extendBy(property).getReverseColumnNameAlias()) //
) == null) {
return null;
}
S instance = newContext.createInstanceInternal(entity, idValue);
PersistentPropertyAccessor<S> accessor = getPropertyAccessor(entity, instance);
for (RelationalPersistentProperty p : entity) {
accessor.setProperty(p, newContext.readOrLoadProperty(idValue, p));
}
return instance;
}
@Nullable
private Object getObjectFromResultSet(String backreferenceName) {
try {
return resultSet.getObject(backreferenceName);
} catch (SQLException o_O) {
throw new MappingException(String.format("Could not read value %s from result set!", backreferenceName), o_O);
}
}
private <S> S createInstanceInternal(RelationalPersistentEntity<S> entity, @Nullable Object idValue) {
return createInstance(entity,parameter -> {
String parameterName = parameter.getName();
Assert.notNull(parameterName, "A constructor parameter name must not be null to be used with Spring Data JDBC");
RelationalPersistentProperty property = entity.getRequiredPersistentProperty(parameterName);
return readOrLoadProperty(idValue, property);
});
}
}
}

View File

@@ -0,0 +1,483 @@
/*
* Copyright 2017-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
*
* https://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.NonNull;
import java.sql.JDBCType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.jdbc.core.DataAccessStrategy;
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.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.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;
/**
* The default {@link DataAccessStrategy} is to generate SQL statements based on meta data from the entity.
*
* @author Jens Schauder
* @author Mark Paluch
* @author Thomas Lang
* @author Bastian Wilhelm
*/
public class DefaultDataAccessStrategy implements DataAccessStrategy {
private final @NonNull SqlGeneratorSource sqlGeneratorSource;
private final @NonNull RelationalMappingContext context;
private final @NonNull JdbcConverter converter;
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) {
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 = mappingAccessStrategy == null ? this : mappingAccessStrategy;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
*/
@Override
public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
return insert(instance, domainType, Identifier.from(additionalParameters));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
*/
@Override
public <T> Object insert(T instance, Class<T> domainType, Identifier identifier) {
KeyHolder holder = new GeneratedKeyHolder();
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
MapSqlParameterSource parameterSource = getParameterSource(instance, persistentEntity, "",
PersistentProperty::isIdProperty);
identifier.forEach((name, value, type) -> addConvertedPropertyValue(parameterSource, name, value, type));
Object idValue = getIdValueOrNull(instance, persistentEntity);
if (idValue != null) {
RelationalPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
addConvertedPropertyValue(parameterSource, idProperty, idValue, idProperty.getColumnName());
}
operations.update( //
sql(domainType).getInsert(new HashSet<>(Arrays.asList(parameterSource.getParameterNames()))), //
parameterSource, //
holder //
);
return getIdFromHolder(holder, persistentEntity);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class)
*/
@Override
public <S> boolean update(S instance, Class<S> domainType) {
RelationalPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType);
return operations.update(sql(domainType).getUpdate(),
getParameterSource(instance, persistentEntity, "", Predicates.includeAll())) != 0;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, java.lang.Class)
*/
@Override
public void delete(Object id, Class<?> domainType) {
String deleteByIdSql = sql(domainType).getDeleteById();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
operations.update(deleteByIdSql, parameter);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, org.springframework.data.mapping.PropertyPath)
*/
@Override
public void delete(Object rootId, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
RelationalPersistentEntity<?> rootEntity = context
.getRequiredPersistentEntity(propertyPath.getBaseProperty().getOwner().getType());
RelationalPersistentProperty referencingProperty = propertyPath.getLeafProperty();
Assert.notNull(referencingProperty, "No property found matching the PropertyPath " + propertyPath);
String format = sql(rootEntity.getType()).createDeleteByPath(propertyPath);
HashMap<String, Object> parameters = new HashMap<>();
parameters.put("rootId", rootId);
operations.update(format, parameters);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(java.lang.Class)
*/
@Override
public <T> void deleteAll(Class<T> domainType) {
operations.getJdbcOperations().update(sql(domainType).createDeleteAllSql(null));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(org.springframework.data.mapping.PropertyPath)
*/
@Override
public void deleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
operations.getJdbcOperations()
.update(sql(propertyPath.getBaseProperty().getOwner().getType()).createDeleteAllSql(propertyPath));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#count(java.lang.Class)
*/
@Override
public long count(Class<?> domainType) {
Long result = operations.getJdbcOperations().queryForObject(sql(domainType).getCount(), Long.class);
Assert.notNull(result, "The result of a count query must not be null.");
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findById(java.lang.Object, java.lang.Class)
*/
@SuppressWarnings("unchecked")
@Override
public <T> T findById(Object id, Class<T> domainType) {
String findOneSql = sql(domainType).getFindOne();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
try {
return operations.queryForObject(findOneSql, parameter, (RowMapper<T>) getEntityRowMapper(domainType));
} catch (EmptyResultDataAccessException e) {
return null;
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAll(java.lang.Class)
*/
@SuppressWarnings("unchecked")
@Override
public <T> Iterable<T> findAll(Class<T> domainType) {
return operations.query(sql(domainType).getFindAll(), (RowMapper<T>) getEntityRowMapper(domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllById(java.lang.Iterable, java.lang.Class)
*/
@SuppressWarnings("unchecked")
@Override
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
RelationalPersistentProperty idProperty = getRequiredPersistentEntity(domainType).getRequiredIdProperty();
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
addConvertedPropertyValuesAsList(parameterSource, idProperty, ids, "ids");
String findAllInListSql = sql(domainType).getFindAllInList();
return operations.query(findAllInListSql, parameterSource, (RowMapper<T>) getEntityRowMapper(domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllByProperty(java.lang.Object, org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty)
*/
@Override
@SuppressWarnings("unchecked")
public <T> Iterable<T> findAllByProperty(Object rootId, RelationalPersistentProperty property) {
Assert.notNull(rootId, "rootId must not be null.");
Class<?> actualType = property.getActualType();
String findAllByProperty = sql(actualType) //
.getFindAllByProperty(property.getReverseColumnName(), property.getKeyColumn(), property.isOrdered());
MapSqlParameterSource parameter = new MapSqlParameterSource(property.getReverseColumnName(), rootId);
return operations.query(findAllByProperty, parameter, //
(RowMapper<T>) (property.isMap() //
? this.getMapEntityRowMapper(property) //
: this.getEntityRowMapper(actualType)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#existsById(java.lang.Object, java.lang.Class)
*/
@Override
public <T> boolean existsById(Object id, Class<T> domainType) {
String existsSql = sql(domainType).getExists();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
Boolean result = operations.queryForObject(existsSql, parameter, Boolean.class);
Assert.notNull(result, "The result of an exists query must not be null");
return result;
}
private <S, T> MapSqlParameterSource getParameterSource(S instance, RelationalPersistentEntity<S> persistentEntity,
String prefix, Predicate<RelationalPersistentProperty> skipProperty) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
PersistentPropertyAccessor<S> propertyAccessor = persistentEntity.getPropertyAccessor(instance);
persistentEntity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {
if (skipProperty.test(property)) {
return;
}
if (property.isEntity() && !property.isEmbedded()) {
return;
}
if (property.isEmbedded()) {
Object value = propertyAccessor.getProperty(property);
RelationalPersistentEntity<?> embeddedEntity = context.getPersistentEntity(property.getType());
MapSqlParameterSource additionalParameters = getParameterSource((T) value,
(RelationalPersistentEntity<T>) embeddedEntity, prefix + property.getEmbeddedPrefix(), skipProperty);
parameters.addValues(additionalParameters.getValues());
} else {
Object value = propertyAccessor.getProperty(property);
String paramName = prefix + property.getColumnName();
addConvertedPropertyValue(parameters, property, value, paramName);
}
});
return parameters;
}
@SuppressWarnings("unchecked")
@Nullable
private <S, ID> ID getIdValueOrNull(S instance, RelationalPersistentEntity<S> persistentEntity) {
ID idValue = (ID) persistentEntity.getIdentifierAccessor(instance).getIdentifier();
return isIdPropertyNullOrScalarZero(idValue, persistentEntity) ? null : idValue;
}
private static <S, ID> boolean isIdPropertyNullOrScalarZero(@Nullable ID idValue,
RelationalPersistentEntity<S> persistentEntity) {
RelationalPersistentProperty idProperty = persistentEntity.getIdProperty();
return idValue == null //
|| idProperty == null //
|| (idProperty.getType() == int.class && idValue.equals(0)) //
|| (idProperty.getType() == long.class && idValue.equals(0L));
}
@Nullable
private <S> Object getIdFromHolder(KeyHolder holder, RelationalPersistentEntity<S> persistentEntity) {
try {
// MySQL just returns one value with a special name
return holder.getKey();
} catch (DataRetrievalFailureException | InvalidDataAccessApiUsageException e) {
// Postgres returns a value for each column
// MS SQL Server returns a value that might be null.
Map<String, Object> keys = holder.getKeys();
if (keys == null || persistentEntity.getIdProperty() == null) {
return null;
}
return keys.get(persistentEntity.getIdColumn());
}
}
private EntityRowMapper<?> getEntityRowMapper(Class<?> domainType) {
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), converter, accessStrategy);
}
private RowMapper<?> getMapEntityRowMapper(RelationalPersistentProperty property) {
String keyColumn = property.getKeyColumn();
Assert.notNull(keyColumn, () -> "KeyColumn must not be null for " + property);
return new MapEntityRowMapper<>(getEntityRowMapper(property.getActualType()), keyColumn);
}
private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> domainType) {
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")
private <S> RelationalPersistentEntity<S> getRequiredPersistentEntity(Class<S> domainType) {
return (RelationalPersistentEntity<S>) context.getRequiredPersistentEntity(domainType);
}
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

@@ -0,0 +1,59 @@
/*
* Copyright 2017-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
*
* https://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.ResultSet;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.jdbc.core.RowMapper;
/**
* Maps a {@link ResultSet} to an entity of type {@code T}, including entities referenced. This {@link RowMapper} might
* trigger additional SQL statements in order to load other members of the same aggregate.
*
* @author Jens Schauder
* @author Oliver Gierke
* @author Mark Paluch
* @author Maciej Walkowiak
* @author Bastian Wilhelm
*/
public class EntityRowMapper<T> implements RowMapper<T> {
private final RelationalPersistentEntity<T> entity;
private final JdbcConverter converter;
private final DataAccessStrategy accessStrategy;
public EntityRowMapper(RelationalPersistentEntity<T> entity, JdbcConverter converter,
DataAccessStrategy accessStrategy) {
this.entity = entity;
this.converter = converter;
this.accessStrategy = accessStrategy;
}
/*
* (non-Javadoc)
* @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
*/
@Override
public T mapRow(ResultSet resultSet, int rowNumber) {
return converter.mapRow(entity, accessStrategy, resultSet);
}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
package org.springframework.data.jdbc.core.convert;
import java.util.HashMap;
import java.util.Map;

View File

@@ -15,10 +15,14 @@
*/
package org.springframework.data.jdbc.core.convert;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import java.sql.ResultSet;
/**
* A {@link JdbcConverter} is responsible for converting for values to the native relational representation and vice
* versa.
@@ -38,4 +42,10 @@ public interface JdbcConverter extends RelationalConverter {
* @return The converted value wrapped in a {@link JdbcValue}. Guaranteed to be not {@literal null}.
*/
JdbcValue writeJdbcValue(@Nullable Object value, Class<?> type, int sqlType);
/*
* (non-Javadoc)
* @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
*/
<T> T mapRow(RelationalPersistentEntity<T> entity, DataAccessStrategy accessStrategy, ResultSet resultSet);
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
package org.springframework.data.jdbc.core.convert;
import java.sql.ResultSet;
import java.sql.SQLException;

View File

@@ -13,8 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
package org.springframework.data.jdbc.core.convert;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.sql.Column;
import org.springframework.data.relational.core.sql.SQL;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
package org.springframework.data.jdbc.core.convert;
import lombok.Value;
@@ -34,27 +34,11 @@ import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
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.core.sql.AssignValue;
import org.springframework.data.relational.core.sql.Assignments;
import org.springframework.data.relational.core.sql.BindMarker;
import org.springframework.data.relational.core.sql.Column;
import org.springframework.data.relational.core.sql.Condition;
import org.springframework.data.relational.core.sql.Delete;
import org.springframework.data.relational.core.sql.DeleteBuilder;
import org.springframework.data.relational.core.sql.Expression;
import org.springframework.data.relational.core.sql.Expressions;
import org.springframework.data.relational.core.sql.Functions;
import org.springframework.data.relational.core.sql.Insert;
import org.springframework.data.relational.core.sql.InsertBuilder;
import org.springframework.data.relational.core.sql.SQL;
import org.springframework.data.relational.core.sql.Select;
import org.springframework.data.relational.core.sql.SelectBuilder;
import org.springframework.data.relational.core.sql.StatementBuilder;
import org.springframework.data.relational.core.sql.Table;
import org.springframework.data.relational.core.sql.Update;
import org.springframework.data.relational.core.sql.*;
import org.springframework.data.relational.core.sql.render.SqlRenderer;
import org.springframework.data.util.Lazy;
import org.springframework.lang.Nullable;
@@ -105,6 +89,48 @@ class SqlGenerator {
this.columns = new Columns(entity, mappingContext);
}
/**
* Construct a IN-condition based on a {@link Select Sub-Select} which selects the ids (or stand ins for ids) of the
* given {@literal path} to those that reference the root entities specified by the {@literal rootCondition}.
*
* @param path specifies the table and id to select
* @param rootCondition the condition on the root of the path determining what to select
* @param filterColumn the column to apply the IN-condition to.
* @return the IN condition
*/
private static Condition getSubselectCondition(PersistentPropertyPathExtension path,
Function<Column, Condition> rootCondition, Column filterColumn) {
PersistentPropertyPathExtension parentPath = path.getParentPath();
if (!parentPath.hasIdProperty()) {
if (parentPath.getLength() > 1) {
return getSubselectCondition(parentPath, rootCondition, filterColumn);
}
return rootCondition.apply(filterColumn);
}
Table subSelectTable = SQL.table(parentPath.getTableName());
Column idColumn = subSelectTable.column(parentPath.getIdColumnName());
Column selectFilterColumn = subSelectTable.column(parentPath.getEffectiveIdColumnName());
Condition innerCondition = parentPath.getLength() == 1 // if the parent is the root of the path
? rootCondition.apply(selectFilterColumn) // apply the rootCondition
: getSubselectCondition(parentPath, rootCondition, selectFilterColumn); // otherwise we need another layer of
// subselect
Select select = Select.builder() //
.select(idColumn) //
.from(subSelectTable) //
.where(innerCondition).build();
return filterColumn.in(select);
}
private static BindMarker getBindMarker(String columnName) {
return SQL.bindMarker(":" + parameterPattern.matcher(columnName).replaceAll(""));
}
/**
* Returns a query for selecting all simple properties of an entity, including those for one-to-one relationships.
* Results are filtered using an {@code IN}-clause on the id column.
@@ -467,34 +493,6 @@ class SqlGenerator {
return render(delete);
}
/**
* Construct a {@link Select Sub-Select}.
*
* @param path
* @param rootCondition
* @param filterColumn
* @return
*/
private static Condition getSubselectCondition(PersistentPropertyPathExtension path,
Function<Column, Condition> rootCondition, Column filterColumn) {
PersistentPropertyPathExtension parentPath = path.getParentPath();
Table subSelectTable = SQL.table(parentPath.getTableName());
Column idColumn = subSelectTable.column(parentPath.getIdColumnName());
Column selectFilterColumn = subSelectTable.column(parentPath.getEffectiveIdColumnName());
Condition innerCondition = parentPath.getLength() == 1 ? rootCondition.apply(selectFilterColumn)
: getSubselectCondition(parentPath, rootCondition, selectFilterColumn);
Select select = Select.builder() //
.select(idColumn) //
.from(subSelectTable) //
.where(innerCondition).build();
return filterColumn.in(select);
}
private String createDeleteByListSql() {
Table table = getTable();
@@ -531,10 +529,6 @@ class SqlGenerator {
return sqlContext.getIdColumn();
}
private static BindMarker getBindMarker(String columnName) {
return SQL.bindMarker(":" + parameterPattern.matcher(columnName).replaceAll(""));
}
/**
* Value object representing a {@code JOIN} association.
*/

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
package org.springframework.data.jdbc.core.convert;
import lombok.RequiredArgsConstructor;

View File

@@ -22,13 +22,12 @@ import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.data.jdbc.core.CascadingDataAccessStrategy;
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.DefaultDataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@@ -102,7 +101,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
* <p>
* Use a {@link SqlSessionTemplate} for {@link SqlSession} or a similar implementation tying the session to the proper
* 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
* functionality of the {@link DefaultDataAccessStrategy} which one normally still
* wants. Use
* {@link #createCombinedAccessStrategy(RelationalMappingContext, JdbcConverter, NamedParameterJdbcOperations, SqlSession, NamespaceStrategy)}
* to create such a {@link DataAccessStrategy}.

View File

@@ -22,13 +22,13 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.jdbc.core.DataAccessStrategy;
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.DefaultDataAccessStrategy;
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.convert.SqlGeneratorSource;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.mapping.NamingStrategy;
@@ -79,8 +79,9 @@ public abstract class AbstractJdbcConfiguration {
/**
* Register custom {@link Converter}s in a {@link JdbcCustomConversions} object if required. These
* {@link JdbcCustomConversions} will be registered with the {@link #jdbcConverter(RelationalMappingContext, JdbcOperations)}.
* Returns an empty {@link JdbcCustomConversions} instance by default.
* {@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}.
*/

View File

@@ -22,14 +22,14 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
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.DefaultDataAccessStrategy;
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.convert.SqlGeneratorSource;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.mapping.NamingStrategy;

View File

@@ -19,10 +19,10 @@ import java.lang.reflect.Method;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.EntityRowMapper;
import org.springframework.data.jdbc.core.convert.EntityRowMapper;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
import org.springframework.data.projection.ProjectionFactory;
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.repository.core.NamedQueries;
@@ -47,7 +47,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
private final ApplicationEventPublisher publisher;
private final RelationalMappingContext context;
private final RelationalConverter converter;
private final JdbcConverter converter;
private final DataAccessStrategy accessStrategy;
private final QueryMappingConfiguration queryMappingConfiguration;
private final NamedParameterJdbcOperations operations;
@@ -63,8 +63,8 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
* @param queryMappingConfiguration must not be {@literal null}.
*/
JdbcQueryLookupStrategy(ApplicationEventPublisher publisher, RelationalMappingContext context,
RelationalConverter converter, DataAccessStrategy accessStrategy,
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations) {
JdbcConverter converter, DataAccessStrategy accessStrategy, QueryMappingConfiguration queryMappingConfiguration,
NamedParameterJdbcOperations operations) {
Assert.notNull(publisher, "Publisher must not be null!");
Assert.notNull(context, "RelationalMappingContext must not be null!");
@@ -118,7 +118,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
EntityRowMapper<?> defaultEntityRowMapper = new EntityRowMapper<>( //
context.getRequiredPersistentEntity(domainType), //
context, //
//
converter, //
accessStrategy);

View File

@@ -20,9 +20,9 @@ import java.util.Optional;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
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.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.repository.core.EntityInformation;
@@ -47,7 +47,7 @@ import org.springframework.util.Assert;
public class JdbcRepositoryFactory extends RepositoryFactorySupport {
private final RelationalMappingContext context;
private final RelationalConverter converter;
private final JdbcConverter converter;
private final ApplicationEventPublisher publisher;
private final DataAccessStrategy accessStrategy;
private final NamedParameterJdbcOperations operations;
@@ -57,15 +57,14 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
/**
* Creates a new {@link JdbcRepositoryFactory} for the given {@link DataAccessStrategy},
* {@link RelationalMappingContext} and {@link ApplicationEventPublisher}.
*
* @param dataAccessStrategy must not be {@literal null}.
* @param dataAccessStrategy must not be {@literal null}.
* @param context must not be {@literal null}.
* @param converter must not be {@literal null}.
* @param publisher must not be {@literal null}.
* @param operations must not be {@literal null}.
*/
public JdbcRepositoryFactory(DataAccessStrategy dataAccessStrategy, RelationalMappingContext context,
RelationalConverter converter, ApplicationEventPublisher publisher, NamedParameterJdbcOperations operations) {
JdbcConverter converter, ApplicationEventPublisher publisher, NamedParameterJdbcOperations operations) {
Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null!");
Assert.notNull(context, "RelationalMappingContext must not be null!");

View File

@@ -22,9 +22,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
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.DefaultDataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
import org.springframework.data.jdbc.repository.RowMapperMap;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;

View File

@@ -25,9 +25,7 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.relational.core.conversion.DbAction.Insert;
import org.springframework.data.relational.core.conversion.DbAction.InsertRoot;
import org.springframework.data.relational.core.mapping.NamingStrategy;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
/**
@@ -38,14 +36,9 @@ import org.springframework.data.relational.domain.Identifier;
public class DefaultJdbcInterpreterUnitTests {
static final long CONTAINER_ID = 23L;
static final String BACK_REFERENCE = "back-reference";
static final String BACK_REFERENCE = "container";
RelationalMappingContext context = new JdbcMappingContext(new NamingStrategy() {
@Override
public String getReverseColumnName(RelationalPersistentProperty property) {
return BACK_REFERENCE;
}
});
RelationalMappingContext context = new JdbcMappingContext();
DataAccessStrategy dataAccessStrategy = mock(DataAccessStrategy.class);
DefaultJdbcInterpreter interpreter = new DefaultJdbcInterpreter(context, dataAccessStrategy);
@@ -54,8 +47,10 @@ public class DefaultJdbcInterpreterUnitTests {
Element element = new Element();
InsertRoot<Container> containerInsert = new InsertRoot<>(container);
Insert<?> insert = new Insert<>(element, PropertyPathTestingUtils.toPath("element", Container.class, context),
Insert<?> elementInsert = new Insert<>(element, PropertyPathTestingUtils.toPath("element", Container.class, context),
containerInsert);
Insert<?> element1Insert = new Insert<>(element, PropertyPathTestingUtils.toPath("element.element1", Container.class, context),
elementInsert);
@Test // DATAJDBC-145
public void insertDoesHonourNamingStrategyForBackReference() {
@@ -63,7 +58,7 @@ public class DefaultJdbcInterpreterUnitTests {
container.id = CONTAINER_ID;
containerInsert.setGeneratedId(CONTAINER_ID);
interpreter.interpret(insert);
interpreter.interpret(elementInsert);
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
@@ -78,7 +73,7 @@ public class DefaultJdbcInterpreterUnitTests {
container.id = CONTAINER_ID;
interpreter.interpret(insert);
interpreter.interpret(elementInsert);
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
@@ -93,7 +88,22 @@ public class DefaultJdbcInterpreterUnitTests {
containerInsert.setGeneratedId(CONTAINER_ID);
interpreter.interpret(insert);
interpreter.interpret(elementInsert);
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
assertThat(argumentCaptor.getValue().getParts()) //
.extracting("name", "value", "targetType") //
.containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class));
}
@Test // DATAJDBC-359
public void generatedIdOfParentsParentGetsPassedOnAsAdditionalParameter() {
containerInsert.setGeneratedId(CONTAINER_ID);
interpreter.interpret(element1Insert);
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
@@ -111,5 +121,11 @@ public class DefaultJdbcInterpreterUnitTests {
Element element;
}
static class Element {}
@SuppressWarnings("unused")
static class Element {
Element1 element1;
}
static class Element1 {
}
}

View File

@@ -20,8 +20,6 @@ import static org.assertj.core.api.Assertions.*;
import lombok.Data;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@@ -45,8 +43,6 @@ import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.test.annotation.IfProfileValue;
import org.springframework.test.annotation.ProfileValueSourceConfiguration;
@@ -71,8 +67,7 @@ public class JdbcAggregateTemplateIntegrationTests {
@ClassRule public static final SpringClassRule classRule = new SpringClassRule();
@Rule public SpringMethodRule methodRule = new SpringMethodRule();
@Autowired JdbcAggregateOperations template;
@Autowired
NamedParameterJdbcOperations jdbcTemplate;
@Autowired NamedParameterJdbcOperations jdbcTemplate;
LegoSet legoSet = createLegoSet();
@Test // DATAJDBC-112
@@ -465,8 +460,39 @@ public class JdbcAggregateTemplateIntegrationTests {
template.delete(chain4, Chain4.class);
String countSelect = "SELECT COUNT(*) FROM %s";
jdbcTemplate.queryForObject(String.format(countSelect, "CHAIN0"),emptyMap(), Long.class);
assertThat(jdbcTemplate.queryForObject("SELECT COUNT(*) FROM CHAIN0", emptyMap(), Long.class)) //
.isEqualTo(0);
}
@Test // DATAJDBC-359
public void saveAndLoadLongChainWithoutIds() {
NoIdChain4 chain4 = new NoIdChain4();
chain4.fourValue = "omega";
chain4.chain3 = new NoIdChain3();
chain4.chain3.threeValue = "delta";
chain4.chain3.chain2 = new NoIdChain2();
chain4.chain3.chain2.twoValue = "gamma";
chain4.chain3.chain2.chain1 = new NoIdChain1();
chain4.chain3.chain2.chain1.oneValue = "beta";
chain4.chain3.chain2.chain1.chain0 = new NoIdChain0();
chain4.chain3.chain2.chain1.chain0.zeroValue = "alpha";
template.save(chain4);
assertThat(chain4.four).isNotNull();
NoIdChain4 reloaded = template.findById(chain4.four, NoIdChain4.class);
assertThat(reloaded).isNotNull();
assertThat(reloaded.four).isEqualTo(chain4.four);
assertThat(reloaded.chain3.chain2.chain1.chain0.zeroValue).isEqualTo(chain4.chain3.chain2.chain1.chain0.zeroValue);
template.delete(chain4, NoIdChain4.class);
assertThat(jdbcTemplate.queryForObject("SELECT COUNT(*) FROM CHAIN0", emptyMap(), Long.class)) //
.isEqualTo(0);
}
private static void assumeNot(String dbProfileName) {
@@ -573,6 +599,9 @@ public class JdbcAggregateTemplateIntegrationTests {
}
}
/**
* One may think of ChainN as a chain with N further elements
*/
static class Chain0 {
@Id Long zero;
String zeroValue;
@@ -601,4 +630,32 @@ public class JdbcAggregateTemplateIntegrationTests {
String fourValue;
Chain3 chain3;
}
/**
* One may think of ChainN as a chain with N further elements
*/
static class NoIdChain0 {
String zeroValue;
}
static class NoIdChain1 {
String oneValue;
NoIdChain0 chain0;
}
static class NoIdChain2 {
String twoValue;
NoIdChain1 chain1;
}
static class NoIdChain3 {
String threeValue;
NoIdChain2 chain2;
}
static class NoIdChain4 {
@Id Long four;
String fourValue;
NoIdChain3 chain3;
}
}

View File

@@ -17,15 +17,18 @@ package org.springframework.data.jdbc.core;
import java.util.List;
import org.assertj.core.api.SoftAssertions;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.Embedded;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import static org.assertj.core.api.SoftAssertions.*;
/**
* @author Jens Schauder
*/
@@ -37,23 +40,23 @@ public class PersistentPropertyPathExtensionUnitTests {
@Test
public void isEmbedded() {
SoftAssertions.assertSoftly(softly -> {
assertSoftly(softly -> {
softly.assertThat(extPath(entity).isEmbedded()).isFalse();
softly.assertThat(extPath("second").isEmbedded()).isFalse();
softly.assertThat(extPath("second.third").isEmbedded()).isTrue();
softly.assertThat(extPath("second.third2").isEmbedded()).isTrue();
});
}
@Test
public void isMultiValued() {
SoftAssertions.assertSoftly(softly -> {
assertSoftly(softly -> {
softly.assertThat(extPath(entity).isMultiValued()).isFalse();
softly.assertThat(extPath("second").isMultiValued()).isFalse();
softly.assertThat(extPath("second.third").isMultiValued()).isFalse();
softly.assertThat(extPath("secondList.third").isMultiValued()).isTrue();
softly.assertThat(extPath("second.third2").isMultiValued()).isFalse();
softly.assertThat(extPath("secondList.third2").isMultiValued()).isTrue();
softly.assertThat(extPath("secondList").isMultiValued()).isTrue();
});
}
@@ -64,12 +67,12 @@ public class PersistentPropertyPathExtensionUnitTests {
RelationalPersistentEntity<?> second = context.getRequiredPersistentEntity(Second.class);
RelationalPersistentEntity<?> third = context.getRequiredPersistentEntity(Third.class);
SoftAssertions.assertSoftly(softly -> {
assertSoftly(softly -> {
softly.assertThat(extPath(entity).getLeafEntity()).isEqualTo(entity);
softly.assertThat(extPath("second").getLeafEntity()).isEqualTo(second);
softly.assertThat(extPath("second.third").getLeafEntity()).isEqualTo(third);
softly.assertThat(extPath("secondList.third").getLeafEntity()).isEqualTo(third);
softly.assertThat(extPath("second.third2").getLeafEntity()).isEqualTo(third);
softly.assertThat(extPath("secondList.third2").getLeafEntity()).isEqualTo(third);
softly.assertThat(extPath("secondList").getLeafEntity()).isEqualTo(second);
});
}
@@ -77,15 +80,15 @@ public class PersistentPropertyPathExtensionUnitTests {
@Test
public void isEntity() {
SoftAssertions.assertSoftly(softly -> {
assertSoftly(softly -> {
softly.assertThat(extPath(entity).isEntity()).isTrue();
String path = "second";
softly.assertThat(extPath(path).isEntity()).isTrue();
softly.assertThat(extPath("second.third").isEntity()).isTrue();
softly.assertThat(extPath("second.third.value").isEntity()).isFalse();
softly.assertThat(extPath("secondList.third").isEntity()).isTrue();
softly.assertThat(extPath("secondList.third.value").isEntity()).isFalse();
softly.assertThat(extPath("second.third2").isEntity()).isTrue();
softly.assertThat(extPath("second.third2.value").isEntity()).isFalse();
softly.assertThat(extPath("secondList.third2").isEntity()).isTrue();
softly.assertThat(extPath("secondList.third2.value").isEntity()).isFalse();
softly.assertThat(extPath("secondList").isEntity()).isTrue();
});
}
@@ -93,14 +96,14 @@ public class PersistentPropertyPathExtensionUnitTests {
@Test
public void getTableName() {
SoftAssertions.assertSoftly(softly -> {
assertSoftly(softly -> {
softly.assertThat(extPath(entity).getTableName()).isEqualTo("dummy_entity");
softly.assertThat(extPath("second").getTableName()).isEqualTo("second");
softly.assertThat(extPath("second.third").getTableName()).isEqualTo("second");
softly.assertThat(extPath("second.third.value").getTableName()).isEqualTo("second");
softly.assertThat(extPath("secondList.third").getTableName()).isEqualTo("second");
softly.assertThat(extPath("secondList.third.value").getTableName()).isEqualTo("second");
softly.assertThat(extPath("second.third2").getTableName()).isEqualTo("second");
softly.assertThat(extPath("second.third2.value").getTableName()).isEqualTo("second");
softly.assertThat(extPath("secondList.third2").getTableName()).isEqualTo("second");
softly.assertThat(extPath("secondList.third2.value").getTableName()).isEqualTo("second");
softly.assertThat(extPath("secondList").getTableName()).isEqualTo("second");
});
}
@@ -108,34 +111,91 @@ public class PersistentPropertyPathExtensionUnitTests {
@Test
public void getTableAlias() {
SoftAssertions.assertSoftly(softly -> {
assertSoftly(softly -> {
softly.assertThat(extPath(entity).getTableAlias()).isEqualTo(null);
softly.assertThat(extPath("second").getTableAlias()).isEqualTo("second");
softly.assertThat(extPath("second.third").getTableAlias()).isEqualTo("second");
softly.assertThat(extPath("second.third.value").getTableAlias()).isEqualTo("second");
softly.assertThat(extPath("second.third2").getTableAlias()).isEqualTo("second_third2");
softly.assertThat(extPath("second.third2.value").getTableAlias()).isEqualTo("second_third2");
softly.assertThat(extPath("secondList.third").getTableAlias()).isEqualTo("secondList");
softly.assertThat(extPath("secondList.third.value").getTableAlias()).isEqualTo("secondList");
softly.assertThat(extPath("secondList.third2").getTableAlias()).isEqualTo("secondList_third2");
softly.assertThat(extPath("secondList.third2.value").getTableAlias()).isEqualTo("secondList_third2");
softly.assertThat(extPath("second.third2").getTableAlias()).isEqualTo("second");
softly.assertThat(extPath("second.third2.value").getTableAlias()).isEqualTo("second");
softly.assertThat(extPath("second.third").getTableAlias()).isEqualTo("second_third");
softly.assertThat(extPath("second.third.value").getTableAlias()).isEqualTo("second_third");
softly.assertThat(extPath("secondList.third2").getTableAlias()).isEqualTo("secondList");
softly.assertThat(extPath("secondList.third2.value").getTableAlias()).isEqualTo("secondList");
softly.assertThat(extPath("secondList.third").getTableAlias()).isEqualTo("secondList_third");
softly.assertThat(extPath("secondList.third.value").getTableAlias()).isEqualTo("secondList_third");
softly.assertThat(extPath("secondList").getTableAlias()).isEqualTo("secondList");
softly.assertThat(extPath("second2.third2").getTableAlias()).isEqualTo("secthird2");
softly.assertThat(extPath("second2.third").getTableAlias()).isEqualTo("secthird");
});
}
@Test
public void getColumnName() {
SoftAssertions.assertSoftly(softly -> {
assertSoftly(softly -> {
softly.assertThat(extPath("second.third.value").getColumnName()).isEqualTo("thrdvalue");
softly.assertThat(extPath("second.third2.value").getColumnName()).isEqualTo("value");
softly.assertThat(extPath("secondList.third.value").getColumnName()).isEqualTo("thrdvalue");
softly.assertThat(extPath("secondList.third2.value").getColumnName()).isEqualTo("value");
softly.assertThat(extPath("second2.third.value").getColumnName()).isEqualTo("secthrdvalue");
softly.assertThat(extPath("second2.third2.value").getColumnName()).isEqualTo("value");
softly.assertThat(extPath("second.third2.value").getColumnName()).isEqualTo("thrdvalue");
softly.assertThat(extPath("second.third.value").getColumnName()).isEqualTo("value");
softly.assertThat(extPath("secondList.third2.value").getColumnName()).isEqualTo("thrdvalue");
softly.assertThat(extPath("secondList.third.value").getColumnName()).isEqualTo("value");
softly.assertThat(extPath("second2.third2.value").getColumnName()).isEqualTo("secthrdvalue");
softly.assertThat(extPath("second2.third.value").getColumnName()).isEqualTo("value");
});
}
@Test // DATAJDBC-359
public void idDefiningPath() {
assertSoftly(softly -> {
softly.assertThat(extPath("second.third2.value").getIdDefiningParentPath().getLength()).isEqualTo(0);
softly.assertThat(extPath("second.third.value").getIdDefiningParentPath().getLength()).isEqualTo(0);
softly.assertThat(extPath("secondList.third2.value").getIdDefiningParentPath().getLength()).isEqualTo(0);
softly.assertThat(extPath("secondList.third.value").getIdDefiningParentPath().getLength()).isEqualTo(0);
softly.assertThat(extPath("second2.third2.value").getIdDefiningParentPath().getLength()).isEqualTo(0);
softly.assertThat(extPath("second2.third.value").getIdDefiningParentPath().getLength()).isEqualTo(0);
softly.assertThat(extPath("withId.second.third2.value").getIdDefiningParentPath().getLength()).isEqualTo(1);
softly.assertThat(extPath("withId.second.third.value").getIdDefiningParentPath().getLength()).isEqualTo(1);
});
}
@Test // DATAJDBC-359
public void reverseColumnName() {
assertSoftly(softly -> {
softly.assertThat(extPath("second.third2").getReverseColumnName()).isEqualTo("dummy_entity");
softly.assertThat(extPath("second.third").getReverseColumnName()).isEqualTo("dummy_entity");
softly.assertThat(extPath("secondList.third2").getReverseColumnName()).isEqualTo("dummy_entity");
softly.assertThat(extPath("secondList.third").getReverseColumnName()).isEqualTo("dummy_entity");
softly.assertThat(extPath("second2.third2").getReverseColumnName()).isEqualTo("dummy_entity");
softly.assertThat(extPath("second2.third").getReverseColumnName()).isEqualTo("dummy_entity");
softly.assertThat(extPath("withId.second.third2.value").getReverseColumnName()).isEqualTo("with_id");
softly.assertThat(extPath("withId.second.third").getReverseColumnName()).isEqualTo("with_id");
softly.assertThat(extPath("withId.second2.third").getReverseColumnName()).isEqualTo("with_id");
});
}
@Test // DATAJDBC-359
public void getRequiredIdProperty() {
assertSoftly(softly -> {
softly.assertThat(extPath(entity).getRequiredIdProperty().getName()).isEqualTo("entityId");
softly.assertThat(extPath("withId").getRequiredIdProperty().getName()).isEqualTo("withIdId");
softly.assertThatThrownBy(() -> extPath("second").getRequiredIdProperty())
.isInstanceOf(IllegalStateException.class);
});
}
@Test // DATAJDBC-359
public void extendBy() {
assertSoftly(softly -> {
softly.assertThat(extPath(entity).extendBy(entity.getRequiredPersistentProperty("withId")))
.isEqualTo(extPath("withId"));
softly.assertThat(extPath("withId").extendBy(extPath("withId").getRequiredIdProperty()))
.isEqualTo(extPath("withId.withIdId"));
});
}
@@ -155,15 +215,17 @@ public class PersistentPropertyPathExtensionUnitTests {
@SuppressWarnings("unused")
static class DummyEntity {
@Id Long entityId;
Second second;
List<Second> secondList;
@Embedded("sec") Second second2;
List<Second> secondList;
WithId withId;
}
@SuppressWarnings("unused")
static class Second {
@Embedded("thrd") Third third;
Third third2;
Third third;
@Embedded("thrd") Third third2;
}
@SuppressWarnings("unused")
@@ -171,4 +233,11 @@ public class PersistentPropertyPathExtensionUnitTests {
String value;
}
@SuppressWarnings("unused")
static class WithId {
@Id Long withIdId;
Second second;
@Embedded("sec") Second second2;
}
}

View File

@@ -29,10 +29,10 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProp
* @author Jens Schauder
*/
@UtilityClass
class PropertyPathTestingUtils {
public class PropertyPathTestingUtils {
static PersistentPropertyPath<RelationalPersistentProperty> toPath(String path, Class source,
RelationalMappingContext context) {
public static PersistentPropertyPath<RelationalPersistentProperty> toPath(String path, Class source,
RelationalMappingContext context) {
PersistentPropertyPaths<?, RelationalPersistentProperty> persistentPropertyPaths = context
.findPersistentPropertyPaths(source, p -> true);

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
package org.springframework.data.jdbc.core.convert;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
@@ -36,6 +36,7 @@ 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.convert.SqlGeneratorSource;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.jdbc.core.JdbcOperations;

View File

@@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
package org.springframework.data.jdbc.core.convert;
import static java.util.Arrays.*;
import static java.util.Collections.*;
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;
@@ -34,18 +34,18 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import javax.naming.OperationNotSupportedException;
import org.assertj.core.api.SoftAssertions;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
import org.springframework.data.jdbc.core.DataAccessStrategy;
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.Embedded;
import org.springframework.data.relational.core.mapping.NamingStrategy;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@@ -213,7 +213,7 @@ public class EntityRowMapperUnitTests {
@Test // DATAJDBC-252
public void doesNotTryToSetPropertiesThatAreSetViaConstructor() throws SQLException {
ResultSet rs = mockResultSet(asList("value"), //
ResultSet rs = mockResultSet(singletonList("value"), //
"value-from-resultSet");
rs.next();
@@ -240,7 +240,7 @@ public class EntityRowMapperUnitTests {
@Test // DATAJDBC-273
public void handlesNonSimplePropertyInConstructor() throws SQLException {
ResultSet rs = mockResultSet(asList("id"), //
ResultSet rs = mockResultSet(singletonList("id"), //
ID_FOR_ENTITY_REFERENCING_LIST);
rs.next();
@@ -249,6 +249,46 @@ public class EntityRowMapperUnitTests {
assertThat(extracted.content).hasSize(2);
}
@Test // DATAJDBC-359
public void chainedEntitiesWithoutId() throws SQLException {
// @formatter:off
Fixture<NoIdChain4> fixture = this.<NoIdChain4> buildFixture() //
// Id of the aggregate root and backreference to it from
// the various aggregate members.
.value(4L).inColumns("four", //
"chain3_no_id_chain4", //
"chain3_chain2_no_id_chain4", //
"chain3_chain2_chain1_no_id_chain4", //
"chain3_chain2_chain1_chain0_no_id_chain4") //
.endUpIn(e -> e.four)
// values for the different entities
.value("four_value").inColumns("four_value").endUpIn(e -> e.fourValue) //
.value("three_value").inColumns("chain3_three_value").endUpIn(e -> e.chain3.threeValue) //
.value("two_value").inColumns("chain3_chain2_two_value").endUpIn(e -> e.chain3.chain2.twoValue) //
.value("one_value").inColumns("chain3_chain2_chain1_one_value").endUpIn(e -> e.chain3.chain2.chain1.oneValue) //
.value("zero_value").inColumns("chain3_chain2_chain1_chain0_zero_value")
.endUpIn(e -> e.chain3.chain2.chain1.chain0.zeroValue) //
.build();
// @formatter:on
ResultSet rs = fixture.resultSet;
rs.next();
NoIdChain4 extracted = createRowMapper(NoIdChain4.class).mapRow(rs, 1);
fixture.assertOn(extracted);
}
private <T> FixtureBuilder<T> buildFixture() {
return new FixtureBuilder<>();
}
private <T> EntityRowMapper<T> createRowMapper(Class<T> type) {
return createRowMapper(type, NamingStrategy.INSTANCE);
}
@@ -275,11 +315,11 @@ public class EntityRowMapperUnitTests {
))).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_REFERENCING_LIST),
any(RelationalPersistentProperty.class));
RelationalConverter converter = new BasicRelationalConverter(context, new JdbcCustomConversions());
JdbcConverter converter = new BasicJdbcConverter(context, new JdbcCustomConversions());
return new EntityRowMapper<>( //
(RelationalPersistentEntity<T>) context.getRequiredPersistentEntity(type), //
context, //
//
converter, //
accessStrategy //
);
@@ -335,27 +375,22 @@ public class EntityRowMapperUnitTests {
switch (invocation.getMethod().getName()) {
case "next":
return next();
case "getObject":
return getObject(invocation.getArgument(0));
case "isAfterLast":
return isAfterLast();
case "isBeforeFirst":
return isBeforeFirst();
case "getRow":
return isAfterLast() || isBeforeFirst() ? 0 : index + 1;
case "toString":
return this.toString();
default:
throw new OperationNotSupportedException(invocation.getMethod().getName());
}
if (invocation.getMethod().getName().equals("next"))
return next();
if (invocation.getMethod().getName().equals("getObject"))
return getObject(invocation.getArgument(0));
if (invocation.getMethod().getName().equals("isAfterLast"))
return isAfterLast();
if (invocation.getMethod().getName().equals("isBeforeFirst"))
return isBeforeFirst();
if (invocation.getMethod().getName().equals("getRow"))
return isAfterLast() || isBeforeFirst() ? 0 : index + 1;
if (invocation.getMethod().getName().equals("toString"))
return this.toString();
throw new OperationNotSupportedException(invocation.getMethod().getName());
}
private boolean isAfterLast() {
@@ -481,4 +516,121 @@ public class EntityRowMapperUnitTests {
final List<Trivial> content;
}
static class NoIdChain0 {
String zeroValue;
}
static class NoIdChain1 {
String oneValue;
NoIdChain0 chain0;
}
static class NoIdChain2 {
String twoValue;
NoIdChain1 chain1;
}
static class NoIdChain3 {
String threeValue;
NoIdChain2 chain2;
}
static class NoIdChain4 {
@Id Long four;
String fourValue;
NoIdChain3 chain3;
}
private interface SetValue<T> {
SetColumns<T> value(Object value);
Fixture<T> build();
}
private interface SetColumns<T> {
SetExpectation<T> inColumns(String... columns);
}
private interface SetExpectation<T> {
SetValue<T> endUpIn(Function<T, Object> extractor);
}
private static class FixtureBuilder<T> implements SetValue<T>, SetColumns<T>, SetExpectation<T> {
private List<Object> values = new ArrayList<>();
private List<String> columns = new ArrayList<>();
private String explainingColumn;
private List<Expectation<T>> expectations = new ArrayList<>();
@Override
public SetColumns<T> value(Object value) {
values.add(value);
return this;
}
@Override
public SetExpectation<T> inColumns(String... columns) {
boolean isFirst = true;
for (String column : columns) {
// if more than one column is mentioned, we need to copy the value for all but the first column;
if (!isFirst) {
values.add(values.get(values.size() - 1));
} else {
explainingColumn = column;
isFirst = false;
}
this.columns.add(column);
}
return this;
}
@Override
public Fixture<T> build() {
return new Fixture<>(mockResultSet(columns, values.toArray()), expectations);
}
@Override
public SetValue<T> endUpIn(Function<T, Object> extractor) {
expectations.add(new Expectation<T>(extractor, values.get(values.size() - 1), explainingColumn));
return this;
}
}
@AllArgsConstructor
private static class Fixture<T> {
final ResultSet resultSet;
final List<Expectation<T>> expectations;
public void assertOn(T result) {
SoftAssertions.assertSoftly(softly -> {
expectations.forEach(expectation -> {
softly.assertThat(expectation.extractor.apply(result)).describedAs("From column: " + expectation.sourceColumn)
.isEqualTo(expectation.expectedValue);
});
});
}
}
@AllArgsConstructor
private static class Expectation<T> {
final Function<T, Object> extractor;
final Object expectedValue;
final String sourceColumn;
}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
package org.springframework.data.jdbc.core.convert;
import static java.util.Arrays.*;
import static java.util.Collections.*;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
package org.springframework.data.jdbc.core.convert;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.jdbc.core.PropertyPathTestingUtils.*;
@@ -22,13 +22,11 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.convert.JdbcIdentifierBuilder;
import org.springframework.data.jdbc.core.JdbcIdentifierBuilder;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.domain.Identifier;
/**
@@ -55,7 +53,7 @@ public class JdbcIdentifierBuilderUnitTests {
@Test // DATAJDBC-326
public void qualifiersForMaps() {
PersistentPropertyPath<RelationalPersistentProperty> path = getPath("children");
PersistentPropertyPathExtension path = getPath("children");
Identifier identifier = JdbcIdentifierBuilder //
.forBackReferences(path, "parent-eins") //
@@ -73,7 +71,7 @@ public class JdbcIdentifierBuilderUnitTests {
@Test // DATAJDBC-326
public void qualifiersForLists() {
PersistentPropertyPath<RelationalPersistentProperty> path = getPath("moreChildren");
PersistentPropertyPathExtension path = getPath("moreChildren");
Identifier identifier = JdbcIdentifierBuilder //
.forBackReferences(path, "parent-eins") //
@@ -98,13 +96,26 @@ public class JdbcIdentifierBuilderUnitTests {
assertThat(identifier.getParts()) //
.extracting("name", "value", "targetType") //
.containsExactly( //
tuple("embeddable", "parent-eins", UUID.class) //
tuple("dummy_entity", "parent-eins", UUID.class) //
);
}
@NotNull
private PersistentPropertyPath<RelationalPersistentProperty> getPath(String dotPath) {
return toPath(dotPath, DummyEntity.class, context);
@Test // DATAJDBC-326
public void backreferenceAcrossNoId() {
Identifier identifier = JdbcIdentifierBuilder //
.forBackReferences(getPath("noId.child"), "parent-eins") //
.build();
assertThat(identifier.getParts()) //
.extracting("name", "value", "targetType") //
.containsExactly( //
tuple("dummy_entity", "parent-eins", UUID.class) //
);
}
private PersistentPropertyPathExtension getPath(String dotPath) {
return new PersistentPropertyPathExtension(context, toPath(dotPath, DummyEntity.class, context));
}
@SuppressWarnings("unused")
@@ -120,6 +131,8 @@ public class JdbcIdentifierBuilderUnitTests {
List<Child> moreChildren;
Embeddable embeddable;
NoId noId;
}
@SuppressWarnings("unused")
@@ -127,5 +140,10 @@ public class JdbcIdentifierBuilderUnitTests {
Child child;
}
@SuppressWarnings("unused")
static class NoId {
Child child;
}
static class Child {}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
package org.springframework.data.jdbc.core.convert;
import static org.assertj.core.api.Assertions.*;
@@ -25,6 +25,7 @@ import java.util.function.Consumer;
import org.assertj.core.api.SoftAssertions;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.convert.SqlGenerator;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils;
import org.springframework.data.mapping.PersistentPropertyPath;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
package org.springframework.data.jdbc.core.convert;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
@@ -23,9 +23,11 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.PropertyPathTestingUtils;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Embedded;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.sql.Aliased;
@@ -206,7 +208,7 @@ public class SqlGeneratorEmbeddedUnitTests {
softly.assertThat(join.getJoinTable().getName()).isEqualTo("other_entity");
softly.assertThat(join.getJoinColumn().getTable()).isEqualTo(join.getJoinTable());
softly.assertThat(join.getJoinColumn().getName()).isEqualTo("embedded_with_reference");
softly.assertThat(join.getJoinColumn().getName()).isEqualTo("dummy_entity2");
softly.assertThat(join.getParentId().getName()).isEqualTo("id");
softly.assertThat(join.getParentId().getTable().getName()).isEqualTo("dummy_entity2");
});

View File

@@ -13,13 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
package org.springframework.data.jdbc.core.convert;
import static org.assertj.core.api.Assertions.*;
import org.assertj.core.api.SoftAssertions;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.convert.SqlGenerator;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils;
import org.springframework.data.mapping.PersistentPropertyPath;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
package org.springframework.data.jdbc.core.convert;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
@@ -24,15 +24,16 @@ import java.util.Set;
import org.assertj.core.api.SoftAssertions;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.jdbc.core.PropertyPathTestingUtils;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.NamingStrategy;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@@ -357,6 +358,31 @@ public class SqlGeneratorUnitTests {
")))");
}
@Test // DATAJDBC-359
public void deletingLongChainNoId() {
assertThat(createSqlGenerator(NoIdChain4.class)
.createDeleteByPath(getPath("chain3.chain2.chain1.chain0", NoIdChain4.class))) //
.isEqualTo("DELETE FROM no_id_chain0 WHERE no_id_chain0.no_id_chain4 = :rootId");
}
@Test // DATAJDBC-359
public void deletingLongChainNoIdWithBackreferenceNotReferencingTheRoot() {
assertThat(createSqlGenerator(IdIdNoIdChain.class)
.createDeleteByPath(getPath("idNoIdChain.chain4.chain3.chain2.chain1.chain0", IdIdNoIdChain.class))) //
.isEqualTo( //
"DELETE FROM no_id_chain0 " //
+ "WHERE no_id_chain0.no_id_chain4 IN (" //
+ "SELECT no_id_chain4.x_four " //
+ "FROM no_id_chain4 " //
+ "WHERE no_id_chain4.id_no_id_chain IN (" //
+ "SELECT id_no_id_chain.x_id " //
+ "FROM id_no_id_chain " //
+ "WHERE id_no_id_chain.id_id_no_id_chain = :rootId" //
+ "))");
}
@Test // DATAJDBC-340
public void noJoinForSimpleColumn() {
assertThat(generateJoin("id", DummyEntity.class)).isNull();
@@ -585,4 +611,39 @@ public class SqlGeneratorUnitTests {
String fourValue;
Chain3 chain3;
}
static class NoIdChain0 {
String zeroValue;
}
static class NoIdChain1 {
String oneValue;
NoIdChain0 chain0;
}
static class NoIdChain2 {
String twoValue;
NoIdChain1 chain1;
}
static class NoIdChain3 {
String threeValue;
NoIdChain2 chain2;
}
static class NoIdChain4 {
@Id Long four;
String fourValue;
NoIdChain3 chain3;
}
static class IdNoIdChain {
@Id Long id;
NoIdChain4 chain4;
}
static class IdIdNoIdChain {
@Id Long id;
IdNoIdChain idNoIdChain;
}
}

View File

@@ -37,8 +37,11 @@ public class DependencyTests {
classpath() //
.noJars() //
.including("org.springframework.data.jdbc.**") //
// the following exclusion exclude deprecated classes necessary for backward compatibility.
.excluding("org.springframework.data.jdbc.core.EntityRowMapper") //
.excluding("org.springframework.data.jdbc.core.DefaultDataAccessStrategy") //
.filterClasspath("*target/classes") // exclude test code
.printOnFailure("degraph.graphml"),
.printOnFailure("degraph-jdbc.graphml"),
JCheck.violationFree());
}
@@ -49,6 +52,9 @@ public class DependencyTests {
classpath() //
// include only Spring Data related classes (for example no JDK code)
.including("org.springframework.data.**") //
// the following exclusion exclude deprecated classes necessary for backward compatibility.
.excluding("org.springframework.data.jdbc.core.EntityRowMapper") //
.excluding("org.springframework.data.jdbc.core.DefaultDataAccessStrategy") //
.filterClasspath(new AbstractFunction1<String, Object>() {
@Override
public Object apply(String s) { //

View File

@@ -242,6 +242,7 @@ public class JdbcRepositoryEmbeddedWithReferenceIntegrationTests {
@Data
private static class Embeddable {
@Column("id")
DummyEntity2 dummyEntity2;

View File

@@ -33,15 +33,14 @@ 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;
import org.springframework.data.jdbc.core.SqlGeneratorSource;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
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.convert.SqlGeneratorSource;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository;

View File

@@ -15,8 +15,10 @@
*/
package org.springframework.data.jdbc.repository.config;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import lombok.Data;
import java.lang.reflect.Field;
@@ -32,13 +34,12 @@ import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
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.DefaultDataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositoriesIntegrationTests.TestConfiguration;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.repository.CrudRepository;
import org.springframework.jdbc.core.ResultSetExtractor;
@@ -49,8 +50,6 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ReflectionUtils;
import lombok.Data;
/**
* Tests the {@link EnableJdbcRepositories} annotation.
*
@@ -66,7 +65,8 @@ public class EnableJdbcRepositoriesIntegrationTests {
static final Field MAPPER_MAP = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class,
"queryMappingConfiguration");
static final Field OPERATIONS = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "operations");
static final Field DATA_ACCESS_STRATEGY = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "dataAccessStrategy");
static final Field DATA_ACCESS_STRATEGY = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class,
"dataAccessStrategy");
public static final RowMapper DUMMY_ENTITY_ROW_MAPPER = mock(RowMapper.class);
public static final RowMapper STRING_ROW_MAPPER = mock(RowMapper.class);
public static final ResultSetExtractor<Integer> INTEGER_RESULT_SET_EXTRACTOR = mock(ResultSetExtractor.class);
@@ -105,13 +105,15 @@ public class EnableJdbcRepositoriesIntegrationTests {
assertThat(mapping.getRowMapper(DummyEntity.class)).isEqualTo(DUMMY_ENTITY_ROW_MAPPER);
}
@Test // DATAJDBC-293
@Test // DATAJDBC-293
public void jdbcOperationsRef() {
NamedParameterJdbcOperations operations = (NamedParameterJdbcOperations) ReflectionUtils.getField(OPERATIONS, factoryBean);
NamedParameterJdbcOperations operations = (NamedParameterJdbcOperations) ReflectionUtils.getField(OPERATIONS,
factoryBean);
assertThat(operations).isNotSameAs(defaultOperations).isSameAs(qualifierJdbcOperations);
DataAccessStrategy dataAccessStrategy = (DataAccessStrategy) ReflectionUtils.getField(DATA_ACCESS_STRATEGY, factoryBean);
DataAccessStrategy dataAccessStrategy = (DataAccessStrategy) ReflectionUtils.getField(DATA_ACCESS_STRATEGY,
factoryBean);
assertThat(dataAccessStrategy).isNotSameAs(defaultDataAccessStrategy).isSameAs(qualifierDataAccessStrategy);
}
@@ -149,7 +151,8 @@ public class EnableJdbcRepositoriesIntegrationTests {
}
@Bean("qualifierDataAccessStrategy")
DataAccessStrategy defaultDataAccessStrategy(@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template,
DataAccessStrategy defaultDataAccessStrategy(
@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template,
RelationalMappingContext context, JdbcConverter converter) {
return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter, template);
}

View File

@@ -27,6 +27,8 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
import org.springframework.data.jdbc.repository.config.DefaultQueryMappingConfiguration;
import org.springframework.data.jdbc.repository.query.Query;
@@ -55,7 +57,7 @@ public class JdbcQueryLookupStrategyUnitTests {
ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class);
RelationalMappingContext mappingContext = mock(RelationalMappingContext.class, RETURNS_DEEP_STUBS);
RelationalConverter converter = mock(BasicRelationalConverter.class);
JdbcConverter converter = mock(JdbcConverter.class);
DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class);
ProjectionFactory projectionFactory = mock(ProjectionFactory.class);
RepositoryMetadata metadata;

View File

@@ -32,8 +32,8 @@ import org.springframework.beans.factory.ObjectProvider;
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.DefaultDataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcTypeFactory;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;

View File

@@ -28,15 +28,14 @@ import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
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.DefaultDataAccessStrategy;
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.convert.SqlGeneratorSource;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
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.namedparam.NamedParameterJdbcOperations;
@@ -63,7 +62,7 @@ public class TestConfiguration {
@Bean
JdbcRepositoryFactory jdbcRepositoryFactory(
@Qualifier("defaultDataAccessStrategy") DataAccessStrategy dataAccessStrategy, RelationalMappingContext context,
RelationalConverter converter) {
JdbcConverter converter) {
return new JdbcRepositoryFactory(dataAccessStrategy, context, converter, publisher, namedParameterJdbcTemplate());
}
@@ -100,6 +99,7 @@ public class TestConfiguration {
@Bean
JdbcConverter relationalConverter(RelationalMappingContext mappingContext, CustomConversions conversions,
@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template) {
return new BasicJdbcConverter(mappingContext, conversions, new DefaultJdbcTypeFactory(template.getJdbcOperations()));
return new BasicJdbcConverter(mappingContext, conversions,
new DefaultJdbcTypeFactory(template.getJdbcOperations()));
}
}

View File

@@ -91,3 +91,37 @@ CREATE TABLE CHAIN0
CHAIN1 BIGINT,
FOREIGN KEY (CHAIN1) REFERENCES CHAIN1 (ONE)
);
CREATE TABLE NO_ID_CHAIN4
(
FOUR BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 40) PRIMARY KEY,
FOUR_VALUE VARCHAR(20)
);
CREATE TABLE NO_ID_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_CHAIN2
(
TWO_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_CHAIN1
(
ONE_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_CHAIN0
(
ZERO_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);

View File

@@ -82,3 +82,37 @@ CREATE TABLE CHAIN0
CHAIN1 BIGINT,
FOREIGN KEY (CHAIN1) REFERENCES CHAIN1(ONE)
);
CREATE TABLE NO_ID_CHAIN4
(
FOUR BIGINT AUTO_INCREMENT PRIMARY KEY,
FOUR_VALUE VARCHAR(20)
);
CREATE TABLE NO_ID_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_CHAIN2
(
TWO_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_CHAIN1
(
ONE_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_CHAIN0
(
ZERO_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);

View File

@@ -90,4 +90,38 @@ CREATE TABLE CHAIN0
ZERO_VALUE VARCHAR(20),
CHAIN1 BIGINT,
FOREIGN KEY (CHAIN1) REFERENCES CHAIN1 (ONE)
);
);
CREATE TABLE NO_ID_CHAIN4
(
FOUR BIGINT IDENTITY PRIMARY KEY,
FOUR_VALUE VARCHAR(20)
);
CREATE TABLE NO_ID_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_CHAIN2
(
TWO_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_CHAIN1
(
ONE_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_CHAIN0
(
ZERO_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);

View File

@@ -81,4 +81,38 @@ CREATE TABLE CHAIN0
ZERO_VALUE VARCHAR(20),
CHAIN1 BIGINT,
FOREIGN KEY (CHAIN1) REFERENCES CHAIN1(ONE)
);
);
CREATE TABLE NO_ID_CHAIN4
(
FOUR BIGINT AUTO_INCREMENT PRIMARY KEY,
FOUR_VALUE VARCHAR(20)
);
CREATE TABLE NO_ID_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_CHAIN2
(
TWO_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_CHAIN1
(
ONE_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_CHAIN0
(
ZERO_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);

View File

@@ -101,4 +101,38 @@ CREATE TABLE CHAIN0
ZERO_VALUE VARCHAR(20),
CHAIN1 BIGINT,
FOREIGN KEY (CHAIN1) REFERENCES CHAIN1 (ONE)
);
);
CREATE TABLE NO_ID_CHAIN4
(
FOUR SERIAL PRIMARY KEY,
FOUR_VALUE VARCHAR(20)
);
CREATE TABLE NO_ID_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_CHAIN2
(
TWO_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_CHAIN1
(
ONE_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_CHAIN0
(
ZERO_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);

View File

@@ -1,2 +1,13 @@
CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100), PREFIX_TEST VARCHAR(100));
CREATE TABLE dummy_entity2 ( id BIGINT, ORDER_KEY BIGINT, TEST VARCHAR(100), PRIMARY KEY(id, ORDER_KEY))
CREATE TABLE dummy_entity
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
TEST VARCHAR(100),
PREFIX_TEST VARCHAR(100)
);
CREATE TABLE dummy_entity2
(
id BIGINT,
ORDER_KEY BIGINT,
TEST VARCHAR(100),
PRIMARY KEY (id, ORDER_KEY)
)

View File

@@ -1,2 +1,11 @@
CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100), PREFIX_TEST VARCHAR(100));
CREATE TABLE dummy_entity2 ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100))
CREATE TABLE dummy_entity
(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
TEST VARCHAR(100),
PREFIX_TEST VARCHAR(100)
);
CREATE TABLE dummy_entity2
(
ID BIGINT,
TEST VARCHAR(100)
)

View File

@@ -24,6 +24,7 @@ import java.util.Set;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@@ -43,12 +44,10 @@ public class AggregateChange<T> {
/** Type of the aggregate root to be changed */
private final Class<T> entityType;
private final List<DbAction<?>> actions = new ArrayList<>();
/** Aggregate root, to which the change applies, if available */
@Nullable private T entity;
private final List<DbAction<?>> actions = new ArrayList<>();
public AggregateChange(Kind kind, Class<T> entityType, @Nullable T entity) {
this.kind = kind;
@@ -57,55 +56,11 @@ public class AggregateChange<T> {
}
@SuppressWarnings("unchecked")
public void executeWith(Interpreter interpreter, RelationalMappingContext context, RelationalConverter converter) {
RelationalPersistentEntity<T> persistentEntity = entity != null
? (RelationalPersistentEntity<T>) context.getRequiredPersistentEntity(entity.getClass())
: null;
PersistentPropertyAccessor<T> propertyAccessor = //
persistentEntity != null //
? converter.getPropertyAccessor(persistentEntity, entity) //
: null;
actions.forEach(a -> {
a.executeWith(interpreter);
if (a instanceof DbAction.WithGeneratedId) {
Assert.notNull(persistentEntity,
"For statements triggering database side id generation a RelationalPersistentEntity must be provided.");
Assert.notNull(propertyAccessor, "propertyAccessor must not be null");
Object generatedId = ((DbAction.WithGeneratedId<?>) a).getGeneratedId();
if (generatedId != null) {
if (a instanceof DbAction.InsertRoot && a.getEntityType().equals(entityType)) {
propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), generatedId);
} else if (a instanceof DbAction.WithDependingOn) {
setId(context, converter, propertyAccessor, (DbAction.WithDependingOn<?>) a, generatedId);
}
}
}
});
if (propertyAccessor != null) {
entity = propertyAccessor.getBean();
}
}
public void addAction(DbAction<?> action) {
actions.add(action);
}
@SuppressWarnings("unchecked")
static void setId(RelationalMappingContext context, RelationalConverter converter,
static void setIdOfNonRootEntity(RelationalMappingContext context, RelationalConverter converter,
PersistentPropertyAccessor<?> propertyAccessor, DbAction.WithDependingOn<?> action, Object generatedId) {
PersistentPropertyPath<RelationalPersistentProperty> propertyPathToEntity = action.getPropertyPath();
PersistentPropertyPathExtension extPath = new PersistentPropertyPathExtension(context, propertyPathToEntity);
RelationalPersistentProperty leafProperty = propertyPathToEntity.getRequiredLeafProperty();
@@ -130,7 +85,7 @@ public class AggregateChange<T> {
} else {
throw new IllegalStateException("Can't handle " + currentPropertyValue);
}
} else {
} else if (extPath.hasIdProperty()) {
RelationalPersistentProperty requiredIdProperty = context
.getRequiredPersistentEntity(propertyPathToEntity.getRequiredLeafProperty().getActualType())
@@ -199,6 +154,57 @@ public class AggregateChange<T> {
return intermediateAccessor;
}
@SuppressWarnings("unchecked")
public void executeWith(Interpreter interpreter, RelationalMappingContext context, RelationalConverter converter) {
RelationalPersistentEntity<T> persistentEntity = entity != null
? (RelationalPersistentEntity<T>) context.getRequiredPersistentEntity(entity.getClass())
: null;
PersistentPropertyAccessor<T> propertyAccessor = //
persistentEntity != null //
? converter.getPropertyAccessor(persistentEntity, entity) //
: null;
actions.forEach(a -> {
a.executeWith(interpreter);
processGeneratedId(context, converter, persistentEntity, propertyAccessor, a);
});
if (propertyAccessor != null) {
entity = propertyAccessor.getBean();
}
}
public void addAction(DbAction<?> action) {
actions.add(action);
}
private void processGeneratedId(RelationalMappingContext context, RelationalConverter converter,
RelationalPersistentEntity<T> persistentEntity, PersistentPropertyAccessor<T> propertyAccessor, DbAction<?> a) {
if (a instanceof DbAction.WithGeneratedId) {
Assert.notNull(persistentEntity,
"For statements triggering database side id generation a RelationalPersistentEntity must be provided.");
Assert.notNull(propertyAccessor, "propertyAccessor must not be null");
Object generatedId = ((DbAction.WithGeneratedId<?>) a).getGeneratedId();
if (generatedId != null) {
if (a instanceof DbAction.InsertRoot && a.getEntityType().equals(entityType)) {
propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), generatedId);
} else if (a instanceof DbAction.WithDependingOn) {
setIdOfNonRootEntity(context, converter, propertyAccessor, (DbAction.WithDependingOn<?>) a, generatedId);
}
}
}
}
/**
* The kind of action to be performed on an aggregate.
*/

View File

@@ -180,6 +180,12 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
return collectionIdColumnName.get().orElseGet(() -> context.getNamingStrategy().getReverseColumnName(this));
}
@Override
public String getReverseColumnName(PersistentPropertyPathExtension path) {
return collectionIdColumnName.get().orElseGet(() -> context.getNamingStrategy().getReverseColumnName(path));
}
@Override
public String getKeyColumn() {

View File

@@ -88,6 +88,11 @@ public interface NamingStrategy {
return property.getOwner().getTableName();
}
default String getReverseColumnName(PersistentPropertyPathExtension path) {
return getTableName(path.getIdDefiningParentPath().getLeafEntity().getType());
}
/**
* For a map valued reference A -> Map&gt;X,B&lt; this is the name of the column in the table for B holding the key of
* the map.

View File

@@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
package org.springframework.data.relational.core.mapping;
import lombok.EqualsAndHashCode;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -29,13 +29,15 @@ import org.springframework.util.Assert;
* @author Jens Schauder
* @since 1.1
*/
class PersistentPropertyPathExtension {
@EqualsAndHashCode
public class PersistentPropertyPathExtension {
private final RelationalPersistentEntity<?> entity;
private final @Nullable PersistentPropertyPath<RelationalPersistentProperty> path;
private final MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context;
PersistentPropertyPathExtension(MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context,
public PersistentPropertyPathExtension(
MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context,
RelationalPersistentEntity<?> entity) {
Assert.notNull(context, "Context must not be null.");
@@ -46,7 +48,8 @@ class PersistentPropertyPathExtension {
this.path = null;
}
PersistentPropertyPathExtension(MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context,
public PersistentPropertyPathExtension(
MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context,
PersistentPropertyPath<RelationalPersistentProperty> path) {
Assert.notNull(context, "Context must not be null.");
@@ -63,7 +66,7 @@ class PersistentPropertyPathExtension {
*
* @return if the leaf property is embedded.
*/
boolean isEmbedded() {
public boolean isEmbedded() {
return path != null && path.getRequiredLeafProperty().isEmbedded();
}
@@ -73,7 +76,7 @@ class PersistentPropertyPathExtension {
* @return the parent path. Guaranteed to be not {@literal null}.
* @throws IllegalStateException when called on an empty path.
*/
PersistentPropertyPathExtension getParentPath() {
public PersistentPropertyPathExtension getParentPath() {
if (path == null) {
throw new IllegalStateException("The parent path of a root path is not defined.");
@@ -92,7 +95,7 @@ class PersistentPropertyPathExtension {
*
* @return {@literal true} if the path contains a multivalued element.
*/
boolean isMultiValued() {
public boolean isMultiValued() {
return path != null && //
(path.getRequiredLeafProperty().isCollectionLike() //
@@ -107,28 +110,28 @@ class PersistentPropertyPathExtension {
* @return Might return {@literal null} when called on a path that does not represent an entity.
*/
@Nullable
RelationalPersistentEntity<?> getLeafEntity() {
public RelationalPersistentEntity<?> getLeafEntity() {
return path == null ? entity : context.getPersistentEntity(path.getRequiredLeafProperty().getActualType());
}
/**
* @return {@literal true} when this is an empty path or the path references an entity.
*/
boolean isEntity() {
public boolean isEntity() {
return path == null || path.getRequiredLeafProperty().isEntity();
}
/**
* @return {@literal true} when this is references a {@link java.util.List} or {@link java.util.Map}.
*/
boolean isQualified() {
public boolean isQualified() {
return path != null && path.getRequiredLeafProperty().isQualified();
}
/**
* @return {@literal true} when this is references a {@link java.util.Collection} or an array.
*/
boolean isCollectionLike() {
public boolean isCollectionLike() {
return path != null && path.getRequiredLeafProperty().isCollectionLike();
}
@@ -137,11 +140,11 @@ class PersistentPropertyPathExtension {
*
* @throws IllegalStateException when called on an empty path.
*/
String getReverseColumnName() {
public String getReverseColumnName() {
Assert.state(path != null, "Path is null");
Assert.state(path != null, "Empty paths don't have a reverse column name");
return path.getRequiredLeafProperty().getReverseColumnName();
return path.getRequiredLeafProperty().getReverseColumnName(this);
}
/**
@@ -149,7 +152,7 @@ class PersistentPropertyPathExtension {
*
* @throws IllegalStateException when called on an empty path.
*/
String getReverseColumnNameAlias() {
public String getReverseColumnNameAlias() {
return prefixWithTableAlias(getReverseColumnName());
}
@@ -159,7 +162,7 @@ class PersistentPropertyPathExtension {
*
* @throws IllegalStateException when called on an empty path.
*/
String getColumnName() {
public String getColumnName() {
Assert.state(path != null, "Path is null");
@@ -171,7 +174,7 @@ class PersistentPropertyPathExtension {
*
* @throws IllegalStateException when called on an empty path.
*/
String getColumnAlias() {
public String getColumnAlias() {
return prefixWithTableAlias(getColumnName());
}
@@ -179,20 +182,20 @@ class PersistentPropertyPathExtension {
/**
* @return {@literal true} if this path represents an entity which has an Id attribute.
*/
boolean hasIdProperty() {
public boolean hasIdProperty() {
RelationalPersistentEntity<?> leafEntity = getLeafEntity();
return leafEntity != null && leafEntity.hasIdProperty();
}
PersistentPropertyPathExtension getIdDefiningParentPath() {
public PersistentPropertyPathExtension getIdDefiningParentPath() {
PersistentPropertyPathExtension parent = getParentPath();
if (parent.path == null) {
return parent;
}
if (parent.isEmbedded()) {
return getParentPath().getIdDefiningParentPath();
if (!parent.hasIdProperty()) {
return parent.getIdDefiningParentPath();
}
return parent;
}
@@ -202,7 +205,7 @@ class PersistentPropertyPathExtension {
*
* @return the name of the table. Guaranteed to be not {@literal null}.
*/
String getTableName() {
public String getTableName() {
return getTableOwningAncestor().getRequiredLeafEntity().getTableName();
}
@@ -212,7 +215,7 @@ class PersistentPropertyPathExtension {
* @return a table alias, {@literal null} if the table owning path is the empty path.
*/
@Nullable
String getTableAlias() {
public String getTableAlias() {
PersistentPropertyPathExtension tableOwner = getTableOwningAncestor();
@@ -223,7 +226,7 @@ class PersistentPropertyPathExtension {
/**
* The column name of the id column of the ancestor path that represents an actual table.
*/
String getIdColumnName() {
public String getIdColumnName() {
return getTableOwningAncestor().getRequiredLeafEntity().getIdColumn();
}
@@ -231,7 +234,7 @@ class PersistentPropertyPathExtension {
* If the table owning ancestor has an id the column name of that id property is returned. Otherwise the reverse
* column is returned.
*/
String getEffectiveIdColumnName() {
public String getEffectiveIdColumnName() {
PersistentPropertyPathExtension owner = getTableOwningAncestor();
return owner.path == null ? owner.getRequiredLeafEntity().getIdColumn() : owner.getReverseColumnName();
@@ -240,7 +243,7 @@ class PersistentPropertyPathExtension {
/**
* The length of the path.
*/
int getLength() {
public int getLength() {
return path == null ? 0 : path.getLength();
}
@@ -299,4 +302,37 @@ class PersistentPropertyPathExtension {
return tableAlias == null ? columnName : tableAlias + "_" + columnName;
}
public boolean matches(PersistentPropertyPath<RelationalPersistentProperty> path) {
return this.path == null ? path.isEmpty() : this.path.equals(path);
}
public RelationalPersistentProperty getRequiredIdProperty() {
return this.path == null ? entity.getRequiredIdProperty() : getLeafEntity().getRequiredIdProperty();
}
public String getKeyColumn() {
return path == null ? "" : path.getRequiredLeafProperty().getKeyColumn();
}
public Class<?> getQualifierColumnType() {
return path == null ? null : path.getRequiredLeafProperty().getQualifierColumnType();
}
public PersistentPropertyPathExtension extendBy(RelationalPersistentProperty property) {
PersistentPropertyPath<RelationalPersistentProperty> newPath;
if (path == null) {
newPath = context.getPersistentPropertyPath(property.getName(), entity.getType());
} else {
newPath = context.getPersistentPropertyPath(path.toDotPath() + "." + property.getName(), entity.getType());
}
return new PersistentPropertyPathExtension(context, newPath);
}
@Override
public String toString() {
return String.format("PersistentPropertyPathExtension[%s, %s]", entity.getName(),
path == null ? "-" : path.toDotPath());
}
}

View File

@@ -56,8 +56,17 @@ public interface RelationalPersistentProperty extends PersistentProperty<Relatio
@Override
RelationalPersistentEntity<?> getOwner();
/**
* @return
* @deprecated Use {@link #getReverseColumnName(PersistentPropertyPathExtension)} instead.
*/
@Deprecated
String getReverseColumnName();
default String getReverseColumnName(PersistentPropertyPathExtension path) {
return getReverseColumnName();
}
@Nullable
String getKeyColumn();

View File

@@ -0,0 +1,4 @@
@NonNullApi
package org.springframework.data.relational.domain;
import org.springframework.lang.NonNullApi;

View File

@@ -67,7 +67,7 @@ public class AggregateChangeUnitTests {
DbAction.Insert<?> insert = createInsert("single", content, null);
AggregateChange.setId(context, converter, propertyAccessor, insert, id);
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
DummyEntity result = propertyAccessor.getBean();
@@ -81,7 +81,7 @@ public class AggregateChangeUnitTests {
DbAction.Insert<?> insert = createInsert("contentSet", content, null);
AggregateChange.setId(context, converter, propertyAccessor, insert, id);
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
DummyEntity result = propertyAccessor.getBean();
assertThat(result.contentSet).isNotNull();
@@ -95,7 +95,7 @@ public class AggregateChangeUnitTests {
DbAction.Insert<?> insert = createInsert("contentList", content, 0);
AggregateChange.setId(context, converter, propertyAccessor, insert, id);
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
DummyEntity result = propertyAccessor.getBean();
assertThat(result.contentList).extracting(c -> c.id).containsExactlyInAnyOrder(23);
@@ -108,7 +108,7 @@ public class AggregateChangeUnitTests {
DbAction.Insert<?> insert = createInsert("contentMap", content, "one");
AggregateChange.setId(context, converter, propertyAccessor, insert, id);
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
DummyEntity result = propertyAccessor.getBean();
assertThat(result.contentMap.entrySet()).extracting(e -> e.getKey(), e -> e.getValue().id)

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2017-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
*
* https://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.relational.degraph;
import static de.schauderhaft.degraph.check.JCheck.*;
import static org.junit.Assert.*;
import de.schauderhaft.degraph.check.JCheck;
import scala.runtime.AbstractFunction1;
import org.junit.Test;
/**
* Test package dependencies for violations.
*
* @author Jens Schauder
*/
public class DependencyTests {
@Test // DATAJDBC-114
public void cycleFree() {
assertThat( //
classpath() //
.noJars() //
.including("org.springframework.data.relational.**") //
.filterClasspath("*target/classes") // exclude test code
.printOnFailure("degraph-relational.graphml"),
JCheck.violationFree());
}
@Test // DATAJDBC-220
public void acrossModules() {
assertThat( //
classpath() //
// include only Spring Data related classes (for example no JDK code)
.including("org.springframework.data.**") //
.filterClasspath(new AbstractFunction1<String, Object>() {
@Override
public Object apply(String s) { //
// only the current module + commons
return s.endsWith("target/classes") || s.contains("spring-data-commons");
}
}) // exclude test code
.withSlicing("sub-modules", // sub-modules are defined by any of the following pattern.
"org.springframework.data.relational.(**).*", //
"org.springframework.data.(**).*") //
.printTo("degraph-across-modules.graphml"), // writes a graphml to this location
JCheck.violationFree());
}
}