DATAJDBC-359 - Support for arbitrary chains of entities with or without Id.
Introduced a `ReadingContext` in the `EntityRowMapper` to avoid passing the `ResultSet` and the `path` all over the place. Added a dependency test to Spring Data Relational and fixed the test failure by moving the PersistentPropertyPathExtension to core.mapping. Original pull request: #150.
This commit is contained in:
committed by
Mark Paluch
parent
aea39d99c5
commit
b1c68d901e
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,470 +15,32 @@
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.sql.JDBCType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcValue;
|
||||
import org.springframework.data.jdbc.support.JdbcUtil;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.domain.Identifier;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
||||
import org.springframework.jdbc.support.JdbcUtils;
|
||||
import org.springframework.jdbc.support.KeyHolder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The default {@link DataAccessStrategy} is to generate SQL statements based on meta data from the entity.
|
||||
*
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
* @author Thomas Lang
|
||||
* @author Bastian Wilhelm
|
||||
* @deprecated Use {@link org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy} instead.
|
||||
*/
|
||||
public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
|
||||
private final @NonNull SqlGeneratorSource sqlGeneratorSource;
|
||||
private final @NonNull RelationalMappingContext context;
|
||||
private final @NonNull JdbcConverter converter;
|
||||
private final @NonNull NamedParameterJdbcOperations operations;
|
||||
private final @NonNull DataAccessStrategy accessStrategy;
|
||||
@Deprecated
|
||||
public class DefaultDataAccessStrategy extends org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy {
|
||||
|
||||
/**
|
||||
* Creates a {@link DefaultDataAccessStrategy} which references it self for resolution of recursive data accesses.
|
||||
* Only suitable if this is the only access strategy in use.
|
||||
*
|
||||
* @param sqlGeneratorSource must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @param operations must not be {@literal null}.
|
||||
*/
|
||||
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, RelationalMappingContext context,
|
||||
JdbcConverter converter, NamedParameterJdbcOperations operations) {
|
||||
this(sqlGeneratorSource, context, converter, operations, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link DefaultDataAccessStrategy}
|
||||
*
|
||||
* @param sqlGeneratorSource must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param mappingAccessStrategy can be {@literal null}.
|
||||
* @since 1.1
|
||||
*/
|
||||
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, RelationalMappingContext context,
|
||||
JdbcConverter converter, NamedParameterJdbcOperations operations,
|
||||
@Nullable DataAccessStrategy mappingAccessStrategy) {
|
||||
|
||||
Assert.notNull(sqlGeneratorSource, "SqlGeneratorSource must not be null");
|
||||
Assert.notNull(context, "RelationalMappingContext must not be null");
|
||||
Assert.notNull(converter, "JdbcConverter must not be null");
|
||||
Assert.notNull(operations, "NamedParameterJdbcOperations must not be null");
|
||||
|
||||
this.sqlGeneratorSource = sqlGeneratorSource;
|
||||
this.context = context;
|
||||
this.converter = converter;
|
||||
this.operations = operations;
|
||||
this.accessStrategy = mappingAccessStrategy == null ? this : mappingAccessStrategy;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
|
||||
return insert(instance, domainType, Identifier.from(additionalParameters));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public <T> Object insert(T instance, Class<T> domainType, Identifier identifier) {
|
||||
|
||||
KeyHolder holder = new GeneratedKeyHolder();
|
||||
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
|
||||
|
||||
MapSqlParameterSource parameterSource = getParameterSource(instance, persistentEntity, "",
|
||||
PersistentProperty::isIdProperty);
|
||||
|
||||
identifier.forEach((name, value, type) -> addConvertedPropertyValue(parameterSource, name, value, type));
|
||||
|
||||
Object idValue = getIdValueOrNull(instance, persistentEntity);
|
||||
if (idValue != null) {
|
||||
|
||||
RelationalPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
|
||||
addConvertedPropertyValue(parameterSource, idProperty, idValue, idProperty.getColumnName());
|
||||
}
|
||||
|
||||
operations.update( //
|
||||
sql(domainType).getInsert(new HashSet<>(Arrays.asList(parameterSource.getParameterNames()))), //
|
||||
parameterSource, //
|
||||
holder //
|
||||
);
|
||||
|
||||
return getIdFromHolder(holder, persistentEntity);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <S> boolean update(S instance, Class<S> domainType) {
|
||||
|
||||
RelationalPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType);
|
||||
|
||||
return operations.update(sql(domainType).getUpdate(),
|
||||
getParameterSource(instance, persistentEntity, "", Predicates.includeAll())) != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public void delete(Object id, Class<?> domainType) {
|
||||
|
||||
String deleteByIdSql = sql(domainType).getDeleteById();
|
||||
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
|
||||
|
||||
operations.update(deleteByIdSql, parameter);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, org.springframework.data.mapping.PropertyPath)
|
||||
*/
|
||||
@Override
|
||||
public void delete(Object rootId, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
|
||||
RelationalPersistentEntity<?> rootEntity = context
|
||||
.getRequiredPersistentEntity(propertyPath.getBaseProperty().getOwner().getType());
|
||||
|
||||
RelationalPersistentProperty referencingProperty = propertyPath.getLeafProperty();
|
||||
Assert.notNull(referencingProperty, "No property found matching the PropertyPath " + propertyPath);
|
||||
|
||||
String format = sql(rootEntity.getType()).createDeleteByPath(propertyPath);
|
||||
|
||||
HashMap<String, Object> parameters = new HashMap<>();
|
||||
parameters.put("rootId", rootId);
|
||||
operations.update(format, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> void deleteAll(Class<T> domainType) {
|
||||
operations.getJdbcOperations().update(sql(domainType).createDeleteAllSql(null));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(org.springframework.data.mapping.PropertyPath)
|
||||
*/
|
||||
@Override
|
||||
public void deleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
operations.getJdbcOperations()
|
||||
.update(sql(propertyPath.getBaseProperty().getOwner().getType()).createDeleteAllSql(propertyPath));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#count(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public long count(Class<?> domainType) {
|
||||
|
||||
Long result = operations.getJdbcOperations().queryForObject(sql(domainType).getCount(), Long.class);
|
||||
|
||||
Assert.notNull(result, "The result of a count query must not be null.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findById(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T findById(Object id, Class<T> domainType) {
|
||||
|
||||
String findOneSql = sql(domainType).getFindOne();
|
||||
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
|
||||
|
||||
try {
|
||||
return operations.queryForObject(findOneSql, parameter, (RowMapper<T>) getEntityRowMapper(domainType));
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAll(java.lang.Class)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> Iterable<T> findAll(Class<T> domainType) {
|
||||
return operations.query(sql(domainType).getFindAll(), (RowMapper<T>) getEntityRowMapper(domainType));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllById(java.lang.Iterable, java.lang.Class)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
|
||||
|
||||
RelationalPersistentProperty idProperty = getRequiredPersistentEntity(domainType).getRequiredIdProperty();
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
|
||||
addConvertedPropertyValuesAsList(parameterSource, idProperty, ids, "ids");
|
||||
|
||||
String findAllInListSql = sql(domainType).getFindAllInList();
|
||||
|
||||
return operations.query(findAllInListSql, parameterSource, (RowMapper<T>) getEntityRowMapper(domainType));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllByProperty(java.lang.Object, org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Iterable<T> findAllByProperty(Object rootId, RelationalPersistentProperty property) {
|
||||
|
||||
Assert.notNull(rootId, "rootId must not be null.");
|
||||
|
||||
Class<?> actualType = property.getActualType();
|
||||
String findAllByProperty = sql(actualType) //
|
||||
.getFindAllByProperty(property.getReverseColumnName(), property.getKeyColumn(), property.isOrdered());
|
||||
|
||||
MapSqlParameterSource parameter = new MapSqlParameterSource(property.getReverseColumnName(), rootId);
|
||||
|
||||
return operations.query(findAllByProperty, parameter, //
|
||||
(RowMapper<T>) (property.isMap() //
|
||||
? this.getMapEntityRowMapper(property) //
|
||||
: this.getEntityRowMapper(actualType)));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#existsById(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> boolean existsById(Object id, Class<T> domainType) {
|
||||
|
||||
String existsSql = sql(domainType).getExists();
|
||||
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
|
||||
|
||||
Boolean result = operations.queryForObject(existsSql, parameter, Boolean.class);
|
||||
|
||||
Assert.notNull(result, "The result of an exists query must not be null");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private <S, T> MapSqlParameterSource getParameterSource(S instance, RelationalPersistentEntity<S> persistentEntity,
|
||||
String prefix, Predicate<RelationalPersistentProperty> skipProperty) {
|
||||
|
||||
MapSqlParameterSource parameters = new MapSqlParameterSource();
|
||||
|
||||
PersistentPropertyAccessor<S> propertyAccessor = persistentEntity.getPropertyAccessor(instance);
|
||||
|
||||
persistentEntity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {
|
||||
|
||||
if (skipProperty.test(property)) {
|
||||
return;
|
||||
}
|
||||
if (property.isEntity() && !property.isEmbedded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (property.isEmbedded()) {
|
||||
|
||||
Object value = propertyAccessor.getProperty(property);
|
||||
RelationalPersistentEntity<?> embeddedEntity = context.getPersistentEntity(property.getType());
|
||||
MapSqlParameterSource additionalParameters = getParameterSource((T) value,
|
||||
(RelationalPersistentEntity<T>) embeddedEntity, prefix + property.getEmbeddedPrefix(), skipProperty);
|
||||
parameters.addValues(additionalParameters.getValues());
|
||||
} else {
|
||||
|
||||
Object value = propertyAccessor.getProperty(property);
|
||||
String paramName = prefix + property.getColumnName();
|
||||
|
||||
addConvertedPropertyValue(parameters, property, value, paramName);
|
||||
}
|
||||
});
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private <S, ID> ID getIdValueOrNull(S instance, RelationalPersistentEntity<S> persistentEntity) {
|
||||
|
||||
ID idValue = (ID) persistentEntity.getIdentifierAccessor(instance).getIdentifier();
|
||||
|
||||
return isIdPropertyNullOrScalarZero(idValue, persistentEntity) ? null : idValue;
|
||||
}
|
||||
|
||||
private static <S, ID> boolean isIdPropertyNullOrScalarZero(@Nullable ID idValue,
|
||||
RelationalPersistentEntity<S> persistentEntity) {
|
||||
|
||||
RelationalPersistentProperty idProperty = persistentEntity.getIdProperty();
|
||||
return idValue == null //
|
||||
|| idProperty == null //
|
||||
|| (idProperty.getType() == int.class && idValue.equals(0)) //
|
||||
|| (idProperty.getType() == long.class && idValue.equals(0L));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <S> Object getIdFromHolder(KeyHolder holder, RelationalPersistentEntity<S> persistentEntity) {
|
||||
|
||||
try {
|
||||
// MySQL just returns one value with a special name
|
||||
return holder.getKey();
|
||||
} catch (DataRetrievalFailureException | InvalidDataAccessApiUsageException e) {
|
||||
// Postgres returns a value for each column
|
||||
// MS SQL Server returns a value that might be null.
|
||||
|
||||
Map<String, Object> keys = holder.getKeys();
|
||||
|
||||
if (keys == null || persistentEntity.getIdProperty() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return keys.get(persistentEntity.getIdColumn());
|
||||
}
|
||||
}
|
||||
|
||||
private EntityRowMapper<?> getEntityRowMapper(Class<?> domainType) {
|
||||
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context, converter, accessStrategy);
|
||||
}
|
||||
|
||||
private RowMapper<?> getMapEntityRowMapper(RelationalPersistentProperty property) {
|
||||
|
||||
String keyColumn = property.getKeyColumn();
|
||||
Assert.notNull(keyColumn, () -> "KeyColumn must not be null for " + property);
|
||||
|
||||
return new MapEntityRowMapper<>(getEntityRowMapper(property.getActualType()), keyColumn);
|
||||
}
|
||||
|
||||
private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> domainType) {
|
||||
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
|
||||
addConvertedPropertyValue( //
|
||||
parameterSource, //
|
||||
getRequiredPersistentEntity(domainType).getRequiredIdProperty(), //
|
||||
id, //
|
||||
"id" //
|
||||
);
|
||||
return parameterSource;
|
||||
}
|
||||
|
||||
private void addConvertedPropertyValue(MapSqlParameterSource parameterSource, RelationalPersistentProperty property,
|
||||
Object value, String paramName) {
|
||||
|
||||
JdbcValue jdbcValue = converter.writeJdbcValue( //
|
||||
value, //
|
||||
property.getColumnType(), //
|
||||
property.getSqlType() //
|
||||
);
|
||||
|
||||
parameterSource.addValue(paramName, jdbcValue.getValue(), JdbcUtil.sqlTypeFor(jdbcValue.getJdbcType()));
|
||||
}
|
||||
|
||||
private void addConvertedPropertyValue(MapSqlParameterSource parameterSource, String name, Object value,
|
||||
Class<?> type) {
|
||||
|
||||
JdbcValue jdbcValue = converter.writeJdbcValue( //
|
||||
value, //
|
||||
type, //
|
||||
JdbcUtil.sqlTypeFor(type) //
|
||||
);
|
||||
|
||||
parameterSource.addValue( //
|
||||
name, //
|
||||
jdbcValue.getValue(), //
|
||||
JdbcUtil.sqlTypeFor(jdbcValue.getJdbcType()) //
|
||||
);
|
||||
}
|
||||
|
||||
private void addConvertedPropertyValuesAsList(MapSqlParameterSource parameterSource,
|
||||
RelationalPersistentProperty property, Iterable<?> values, String paramName) {
|
||||
|
||||
List<Object> convertedIds = new ArrayList<>();
|
||||
JdbcValue jdbcValue = null;
|
||||
for (Object id : values) {
|
||||
|
||||
Class<?> columnType = property.getColumnType();
|
||||
int sqlType = property.getSqlType();
|
||||
|
||||
jdbcValue = converter.writeJdbcValue(id, columnType, sqlType);
|
||||
convertedIds.add(jdbcValue.getValue());
|
||||
}
|
||||
|
||||
Assert.notNull(jdbcValue, "JdbcValue must be not null at this point. Please report this as a bug.");
|
||||
|
||||
JDBCType jdbcType = jdbcValue.getJdbcType();
|
||||
int typeNumber = jdbcType == null ? JdbcUtils.TYPE_UNKNOWN : jdbcType.getVendorTypeNumber();
|
||||
|
||||
parameterSource.addValue(paramName, convertedIds, typeNumber);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <S> RelationalPersistentEntity<S> getRequiredPersistentEntity(Class<S> domainType) {
|
||||
return (RelationalPersistentEntity<S>) context.getRequiredPersistentEntity(domainType);
|
||||
}
|
||||
|
||||
private SqlGenerator sql(Class<?> domainType) {
|
||||
return sqlGeneratorSource.getSqlGenerator(domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to create {@link Predicate}s.
|
||||
*/
|
||||
static class Predicates {
|
||||
|
||||
/**
|
||||
* Include all {@link Predicate} returning {@literal false} to never skip a property.
|
||||
* Creates a {@link org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy} which references it self for resolution of recursive data accesses.
|
||||
* Only suitable if this is the only access strategy in use.
|
||||
*
|
||||
* @return the include all {@link Predicate}.
|
||||
* @param sqlGeneratorSource must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @param operations must not be {@literal null}.
|
||||
*/
|
||||
static Predicate<RelationalPersistentProperty> includeAll() {
|
||||
return it -> false;
|
||||
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, RelationalMappingContext context,
|
||||
JdbcConverter converter, NamedParameterJdbcOperations operations) {
|
||||
super(sqlGeneratorSource, context, converter, operations);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import lombok.RequiredArgsConstructor;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.jdbc.core.convert.JdbcIdentifierBuilder;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.conversion.DbAction;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Delete;
|
||||
@@ -33,11 +32,12 @@ import org.springframework.data.relational.core.conversion.DbAction.Merge;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Update;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.UpdateRoot;
|
||||
import org.springframework.data.relational.core.conversion.Interpreter;
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.domain.Identifier;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link Interpreter} for {@link DbAction}s using a {@link DataAccessStrategy} for performing actual database
|
||||
@@ -144,38 +144,67 @@ class DefaultJdbcInterpreter implements Interpreter {
|
||||
|
||||
private Identifier getParentKeys(DbAction.WithDependingOn<?> action) {
|
||||
|
||||
DbAction.WithEntity<?> dependingOn = action.getDependingOn();
|
||||
Object id = getParentId(action);
|
||||
|
||||
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(dependingOn.getEntityType());
|
||||
|
||||
Object id = getIdFromEntityDependingOn(dependingOn, persistentEntity);
|
||||
JdbcIdentifierBuilder identifier = JdbcIdentifierBuilder //
|
||||
.forBackReferences(action.getPropertyPath(), id);
|
||||
.forBackReferences(new PersistentPropertyPathExtension(context, action.getPropertyPath()), id);
|
||||
|
||||
for (Map.Entry<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifier : action.getQualifiers()
|
||||
.entrySet()) {
|
||||
identifier = identifier.withQualifier(qualifier.getKey(), qualifier.getValue());
|
||||
identifier = identifier.withQualifier(new PersistentPropertyPathExtension(context, qualifier.getKey()),
|
||||
qualifier.getValue());
|
||||
}
|
||||
|
||||
return identifier.build();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object getIdFromEntityDependingOn(DbAction.WithEntity<?> dependingOn,
|
||||
RelationalPersistentEntity<?> persistentEntity) {
|
||||
private Object getParentId(DbAction.WithDependingOn<?> action) {
|
||||
|
||||
Object entity = dependingOn.getEntity();
|
||||
PersistentPropertyPathExtension path = new PersistentPropertyPathExtension(context, action.getPropertyPath());
|
||||
PersistentPropertyPathExtension idPath = path.getIdDefiningParentPath();
|
||||
|
||||
if (dependingOn instanceof DbAction.WithGeneratedId) {
|
||||
DbAction.WithEntity idOwningAction = getIdOwningAction(action, idPath);
|
||||
|
||||
Object generatedId = ((DbAction.WithGeneratedId<?>) dependingOn).getGeneratedId();
|
||||
return getIdFrom(idOwningAction);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private DbAction.WithEntity getIdOwningAction(DbAction.WithEntity action, PersistentPropertyPathExtension idPath) {
|
||||
|
||||
if (!(action instanceof DbAction.WithDependingOn)) {
|
||||
|
||||
Assert.state(idPath.getLength() == 0,
|
||||
"When the id path is not empty the id providing action should be of type WithDependingOn");
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
DbAction.WithDependingOn withDependingOn = (DbAction.WithDependingOn) action;
|
||||
|
||||
if (idPath.matches(withDependingOn.getPropertyPath())) {
|
||||
return action;
|
||||
}
|
||||
|
||||
return getIdOwningAction(withDependingOn.getDependingOn(), idPath);
|
||||
}
|
||||
|
||||
private Object getIdFrom(DbAction.WithEntity idOwningAction) {
|
||||
|
||||
if (idOwningAction instanceof DbAction.WithGeneratedId) {
|
||||
|
||||
Object generatedId = ((DbAction.WithGeneratedId<?>) idOwningAction).getGeneratedId();
|
||||
|
||||
if (generatedId != null) {
|
||||
return generatedId;
|
||||
}
|
||||
}
|
||||
|
||||
return persistentEntity.getIdentifierAccessor(entity).getIdentifier();
|
||||
}
|
||||
RelationalPersistentEntity<?> persistentEntity = context
|
||||
.getRequiredPersistentEntity(idOwningAction.getEntityType());
|
||||
Object identifier = persistentEntity.getIdentifierAccessor(idOwningAction.getEntity()).getIdentifier();
|
||||
|
||||
Assert.state(identifier != null, "Couldn't get obtain a required id value");
|
||||
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,209 +15,20 @@
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PreferredConstructor;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Maps a {@link ResultSet} to an entity of type {@code T}, including entities referenced. This {@link RowMapper} might
|
||||
* trigger additional SQL statements in order to load other members of the same aggregate.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Maciej Walkowiak
|
||||
* @author Bastian Wilhelm
|
||||
*
|
||||
* @deprecated Use {@link org.springframework.data.jdbc.core.convert.EntityRowMapper} instead.
|
||||
*/
|
||||
public class EntityRowMapper<T> implements RowMapper<T> {
|
||||
@Deprecated
|
||||
public class EntityRowMapper<T> extends org.springframework.data.jdbc.core.convert.EntityRowMapper<T> {
|
||||
|
||||
private static final Converter<Iterable<?>, Map<?, ?>> ITERABLE_OF_ENTRY_TO_MAP_CONVERTER = new IterableOfEntryToMapConverter();
|
||||
|
||||
private final RelationalPersistentEntity<T> entity;
|
||||
|
||||
private final RelationalConverter converter;
|
||||
private final RelationalMappingContext context;
|
||||
private final DataAccessStrategy accessStrategy;
|
||||
private final RelationalPersistentProperty idProperty;
|
||||
|
||||
public EntityRowMapper(RelationalPersistentEntity<T> entity, RelationalMappingContext context,
|
||||
RelationalConverter converter, DataAccessStrategy accessStrategy) {
|
||||
|
||||
this.entity = entity;
|
||||
this.converter = converter;
|
||||
this.context = context;
|
||||
this.accessStrategy = accessStrategy;
|
||||
this.idProperty = entity.getIdProperty();
|
||||
public EntityRowMapper(RelationalPersistentEntity<T> entity, JdbcConverter converter,
|
||||
DataAccessStrategy accessStrategy) {
|
||||
super(entity, converter, accessStrategy);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
|
||||
*/
|
||||
@Override
|
||||
public T mapRow(ResultSet resultSet, int rowNumber) {
|
||||
|
||||
String prefix = "";
|
||||
|
||||
RelationalPersistentProperty idProperty = entity.getIdProperty();
|
||||
|
||||
Object idValue = null;
|
||||
if (idProperty != null) {
|
||||
idValue = readFrom(resultSet, idProperty, prefix);
|
||||
}
|
||||
|
||||
T result = createInstance(entity, resultSet, idValue, prefix);
|
||||
|
||||
return entity.requiresPropertyPopulation() //
|
||||
? populateProperties(result, resultSet) //
|
||||
: result;
|
||||
}
|
||||
|
||||
private T populateProperties(T result, ResultSet resultSet) {
|
||||
|
||||
PersistentPropertyAccessor<T> propertyAccessor = converter.getPropertyAccessor(entity, result);
|
||||
|
||||
Object id = idProperty == null ? null : readFrom(resultSet, idProperty, "");
|
||||
|
||||
PreferredConstructor<T, RelationalPersistentProperty> persistenceConstructor = entity.getPersistenceConstructor();
|
||||
|
||||
for (RelationalPersistentProperty property : entity) {
|
||||
|
||||
if (persistenceConstructor != null && persistenceConstructor.isConstructorParameter(property)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
propertyAccessor.setProperty(property, readOrLoadProperty(resultSet, id, property, ""));
|
||||
}
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object readOrLoadProperty(ResultSet resultSet, @Nullable Object id, RelationalPersistentProperty property,
|
||||
String prefix) {
|
||||
|
||||
if (property.isCollectionLike() && property.isEntity() && id != null) {
|
||||
return accessStrategy.findAllByProperty(id, property);
|
||||
} else if (property.isMap() && id != null) {
|
||||
return ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(accessStrategy.findAllByProperty(id, property));
|
||||
} else if (property.isEmbedded()) {
|
||||
return readEmbeddedEntityFrom(resultSet, id, property, prefix);
|
||||
} else {
|
||||
return readFrom(resultSet, property, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single value or a complete Entity from the {@link ResultSet} passed as an argument.
|
||||
*
|
||||
* @param resultSet the {@link ResultSet} to extract the value from. Must not be {@code null}.
|
||||
* @param property the {@link RelationalPersistentProperty} for which the value is intended. Must not be {@code null}.
|
||||
* @param prefix to be used for all column names accessed by this method. Must not be {@code null}.
|
||||
* @return the value read from the {@link ResultSet}. May be {@code null}.
|
||||
*/
|
||||
@Nullable
|
||||
private Object readFrom(ResultSet resultSet, RelationalPersistentProperty property, String prefix) {
|
||||
|
||||
if (property.isEntity()) {
|
||||
return readEntityFrom(resultSet, property, prefix);
|
||||
}
|
||||
|
||||
Object value = getObjectFromResultSet(resultSet, prefix + property.getColumnName());
|
||||
return converter.readValue(value, property.getTypeInformation());
|
||||
|
||||
}
|
||||
|
||||
private Object readEmbeddedEntityFrom(ResultSet rs, @Nullable Object id, RelationalPersistentProperty property,
|
||||
String prefix) {
|
||||
|
||||
String newPrefix = prefix + property.getEmbeddedPrefix();
|
||||
|
||||
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(property.getActualType());
|
||||
|
||||
Object instance = createInstance(entity, rs, null, newPrefix);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
PersistentPropertyAccessor<?> accessor = converter.getPropertyAccessor((PersistentEntity<Object, ?>) entity,
|
||||
instance);
|
||||
|
||||
for (RelationalPersistentProperty p : entity) {
|
||||
accessor.setProperty(p, readOrLoadProperty(rs, id, p, newPrefix));
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <S> S readEntityFrom(ResultSet rs, RelationalPersistentProperty property, String prefix) {
|
||||
|
||||
String newPrefix = prefix + property.getName() + "_";
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
RelationalPersistentEntity<S> entity = (RelationalPersistentEntity<S>) context
|
||||
.getRequiredPersistentEntity(property.getActualType());
|
||||
|
||||
RelationalPersistentProperty idProperty = entity.getIdProperty();
|
||||
|
||||
Object idValue = null;
|
||||
|
||||
if (idProperty != null) {
|
||||
idValue = readFrom(rs, idProperty, newPrefix);
|
||||
}
|
||||
|
||||
if ((idProperty != null //
|
||||
? idValue //
|
||||
: getObjectFromResultSet(rs, newPrefix + property.getReverseColumnName()) //
|
||||
) == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
S instance = createInstance(entity, rs, idValue, newPrefix);
|
||||
|
||||
PersistentPropertyAccessor<S> accessor = converter.getPropertyAccessor(entity, instance);
|
||||
|
||||
for (RelationalPersistentProperty p : entity) {
|
||||
accessor.setProperty(p, readOrLoadProperty(rs, idValue, p, newPrefix));
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object getObjectFromResultSet(ResultSet rs, String backreferenceName) {
|
||||
|
||||
try {
|
||||
return rs.getObject(backreferenceName);
|
||||
} catch (SQLException o_O) {
|
||||
throw new MappingException(String.format("Could not read value %s from result set!", backreferenceName), o_O);
|
||||
}
|
||||
}
|
||||
|
||||
private <S> S createInstance(RelationalPersistentEntity<S> entity, ResultSet rs, @Nullable Object idValue,
|
||||
String prefix) {
|
||||
|
||||
return converter.createInstance(entity, parameter -> {
|
||||
|
||||
String parameterName = parameter.getName();
|
||||
|
||||
Assert.notNull(parameterName, "A constructor parameter name must not be null to be used with Spring Data JDBC");
|
||||
|
||||
RelationalPersistentProperty property = entity.getRequiredPersistentProperty(parameterName);
|
||||
|
||||
return readOrLoadProperty(rs, idValue, property, prefix);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
package org.springframework.data.jdbc.core;
|
||||
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.domain.Identifier;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Builder for {@link Identifier}. Mainly for internal use within the framework
|
||||
@@ -41,30 +43,17 @@ public class JdbcIdentifierBuilder {
|
||||
/**
|
||||
* Creates ParentKeys with backreference for the given path and value of the parents id.
|
||||
*/
|
||||
public static JdbcIdentifierBuilder forBackReferences(PersistentPropertyPath<RelationalPersistentProperty> path,
|
||||
@Nullable Object value) {
|
||||
public static JdbcIdentifierBuilder forBackReferences(PersistentPropertyPathExtension path, @Nullable Object value) {
|
||||
|
||||
Identifier identifier = Identifier.of( //
|
||||
path.getRequiredLeafProperty().getReverseColumnName(), //
|
||||
path.getReverseColumnName(), //
|
||||
value, //
|
||||
getLastIdProperty(path).getColumnType() //
|
||||
path.getIdDefiningParentPath().getRequiredIdProperty().getColumnType() //
|
||||
);
|
||||
|
||||
return new JdbcIdentifierBuilder(identifier);
|
||||
}
|
||||
|
||||
public JdbcIdentifierBuilder withQualifier(PersistentPropertyPath<RelationalPersistentProperty> path, Object value) {
|
||||
|
||||
RelationalPersistentProperty leafProperty = path.getRequiredLeafProperty();
|
||||
identifier = identifier.withPart(leafProperty.getKeyColumn(), value, leafProperty.getQualifierColumnType());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Identifier build() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
private static RelationalPersistentProperty getLastIdProperty(
|
||||
PersistentPropertyPath<RelationalPersistentProperty> path) {
|
||||
|
||||
@@ -76,4 +65,25 @@ public class JdbcIdentifierBuilder {
|
||||
|
||||
return getLastIdProperty(path.getParentPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a qualifier to the identifier to build. A qualifier is a map key or a list index.
|
||||
*
|
||||
* @param path path to the map that gets qualified by {@code value}. Must not be {@literal null}.
|
||||
* @param value map key or list index qualifying the map identified by {@code path}. Must not be {@literal null}.
|
||||
* @return this builder. Guaranteed to be not {@literal null}.
|
||||
*/
|
||||
public JdbcIdentifierBuilder withQualifier(PersistentPropertyPathExtension path, Object value) {
|
||||
|
||||
Assert.notNull(path, "Path must not be null");
|
||||
Assert.notNull(value, "Value must not be null");
|
||||
|
||||
identifier = identifier.withPart(path.getKeyColumn(), value, path.getQualifierColumnType());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Identifier build() {
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
@@ -15,22 +15,32 @@
|
||||
*/
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.sql.Array;
|
||||
import java.sql.JDBCType;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.mapping.AggregateReference;
|
||||
import org.springframework.data.jdbc.support.JdbcUtil;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PreferredConstructor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
@@ -53,7 +63,7 @@ import org.springframework.util.Assert;
|
||||
public class BasicJdbcConverter extends BasicRelationalConverter implements JdbcConverter {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BasicJdbcConverter.class);
|
||||
|
||||
private static final Converter<Iterable<?>, Map<?, ?>> ITERABLE_OF_ENTRY_TO_MAP_CONVERTER = new IterableOfEntryToMapConverter();
|
||||
private final JdbcTypeFactory typeFactory;
|
||||
|
||||
/**
|
||||
@@ -238,4 +248,194 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
|
||||
*/
|
||||
@Override
|
||||
public <T> T mapRow(RelationalPersistentEntity<T> entity, DataAccessStrategy accessStrategy, ResultSet resultSet) {
|
||||
return new ReadingContext<T>(entity, accessStrategy, resultSet).mapRow();
|
||||
}
|
||||
|
||||
@Value
|
||||
private class ReadingContext<T> {
|
||||
|
||||
private final RelationalPersistentEntity<T> entity;
|
||||
private final RelationalPersistentProperty idProperty;
|
||||
|
||||
private final ResultSet resultSet;
|
||||
PersistentPropertyPathExtension path;
|
||||
private final DataAccessStrategy accessStrategy;
|
||||
|
||||
ReadingContext(RelationalPersistentEntity<T> entity, DataAccessStrategy accessStrategy, ResultSet resultSet) {
|
||||
|
||||
this.entity = entity;
|
||||
this.idProperty = entity.getIdProperty();
|
||||
this.accessStrategy = accessStrategy;
|
||||
this.resultSet = resultSet;
|
||||
this.path = new PersistentPropertyPathExtension((MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty>) getMappingContext(), entity);
|
||||
}
|
||||
|
||||
public ReadingContext(RelationalPersistentEntity<T> entity, DataAccessStrategy accessStrategy, ResultSet resultSet, PersistentPropertyPathExtension path) {
|
||||
|
||||
this.entity = entity;
|
||||
this.idProperty = entity.getIdProperty();
|
||||
this.accessStrategy = accessStrategy;
|
||||
this.resultSet = resultSet;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
private ReadingContext extendBy(RelationalPersistentProperty property) {
|
||||
return new ReadingContext(entity, accessStrategy, resultSet, path.extendBy(property));
|
||||
}
|
||||
|
||||
T mapRow() {
|
||||
|
||||
RelationalPersistentProperty idProperty = entity.getIdProperty();
|
||||
|
||||
Object idValue = null;
|
||||
if (idProperty != null) {
|
||||
idValue = readFrom(idProperty);
|
||||
}
|
||||
|
||||
T result = createInstanceInternal(entity, idValue);
|
||||
|
||||
return entity.requiresPropertyPopulation() //
|
||||
? populateProperties(result) //
|
||||
: result;
|
||||
}
|
||||
|
||||
private T populateProperties(T result) {
|
||||
|
||||
PersistentPropertyAccessor<T> propertyAccessor = getPropertyAccessor(entity, result);
|
||||
|
||||
Object id = idProperty == null ? null : readFrom(idProperty);
|
||||
|
||||
PreferredConstructor<T, RelationalPersistentProperty> persistenceConstructor = entity.getPersistenceConstructor();
|
||||
|
||||
for (RelationalPersistentProperty property : entity) {
|
||||
|
||||
if (persistenceConstructor != null && persistenceConstructor.isConstructorParameter(property)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
propertyAccessor.setProperty(property, readOrLoadProperty(id, property));
|
||||
}
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object readOrLoadProperty(@Nullable Object id, RelationalPersistentProperty property) {
|
||||
|
||||
if (property.isCollectionLike() && property.isEntity() && id != null) {
|
||||
return accessStrategy.findAllByProperty(id, property);
|
||||
} else if (property.isMap() && id != null) {
|
||||
return ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(accessStrategy.findAllByProperty(id, property));
|
||||
} else if (property.isEmbedded()) {
|
||||
return readEmbeddedEntityFrom(id, property);
|
||||
} else {
|
||||
return readFrom(property);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single value or a complete Entity from the {@link ResultSet} passed as an argument.
|
||||
*
|
||||
* @param property the {@link RelationalPersistentProperty} for which the value is intended. Must not be
|
||||
* {@code null}.
|
||||
* @return the value read from the {@link ResultSet}. May be {@code null}.
|
||||
*/
|
||||
@Nullable
|
||||
private Object readFrom(RelationalPersistentProperty property) {
|
||||
|
||||
if (property.isEntity()) {
|
||||
return readEntityFrom(property, path);
|
||||
}
|
||||
|
||||
Object value = getObjectFromResultSet(path.extendBy(property).getColumnAlias());
|
||||
return readValue(value, property.getTypeInformation());
|
||||
|
||||
}
|
||||
|
||||
private Object readEmbeddedEntityFrom(@Nullable Object id, RelationalPersistentProperty property) {
|
||||
|
||||
ReadingContext newContext = extendBy(property);
|
||||
|
||||
RelationalPersistentEntity<?> entity = getMappingContext().getRequiredPersistentEntity(property.getActualType());
|
||||
|
||||
Object instance = newContext.createInstanceInternal(entity, null);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
PersistentPropertyAccessor<?> accessor = getPropertyAccessor((PersistentEntity<Object, ?>) entity, instance);
|
||||
|
||||
for (RelationalPersistentProperty p : entity) {
|
||||
accessor.setProperty(p, newContext.readOrLoadProperty(id, p));
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <S> S readEntityFrom(RelationalPersistentProperty property, PersistentPropertyPathExtension path) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ReadingContext<S> newContext = extendBy(property);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
RelationalPersistentEntity<S> entity = (RelationalPersistentEntity<S>) getMappingContext()
|
||||
.getRequiredPersistentEntity(property.getActualType());
|
||||
|
||||
RelationalPersistentProperty idProperty = entity.getIdProperty();
|
||||
|
||||
Object idValue = null;
|
||||
|
||||
if (idProperty != null) {
|
||||
idValue = newContext.readFrom(idProperty);
|
||||
}
|
||||
|
||||
if ((idProperty != null //
|
||||
? idValue //
|
||||
: newContext.getObjectFromResultSet(path.extendBy(property).getReverseColumnNameAlias()) //
|
||||
) == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
S instance = newContext.createInstanceInternal(entity, idValue);
|
||||
|
||||
PersistentPropertyAccessor<S> accessor = getPropertyAccessor(entity, instance);
|
||||
|
||||
for (RelationalPersistentProperty p : entity) {
|
||||
accessor.setProperty(p, newContext.readOrLoadProperty(idValue, p));
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object getObjectFromResultSet(String backreferenceName) {
|
||||
|
||||
try {
|
||||
return resultSet.getObject(backreferenceName);
|
||||
} catch (SQLException o_O) {
|
||||
throw new MappingException(String.format("Could not read value %s from result set!", backreferenceName), o_O);
|
||||
}
|
||||
}
|
||||
|
||||
private <S> S createInstanceInternal(RelationalPersistentEntity<S> entity, @Nullable Object idValue) {
|
||||
|
||||
return createInstance(entity,parameter -> {
|
||||
|
||||
String parameterName = parameter.getName();
|
||||
|
||||
Assert.notNull(parameterName, "A constructor parameter name must not be null to be used with Spring Data JDBC");
|
||||
|
||||
RelationalPersistentProperty property = entity.getRequiredPersistentProperty(parameterName);
|
||||
|
||||
return readOrLoadProperty(idValue, property);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,483 @@
|
||||
/*
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.sql.JDBCType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.support.JdbcUtil;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.domain.Identifier;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
||||
import org.springframework.jdbc.support.JdbcUtils;
|
||||
import org.springframework.jdbc.support.KeyHolder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The default {@link DataAccessStrategy} is to generate SQL statements based on meta data from the entity.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
* @author Thomas Lang
|
||||
* @author Bastian Wilhelm
|
||||
*/
|
||||
public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
|
||||
private final @NonNull SqlGeneratorSource sqlGeneratorSource;
|
||||
private final @NonNull RelationalMappingContext context;
|
||||
private final @NonNull JdbcConverter converter;
|
||||
private final @NonNull NamedParameterJdbcOperations operations;
|
||||
private final @NonNull DataAccessStrategy accessStrategy;
|
||||
|
||||
/**
|
||||
* Creates a {@link DefaultDataAccessStrategy} which references it self for resolution of recursive data accesses.
|
||||
* Only suitable if this is the only access strategy in use.
|
||||
*
|
||||
* @param sqlGeneratorSource must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @param operations must not be {@literal null}.
|
||||
*/
|
||||
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, RelationalMappingContext context,
|
||||
JdbcConverter converter, NamedParameterJdbcOperations operations) {
|
||||
this(sqlGeneratorSource, context, converter, operations, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link DefaultDataAccessStrategy}
|
||||
*
|
||||
* @param sqlGeneratorSource must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param mappingAccessStrategy can be {@literal null}.
|
||||
* @since 1.1
|
||||
*/
|
||||
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, RelationalMappingContext context,
|
||||
JdbcConverter converter, NamedParameterJdbcOperations operations,
|
||||
@Nullable DataAccessStrategy mappingAccessStrategy) {
|
||||
|
||||
Assert.notNull(sqlGeneratorSource, "SqlGeneratorSource must not be null");
|
||||
Assert.notNull(context, "RelationalMappingContext must not be null");
|
||||
Assert.notNull(converter, "JdbcConverter must not be null");
|
||||
Assert.notNull(operations, "NamedParameterJdbcOperations must not be null");
|
||||
|
||||
this.sqlGeneratorSource = sqlGeneratorSource;
|
||||
this.context = context;
|
||||
this.converter = converter;
|
||||
this.operations = operations;
|
||||
this.accessStrategy = mappingAccessStrategy == null ? this : mappingAccessStrategy;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
|
||||
return insert(instance, domainType, Identifier.from(additionalParameters));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public <T> Object insert(T instance, Class<T> domainType, Identifier identifier) {
|
||||
|
||||
KeyHolder holder = new GeneratedKeyHolder();
|
||||
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
|
||||
|
||||
MapSqlParameterSource parameterSource = getParameterSource(instance, persistentEntity, "",
|
||||
PersistentProperty::isIdProperty);
|
||||
|
||||
identifier.forEach((name, value, type) -> addConvertedPropertyValue(parameterSource, name, value, type));
|
||||
|
||||
Object idValue = getIdValueOrNull(instance, persistentEntity);
|
||||
if (idValue != null) {
|
||||
|
||||
RelationalPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
|
||||
addConvertedPropertyValue(parameterSource, idProperty, idValue, idProperty.getColumnName());
|
||||
}
|
||||
|
||||
operations.update( //
|
||||
sql(domainType).getInsert(new HashSet<>(Arrays.asList(parameterSource.getParameterNames()))), //
|
||||
parameterSource, //
|
||||
holder //
|
||||
);
|
||||
|
||||
return getIdFromHolder(holder, persistentEntity);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <S> boolean update(S instance, Class<S> domainType) {
|
||||
|
||||
RelationalPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType);
|
||||
|
||||
return operations.update(sql(domainType).getUpdate(),
|
||||
getParameterSource(instance, persistentEntity, "", Predicates.includeAll())) != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public void delete(Object id, Class<?> domainType) {
|
||||
|
||||
String deleteByIdSql = sql(domainType).getDeleteById();
|
||||
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
|
||||
|
||||
operations.update(deleteByIdSql, parameter);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, org.springframework.data.mapping.PropertyPath)
|
||||
*/
|
||||
@Override
|
||||
public void delete(Object rootId, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
|
||||
RelationalPersistentEntity<?> rootEntity = context
|
||||
.getRequiredPersistentEntity(propertyPath.getBaseProperty().getOwner().getType());
|
||||
|
||||
RelationalPersistentProperty referencingProperty = propertyPath.getLeafProperty();
|
||||
Assert.notNull(referencingProperty, "No property found matching the PropertyPath " + propertyPath);
|
||||
|
||||
String format = sql(rootEntity.getType()).createDeleteByPath(propertyPath);
|
||||
|
||||
HashMap<String, Object> parameters = new HashMap<>();
|
||||
parameters.put("rootId", rootId);
|
||||
operations.update(format, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> void deleteAll(Class<T> domainType) {
|
||||
operations.getJdbcOperations().update(sql(domainType).createDeleteAllSql(null));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(org.springframework.data.mapping.PropertyPath)
|
||||
*/
|
||||
@Override
|
||||
public void deleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
operations.getJdbcOperations()
|
||||
.update(sql(propertyPath.getBaseProperty().getOwner().getType()).createDeleteAllSql(propertyPath));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#count(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public long count(Class<?> domainType) {
|
||||
|
||||
Long result = operations.getJdbcOperations().queryForObject(sql(domainType).getCount(), Long.class);
|
||||
|
||||
Assert.notNull(result, "The result of a count query must not be null.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findById(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T findById(Object id, Class<T> domainType) {
|
||||
|
||||
String findOneSql = sql(domainType).getFindOne();
|
||||
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
|
||||
|
||||
try {
|
||||
return operations.queryForObject(findOneSql, parameter, (RowMapper<T>) getEntityRowMapper(domainType));
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAll(java.lang.Class)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> Iterable<T> findAll(Class<T> domainType) {
|
||||
return operations.query(sql(domainType).getFindAll(), (RowMapper<T>) getEntityRowMapper(domainType));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllById(java.lang.Iterable, java.lang.Class)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
|
||||
|
||||
RelationalPersistentProperty idProperty = getRequiredPersistentEntity(domainType).getRequiredIdProperty();
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
|
||||
addConvertedPropertyValuesAsList(parameterSource, idProperty, ids, "ids");
|
||||
|
||||
String findAllInListSql = sql(domainType).getFindAllInList();
|
||||
|
||||
return operations.query(findAllInListSql, parameterSource, (RowMapper<T>) getEntityRowMapper(domainType));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllByProperty(java.lang.Object, org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Iterable<T> findAllByProperty(Object rootId, RelationalPersistentProperty property) {
|
||||
|
||||
Assert.notNull(rootId, "rootId must not be null.");
|
||||
|
||||
Class<?> actualType = property.getActualType();
|
||||
String findAllByProperty = sql(actualType) //
|
||||
.getFindAllByProperty(property.getReverseColumnName(), property.getKeyColumn(), property.isOrdered());
|
||||
|
||||
MapSqlParameterSource parameter = new MapSqlParameterSource(property.getReverseColumnName(), rootId);
|
||||
|
||||
return operations.query(findAllByProperty, parameter, //
|
||||
(RowMapper<T>) (property.isMap() //
|
||||
? this.getMapEntityRowMapper(property) //
|
||||
: this.getEntityRowMapper(actualType)));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#existsById(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> boolean existsById(Object id, Class<T> domainType) {
|
||||
|
||||
String existsSql = sql(domainType).getExists();
|
||||
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
|
||||
|
||||
Boolean result = operations.queryForObject(existsSql, parameter, Boolean.class);
|
||||
|
||||
Assert.notNull(result, "The result of an exists query must not be null");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private <S, T> MapSqlParameterSource getParameterSource(S instance, RelationalPersistentEntity<S> persistentEntity,
|
||||
String prefix, Predicate<RelationalPersistentProperty> skipProperty) {
|
||||
|
||||
MapSqlParameterSource parameters = new MapSqlParameterSource();
|
||||
|
||||
PersistentPropertyAccessor<S> propertyAccessor = persistentEntity.getPropertyAccessor(instance);
|
||||
|
||||
persistentEntity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {
|
||||
|
||||
if (skipProperty.test(property)) {
|
||||
return;
|
||||
}
|
||||
if (property.isEntity() && !property.isEmbedded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (property.isEmbedded()) {
|
||||
|
||||
Object value = propertyAccessor.getProperty(property);
|
||||
RelationalPersistentEntity<?> embeddedEntity = context.getPersistentEntity(property.getType());
|
||||
MapSqlParameterSource additionalParameters = getParameterSource((T) value,
|
||||
(RelationalPersistentEntity<T>) embeddedEntity, prefix + property.getEmbeddedPrefix(), skipProperty);
|
||||
parameters.addValues(additionalParameters.getValues());
|
||||
} else {
|
||||
|
||||
Object value = propertyAccessor.getProperty(property);
|
||||
String paramName = prefix + property.getColumnName();
|
||||
|
||||
addConvertedPropertyValue(parameters, property, value, paramName);
|
||||
}
|
||||
});
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private <S, ID> ID getIdValueOrNull(S instance, RelationalPersistentEntity<S> persistentEntity) {
|
||||
|
||||
ID idValue = (ID) persistentEntity.getIdentifierAccessor(instance).getIdentifier();
|
||||
|
||||
return isIdPropertyNullOrScalarZero(idValue, persistentEntity) ? null : idValue;
|
||||
}
|
||||
|
||||
private static <S, ID> boolean isIdPropertyNullOrScalarZero(@Nullable ID idValue,
|
||||
RelationalPersistentEntity<S> persistentEntity) {
|
||||
|
||||
RelationalPersistentProperty idProperty = persistentEntity.getIdProperty();
|
||||
return idValue == null //
|
||||
|| idProperty == null //
|
||||
|| (idProperty.getType() == int.class && idValue.equals(0)) //
|
||||
|| (idProperty.getType() == long.class && idValue.equals(0L));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <S> Object getIdFromHolder(KeyHolder holder, RelationalPersistentEntity<S> persistentEntity) {
|
||||
|
||||
try {
|
||||
// MySQL just returns one value with a special name
|
||||
return holder.getKey();
|
||||
} catch (DataRetrievalFailureException | InvalidDataAccessApiUsageException e) {
|
||||
// Postgres returns a value for each column
|
||||
// MS SQL Server returns a value that might be null.
|
||||
|
||||
Map<String, Object> keys = holder.getKeys();
|
||||
|
||||
if (keys == null || persistentEntity.getIdProperty() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return keys.get(persistentEntity.getIdColumn());
|
||||
}
|
||||
}
|
||||
|
||||
private EntityRowMapper<?> getEntityRowMapper(Class<?> domainType) {
|
||||
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), converter, accessStrategy);
|
||||
}
|
||||
|
||||
private RowMapper<?> getMapEntityRowMapper(RelationalPersistentProperty property) {
|
||||
|
||||
String keyColumn = property.getKeyColumn();
|
||||
Assert.notNull(keyColumn, () -> "KeyColumn must not be null for " + property);
|
||||
|
||||
return new MapEntityRowMapper<>(getEntityRowMapper(property.getActualType()), keyColumn);
|
||||
}
|
||||
|
||||
private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> domainType) {
|
||||
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
|
||||
addConvertedPropertyValue( //
|
||||
parameterSource, //
|
||||
getRequiredPersistentEntity(domainType).getRequiredIdProperty(), //
|
||||
id, //
|
||||
"id" //
|
||||
);
|
||||
return parameterSource;
|
||||
}
|
||||
|
||||
private void addConvertedPropertyValue(MapSqlParameterSource parameterSource, RelationalPersistentProperty property,
|
||||
Object value, String paramName) {
|
||||
|
||||
JdbcValue jdbcValue = converter.writeJdbcValue( //
|
||||
value, //
|
||||
property.getColumnType(), //
|
||||
property.getSqlType() //
|
||||
);
|
||||
|
||||
parameterSource.addValue(paramName, jdbcValue.getValue(), JdbcUtil.sqlTypeFor(jdbcValue.getJdbcType()));
|
||||
}
|
||||
|
||||
private void addConvertedPropertyValue(MapSqlParameterSource parameterSource, String name, Object value,
|
||||
Class<?> type) {
|
||||
|
||||
JdbcValue jdbcValue = converter.writeJdbcValue( //
|
||||
value, //
|
||||
type, //
|
||||
JdbcUtil.sqlTypeFor(type) //
|
||||
);
|
||||
|
||||
parameterSource.addValue( //
|
||||
name, //
|
||||
jdbcValue.getValue(), //
|
||||
JdbcUtil.sqlTypeFor(jdbcValue.getJdbcType()) //
|
||||
);
|
||||
}
|
||||
|
||||
private void addConvertedPropertyValuesAsList(MapSqlParameterSource parameterSource,
|
||||
RelationalPersistentProperty property, Iterable<?> values, String paramName) {
|
||||
|
||||
List<Object> convertedIds = new ArrayList<>();
|
||||
JdbcValue jdbcValue = null;
|
||||
for (Object id : values) {
|
||||
|
||||
Class<?> columnType = property.getColumnType();
|
||||
int sqlType = property.getSqlType();
|
||||
|
||||
jdbcValue = converter.writeJdbcValue(id, columnType, sqlType);
|
||||
convertedIds.add(jdbcValue.getValue());
|
||||
}
|
||||
|
||||
Assert.notNull(jdbcValue, "JdbcValue must be not null at this point. Please report this as a bug.");
|
||||
|
||||
JDBCType jdbcType = jdbcValue.getJdbcType();
|
||||
int typeNumber = jdbcType == null ? JdbcUtils.TYPE_UNKNOWN : jdbcType.getVendorTypeNumber();
|
||||
|
||||
parameterSource.addValue(paramName, convertedIds, typeNumber);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <S> RelationalPersistentEntity<S> getRequiredPersistentEntity(Class<S> domainType) {
|
||||
return (RelationalPersistentEntity<S>) context.getRequiredPersistentEntity(domainType);
|
||||
}
|
||||
|
||||
private SqlGenerator sql(Class<?> domainType) {
|
||||
return sqlGeneratorSource.getSqlGenerator(domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to create {@link Predicate}s.
|
||||
*/
|
||||
static class Predicates {
|
||||
|
||||
/**
|
||||
* Include all {@link Predicate} returning {@literal false} to never skip a property.
|
||||
*
|
||||
* @return the include all {@link Predicate}.
|
||||
*/
|
||||
static Predicate<RelationalPersistentProperty> includeAll() {
|
||||
return it -> false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
/**
|
||||
* Maps a {@link ResultSet} to an entity of type {@code T}, including entities referenced. This {@link RowMapper} might
|
||||
* trigger additional SQL statements in order to load other members of the same aggregate.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Maciej Walkowiak
|
||||
* @author Bastian Wilhelm
|
||||
*/
|
||||
public class EntityRowMapper<T> implements RowMapper<T> {
|
||||
|
||||
private final RelationalPersistentEntity<T> entity;
|
||||
|
||||
private final JdbcConverter converter;
|
||||
private final DataAccessStrategy accessStrategy;
|
||||
|
||||
public EntityRowMapper(RelationalPersistentEntity<T> entity, JdbcConverter converter,
|
||||
DataAccessStrategy accessStrategy) {
|
||||
|
||||
this.entity = entity;
|
||||
this.converter = converter;
|
||||
this.accessStrategy = accessStrategy;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
|
||||
*/
|
||||
@Override
|
||||
public T mapRow(ResultSet resultSet, int rowNumber) {
|
||||
return converter.mapRow(entity, accessStrategy, resultSet);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -15,10 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
|
||||
/**
|
||||
* A {@link JdbcConverter} is responsible for converting for values to the native relational representation and vice
|
||||
* versa.
|
||||
@@ -38,4 +42,10 @@ public interface JdbcConverter extends RelationalConverter {
|
||||
* @return The converted value wrapped in a {@link JdbcValue}. Guaranteed to be not {@literal null}.
|
||||
*/
|
||||
JdbcValue writeJdbcValue(@Nullable Object value, Class<?> type, int sqlType);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
|
||||
*/
|
||||
<T> T mapRow(RelationalPersistentEntity<T> entity, DataAccessStrategy accessStrategy, ResultSet resultSet);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
@@ -13,8 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.sql.Column;
|
||||
import org.springframework.data.relational.core.sql.SQL;
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
@@ -34,27 +34,11 @@ import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.sql.AssignValue;
|
||||
import org.springframework.data.relational.core.sql.Assignments;
|
||||
import org.springframework.data.relational.core.sql.BindMarker;
|
||||
import org.springframework.data.relational.core.sql.Column;
|
||||
import org.springframework.data.relational.core.sql.Condition;
|
||||
import org.springframework.data.relational.core.sql.Delete;
|
||||
import org.springframework.data.relational.core.sql.DeleteBuilder;
|
||||
import org.springframework.data.relational.core.sql.Expression;
|
||||
import org.springframework.data.relational.core.sql.Expressions;
|
||||
import org.springframework.data.relational.core.sql.Functions;
|
||||
import org.springframework.data.relational.core.sql.Insert;
|
||||
import org.springframework.data.relational.core.sql.InsertBuilder;
|
||||
import org.springframework.data.relational.core.sql.SQL;
|
||||
import org.springframework.data.relational.core.sql.Select;
|
||||
import org.springframework.data.relational.core.sql.SelectBuilder;
|
||||
import org.springframework.data.relational.core.sql.StatementBuilder;
|
||||
import org.springframework.data.relational.core.sql.Table;
|
||||
import org.springframework.data.relational.core.sql.Update;
|
||||
import org.springframework.data.relational.core.sql.*;
|
||||
import org.springframework.data.relational.core.sql.render.SqlRenderer;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -105,6 +89,48 @@ class SqlGenerator {
|
||||
this.columns = new Columns(entity, mappingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a IN-condition based on a {@link Select Sub-Select} which selects the ids (or stand ins for ids) of the
|
||||
* given {@literal path} to those that reference the root entities specified by the {@literal rootCondition}.
|
||||
*
|
||||
* @param path specifies the table and id to select
|
||||
* @param rootCondition the condition on the root of the path determining what to select
|
||||
* @param filterColumn the column to apply the IN-condition to.
|
||||
* @return the IN condition
|
||||
*/
|
||||
private static Condition getSubselectCondition(PersistentPropertyPathExtension path,
|
||||
Function<Column, Condition> rootCondition, Column filterColumn) {
|
||||
|
||||
PersistentPropertyPathExtension parentPath = path.getParentPath();
|
||||
|
||||
if (!parentPath.hasIdProperty()) {
|
||||
if (parentPath.getLength() > 1) {
|
||||
return getSubselectCondition(parentPath, rootCondition, filterColumn);
|
||||
}
|
||||
return rootCondition.apply(filterColumn);
|
||||
}
|
||||
|
||||
Table subSelectTable = SQL.table(parentPath.getTableName());
|
||||
Column idColumn = subSelectTable.column(parentPath.getIdColumnName());
|
||||
Column selectFilterColumn = subSelectTable.column(parentPath.getEffectiveIdColumnName());
|
||||
|
||||
Condition innerCondition = parentPath.getLength() == 1 // if the parent is the root of the path
|
||||
? rootCondition.apply(selectFilterColumn) // apply the rootCondition
|
||||
: getSubselectCondition(parentPath, rootCondition, selectFilterColumn); // otherwise we need another layer of
|
||||
// subselect
|
||||
|
||||
Select select = Select.builder() //
|
||||
.select(idColumn) //
|
||||
.from(subSelectTable) //
|
||||
.where(innerCondition).build();
|
||||
|
||||
return filterColumn.in(select);
|
||||
}
|
||||
|
||||
private static BindMarker getBindMarker(String columnName) {
|
||||
return SQL.bindMarker(":" + parameterPattern.matcher(columnName).replaceAll(""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a query for selecting all simple properties of an entity, including those for one-to-one relationships.
|
||||
* Results are filtered using an {@code IN}-clause on the id column.
|
||||
@@ -467,34 +493,6 @@ class SqlGenerator {
|
||||
return render(delete);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link Select Sub-Select}.
|
||||
*
|
||||
* @param path
|
||||
* @param rootCondition
|
||||
* @param filterColumn
|
||||
* @return
|
||||
*/
|
||||
private static Condition getSubselectCondition(PersistentPropertyPathExtension path,
|
||||
Function<Column, Condition> rootCondition, Column filterColumn) {
|
||||
|
||||
PersistentPropertyPathExtension parentPath = path.getParentPath();
|
||||
|
||||
Table subSelectTable = SQL.table(parentPath.getTableName());
|
||||
Column idColumn = subSelectTable.column(parentPath.getIdColumnName());
|
||||
Column selectFilterColumn = subSelectTable.column(parentPath.getEffectiveIdColumnName());
|
||||
|
||||
Condition innerCondition = parentPath.getLength() == 1 ? rootCondition.apply(selectFilterColumn)
|
||||
: getSubselectCondition(parentPath, rootCondition, selectFilterColumn);
|
||||
|
||||
Select select = Select.builder() //
|
||||
.select(idColumn) //
|
||||
.from(subSelectTable) //
|
||||
.where(innerCondition).build();
|
||||
|
||||
return filterColumn.in(select);
|
||||
}
|
||||
|
||||
private String createDeleteByListSql() {
|
||||
|
||||
Table table = getTable();
|
||||
@@ -531,10 +529,6 @@ class SqlGenerator {
|
||||
return sqlContext.getIdColumn();
|
||||
}
|
||||
|
||||
private static BindMarker getBindMarker(String columnName) {
|
||||
return SQL.bindMarker(":" + parameterPattern.matcher(columnName).replaceAll(""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object representing a {@code JOIN} association.
|
||||
*/
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@@ -22,13 +22,12 @@ import java.util.Map;
|
||||
|
||||
import org.apache.ibatis.session.SqlSession;
|
||||
import org.mybatis.spring.SqlSessionTemplate;
|
||||
|
||||
import org.springframework.data.jdbc.core.CascadingDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.DelegatingDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
@@ -102,7 +101,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
* <p>
|
||||
* Use a {@link SqlSessionTemplate} for {@link SqlSession} or a similar implementation tying the session to the proper
|
||||
* transaction. Note that the resulting {@link DataAccessStrategy} only handles MyBatis. It does not include the
|
||||
* functionality of the {@link org.springframework.data.jdbc.core.DefaultDataAccessStrategy} which one normally still
|
||||
* functionality of the {@link DefaultDataAccessStrategy} which one normally still
|
||||
* wants. Use
|
||||
* {@link #createCombinedAccessStrategy(RelationalMappingContext, JdbcConverter, NamedParameterJdbcOperations, SqlSession, NamespaceStrategy)}
|
||||
* to create such a {@link DataAccessStrategy}.
|
||||
|
||||
@@ -22,13 +22,13 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
|
||||
import org.springframework.data.jdbc.core.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
|
||||
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.NamingStrategy;
|
||||
@@ -79,8 +79,9 @@ public abstract class AbstractJdbcConfiguration {
|
||||
|
||||
/**
|
||||
* Register custom {@link Converter}s in a {@link JdbcCustomConversions} object if required. These
|
||||
* {@link JdbcCustomConversions} will be registered with the {@link #jdbcConverter(RelationalMappingContext, JdbcOperations)}.
|
||||
* Returns an empty {@link JdbcCustomConversions} instance by default.
|
||||
* {@link JdbcCustomConversions} will be registered with the
|
||||
* {@link #jdbcConverter(RelationalMappingContext, JdbcOperations)}. Returns an empty {@link JdbcCustomConversions}
|
||||
* instance by default.
|
||||
*
|
||||
* @return must not be {@literal null}.
|
||||
*/
|
||||
|
||||
@@ -22,14 +22,14 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
|
||||
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
|
||||
import org.springframework.data.jdbc.core.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcTypeFactory;
|
||||
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.NamingStrategy;
|
||||
|
||||
@@ -19,10 +19,10 @@ import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.EntityRowMapper;
|
||||
import org.springframework.data.jdbc.core.convert.EntityRowMapper;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
@@ -47,7 +47,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
|
||||
|
||||
private final ApplicationEventPublisher publisher;
|
||||
private final RelationalMappingContext context;
|
||||
private final RelationalConverter converter;
|
||||
private final JdbcConverter converter;
|
||||
private final DataAccessStrategy accessStrategy;
|
||||
private final QueryMappingConfiguration queryMappingConfiguration;
|
||||
private final NamedParameterJdbcOperations operations;
|
||||
@@ -63,8 +63,8 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
|
||||
* @param queryMappingConfiguration must not be {@literal null}.
|
||||
*/
|
||||
JdbcQueryLookupStrategy(ApplicationEventPublisher publisher, RelationalMappingContext context,
|
||||
RelationalConverter converter, DataAccessStrategy accessStrategy,
|
||||
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations) {
|
||||
JdbcConverter converter, DataAccessStrategy accessStrategy, QueryMappingConfiguration queryMappingConfiguration,
|
||||
NamedParameterJdbcOperations operations) {
|
||||
|
||||
Assert.notNull(publisher, "Publisher must not be null!");
|
||||
Assert.notNull(context, "RelationalMappingContext must not be null!");
|
||||
@@ -118,7 +118,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
|
||||
|
||||
EntityRowMapper<?> defaultEntityRowMapper = new EntityRowMapper<>( //
|
||||
context.getRequiredPersistentEntity(domainType), //
|
||||
context, //
|
||||
//
|
||||
converter, //
|
||||
accessStrategy);
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ import java.util.Optional;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.repository.RowMapperMap;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
@@ -47,7 +47,7 @@ import org.springframework.util.Assert;
|
||||
public class JdbcRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
private final RelationalMappingContext context;
|
||||
private final RelationalConverter converter;
|
||||
private final JdbcConverter converter;
|
||||
private final ApplicationEventPublisher publisher;
|
||||
private final DataAccessStrategy accessStrategy;
|
||||
private final NamedParameterJdbcOperations operations;
|
||||
@@ -57,15 +57,14 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
|
||||
/**
|
||||
* Creates a new {@link JdbcRepositoryFactory} for the given {@link DataAccessStrategy},
|
||||
* {@link RelationalMappingContext} and {@link ApplicationEventPublisher}.
|
||||
*
|
||||
* @param dataAccessStrategy must not be {@literal null}.
|
||||
* @param dataAccessStrategy must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @param publisher must not be {@literal null}.
|
||||
* @param operations must not be {@literal null}.
|
||||
*/
|
||||
public JdbcRepositoryFactory(DataAccessStrategy dataAccessStrategy, RelationalMappingContext context,
|
||||
RelationalConverter converter, ApplicationEventPublisher publisher, NamedParameterJdbcOperations operations) {
|
||||
JdbcConverter converter, ApplicationEventPublisher publisher, NamedParameterJdbcOperations operations) {
|
||||
|
||||
Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null!");
|
||||
Assert.notNull(context, "RelationalMappingContext must not be null!");
|
||||
|
||||
@@ -22,9 +22,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.repository.RowMapperMap;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
|
||||
@@ -25,9 +25,7 @@ import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Insert;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.InsertRoot;
|
||||
import org.springframework.data.relational.core.mapping.NamingStrategy;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.domain.Identifier;
|
||||
|
||||
/**
|
||||
@@ -38,14 +36,9 @@ import org.springframework.data.relational.domain.Identifier;
|
||||
public class DefaultJdbcInterpreterUnitTests {
|
||||
|
||||
static final long CONTAINER_ID = 23L;
|
||||
static final String BACK_REFERENCE = "back-reference";
|
||||
static final String BACK_REFERENCE = "container";
|
||||
|
||||
RelationalMappingContext context = new JdbcMappingContext(new NamingStrategy() {
|
||||
@Override
|
||||
public String getReverseColumnName(RelationalPersistentProperty property) {
|
||||
return BACK_REFERENCE;
|
||||
}
|
||||
});
|
||||
RelationalMappingContext context = new JdbcMappingContext();
|
||||
|
||||
DataAccessStrategy dataAccessStrategy = mock(DataAccessStrategy.class);
|
||||
DefaultJdbcInterpreter interpreter = new DefaultJdbcInterpreter(context, dataAccessStrategy);
|
||||
@@ -54,8 +47,10 @@ public class DefaultJdbcInterpreterUnitTests {
|
||||
Element element = new Element();
|
||||
|
||||
InsertRoot<Container> containerInsert = new InsertRoot<>(container);
|
||||
Insert<?> insert = new Insert<>(element, PropertyPathTestingUtils.toPath("element", Container.class, context),
|
||||
Insert<?> elementInsert = new Insert<>(element, PropertyPathTestingUtils.toPath("element", Container.class, context),
|
||||
containerInsert);
|
||||
Insert<?> element1Insert = new Insert<>(element, PropertyPathTestingUtils.toPath("element.element1", Container.class, context),
|
||||
elementInsert);
|
||||
|
||||
@Test // DATAJDBC-145
|
||||
public void insertDoesHonourNamingStrategyForBackReference() {
|
||||
@@ -63,7 +58,7 @@ public class DefaultJdbcInterpreterUnitTests {
|
||||
container.id = CONTAINER_ID;
|
||||
containerInsert.setGeneratedId(CONTAINER_ID);
|
||||
|
||||
interpreter.interpret(insert);
|
||||
interpreter.interpret(elementInsert);
|
||||
|
||||
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
|
||||
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
|
||||
@@ -78,7 +73,7 @@ public class DefaultJdbcInterpreterUnitTests {
|
||||
|
||||
container.id = CONTAINER_ID;
|
||||
|
||||
interpreter.interpret(insert);
|
||||
interpreter.interpret(elementInsert);
|
||||
|
||||
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
|
||||
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
|
||||
@@ -93,7 +88,22 @@ public class DefaultJdbcInterpreterUnitTests {
|
||||
|
||||
containerInsert.setGeneratedId(CONTAINER_ID);
|
||||
|
||||
interpreter.interpret(insert);
|
||||
interpreter.interpret(elementInsert);
|
||||
|
||||
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
|
||||
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
|
||||
|
||||
assertThat(argumentCaptor.getValue().getParts()) //
|
||||
.extracting("name", "value", "targetType") //
|
||||
.containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class));
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-359
|
||||
public void generatedIdOfParentsParentGetsPassedOnAsAdditionalParameter() {
|
||||
|
||||
containerInsert.setGeneratedId(CONTAINER_ID);
|
||||
|
||||
interpreter.interpret(element1Insert);
|
||||
|
||||
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
|
||||
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
|
||||
@@ -111,5 +121,11 @@ public class DefaultJdbcInterpreterUnitTests {
|
||||
Element element;
|
||||
}
|
||||
|
||||
static class Element {}
|
||||
@SuppressWarnings("unused")
|
||||
static class Element {
|
||||
Element1 element1;
|
||||
}
|
||||
|
||||
static class Element1 {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
@@ -45,8 +43,6 @@ import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.test.annotation.IfProfileValue;
|
||||
import org.springframework.test.annotation.ProfileValueSourceConfiguration;
|
||||
@@ -71,8 +67,7 @@ public class JdbcAggregateTemplateIntegrationTests {
|
||||
@ClassRule public static final SpringClassRule classRule = new SpringClassRule();
|
||||
@Rule public SpringMethodRule methodRule = new SpringMethodRule();
|
||||
@Autowired JdbcAggregateOperations template;
|
||||
@Autowired
|
||||
NamedParameterJdbcOperations jdbcTemplate;
|
||||
@Autowired NamedParameterJdbcOperations jdbcTemplate;
|
||||
LegoSet legoSet = createLegoSet();
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
@@ -465,8 +460,39 @@ public class JdbcAggregateTemplateIntegrationTests {
|
||||
|
||||
template.delete(chain4, Chain4.class);
|
||||
|
||||
String countSelect = "SELECT COUNT(*) FROM %s";
|
||||
jdbcTemplate.queryForObject(String.format(countSelect, "CHAIN0"),emptyMap(), Long.class);
|
||||
assertThat(jdbcTemplate.queryForObject("SELECT COUNT(*) FROM CHAIN0", emptyMap(), Long.class)) //
|
||||
.isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-359
|
||||
public void saveAndLoadLongChainWithoutIds() {
|
||||
|
||||
NoIdChain4 chain4 = new NoIdChain4();
|
||||
chain4.fourValue = "omega";
|
||||
chain4.chain3 = new NoIdChain3();
|
||||
chain4.chain3.threeValue = "delta";
|
||||
chain4.chain3.chain2 = new NoIdChain2();
|
||||
chain4.chain3.chain2.twoValue = "gamma";
|
||||
chain4.chain3.chain2.chain1 = new NoIdChain1();
|
||||
chain4.chain3.chain2.chain1.oneValue = "beta";
|
||||
chain4.chain3.chain2.chain1.chain0 = new NoIdChain0();
|
||||
chain4.chain3.chain2.chain1.chain0.zeroValue = "alpha";
|
||||
|
||||
template.save(chain4);
|
||||
|
||||
assertThat(chain4.four).isNotNull();
|
||||
|
||||
NoIdChain4 reloaded = template.findById(chain4.four, NoIdChain4.class);
|
||||
|
||||
assertThat(reloaded).isNotNull();
|
||||
|
||||
assertThat(reloaded.four).isEqualTo(chain4.four);
|
||||
assertThat(reloaded.chain3.chain2.chain1.chain0.zeroValue).isEqualTo(chain4.chain3.chain2.chain1.chain0.zeroValue);
|
||||
|
||||
template.delete(chain4, NoIdChain4.class);
|
||||
|
||||
assertThat(jdbcTemplate.queryForObject("SELECT COUNT(*) FROM CHAIN0", emptyMap(), Long.class)) //
|
||||
.isEqualTo(0);
|
||||
}
|
||||
|
||||
private static void assumeNot(String dbProfileName) {
|
||||
@@ -573,6 +599,9 @@ public class JdbcAggregateTemplateIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One may think of ChainN as a chain with N further elements
|
||||
*/
|
||||
static class Chain0 {
|
||||
@Id Long zero;
|
||||
String zeroValue;
|
||||
@@ -601,4 +630,32 @@ public class JdbcAggregateTemplateIntegrationTests {
|
||||
String fourValue;
|
||||
Chain3 chain3;
|
||||
}
|
||||
|
||||
/**
|
||||
* One may think of ChainN as a chain with N further elements
|
||||
*/
|
||||
static class NoIdChain0 {
|
||||
String zeroValue;
|
||||
}
|
||||
|
||||
static class NoIdChain1 {
|
||||
String oneValue;
|
||||
NoIdChain0 chain0;
|
||||
}
|
||||
|
||||
static class NoIdChain2 {
|
||||
String twoValue;
|
||||
NoIdChain1 chain1;
|
||||
}
|
||||
|
||||
static class NoIdChain3 {
|
||||
String threeValue;
|
||||
NoIdChain2 chain2;
|
||||
}
|
||||
|
||||
static class NoIdChain4 {
|
||||
@Id Long four;
|
||||
String fourValue;
|
||||
NoIdChain3 chain3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,18 @@ package org.springframework.data.jdbc.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.Embedded;
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
|
||||
import static org.assertj.core.api.SoftAssertions.*;
|
||||
|
||||
/**
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@@ -37,23 +40,23 @@ public class PersistentPropertyPathExtensionUnitTests {
|
||||
@Test
|
||||
public void isEmbedded() {
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(extPath(entity).isEmbedded()).isFalse();
|
||||
softly.assertThat(extPath("second").isEmbedded()).isFalse();
|
||||
softly.assertThat(extPath("second.third").isEmbedded()).isTrue();
|
||||
softly.assertThat(extPath("second.third2").isEmbedded()).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isMultiValued() {
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(extPath(entity).isMultiValued()).isFalse();
|
||||
softly.assertThat(extPath("second").isMultiValued()).isFalse();
|
||||
softly.assertThat(extPath("second.third").isMultiValued()).isFalse();
|
||||
softly.assertThat(extPath("secondList.third").isMultiValued()).isTrue();
|
||||
softly.assertThat(extPath("second.third2").isMultiValued()).isFalse();
|
||||
softly.assertThat(extPath("secondList.third2").isMultiValued()).isTrue();
|
||||
softly.assertThat(extPath("secondList").isMultiValued()).isTrue();
|
||||
});
|
||||
}
|
||||
@@ -64,12 +67,12 @@ public class PersistentPropertyPathExtensionUnitTests {
|
||||
RelationalPersistentEntity<?> second = context.getRequiredPersistentEntity(Second.class);
|
||||
RelationalPersistentEntity<?> third = context.getRequiredPersistentEntity(Third.class);
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(extPath(entity).getLeafEntity()).isEqualTo(entity);
|
||||
softly.assertThat(extPath("second").getLeafEntity()).isEqualTo(second);
|
||||
softly.assertThat(extPath("second.third").getLeafEntity()).isEqualTo(third);
|
||||
softly.assertThat(extPath("secondList.third").getLeafEntity()).isEqualTo(third);
|
||||
softly.assertThat(extPath("second.third2").getLeafEntity()).isEqualTo(third);
|
||||
softly.assertThat(extPath("secondList.third2").getLeafEntity()).isEqualTo(third);
|
||||
softly.assertThat(extPath("secondList").getLeafEntity()).isEqualTo(second);
|
||||
});
|
||||
}
|
||||
@@ -77,15 +80,15 @@ public class PersistentPropertyPathExtensionUnitTests {
|
||||
@Test
|
||||
public void isEntity() {
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(extPath(entity).isEntity()).isTrue();
|
||||
String path = "second";
|
||||
softly.assertThat(extPath(path).isEntity()).isTrue();
|
||||
softly.assertThat(extPath("second.third").isEntity()).isTrue();
|
||||
softly.assertThat(extPath("second.third.value").isEntity()).isFalse();
|
||||
softly.assertThat(extPath("secondList.third").isEntity()).isTrue();
|
||||
softly.assertThat(extPath("secondList.third.value").isEntity()).isFalse();
|
||||
softly.assertThat(extPath("second.third2").isEntity()).isTrue();
|
||||
softly.assertThat(extPath("second.third2.value").isEntity()).isFalse();
|
||||
softly.assertThat(extPath("secondList.third2").isEntity()).isTrue();
|
||||
softly.assertThat(extPath("secondList.third2.value").isEntity()).isFalse();
|
||||
softly.assertThat(extPath("secondList").isEntity()).isTrue();
|
||||
});
|
||||
}
|
||||
@@ -93,14 +96,14 @@ public class PersistentPropertyPathExtensionUnitTests {
|
||||
@Test
|
||||
public void getTableName() {
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(extPath(entity).getTableName()).isEqualTo("dummy_entity");
|
||||
softly.assertThat(extPath("second").getTableName()).isEqualTo("second");
|
||||
softly.assertThat(extPath("second.third").getTableName()).isEqualTo("second");
|
||||
softly.assertThat(extPath("second.third.value").getTableName()).isEqualTo("second");
|
||||
softly.assertThat(extPath("secondList.third").getTableName()).isEqualTo("second");
|
||||
softly.assertThat(extPath("secondList.third.value").getTableName()).isEqualTo("second");
|
||||
softly.assertThat(extPath("second.third2").getTableName()).isEqualTo("second");
|
||||
softly.assertThat(extPath("second.third2.value").getTableName()).isEqualTo("second");
|
||||
softly.assertThat(extPath("secondList.third2").getTableName()).isEqualTo("second");
|
||||
softly.assertThat(extPath("secondList.third2.value").getTableName()).isEqualTo("second");
|
||||
softly.assertThat(extPath("secondList").getTableName()).isEqualTo("second");
|
||||
});
|
||||
}
|
||||
@@ -108,34 +111,91 @@ public class PersistentPropertyPathExtensionUnitTests {
|
||||
@Test
|
||||
public void getTableAlias() {
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(extPath(entity).getTableAlias()).isEqualTo(null);
|
||||
softly.assertThat(extPath("second").getTableAlias()).isEqualTo("second");
|
||||
softly.assertThat(extPath("second.third").getTableAlias()).isEqualTo("second");
|
||||
softly.assertThat(extPath("second.third.value").getTableAlias()).isEqualTo("second");
|
||||
softly.assertThat(extPath("second.third2").getTableAlias()).isEqualTo("second_third2");
|
||||
softly.assertThat(extPath("second.third2.value").getTableAlias()).isEqualTo("second_third2");
|
||||
softly.assertThat(extPath("secondList.third").getTableAlias()).isEqualTo("secondList");
|
||||
softly.assertThat(extPath("secondList.third.value").getTableAlias()).isEqualTo("secondList");
|
||||
softly.assertThat(extPath("secondList.third2").getTableAlias()).isEqualTo("secondList_third2");
|
||||
softly.assertThat(extPath("secondList.third2.value").getTableAlias()).isEqualTo("secondList_third2");
|
||||
softly.assertThat(extPath("second.third2").getTableAlias()).isEqualTo("second");
|
||||
softly.assertThat(extPath("second.third2.value").getTableAlias()).isEqualTo("second");
|
||||
softly.assertThat(extPath("second.third").getTableAlias()).isEqualTo("second_third");
|
||||
softly.assertThat(extPath("second.third.value").getTableAlias()).isEqualTo("second_third");
|
||||
softly.assertThat(extPath("secondList.third2").getTableAlias()).isEqualTo("secondList");
|
||||
softly.assertThat(extPath("secondList.third2.value").getTableAlias()).isEqualTo("secondList");
|
||||
softly.assertThat(extPath("secondList.third").getTableAlias()).isEqualTo("secondList_third");
|
||||
softly.assertThat(extPath("secondList.third.value").getTableAlias()).isEqualTo("secondList_third");
|
||||
softly.assertThat(extPath("secondList").getTableAlias()).isEqualTo("secondList");
|
||||
softly.assertThat(extPath("second2.third2").getTableAlias()).isEqualTo("secthird2");
|
||||
softly.assertThat(extPath("second2.third").getTableAlias()).isEqualTo("secthird");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getColumnName() {
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(extPath("second.third.value").getColumnName()).isEqualTo("thrdvalue");
|
||||
softly.assertThat(extPath("second.third2.value").getColumnName()).isEqualTo("value");
|
||||
softly.assertThat(extPath("secondList.third.value").getColumnName()).isEqualTo("thrdvalue");
|
||||
softly.assertThat(extPath("secondList.third2.value").getColumnName()).isEqualTo("value");
|
||||
softly.assertThat(extPath("second2.third.value").getColumnName()).isEqualTo("secthrdvalue");
|
||||
softly.assertThat(extPath("second2.third2.value").getColumnName()).isEqualTo("value");
|
||||
softly.assertThat(extPath("second.third2.value").getColumnName()).isEqualTo("thrdvalue");
|
||||
softly.assertThat(extPath("second.third.value").getColumnName()).isEqualTo("value");
|
||||
softly.assertThat(extPath("secondList.third2.value").getColumnName()).isEqualTo("thrdvalue");
|
||||
softly.assertThat(extPath("secondList.third.value").getColumnName()).isEqualTo("value");
|
||||
softly.assertThat(extPath("second2.third2.value").getColumnName()).isEqualTo("secthrdvalue");
|
||||
softly.assertThat(extPath("second2.third.value").getColumnName()).isEqualTo("value");
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-359
|
||||
public void idDefiningPath() {
|
||||
|
||||
assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(extPath("second.third2.value").getIdDefiningParentPath().getLength()).isEqualTo(0);
|
||||
softly.assertThat(extPath("second.third.value").getIdDefiningParentPath().getLength()).isEqualTo(0);
|
||||
softly.assertThat(extPath("secondList.third2.value").getIdDefiningParentPath().getLength()).isEqualTo(0);
|
||||
softly.assertThat(extPath("secondList.third.value").getIdDefiningParentPath().getLength()).isEqualTo(0);
|
||||
softly.assertThat(extPath("second2.third2.value").getIdDefiningParentPath().getLength()).isEqualTo(0);
|
||||
softly.assertThat(extPath("second2.third.value").getIdDefiningParentPath().getLength()).isEqualTo(0);
|
||||
softly.assertThat(extPath("withId.second.third2.value").getIdDefiningParentPath().getLength()).isEqualTo(1);
|
||||
softly.assertThat(extPath("withId.second.third.value").getIdDefiningParentPath().getLength()).isEqualTo(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-359
|
||||
public void reverseColumnName() {
|
||||
|
||||
assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(extPath("second.third2").getReverseColumnName()).isEqualTo("dummy_entity");
|
||||
softly.assertThat(extPath("second.third").getReverseColumnName()).isEqualTo("dummy_entity");
|
||||
softly.assertThat(extPath("secondList.third2").getReverseColumnName()).isEqualTo("dummy_entity");
|
||||
softly.assertThat(extPath("secondList.third").getReverseColumnName()).isEqualTo("dummy_entity");
|
||||
softly.assertThat(extPath("second2.third2").getReverseColumnName()).isEqualTo("dummy_entity");
|
||||
softly.assertThat(extPath("second2.third").getReverseColumnName()).isEqualTo("dummy_entity");
|
||||
softly.assertThat(extPath("withId.second.third2.value").getReverseColumnName()).isEqualTo("with_id");
|
||||
softly.assertThat(extPath("withId.second.third").getReverseColumnName()).isEqualTo("with_id");
|
||||
softly.assertThat(extPath("withId.second2.third").getReverseColumnName()).isEqualTo("with_id");
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-359
|
||||
public void getRequiredIdProperty() {
|
||||
|
||||
assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(extPath(entity).getRequiredIdProperty().getName()).isEqualTo("entityId");
|
||||
softly.assertThat(extPath("withId").getRequiredIdProperty().getName()).isEqualTo("withIdId");
|
||||
softly.assertThatThrownBy(() -> extPath("second").getRequiredIdProperty())
|
||||
.isInstanceOf(IllegalStateException.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-359
|
||||
public void extendBy() {
|
||||
|
||||
assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(extPath(entity).extendBy(entity.getRequiredPersistentProperty("withId")))
|
||||
.isEqualTo(extPath("withId"));
|
||||
softly.assertThat(extPath("withId").extendBy(extPath("withId").getRequiredIdProperty()))
|
||||
.isEqualTo(extPath("withId.withIdId"));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -155,15 +215,17 @@ public class PersistentPropertyPathExtensionUnitTests {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class DummyEntity {
|
||||
@Id Long entityId;
|
||||
Second second;
|
||||
List<Second> secondList;
|
||||
@Embedded("sec") Second second2;
|
||||
List<Second> secondList;
|
||||
WithId withId;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class Second {
|
||||
@Embedded("thrd") Third third;
|
||||
Third third2;
|
||||
Third third;
|
||||
@Embedded("thrd") Third third2;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -171,4 +233,11 @@ public class PersistentPropertyPathExtensionUnitTests {
|
||||
String value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class WithId {
|
||||
@Id Long withIdId;
|
||||
Second second;
|
||||
@Embedded("sec") Second second2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,10 +29,10 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProp
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@UtilityClass
|
||||
class PropertyPathTestingUtils {
|
||||
public class PropertyPathTestingUtils {
|
||||
|
||||
static PersistentPropertyPath<RelationalPersistentProperty> toPath(String path, Class source,
|
||||
RelationalMappingContext context) {
|
||||
public static PersistentPropertyPath<RelationalPersistentProperty> toPath(String path, Class source,
|
||||
RelationalMappingContext context) {
|
||||
|
||||
PersistentPropertyPaths<?, RelationalPersistentProperty> persistentPropertyPaths = context
|
||||
.findPersistentPropertyPaths(source, p -> true);
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
@@ -36,6 +36,7 @@ import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
|
||||
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
@@ -13,12 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
import static java.util.Collections.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -34,18 +34,18 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.naming.OperationNotSupportedException;
|
||||
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.Test;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.Embedded;
|
||||
import org.springframework.data.relational.core.mapping.NamingStrategy;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
@@ -213,7 +213,7 @@ public class EntityRowMapperUnitTests {
|
||||
@Test // DATAJDBC-252
|
||||
public void doesNotTryToSetPropertiesThatAreSetViaConstructor() throws SQLException {
|
||||
|
||||
ResultSet rs = mockResultSet(asList("value"), //
|
||||
ResultSet rs = mockResultSet(singletonList("value"), //
|
||||
"value-from-resultSet");
|
||||
rs.next();
|
||||
|
||||
@@ -240,7 +240,7 @@ public class EntityRowMapperUnitTests {
|
||||
@Test // DATAJDBC-273
|
||||
public void handlesNonSimplePropertyInConstructor() throws SQLException {
|
||||
|
||||
ResultSet rs = mockResultSet(asList("id"), //
|
||||
ResultSet rs = mockResultSet(singletonList("id"), //
|
||||
ID_FOR_ENTITY_REFERENCING_LIST);
|
||||
rs.next();
|
||||
|
||||
@@ -249,6 +249,46 @@ public class EntityRowMapperUnitTests {
|
||||
assertThat(extracted.content).hasSize(2);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-359
|
||||
public void chainedEntitiesWithoutId() throws SQLException {
|
||||
|
||||
// @formatter:off
|
||||
Fixture<NoIdChain4> fixture = this.<NoIdChain4> buildFixture() //
|
||||
// Id of the aggregate root and backreference to it from
|
||||
// the various aggregate members.
|
||||
.value(4L).inColumns("four", //
|
||||
"chain3_no_id_chain4", //
|
||||
"chain3_chain2_no_id_chain4", //
|
||||
"chain3_chain2_chain1_no_id_chain4", //
|
||||
"chain3_chain2_chain1_chain0_no_id_chain4") //
|
||||
.endUpIn(e -> e.four)
|
||||
// values for the different entities
|
||||
.value("four_value").inColumns("four_value").endUpIn(e -> e.fourValue) //
|
||||
|
||||
.value("three_value").inColumns("chain3_three_value").endUpIn(e -> e.chain3.threeValue) //
|
||||
|
||||
.value("two_value").inColumns("chain3_chain2_two_value").endUpIn(e -> e.chain3.chain2.twoValue) //
|
||||
|
||||
.value("one_value").inColumns("chain3_chain2_chain1_one_value").endUpIn(e -> e.chain3.chain2.chain1.oneValue) //
|
||||
|
||||
.value("zero_value").inColumns("chain3_chain2_chain1_chain0_zero_value")
|
||||
.endUpIn(e -> e.chain3.chain2.chain1.chain0.zeroValue) //
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
ResultSet rs = fixture.resultSet;
|
||||
|
||||
rs.next();
|
||||
|
||||
NoIdChain4 extracted = createRowMapper(NoIdChain4.class).mapRow(rs, 1);
|
||||
|
||||
fixture.assertOn(extracted);
|
||||
}
|
||||
|
||||
private <T> FixtureBuilder<T> buildFixture() {
|
||||
return new FixtureBuilder<>();
|
||||
}
|
||||
|
||||
private <T> EntityRowMapper<T> createRowMapper(Class<T> type) {
|
||||
return createRowMapper(type, NamingStrategy.INSTANCE);
|
||||
}
|
||||
@@ -275,11 +315,11 @@ public class EntityRowMapperUnitTests {
|
||||
))).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_REFERENCING_LIST),
|
||||
any(RelationalPersistentProperty.class));
|
||||
|
||||
RelationalConverter converter = new BasicRelationalConverter(context, new JdbcCustomConversions());
|
||||
JdbcConverter converter = new BasicJdbcConverter(context, new JdbcCustomConversions());
|
||||
|
||||
return new EntityRowMapper<>( //
|
||||
(RelationalPersistentEntity<T>) context.getRequiredPersistentEntity(type), //
|
||||
context, //
|
||||
//
|
||||
converter, //
|
||||
accessStrategy //
|
||||
);
|
||||
@@ -335,27 +375,22 @@ public class EntityRowMapperUnitTests {
|
||||
|
||||
switch (invocation.getMethod().getName()) {
|
||||
case "next":
|
||||
return next();
|
||||
case "getObject":
|
||||
return getObject(invocation.getArgument(0));
|
||||
case "isAfterLast":
|
||||
return isAfterLast();
|
||||
case "isBeforeFirst":
|
||||
return isBeforeFirst();
|
||||
case "getRow":
|
||||
return isAfterLast() || isBeforeFirst() ? 0 : index + 1;
|
||||
case "toString":
|
||||
return this.toString();
|
||||
default:
|
||||
throw new OperationNotSupportedException(invocation.getMethod().getName());
|
||||
|
||||
}
|
||||
|
||||
if (invocation.getMethod().getName().equals("next"))
|
||||
return next();
|
||||
|
||||
if (invocation.getMethod().getName().equals("getObject"))
|
||||
return getObject(invocation.getArgument(0));
|
||||
|
||||
if (invocation.getMethod().getName().equals("isAfterLast"))
|
||||
return isAfterLast();
|
||||
|
||||
if (invocation.getMethod().getName().equals("isBeforeFirst"))
|
||||
return isBeforeFirst();
|
||||
|
||||
if (invocation.getMethod().getName().equals("getRow"))
|
||||
return isAfterLast() || isBeforeFirst() ? 0 : index + 1;
|
||||
|
||||
if (invocation.getMethod().getName().equals("toString"))
|
||||
return this.toString();
|
||||
|
||||
throw new OperationNotSupportedException(invocation.getMethod().getName());
|
||||
}
|
||||
|
||||
private boolean isAfterLast() {
|
||||
@@ -481,4 +516,121 @@ public class EntityRowMapperUnitTests {
|
||||
|
||||
final List<Trivial> content;
|
||||
}
|
||||
|
||||
static class NoIdChain0 {
|
||||
String zeroValue;
|
||||
}
|
||||
|
||||
static class NoIdChain1 {
|
||||
String oneValue;
|
||||
NoIdChain0 chain0;
|
||||
}
|
||||
|
||||
static class NoIdChain2 {
|
||||
String twoValue;
|
||||
NoIdChain1 chain1;
|
||||
}
|
||||
|
||||
static class NoIdChain3 {
|
||||
String threeValue;
|
||||
NoIdChain2 chain2;
|
||||
}
|
||||
|
||||
static class NoIdChain4 {
|
||||
@Id Long four;
|
||||
String fourValue;
|
||||
NoIdChain3 chain3;
|
||||
}
|
||||
|
||||
private interface SetValue<T> {
|
||||
SetColumns<T> value(Object value);
|
||||
|
||||
Fixture<T> build();
|
||||
}
|
||||
|
||||
private interface SetColumns<T> {
|
||||
|
||||
SetExpectation<T> inColumns(String... columns);
|
||||
}
|
||||
|
||||
private interface SetExpectation<T> {
|
||||
SetValue<T> endUpIn(Function<T, Object> extractor);
|
||||
}
|
||||
|
||||
private static class FixtureBuilder<T> implements SetValue<T>, SetColumns<T>, SetExpectation<T> {
|
||||
|
||||
private List<Object> values = new ArrayList<>();
|
||||
private List<String> columns = new ArrayList<>();
|
||||
private String explainingColumn;
|
||||
private List<Expectation<T>> expectations = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public SetColumns<T> value(Object value) {
|
||||
|
||||
values.add(value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SetExpectation<T> inColumns(String... columns) {
|
||||
|
||||
boolean isFirst = true;
|
||||
for (String column : columns) {
|
||||
|
||||
// if more than one column is mentioned, we need to copy the value for all but the first column;
|
||||
if (!isFirst) {
|
||||
|
||||
values.add(values.get(values.size() - 1));
|
||||
} else {
|
||||
|
||||
explainingColumn = column;
|
||||
isFirst = false;
|
||||
}
|
||||
|
||||
this.columns.add(column);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fixture<T> build() {
|
||||
|
||||
return new Fixture<>(mockResultSet(columns, values.toArray()), expectations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SetValue<T> endUpIn(Function<T, Object> extractor) {
|
||||
|
||||
expectations.add(new Expectation<T>(extractor, values.get(values.size() - 1), explainingColumn));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private static class Fixture<T> {
|
||||
final ResultSet resultSet;
|
||||
final List<Expectation<T>> expectations;
|
||||
|
||||
public void assertOn(T result) {
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
expectations.forEach(expectation -> {
|
||||
|
||||
softly.assertThat(expectation.extractor.apply(result)).describedAs("From column: " + expectation.sourceColumn)
|
||||
.isEqualTo(expectation.expectedValue);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private static class Expectation<T> {
|
||||
|
||||
final Function<T, Object> extractor;
|
||||
final Object expectedValue;
|
||||
final String sourceColumn;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
import static java.util.Collections.*;
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.jdbc.core.PropertyPathTestingUtils.*;
|
||||
@@ -22,13 +22,11 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcIdentifierBuilder;
|
||||
import org.springframework.data.jdbc.core.JdbcIdentifierBuilder;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
|
||||
import org.springframework.data.relational.domain.Identifier;
|
||||
|
||||
/**
|
||||
@@ -55,7 +53,7 @@ public class JdbcIdentifierBuilderUnitTests {
|
||||
@Test // DATAJDBC-326
|
||||
public void qualifiersForMaps() {
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> path = getPath("children");
|
||||
PersistentPropertyPathExtension path = getPath("children");
|
||||
|
||||
Identifier identifier = JdbcIdentifierBuilder //
|
||||
.forBackReferences(path, "parent-eins") //
|
||||
@@ -73,7 +71,7 @@ public class JdbcIdentifierBuilderUnitTests {
|
||||
@Test // DATAJDBC-326
|
||||
public void qualifiersForLists() {
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> path = getPath("moreChildren");
|
||||
PersistentPropertyPathExtension path = getPath("moreChildren");
|
||||
|
||||
Identifier identifier = JdbcIdentifierBuilder //
|
||||
.forBackReferences(path, "parent-eins") //
|
||||
@@ -98,13 +96,26 @@ public class JdbcIdentifierBuilderUnitTests {
|
||||
assertThat(identifier.getParts()) //
|
||||
.extracting("name", "value", "targetType") //
|
||||
.containsExactly( //
|
||||
tuple("embeddable", "parent-eins", UUID.class) //
|
||||
tuple("dummy_entity", "parent-eins", UUID.class) //
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private PersistentPropertyPath<RelationalPersistentProperty> getPath(String dotPath) {
|
||||
return toPath(dotPath, DummyEntity.class, context);
|
||||
@Test // DATAJDBC-326
|
||||
public void backreferenceAcrossNoId() {
|
||||
|
||||
Identifier identifier = JdbcIdentifierBuilder //
|
||||
.forBackReferences(getPath("noId.child"), "parent-eins") //
|
||||
.build();
|
||||
|
||||
assertThat(identifier.getParts()) //
|
||||
.extracting("name", "value", "targetType") //
|
||||
.containsExactly( //
|
||||
tuple("dummy_entity", "parent-eins", UUID.class) //
|
||||
);
|
||||
}
|
||||
|
||||
private PersistentPropertyPathExtension getPath(String dotPath) {
|
||||
return new PersistentPropertyPathExtension(context, toPath(dotPath, DummyEntity.class, context));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -120,6 +131,8 @@ public class JdbcIdentifierBuilderUnitTests {
|
||||
List<Child> moreChildren;
|
||||
|
||||
Embeddable embeddable;
|
||||
|
||||
NoId noId;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -127,5 +140,10 @@ public class JdbcIdentifierBuilderUnitTests {
|
||||
Child child;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class NoId {
|
||||
Child child;
|
||||
}
|
||||
|
||||
static class Child {}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.function.Consumer;
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.convert.SqlGenerator;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import static java.util.Collections.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
@@ -23,9 +23,11 @@ import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.PropertyPathTestingUtils;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.Embedded;
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.sql.Aliased;
|
||||
@@ -206,7 +208,7 @@ public class SqlGeneratorEmbeddedUnitTests {
|
||||
|
||||
softly.assertThat(join.getJoinTable().getName()).isEqualTo("other_entity");
|
||||
softly.assertThat(join.getJoinColumn().getTable()).isEqualTo(join.getJoinTable());
|
||||
softly.assertThat(join.getJoinColumn().getName()).isEqualTo("embedded_with_reference");
|
||||
softly.assertThat(join.getJoinColumn().getName()).isEqualTo("dummy_entity2");
|
||||
softly.assertThat(join.getParentId().getName()).isEqualTo("id");
|
||||
softly.assertThat(join.getParentId().getTable().getName()).isEqualTo("dummy_entity2");
|
||||
});
|
||||
@@ -13,13 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.convert.SqlGenerator;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import static java.util.Collections.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
@@ -24,15 +24,16 @@ import java.util.Set;
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.jdbc.core.PropertyPathTestingUtils;
|
||||
import org.springframework.data.jdbc.core.mapping.AggregateReference;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.NamingStrategy;
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
@@ -357,6 +358,31 @@ public class SqlGeneratorUnitTests {
|
||||
")))");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-359
|
||||
public void deletingLongChainNoId() {
|
||||
|
||||
assertThat(createSqlGenerator(NoIdChain4.class)
|
||||
.createDeleteByPath(getPath("chain3.chain2.chain1.chain0", NoIdChain4.class))) //
|
||||
.isEqualTo("DELETE FROM no_id_chain0 WHERE no_id_chain0.no_id_chain4 = :rootId");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-359
|
||||
public void deletingLongChainNoIdWithBackreferenceNotReferencingTheRoot() {
|
||||
|
||||
assertThat(createSqlGenerator(IdIdNoIdChain.class)
|
||||
.createDeleteByPath(getPath("idNoIdChain.chain4.chain3.chain2.chain1.chain0", IdIdNoIdChain.class))) //
|
||||
.isEqualTo( //
|
||||
"DELETE FROM no_id_chain0 " //
|
||||
+ "WHERE no_id_chain0.no_id_chain4 IN (" //
|
||||
+ "SELECT no_id_chain4.x_four " //
|
||||
+ "FROM no_id_chain4 " //
|
||||
+ "WHERE no_id_chain4.id_no_id_chain IN (" //
|
||||
+ "SELECT id_no_id_chain.x_id " //
|
||||
+ "FROM id_no_id_chain " //
|
||||
+ "WHERE id_no_id_chain.id_id_no_id_chain = :rootId" //
|
||||
+ "))");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-340
|
||||
public void noJoinForSimpleColumn() {
|
||||
assertThat(generateJoin("id", DummyEntity.class)).isNull();
|
||||
@@ -585,4 +611,39 @@ public class SqlGeneratorUnitTests {
|
||||
String fourValue;
|
||||
Chain3 chain3;
|
||||
}
|
||||
|
||||
static class NoIdChain0 {
|
||||
String zeroValue;
|
||||
}
|
||||
|
||||
static class NoIdChain1 {
|
||||
String oneValue;
|
||||
NoIdChain0 chain0;
|
||||
}
|
||||
|
||||
static class NoIdChain2 {
|
||||
String twoValue;
|
||||
NoIdChain1 chain1;
|
||||
}
|
||||
|
||||
static class NoIdChain3 {
|
||||
String threeValue;
|
||||
NoIdChain2 chain2;
|
||||
}
|
||||
|
||||
static class NoIdChain4 {
|
||||
@Id Long four;
|
||||
String fourValue;
|
||||
NoIdChain3 chain3;
|
||||
}
|
||||
|
||||
static class IdNoIdChain {
|
||||
@Id Long id;
|
||||
NoIdChain4 chain4;
|
||||
}
|
||||
|
||||
static class IdIdNoIdChain {
|
||||
@Id Long id;
|
||||
IdNoIdChain idNoIdChain;
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,11 @@ public class DependencyTests {
|
||||
classpath() //
|
||||
.noJars() //
|
||||
.including("org.springframework.data.jdbc.**") //
|
||||
// the following exclusion exclude deprecated classes necessary for backward compatibility.
|
||||
.excluding("org.springframework.data.jdbc.core.EntityRowMapper") //
|
||||
.excluding("org.springframework.data.jdbc.core.DefaultDataAccessStrategy") //
|
||||
.filterClasspath("*target/classes") // exclude test code
|
||||
.printOnFailure("degraph.graphml"),
|
||||
.printOnFailure("degraph-jdbc.graphml"),
|
||||
JCheck.violationFree());
|
||||
}
|
||||
|
||||
@@ -49,6 +52,9 @@ public class DependencyTests {
|
||||
classpath() //
|
||||
// include only Spring Data related classes (for example no JDK code)
|
||||
.including("org.springframework.data.**") //
|
||||
// the following exclusion exclude deprecated classes necessary for backward compatibility.
|
||||
.excluding("org.springframework.data.jdbc.core.EntityRowMapper") //
|
||||
.excluding("org.springframework.data.jdbc.core.DefaultDataAccessStrategy") //
|
||||
.filterClasspath(new AbstractFunction1<String, Object>() {
|
||||
@Override
|
||||
public Object apply(String s) { //
|
||||
|
||||
@@ -242,6 +242,7 @@ public class JdbcRepositoryEmbeddedWithReferenceIntegrationTests {
|
||||
|
||||
@Data
|
||||
private static class Embeddable {
|
||||
|
||||
@Column("id")
|
||||
DummyEntity2 dummyEntity2;
|
||||
|
||||
|
||||
@@ -33,15 +33,14 @@ import org.assertj.core.groups.Tuple;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
|
||||
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
|
||||
import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository;
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
@@ -32,13 +34,12 @@ import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositoriesIntegrationTests.TestConfiguration;
|
||||
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
@@ -49,8 +50,6 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Tests the {@link EnableJdbcRepositories} annotation.
|
||||
*
|
||||
@@ -66,7 +65,8 @@ public class EnableJdbcRepositoriesIntegrationTests {
|
||||
static final Field MAPPER_MAP = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class,
|
||||
"queryMappingConfiguration");
|
||||
static final Field OPERATIONS = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "operations");
|
||||
static final Field DATA_ACCESS_STRATEGY = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "dataAccessStrategy");
|
||||
static final Field DATA_ACCESS_STRATEGY = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class,
|
||||
"dataAccessStrategy");
|
||||
public static final RowMapper DUMMY_ENTITY_ROW_MAPPER = mock(RowMapper.class);
|
||||
public static final RowMapper STRING_ROW_MAPPER = mock(RowMapper.class);
|
||||
public static final ResultSetExtractor<Integer> INTEGER_RESULT_SET_EXTRACTOR = mock(ResultSetExtractor.class);
|
||||
@@ -105,13 +105,15 @@ public class EnableJdbcRepositoriesIntegrationTests {
|
||||
assertThat(mapping.getRowMapper(DummyEntity.class)).isEqualTo(DUMMY_ENTITY_ROW_MAPPER);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-293
|
||||
@Test // DATAJDBC-293
|
||||
public void jdbcOperationsRef() {
|
||||
|
||||
NamedParameterJdbcOperations operations = (NamedParameterJdbcOperations) ReflectionUtils.getField(OPERATIONS, factoryBean);
|
||||
NamedParameterJdbcOperations operations = (NamedParameterJdbcOperations) ReflectionUtils.getField(OPERATIONS,
|
||||
factoryBean);
|
||||
assertThat(operations).isNotSameAs(defaultOperations).isSameAs(qualifierJdbcOperations);
|
||||
|
||||
DataAccessStrategy dataAccessStrategy = (DataAccessStrategy) ReflectionUtils.getField(DATA_ACCESS_STRATEGY, factoryBean);
|
||||
DataAccessStrategy dataAccessStrategy = (DataAccessStrategy) ReflectionUtils.getField(DATA_ACCESS_STRATEGY,
|
||||
factoryBean);
|
||||
assertThat(dataAccessStrategy).isNotSameAs(defaultDataAccessStrategy).isSameAs(qualifierDataAccessStrategy);
|
||||
}
|
||||
|
||||
@@ -149,7 +151,8 @@ public class EnableJdbcRepositoriesIntegrationTests {
|
||||
}
|
||||
|
||||
@Bean("qualifierDataAccessStrategy")
|
||||
DataAccessStrategy defaultDataAccessStrategy(@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template,
|
||||
DataAccessStrategy defaultDataAccessStrategy(
|
||||
@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template,
|
||||
RelationalMappingContext context, JdbcConverter converter) {
|
||||
return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter, template);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.repository.config.DefaultQueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.repository.query.Query;
|
||||
@@ -55,7 +57,7 @@ public class JdbcQueryLookupStrategyUnitTests {
|
||||
|
||||
ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class);
|
||||
RelationalMappingContext mappingContext = mock(RelationalMappingContext.class, RETURNS_DEEP_STUBS);
|
||||
RelationalConverter converter = mock(BasicRelationalConverter.class);
|
||||
JdbcConverter converter = mock(JdbcConverter.class);
|
||||
DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class);
|
||||
ProjectionFactory projectionFactory = mock(ProjectionFactory.class);
|
||||
RepositoryMetadata metadata;
|
||||
|
||||
@@ -32,8 +32,8 @@ import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcTypeFactory;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
|
||||
@@ -28,15 +28,14 @@ import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
|
||||
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.NamingStrategy;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
@@ -63,7 +62,7 @@ public class TestConfiguration {
|
||||
@Bean
|
||||
JdbcRepositoryFactory jdbcRepositoryFactory(
|
||||
@Qualifier("defaultDataAccessStrategy") DataAccessStrategy dataAccessStrategy, RelationalMappingContext context,
|
||||
RelationalConverter converter) {
|
||||
JdbcConverter converter) {
|
||||
return new JdbcRepositoryFactory(dataAccessStrategy, context, converter, publisher, namedParameterJdbcTemplate());
|
||||
}
|
||||
|
||||
@@ -100,6 +99,7 @@ public class TestConfiguration {
|
||||
@Bean
|
||||
JdbcConverter relationalConverter(RelationalMappingContext mappingContext, CustomConversions conversions,
|
||||
@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template) {
|
||||
return new BasicJdbcConverter(mappingContext, conversions, new DefaultJdbcTypeFactory(template.getJdbcOperations()));
|
||||
return new BasicJdbcConverter(mappingContext, conversions,
|
||||
new DefaultJdbcTypeFactory(template.getJdbcOperations()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,3 +91,37 @@ CREATE TABLE CHAIN0
|
||||
CHAIN1 BIGINT,
|
||||
FOREIGN KEY (CHAIN1) REFERENCES CHAIN1 (ONE)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN4
|
||||
(
|
||||
FOUR BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 40) PRIMARY KEY,
|
||||
FOUR_VALUE VARCHAR(20)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN3
|
||||
(
|
||||
THREE_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN2
|
||||
(
|
||||
TWO_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN1
|
||||
(
|
||||
ONE_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN0
|
||||
(
|
||||
ZERO_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
@@ -82,3 +82,37 @@ CREATE TABLE CHAIN0
|
||||
CHAIN1 BIGINT,
|
||||
FOREIGN KEY (CHAIN1) REFERENCES CHAIN1(ONE)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN4
|
||||
(
|
||||
FOUR BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
FOUR_VALUE VARCHAR(20)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN3
|
||||
(
|
||||
THREE_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN2
|
||||
(
|
||||
TWO_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN1
|
||||
(
|
||||
ONE_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN0
|
||||
(
|
||||
ZERO_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
@@ -90,4 +90,38 @@ CREATE TABLE CHAIN0
|
||||
ZERO_VALUE VARCHAR(20),
|
||||
CHAIN1 BIGINT,
|
||||
FOREIGN KEY (CHAIN1) REFERENCES CHAIN1 (ONE)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN4
|
||||
(
|
||||
FOUR BIGINT IDENTITY PRIMARY KEY,
|
||||
FOUR_VALUE VARCHAR(20)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN3
|
||||
(
|
||||
THREE_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN2
|
||||
(
|
||||
TWO_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN1
|
||||
(
|
||||
ONE_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN0
|
||||
(
|
||||
ZERO_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
@@ -81,4 +81,38 @@ CREATE TABLE CHAIN0
|
||||
ZERO_VALUE VARCHAR(20),
|
||||
CHAIN1 BIGINT,
|
||||
FOREIGN KEY (CHAIN1) REFERENCES CHAIN1(ONE)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN4
|
||||
(
|
||||
FOUR BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
FOUR_VALUE VARCHAR(20)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN3
|
||||
(
|
||||
THREE_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN2
|
||||
(
|
||||
TWO_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN1
|
||||
(
|
||||
ONE_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN0
|
||||
(
|
||||
ZERO_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
@@ -101,4 +101,38 @@ CREATE TABLE CHAIN0
|
||||
ZERO_VALUE VARCHAR(20),
|
||||
CHAIN1 BIGINT,
|
||||
FOREIGN KEY (CHAIN1) REFERENCES CHAIN1 (ONE)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN4
|
||||
(
|
||||
FOUR SERIAL PRIMARY KEY,
|
||||
FOUR_VALUE VARCHAR(20)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN3
|
||||
(
|
||||
THREE_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN2
|
||||
(
|
||||
TWO_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN1
|
||||
(
|
||||
ONE_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
CREATE TABLE NO_ID_CHAIN0
|
||||
(
|
||||
ZERO_VALUE VARCHAR(20),
|
||||
NO_ID_CHAIN4 BIGINT,
|
||||
FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR)
|
||||
);
|
||||
|
||||
@@ -1,2 +1,13 @@
|
||||
CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100), PREFIX_TEST VARCHAR(100));
|
||||
CREATE TABLE dummy_entity2 ( id BIGINT, ORDER_KEY BIGINT, TEST VARCHAR(100), PRIMARY KEY(id, ORDER_KEY))
|
||||
CREATE TABLE dummy_entity
|
||||
(
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
|
||||
TEST VARCHAR(100),
|
||||
PREFIX_TEST VARCHAR(100)
|
||||
);
|
||||
CREATE TABLE dummy_entity2
|
||||
(
|
||||
id BIGINT,
|
||||
ORDER_KEY BIGINT,
|
||||
TEST VARCHAR(100),
|
||||
PRIMARY KEY (id, ORDER_KEY)
|
||||
)
|
||||
|
||||
@@ -1,2 +1,11 @@
|
||||
CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100), PREFIX_TEST VARCHAR(100));
|
||||
CREATE TABLE dummy_entity2 ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100))
|
||||
CREATE TABLE dummy_entity
|
||||
(
|
||||
ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
|
||||
TEST VARCHAR(100),
|
||||
PREFIX_TEST VARCHAR(100)
|
||||
);
|
||||
CREATE TABLE dummy_entity2
|
||||
(
|
||||
ID BIGINT,
|
||||
TEST VARCHAR(100)
|
||||
)
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.Set;
|
||||
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
@@ -43,12 +44,10 @@ public class AggregateChange<T> {
|
||||
|
||||
/** Type of the aggregate root to be changed */
|
||||
private final Class<T> entityType;
|
||||
|
||||
private final List<DbAction<?>> actions = new ArrayList<>();
|
||||
/** Aggregate root, to which the change applies, if available */
|
||||
@Nullable private T entity;
|
||||
|
||||
private final List<DbAction<?>> actions = new ArrayList<>();
|
||||
|
||||
public AggregateChange(Kind kind, Class<T> entityType, @Nullable T entity) {
|
||||
|
||||
this.kind = kind;
|
||||
@@ -57,55 +56,11 @@ public class AggregateChange<T> {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void executeWith(Interpreter interpreter, RelationalMappingContext context, RelationalConverter converter) {
|
||||
|
||||
RelationalPersistentEntity<T> persistentEntity = entity != null
|
||||
? (RelationalPersistentEntity<T>) context.getRequiredPersistentEntity(entity.getClass())
|
||||
: null;
|
||||
|
||||
PersistentPropertyAccessor<T> propertyAccessor = //
|
||||
persistentEntity != null //
|
||||
? converter.getPropertyAccessor(persistentEntity, entity) //
|
||||
: null;
|
||||
|
||||
actions.forEach(a -> {
|
||||
|
||||
a.executeWith(interpreter);
|
||||
|
||||
if (a instanceof DbAction.WithGeneratedId) {
|
||||
|
||||
Assert.notNull(persistentEntity,
|
||||
"For statements triggering database side id generation a RelationalPersistentEntity must be provided.");
|
||||
Assert.notNull(propertyAccessor, "propertyAccessor must not be null");
|
||||
|
||||
Object generatedId = ((DbAction.WithGeneratedId<?>) a).getGeneratedId();
|
||||
|
||||
if (generatedId != null) {
|
||||
|
||||
if (a instanceof DbAction.InsertRoot && a.getEntityType().equals(entityType)) {
|
||||
propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), generatedId);
|
||||
} else if (a instanceof DbAction.WithDependingOn) {
|
||||
|
||||
setId(context, converter, propertyAccessor, (DbAction.WithDependingOn<?>) a, generatedId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (propertyAccessor != null) {
|
||||
entity = propertyAccessor.getBean();
|
||||
}
|
||||
}
|
||||
|
||||
public void addAction(DbAction<?> action) {
|
||||
actions.add(action);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static void setId(RelationalMappingContext context, RelationalConverter converter,
|
||||
static void setIdOfNonRootEntity(RelationalMappingContext context, RelationalConverter converter,
|
||||
PersistentPropertyAccessor<?> propertyAccessor, DbAction.WithDependingOn<?> action, Object generatedId) {
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> propertyPathToEntity = action.getPropertyPath();
|
||||
PersistentPropertyPathExtension extPath = new PersistentPropertyPathExtension(context, propertyPathToEntity);
|
||||
|
||||
RelationalPersistentProperty leafProperty = propertyPathToEntity.getRequiredLeafProperty();
|
||||
|
||||
@@ -130,7 +85,7 @@ public class AggregateChange<T> {
|
||||
} else {
|
||||
throw new IllegalStateException("Can't handle " + currentPropertyValue);
|
||||
}
|
||||
} else {
|
||||
} else if (extPath.hasIdProperty()) {
|
||||
|
||||
RelationalPersistentProperty requiredIdProperty = context
|
||||
.getRequiredPersistentEntity(propertyPathToEntity.getRequiredLeafProperty().getActualType())
|
||||
@@ -199,6 +154,57 @@ public class AggregateChange<T> {
|
||||
return intermediateAccessor;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void executeWith(Interpreter interpreter, RelationalMappingContext context, RelationalConverter converter) {
|
||||
|
||||
RelationalPersistentEntity<T> persistentEntity = entity != null
|
||||
? (RelationalPersistentEntity<T>) context.getRequiredPersistentEntity(entity.getClass())
|
||||
: null;
|
||||
|
||||
PersistentPropertyAccessor<T> propertyAccessor = //
|
||||
persistentEntity != null //
|
||||
? converter.getPropertyAccessor(persistentEntity, entity) //
|
||||
: null;
|
||||
|
||||
actions.forEach(a -> {
|
||||
|
||||
a.executeWith(interpreter);
|
||||
|
||||
processGeneratedId(context, converter, persistentEntity, propertyAccessor, a);
|
||||
});
|
||||
|
||||
if (propertyAccessor != null) {
|
||||
entity = propertyAccessor.getBean();
|
||||
}
|
||||
}
|
||||
|
||||
public void addAction(DbAction<?> action) {
|
||||
actions.add(action);
|
||||
}
|
||||
|
||||
private void processGeneratedId(RelationalMappingContext context, RelationalConverter converter,
|
||||
RelationalPersistentEntity<T> persistentEntity, PersistentPropertyAccessor<T> propertyAccessor, DbAction<?> a) {
|
||||
|
||||
if (a instanceof DbAction.WithGeneratedId) {
|
||||
|
||||
Assert.notNull(persistentEntity,
|
||||
"For statements triggering database side id generation a RelationalPersistentEntity must be provided.");
|
||||
Assert.notNull(propertyAccessor, "propertyAccessor must not be null");
|
||||
|
||||
Object generatedId = ((DbAction.WithGeneratedId<?>) a).getGeneratedId();
|
||||
|
||||
if (generatedId != null) {
|
||||
|
||||
if (a instanceof DbAction.InsertRoot && a.getEntityType().equals(entityType)) {
|
||||
propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), generatedId);
|
||||
} else if (a instanceof DbAction.WithDependingOn) {
|
||||
|
||||
setIdOfNonRootEntity(context, converter, propertyAccessor, (DbAction.WithDependingOn<?>) a, generatedId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of action to be performed on an aggregate.
|
||||
*/
|
||||
|
||||
@@ -180,6 +180,12 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
|
||||
return collectionIdColumnName.get().orElseGet(() -> context.getNamingStrategy().getReverseColumnName(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReverseColumnName(PersistentPropertyPathExtension path) {
|
||||
|
||||
return collectionIdColumnName.get().orElseGet(() -> context.getNamingStrategy().getReverseColumnName(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKeyColumn() {
|
||||
|
||||
|
||||
@@ -88,6 +88,11 @@ public interface NamingStrategy {
|
||||
return property.getOwner().getTableName();
|
||||
}
|
||||
|
||||
default String getReverseColumnName(PersistentPropertyPathExtension path) {
|
||||
|
||||
return getTableName(path.getIdDefiningParentPath().getLeafEntity().getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* For a map valued reference A -> Map>X,B< this is the name of the column in the table for B holding the key of
|
||||
* the map.
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
package org.springframework.data.relational.core.mapping;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -29,13 +29,15 @@ import org.springframework.util.Assert;
|
||||
* @author Jens Schauder
|
||||
* @since 1.1
|
||||
*/
|
||||
class PersistentPropertyPathExtension {
|
||||
@EqualsAndHashCode
|
||||
public class PersistentPropertyPathExtension {
|
||||
|
||||
private final RelationalPersistentEntity<?> entity;
|
||||
private final @Nullable PersistentPropertyPath<RelationalPersistentProperty> path;
|
||||
private final MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context;
|
||||
|
||||
PersistentPropertyPathExtension(MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context,
|
||||
public PersistentPropertyPathExtension(
|
||||
MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context,
|
||||
RelationalPersistentEntity<?> entity) {
|
||||
|
||||
Assert.notNull(context, "Context must not be null.");
|
||||
@@ -46,7 +48,8 @@ class PersistentPropertyPathExtension {
|
||||
this.path = null;
|
||||
}
|
||||
|
||||
PersistentPropertyPathExtension(MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context,
|
||||
public PersistentPropertyPathExtension(
|
||||
MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context,
|
||||
PersistentPropertyPath<RelationalPersistentProperty> path) {
|
||||
|
||||
Assert.notNull(context, "Context must not be null.");
|
||||
@@ -63,7 +66,7 @@ class PersistentPropertyPathExtension {
|
||||
*
|
||||
* @return if the leaf property is embedded.
|
||||
*/
|
||||
boolean isEmbedded() {
|
||||
public boolean isEmbedded() {
|
||||
return path != null && path.getRequiredLeafProperty().isEmbedded();
|
||||
}
|
||||
|
||||
@@ -73,7 +76,7 @@ class PersistentPropertyPathExtension {
|
||||
* @return the parent path. Guaranteed to be not {@literal null}.
|
||||
* @throws IllegalStateException when called on an empty path.
|
||||
*/
|
||||
PersistentPropertyPathExtension getParentPath() {
|
||||
public PersistentPropertyPathExtension getParentPath() {
|
||||
|
||||
if (path == null) {
|
||||
throw new IllegalStateException("The parent path of a root path is not defined.");
|
||||
@@ -92,7 +95,7 @@ class PersistentPropertyPathExtension {
|
||||
*
|
||||
* @return {@literal true} if the path contains a multivalued element.
|
||||
*/
|
||||
boolean isMultiValued() {
|
||||
public boolean isMultiValued() {
|
||||
|
||||
return path != null && //
|
||||
(path.getRequiredLeafProperty().isCollectionLike() //
|
||||
@@ -107,28 +110,28 @@ class PersistentPropertyPathExtension {
|
||||
* @return Might return {@literal null} when called on a path that does not represent an entity.
|
||||
*/
|
||||
@Nullable
|
||||
RelationalPersistentEntity<?> getLeafEntity() {
|
||||
public RelationalPersistentEntity<?> getLeafEntity() {
|
||||
return path == null ? entity : context.getPersistentEntity(path.getRequiredLeafProperty().getActualType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} when this is an empty path or the path references an entity.
|
||||
*/
|
||||
boolean isEntity() {
|
||||
public boolean isEntity() {
|
||||
return path == null || path.getRequiredLeafProperty().isEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} when this is references a {@link java.util.List} or {@link java.util.Map}.
|
||||
*/
|
||||
boolean isQualified() {
|
||||
public boolean isQualified() {
|
||||
return path != null && path.getRequiredLeafProperty().isQualified();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} when this is references a {@link java.util.Collection} or an array.
|
||||
*/
|
||||
boolean isCollectionLike() {
|
||||
public boolean isCollectionLike() {
|
||||
return path != null && path.getRequiredLeafProperty().isCollectionLike();
|
||||
}
|
||||
|
||||
@@ -137,11 +140,11 @@ class PersistentPropertyPathExtension {
|
||||
*
|
||||
* @throws IllegalStateException when called on an empty path.
|
||||
*/
|
||||
String getReverseColumnName() {
|
||||
public String getReverseColumnName() {
|
||||
|
||||
Assert.state(path != null, "Path is null");
|
||||
Assert.state(path != null, "Empty paths don't have a reverse column name");
|
||||
|
||||
return path.getRequiredLeafProperty().getReverseColumnName();
|
||||
return path.getRequiredLeafProperty().getReverseColumnName(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,7 +152,7 @@ class PersistentPropertyPathExtension {
|
||||
*
|
||||
* @throws IllegalStateException when called on an empty path.
|
||||
*/
|
||||
String getReverseColumnNameAlias() {
|
||||
public String getReverseColumnNameAlias() {
|
||||
|
||||
return prefixWithTableAlias(getReverseColumnName());
|
||||
}
|
||||
@@ -159,7 +162,7 @@ class PersistentPropertyPathExtension {
|
||||
*
|
||||
* @throws IllegalStateException when called on an empty path.
|
||||
*/
|
||||
String getColumnName() {
|
||||
public String getColumnName() {
|
||||
|
||||
Assert.state(path != null, "Path is null");
|
||||
|
||||
@@ -171,7 +174,7 @@ class PersistentPropertyPathExtension {
|
||||
*
|
||||
* @throws IllegalStateException when called on an empty path.
|
||||
*/
|
||||
String getColumnAlias() {
|
||||
public String getColumnAlias() {
|
||||
|
||||
return prefixWithTableAlias(getColumnName());
|
||||
}
|
||||
@@ -179,20 +182,20 @@ class PersistentPropertyPathExtension {
|
||||
/**
|
||||
* @return {@literal true} if this path represents an entity which has an Id attribute.
|
||||
*/
|
||||
boolean hasIdProperty() {
|
||||
public boolean hasIdProperty() {
|
||||
|
||||
RelationalPersistentEntity<?> leafEntity = getLeafEntity();
|
||||
return leafEntity != null && leafEntity.hasIdProperty();
|
||||
}
|
||||
|
||||
PersistentPropertyPathExtension getIdDefiningParentPath() {
|
||||
public PersistentPropertyPathExtension getIdDefiningParentPath() {
|
||||
|
||||
PersistentPropertyPathExtension parent = getParentPath();
|
||||
if (parent.path == null) {
|
||||
return parent;
|
||||
}
|
||||
if (parent.isEmbedded()) {
|
||||
return getParentPath().getIdDefiningParentPath();
|
||||
if (!parent.hasIdProperty()) {
|
||||
return parent.getIdDefiningParentPath();
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
@@ -202,7 +205,7 @@ class PersistentPropertyPathExtension {
|
||||
*
|
||||
* @return the name of the table. Guaranteed to be not {@literal null}.
|
||||
*/
|
||||
String getTableName() {
|
||||
public String getTableName() {
|
||||
return getTableOwningAncestor().getRequiredLeafEntity().getTableName();
|
||||
}
|
||||
|
||||
@@ -212,7 +215,7 @@ class PersistentPropertyPathExtension {
|
||||
* @return a table alias, {@literal null} if the table owning path is the empty path.
|
||||
*/
|
||||
@Nullable
|
||||
String getTableAlias() {
|
||||
public String getTableAlias() {
|
||||
|
||||
PersistentPropertyPathExtension tableOwner = getTableOwningAncestor();
|
||||
|
||||
@@ -223,7 +226,7 @@ class PersistentPropertyPathExtension {
|
||||
/**
|
||||
* The column name of the id column of the ancestor path that represents an actual table.
|
||||
*/
|
||||
String getIdColumnName() {
|
||||
public String getIdColumnName() {
|
||||
return getTableOwningAncestor().getRequiredLeafEntity().getIdColumn();
|
||||
}
|
||||
|
||||
@@ -231,7 +234,7 @@ class PersistentPropertyPathExtension {
|
||||
* If the table owning ancestor has an id the column name of that id property is returned. Otherwise the reverse
|
||||
* column is returned.
|
||||
*/
|
||||
String getEffectiveIdColumnName() {
|
||||
public String getEffectiveIdColumnName() {
|
||||
|
||||
PersistentPropertyPathExtension owner = getTableOwningAncestor();
|
||||
return owner.path == null ? owner.getRequiredLeafEntity().getIdColumn() : owner.getReverseColumnName();
|
||||
@@ -240,7 +243,7 @@ class PersistentPropertyPathExtension {
|
||||
/**
|
||||
* The length of the path.
|
||||
*/
|
||||
int getLength() {
|
||||
public int getLength() {
|
||||
return path == null ? 0 : path.getLength();
|
||||
}
|
||||
|
||||
@@ -299,4 +302,37 @@ class PersistentPropertyPathExtension {
|
||||
return tableAlias == null ? columnName : tableAlias + "_" + columnName;
|
||||
}
|
||||
|
||||
public boolean matches(PersistentPropertyPath<RelationalPersistentProperty> path) {
|
||||
return this.path == null ? path.isEmpty() : this.path.equals(path);
|
||||
}
|
||||
|
||||
public RelationalPersistentProperty getRequiredIdProperty() {
|
||||
return this.path == null ? entity.getRequiredIdProperty() : getLeafEntity().getRequiredIdProperty();
|
||||
}
|
||||
|
||||
public String getKeyColumn() {
|
||||
return path == null ? "" : path.getRequiredLeafProperty().getKeyColumn();
|
||||
}
|
||||
|
||||
public Class<?> getQualifierColumnType() {
|
||||
return path == null ? null : path.getRequiredLeafProperty().getQualifierColumnType();
|
||||
}
|
||||
|
||||
public PersistentPropertyPathExtension extendBy(RelationalPersistentProperty property) {
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> newPath;
|
||||
if (path == null) {
|
||||
newPath = context.getPersistentPropertyPath(property.getName(), entity.getType());
|
||||
} else {
|
||||
newPath = context.getPersistentPropertyPath(path.toDotPath() + "." + property.getName(), entity.getType());
|
||||
}
|
||||
|
||||
return new PersistentPropertyPathExtension(context, newPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PersistentPropertyPathExtension[%s, %s]", entity.getName(),
|
||||
path == null ? "-" : path.toDotPath());
|
||||
}
|
||||
}
|
||||
@@ -56,8 +56,17 @@ public interface RelationalPersistentProperty extends PersistentProperty<Relatio
|
||||
@Override
|
||||
RelationalPersistentEntity<?> getOwner();
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @deprecated Use {@link #getReverseColumnName(PersistentPropertyPathExtension)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
String getReverseColumnName();
|
||||
|
||||
default String getReverseColumnName(PersistentPropertyPathExtension path) {
|
||||
return getReverseColumnName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getKeyColumn();
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
@NonNullApi
|
||||
package org.springframework.data.relational.domain;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
@@ -67,7 +67,7 @@ public class AggregateChangeUnitTests {
|
||||
|
||||
DbAction.Insert<?> insert = createInsert("single", content, null);
|
||||
|
||||
AggregateChange.setId(context, converter, propertyAccessor, insert, id);
|
||||
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
|
||||
|
||||
DummyEntity result = propertyAccessor.getBean();
|
||||
|
||||
@@ -81,7 +81,7 @@ public class AggregateChangeUnitTests {
|
||||
|
||||
DbAction.Insert<?> insert = createInsert("contentSet", content, null);
|
||||
|
||||
AggregateChange.setId(context, converter, propertyAccessor, insert, id);
|
||||
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
|
||||
|
||||
DummyEntity result = propertyAccessor.getBean();
|
||||
assertThat(result.contentSet).isNotNull();
|
||||
@@ -95,7 +95,7 @@ public class AggregateChangeUnitTests {
|
||||
|
||||
DbAction.Insert<?> insert = createInsert("contentList", content, 0);
|
||||
|
||||
AggregateChange.setId(context, converter, propertyAccessor, insert, id);
|
||||
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
|
||||
|
||||
DummyEntity result = propertyAccessor.getBean();
|
||||
assertThat(result.contentList).extracting(c -> c.id).containsExactlyInAnyOrder(23);
|
||||
@@ -108,7 +108,7 @@ public class AggregateChangeUnitTests {
|
||||
|
||||
DbAction.Insert<?> insert = createInsert("contentMap", content, "one");
|
||||
|
||||
AggregateChange.setId(context, converter, propertyAccessor, insert, id);
|
||||
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
|
||||
|
||||
DummyEntity result = propertyAccessor.getBean();
|
||||
assertThat(result.contentMap.entrySet()).extracting(e -> e.getKey(), e -> e.getValue().id)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.relational.degraph;
|
||||
|
||||
import static de.schauderhaft.degraph.check.JCheck.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import de.schauderhaft.degraph.check.JCheck;
|
||||
import scala.runtime.AbstractFunction1;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test package dependencies for violations.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class DependencyTests {
|
||||
|
||||
@Test // DATAJDBC-114
|
||||
public void cycleFree() {
|
||||
|
||||
assertThat( //
|
||||
classpath() //
|
||||
.noJars() //
|
||||
.including("org.springframework.data.relational.**") //
|
||||
.filterClasspath("*target/classes") // exclude test code
|
||||
.printOnFailure("degraph-relational.graphml"),
|
||||
JCheck.violationFree());
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-220
|
||||
public void acrossModules() {
|
||||
|
||||
assertThat( //
|
||||
classpath() //
|
||||
// include only Spring Data related classes (for example no JDK code)
|
||||
.including("org.springframework.data.**") //
|
||||
.filterClasspath(new AbstractFunction1<String, Object>() {
|
||||
@Override
|
||||
public Object apply(String s) { //
|
||||
// only the current module + commons
|
||||
return s.endsWith("target/classes") || s.contains("spring-data-commons");
|
||||
}
|
||||
}) // exclude test code
|
||||
.withSlicing("sub-modules", // sub-modules are defined by any of the following pattern.
|
||||
"org.springframework.data.relational.(**).*", //
|
||||
"org.springframework.data.(**).*") //
|
||||
.printTo("degraph-across-modules.graphml"), // writes a graphml to this location
|
||||
JCheck.violationFree());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user