Browse Source
This introduces the JdbcRepository interface which offers two additional methods beyond the CrudRepository: `insert` and `update`. Both methods skip the test if the aggregate is new and perform the respective operation. Especially `insert` is useful for saving new aggregates which are new but have an ID set by the client and not generated by the database. Original pull request: #107.pull/108/head
11 changed files with 1074 additions and 584 deletions
@ -0,0 +1,18 @@ |
|||||||
|
package org.springframework.data.jdbc.core; |
||||||
|
|
||||||
|
import org.springframework.data.repository.Repository; |
||||||
|
|
||||||
|
/** |
||||||
|
* Jdbc repository for dedicated insert(), update() and upsert() sql functions. |
||||||
|
* Other than {@link org.springframework.data.jdbc.repository.support.SimpleJdbcRepository} |
||||||
|
* there should be bypassing of the isNew check. |
||||||
|
* |
||||||
|
* @author Thomas Lang |
||||||
|
* @see <a href="https://jira.spring.io/browse/DATAJDBC-282">DATAJDBC-282</a> |
||||||
|
*/ |
||||||
|
public interface JdbcRepository<T, ID> extends Repository<T, ID> { |
||||||
|
|
||||||
|
<S extends T> S insert(S var1); |
||||||
|
|
||||||
|
<S extends T> S update(S var1); |
||||||
|
} |
||||||
@ -0,0 +1,274 @@ |
|||||||
|
package org.springframework.data.relational.core.conversion; |
||||||
|
|
||||||
|
import lombok.Value; |
||||||
|
import org.springframework.data.convert.EntityWriter; |
||||||
|
import org.springframework.data.mapping.PersistentProperty; |
||||||
|
import org.springframework.data.mapping.PersistentPropertyPath; |
||||||
|
import org.springframework.data.mapping.PersistentPropertyPaths; |
||||||
|
import org.springframework.data.relational.core.mapping.RelationalMappingContext; |
||||||
|
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; |
||||||
|
import org.springframework.data.util.Pair; |
||||||
|
import org.springframework.lang.Nullable; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collection; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* Used as an abstract class to derive relational writer actions (save, insert, update). |
||||||
|
* Implementations see {@link RelationalEntityWriter} and {@link RelationalEntityInsertWriter} |
||||||
|
* |
||||||
|
* @author Thomas Lang |
||||||
|
*/ |
||||||
|
abstract class AbstractRelationalEntityWriter implements EntityWriter<Object, AggregateChange<?>> { |
||||||
|
|
||||||
|
protected final RelationalMappingContext context; |
||||||
|
|
||||||
|
AbstractRelationalEntityWriter(RelationalMappingContext context) { |
||||||
|
this.context = context; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Holds context information for the current save operation. |
||||||
|
*/ |
||||||
|
class WritingContext { |
||||||
|
|
||||||
|
private final Object root; |
||||||
|
private final Object entity; |
||||||
|
private final Class<?> entityType; |
||||||
|
private final PersistentPropertyPaths<?, RelationalPersistentProperty> paths; |
||||||
|
private final Map<PathNode, DbAction> previousActions = new HashMap<>(); |
||||||
|
private Map<PersistentPropertyPath<RelationalPersistentProperty>, List<PathNode>> nodesCache = new HashMap<>(); |
||||||
|
|
||||||
|
WritingContext(Object root, AggregateChange<?> aggregateChange) { |
||||||
|
|
||||||
|
this.root = root; |
||||||
|
this.entity = aggregateChange.getEntity(); |
||||||
|
this.entityType = aggregateChange.getEntityType(); |
||||||
|
this.paths = context.findPersistentPropertyPaths(entityType, PersistentProperty::isEntity); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Leaves out the isNew check as defined in #DATAJDBC-282 |
||||||
|
* |
||||||
|
* @return List of {@link DbAction}s |
||||||
|
* @see <a href="https://jira.spring.io/browse/DATAJDBC-282">DAJDBC-282</a> |
||||||
|
*/ |
||||||
|
List<DbAction<?>> insert() { |
||||||
|
|
||||||
|
List<DbAction<?>> actions = new ArrayList<>(); |
||||||
|
actions.add(setRootAction(new DbAction.InsertRoot<>(entity))); |
||||||
|
actions.addAll(insertReferenced()); |
||||||
|
return actions; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Leaves out the isNew check as defined in #DATAJDBC-282 |
||||||
|
* |
||||||
|
* @return List of {@link DbAction}s |
||||||
|
* @see <a href="https://jira.spring.io/browse/DATAJDBC-282">DAJDBC-282</a> |
||||||
|
*/ |
||||||
|
List<DbAction<?>> update() { |
||||||
|
|
||||||
|
List<DbAction<?>> actions = new ArrayList<>(deleteReferenced()); |
||||||
|
actions.add(setRootAction(new DbAction.UpdateRoot<>(entity))); |
||||||
|
actions.addAll(insertReferenced()); |
||||||
|
return actions; |
||||||
|
} |
||||||
|
|
||||||
|
List<DbAction<?>> save() { |
||||||
|
|
||||||
|
List<DbAction<?>> actions = new ArrayList<>(); |
||||||
|
if (isNew(root)) { |
||||||
|
|
||||||
|
actions.add(setRootAction(new DbAction.InsertRoot<>(entity))); |
||||||
|
actions.addAll(insertReferenced()); |
||||||
|
} else { |
||||||
|
|
||||||
|
actions.addAll(deleteReferenced()); |
||||||
|
actions.add(setRootAction(new DbAction.UpdateRoot<>(entity))); |
||||||
|
actions.addAll(insertReferenced()); |
||||||
|
} |
||||||
|
|
||||||
|
return actions; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isNew(Object o) { |
||||||
|
return context.getRequiredPersistentEntity(o.getClass()).isNew(o); |
||||||
|
} |
||||||
|
|
||||||
|
//// Operations on all paths
|
||||||
|
|
||||||
|
private List<DbAction<?>> insertReferenced() { |
||||||
|
|
||||||
|
List<DbAction<?>> actions = new ArrayList<>(); |
||||||
|
|
||||||
|
paths.forEach(path -> actions.addAll(insertAll(path))); |
||||||
|
|
||||||
|
return actions; |
||||||
|
} |
||||||
|
|
||||||
|
private List<DbAction<?>> insertAll(PersistentPropertyPath<RelationalPersistentProperty> path) { |
||||||
|
|
||||||
|
List<DbAction<?>> actions = new ArrayList<>(); |
||||||
|
|
||||||
|
from(path).forEach(node -> { |
||||||
|
|
||||||
|
DbAction.Insert<Object> insert; |
||||||
|
if (node.path.getRequiredLeafProperty().isQualified()) { |
||||||
|
|
||||||
|
Pair<Object, Object> value = (Pair) node.getValue(); |
||||||
|
insert = new DbAction.Insert<>(value.getSecond(), path, getAction(node.parent)); |
||||||
|
insert.getAdditionalValues().put(node.path.getRequiredLeafProperty().getKeyColumn(), value.getFirst()); |
||||||
|
|
||||||
|
} else { |
||||||
|
insert = new DbAction.Insert<>(node.getValue(), path, getAction(node.parent)); |
||||||
|
} |
||||||
|
|
||||||
|
previousActions.put(node, insert); |
||||||
|
actions.add(insert); |
||||||
|
}); |
||||||
|
|
||||||
|
return actions; |
||||||
|
} |
||||||
|
|
||||||
|
private List<DbAction<?>> deleteReferenced() { |
||||||
|
|
||||||
|
List<DbAction<?>> deletes = new ArrayList<>(); |
||||||
|
paths.forEach(path -> deletes.add(0, deleteReferenced(path))); |
||||||
|
|
||||||
|
return deletes; |
||||||
|
} |
||||||
|
|
||||||
|
/// Operations on a single path
|
||||||
|
|
||||||
|
private DbAction.Delete<?> deleteReferenced(PersistentPropertyPath<RelationalPersistentProperty> path) { |
||||||
|
|
||||||
|
Object id = context.getRequiredPersistentEntity(entityType).getIdentifierAccessor(entity).getIdentifier(); |
||||||
|
|
||||||
|
return new DbAction.Delete<>(id, path); |
||||||
|
} |
||||||
|
|
||||||
|
//// methods not directly related to the creation of DbActions
|
||||||
|
|
||||||
|
private DbAction<?> setRootAction(DbAction<?> dbAction) { |
||||||
|
|
||||||
|
previousActions.put(null, dbAction); |
||||||
|
return dbAction; |
||||||
|
} |
||||||
|
|
||||||
|
@Nullable |
||||||
|
private DbAction.WithEntity<?> getAction(@Nullable RelationalEntityInsertWriter.PathNode parent) { |
||||||
|
|
||||||
|
DbAction action = previousActions.get(parent); |
||||||
|
|
||||||
|
if (action != null) { |
||||||
|
|
||||||
|
Assert.isInstanceOf( //
|
||||||
|
DbAction.WithEntity.class, //
|
||||||
|
action, //
|
||||||
|
"dependsOn action is not a WithEntity, but " + action.getClass().getSimpleName() //
|
||||||
|
); |
||||||
|
|
||||||
|
return (DbAction.WithEntity<?>) action; |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
// commented as of #DATAJDBC-282
|
||||||
|
// private boolean isNew(Object o) {
|
||||||
|
// return context.getRequiredPersistentEntity(o.getClass()).isNew(o);
|
||||||
|
// }
|
||||||
|
|
||||||
|
private List<RelationalEntityInsertWriter.PathNode> from( |
||||||
|
PersistentPropertyPath<RelationalPersistentProperty> path) { |
||||||
|
|
||||||
|
List<RelationalEntityInsertWriter.PathNode> nodes = new ArrayList<>(); |
||||||
|
|
||||||
|
if (path.getLength() == 1) { |
||||||
|
|
||||||
|
Object value = context //
|
||||||
|
.getRequiredPersistentEntity(entityType) //
|
||||||
|
.getPropertyAccessor(entity) //
|
||||||
|
.getProperty(path.getRequiredLeafProperty()); |
||||||
|
|
||||||
|
nodes.addAll(createNodes(path, null, value)); |
||||||
|
|
||||||
|
} else { |
||||||
|
|
||||||
|
List<RelationalEntityInsertWriter.PathNode> pathNodes = nodesCache.get(path.getParentPath()); |
||||||
|
pathNodes.forEach(parentNode -> { |
||||||
|
|
||||||
|
Object value = path.getRequiredLeafProperty().getOwner().getPropertyAccessor(parentNode.getValue()) |
||||||
|
.getProperty(path.getRequiredLeafProperty()); |
||||||
|
|
||||||
|
nodes.addAll(createNodes(path, parentNode, value)); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
nodesCache.put(path, nodes); |
||||||
|
|
||||||
|
return nodes; |
||||||
|
} |
||||||
|
|
||||||
|
private List<RelationalEntityInsertWriter.PathNode> createNodes( |
||||||
|
PersistentPropertyPath<RelationalPersistentProperty> path, |
||||||
|
@Nullable RelationalEntityInsertWriter.PathNode parentNode, @Nullable Object value) { |
||||||
|
|
||||||
|
if (value == null) { |
||||||
|
return Collections.emptyList(); |
||||||
|
} |
||||||
|
|
||||||
|
List<RelationalEntityInsertWriter.PathNode> nodes = new ArrayList<>(); |
||||||
|
|
||||||
|
if (path.getRequiredLeafProperty().isQualified()) { |
||||||
|
|
||||||
|
if (path.getRequiredLeafProperty().isMap()) { |
||||||
|
((Map<?, ?>) value) |
||||||
|
.forEach((k, v) -> nodes.add(new RelationalEntityInsertWriter.PathNode(path, parentNode, Pair.of(k, v)))); |
||||||
|
} else { |
||||||
|
|
||||||
|
List listValue = (List) value; |
||||||
|
for (int k = 0; k < listValue.size(); k++) { |
||||||
|
nodes.add(new RelationalEntityInsertWriter.PathNode(path, parentNode, Pair.of(k, listValue.get(k)))); |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (path.getRequiredLeafProperty().isCollectionLike()) { // collection value
|
||||||
|
((Collection<?>) value).forEach(v -> nodes.add(new RelationalEntityInsertWriter.PathNode(path, parentNode, v))); |
||||||
|
} else { // single entity value
|
||||||
|
nodes.add(new RelationalEntityInsertWriter.PathNode(path, parentNode, value)); |
||||||
|
} |
||||||
|
|
||||||
|
return nodes; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Represents a single entity in an aggregate along with its property path from the root entity and the chain of |
||||||
|
* objects to traverse a long this path. |
||||||
|
*/ |
||||||
|
@Value |
||||||
|
static class PathNode { |
||||||
|
|
||||||
|
/** |
||||||
|
* The path to this entity |
||||||
|
*/ |
||||||
|
PersistentPropertyPath<RelationalPersistentProperty> path; |
||||||
|
|
||||||
|
/** |
||||||
|
* The parent {@link PathNode}. This is {@code null} if this is the root entity. |
||||||
|
*/ |
||||||
|
@Nullable |
||||||
|
PathNode parent; |
||||||
|
|
||||||
|
/** |
||||||
|
* The value of the entity. |
||||||
|
*/ |
||||||
|
Object value; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2017-2018 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.relational.core.conversion; |
||||||
|
|
||||||
|
import org.springframework.data.relational.core.mapping.RelationalMappingContext; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* Converts an aggregate represented by its root into an {@link AggregateChange}. |
||||||
|
* Does not perform any isNew check. |
||||||
|
* |
||||||
|
* @author Jens Schauder |
||||||
|
* @author Mark Paluch |
||||||
|
* @author Thomas Lang |
||||||
|
*/ |
||||||
|
public class RelationalEntityInsertWriter extends AbstractRelationalEntityWriter { |
||||||
|
|
||||||
|
public RelationalEntityInsertWriter(RelationalMappingContext context) { |
||||||
|
super(context); |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* @see org.springframework.data.convert.EntityWriter#save(java.lang.Object, java.lang.Object) |
||||||
|
*/ |
||||||
|
@Override public void write(Object root, AggregateChange<?> aggregateChange) { |
||||||
|
List<DbAction<?>> actions = new WritingContext(root, aggregateChange).insert(); |
||||||
|
actions.forEach(aggregateChange::addAction); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2017-2018 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.relational.core.conversion; |
||||||
|
|
||||||
|
import org.springframework.data.relational.core.mapping.RelationalMappingContext; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* Converts an aggregate represented by its root into an {@link AggregateChange}. |
||||||
|
* Does not perform any isNew check. |
||||||
|
* |
||||||
|
* @author Jens Schauder |
||||||
|
* @author Mark Paluch |
||||||
|
* @author Thomas Lang |
||||||
|
*/ |
||||||
|
public class RelationalEntityUpdateWriter extends AbstractRelationalEntityWriter { |
||||||
|
|
||||||
|
public RelationalEntityUpdateWriter(RelationalMappingContext context) { |
||||||
|
super(context); |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* @see org.springframework.data.convert.EntityWriter#save(java.lang.Object, java.lang.Object) |
||||||
|
*/ |
||||||
|
@Override public void write(Object root, AggregateChange<?> aggregateChange) { |
||||||
|
List<DbAction<?>> actions = new WritingContext(root, aggregateChange).update(); |
||||||
|
actions.forEach(aggregateChange::addAction); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,111 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2017-2018 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.relational.core.conversion; |
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.junit.MockitoJUnitRunner; |
||||||
|
import org.springframework.data.annotation.Id; |
||||||
|
import org.springframework.data.relational.core.conversion.AggregateChange.Kind; |
||||||
|
import org.springframework.data.relational.core.conversion.DbAction.InsertRoot; |
||||||
|
import org.springframework.data.relational.core.mapping.RelationalMappingContext; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* Unit tests for the {@link RelationalEntityInsertWriter} |
||||||
|
* |
||||||
|
* @author Jens Schauder |
||||||
|
* @author Thomas Lang |
||||||
|
*/ |
||||||
|
@RunWith(MockitoJUnitRunner.class) public class RelationalEntityInsertWriterUnitTests { |
||||||
|
|
||||||
|
public static final long SOME_ENTITY_ID = 23L; |
||||||
|
RelationalEntityInsertWriter converter = new RelationalEntityInsertWriter(new RelationalMappingContext()); |
||||||
|
|
||||||
|
@Test // DATAJDBC-112
|
||||||
|
public void newEntityGetsConvertedToOneInsert() { |
||||||
|
|
||||||
|
SingleReferenceEntity entity = new SingleReferenceEntity(null); |
||||||
|
AggregateChange<SingleReferenceEntity> aggregateChange = //
|
||||||
|
new AggregateChange(Kind.SAVE, SingleReferenceEntity.class, entity); |
||||||
|
|
||||||
|
converter.write(entity, aggregateChange); |
||||||
|
|
||||||
|
assertThat(aggregateChange.getActions()) //
|
||||||
|
.extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, |
||||||
|
this::isWithDependsOn) //
|
||||||
|
.containsExactly( //
|
||||||
|
tuple(InsertRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false) //
|
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@Test // DATAJDBC-282
|
||||||
|
public void existingEntityGetsNotConvertedToDeletePlusUpdate() { |
||||||
|
|
||||||
|
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); |
||||||
|
|
||||||
|
AggregateChange<SingleReferenceEntity> aggregateChange = //
|
||||||
|
new AggregateChange(Kind.SAVE, SingleReferenceEntity.class, entity); |
||||||
|
|
||||||
|
converter.write(entity, aggregateChange); |
||||||
|
|
||||||
|
assertThat(aggregateChange.getActions()) //
|
||||||
|
.extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, |
||||||
|
this::isWithDependsOn) //
|
||||||
|
.containsExactly( //
|
||||||
|
tuple(InsertRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false) //
|
||||||
|
); |
||||||
|
|
||||||
|
assertThat(aggregateChange.getEntity()).isNotNull(); |
||||||
|
// the new id should not be the same as the origin one - should do insert, not update
|
||||||
|
// assertThat(aggregateChange.getEntity().id).isNotEqualTo(SOME_ENTITY_ID);
|
||||||
|
} |
||||||
|
|
||||||
|
private String extractPath(DbAction action) { |
||||||
|
|
||||||
|
if (action instanceof DbAction.WithPropertyPath) { |
||||||
|
return ((DbAction.WithPropertyPath<?>) action).getPropertyPath().toDotPath(); |
||||||
|
} |
||||||
|
|
||||||
|
return ""; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isWithDependsOn(DbAction dbAction) { |
||||||
|
return dbAction instanceof DbAction.WithDependingOn; |
||||||
|
} |
||||||
|
|
||||||
|
private Class<?> actualEntityType(DbAction a) { |
||||||
|
|
||||||
|
if (a instanceof DbAction.WithEntity) { |
||||||
|
return ((DbAction.WithEntity) a).getEntity().getClass(); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@RequiredArgsConstructor static class SingleReferenceEntity { |
||||||
|
|
||||||
|
@Id final Long id; |
||||||
|
Element other; |
||||||
|
// should not trigger own Dbaction
|
||||||
|
String name; |
||||||
|
} |
||||||
|
|
||||||
|
@RequiredArgsConstructor private static class Element { |
||||||
|
@Id final Long id; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,96 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2017-2018 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.relational.core.conversion; |
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.junit.MockitoJUnitRunner; |
||||||
|
import org.springframework.data.annotation.Id; |
||||||
|
import org.springframework.data.relational.core.conversion.AggregateChange.Kind; |
||||||
|
import org.springframework.data.relational.core.mapping.RelationalMappingContext; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* Unit tests for the {@link RelationalEntityUpdateWriter} |
||||||
|
* |
||||||
|
* @author Jens Schauder |
||||||
|
* @author Thomas Lang |
||||||
|
*/ |
||||||
|
@RunWith(MockitoJUnitRunner.class) |
||||||
|
public class RelationalEntityUpdateWriterUnitTests { |
||||||
|
|
||||||
|
public static final long SOME_ENTITY_ID = 23L; |
||||||
|
RelationalEntityUpdateWriter converter = new RelationalEntityUpdateWriter(new RelationalMappingContext()); |
||||||
|
|
||||||
|
@Test // DATAJDBC-112
|
||||||
|
public void existingEntityGetsConvertedToDeletePlusUpdate() { |
||||||
|
|
||||||
|
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); |
||||||
|
|
||||||
|
AggregateChange<RelationalEntityWriterUnitTests.SingleReferenceEntity> aggregateChange = //
|
||||||
|
new AggregateChange(Kind.SAVE, SingleReferenceEntity.class, entity); |
||||||
|
|
||||||
|
converter.write(entity, aggregateChange); |
||||||
|
|
||||||
|
assertThat(aggregateChange.getActions()) //
|
||||||
|
.extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, |
||||||
|
this::isWithDependsOn) //
|
||||||
|
.containsExactly( //
|
||||||
|
tuple(DbAction.Delete.class, Element.class, "other", null, false), //
|
||||||
|
tuple(DbAction.UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false) //
|
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
private String extractPath(DbAction action) { |
||||||
|
|
||||||
|
if (action instanceof DbAction.WithPropertyPath) { |
||||||
|
return ((DbAction.WithPropertyPath<?>) action).getPropertyPath().toDotPath(); |
||||||
|
} |
||||||
|
|
||||||
|
return ""; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isWithDependsOn(DbAction dbAction) { |
||||||
|
return dbAction instanceof DbAction.WithDependingOn; |
||||||
|
} |
||||||
|
|
||||||
|
private Class<?> actualEntityType(DbAction a) { |
||||||
|
|
||||||
|
if (a instanceof DbAction.WithEntity) { |
||||||
|
return ((DbAction.WithEntity) a).getEntity().getClass(); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@RequiredArgsConstructor |
||||||
|
static class SingleReferenceEntity { |
||||||
|
|
||||||
|
@Id |
||||||
|
final Long id; |
||||||
|
Element other; |
||||||
|
// should not trigger own Dbaction
|
||||||
|
String name; |
||||||
|
} |
||||||
|
|
||||||
|
@RequiredArgsConstructor |
||||||
|
private static class Element { |
||||||
|
@Id |
||||||
|
final Long id; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue