From a478264935743311ac9bb1d22f95553d317d84a5 Mon Sep 17 00:00:00 2001 From: Christian Franzen <13965040+cfranzen@users.noreply.github.com> Date: Fri, 21 Apr 2023 22:37:29 +0200 Subject: [PATCH] feat: add support for insert and insertAll in DatastoreOperations (#1729) --- docs/src/main/asciidoc/datastore.adoc | 1 + docs/src/main/md/datastore.md | 3 +- .../datastore/core/DatastoreOperations.java | 25 ++ .../datastore/core/DatastoreTemplate.java | 46 +-- .../core/DatastoreTemplateTests.java | 262 ++++++++++-------- .../DatastoreTransactionTemplateTests.java | 10 +- 6 files changed, 215 insertions(+), 132 deletions(-) diff --git a/docs/src/main/asciidoc/datastore.adoc b/docs/src/main/asciidoc/datastore.adoc index fa63364012..9d34d63328 100644 --- a/docs/src/main/asciidoc/datastore.adoc +++ b/docs/src/main/asciidoc/datastore.adoc @@ -810,6 +810,7 @@ this.datastoreTemplate.save(t); ---- The `save` method behaves as update-or-insert. +In contrast, the `insert` method will fail if an entity already exists. ===== Partial Update diff --git a/docs/src/main/md/datastore.md b/docs/src/main/md/datastore.md index 65e8576f78..e106a56499 100644 --- a/docs/src/main/md/datastore.md +++ b/docs/src/main/md/datastore.md @@ -890,7 +890,8 @@ Trader t = new Trader(); this.datastoreTemplate.save(t); ``` -The `save` method behaves as update-or-insert. +The `save` method behaves as update-or-insert. +In contrast, the `insert` method will fail if an entity already exists. ##### Partial Update diff --git a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/DatastoreOperations.java b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/DatastoreOperations.java index 8779771d29..f04799c441 100644 --- a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/DatastoreOperations.java +++ b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/DatastoreOperations.java @@ -17,6 +17,7 @@ package com.google.cloud.spring.data.datastore.core; import com.google.cloud.datastore.BaseEntity; +import com.google.cloud.datastore.DatastoreException; import com.google.cloud.datastore.Key; import com.google.cloud.datastore.KeyQuery; import com.google.cloud.datastore.Query; @@ -71,6 +72,30 @@ public interface DatastoreOperations { */ Iterable saveAll(Iterable entities, Key... ancestors); + /** + * Inserts an instance of an object to Cloud Datastore. Throws a DatastoreException if an entry with same ID + * already exists. Ancestors can be added only to entries with Key ids. + * + * @param instance the instance to save. + * @param ancestors ancestors that should be added to the entry + * @param the type of the object to save + * @throws DatastoreException If the entity already exists + * @return the instance that was saved. + */ + T insert(T instance, Key... ancestors); + + /** + * Saves multiple instances of objects to Cloud Datastore. Throws a DatastoreException if any entry with one of + * the IDs already exists. Ancestors can be added only to entries with Key ids. + * + * @param entities the objects to save. + * @param ancestors ancestors that should be added to each entry + * @param the type of entities to save + * @throws DatastoreException If any entity already exists + * @return the entities that were saved. + */ + Iterable insertAll(Iterable entities, Key... ancestors); + /** * Delete an entity from Cloud Datastore. Deleting IDs that do not exist in Cloud Datastore will * result in no operation. diff --git a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplate.java b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplate.java index 662985815b..fb951a5f22 100644 --- a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplate.java +++ b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplate.java @@ -67,6 +67,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -144,21 +145,44 @@ public T findById(Object id, Class entityClass) { @Override public T save(T instance, Key... ancestors) { List instances = Collections.singletonList(instance); - saveEntities(instances, ancestors); + insertOrSaveEntities(instances, ancestors, getDatastoreReadWriter()::put); return instance; } @Override public Iterable saveAll(Iterable entities, Key... ancestors) { + insertOrSaveEntities(entities, ancestors, getDatastoreReadWriter()::put); + return entities; + } + + @Override + public T insert(final T instance, final Key... ancestors) { + List instances = Collections.singletonList(instance); + insertOrSaveEntities(instances, ancestors, getDatastoreReadWriter()::add); + return instance; + } + + @Override + public Iterable insertAll(final Iterable entities, final Key... ancestors) { + insertOrSaveEntities(entities, ancestors, getDatastoreReadWriter()::add); + return entities; + } + + private void insertOrSaveEntities(Iterable iterable, Key[] ancestors, Consumer[]> consumer) { List instances; - if (entities instanceof List) { - instances = (List) entities; + if (iterable instanceof List) { + instances = (List) iterable; } else { instances = new ArrayList<>(); - entities.forEach(instances::add); + iterable.forEach(instances::add); + } + + if (!instances.isEmpty()) { + maybeEmitEvent(new BeforeSaveEvent(instances)); + List entities = getEntitiesForSave(instances, new HashSet<>(), ancestors); + SliceUtil.sliceAndExecute(entities.toArray(new Entity[0]), this.maxWriteSize, consumer); + maybeEmitEvent(new AfterSaveEvent(entities, instances)); } - saveEntities(instances, ancestors); - return entities; } private List getEntitiesForSave( @@ -174,16 +198,6 @@ private List getEntitiesForSave( return entitiesForSave; } - private void saveEntities(List instances, Key[] ancestors) { - if (!instances.isEmpty()) { - maybeEmitEvent(new BeforeSaveEvent(instances)); - List entities = getEntitiesForSave(instances, new HashSet<>(), ancestors); - SliceUtil.sliceAndExecute( - entities.toArray(new Entity[0]), this.maxWriteSize, getDatastoreReadWriter()::put); - maybeEmitEvent(new AfterSaveEvent(entities, instances)); - } - } - @Override public void deleteById(Object id, Class entityClass) { performDelete( diff --git a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplateTests.java b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplateTests.java index 87171017a6..21be338b14 100644 --- a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplateTests.java +++ b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplateTests.java @@ -83,9 +83,12 @@ import java.util.function.Supplier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mockito; +import org.mockito.verification.VerificationMode; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.annotation.Id; @@ -561,8 +564,9 @@ void findAllReferenceLoopTest() { x -> {}); } - @Test - void saveReferenceLoopTest() { + @ParameterizedTest + @EnumSource(SaveOrInsertMethod.class) + void saveOrInsertReferenceLoopTest(SaveOrInsertMethod method) { ReferenceTestEntity referenceTestEntity = new ReferenceTestEntity(); referenceTestEntity.id = 1L; referenceTestEntity.sibling = referenceTestEntity; @@ -571,10 +575,10 @@ void saveReferenceLoopTest() { List callsArgs = gatherVarArgCallsArgs( - this.datastore.put(ArgumentMatchers.any()), + datastorePutOrAddAll(method, ArgumentMatchers.any()), Collections.singletonList(this.e1)); - assertThat(this.datastoreTemplate.save(referenceTestEntity)) + assertThat(saveOrInsert(method, referenceTestEntity)) .isInstanceOf(ReferenceTestEntity.class); Entity writtenEntity = Entity.newBuilder(this.key1).set("sibling", this.key1).build(); @@ -586,32 +590,35 @@ void saveReferenceLoopTest() { .buildModifiable()); } - @Test - void saveTest() { - saveTestCommon(this.ob1, false); + @ParameterizedTest + @EnumSource(SaveOrInsertMethod.class) + void saveOrInsertTest(SaveOrInsertMethod method) { + saveOrInsertTestCommon(method, this.ob1, false); } - @Test - void saveTestCollectionLazy() { + @ParameterizedTest + @EnumSource(SaveOrInsertMethod.class) + void saveOrInsertTestCollectionLazy(SaveOrInsertMethod method) { this.ob1.lazyMultipleReference = LazyUtil.wrapSimpleLazyProxy( () -> Collections.singletonList(this.childEntity7), List.class, ListValue.of(KeyValue.of(this.childKey7))); - saveTestCommon(this.ob1, true); + saveOrInsertTestCommon(method, this.ob1, true); } - @Test - void saveTestNotInterfaceLazy() { + @ParameterizedTest + @EnumSource(SaveOrInsertMethod.class) + void saveOrInsertTestNotInterfaceLazy(SaveOrInsertMethod method) { List arrayList = new ArrayList<>(); arrayList.add(this.childEntity7); this.ob1.lazyMultipleReference = LazyUtil.wrapSimpleLazyProxy( () -> arrayList, List.class, ListValue.of(KeyValue.of(this.childKey7))); - saveTestCommon(this.ob1, true); + saveOrInsertTestCommon(method, this.ob1, true); } - void saveTestCommon(TestEntity parent, boolean lazy) { + void saveOrInsertTestCommon(SaveOrInsertMethod method, TestEntity parent, boolean lazy) { Entity writtenEntity = Entity.newBuilder(this.key1) .set("singularReference", this.childKey4) @@ -628,29 +635,27 @@ void saveTestCommon(TestEntity parent, boolean lazy) { Entity writtenChildEntity6 = Entity.newBuilder(this.childKey6).build(); Entity writtenChildEntity7 = Entity.newBuilder(this.childKey7).build(); - doAnswer( - invocation -> { - Object[] arguments = invocation.getArguments(); - assertThat(arguments).contains(writtenEntity); - assertThat(arguments).contains(writtenChildEntity2); - assertThat(arguments).contains(writtenChildEntity3); - assertThat(arguments).contains(writtenChildEntity4); - assertThat(arguments).contains(writtenChildEntity5); - assertThat(arguments).contains(writtenChildEntity6); - if (lazy) { - assertThat(arguments).hasSize(6); - } else { - assertThat(arguments).contains(writtenChildEntity7); - assertThat(arguments).hasSize(7); - } - - return null; - }) - .when(this.datastore) - .put(ArgumentMatchers.any()); - - assertThat(this.datastoreTemplate.save(parent)).isInstanceOf(TestEntity.class); - verify(this.datastore, times(1)).put(ArgumentMatchers.any()); + when(datastorePutOrAddAll(method, ArgumentMatchers.any())) + .thenAnswer(invocation -> { + Object[] arguments = invocation.getArguments(); + assertThat(arguments).contains(writtenEntity); + assertThat(arguments).contains(writtenChildEntity2); + assertThat(arguments).contains(writtenChildEntity3); + assertThat(arguments).contains(writtenChildEntity4); + assertThat(arguments).contains(writtenChildEntity5); + assertThat(arguments).contains(writtenChildEntity6); + if (lazy) { + assertThat(arguments).hasSize(6); + } else { + assertThat(arguments).contains(writtenChildEntity7); + assertThat(arguments).hasSize(7); + } + + return null; + }); + + assertThat(saveOrInsert(method, parent)).isInstanceOf(TestEntity.class); + verifyPutOrAdd(method, times(1)); verify(this.datastoreEntityConverter, times(1)).write(same(parent), notNull()); verify(this.datastoreEntityConverter, times(1)).write(same(this.childEntity2), notNull()); verify(this.datastoreEntityConverter, times(1)).write(same(this.childEntity3), notNull()); @@ -684,18 +689,19 @@ private void assertArgs(List callsArgs, Map expected) { (key, value) -> assertThat(value).as("Extra calls with argument " + key).isZero()); } - @Test - void saveTestNonKeyId() { - + @ParameterizedTest + @EnumSource(SaveOrInsertMethod.class) + void saveOrInsertTestNonKeyId(SaveOrInsertMethod method) { Key testKey = createFakeKey("key0"); - assertThatThrownBy(() -> this.datastoreTemplate.save(this.ob1, testKey)) - .isInstanceOf(DatastoreDataException.class) - .hasMessage("Only Key types are allowed for descendants id"); + assertThatThrownBy(() -> saveOrInsert(method, this.ob1, testKey)) + .isInstanceOf(DatastoreDataException.class) + .hasMessage("Only Key types are allowed for descendants id"); } - @Test - void saveTestNullDescendantsAndReferences() { + @ParameterizedTest + @EnumSource(SaveOrInsertMethod.class) + void saveOrInsertTestNullDescendantsAndReferences(SaveOrInsertMethod method) { // making sure save works when descendants are null assertThat(this.ob2.childEntities).isNull(); assertThat(this.ob2.singularReference).isNull(); @@ -703,10 +709,10 @@ void saveTestNullDescendantsAndReferences() { List callsArgs = gatherVarArgCallsArgs( - this.datastore.put(ArgumentMatchers.any()), + datastorePutOrAddAll(method, ArgumentMatchers.any()), Collections.singletonList(this.e1)); - this.datastoreTemplate.save(this.ob2); + saveOrInsert(method, this.ob2); assertArgs( callsArgs, @@ -715,20 +721,21 @@ void saveTestNullDescendantsAndReferences() { .buildModifiable()); } - @Test - void saveTestKeyNoAncestor() { - + @ParameterizedTest + @EnumSource(SaveOrInsertMethod.class) + void saveOrInsertTestKeyNoAncestor(SaveOrInsertMethod method) { when(this.objectToKeyFactory.getKeyFromObject(eq(this.childEntity1), any())) .thenReturn(this.childEntity1.id); Key testKey = createFakeKey("key0"); - assertThatThrownBy(() -> this.datastoreTemplate.save(this.childEntity1, testKey)) - .isInstanceOf(DatastoreDataException.class) - .hasMessage("Descendant object has a key without current ancestor"); + assertThatThrownBy(() -> saveOrInsert(method, this.childEntity1, testKey)) + .isInstanceOf(DatastoreDataException.class) + .hasMessage("Descendant object has a key without current ancestor"); } - @Test - void saveTestKeyWithAncestor() { + @ParameterizedTest + @EnumSource(SaveOrInsertMethod.class) + void saveOrInsertTestKeyWithAncestor(SaveOrInsertMethod method) { Key key0 = createFakeKey("key0"); Key keyA = Key.newBuilder(key0) @@ -740,10 +747,10 @@ void saveTestKeyWithAncestor() { when(this.objectToKeyFactory.getKeyFromObject(eq(childEntity), any())).thenReturn(keyA); List callsArgs = gatherVarArgCallsArgs( - this.datastore.put(ArgumentMatchers.any()), + datastorePutOrAddAll(method, ArgumentMatchers.any()), Collections.singletonList(this.e1)); - this.datastoreTemplate.save(childEntity, key0); + saveOrInsert(method, childEntity, key0); Entity writtenChildEntity = Entity.newBuilder(keyA).build(); @@ -754,8 +761,9 @@ void saveTestKeyWithAncestor() { .buildModifiable()); } - @Test - void saveAndAllocateIdTest() { + @ParameterizedTest + @EnumSource(SaveOrInsertMethod.class) + void saveOrInsertAndAllocateIdTest(SaveOrInsertMethod method) { when(this.objectToKeyFactory.allocateKeyForObject(same(this.ob1), any())).thenReturn(this.key1); Entity writtenEntity1 = Entity.newBuilder(this.key1) @@ -771,31 +779,29 @@ void saveAndAllocateIdTest() { Entity writtenChildEntity5 = Entity.newBuilder(this.childKey5).build(); Entity writtenChildEntity6 = Entity.newBuilder(this.childKey6).build(); Entity writtenChildEntity7 = Entity.newBuilder(this.childKey7).build(); - doAnswer( - invocation -> { - assertThat(invocation.getArguments()) - .containsExactlyInAnyOrder( - writtenChildEntity2, - writtenChildEntity3, - writtenChildEntity4, - writtenChildEntity5, - writtenChildEntity6, - writtenEntity1, - writtenChildEntity7); - return null; - }) - .when(this.datastore) - .put(ArgumentMatchers.any()); - - assertThat(this.datastoreTemplate.save(this.ob1)).isInstanceOf(TestEntity.class); - - verify(this.datastore, times(1)).put(ArgumentMatchers.any()); + when(datastorePutOrAddAll(method, ArgumentMatchers.any())) + .thenAnswer(invocation -> { + assertThat(invocation.getArguments()) + .containsExactlyInAnyOrder( + writtenChildEntity2, + writtenChildEntity3, + writtenChildEntity4, + writtenChildEntity5, + writtenChildEntity6, + writtenEntity1, + writtenChildEntity7); + return null; + }); + + assertThat(saveOrInsert(method, this.ob1)).isInstanceOf(TestEntity.class); + verifyPutOrAdd(method, times(1)); verify(this.datastoreEntityConverter, times(1)).write(same(this.ob1), notNull()); } - @Test - void saveAllTest() { + @ParameterizedTest + @EnumSource(SaveOrInsertMethod.class) + void saveOrInsertAllTest(SaveOrInsertMethod method) { when(this.objectToKeyFactory.allocateKeyForObject(same(this.ob1), any())).thenReturn(this.key1); when(this.objectToKeyFactory.getKeyFromObject(same(this.ob2), any())).thenReturn(this.key2); Entity writtenEntity1 = @@ -814,22 +820,21 @@ void saveAllTest() { Entity writtenChildEntity5 = Entity.newBuilder(this.childKey5).build(); Entity writtenChildEntity6 = Entity.newBuilder(this.childKey6).build(); Entity writtenChildEntity7 = Entity.newBuilder(this.childKey7).build(); - doAnswer( - invocation -> { - assertThat(invocation.getArguments()) - .containsExactlyInAnyOrder( - writtenChildEntity2, - writtenChildEntity3, - writtenChildEntity4, - writtenChildEntity5, - writtenChildEntity6, - writtenEntity1, - writtenEntity2, - writtenChildEntity7); - return null; - }) - .when(this.datastore) - .put(ArgumentMatchers.any()); + + when(datastorePutOrAddAll(method, ArgumentMatchers.any())) + .thenAnswer(invocation -> { + assertThat(invocation.getArguments()) + .containsExactlyInAnyOrder( + writtenChildEntity2, + writtenChildEntity3, + writtenChildEntity4, + writtenChildEntity5, + writtenChildEntity6, + writtenEntity1, + writtenEntity2, + writtenChildEntity7); + return null; + }); List expected = Arrays.asList( @@ -846,7 +851,7 @@ void saveAllTest() { verifyBeforeAndAfterEvents( new BeforeSaveEvent(javaExpected), new AfterSaveEvent(expected, javaExpected), - () -> this.datastoreTemplate.saveAll(Arrays.asList(this.ob1, this.ob2)), + () -> saveOrInsertAll(method, Arrays.asList(this.ob1, this.ob2)), x -> {}); verify(this.datastoreEntityConverter, times(1)).write(same(this.ob1), notNull()); @@ -856,11 +861,12 @@ void saveAllTest() { verify(this.datastoreEntityConverter, times(1)).write(same(this.childEntity4), notNull()); verify(this.datastoreEntityConverter, times(1)).write(same(this.childEntity5), notNull()); verify(this.datastoreEntityConverter, times(1)).write(same(this.childEntity6), notNull()); - verify(this.datastore, times(1)).put(ArgumentMatchers.any()); + verifyPutOrAdd(method, times(1)); } - @Test - void saveAllMaxWriteSizeTest() { + @ParameterizedTest + @EnumSource(SaveOrInsertMethod.class) + void saveOrInsertAllMaxWriteSizeTest(SaveOrInsertMethod method) { when(this.objectToKeyFactory.allocateKeyForObject(same(this.ob1), any())).thenReturn(this.key1); when(this.objectToKeyFactory.getKeyFromObject(same(this.ob2), any())).thenReturn(this.key2); Entity writtenEntity1 = @@ -889,15 +895,13 @@ void saveAllMaxWriteSizeTest() { writtenChildEntity6, writtenEntity1, writtenEntity2)); - doAnswer( - invocation -> { - assertThat(invocation.getArguments()).hasSize(1); - assertThat(entities).contains((Entity) invocation.getArguments()[0]); - entities.remove(invocation.getArguments()[0]); - return null; - }) - .when(this.datastore) - .put(ArgumentMatchers.any()); + when(datastorePutOrAddAll(method, ArgumentMatchers.any())) + .thenAnswer(invocation -> { + assertThat(invocation.getArguments()).hasSize(1); + assertThat(entities).contains((Entity) invocation.getArguments()[0]); + entities.remove(invocation.getArguments()[0]); + return null; + }); List expected = Arrays.asList( @@ -915,7 +919,7 @@ void saveAllMaxWriteSizeTest() { verifyBeforeAndAfterEvents( new BeforeSaveEvent(javaExpected), new AfterSaveEvent(expected, javaExpected), - () -> this.datastoreTemplate.saveAll(Arrays.asList(this.ob1, this.ob2)), + () -> saveOrInsertAll(method, Arrays.asList(this.ob1, this.ob2)), x -> {}); assertThat(entities).isEmpty(); @@ -928,8 +932,44 @@ void saveAllMaxWriteSizeTest() { verify(this.datastoreEntityConverter, times(1)).write(same(this.childEntity5), notNull()); verify(this.datastoreEntityConverter, times(1)).write(same(this.childEntity6), notNull()); verify(this.datastoreEntityConverter, times(1)).write(same(this.childEntity7), notNull()); + verifyPutOrAdd(method, times(8)); + } + + private T saveOrInsert(SaveOrInsertMethod method, T instance, Key... ancestors) { + if (SaveOrInsertMethod.SAVE == method) { + return this.datastoreTemplate.save(instance, ancestors); + } else { + return this.datastoreTemplate.insert(instance, ancestors); + } + } + + private Iterable saveOrInsertAll(SaveOrInsertMethod method, Iterable entities, + Key... ancestors) { + if (SaveOrInsertMethod.SAVE == method) { + return this.datastoreTemplate.saveAll(entities, ancestors); + } else { + return this.datastoreTemplate.insertAll(entities, ancestors); + } + } + + private List datastorePutOrAddAll(SaveOrInsertMethod method, FullEntity... entity) { + if (SaveOrInsertMethod.SAVE == method) { + return this.datastore.put(entity); + } else { + return this.datastore.add(entity); + } + } + + private void verifyPutOrAdd(SaveOrInsertMethod method, VerificationMode verification) { + if (SaveOrInsertMethod.SAVE == method) { + verify(this.datastore, verification).put(ArgumentMatchers.any()); + } else { + verify(this.datastore, verification).add(ArgumentMatchers.any()); + } + } - verify(this.datastore, times(8)).put(ArgumentMatchers.any()); + private enum SaveOrInsertMethod { + SAVE, INSERT; } @Test diff --git a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTransactionTemplateTests.java b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTransactionTemplateTests.java index 3a60edd144..0f09e63168 100644 --- a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTransactionTemplateTests.java +++ b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTransactionTemplateTests.java @@ -106,7 +106,8 @@ void newTransaction() { verify(this.datastore, times(1)).newTransaction(); verify(this.transaction, times(1)).commit(); verify(this.transaction, times(0)).rollback(); - verify(this.transaction, times(3)).put((FullEntity[]) any()); + verify(this.transaction, times(2)).put((FullEntity[]) any()); + verify(this.transaction, times(1)).add((FullEntity[]) any()); verify(this.transaction, times(1)).fetch((Key[]) any()); verify(this.transaction, times(1)).delete(any()); } @@ -131,6 +132,7 @@ void doWithoutTransactionTest() { verify(this.transaction, never()).commit(); verify(this.transaction, never()).rollback(); verify(this.transaction, never()).put((FullEntity) any()); + verify(this.transaction, never()).add((FullEntity) any()); verify(this.transaction, never()).fetch((Key[]) any()); verify(this.transaction, never()).delete(any()); verify(this.datastore, never()).newTransaction(); @@ -197,7 +199,7 @@ public static class TransactionalService { @Transactional public void doInTransaction(TestEntity entity1, TestEntity entity2) { this.datastoreTemplate.findById("abc", TestEntity.class); - this.datastoreTemplate.save(entity1); + this.datastoreTemplate.insert(entity1); this.datastoreTemplate.save(entity2); this.datastoreTemplate.delete(entity1); this.datastoreTemplate.save(entity2); @@ -206,7 +208,7 @@ public void doInTransaction(TestEntity entity1, TestEntity entity2) { @Transactional public void doInTransactionWithException(TestEntity entity1, TestEntity entity2) { this.datastoreTemplate.findById("abc", TestEntity.class); - this.datastoreTemplate.save(entity1); + this.datastoreTemplate.insert(entity1); this.datastoreTemplate.save(entity2); this.datastoreTemplate.delete(entity1); this.datastoreTemplate.save(entity2); @@ -215,7 +217,7 @@ public void doInTransactionWithException(TestEntity entity1, TestEntity entity2) public void doWithoutTransaction(TestEntity entity1, TestEntity entity2) { this.datastoreTemplate.findById("abc", TestEntity.class); - this.datastoreTemplate.save(entity1); + this.datastoreTemplate.insert(entity1); this.datastoreTemplate.save(entity2); this.datastoreTemplate.delete(entity1); this.datastoreTemplate.save(entity2);