DATAJDBC-223 - Chains of Lists or Maps get properly stored and loaded.

Also solved DATAJDBC-369 since it made refactoring easier.

Known limitation: Back-references to intermediate key columns currently can't get renamed and use the name defined by the property where they become part of the path.

Backward compatibility of the JdbcConfiguration is still broken.

Original pull request: #153.
This commit is contained in:
Jens Schauder
2019-05-08 14:02:44 +02:00
committed by Mark Paluch
parent c87d8b8d31
commit 097190ee42
35 changed files with 1908 additions and 358 deletions

View File

@@ -28,6 +28,6 @@ public class EntityRowMapper<T> extends org.springframework.data.jdbc.core.conve
public EntityRowMapper(RelationalPersistentEntity<T> entity, JdbcConverter converter,
DataAccessStrategy accessStrategy) {
super(entity, converter, accessStrategy);
super(entity, converter);
}
}

View File

@@ -15,9 +15,7 @@
*/
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;
@@ -54,18 +52,6 @@ public class JdbcIdentifierBuilder {
return new JdbcIdentifierBuilder(identifier);
}
private static RelationalPersistentProperty getLastIdProperty(
PersistentPropertyPath<RelationalPersistentProperty> path) {
RelationalPersistentProperty idProperty = path.getRequiredLeafProperty().getOwner().getIdProperty();
if (idProperty != null) {
return idProperty;
}
return getLastIdProperty(path.getParentPath());
}
/**
* Adds a qualifier to the identifier to build. A qualifier is a map key or a list index.
*
@@ -78,7 +64,7 @@ public class JdbcIdentifierBuilder {
Assert.notNull(path, "Path must not be null");
Assert.notNull(value, "Value must not be null");
identifier = identifier.withPart(path.getKeyColumn(), value, path.getQualifierColumnType());
identifier = identifier.withPart(path.getQualifierColumn(), value, path.getQualifierColumnType());
return this;
}

View File

@@ -31,6 +31,7 @@ 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.PersistentPropertyAccessor;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.SimpleTypeHolder;
@@ -39,6 +40,7 @@ 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.relational.domain.Identifier;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
@@ -53,6 +55,7 @@ import org.springframework.util.Assert;
* @author Mark Paluch
* @author Jens Schauder
* @author Christoph Strobl
* @since 1.1
* @see MappingContext
* @see SimpleTypeHolder
* @see CustomConversions
@@ -61,70 +64,51 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
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;
private RelationResolver relationResolver;
/**
* Creates a new {@link BasicRelationalConverter} given {@link MappingContext} and a
* {@link JdbcTypeFactory#unsupported() no-op type factory} throwing {@link UnsupportedOperationException} on type
* creation. Use {@link #BasicJdbcConverter(MappingContext, JdbcTypeFactory)} to convert arrays and large objects into
* JDBC-specific types.
* creation. Use {@link #BasicJdbcConverter(MappingContext, RelationResolver, JdbcTypeFactory)} to convert arrays and
* large objects into JDBC-specific types.
*
* @param context must not be {@literal null}.
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
*/
public BasicJdbcConverter(
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context) {
this(context, JdbcTypeFactory.unsupported());
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
RelationResolver relationResolver) {
super(context);
Assert.notNull(relationResolver, "RelationResolver must not be null");
this.relationResolver = relationResolver;
this.typeFactory = JdbcTypeFactory.unsupported();
}
/**
* Creates a new {@link BasicRelationalConverter} given {@link MappingContext}.
*
* @param context must not be {@literal null}.
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
* @param typeFactory must not be {@literal null}
* @since 1.1
*/
public BasicJdbcConverter(
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
JdbcTypeFactory typeFactory) {
super(context);
Assert.notNull(typeFactory, "JdbcTypeFactory must not be null");
this.typeFactory = typeFactory;
}
/**
* Creates a new {@link BasicRelationalConverter} given {@link MappingContext}, {@link CustomConversions}, and
* {@link JdbcTypeFactory}.
*
* @param context must not be {@literal null}.
* @param conversions must not be {@literal null}.
* @param typeFactory must not be {@literal null}
* @since 1.1
*/
public BasicJdbcConverter(
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
CustomConversions conversions, JdbcTypeFactory typeFactory) {
RelationResolver relationResolver, CustomConversions conversions, JdbcTypeFactory typeFactory) {
super(context, conversions);
Assert.notNull(typeFactory, "JdbcTypeFactory must not be null");
this.typeFactory = typeFactory;
}
Assert.notNull(relationResolver, "RelationResolver must not be null");
/**
* Creates a new {@link BasicRelationalConverter} given {@link MappingContext} and {@link CustomConversions}.
*
* @param context must not be {@literal null}.
* @param conversions must not be {@literal null}.
* @deprecated use one of the constructors with {@link JdbcTypeFactory} parameter.
*/
@Deprecated
public BasicJdbcConverter(
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
CustomConversions conversions) {
this(context, conversions, JdbcTypeFactory.unsupported());
this.relationResolver = relationResolver;
this.typeFactory = typeFactory;
}
/*
@@ -145,9 +129,7 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
if (AggregateReference.class.isAssignableFrom(type.getType())) {
TypeInformation<?> idType = type.getSuperTypeInformation(AggregateReference.class).getTypeArguments().get(1);
return AggregateReference.to(readValue(value, idType));
return readAggregateReference(value, type);
}
if (value instanceof Array) {
@@ -161,6 +143,14 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
return super.readValue(value, type);
}
@SuppressWarnings("ConstantConditions")
private Object readAggregateReference(@Nullable Object value, TypeInformation<?> type) {
TypeInformation<?> idType = type.getSuperTypeInformation(AggregateReference.class).getTypeArguments().get(1);
return AggregateReference.to(readValue(value, idType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.conversion.RelationalConverter#writeValue(java.lang.Object, org.springframework.data.util.TypeInformation)
@@ -246,47 +236,63 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
*/
@SuppressWarnings("unchecked")
@Override
public <T> T mapRow(RelationalPersistentEntity<T> entity, DataAccessStrategy accessStrategy, ResultSet resultSet) {
return new ReadingContext<T>(entity, accessStrategy, resultSet).mapRow();
public <T> T mapRow(RelationalPersistentEntity<T> entity, ResultSet resultSet, Object key) {
return new ReadingContext<T>(
new PersistentPropertyPathExtension(
(MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty>) getMappingContext(), entity),
resultSet, Identifier.empty(), key).mapRow();
}
@Override
public <T> T mapRow(PersistentPropertyPathExtension path, ResultSet resultSet, Identifier identifier, Object key) {
return new ReadingContext<T>(path, resultSet, identifier, key).mapRow();
}
private class ReadingContext<T> {
private final RelationalPersistentEntity<T> entity;
private final RelationalPersistentProperty idProperty;
private final ResultSet resultSet;
PersistentPropertyPathExtension path;
private final DataAccessStrategy accessStrategy;
private final PersistentPropertyPathExtension rootPath;
private final PersistentPropertyPathExtension path;
private final Identifier identifier;
private final Object key;
ReadingContext(RelationalPersistentEntity<T> entity, DataAccessStrategy accessStrategy, ResultSet resultSet) {
@SuppressWarnings("unchecked")
private ReadingContext(PersistentPropertyPathExtension rootPath, ResultSet resultSet, Identifier identifier,
Object key) {
RelationalPersistentEntity<T> entity = (RelationalPersistentEntity<T>) rootPath.getLeafEntity();
Assert.notNull(entity, "The rootPath must point to an entity.");
this.entity = entity;
this.idProperty = entity.getIdProperty();
this.accessStrategy = accessStrategy;
this.resultSet = resultSet;
this.rootPath = rootPath;
this.path = new PersistentPropertyPathExtension(
(MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty>) getMappingContext(), entity);
(MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty>) getMappingContext(), this.entity);
this.identifier = identifier;
this.key = key;
}
public ReadingContext(RelationalPersistentEntity<T> entity, DataAccessStrategy accessStrategy, ResultSet resultSet,
PersistentPropertyPathExtension path) {
private ReadingContext(RelationalPersistentEntity<T> entity, ResultSet resultSet,
PersistentPropertyPathExtension rootPath, PersistentPropertyPathExtension path, Identifier identifier,
Object key) {
this.entity = entity;
this.idProperty = entity.getIdProperty();
this.accessStrategy = accessStrategy;
this.resultSet = resultSet;
this.rootPath = rootPath;
this.path = path;
this.identifier = identifier;
this.key = key;
}
private ReadingContext<?> extendBy(RelationalPersistentProperty property) {
return new ReadingContext(getMappingContext().getRequiredPersistentEntity(property.getActualType()),
accessStrategy, resultSet, path.extendBy(property));
private <S> ReadingContext<S> extendBy(RelationalPersistentProperty property) {
return new ReadingContext<S>((RelationalPersistentEntity<S>) getMappingContext().getRequiredPersistentEntity(property.getActualType()), resultSet,
rootPath.extendBy(property), path.extendBy(property), identifier, key);
}
T mapRow() {
@@ -319,10 +325,14 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
@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));
if ((property.isCollectionLike() && property.isEntity()) || property.isMap()) {
Iterable<Object> allByPath = resolveRelation(id, property);
return property.isMap() //
? ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(allByPath) //
: allByPath;
} else if (property.isEmbedded()) {
return readEmbeddedEntityFrom(id, property);
} else {
@@ -330,6 +340,18 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
}
}
private Iterable<Object> resolveRelation(@Nullable Object id, RelationalPersistentProperty property) {
Identifier identifier = id == null //
? this.identifier.withPart(rootPath.getQualifierColumn(), key, Object.class) //
: Identifier.of(rootPath.extendBy(property).getReverseColumnName(), id, Object.class);
PersistentPropertyPath<RelationalPersistentProperty> propertyPath = path.extendBy(property)
.getRequiredPersistentPropertyPath();
return relationResolver.findAllByPath(identifier, propertyPath);
}
/**
* Read a single value or a complete Entity from the {@link ResultSet} passed as an argument.
*
@@ -355,10 +377,12 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
return newContext.hasInstanceValues(idValue) ? newContext.createInstanceInternal(idValue) : null;
}
private boolean hasInstanceValues(Object idValue) {
private boolean hasInstanceValues(@Nullable Object idValue) {
RelationalPersistentEntity<?> persistentEntity = path.getLeafEntity();
Assert.state(persistentEntity != null, "Entity must not be null");
for (RelationalPersistentProperty embeddedProperty : persistentEntity) {
// if the embedded contains Lists, Sets or Maps we consider it non-empty
@@ -375,11 +399,11 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
}
@Nullable
private <S> S readEntityFrom(RelationalPersistentProperty property, PersistentPropertyPathExtension path) {
private Object readEntityFrom(RelationalPersistentProperty property, PersistentPropertyPathExtension path) {
ReadingContext<S> newContext = (ReadingContext<S>) extendBy(property);
ReadingContext<?> newContext = extendBy(property);
RelationalPersistentEntity<S> entity = (RelationalPersistentEntity<S>) getMappingContext()
RelationalPersistentEntity<?> entity = getMappingContext()
.getRequiredPersistentEntity(property.getActualType());
RelationalPersistentProperty idProperty = entity.getIdProperty();

View File

@@ -140,6 +140,15 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
return collect(das -> das.findAllById(ids, domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.RelationResolver#findAllByPath(org.springframework.data.relational.domain.Identifier, org.springframework.data.mapping.PersistentPropertyPath)
*/
@Override
public <T> Iterable<T> findAllByPath(Identifier identifier, PersistentPropertyPath<RelationalPersistentProperty> path) {
return collect(das -> das.findAllByPath(identifier, path));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllByProperty(java.lang.Object, org.springframework.data.relational.core.mapping.RelationalPersistentProperty)

View File

@@ -30,7 +30,7 @@ import org.springframework.lang.Nullable;
*
* @author Jens Schauder
*/
public interface DataAccessStrategy {
public interface DataAccessStrategy extends RelationResolver {
/**
* Inserts a the data of a single entity. Referenced entities don't get handled.
@@ -144,12 +144,26 @@ public interface DataAccessStrategy {
*/
<T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType);
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.RelationResolver#findAllByPath(org.springframework.data.relational.domain.Identifier, org.springframework.data.mapping.PersistentPropertyPath)
*/
@Override
default <T> Iterable<T> findAllByPath(Identifier identifier,
PersistentPropertyPath<RelationalPersistentProperty> path) {
Object rootId = identifier.toMap().get(path.getRequiredLeafProperty().getReverseColumnName());
return findAllByProperty(rootId, path.getRequiredLeafProperty());
};
/**
* Finds all entities reachable via {@literal property} from the instance identified by {@literal rootId}.
*
* @param rootId Id of the root object on which the {@literal propertyPath} is based.
* @param property Leading from the root object to the entities to be found.
* @deprecated Use #findAllByPath instead.
*/
@Deprecated
<T> Iterable<T> findAllByProperty(Object rootId, RelationalPersistentProperty property);
/**

View File

@@ -34,6 +34,7 @@ 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.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;
@@ -63,21 +64,6 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
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}
@@ -86,12 +72,10 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
* @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) {
JdbcConverter converter, NamedParameterJdbcOperations operations) {
Assert.notNull(sqlGeneratorSource, "SqlGeneratorSource must not be null");
Assert.notNull(context, "RelationalMappingContext must not be null");
@@ -102,7 +86,6 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
this.context = context;
this.converter = converter;
this.operations = operations;
this.accessStrategy = mappingAccessStrategy == null ? this : mappingAccessStrategy;
}
/*
@@ -236,7 +219,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
try {
return operations.queryForObject(findOneSql, parameter, (RowMapper<T>) getEntityRowMapper(domainType));
return operations.queryForObject(findOneSql, parameter,
(RowMapper<T>) getEntityRowMapper(domainType));
} catch (EmptyResultDataAccessException e) {
return null;
}
@@ -249,7 +233,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
@SuppressWarnings("unchecked")
@Override
public <T> Iterable<T> findAll(Class<T> domainType) {
return operations.query(sql(domainType).getFindAll(), (RowMapper<T>) getEntityRowMapper(domainType));
return operations.query(sql(domainType).getFindAll(),
(RowMapper<T>) getEntityRowMapper(domainType));
}
/*
@@ -267,7 +252,39 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
String findAllInListSql = sql(domainType).getFindAllInList();
return operations.query(findAllInListSql, parameterSource, (RowMapper<T>) getEntityRowMapper(domainType));
return operations.query(findAllInListSql, parameterSource,
(RowMapper<T>) getEntityRowMapper(domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.RelationResolver#findAllByPath(org.springframework.data.relational.domain.Identifier, org.springframework.data.mapping.PersistentPropertyPath)
*/
@SuppressWarnings("unchecked")
@Override
public <T> Iterable<T> findAllByPath(Identifier identifier,
PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
Assert.notNull(identifier, "identifier must not be null.");
Assert.notNull(propertyPath, "propertyPath must not be null.");
PersistentPropertyPathExtension path = new PersistentPropertyPathExtension(context, propertyPath);
Class<?> actualType = path.getActualType();
String findAllByProperty = sql(actualType) //
.getFindAllByProperty(identifier, path.getQualifierColumn(), path.isOrdered());
MapSqlParameterSource parameters = new MapSqlParameterSource();
identifier.forEach((name, value, targetType) -> {
parameters.addValue(name, value);
});
return operations.query(findAllByProperty, parameters, //
(RowMapper<T>) (path.isMap() //
? this.getMapEntityRowMapper(path, identifier) //
: this.getEntityRowMapper(path, identifier)));
}
/*
@@ -280,16 +297,9 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
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)));
Class<?> rootType = property.getOwner().getType();
return findAllByPath(Identifier.of(property.getReverseColumnName(), rootId, rootType),
context.getPersistentPropertyPath(property.getName(), rootType));
}
/*
@@ -385,15 +395,19 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
}
private EntityRowMapper<?> getEntityRowMapper(Class<?> domainType) {
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), converter, accessStrategy);
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), converter);
}
private RowMapper<?> getMapEntityRowMapper(RelationalPersistentProperty property) {
private EntityRowMapper<?> getEntityRowMapper(PersistentPropertyPathExtension path, Identifier identifier) {
return new EntityRowMapper<>(path, converter, identifier);
}
String keyColumn = property.getKeyColumn();
Assert.notNull(keyColumn, () -> "KeyColumn must not be null for " + property);
private RowMapper<?> getMapEntityRowMapper(PersistentPropertyPathExtension path, Identifier identifier) {
return new MapEntityRowMapper<>(getEntityRowMapper(property.getActualType()), keyColumn);
String keyColumn = path.getQualifierColumn();
Assert.notNull(keyColumn, () -> "KeyColumn must not be null for " + path);
return new MapEntityRowMapper<>(path, converter, identifier, keyColumn);
}
private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> domainType) {

View File

@@ -135,6 +135,15 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy {
return delegate.findAllById(ids, domainType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.RelationResolver#findAllByPath(org.springframework.data.relational.domain.Identifier, org.springframework.data.mapping.PersistentPropertyPath)
*/
@Override
public <T> Iterable<T> findAllByPath(Identifier identifier, PersistentPropertyPath<RelationalPersistentProperty> path) {
return delegate.findAllByPath(identifier, path);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllByProperty(java.lang.Object, org.springframework.data.relational.core.mapping.RelationalPersistentProperty)

View File

@@ -17,7 +17,9 @@ package org.springframework.data.jdbc.core.convert;
import java.sql.ResultSet;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.jdbc.core.RowMapper;
/**
@@ -34,16 +36,24 @@ import org.springframework.jdbc.core.RowMapper;
public class EntityRowMapper<T> implements RowMapper<T> {
private final RelationalPersistentEntity<T> entity;
private final PersistentPropertyPathExtension path;
private final JdbcConverter converter;
private final DataAccessStrategy accessStrategy;
private final Identifier identifier;
public EntityRowMapper(RelationalPersistentEntity<T> entity, JdbcConverter converter,
DataAccessStrategy accessStrategy) {
public EntityRowMapper(PersistentPropertyPathExtension path, JdbcConverter converter, Identifier identifier) {
this.entity = (RelationalPersistentEntity<T>) path.getLeafEntity();
this.path = path;
this.converter = converter;
this.identifier = identifier;
}
public EntityRowMapper(RelationalPersistentEntity<T> entity, JdbcConverter converter) {
this.entity = entity;
this.path = null;
this.converter = converter;
this.accessStrategy = accessStrategy;
this.identifier = null;
}
/*
@@ -52,7 +62,9 @@ public class EntityRowMapper<T> implements RowMapper<T> {
*/
@Override
public T mapRow(ResultSet resultSet, int rowNumber) {
return converter.mapRow(entity, accessStrategy, resultSet);
return path == null ? converter.mapRow(entity, resultSet, rowNumber)
: converter.mapRow(path, resultSet, identifier, rowNumber);
}
}

View File

@@ -18,7 +18,9 @@ package org.springframework.data.jdbc.core.convert;
import java.sql.ResultSet;
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.domain.Identifier;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
@@ -42,9 +44,7 @@ public interface JdbcConverter extends RelationalConverter {
*/
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);
<T> T mapRow(RelationalPersistentEntity<T> entity, ResultSet resultSet, Object key);
<T> T mapRow(PersistentPropertyPathExtension path, ResultSet resultSet, Identifier identifier, Object key);
}

View File

@@ -20,6 +20,8 @@ import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.NonNull;
@@ -32,22 +34,35 @@ import org.springframework.lang.NonNull;
*/
class MapEntityRowMapper<T> implements RowMapper<Map.Entry<Object, T>> {
private final RowMapper<T> delegate;
private final PersistentPropertyPathExtension path;
private final JdbcConverter converter;
private final Identifier identifier;
private final String keyColumn;
/**
* @param delegate rowmapper used as a delegate for obtaining the map values.
* @param path
* @param converter
* @param identifier
* @param keyColumn the name of the key column.
*/
MapEntityRowMapper(RowMapper<T> delegate, String keyColumn) {
MapEntityRowMapper(PersistentPropertyPathExtension path, JdbcConverter converter,
Identifier identifier, String keyColumn) {
this.delegate = delegate;
this.path = path;
this.converter = converter;
this.identifier = identifier;
this.keyColumn = keyColumn;
}
@NonNull
@Override
public Map.Entry<Object, T> mapRow(ResultSet rs, int rowNum) throws SQLException {
return new HashMap.SimpleEntry<>(rs.getObject(keyColumn), delegate.mapRow(rs, rowNum));
Object key = rs.getObject(keyColumn);
return new HashMap.SimpleEntry<>(key, mapEntity(rs, key));
}
private T mapEntity(ResultSet resultSet, Object key) {
return converter.mapRow(path, resultSet, identifier, key);
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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 org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
/**
* Resolves relations within an aggregate.
*
* @author Jens Schauder
*
* @since 1.1
*/
public interface RelationResolver {
/**
* Finds all entities reachable via {@literal path}.
*
* @param identifier the combination of Id, map keys and list indexes that identify the parent of the entity to be loaded. Must not be {@literal null}.
* @param path the path from the aggregate root to the entities to be resolved. Must not be {@literal null}.
* @param <T> the type of entity created by this class
* @return Guaranteed to be not {@literal null}.
*/
<T> Iterable<T> findAllByPath(Identifier identifier,
PersistentPropertyPath<RelationalPersistentProperty> path);
}

View File

@@ -40,6 +40,7 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentEnti
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.core.sql.*;
import org.springframework.data.relational.core.sql.render.SqlRenderer;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.data.util.Lazy;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -163,34 +164,51 @@ class SqlGenerator {
* {@literal columnName}. This is used to select values for a complex property ({@link Set}, {@link Map} ...) based on
* a referencing entity.
*
* @param columnName name of the column of the FK back to the referencing entity.
* @param parentIdentifier name of the column of the FK back to the referencing entity.
* @param keyColumn if the property is of type {@link Map} this column contains the map key.
* @param ordered whether the SQL statement should include an ORDER BY for the keyColumn. If this is {@code true}, the
* keyColumn must not be {@code null}.
* @return a SQL String.
*/
String getFindAllByProperty(String columnName, @Nullable String keyColumn, boolean ordered) {
String getFindAllByProperty(Identifier parentIdentifier, @Nullable String keyColumn, boolean ordered) {
Assert.isTrue(keyColumn != null || !ordered,
"If the SQL statement should be ordered a keyColumn to order by must be provided.");
SelectBuilder.SelectWhere builder = selectBuilder(
keyColumn == null ? Collections.emptyList() : Collections.singleton(keyColumn));
SelectBuilder.SelectWhere builder = selectBuilder( //
keyColumn == null //
? Collections.emptyList() //
: Collections.singleton(keyColumn) //
);
Table table = getTable();
SelectBuilder.SelectWhereAndOr withWhereClause = builder
.where(table.column(columnName).isEqualTo(getBindMarker(columnName)));
Select select;
if (ordered) {
select = withWhereClause.orderBy(table.column(keyColumn).as(keyColumn)).build();
} else {
select = withWhereClause.build();
}
Condition condition = buildConditionForBackReference(parentIdentifier, table);
SelectBuilder.SelectWhereAndOr withWhereClause = builder.where(condition);
Select select = ordered //
? withWhereClause.orderBy(table.column(keyColumn).as(keyColumn)).build() //
: withWhereClause.build();
return render(select);
}
private Condition buildConditionForBackReference(Identifier parentIdentifier, Table table) {
Condition condition = null;
for (String backReferenceColumn : parentIdentifier.toMap().keySet()) {
Condition newCondition = table.column(backReferenceColumn).isEqualTo(getBindMarker(backReferenceColumn));
condition = condition == null ? newCondition : condition.and(newCondition);
}
Assert.state(condition != null, "We need at least one condition");
return condition;
}
/**
* Create a {@code SELECT COUNT(id) FROM … WHERE :id = …} statement.
*

View File

@@ -22,7 +22,6 @@ import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.data.jdbc.core.convert.CascadingDataAccessStrategy;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
@@ -88,8 +87,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
sqlGeneratorSource, //
context, //
converter, //
operations, //
cascadingDataAccessStrategy //
operations //
);
delegatingDataAccessStrategy.setDelegate(defaultDataAccessStrategy);
@@ -244,6 +242,13 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
new MyBatisContext(ids, null, domainType, Collections.emptyMap()));
}
@Override
public <T> Iterable<T> findAllByPath(Identifier identifier,
PersistentPropertyPath<RelationalPersistentProperty> path) {
return sqlSession().selectList(namespace(path.getBaseProperty().getOwner().getType()) + ".findAllByPath",
new MyBatisContext(identifier, null, path.getLeafProperty().getType(), Collections.emptyMap()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllByProperty(java.lang.Object, org.springframework.data.relational.core.mapping.RelationalPersistentProperty)

View File

@@ -20,7 +20,9 @@ import java.util.Optional;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
@@ -28,6 +30,7 @@ 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.RelationResolver;
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.relational.core.conversion.RelationalConverter;
@@ -73,8 +76,11 @@ public abstract class AbstractJdbcConfiguration {
* @return must not be {@literal null}.
*/
@Bean
public JdbcConverter jdbcConverter(RelationalMappingContext mappingContext, JdbcOperations operations) {
return new BasicJdbcConverter(mappingContext, jdbcCustomConversions(), new DefaultJdbcTypeFactory(operations));
public JdbcConverter jdbcConverter(RelationalMappingContext mappingContext, NamedParameterJdbcOperations operations,
@Lazy RelationResolver relationResolver) {
return new BasicJdbcConverter(mappingContext, relationResolver, jdbcCustomConversions(),
new DefaultJdbcTypeFactory(operations.getJdbcOperations()));
}
/**
@@ -97,16 +103,30 @@ public abstract class AbstractJdbcConfiguration {
* @param publisher for publishing events. Must not be {@literal null}.
* @param context the mapping context to be used. Must not be {@literal null}.
* @param converter the conversions used when reading and writing from/to the database. Must not be {@literal null}.
* @param operations {@link NamedParameterJdbcOperations} used for accessing the database. Must not be
* {@literal null}.
* @return a {@link JdbcAggregateTemplate}. Guaranteed to be not {@literal null}.
*/
@Bean
public JdbcAggregateTemplate jdbcAggregateTemplate(ApplicationEventPublisher publisher,
RelationalMappingContext context, JdbcConverter converter, NamedParameterJdbcOperations operations) {
RelationalMappingContext context, JdbcConverter converter, DataAccessStrategy dataAccessStrategy) {
DataAccessStrategy dataAccessStrategy = new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context,
converter, operations);
return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy);
}
/**
* Register a {@link DataAccessStrategy} as a bean for reuse in the {@link JdbcAggregateOperations} and the
* {@link RelationalConverter}.
*
* @param operations
* @param namingStrategy
* @param jdbcConverter
* @return
*/
@Bean
public DataAccessStrategy dataAccessStrategy(NamedParameterJdbcOperations operations,
Optional<NamingStrategy> namingStrategy, JdbcConverter jdbcConverter) {
JdbcMappingContext context = jdbcMappingContext(namingStrategy);
return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, jdbcConverter, operations);
}
}

View File

@@ -20,6 +20,7 @@ import java.util.Optional;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
@@ -29,6 +30,7 @@ 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.RelationResolver;
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.relational.core.conversion.RelationalConverter;
@@ -74,9 +76,9 @@ public class JdbcConfiguration {
* @return must not be {@literal null}.
*/
@Bean
public RelationalConverter relationalConverter(RelationalMappingContext mappingContext) {
public RelationalConverter relationalConverter(RelationalMappingContext mappingContext, @Lazy RelationResolver relationalResolver) {
return new BasicJdbcConverter(mappingContext, jdbcCustomConversions(), JdbcTypeFactory.unsupported());
return new BasicJdbcConverter(mappingContext, relationalResolver, jdbcCustomConversions(), JdbcTypeFactory.unsupported());
}
/**
@@ -104,8 +106,23 @@ public class JdbcConfiguration {
@Bean
public JdbcAggregateOperations jdbcAggregateOperations(ApplicationEventPublisher publisher,
RelationalMappingContext context, JdbcConverter converter, NamedParameterJdbcOperations operations) {
DataAccessStrategy dataAccessStrategy = new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context,
converter, operations);
return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy);
return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy(context, converter, operations));
}
/**
* Register a {@link DataAccessStrategy} as a bean for reuse in the {@link JdbcAggregateOperations} and the
* {@link RelationalConverter}.
*
* @param context
* @param converter
* @param operations
* @return
*/
@Bean
public DataAccessStrategy dataAccessStrategy(RelationalMappingContext context, JdbcConverter converter,
NamedParameterJdbcOperations operations) {
return new DefaultDataAccessStrategy(new SqlGeneratorSource(context),
context, converter, operations);
}
}

View File

@@ -118,9 +118,8 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
EntityRowMapper<?> defaultEntityRowMapper = new EntityRowMapper<>( //
context.getRequiredPersistentEntity(domainType), //
//
converter, //
accessStrategy);
converter //
);
return defaultEntityRowMapper;
}

View File

@@ -18,17 +18,22 @@ package org.springframework.data.jdbc.core;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.jdbc.core.PropertyPathTestingUtils.*;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.mapping.PersistentPropertyPath;
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.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
import java.util.List;
/**
* Unit tests for {@link DefaultJdbcInterpreter}
*
@@ -48,9 +53,9 @@ public class DefaultJdbcInterpreterUnitTests {
Element element = new Element();
InsertRoot<Container> containerInsert = new InsertRoot<>(container);
Insert<?> elementInsert = new Insert<>(element, PropertyPathTestingUtils.toPath("element", Container.class, context),
Insert<?> elementInsert = new Insert<>(element, toPath("element", Container.class, context),
containerInsert);
Insert<?> element1Insert = new Insert<>(element, PropertyPathTestingUtils.toPath("element.element1", Container.class, context),
Insert<?> element1Insert = new Insert<>(element, toPath("element.element1", Container.class, context),
elementInsert);
@Test // DATAJDBC-145
@@ -114,6 +119,41 @@ public class DefaultJdbcInterpreterUnitTests {
.containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class));
}
@Test // DATAJDBC-223
public void generateCascadingIds() {
ListListContainer listListContainer = new ListListContainer();
ListContainer listContainer = new ListContainer();
InsertRoot<ListListContainer> listListContainerInsert = new InsertRoot<>(listListContainer);
PersistentPropertyPath<RelationalPersistentProperty> listContainersPath = toPath("listContainers", ListListContainer.class, context);
Insert<?> listContainerInsert = new Insert<>(listContainer, listContainersPath, listListContainerInsert);
listContainerInsert.getQualifiers().put(listContainersPath, 3);
PersistentPropertyPath<RelationalPersistentProperty> listContainersElementsPath = toPath("listContainers.elements", ListListContainer.class, context);
Insert<?> elementInsertInList = new Insert<>(element, listContainersElementsPath, listContainerInsert);
elementInsertInList.getQualifiers().put(listContainersElementsPath, 6);
elementInsertInList.getQualifiers().put(listContainersPath, 3);
listListContainerInsert.setGeneratedId(CONTAINER_ID);
interpreter.interpret(elementInsertInList);
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("list_list_container", CONTAINER_ID, Long.class), // the top level id
tuple("list_list_container_key", 3, Integer.class), // midlevel key
tuple("list_container_key", 6, Integer.class) // lowlevel key
);
}
@SuppressWarnings("unused")
static class Container {
@@ -129,4 +169,16 @@ public class DefaultJdbcInterpreterUnitTests {
static class Element1 {
}
static class ListListContainer {
@Id
Long id;
List<ListContainer> listContainers;
}
private static class ListContainer {
List<Element> elements;
}
}

View File

@@ -22,11 +22,14 @@ import lombok.Data;
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.Set;
import org.assertj.core.api.SoftAssertions;
import org.jetbrains.annotations.NotNull;
import org.junit.Assume;
import org.junit.ClassRule;
import org.junit.Rule;
@@ -461,8 +464,7 @@ public class JdbcAggregateTemplateIntegrationTests {
template.delete(chain4, Chain4.class);
assertThat(jdbcTemplate.queryForObject("SELECT COUNT(*) FROM CHAIN0", emptyMap(), Long.class)) //
.isEqualTo(0);
assertThat(count("CHAIN0")).isEqualTo(0);
}
@Test // DATAJDBC-359
@@ -492,8 +494,224 @@ public class JdbcAggregateTemplateIntegrationTests {
template.delete(chain4, NoIdChain4.class);
assertThat(jdbcTemplate.queryForObject("SELECT COUNT(*) FROM CHAIN0", emptyMap(), Long.class)) //
.isEqualTo(0);
assertThat(count("CHAIN0")).isEqualTo(0);
}
@Test // DATAJDBC-223
public void saveAndLoadLongChainOfListsWithoutIds() {
NoIdListChain4 saved = template.save(createNoIdTree());
assertThat(saved.four).describedAs("Something went wrong during saving").isNotNull();
NoIdListChain4 reloaded = template.findById(saved.four, NoIdListChain4.class);
assertIsUnchanged(saved, reloaded);
template.deleteById(saved.four, NoIdListChain4.class);
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(count("NO_ID_LIST_CHAIN4")).describedAs("Chain4 elements got deleted").isEqualTo(0);
softly.assertThat(count("NO_ID_LIST_CHAIN3")).describedAs("Chain3 elements got deleted").isEqualTo(0);
softly.assertThat(count("NO_ID_LIST_CHAIN2")).describedAs("Chain2 elements got deleted").isEqualTo(0);
softly.assertThat(count("NO_ID_LIST_CHAIN1")).describedAs("Chain1 elements got deleted").isEqualTo(0);
softly.assertThat(count("NO_ID_LIST_CHAIN0")).describedAs("Chain0 elements got deleted").isEqualTo(0);
});
}
/**
* creates an instance of {@link NoIdListChain4} with the following properties:
* <ul>
* <li>Each element has two children with indices 0 and 1.</li>
* <li>the xxxValue of each element is a {@literal v} followed by the indices used to navigate to the given instance.
* </li>
* </ul>
*
* @return Guaranteed to be not {@literal null}.
*/
@NotNull
private JdbcAggregateTemplateIntegrationTests.NoIdListChain4 createNoIdTree() {
NoIdListChain4 chain4 = new NoIdListChain4();
chain4.fourValue = "v";
for (int _3 = 0; _3 <= 1; _3++) {
NoIdListChain3 c3 = new NoIdListChain3();
c3.threeValue = chain4.fourValue + _3;
chain4.chain3.add(c3);
for (int _2 = 0; _2 <= 1; _2++) {
NoIdListChain2 c2 = new NoIdListChain2();
c2.twoValue = c3.threeValue + _2;
c3.chain2.add(c2);
for (int _1 = 0; _1 <= 1; _1++) {
NoIdListChain1 c1 = new NoIdListChain1();
c1.oneValue = c2.twoValue + _1;
c2.chain1.add(c1);
for (int _0 = 0; _0 <= 1; _0++) {
NoIdListChain0 c0 = new NoIdListChain0();
c0.zeroValue = c1.oneValue + _0;
c1.chain0.add(c0);
}
}
}
}
return chain4;
}
private void assertIsUnchanged(NoIdListChain4 original, NoIdListChain4 reloaded) {
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(reloaded.fourValue).isEqualTo("v");
softly.assertThat(reloaded.chain3).hasSize(2);
for (int _3 = 0; _3 <= 1; _3++) {
NoIdListChain3 c3 = reloaded.chain3.get(_3);
softly.assertThat(c3.threeValue).isEqualTo(original.fourValue + _3);
softly.assertThat(c3.chain2).hasSize(2);
for (int _2 = 0; _2 <= 1; _2++) {
NoIdListChain2 c2 = c3.chain2.get(_2);
softly.assertThat(c2.twoValue).isEqualTo(c3.threeValue + _2);
softly.assertThat(c2.chain1).hasSize(2);
for (int _1 = 0; _1 <= 1; _1++) {
NoIdListChain1 c1 = c2.chain1.get(_1);
softly.assertThat(c1.oneValue).isEqualTo(c2.twoValue + _1);
softly.assertThat(c1.chain0).hasSize(2);
for (int _0 = 0; _0 <= 1; _0++) {
NoIdListChain0 c0 = c1.chain0.get(_0);
softly.assertThat(c0.zeroValue).isEqualTo(c1.oneValue + _0);
}
}
}
}
});
}
@Test // DATAJDBC-223
public void saveAndLoadLongChainOfMapsWithoutIds() {
NoIdMapChain4 saved = template.save(createNoIdMapTree());
assertThat(saved.four).isNotNull();
NoIdMapChain4 reloaded = template.findById(saved.four, NoIdMapChain4.class);
assertIsUnchanged(saved, reloaded);
template.deleteById(saved.four, NoIdMapChain4.class);
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(count("NO_ID_MAP_CHAIN4")).describedAs("Chain4 elements got deleted").isEqualTo(0);
softly.assertThat(count("NO_ID_MAP_CHAIN3")).describedAs("Chain3 elements got deleted").isEqualTo(0);
softly.assertThat(count("NO_ID_MAP_CHAIN2")).describedAs("Chain2 elements got deleted").isEqualTo(0);
softly.assertThat(count("NO_ID_MAP_CHAIN1")).describedAs("Chain1 elements got deleted").isEqualTo(0);
softly.assertThat(count("NO_ID_MAP_CHAIN0")).describedAs("Chain0 elements got deleted").isEqualTo(0);
});
}
@NotNull
private JdbcAggregateTemplateIntegrationTests.NoIdMapChain4 createNoIdMapTree() {
NoIdMapChain4 chain4 = new NoIdMapChain4();
chain4.fourValue = "v";
for (int _3 = 0; _3 <= 1; _3++) {
NoIdMapChain3 c3 = new NoIdMapChain3();
c3.threeValue = chain4.fourValue + _3;
chain4.chain3.put(asString(_3), c3);
for (int _2 = 0; _2 <= 1; _2++) {
NoIdMapChain2 c2 = new NoIdMapChain2();
c2.twoValue = c3.threeValue + _2;
c3.chain2.put(asString(_2), c2);
for (int _1 = 0; _1 <= 1; _1++) {
NoIdMapChain1 c1 = new NoIdMapChain1();
c1.oneValue = c2.twoValue + _1;
c2.chain1.put(asString(_1), c1);
for (int _0 = 0; _0 <= 1; _0++) {
NoIdMapChain0 c0 = new NoIdMapChain0();
c0.zeroValue = c1.oneValue + _0;
c1.chain0.put(asString(_0), c0);
}
}
}
}
return chain4;
}
private void assertIsUnchanged(NoIdMapChain4 original, NoIdMapChain4 reloaded) {
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(reloaded.fourValue).isEqualTo("v");
softly.assertThat(reloaded.chain3).hasSize(2);
for (int _3 = 0; _3 <= 1; _3++) {
NoIdMapChain3 c3 = reloaded.chain3.get(asString(_3));
softly.assertThat(c3.threeValue).isEqualTo(original.fourValue + _3);
softly.assertThat(c3.chain2).hasSize(2);
for (int _2 = 0; _2 <= 1; _2++) {
NoIdMapChain2 c2 = c3.chain2.get(asString(_2));
softly.assertThat(c2.twoValue).isEqualTo(c3.threeValue + _2);
softly.assertThat(c2.chain1).hasSize(2);
for (int _1 = 0; _1 <= 1; _1++) {
NoIdMapChain1 c1 = c2.chain1.get(asString(_1));
softly.assertThat(c1.oneValue).isEqualTo(c2.twoValue + _1);
softly.assertThat(c1.chain0).hasSize(2);
for (int _0 = 0; _0 <= 1; _0++) {
NoIdMapChain0 c0 = c1.chain0.get(asString(_0));
softly.assertThat(c0.zeroValue).isEqualTo(c1.oneValue + _0);
}
}
}
}
});
}
private static String asString(int i) {
return "_" + i;
}
private Long count(String tableName) {
return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM " + tableName, emptyMap(), Long.class);
}
private static void assumeNot(String dbProfileName) {
@@ -659,4 +877,60 @@ public class JdbcAggregateTemplateIntegrationTests {
String fourValue;
NoIdChain3 chain3;
}
/**
* One may think of ChainN as a chain with N further elements
*/
static class NoIdListChain0 {
String zeroValue;
}
static class NoIdListChain1 {
String oneValue;
List<NoIdListChain0> chain0 = new ArrayList<>();
}
static class NoIdListChain2 {
String twoValue;
List<NoIdListChain1> chain1 = new ArrayList<>();
}
static class NoIdListChain3 {
String threeValue;
List<NoIdListChain2> chain2 = new ArrayList<>();
}
static class NoIdListChain4 {
@Id Long four;
String fourValue;
List<NoIdListChain3> chain3 = new ArrayList<>();
}
/**
* One may think of ChainN as a chain with N further elements
*/
static class NoIdMapChain0 {
String zeroValue;
}
static class NoIdMapChain1 {
String oneValue;
Map<String, NoIdMapChain0> chain0 = new HashMap<>();
}
static class NoIdMapChain2 {
String twoValue;
Map<String, NoIdMapChain1> chain1 = new HashMap<>();
}
static class NoIdMapChain3 {
String threeValue;
Map<String, NoIdMapChain2> chain2 = new HashMap<>();
}
static class NoIdMapChain4 {
@Id Long four;
String fourValue;
Map<String, NoIdMapChain3> chain3 = new HashMap<>();
}
}

View File

@@ -16,12 +16,10 @@
package org.springframework.data.jdbc.core.convert;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.SoftAssertions;
import org.junit.Test;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
@@ -39,7 +37,7 @@ import org.springframework.data.util.ClassTypeInformation;
public class BasicRelationalConverterAggregateReferenceUnitTests {
JdbcMappingContext context = new JdbcMappingContext();
RelationalConverter converter = new BasicJdbcConverter(context);
RelationalConverter converter = new BasicJdbcConverter(context, mock(RelationResolver.class));
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(DummyEntity.class);

View File

@@ -25,18 +25,13 @@ import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Id;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.jdbc.core.JdbcOperations;
@@ -58,16 +53,27 @@ public class DefaultDataAccessStrategyUnitTests {
NamedParameterJdbcOperations namedJdbcOperations = mock(NamedParameterJdbcOperations.class);
JdbcOperations jdbcOperations = mock(JdbcOperations.class);
RelationalMappingContext context = new JdbcMappingContext();
JdbcConverter converter = new BasicJdbcConverter(context, new JdbcCustomConversions(),
new DefaultJdbcTypeFactory(jdbcOperations));
HashMap<String, Object> additionalParameters = new HashMap<>();
ArgumentCaptor<SqlParameterSource> paramSourceCaptor = ArgumentCaptor.forClass(SqlParameterSource.class);
DefaultDataAccessStrategy accessStrategy = new DefaultDataAccessStrategy( //
new SqlGeneratorSource(context), //
context, //
converter, //
namedJdbcOperations);
JdbcConverter converter;
DefaultDataAccessStrategy accessStrategy;
@Before
public void before() {
DelegatingDataAccessStrategy relationResolver = new DelegatingDataAccessStrategy();
converter = new BasicJdbcConverter(context, relationResolver,
new JdbcCustomConversions(), new DefaultJdbcTypeFactory(jdbcOperations));
accessStrategy = new DefaultDataAccessStrategy( //
new SqlGeneratorSource(context), //
context, //
converter, //
namedJdbcOperations);
relationResolver.setDelegate(accessStrategy);
}
@Test // DATAJDBC-146
public void additionalParameterForIdDoesNotLeadToDuplicateParameters() {
@@ -100,7 +106,9 @@ public class DefaultDataAccessStrategyUnitTests {
@Test // DATAJDBC-235
public void considersConfiguredWriteConverter() {
JdbcConverter converter = new BasicJdbcConverter(context,
DelegatingDataAccessStrategy relationResolver = new DelegatingDataAccessStrategy();
JdbcConverter converter = new BasicJdbcConverter(context, relationResolver,
new JdbcCustomConversions(Arrays.asList(BooleanToStringConverter.INSTANCE, StringToBooleanConverter.INSTANCE)),
new DefaultJdbcTypeFactory(jdbcOperations));
@@ -110,6 +118,8 @@ public class DefaultDataAccessStrategyUnitTests {
converter, //
namedJdbcOperations);
relationResolver.setDelegate(accessStrategy);
ArgumentCaptor<String> sqlCaptor = ArgumentCaptor.forClass(String.class);
EntityWithBoolean entity = new EntityWithBoolean(ORIGINAL_ID, true);

View File

@@ -18,8 +18,7 @@ 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;
@@ -45,16 +44,19 @@ import javax.naming.OperationNotSupportedException;
import org.assertj.core.api.SoftAssertions;
import org.junit.Test;
import org.mockito.ArgumentMatchers;
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.mapping.JdbcMappingContext;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.Embedded;
import org.springframework.data.relational.core.mapping.NamingStrategy;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.data.repository.query.Param;
import org.springframework.util.Assert;
@@ -580,32 +582,52 @@ public class EntityRowMapperUnitTests {
DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class);
// the ID of the entity is used to determine what kind of ResultSet is needed for subsequent selects.
doReturn(new HashSet<>(asList(new Trivial(1L, "one"), new Trivial(2L, "two")))).when(accessStrategy)
.findAllByProperty(eq(ID_FOR_ENTITY_NOT_REFERENCING_MAP), any(RelationalPersistentProperty.class));
HashSet<Trivial> trivials = new HashSet<>(asList( //
new Trivial(1L, "one"), //
new Trivial(2L, "two") //
));
doReturn(new HashSet<>(asList( //
HashSet<SimpleEntry<Integer, Trivial>> simpleEntriesWithInts = new HashSet<>(asList( //
new SimpleEntry<>(1, new Trivial(1L, "one")), //
new SimpleEntry<>(2, new Trivial(2L, "two")) //
));
HashSet<SimpleEntry<String, Trivial>> simpleEntriesWithStringKeys = new HashSet<>(asList( //
new SimpleEntry<>("one", new Trivial(1L, "one")), //
new SimpleEntry<>("two", new Trivial(2L, "two")) //
))).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_REFERENCING_MAP),
));
doReturn(trivials).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_NOT_REFERENCING_MAP),
any(RelationalPersistentProperty.class));
doReturn(new HashSet<>(asList( //
new SimpleEntry<>(1, new Trivial(1L, "one")), //
new SimpleEntry<>(2, new Trivial(2L, "tow")) //
))).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_REFERENCING_LIST),
doReturn(simpleEntriesWithStringKeys).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_REFERENCING_MAP),
any(RelationalPersistentProperty.class));
JdbcConverter converter = new BasicJdbcConverter(context, new JdbcCustomConversions(),
doReturn(simpleEntriesWithInts).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_REFERENCING_LIST),
any(RelationalPersistentProperty.class));
doReturn(trivials).when(accessStrategy).findAllByPath(identifierOfValue(ID_FOR_ENTITY_NOT_REFERENCING_MAP),
any(PersistentPropertyPath.class));
doReturn(simpleEntriesWithStringKeys).when(accessStrategy)
.findAllByPath(identifierOfValue(ID_FOR_ENTITY_REFERENCING_MAP), any(PersistentPropertyPath.class));
doReturn(simpleEntriesWithInts).when(accessStrategy)
.findAllByPath(identifierOfValue(ID_FOR_ENTITY_REFERENCING_LIST), any(PersistentPropertyPath.class));
BasicJdbcConverter converter = new BasicJdbcConverter(context, accessStrategy, new JdbcCustomConversions(),
JdbcTypeFactory.unsupported());
return new EntityRowMapper<>( //
(RelationalPersistentEntity<T>) context.getRequiredPersistentEntity(type), //
//
converter, //
accessStrategy //
converter //
);
}
private Identifier identifierOfValue(long value) {
return ArgumentMatchers.argThat(argument -> argument.toMap().containsValue(value));
}
private static ResultSet mockResultSet(List<String> columns, Object... values) {
Assert.isTrue( //

View File

@@ -39,6 +39,7 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentEnti
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.core.sql.Aliased;
import org.springframework.data.relational.core.sql.Table;
import org.springframework.data.relational.domain.Identifier;
/**
* Unit tests for the {@link SqlGenerator}.
@@ -51,6 +52,8 @@ import org.springframework.data.relational.core.sql.Table;
*/
public class SqlGeneratorUnitTests {
static final Identifier BACKREF = Identifier.of("backref", "some-value", String.class);
SqlGenerator sqlGenerator;
NamingStrategy namingStrategy = new PrefixingNamingStrategy();
RelationalMappingContext context = new JdbcMappingContext(namingStrategy);
@@ -149,7 +152,7 @@ public class SqlGeneratorUnitTests {
public void findAllByProperty() {
// this would get called when ListParent is the element type of a Set
String sql = sqlGenerator.getFindAllByProperty("backref", null, false);
String sql = sqlGenerator.getFindAllByProperty(BACKREF, null, false);
assertThat(sql).contains("SELECT", //
"dummy_entity.id1 AS id1", //
@@ -165,11 +168,33 @@ public class SqlGeneratorUnitTests {
"WHERE dummy_entity.backref = :backref");
}
@Test // DATAJDBC-223
public void findAllByPropertyWithMultipartIdentifier() {
// this would get called when ListParent is the element type of a Set
String sql = sqlGenerator.getFindAllByProperty(Identifier.of("backref", "some-value", String.class).withPart("backref_key", "key-value", Object.class), null, false);
assertThat(sql).contains("SELECT", //
"dummy_entity.id1 AS id1", //
"dummy_entity.x_name AS x_name", //
"dummy_entity.x_other AS x_other", //
"ref.x_l1id AS ref_x_l1id", //
"ref.x_content AS ref_x_content", //
"ref_further.x_l2id AS ref_further_x_l2id", //
"ref_further.x_something AS ref_further_x_something", //
"FROM dummy_entity ", //
"LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1", //
"LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = ref.x_l1id", //
"dummy_entity.backref = :backref",
"dummy_entity.backref_key = :backref_key"
);
}
@Test // DATAJDBC-131, DATAJDBC-111
public void findAllByPropertyWithKey() {
// this would get called when ListParent is th element type of a Map
String sql = sqlGenerator.getFindAllByProperty("backref", "key-column", false);
String sql = sqlGenerator.getFindAllByProperty(BACKREF, "key-column", false);
assertThat(sql).isEqualTo("SELECT dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name, " //
+ "dummy_entity.x_other AS x_other, " //
@@ -184,14 +209,14 @@ public class SqlGeneratorUnitTests {
@Test(expected = IllegalArgumentException.class) // DATAJDBC-130
public void findAllByPropertyOrderedWithoutKey() {
sqlGenerator.getFindAllByProperty("back-ref", null, true);
sqlGenerator.getFindAllByProperty(BACKREF, null, true);
}
@Test // DATAJDBC-131, DATAJDBC-111
public void findAllByPropertyWithKeyOrdered() {
// this would get called when ListParent is th element type of a Map
String sql = sqlGenerator.getFindAllByProperty("backref", "key-column", true);
String sql = sqlGenerator.getFindAllByProperty(BACKREF, "key-column", true);
assertThat(sql).isEqualTo("SELECT dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name, " //
+ "dummy_entity.x_other AS x_other, " //
@@ -297,7 +322,7 @@ public class SqlGeneratorUnitTests {
final SqlGenerator sqlGenerator = createSqlGenerator(EntityWithReadOnlyProperty.class);
assertThat(sqlGenerator.getFindAllByProperty("backref", "key-column", true)).isEqualToIgnoringCase( //
assertThat(sqlGenerator.getFindAllByProperty(BACKREF, "key-column", true)).isEqualToIgnoringCase( //
"SELECT " //
+ "entity_with_read_only_property.x_id AS x_id, " //
+ "entity_with_read_only_property.x_name AS x_name, " //

View File

@@ -38,6 +38,7 @@ import org.springframework.data.annotation.Id;
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.DelegatingDataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
@@ -77,11 +78,13 @@ public class SimpleJdbcRepositoryEventsUnitTests {
RelationalMappingContext context = new JdbcMappingContext();
NamedParameterJdbcOperations operations = createIdGeneratingOperations();
JdbcConverter converter = new BasicJdbcConverter(context, new JdbcCustomConversions(),
DelegatingDataAccessStrategy delegatingDataAccessStrategy = new DelegatingDataAccessStrategy();
JdbcConverter converter = new BasicJdbcConverter(context, delegatingDataAccessStrategy, new JdbcCustomConversions(),
new DefaultJdbcTypeFactory(operations.getJdbcOperations()));
SqlGeneratorSource generatorSource = new SqlGeneratorSource(context);
this.dataAccessStrategy = spy(new DefaultDataAccessStrategy(generatorSource, context, converter, operations));
delegatingDataAccessStrategy.setDelegate(dataAccessStrategy);
JdbcRepositoryFactory factory = new JdbcRepositoryFactory(dataAccessStrategy, context, converter, publisher,
operations);

View File

@@ -84,7 +84,7 @@ public class JdbcRepositoryFactoryBeanUnitTests {
factoryBean.setDataAccessStrategy(dataAccessStrategy);
factoryBean.setMappingContext(mappingContext);
factoryBean.setConverter(new BasicJdbcConverter(mappingContext, JdbcTypeFactory.unsupported()));
factoryBean.setConverter(new BasicJdbcConverter(mappingContext, dataAccessStrategy));
factoryBean.setApplicationEventPublisher(publisher);
factoryBean.setBeanFactory(beanFactory);
factoryBean.afterPropertiesSet();
@@ -111,7 +111,7 @@ public class JdbcRepositoryFactoryBeanUnitTests {
public void afterPropertiesSetDefaultsNullablePropertiesCorrectly() {
factoryBean.setMappingContext(mappingContext);
factoryBean.setConverter(new BasicJdbcConverter(mappingContext, JdbcTypeFactory.unsupported()));
factoryBean.setConverter(new BasicJdbcConverter(mappingContext, dataAccessStrategy));
factoryBean.setApplicationEventPublisher(publisher);
factoryBean.setBeanFactory(beanFactory);
factoryBean.afterPropertiesSet();

View File

@@ -20,13 +20,13 @@ import java.util.Optional;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
@@ -34,6 +34,7 @@ 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.RelationResolver;
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
@@ -81,7 +82,11 @@ public class TestConfiguration {
DataAccessStrategy defaultDataAccessStrategy(
@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template, RelationalMappingContext context,
JdbcConverter converter) {
return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter, template);
DefaultDataAccessStrategy defaultDataAccessStrategy = new DefaultDataAccessStrategy(new SqlGeneratorSource(context),
context, converter, template);
return defaultDataAccessStrategy;
}
@Bean
@@ -98,9 +103,14 @@ public class TestConfiguration {
}
@Bean
JdbcConverter relationalConverter(RelationalMappingContext mappingContext, CustomConversions conversions,
@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template) {
return new BasicJdbcConverter(mappingContext, conversions,
new DefaultJdbcTypeFactory(template.getJdbcOperations()));
JdbcConverter relationalConverter(RelationalMappingContext mappingContext, @Lazy RelationResolver relationResolver,
CustomConversions conversions, @Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template) {
return new BasicJdbcConverter( //
mappingContext, //
relationResolver, //
conversions, //
new DefaultJdbcTypeFactory(template.getJdbcOperations()) //
);
}
}

View File

@@ -100,28 +100,196 @@ CREATE TABLE NO_ID_CHAIN4
CREATE TABLE NO_ID_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
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,
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,
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,
ZERO_VALUE VARCHAR(20),
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_LIST_CHAIN4
(
FOUR BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 40) PRIMARY KEY,
FOUR_VALUE VARCHAR(20)
);
CREATE TABLE NO_ID_LIST_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY),
FOREIGN KEY (NO_ID_LIST_CHAIN4) REFERENCES NO_ID_LIST_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_LIST_CHAIN2
(
TWO_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
NO_ID_LIST_CHAIN3_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY),
FOREIGN KEY (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY
) REFERENCES NO_ID_LIST_CHAIN3 (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY
)
);
CREATE TABLE NO_ID_LIST_CHAIN1
(
ONE_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
NO_ID_LIST_CHAIN3_KEY BIGINT,
NO_ID_LIST_CHAIN2_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY),
FOREIGN KEY (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY
) REFERENCES NO_ID_LIST_CHAIN2 (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY
)
);
CREATE TABLE NO_ID_LIST_CHAIN0
(
ZERO_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
NO_ID_LIST_CHAIN3_KEY BIGINT,
NO_ID_LIST_CHAIN2_KEY BIGINT,
NO_ID_LIST_CHAIN1_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY,
NO_ID_LIST_CHAIN1_KEY),
FOREIGN KEY (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY
) REFERENCES NO_ID_LIST_CHAIN1 (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY
)
);
CREATE TABLE NO_ID_MAP_CHAIN4
(
FOUR BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 40) PRIMARY KEY,
FOUR_VALUE VARCHAR(20)
);
CREATE TABLE NO_ID_MAP_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY),
FOREIGN KEY (NO_ID_MAP_CHAIN4) REFERENCES NO_ID_MAP_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_MAP_CHAIN2
(
TWO_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
NO_ID_MAP_CHAIN3_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY),
FOREIGN KEY (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY
) REFERENCES NO_ID_MAP_CHAIN3 (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY
)
);
CREATE TABLE NO_ID_MAP_CHAIN1
(
ONE_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
NO_ID_MAP_CHAIN3_KEY VARCHAR(20),
NO_ID_MAP_CHAIN2_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY),
FOREIGN KEY (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY
) REFERENCES NO_ID_MAP_CHAIN2 (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY
)
);
CREATE TABLE NO_ID_MAP_CHAIN0
(
ZERO_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
NO_ID_MAP_CHAIN3_KEY VARCHAR(20),
NO_ID_MAP_CHAIN2_KEY VARCHAR(20),
NO_ID_MAP_CHAIN1_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY,
NO_ID_MAP_CHAIN1_KEY),
FOREIGN KEY (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY
) REFERENCES NO_ID_MAP_CHAIN1 (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY
)
);

View File

@@ -116,3 +116,171 @@ CREATE TABLE NO_ID_CHAIN0
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_LIST_CHAIN4
(
FOUR BIGINT AUTO_INCREMENT PRIMARY KEY,
FOUR_VALUE VARCHAR(20)
);
CREATE TABLE NO_ID_LIST_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY),
FOREIGN KEY (NO_ID_LIST_CHAIN4) REFERENCES NO_ID_LIST_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_LIST_CHAIN2
(
TWO_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
NO_ID_LIST_CHAIN3_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY),
FOREIGN KEY (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY
) REFERENCES NO_ID_LIST_CHAIN3 (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY
)
);
CREATE TABLE NO_ID_LIST_CHAIN1
(
ONE_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
NO_ID_LIST_CHAIN3_KEY BIGINT,
NO_ID_LIST_CHAIN2_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY),
FOREIGN KEY (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY
) REFERENCES NO_ID_LIST_CHAIN2 (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY
)
);
CREATE TABLE NO_ID_LIST_CHAIN0
(
ZERO_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
NO_ID_LIST_CHAIN3_KEY BIGINT,
NO_ID_LIST_CHAIN2_KEY BIGINT,
NO_ID_LIST_CHAIN1_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY,
NO_ID_LIST_CHAIN1_KEY),
FOREIGN KEY (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY
) REFERENCES NO_ID_LIST_CHAIN1 (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY
)
);
CREATE TABLE NO_ID_MAP_CHAIN4
(
FOUR BIGINT AUTO_INCREMENT PRIMARY KEY,
FOUR_VALUE VARCHAR(20)
);
CREATE TABLE NO_ID_MAP_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY),
FOREIGN KEY (NO_ID_MAP_CHAIN4) REFERENCES NO_ID_MAP_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_MAP_CHAIN2
(
TWO_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
NO_ID_MAP_CHAIN3_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY),
FOREIGN KEY (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY
) REFERENCES NO_ID_MAP_CHAIN3 (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY
)
);
CREATE TABLE NO_ID_MAP_CHAIN1
(
ONE_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
NO_ID_MAP_CHAIN3_KEY VARCHAR(20),
NO_ID_MAP_CHAIN2_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY),
FOREIGN KEY (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY
) REFERENCES NO_ID_MAP_CHAIN2 (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY
)
);
CREATE TABLE NO_ID_MAP_CHAIN0
(
ZERO_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
NO_ID_MAP_CHAIN3_KEY VARCHAR(20),
NO_ID_MAP_CHAIN2_KEY VARCHAR(20),
NO_ID_MAP_CHAIN1_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY,
NO_ID_MAP_CHAIN1_KEY),
FOREIGN KEY (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY
) REFERENCES NO_ID_MAP_CHAIN1 (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY
)
);

View File

@@ -125,3 +125,170 @@ CREATE TABLE NO_ID_CHAIN0
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_LIST_CHAIN4
(
FOUR BIGINT IDENTITY PRIMARY KEY,
FOUR_VALUE VARCHAR(20)
);
CREATE TABLE NO_ID_LIST_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY),
FOREIGN KEY (NO_ID_LIST_CHAIN4) REFERENCES NO_ID_LIST_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_LIST_CHAIN2
(
TWO_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
NO_ID_LIST_CHAIN3_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY),
FOREIGN KEY (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY
) REFERENCES NO_ID_LIST_CHAIN3 (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY
)
);
CREATE TABLE NO_ID_LIST_CHAIN1
(
ONE_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
NO_ID_LIST_CHAIN3_KEY BIGINT,
NO_ID_LIST_CHAIN2_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY),
FOREIGN KEY (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY
) REFERENCES NO_ID_LIST_CHAIN2 (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY
)
);
CREATE TABLE NO_ID_LIST_CHAIN0
(
ZERO_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
NO_ID_LIST_CHAIN3_KEY BIGINT,
NO_ID_LIST_CHAIN2_KEY BIGINT,
NO_ID_LIST_CHAIN1_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY,
NO_ID_LIST_CHAIN1_KEY),
FOREIGN KEY (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY
) REFERENCES NO_ID_LIST_CHAIN1 (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY
)
);
CREATE TABLE NO_ID_MAP_CHAIN4
(
FOUR BIGINT IDENTITY PRIMARY KEY,
FOUR_VALUE VARCHAR(20)
);
CREATE TABLE NO_ID_MAP_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY),
FOREIGN KEY (NO_ID_MAP_CHAIN4) REFERENCES NO_ID_MAP_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_MAP_CHAIN2
(
TWO_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
NO_ID_MAP_CHAIN3_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY),
FOREIGN KEY (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY
) REFERENCES NO_ID_MAP_CHAIN3 (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY
)
);
CREATE TABLE NO_ID_MAP_CHAIN1
(
ONE_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
NO_ID_MAP_CHAIN3_KEY VARCHAR(20),
NO_ID_MAP_CHAIN2_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY),
FOREIGN KEY (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY
) REFERENCES NO_ID_MAP_CHAIN2 (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY
)
);
CREATE TABLE NO_ID_MAP_CHAIN0
(
ZERO_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
NO_ID_MAP_CHAIN3_KEY VARCHAR(20),
NO_ID_MAP_CHAIN2_KEY VARCHAR(20),
NO_ID_MAP_CHAIN1_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY,
NO_ID_MAP_CHAIN1_KEY),
FOREIGN KEY (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY
) REFERENCES NO_ID_MAP_CHAIN1 (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY
)
);

View File

@@ -116,3 +116,170 @@ CREATE TABLE NO_ID_CHAIN0
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_LIST_CHAIN4
(
FOUR BIGINT AUTO_INCREMENT PRIMARY KEY,
FOUR_VALUE VARCHAR(20)
);
CREATE TABLE NO_ID_LIST_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY),
FOREIGN KEY (NO_ID_LIST_CHAIN4) REFERENCES NO_ID_LIST_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_LIST_CHAIN2
(
TWO_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
NO_ID_LIST_CHAIN3_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY),
FOREIGN KEY (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY
) REFERENCES NO_ID_LIST_CHAIN3 (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY
)
);
CREATE TABLE NO_ID_LIST_CHAIN1
(
ONE_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
NO_ID_LIST_CHAIN3_KEY BIGINT,
NO_ID_LIST_CHAIN2_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY),
FOREIGN KEY (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY
) REFERENCES NO_ID_LIST_CHAIN2 (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY
)
);
CREATE TABLE NO_ID_LIST_CHAIN0
(
ZERO_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
NO_ID_LIST_CHAIN3_KEY BIGINT,
NO_ID_LIST_CHAIN2_KEY BIGINT,
NO_ID_LIST_CHAIN1_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY,
NO_ID_LIST_CHAIN1_KEY),
FOREIGN KEY (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY
) REFERENCES NO_ID_LIST_CHAIN1 (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY
)
);
CREATE TABLE NO_ID_MAP_CHAIN4
(
FOUR BIGINT AUTO_INCREMENT PRIMARY KEY,
FOUR_VALUE VARCHAR(20)
);
CREATE TABLE NO_ID_MAP_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY),
FOREIGN KEY (NO_ID_MAP_CHAIN4) REFERENCES NO_ID_MAP_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_MAP_CHAIN2
(
TWO_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
NO_ID_MAP_CHAIN3_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY),
FOREIGN KEY (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY
) REFERENCES NO_ID_MAP_CHAIN3 (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY
)
);
CREATE TABLE NO_ID_MAP_CHAIN1
(
ONE_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
NO_ID_MAP_CHAIN3_KEY VARCHAR(20),
NO_ID_MAP_CHAIN2_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY),
FOREIGN KEY (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY
) REFERENCES NO_ID_MAP_CHAIN2 (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY
)
);
CREATE TABLE NO_ID_MAP_CHAIN0
(
ZERO_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
NO_ID_MAP_CHAIN3_KEY VARCHAR(20),
NO_ID_MAP_CHAIN2_KEY VARCHAR(20),
NO_ID_MAP_CHAIN1_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY,
NO_ID_MAP_CHAIN1_KEY),
FOREIGN KEY (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY
) REFERENCES NO_ID_MAP_CHAIN1 (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY
)
);

View File

@@ -136,3 +136,171 @@ CREATE TABLE NO_ID_CHAIN0
NO_ID_CHAIN4 BIGINT,
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_LIST_CHAIN4
(
FOUR SERIAL PRIMARY KEY,
FOUR_VALUE VARCHAR(20)
);
CREATE TABLE NO_ID_LIST_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY),
FOREIGN KEY (NO_ID_LIST_CHAIN4) REFERENCES NO_ID_LIST_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_LIST_CHAIN2
(
TWO_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
NO_ID_LIST_CHAIN3_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY),
FOREIGN KEY (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY
) REFERENCES NO_ID_LIST_CHAIN3 (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY
)
);
CREATE TABLE NO_ID_LIST_CHAIN1
(
ONE_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
NO_ID_LIST_CHAIN3_KEY BIGINT,
NO_ID_LIST_CHAIN2_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY),
FOREIGN KEY (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY
) REFERENCES NO_ID_LIST_CHAIN2 (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY
)
);
CREATE TABLE NO_ID_LIST_CHAIN0
(
ZERO_VALUE VARCHAR(20),
NO_ID_LIST_CHAIN4 BIGINT,
NO_ID_LIST_CHAIN4_KEY BIGINT,
NO_ID_LIST_CHAIN3_KEY BIGINT,
NO_ID_LIST_CHAIN2_KEY BIGINT,
NO_ID_LIST_CHAIN1_KEY BIGINT,
PRIMARY KEY (NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY,
NO_ID_LIST_CHAIN1_KEY),
FOREIGN KEY (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY
) REFERENCES NO_ID_LIST_CHAIN1 (
NO_ID_LIST_CHAIN4,
NO_ID_LIST_CHAIN4_KEY,
NO_ID_LIST_CHAIN3_KEY,
NO_ID_LIST_CHAIN2_KEY
)
);
CREATE TABLE NO_ID_MAP_CHAIN4
(
FOUR SERIAL PRIMARY KEY,
FOUR_VALUE VARCHAR(20)
);
CREATE TABLE NO_ID_MAP_CHAIN3
(
THREE_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY),
FOREIGN KEY (NO_ID_MAP_CHAIN4) REFERENCES NO_ID_MAP_CHAIN4 (FOUR)
);
CREATE TABLE NO_ID_MAP_CHAIN2
(
TWO_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
NO_ID_MAP_CHAIN3_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY),
FOREIGN KEY (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY
) REFERENCES NO_ID_MAP_CHAIN3 (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY
)
);
CREATE TABLE NO_ID_MAP_CHAIN1
(
ONE_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
NO_ID_MAP_CHAIN3_KEY VARCHAR(20),
NO_ID_MAP_CHAIN2_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY),
FOREIGN KEY (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY
) REFERENCES NO_ID_MAP_CHAIN2 (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY
)
);
CREATE TABLE NO_ID_MAP_CHAIN0
(
ZERO_VALUE VARCHAR(20),
NO_ID_MAP_CHAIN4 BIGINT,
NO_ID_MAP_CHAIN4_KEY VARCHAR(20),
NO_ID_MAP_CHAIN3_KEY VARCHAR(20),
NO_ID_MAP_CHAIN2_KEY VARCHAR(20),
NO_ID_MAP_CHAIN1_KEY VARCHAR(20),
PRIMARY KEY (NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY,
NO_ID_MAP_CHAIN1_KEY),
FOREIGN KEY (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY
) REFERENCES NO_ID_MAP_CHAIN1 (
NO_ID_MAP_CHAIN4,
NO_ID_MAP_CHAIN4_KEY,
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY
)
);

View File

@@ -16,13 +16,15 @@
package org.springframework.data.relational.core.conversion;
import lombok.Value;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.util.Pair;
import org.springframework.lang.Nullable;
/**
* Represents a single entity in an aggregate along with its property path from the root entity and the chain of
* objects to traverse a long this path.
* Represents a single entity in an aggregate along with its property path from the root entity and the chain of objects
* to traverse a long this path.
*
* @author Jens Schauder
*/
@@ -37,11 +39,23 @@ class PathNode {
/**
* The parent {@link PathNode}. This is {@code null} if this is the root entity.
*/
@Nullable
PathNode parent;
@Nullable PathNode parent;
/**
* The value of the entity.
*/
Object value;
/**
* If the node represents a qualified property (i.e. a {@link java.util.List} or {@link java.util.Map}) the actual
* value is an element of the {@literal List} or a value of the {@literal Map}, while the {@link #value} is actually a
* {@link Pair} with the index or key as the first element and the actual value as second element.
*
*/
Object getActualValue() {
return getPath().getRequiredLeafProperty().isQualified() //
? ((Pair) getValue()).getSecond() //
: getValue();
}
}

View File

@@ -1,72 +0,0 @@
/*
* 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.core.conversion;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A replacement for {@link org.springframework.data.mapping.PropertyPath} as long as it doesn't support objects with
* empty path. See https://jira.spring.io/browse/DATACMNS-1204.
*
* @author Jens Schauder
*/
public class RelationalPropertyPath {
private final PropertyPath path;
private final Class<?> rootType;
RelationalPropertyPath(PropertyPath path) {
Assert.notNull(path, "path must not be null if rootType is not set");
this.path = path;
this.rootType = null;
}
private RelationalPropertyPath(Class<?> type) {
Assert.notNull(type, "type must not be null if path is not set");
this.path = null;
this.rootType = type;
}
public static RelationalPropertyPath from(String source, Class<?> type) {
if (StringUtils.isEmpty(source)) {
return new RelationalPropertyPath(type);
} else {
return new RelationalPropertyPath(PropertyPath.from(source, type));
}
}
public RelationalPropertyPath nested(String name) {
return path == null ? //
new RelationalPropertyPath(PropertyPath.from(name, rootType)) //
: new RelationalPropertyPath(path.nested(name));
}
public PropertyPath getPath() {
return path;
}
public String toDotPath() {
return path == null ? "" : path.toDotPath();
}
}

View File

@@ -25,6 +25,7 @@ import java.util.Map;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
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.util.Pair;
import org.springframework.lang.Nullable;
@@ -121,16 +122,23 @@ class WritingContext {
from(path).forEach(node -> {
DbAction.WithEntity<?> parentAction = getAction(node.getParent());
DbAction.Insert<Object> insert;
if (node.getPath().getRequiredLeafProperty().isQualified()) {
@SuppressWarnings("unchecked")
Pair<Object, Object> value = (Pair) node.getValue();
insert = new DbAction.Insert<>(value.getSecond(), path, getAction(node.getParent()));
insert = new DbAction.Insert<>(value.getSecond(), path, parentAction);
insert.getQualifiers().put(node.getPath(), value.getFirst());
RelationalPersistentEntity<?> parentEntity = context.getRequiredPersistentEntity(parentAction.getEntityType());
if (!parentEntity.hasIdProperty() && parentAction instanceof DbAction.Insert) {
insert.getQualifiers().putAll(((DbAction.Insert<?>) parentAction).getQualifiers());
}
} else {
insert = new DbAction.Insert<>(node.getValue(), path, getAction(node.getParent()));
insert = new DbAction.Insert<>(node.getValue(), path, parentAction);
}
previousActions.put(node, insert);
actions.add(insert);
@@ -182,10 +190,6 @@ class WritingContext {
return null;
}
// commented as of #DATAJDBC-282
// private boolean isNew(Object o) {
// return context.getRequiredPersistentEntity(o.getClass()).isNew(o);
// }
private List<PathNode> from(PersistentPropertyPath<RelationalPersistentProperty> path) {
@@ -201,7 +205,10 @@ class WritingContext {
List<PathNode> pathNodes = nodesCache.get(path.getParentPath());
pathNodes.forEach(parentNode -> {
Object value = path.getRequiredLeafProperty().getOwner().getPropertyAccessor(parentNode.getValue())
// todo: this should go into pathnode
Object parentValue = parentNode.getActualValue();
Object value = path.getRequiredLeafProperty().getOwner().getPropertyAccessor(parentValue)
.getProperty(path.getRequiredLeafProperty());
nodes.addAll(createNodes(path, parentNode, value));

View File

@@ -17,6 +17,7 @@ package org.springframework.data.relational.core.mapping;
import lombok.EqualsAndHashCode;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.lang.Nullable;
@@ -24,7 +25,7 @@ import org.springframework.util.Assert;
/**
* A wrapper around a {@link org.springframework.data.mapping.PersistentPropertyPath} for making common operations
* available used in SQL generation.
* available used in SQL generation and conversion
*
* @author Jens Schauder
* @since 1.1
@@ -36,6 +37,12 @@ public class PersistentPropertyPathExtension {
private final @Nullable PersistentPropertyPath<RelationalPersistentProperty> path;
private final MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context;
/**
* Creates the empty path referencing the root itself.
*
* @param context Must not be {@literal null}.
* @param entity Root entity of the path. Must not be {@literal null}.
*/
public PersistentPropertyPathExtension(
MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context,
RelationalPersistentEntity<?> entity) {
@@ -48,6 +55,12 @@ public class PersistentPropertyPathExtension {
this.path = null;
}
/**
* Creates a non empty path
*
* @param context Must not be {@literal null}.
* @param path Must not be {@literal null}.
*/
public PersistentPropertyPathExtension(
MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context,
PersistentPropertyPath<RelationalPersistentProperty> path) {
@@ -188,6 +201,11 @@ public class PersistentPropertyPathExtension {
return leafEntity != null && leafEntity.hasIdProperty();
}
/**
* Returns the longest ancestor path that has an Id property.
*
* @return A path that starts just as this path but is shorter. Guaranteed to be not {@literal null}.
*/
public PersistentPropertyPathExtension getIdDefiningParentPath() {
PersistentPropertyPathExtension parent = getParentPath();
@@ -247,6 +265,112 @@ public class PersistentPropertyPathExtension {
return path == null ? 0 : path.getLength();
}
/**
* Tests if {@code this} and the argument represent the same path.
*
* @param path to which this path gets compared. May be {@literal null}.
* @return Whence the argument matches the path represented by this instance.
*/
public boolean matches(PersistentPropertyPath<RelationalPersistentProperty> path) {
return this.path == null ? path.isEmpty() : this.path.equals(path);
}
/**
* The id property of the final element of the path.
*
* @return Guaranteed to be not {@literal null}.
* @throws IllegalStateException if no such property exists.
*/
public RelationalPersistentProperty getRequiredIdProperty() {
return this.path == null ? entity.getRequiredIdProperty() : getRequiredLeafEntity().getRequiredIdProperty();
}
/**
* The column name used for the list index or map key of the leaf property of this path.
*
* @return May be {@literal null}.
*/
@Nullable
public String getQualifierColumn() {
return path == null ? "" : path.getRequiredLeafProperty().getKeyColumn();
}
/**
* The type of the qualifier column of the leaf property of this path or {@literal null} if this is not applicable.
*
* @return May be {@literal null}.
*/
@Nullable
public Class<?> getQualifierColumnType() {
return path == null ? null : path.getRequiredLeafProperty().getQualifierColumnType();
}
/**
* Creates a new path by extending the current path by the property passed as an argument.
*
* @param property Must not be {@literal null}.
* @return Guaranteed to be not {@literal null}.
*/
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());
}
/**
* For empty paths this is the type of the entity. For non empty paths this is the actual type of the leaf property.
*
* @return Guaranteed to be not {@literal null}.
* @see PersistentProperty#getActualType()
*/
public Class<?> getActualType() {
return path == null //
? entity.getType() //
: path.getRequiredLeafProperty().getActualType();
}
/**
* @return whether the leaf end of the path is ordered, i.e. the data to populate must be ordered.
* @see RelationalPersistentProperty#isOrdered()
*/
public boolean isOrdered() {
return path != null && path.getRequiredLeafProperty().isOrdered();
}
/**
* @return {@literal true} if the leaf property of this path is a {@link java.util.Map}.
* @see RelationalPersistentProperty#isMap()
*/
public boolean isMap() {
return path != null && path.getRequiredLeafProperty().isMap();
}
/**
* Converts this path to a non-null {@link PersistentPropertyPath}.
*
* @return Guaranteed to be not {@literal null}.
* @throws IllegalStateException if this path is empty.
*/
public PersistentPropertyPath<RelationalPersistentProperty> getRequiredPersistentPropertyPath() {
Assert.state(path != null, "No path.");
return path;
}
/**
* Finds and returns the longest path with ich identical or an ancestor to the current path and maps directly to a
* table.
@@ -291,7 +415,6 @@ public class PersistentPropertyPathExtension {
return getParentPath().assembleColumnName(embeddedPrefix + suffix);
}
@SuppressWarnings("unchecked")
private RelationalPersistentEntity<?> getRequiredLeafEntity() {
return path == null ? entity : context.getRequiredPersistentEntity(path.getRequiredLeafProperty().getActualType());
}
@@ -302,37 +425,4 @@ public 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

@@ -40,6 +40,7 @@ import org.springframework.data.relational.core.conversion.DbAction.UpdateRoot;
import org.springframework.data.relational.core.mapping.Embedded;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.lang.Nullable;
/**
* Unit tests for the {@link RelationalEntityWriter}
@@ -54,9 +55,23 @@ public class RelationalEntityWriterUnitTests {
final RelationalMappingContext context = new RelationalMappingContext();
final RelationalEntityWriter converter = new RelationalEntityWriter(context);
final PersistentPropertyPath<RelationalPersistentProperty> listContainerElements = toPath("elements", ListContainer.class, context);
final PersistentPropertyPath<RelationalPersistentProperty> listContainerElements = toPath("elements",
ListContainer.class, context);
private final PersistentPropertyPath<RelationalPersistentProperty> mapContainerElements = toPath("elements", MapContainer.class, context);
private final PersistentPropertyPath<RelationalPersistentProperty> mapContainerElements = toPath("elements",
MapContainer.class, context);
private final PersistentPropertyPath<RelationalPersistentProperty> listMapContainerElements = toPath("maps.elements",
ListMapContainer.class, context);
private final PersistentPropertyPath<RelationalPersistentProperty> listMapContainerMaps = toPath("maps",
ListMapContainer.class, context);
private final PersistentPropertyPath<RelationalPersistentProperty> noIdListMapContainerElements = toPath("maps.elements",
NoIdListMapContainer.class, context);
private final PersistentPropertyPath<RelationalPersistentProperty> noIdListMapContainerMaps = toPath("maps",
NoIdListMapContainer.class, context);
@Test // DATAJDBC-112
public void newEntityGetsConvertedToOneInsert() {
@@ -419,6 +434,52 @@ public class RelationalEntityWriterUnitTests {
);
}
@Test // DATAJDBC-223
public void multiLevelQualifiedReferencesWithId() {
ListMapContainer listMapContainer = new ListMapContainer(SOME_ENTITY_ID);
listMapContainer.maps.add(new MapContainer(SOME_ENTITY_ID));
listMapContainer.maps.get(0).elements.put("one", new Element(null));
AggregateChange<ListMapContainer> aggregateChange = new AggregateChange<>(Kind.SAVE, ListMapContainer.class,
listMapContainer);
converter.write(listMapContainer, aggregateChange);
assertThat(aggregateChange.getActions()) //
.extracting(DbAction::getClass, DbAction::getEntityType, a -> getQualifier(a, listMapContainerMaps),a -> getQualifier(a, listMapContainerElements), DbActionTestSupport::extractPath) //
.containsExactly( //
tuple(Delete.class, Element.class, null, null, "maps.elements"), //
tuple(Delete.class, MapContainer.class, null, null, "maps"), //
tuple(UpdateRoot.class, ListMapContainer.class, null, null, ""), //
tuple(Insert.class, MapContainer.class, 0, null, "maps"), //
tuple(Insert.class, Element.class, null, "one", "maps.elements") //
);
}
@Test // DATAJDBC-223
public void multiLevelQualifiedReferencesWithOutId() {
NoIdListMapContainer listMapContainer = new NoIdListMapContainer(SOME_ENTITY_ID);
listMapContainer.maps.add(new NoIdMapContainer());
listMapContainer.maps.get(0).elements.put("one", new NoIdElement());
AggregateChange<NoIdListMapContainer> aggregateChange = new AggregateChange<>(Kind.SAVE, NoIdListMapContainer.class,
listMapContainer);
converter.write(listMapContainer, aggregateChange);
assertThat(aggregateChange.getActions()) //
.extracting(DbAction::getClass, DbAction::getEntityType, a -> getQualifier(a, noIdListMapContainerMaps),a -> getQualifier(a, noIdListMapContainerElements), DbActionTestSupport::extractPath) //
.containsExactly( //
tuple(Delete.class, NoIdElement.class, null, null, "maps.elements"), //
tuple(Delete.class, NoIdMapContainer.class, null, null, "maps"), //
tuple(UpdateRoot.class, NoIdListMapContainer.class, null, null, ""), //
tuple(Insert.class, NoIdMapContainer.class, 0, null, "maps"), //
tuple(Insert.class, NoIdElement.class, 0, "one", "maps.elements") //
);
}
private CascadingReferenceMiddleElement createMiddleElement(Element first, Element second) {
CascadingReferenceMiddleElement middleElement1 = new CascadingReferenceMiddleElement(null);
@@ -429,21 +490,35 @@ public class RelationalEntityWriterUnitTests {
private Object getMapKey(DbAction a) {
return a instanceof DbAction.WithDependingOn //
? ((DbAction.WithDependingOn) a).getQualifiers().get(mapContainerElements) //
: null;
PersistentPropertyPath<RelationalPersistentProperty> path = this.mapContainerElements;
return getQualifier(a, path);
}
private Object getListKey(DbAction a) {
PersistentPropertyPath<RelationalPersistentProperty> path = this.listContainerElements;
return getQualifier(a, path);
}
@Nullable
private Object getQualifier(DbAction a, PersistentPropertyPath<RelationalPersistentProperty> path) {
return a instanceof DbAction.WithDependingOn //
? ((DbAction.WithDependingOn) a).getQualifiers()
.get(listContainerElements) //
? ((DbAction.WithDependingOn) a).getQualifiers().get(path) //
: null;
}
private int getQualifierCount(DbAction a, PersistentPropertyPath<RelationalPersistentProperty> path) {
return a instanceof DbAction.WithDependingOn //
? ((DbAction.WithDependingOn) a).getQualifiers().size() //
: 0;
}
static PersistentPropertyPath<RelationalPersistentProperty> toPath(String path, Class source,
RelationalMappingContext context) {
RelationalMappingContext context) {
PersistentPropertyPaths<?, RelationalPersistentProperty> persistentPropertyPaths = context
.findPersistentPropertyPaths(source, p -> true);
@@ -456,7 +531,7 @@ public class RelationalEntityWriterUnitTests {
@Id final Long id;
Element other;
// should not trigger own Dbaction
// should not trigger own DbAction
String name;
}
@@ -472,7 +547,7 @@ public class RelationalEntityWriterUnitTests {
@Id final Long id;
NoIdElement other;
// should not trigger own Dbaction
// should not trigger own DbAction
String name;
}
@@ -497,6 +572,13 @@ public class RelationalEntityWriterUnitTests {
Set<Element> elements = new HashSet<>();
}
@RequiredArgsConstructor
private static class ListMapContainer {
@Id final Long id;
List<MapContainer> maps = new ArrayList<>();
}
@RequiredArgsConstructor
private static class MapContainer {
@@ -516,6 +598,20 @@ public class RelationalEntityWriterUnitTests {
@Id final Long id;
}
@RequiredArgsConstructor
private static class NoIdListMapContainer {
@Id final Long id;
List<NoIdMapContainer> maps = new ArrayList<>();
}
@RequiredArgsConstructor
private static class NoIdMapContainer {
Map<String, NoIdElement> elements = new HashMap<>();
}
@RequiredArgsConstructor
private static class NoIdElement {
// empty classes feel weird.