Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jdbc</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<version>1.0.0.DATAJDBC-183-SNAPSHOT</version>

<name>Spring Data JDBC</name>
<description>Spring Data module for JDBC repositories.</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@
*/
package org.springframework.data.jdbc.core.conversion;

import lombok.RequiredArgsConstructor;
import lombok.Data;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

import org.springframework.data.jdbc.core.conversion.DbAction.Insert;
import org.springframework.data.jdbc.core.conversion.DbAction.Update;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
Expand All @@ -27,13 +34,6 @@
import org.springframework.data.util.StreamUtils;
import org.springframework.util.ClassUtils;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Stream;

/**
* Converts an entity that is about to be saved into {@link DbAction}s inside a {@link AggregateChange} that need to be
* executed against the database to recreate the appropriate state in the database.
Expand Down Expand Up @@ -62,76 +62,49 @@ private void write(Object o, AggregateChange aggregateChange, DbAction depending
Insert<Object> insert = DbAction.insert(o, propertyPath, dependingOn);
aggregateChange.addAction(insert);

referencedEntities(o).forEach(propertyAndValue -> saveReferencedEntities(propertyAndValue, aggregateChange,
propertyPath.nested(propertyAndValue.property.getName()), insert));
referencedEntities(o) //
.forEach( //
propertyAndValue -> //
insertReferencedEntities( //
propertyAndValue, //
aggregateChange, //
propertyPath.nested(propertyAndValue.property.getName()), //
insert) //
);
} else {

deleteReferencedEntities(entityInformation.getRequiredId(o), aggregateChange);

Update<Object> update = DbAction.update(o, propertyPath, dependingOn);
aggregateChange.addAction(update);

referencedEntities(o).forEach(
propertyAndValue -> insertReferencedEntities(propertyAndValue, aggregateChange, propertyPath.nested(propertyAndValue.property.getName()), update));
referencedEntities(o).forEach(propertyAndValue -> insertReferencedEntities(propertyAndValue, aggregateChange,
propertyPath.nested(propertyAndValue.property.getName()), update));
}
}

private void saveReferencedEntities(PropertyAndValue propertyAndValue, AggregateChange aggregateChange,
JdbcPropertyPath propertyPath, DbAction dependingOn) {

saveActions(propertyAndValue, propertyPath, dependingOn).forEach(a -> {

aggregateChange.addAction(a);
referencedEntities(propertyAndValue.value)
.forEach(pav -> saveReferencedEntities(pav, aggregateChange, propertyPath.nested(pav.property.getName()), a));
});
}

private Stream<DbAction> saveActions(PropertyAndValue propertyAndValue, JdbcPropertyPath propertyPath,
DbAction dependingOn) {

if (Map.Entry.class.isAssignableFrom(ClassUtils.getUserClass(propertyAndValue.value))) {
return mapEntrySaveAction(propertyAndValue, propertyPath, dependingOn);
}

return Stream.of(singleSaveAction(propertyAndValue.value, propertyPath, dependingOn));
}

private Stream<DbAction> mapEntrySaveAction(PropertyAndValue propertyAndValue, JdbcPropertyPath propertyPath,
DbAction dependingOn) {

Map.Entry<Object, Object> entry = (Map.Entry) propertyAndValue.value;

DbAction action = singleSaveAction(entry.getValue(), propertyPath, dependingOn);
action.getAdditionalValues().put(propertyAndValue.property.getKeyColumn(), entry.getKey());
return Stream.of(action);
}

private <T> DbAction singleSaveAction(T t, JdbcPropertyPath propertyPath, DbAction dependingOn) {

JdbcPersistentEntityInformation<T, ?> entityInformation = context
.getRequiredPersistentEntityInformation((Class<T>) ClassUtils.getUserClass(t));

return entityInformation.isNew(t) ? DbAction.insert(t, propertyPath, dependingOn)
: DbAction.update(t, propertyPath, dependingOn);
}

private void insertReferencedEntities(PropertyAndValue propertyAndValue, AggregateChange aggregateChange,
JdbcPropertyPath propertyPath, DbAction dependingOn) {
JdbcPropertyPath propertyPath, DbAction dependingOn) {

Insert<Object> insert;
if (propertyAndValue.property.isQualified()) {

Entry<Object, Object> valueAsEntry = (Entry<Object, Object>) propertyAndValue.value;
KeyValue valueAsEntry = (KeyValue) propertyAndValue.value;
insert = DbAction.insert(valueAsEntry.getValue(), propertyPath, dependingOn);
insert.getAdditionalValues().put(propertyAndValue.property.getKeyColumn(), valueAsEntry.getKey());
} else {
insert = DbAction.insert(propertyAndValue.value, propertyPath, dependingOn);
}

aggregateChange.addAction(insert);
referencedEntities(insert.getEntity())
.forEach(pav -> insertReferencedEntities(pav, aggregateChange, propertyPath.nested(pav.property.getName()), dependingOn));
referencedEntities(insert.getEntity()) //
.peek(System.out::println)
.forEach(pav -> insertReferencedEntities( //
pav, //
aggregateChange, //
propertyPath.nested(pav.property.getName()), //
dependingOn) //
);
}

private Stream<PropertyAndValue> referencedEntities(Object o) {
Expand All @@ -143,7 +116,7 @@ private Stream<PropertyAndValue> referencedEntities(Object o) {
.flatMap( //
p -> referencedEntity(p, persistentEntity.getPropertyAccessor(o)) //
.map(e -> new PropertyAndValue(p, e)) //
);
);
}

private Stream<Object> referencedEntity(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) {
Expand Down Expand Up @@ -174,7 +147,7 @@ private Stream<Object> referencedEntity(JdbcPersistentProperty p, PersistentProp
}

private Stream<Object> collectionPropertyAsStream(JdbcPersistentProperty p,
PersistentPropertyAccessor propertyAccessor) {
PersistentPropertyAccessor propertyAccessor) {

Object property = propertyAccessor.getProperty(p);

Expand All @@ -187,15 +160,15 @@ private Stream<Object> listPropertyAsStream(JdbcPersistentProperty p, Persistent

Object property = propertyAccessor.getProperty(p);

if (property == null) return Stream.empty();
if (property == null) {
return Stream.empty();
}

// ugly hackery since Java streams don't have a zip method.
AtomicInteger index = new AtomicInteger();
List<Object> listProperty = (List<Object>) property;
HashMap<Integer, Object> map = new HashMap<>();
for (int i = 0; i < listProperty.size(); i++) {
map.put(i, listProperty.get(i));
}

return map.entrySet().stream().map(e -> (Object) e);
return listProperty.stream().map(e -> new KeyValue(index.getAndIncrement(), e));
}

private Stream<Object> mapPropertyAsStream(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) {
Expand All @@ -204,7 +177,7 @@ private Stream<Object> mapPropertyAsStream(JdbcPersistentProperty p, PersistentP

return property == null //
? Stream.empty() //
: ((Map<Object, Object>) property).entrySet().stream().map(e -> (Object) e);
: ((Map<Object, Object>) property).entrySet().stream().map(e -> new KeyValue(e.getKey(), e.getValue()));
}

private Stream<Object> singlePropertyAsStream(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) {
Expand All @@ -217,7 +190,16 @@ private Stream<Object> singlePropertyAsStream(JdbcPersistentProperty p, Persiste
return Stream.of(property);
}

@RequiredArgsConstructor
/**
* Holds key and value of a {@link Map.Entry} but without any ties to {@link Map} implementations.
*/
@Data
private static class KeyValue {
private final Object key;
private final Object value;
}

@Data
private static class PropertyAndValue {

private final JdbcPersistentProperty property;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,45 @@ public void newEntityWithMapResultsInAdditionalInsertPerElement() {
);
}

@Test // DATAJDBC-183
public void newEntityWithFullMapResultsInAdditionalInsertPerElement() {

MapContainer entity = new MapContainer(null);
entity.elements.put("1", new Element(null));
entity.elements.put("2", new Element(null));
entity.elements.put("3", new Element(null));
entity.elements.put("4", new Element(null));
entity.elements.put("5", new Element(null));
entity.elements.put("6", new Element(null));
entity.elements.put("7", new Element(null));
entity.elements.put("8", new Element(null));
entity.elements.put("9", new Element(null));
entity.elements.put("0", new Element(null));
entity.elements.put("a", new Element(null));
entity.elements.put("b", new Element(null));

AggregateChange<MapContainer> aggregateChange = new AggregateChange(Kind.SAVE, MapContainer.class, entity);
converter.write(entity, aggregateChange);

assertThat(aggregateChange.getActions())
.extracting(DbAction::getClass, DbAction::getEntityType, this::getMapKey, this::extractPath) //
.containsExactlyInAnyOrder( //
tuple(Insert.class, MapContainer.class, null, ""), //
tuple(Insert.class, Element.class, "1", "elements"), //
tuple(Insert.class, Element.class, "2", "elements"), //
tuple(Insert.class, Element.class, "3", "elements"), //
tuple(Insert.class, Element.class, "4", "elements"), //
tuple(Insert.class, Element.class, "5", "elements"), //
tuple(Insert.class, Element.class, "6", "elements"), //
tuple(Insert.class, Element.class, "7", "elements"), //
tuple(Insert.class, Element.class, "8", "elements"), //
tuple(Insert.class, Element.class, "9", "elements"), //
tuple(Insert.class, Element.class, "0", "elements"), //
tuple(Insert.class, Element.class, "a", "elements"), //
tuple(Insert.class, Element.class, "b", "elements") //
);
}

@Test // DATAJDBC-130
public void newEntityWithEmptyListResultsInSingleInsert() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,6 @@ public void findAllLoadsList() {

Iterable<DummyEntity> reloaded = repository.findAll();

reloaded.forEach(de -> System.out.println("id " + de.id + " content " + de.content.iterator().next().content));

assertThat(reloaded) //
.extracting(e -> e.id, e -> e.content.size()) //
.containsExactly(tuple(entity.id, entity.content.size()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,6 @@ public void findAllLoadsMap() {

Iterable<DummyEntity> reloaded = repository.findAll();

reloaded.forEach(de -> System.out.println("id " + de.id + " content " + de.content.values().iterator().next().content));

assertThat(reloaded) //
.extracting(e -> e.id, e -> e.content.size()) //
.containsExactly(tuple(entity.id, entity.content.size()));
Expand Down