DATAJDBC-326 - Support conversion of backreferences.

Ids used as backreferences now get properly converted.

Introduced Identifier to hold information about data that needs to be considered for inserts or updates but is not part of the entity.
Apart from column names and values they also hold information about the desired JdbcType in order to facilitate conversions.
This replaces the Map handed around in the past.

Original pull request: #118.
This commit is contained in:
Jens Schauder
2019-02-12 07:50:05 +01:00
committed by Mark Paluch
parent 8eb80ba5cc
commit ade3324e2c
24 changed files with 610 additions and 95 deletions

View File

@@ -21,6 +21,7 @@ import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@@ -47,6 +48,15 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
return collect(das -> das.insert(instance, domainType, additionalParameters));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, org.springframework.data.jdbc.core.ParentKeys)
*/
@Override
public <T> Object insert(T instance, Class<T> domainType, Identifier identifier) {
return collect(das -> das.insert(instance, domainType, identifier));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class)
@@ -149,7 +159,7 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
private <T> T collect(Function<DataAccessStrategy, T> function) {
// Keep <T> as Eclipse fails to compile if <> is used.
return strategies.stream().collect(new FunctionCollector<T>(function));
return strategies.stream().collect(new FunctionCollector<>(function));
}
private void collectVoid(Consumer<DataAccessStrategy> consumer) {

View File

@@ -17,6 +17,7 @@ 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.lang.Nullable;
@@ -39,9 +40,27 @@ 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
<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 <T> the type of the instance.
* @return the id generated by the database if any.
*/
default <T> Object insert(T instance, Class<T> domainType, Identifier identifier){
return insert(instance, domainType, identifier.getParametersByName());
}
/**
* Updates the data of a single entity in the database. Referenced entities don't get handled.
*

View File

@@ -29,6 +29,7 @@ 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;
@@ -37,6 +38,7 @@ 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.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
@@ -87,10 +89,24 @@ 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());
}
/*
* (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);
Map<String, Object> parameters = new LinkedHashMap<>(additionalParameters);
Map<String, Object> parameters = new LinkedHashMap<>();
identifier.forEach(identifierValue -> {
parameters.put(identifierValue.getName(),
converter.writeValue(identifierValue.getValue(), ClassTypeInformation.from(identifierValue.getTargetType())));
});
MapSqlParameterSource parameterSource = getPropertyMap(instance, persistentEntity, "");
@@ -282,7 +298,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
return result;
}
private <S, T> MapSqlParameterSource getPropertyMap(final S instance, RelationalPersistentEntity<S> persistentEntity, String prefix) {
private <S, T> MapSqlParameterSource getPropertyMap(final S instance, RelationalPersistentEntity<S> persistentEntity,
String prefix) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
@@ -294,23 +311,26 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
return;
}
if(property.isEmbedded()){
if (property.isEmbedded()) {
Object value = propertyAccessor.getProperty(property);
final RelationalPersistentEntity<?> embeddedEntity = context.getPersistentEntity(property.getType());
final MapSqlParameterSource additionalParameters = getPropertyMap((T)value, (RelationalPersistentEntity<T>) embeddedEntity, prefix + property.getEmbeddedPrefix());
final RelationalPersistentEntity<?> embeddedEntity = context.getPersistentEntity(property.getType());
final MapSqlParameterSource additionalParameters = getPropertyMap((T) value,
(RelationalPersistentEntity<T>) embeddedEntity, prefix + property.getEmbeddedPrefix());
parameters.addValues(additionalParameters.getValues());
} else {
Object value = propertyAccessor.getProperty(property);
Object convertedValue = convertForWrite(property, value);
parameters.addValue(prefix + property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType()));
parameters.addValue(prefix + property.getColumnName(), convertedValue,
JdbcUtil.sqlTypeFor(property.getColumnType()));
}
});
return parameters;
}
@Nullable
private Object convertForWrite(RelationalPersistentProperty property, @Nullable Object value) {
@@ -327,9 +347,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
String typeName = JDBCType.valueOf(JdbcUtil.sqlTypeFor(componentType)).getName();
return operations.getJdbcOperations().execute(
(Connection c) -> c.createArrayOf(typeName, (Object[]) convertedValue)
);
return operations.getJdbcOperations()
.execute((Connection c) -> c.createArrayOf(typeName, (Object[]) convertedValue));
}
@SuppressWarnings("unchecked")
@@ -354,22 +373,22 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
@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
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();
Map<String, Object> keys = holder.getKeys();
if (keys == null || persistentEntity.getIdProperty() == null) {
return null;
}
if (keys == null || persistentEntity.getIdProperty() == null) {
return null;
}
return keys.get(persistentEntity.getIdColumn());
}
}
return keys.get(persistentEntity.getIdColumn());
}
}
private EntityRowMapper<?> getEntityRowMapper(Class<?> domainType) {
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context, converter, accessStrategy);

View File

@@ -18,9 +18,10 @@ package org.springframework.data.jdbc.core;
import lombok.RequiredArgsConstructor;
import java.util.Collections;
import java.util.HashMap;
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;
@@ -58,7 +59,7 @@ class DefaultJdbcInterpreter implements Interpreter {
@Override
public <T> void interpret(Insert<T> insert) {
Object id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), createAdditionalColumnValues(insert));
Object id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), getParentKeys(insert));
insert.setGeneratedId(id);
}
@@ -101,7 +102,7 @@ class DefaultJdbcInterpreter implements Interpreter {
// temporary implementation
if (!accessStrategy.update(merge.getEntity(), merge.getEntityType())) {
accessStrategy.insert(merge.getEntity(), merge.getEntityType(), createAdditionalColumnValues(merge));
accessStrategy.insert(merge.getEntity(), merge.getEntityType(), getParentKeys(merge));
}
}
@@ -141,27 +142,21 @@ class DefaultJdbcInterpreter implements Interpreter {
accessStrategy.deleteAll(deleteAllRoot.getEntityType());
}
private <T> Map<String, Object> createAdditionalColumnValues(DbAction.WithDependingOn<T> action) {
Map<String, Object> additionalColumnValues = new HashMap<>();
addDependingOnInformation(action, additionalColumnValues);
additionalColumnValues.putAll(action.getAdditionalValues());
return additionalColumnValues;
}
private <T> void addDependingOnInformation(DbAction.WithDependingOn<T> action,
Map<String, Object> additionalColumnValues) {
private <T> Identifier getParentKeys(DbAction.WithDependingOn<?> action) {
DbAction.WithEntity<?> dependingOn = action.getDependingOn();
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(dependingOn.getEntityType());
String columnName = getColumnNameForReverseColumn(action);
Object id = getIdFromEntityDependingOn(dependingOn, persistentEntity);
JdbcIdentifierBuilder identifier = JdbcIdentifierBuilder //
.forBackReferences(action.getPropertyPath(), id);
Object identifier = getIdFromEntityDependingOn(dependingOn, persistentEntity);
for (Map.Entry<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifier : action.getQualifiers().entrySet()) {
identifier = identifier.withQualifier(qualifier.getKey(), qualifier.getValue());
}
additionalColumnValues.put(columnName, identifier);
return identifier.build();
}
@Nullable
@@ -182,9 +177,4 @@ class DefaultJdbcInterpreter implements Interpreter {
return persistentEntity.getIdentifierAccessor(entity).getIdentifier();
}
private String getColumnNameForReverseColumn(DbAction.WithPropertyPath<?> action) {
PersistentPropertyPath<RelationalPersistentProperty> path = action.getPropertyPath();
return path.getRequiredLeafProperty().getReverseColumnName();
}
}

View File

@@ -17,6 +17,7 @@ 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.util.Assert;
@@ -40,6 +41,15 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy {
return delegate.insert(instance, domainType, additionalParameters);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, org.springframework.data.jdbc.core.ParentKeys)
*/
@Override
public <T> Object insert(T instance, Class<T> domainType, Identifier identifier) {
return delegate.insert(instance, domainType, identifier);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class)

View File

@@ -20,17 +20,23 @@ import org.slf4j.LoggerFactory;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.mapping.PersistentPropertyPath;
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.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import java.sql.Array;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* {@link RelationalConverter} that uses a {@link MappingContext} to apply basic conversion of relational values to
@@ -122,4 +128,6 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
return super.writeValue(value, type);
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://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.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;
/**
* @author Jens Schauder
* @since 1.1
*/
public class JdbcIdentifierBuilder {
private Identifier identifier;
private JdbcIdentifierBuilder(Identifier identifier) {
this.identifier = identifier;
}
public static JdbcIdentifierBuilder empty() {
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( //
path.getRequiredLeafProperty().getReverseColumnName(), //
value, //
getLastIdProperty(path).getColumnType() //
);
return new JdbcIdentifierBuilder(identifier);
}
public JdbcIdentifierBuilder withQualifier(PersistentPropertyPath<RelationalPersistentProperty> path, Object value) {
RelationalPersistentProperty leafProperty = path.getRequiredLeafProperty();
identifier = identifier.add(leafProperty.getKeyColumn(), value, leafProperty.getQualifierColumnType());
return this;
}
public Identifier build() {
return identifier;
}
private static RelationalPersistentProperty getLastIdProperty(
PersistentPropertyPath<RelationalPersistentProperty> path) {
RelationalPersistentProperty idProperty = path.getRequiredLeafProperty().getOwner().getIdProperty();
if (idProperty != null) {
return idProperty;
}
return getLastIdProperty(path.getParentPath());
}
}

View File

@@ -26,6 +26,7 @@ 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;
@@ -136,6 +137,19 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
return myBatisContext.getId();
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, ParentKeys)
*/
@Override
public <T> Object insert(T instance, Class<T> domainType, Identifier identifier) {
MyBatisContext myBatisContext = new MyBatisContext(null, instance, domainType, identifier.getParametersByName());
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)

View File

@@ -16,12 +16,9 @@
package org.springframework.data.jdbc.core;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.data.annotation.Id;
@@ -31,6 +28,7 @@ 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;
/**
* Unit tests for {@link DefaultJdbcInterpreter}
@@ -67,10 +65,12 @@ public class DefaultJdbcInterpreterUnitTests {
interpreter.interpret(insert);
ArgumentCaptor<Map<String, Object>> argumentCaptor = ArgumentCaptor.forClass(Map.class);
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
assertThat(argumentCaptor.getValue()).containsExactly(new SimpleEntry(BACK_REFERENCE, CONTAINER_ID));
assertThat(argumentCaptor.getValue().getParameters()) //
.extracting("name", "value", "targetType") //
.containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class));
}
@Test // DATAJDBC-251
@@ -80,10 +80,12 @@ public class DefaultJdbcInterpreterUnitTests {
interpreter.interpret(insert);
ArgumentCaptor<Map<String, Object>> argumentCaptor = ArgumentCaptor.forClass(Map.class);
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
assertThat(argumentCaptor.getValue()).containsExactly(new SimpleEntry(BACK_REFERENCE, CONTAINER_ID));
assertThat(argumentCaptor.getValue().getParameters()) //
.extracting("name", "value", "targetType") //
.containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class));
}
@Test // DATAJDBC-251
@@ -93,10 +95,12 @@ public class DefaultJdbcInterpreterUnitTests {
interpreter.interpret(insert);
ArgumentCaptor<Map<String, Object>> argumentCaptor = ArgumentCaptor.forClass(Map.class);
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
assertThat(argumentCaptor.getValue()).containsExactly(new SimpleEntry(BACK_REFERENCE, CONTAINER_ID));
assertThat(argumentCaptor.getValue().getParameters()) //
.extracting("name", "value", "targetType") //
.containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class));
}
static class Container {

View File

@@ -0,0 +1,167 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://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;
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;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
/**
* Unit tests for the {@link JdbcIdentifierBuilder}.
*
* @author Jens Schauder
*/
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()) //
.extracting("name", "value", "targetType") //
.containsExactly( //
tuple("dummy_entity", "eins", UUID.class) //
);
}
@Test // DATAJDBC-326
public void qualifiersForMaps() {
PersistentPropertyPath<RelationalPersistentProperty> path = getPath("children");
Identifier identifier = JdbcIdentifierBuilder //
.forBackReferences(path, "parent-eins") //
.withQualifier(path, "map-key-eins") //
.build();
assertThat(identifier.getParameters()) //
.extracting("name", "value", "targetType") //
.containsExactlyInAnyOrder( //
tuple("dummy_entity", "parent-eins", UUID.class), //
tuple("dummy_entity_key", "map-key-eins", String.class) //
);
}
@Test // DATAJDBC-326
public void qualifiersForLists() {
PersistentPropertyPath<RelationalPersistentProperty> path = getPath("moreChildren");
Identifier identifier = JdbcIdentifierBuilder //
.forBackReferences(path, "parent-eins") //
.withQualifier(path, "list-index-eins") //
.build();
assertThat(identifier.getParameters()) //
.extracting("name", "value", "targetType") //
.containsExactlyInAnyOrder( //
tuple("dummy_entity", "parent-eins", UUID.class), //
tuple("dummy_entity_key", "list-index-eins", Integer.class) //
);
}
@Test // DATAJDBC-326
public void backreferenceAcrossEmbeddable() {
Identifier identifier = JdbcIdentifierBuilder //
.forBackReferences(getPath("embeddable.child"), "parent-eins") //
.build();
assertThat(identifier.getParameters()) //
.extracting("name", "value", "targetType") //
.containsExactly( //
tuple("embeddable", "parent-eins", UUID.class) //
);
}
@NotNull
private PersistentPropertyPath<RelationalPersistentProperty> getPath(String dotPath) {
return toPath(dotPath, DummyEntity.class, context);
}
@SuppressWarnings("unused")
static class DummyEntity {
@Id UUID id;
String one;
Long two;
Child child;
Map<String, Child> children;
List<Child> moreChildren;
Embeddable embeddable;
}
@SuppressWarnings("unused")
static class Embeddable {
Child child;
}
static class Child {}
}

View File

@@ -96,7 +96,9 @@ public class JdbcRepositoryWithMapsIntegrationTests {
public void saveAndLoadNonEmptyMap() {
Element element1 = new Element();
element1.content = "element 1";
Element element2 = new Element();
element2.content = "element 2";
DummyEntity entity = createDummyEntity();
entity.content.put("one", element1);

View File

@@ -12,7 +12,7 @@
<!--<logger name="org.springframework.data.jdbc.mybatis.DummyEntityMapper" level="trace" />-->
<root level="warn">
<appender-ref ref="console" />
<appender-ref ref="console"/>
</root>
</configuration>

View File

@@ -8,6 +8,10 @@ CREATE TABLE ONE_TO_ONE_PARENT ( id3 BIGINT GENERATED BY DEFAULT AS IDENTITY(STA
CREATE TABLE Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR(30));
CREATE TABLE LIST_PARENT ( id4 BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT BIGINT);
CREATE TABLE ELEMENT_NO_ID ( content VARCHAR(100), LIST_PARENT_KEY BIGINT, LIST_PARENT BIGINT);
ALTER TABLE ELEMENT_NO_ID
ADD FOREIGN KEY (LIST_PARENT)
REFERENCES LIST_PARENT(id4);
CREATE TABLE ARRAY_OWNER (ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, DIGITS VARCHAR(20) ARRAY[10] NOT NULL, MULTIDIMENSIONAL VARCHAR(20) ARRAY[10] NULL);

View File

@@ -1,2 +1,6 @@
CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE element (id BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, content VARCHAR(100), Dummy_Entity_key VARCHAR(100), dummy_entity BIGINT);
ALTER TABLE ELEMENT
ADD FOREIGN KEY (dummy_entity)
REFERENCES dummy_entity(id);