DATAJDBC-326 - Polishing.
Add Javadoc. Tweak naming and factory methods. Add equals/hashcode to Identifier. Extend tests. Original pull request: #118.
This commit is contained in:
@@ -17,9 +17,9 @@ package org.springframework.data.jdbc.core;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.relational.domain.Identifier;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.domain.Identifier;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -40,25 +40,25 @@ public interface DataAccessStrategy {
|
||||
* to get referenced are contained in this map. Must not be {@code null}.
|
||||
* @param <T> the type of the instance.
|
||||
* @return the id generated by the database if any.
|
||||
*
|
||||
* @deprecated use {@link #insert(Object, Class, Identifier)} instead.
|
||||
* @deprecated since 1.1, use {@link #insert(Object, Class, Identifier)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
<T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters);
|
||||
|
||||
|
||||
/**
|
||||
* Inserts a the data of a single entity. Referenced entities don't get handled.
|
||||
*
|
||||
* @param instance the instance to be stored. Must not be {@code null}.
|
||||
* @param domainType the type of the instance. Must not be {@code null}.
|
||||
* @param identifier information about data that needs to be considered for the insert but which is not part of the entity.
|
||||
* Namely references back to a parent entity and key/index columns for entities that are stored in a {@link Map} or {@link java.util.List}.
|
||||
* @param identifier information about data that needs to be considered for the insert but which is not part of the
|
||||
* entity. Namely references back to a parent entity and key/index columns for entities that are stored in a
|
||||
* {@link Map} or {@link java.util.List}.
|
||||
* @param <T> the type of the instance.
|
||||
* @return the id generated by the database if any.
|
||||
* @since 1.1
|
||||
*/
|
||||
default <T> Object insert(T instance, Class<T> domainType, Identifier identifier){
|
||||
return insert(instance, domainType, identifier.getParametersByName());
|
||||
return insert(instance, domainType, identifier.toMap());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,7 +29,6 @@ import java.util.stream.StreamSupport;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcIdentifierBuilder;
|
||||
import org.springframework.data.jdbc.support.JdbcUtil;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
@@ -59,10 +58,6 @@ import org.springframework.util.Assert;
|
||||
@RequiredArgsConstructor
|
||||
public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
|
||||
private static final String ENTITY_NEW_AFTER_INSERT = "Entity [%s] still 'new' after insert. Please set either"
|
||||
+ " the id property in a BeforeInsert event handler, or ensure the database creates a value and your "
|
||||
+ "JDBC driver returns it.";
|
||||
|
||||
private final @NonNull SqlGeneratorSource sqlGeneratorSource;
|
||||
private final @NonNull RelationalMappingContext context;
|
||||
private final @NonNull RelationalConverter converter;
|
||||
@@ -89,7 +84,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
*/
|
||||
@Override
|
||||
public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
|
||||
return insert(instance, domainType, JdbcIdentifierBuilder.from(additionalParameters).build());
|
||||
return insert(instance, domainType, Identifier.from(additionalParameters));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -102,10 +97,9 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
KeyHolder holder = new GeneratedKeyHolder();
|
||||
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
|
||||
|
||||
Map<String, Object> parameters = new LinkedHashMap<>();
|
||||
identifier.forEach(identifierValue -> {
|
||||
parameters.put(identifierValue.getName(),
|
||||
converter.writeValue(identifierValue.getValue(), ClassTypeInformation.from(identifierValue.getTargetType())));
|
||||
Map<String, Object> parameters = new LinkedHashMap<>(identifier.size());
|
||||
identifier.forEach((name, value, type) -> {
|
||||
parameters.put(name, converter.writeValue(value, ClassTypeInformation.from(type)));
|
||||
});
|
||||
|
||||
MapSqlParameterSource parameterSource = getPropertyMap(instance, persistentEntity, "");
|
||||
@@ -298,7 +292,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
return result;
|
||||
}
|
||||
|
||||
private <S, T> MapSqlParameterSource getPropertyMap(final S instance, RelationalPersistentEntity<S> persistentEntity,
|
||||
private <S, T> MapSqlParameterSource getPropertyMap(S instance, RelationalPersistentEntity<S> persistentEntity,
|
||||
String prefix) {
|
||||
|
||||
MapSqlParameterSource parameters = new MapSqlParameterSource();
|
||||
@@ -314,8 +308,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
if (property.isEmbedded()) {
|
||||
|
||||
Object value = propertyAccessor.getProperty(property);
|
||||
final RelationalPersistentEntity<?> embeddedEntity = context.getPersistentEntity(property.getType());
|
||||
final MapSqlParameterSource additionalParameters = getPropertyMap((T) value,
|
||||
RelationalPersistentEntity<?> embeddedEntity = context.getPersistentEntity(property.getType());
|
||||
MapSqlParameterSource additionalParameters = getPropertyMap((T) value,
|
||||
(RelationalPersistentEntity<T>) embeddedEntity, prefix + property.getEmbeddedPrefix());
|
||||
parameters.addValues(additionalParameters.getValues());
|
||||
} else {
|
||||
|
||||
@@ -21,7 +21,6 @@ import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.jdbc.core.convert.JdbcIdentifierBuilder;
|
||||
import org.springframework.data.relational.domain.Identifier;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.conversion.DbAction;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Delete;
|
||||
@@ -37,6 +36,7 @@ import org.springframework.data.relational.core.conversion.Interpreter;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -142,7 +142,7 @@ class DefaultJdbcInterpreter implements Interpreter {
|
||||
accessStrategy.deleteAll(deleteAllRoot.getEntityType());
|
||||
}
|
||||
|
||||
private <T> Identifier getParentKeys(DbAction.WithDependingOn<?> action) {
|
||||
private Identifier getParentKeys(DbAction.WithDependingOn<?> action) {
|
||||
|
||||
DbAction.WithEntity<?> dependingOn = action.getDependingOn();
|
||||
|
||||
@@ -152,7 +152,8 @@ class DefaultJdbcInterpreter implements Interpreter {
|
||||
JdbcIdentifierBuilder identifier = JdbcIdentifierBuilder //
|
||||
.forBackReferences(action.getPropertyPath(), id);
|
||||
|
||||
for (Map.Entry<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifier : action.getQualifiers().entrySet()) {
|
||||
for (Map.Entry<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifier : action.getQualifiers()
|
||||
.entrySet()) {
|
||||
identifier = identifier.withQualifier(qualifier.getKey(), qualifier.getValue());
|
||||
}
|
||||
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.domain.Identifier;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Builder for {@link Identifier}. Mainly for internal use within the framework
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @since 1.1
|
||||
*/
|
||||
@@ -38,23 +38,13 @@ public class JdbcIdentifierBuilder {
|
||||
return new JdbcIdentifierBuilder(Identifier.empty());
|
||||
}
|
||||
|
||||
public static JdbcIdentifierBuilder from(Map<String, Object> additionalParameters) {
|
||||
|
||||
Identifier[] identifier = new Identifier[] { Identifier.empty() };
|
||||
|
||||
additionalParameters
|
||||
.forEach((k, v) -> identifier[0] = identifier[0].add(k, v, v == null ? Object.class : v.getClass()));
|
||||
|
||||
return new JdbcIdentifierBuilder(identifier[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates ParentKeys with backreference for the given path and value of the parents id.
|
||||
*/
|
||||
public static JdbcIdentifierBuilder forBackReferences(PersistentPropertyPath<RelationalPersistentProperty> path,
|
||||
@Nullable Object value) {
|
||||
|
||||
Identifier identifier = Identifier.simple( //
|
||||
Identifier identifier = Identifier.of( //
|
||||
path.getRequiredLeafProperty().getReverseColumnName(), //
|
||||
value, //
|
||||
getLastIdProperty(path).getColumnType() //
|
||||
@@ -66,7 +56,7 @@ public class JdbcIdentifierBuilder {
|
||||
public JdbcIdentifierBuilder withQualifier(PersistentPropertyPath<RelationalPersistentProperty> path, Object value) {
|
||||
|
||||
RelationalPersistentProperty leafProperty = path.getRequiredLeafProperty();
|
||||
identifier = identifier.add(leafProperty.getKeyColumn(), value, leafProperty.getQualifierColumnType());
|
||||
identifier = identifier.withPart(leafProperty.getKeyColumn(), value, leafProperty.getQualifierColumnType());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -22,17 +22,18 @@ 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.relational.domain.Identifier;
|
||||
import org.springframework.data.jdbc.core.SqlGeneratorSource;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.domain.Identifier;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -124,7 +125,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
this.namespaceStrategy = namespaceStrategy;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
|
||||
*/
|
||||
@@ -144,13 +145,13 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
@Override
|
||||
public <T> Object insert(T instance, Class<T> domainType, Identifier identifier) {
|
||||
|
||||
MyBatisContext myBatisContext = new MyBatisContext(null, instance, domainType, identifier.getParametersByName());
|
||||
MyBatisContext myBatisContext = new MyBatisContext(null, instance, domainType, identifier.toMap());
|
||||
sqlSession().insert(namespace(domainType) + ".insert", myBatisContext);
|
||||
|
||||
return myBatisContext.getId();
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@@ -161,7 +162,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
new MyBatisContext(null, instance, domainType, Collections.emptyMap())) != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@@ -172,7 +173,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
new MyBatisContext(id, null, domainType, Collections.emptyMap()));
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, org.springframework.data.mapping.PersistentPropertyPath)
|
||||
*/
|
||||
@@ -185,7 +186,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
Collections.emptyMap()));
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(java.lang.Class)
|
||||
*/
|
||||
@@ -198,7 +199,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(org.springframework.data.mapping.PersistentPropertyPath)
|
||||
*/
|
||||
@@ -214,7 +215,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findById(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@@ -224,7 +225,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
new MyBatisContext(id, null, domainType, Collections.emptyMap()));
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAll(java.lang.Class)
|
||||
*/
|
||||
@@ -234,7 +235,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
new MyBatisContext(null, null, domainType, Collections.emptyMap()));
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllById(java.lang.Iterable, java.lang.Class)
|
||||
*/
|
||||
@@ -244,7 +245,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
new MyBatisContext(ids, null, domainType, Collections.emptyMap()));
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllByProperty(java.lang.Object, org.springframework.data.relational.core.mapping.RelationalPersistentProperty)
|
||||
*/
|
||||
@@ -255,7 +256,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
new MyBatisContext(rootId, null, property.getType(), Collections.emptyMap()));
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#existsById(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@@ -265,7 +266,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
new MyBatisContext(id, null, domainType, Collections.emptyMap()));
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#count(java.lang.Class)
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,7 @@ import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Insert;
|
||||
@@ -68,7 +69,7 @@ public class DefaultJdbcInterpreterUnitTests {
|
||||
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
|
||||
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
|
||||
|
||||
assertThat(argumentCaptor.getValue().getParameters()) //
|
||||
assertThat(argumentCaptor.getValue().getParts()) //
|
||||
.extracting("name", "value", "targetType") //
|
||||
.containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class));
|
||||
}
|
||||
@@ -83,7 +84,7 @@ public class DefaultJdbcInterpreterUnitTests {
|
||||
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
|
||||
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
|
||||
|
||||
assertThat(argumentCaptor.getValue().getParameters()) //
|
||||
assertThat(argumentCaptor.getValue().getParts()) //
|
||||
.extracting("name", "value", "targetType") //
|
||||
.containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class));
|
||||
}
|
||||
@@ -98,7 +99,7 @@ public class DefaultJdbcInterpreterUnitTests {
|
||||
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
|
||||
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
|
||||
|
||||
assertThat(argumentCaptor.getValue().getParameters()) //
|
||||
assertThat(argumentCaptor.getValue().getParts()) //
|
||||
.extracting("name", "value", "targetType") //
|
||||
.containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class));
|
||||
}
|
||||
|
||||
@@ -18,16 +18,13 @@ package org.springframework.data.jdbc.core;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.jdbc.core.PropertyPathUtils.*;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.assertj.core.groups.Tuple;
|
||||
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.mapping.JdbcMappingContext;
|
||||
@@ -44,44 +41,12 @@ public class JdbcIdentifierBuilderUnitTests {
|
||||
|
||||
JdbcMappingContext context = new JdbcMappingContext();
|
||||
|
||||
@Test // DATAJDBC-326
|
||||
public void parametersWithStringKeysUseTheValuesType() {
|
||||
|
||||
HashMap<String, Object> parameters = new HashMap<>();
|
||||
parameters.put("one", "eins");
|
||||
parameters.put("two", 2L);
|
||||
|
||||
Identifier identifier = JdbcIdentifierBuilder.from(parameters).build();
|
||||
|
||||
assertThat(identifier.getParameters()) //
|
||||
.extracting("name", "value", "targetType") //
|
||||
.containsExactlyInAnyOrder( //
|
||||
tuple("one", "eins", String.class), //
|
||||
tuple("two", 2L, Long.class) //
|
||||
);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-326
|
||||
public void parametersWithStringKeysUseObjectAsTypeForNull() {
|
||||
|
||||
HashMap<String, Object> parameters = new HashMap<>();
|
||||
parameters.put("one", null);
|
||||
|
||||
Identifier identifier = JdbcIdentifierBuilder.from(parameters).build();
|
||||
|
||||
assertThat(identifier.getParameters()) //
|
||||
.extracting("name", "value", "targetType") //
|
||||
.containsExactly( //
|
||||
tuple("one", null, Object.class) //
|
||||
);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-326
|
||||
public void parametersWithPropertyKeysUseTheParentPropertyJdbcType() {
|
||||
|
||||
Identifier identifier = JdbcIdentifierBuilder.forBackReferences(getPath("child"), "eins").build();
|
||||
|
||||
assertThat(identifier.getParameters()) //
|
||||
assertThat(identifier.getParts()) //
|
||||
.extracting("name", "value", "targetType") //
|
||||
.containsExactly( //
|
||||
tuple("dummy_entity", "eins", UUID.class) //
|
||||
@@ -98,7 +63,7 @@ public class JdbcIdentifierBuilderUnitTests {
|
||||
.withQualifier(path, "map-key-eins") //
|
||||
.build();
|
||||
|
||||
assertThat(identifier.getParameters()) //
|
||||
assertThat(identifier.getParts()) //
|
||||
.extracting("name", "value", "targetType") //
|
||||
.containsExactlyInAnyOrder( //
|
||||
tuple("dummy_entity", "parent-eins", UUID.class), //
|
||||
@@ -116,7 +81,7 @@ public class JdbcIdentifierBuilderUnitTests {
|
||||
.withQualifier(path, "list-index-eins") //
|
||||
.build();
|
||||
|
||||
assertThat(identifier.getParameters()) //
|
||||
assertThat(identifier.getParts()) //
|
||||
.extracting("name", "value", "targetType") //
|
||||
.containsExactlyInAnyOrder( //
|
||||
tuple("dummy_entity", "parent-eins", UUID.class), //
|
||||
@@ -131,7 +96,7 @@ public class JdbcIdentifierBuilderUnitTests {
|
||||
.forBackReferences(getPath("embeddable.child"), "parent-eins") //
|
||||
.build();
|
||||
|
||||
assertThat(identifier.getParameters()) //
|
||||
assertThat(identifier.getParts()) //
|
||||
.extracting("name", "value", "targetType") //
|
||||
.containsExactly( //
|
||||
tuple("embeddable", "parent-eins", UUID.class) //
|
||||
|
||||
@@ -17,60 +17,165 @@ package org.springframework.data.relational.domain;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@literal Identifier} represents a multi part id of an entity. Parts or all of the entity might not have a
|
||||
* representation as a property in the entity but might only be derived from other entities referencing it.
|
||||
*
|
||||
* {@literal Identifier} represents a composite id of an entity that may be composed of one or many parts. Parts or all
|
||||
* of the entity might not have a representation as a property in the entity but might only be derived from other
|
||||
* entities referencing it.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public final class Identifier {
|
||||
|
||||
private final List<SingleIdentifierValue> keys;
|
||||
private static final Identifier EMPTY = new Identifier(Collections.emptyList());
|
||||
|
||||
private Identifier(List<SingleIdentifierValue> keys) {
|
||||
this.keys = keys;
|
||||
private final List<SingleIdentifierValue> parts;
|
||||
|
||||
private Identifier(List<SingleIdentifierValue> parts) {
|
||||
this.parts = parts;
|
||||
}
|
||||
|
||||
static public Identifier empty() {
|
||||
return new Identifier(Collections.emptyList());
|
||||
/**
|
||||
* Returns an empty {@link Identifier}.
|
||||
*
|
||||
* @return an empty {@link Identifier}.
|
||||
*/
|
||||
public static Identifier empty() {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
static public Identifier simple(String name, Object value, Class<?> targetType) {
|
||||
/**
|
||||
* Creates an {@link Identifier} from {@code name}, {@code value}, and a {@link Class target type}.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param value
|
||||
* @param targetType must not be {@literal null}.
|
||||
* @return the {@link Identifier} for {@code name}, {@code value}, and a {@link Class target type}.
|
||||
*/
|
||||
public static Identifier of(String name, Object value, Class<?> targetType) {
|
||||
|
||||
Assert.hasText(name, "Name must not be empty!");
|
||||
Assert.notNull(targetType, "Target type must not be null!");
|
||||
|
||||
return new Identifier(Collections.singletonList(new SingleIdentifierValue(name, value, targetType)));
|
||||
}
|
||||
|
||||
public Identifier add(String name, Object value, Class<?> targetType) {
|
||||
/**
|
||||
* Creates an {@link Identifier} from a {@link Map} of name to value tuples.
|
||||
*
|
||||
* @param map must not be {@literal null}.
|
||||
* @return the {@link Identifier} from a {@link Map} of name to value tuples.
|
||||
*/
|
||||
public static Identifier from(Map<String, Object> map) {
|
||||
|
||||
List<SingleIdentifierValue> keys = new ArrayList<>(this.keys);
|
||||
keys.add(new SingleIdentifierValue(name, value, targetType));
|
||||
return new Identifier(keys);
|
||||
Assert.notNull(map, "Map must not be null!");
|
||||
|
||||
if (map.isEmpty()) {
|
||||
return empty();
|
||||
}
|
||||
|
||||
List<SingleIdentifierValue> values = new ArrayList<>();
|
||||
|
||||
map.forEach((k, v) -> {
|
||||
|
||||
values.add(new SingleIdentifierValue(k, v, v != null ? ClassUtils.getUserClass(v) : Object.class));
|
||||
});
|
||||
|
||||
return new Identifier(Collections.unmodifiableList(values));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Map<String, Object> getParametersByName() {
|
||||
/**
|
||||
* Creates a new {@link Identifier} from the current instance and sets the value for {@code key}. Existing key
|
||||
* definitions for {@code name} are overwritten if they already exist.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param value
|
||||
* @param targetType must not be {@literal null}.
|
||||
* @return the {@link Identifier} containing all existing keys and the key part for {@code name}, {@code value}, and a
|
||||
* {@link Class target type}.
|
||||
*/
|
||||
public Identifier withPart(String name, Object value, Class<?> targetType) {
|
||||
|
||||
HashMap<String, Object> result = new HashMap<>();
|
||||
forEach(v -> result.put(v.name, v.value));
|
||||
Assert.hasText(name, "Name must not be empty!");
|
||||
Assert.notNull(targetType, "Target type must not be null!");
|
||||
|
||||
boolean overwritten = false;
|
||||
List<SingleIdentifierValue> keys = new ArrayList<>(this.parts.size() + 1);
|
||||
|
||||
for (SingleIdentifierValue singleValue : this.parts) {
|
||||
|
||||
if (singleValue.getName().equals(name)) {
|
||||
overwritten = true;
|
||||
keys.add(new SingleIdentifierValue(singleValue.getName(), value, targetType));
|
||||
} else {
|
||||
keys.add(singleValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (!overwritten) {
|
||||
keys.add(new SingleIdentifierValue(name, value, targetType));
|
||||
}
|
||||
|
||||
return new Identifier(Collections.unmodifiableList(keys));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Map} containing the identifier name to value tuples.
|
||||
*
|
||||
* @return a {@link Map} containing the identifier name to value tuples.
|
||||
*/
|
||||
public Map<String, Object> toMap() {
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
forEach((name, value, type) -> result.put(name, value));
|
||||
return result;
|
||||
}
|
||||
|
||||
public Collection<SingleIdentifierValue> getParameters() {
|
||||
return keys;
|
||||
/**
|
||||
* @return the {@link SingleIdentifierValue key parts}.
|
||||
*/
|
||||
public Collection<SingleIdentifierValue> getParts() {
|
||||
return this.parts;
|
||||
}
|
||||
|
||||
public void forEach(Consumer<SingleIdentifierValue> consumer) {
|
||||
getParameters().forEach(consumer);
|
||||
/**
|
||||
* Performs the given action for each element of the {@link Identifier} until all elements have been processed or the
|
||||
* action throws an exception. Unless otherwise specified by the implementing class, actions are performed in the
|
||||
* order of iteration (if an iteration order is specified). Exceptions thrown by the action are relayed to the caller.
|
||||
*
|
||||
* @param consumer the action, must not be {@literal null}.
|
||||
*/
|
||||
public void forEach(IdentifierConsumer consumer) {
|
||||
|
||||
Assert.notNull(consumer, "IdentifierConsumer must not be null");
|
||||
|
||||
getParts().forEach(it -> consumer.accept(it.name, it.value, it.targetType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of key parts in this collection.
|
||||
*
|
||||
* @return the number of key parts in this collection.
|
||||
*/
|
||||
public int size() {
|
||||
return this.parts.size();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,14 +183,32 @@ public final class Identifier {
|
||||
* store the element in the database.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @since 1.1
|
||||
*/
|
||||
@Value
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static class SingleIdentifierValue {
|
||||
static class SingleIdentifierValue {
|
||||
|
||||
String name;
|
||||
Object value;
|
||||
Class<?> targetType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an operation that accepts identifier key parts (name, value and {@link Class target type}) defining a
|
||||
* contract to consume {@link Identifier} values.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface IdentifierConsumer {
|
||||
|
||||
/**
|
||||
* Performs this operation on the given arguments.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @param targetType
|
||||
*/
|
||||
void accept(String name, Object value, Class<?> targetType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,23 +15,90 @@
|
||||
*/
|
||||
package org.springframework.data.relational.domain;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Identifier}.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class IdentifierUnitTests {
|
||||
|
||||
@Test // DATAJDBC-326
|
||||
public void getParametersByName() {
|
||||
|
||||
Identifier identifier = Identifier.simple("aName", "aValue", String.class);;
|
||||
Identifier identifier = Identifier.of("aName", "aValue", String.class);
|
||||
|
||||
assertThat(identifier.getParametersByName())
|
||||
.containsExactly(new AbstractMap.SimpleEntry<>("aName", "aValue"));
|
||||
assertThat(identifier.toMap()).hasSize(1).containsEntry("aName", "aValue");
|
||||
}
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-326
|
||||
public void parametersWithStringKeysUseObjectAsTypeForNull() {
|
||||
|
||||
HashMap<String, Object> parameters = new HashMap<>();
|
||||
parameters.put("one", null);
|
||||
|
||||
Identifier identifier = Identifier.from(parameters);
|
||||
|
||||
assertThat(identifier.getParts()) //
|
||||
.extracting("name", "value", "targetType") //
|
||||
.containsExactly( //
|
||||
tuple("one", null, Object.class) //
|
||||
);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-326
|
||||
public void createsIdentifierFromMap() {
|
||||
|
||||
Identifier identifier = Identifier.from(Collections.singletonMap("aName", "aValue"));
|
||||
|
||||
assertThat(identifier.toMap()).hasSize(1).containsEntry("aName", "aValue");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-326
|
||||
public void withAddsNewEntries() {
|
||||
|
||||
Identifier identifier = Identifier.from(Collections.singletonMap("aName", "aValue")).withPart("foo", "bar",
|
||||
String.class);
|
||||
|
||||
assertThat(identifier.toMap()).hasSize(2).containsEntry("aName", "aValue").containsEntry("foo", "bar");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-326
|
||||
public void withOverridesExistingEntries() {
|
||||
|
||||
Identifier identifier = Identifier.from(Collections.singletonMap("aName", "aValue")).withPart("aName", "bar",
|
||||
String.class);
|
||||
|
||||
assertThat(identifier.toMap()).hasSize(1).containsEntry("aName", "bar");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-326
|
||||
public void forEachIteratesOverKeys() {
|
||||
|
||||
List<String> keys = new ArrayList<>();
|
||||
|
||||
Identifier.from(Collections.singletonMap("aName", "aValue")).forEach((name, value, targetType) -> keys.add(name));
|
||||
|
||||
assertThat(keys).containsOnly("aName");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-326
|
||||
public void equalsConsidersEquality() {
|
||||
|
||||
Identifier one = Identifier.from(Collections.singletonMap("aName", "aValue"));
|
||||
Identifier two = Identifier.from(Collections.singletonMap("aName", "aValue"));
|
||||
Identifier three = Identifier.from(Collections.singletonMap("aName", "different"));
|
||||
|
||||
assertThat(one).isEqualTo(two);
|
||||
assertThat(one).isNotEqualTo(three);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user