Skip to content

Commit 4ba3863

Browse files
authored
Merge pull request #104 from derjust/issue_68
Issue #68: Batch save should return the failed entities
2 parents 130c54f + 91606ca commit 4ba3863

File tree

5 files changed

+117
-53
lines changed

5 files changed

+117
-53
lines changed

src/main/java/org/socialsignin/spring/data/dynamodb/core/DynamoDBOperations.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.socialsignin.spring.data.dynamodb.core;
22

3+
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.FailedBatch;
34
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperTableModel;
45
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression;
56
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression;
@@ -27,12 +28,10 @@ public interface DynamoDBOperations {
2728
Map<String, List<Object>> batchLoad(Map<Class<?>, List<KeyPair>> itemsToGet);
2829

2930
void save(Object entity);
30-
void batchSave(List<?> entities);
31-
void batchSave(Iterable<?> entities);
31+
List<FailedBatch> batchSave(Iterable<?> entities);
3232

3333
void delete(Object entity);
34-
void batchDelete(List<?> entities);
35-
void batchDelete(Iterable<?> entities);
34+
List<FailedBatch> batchDelete(Iterable<?> entities);
3635

3736
<T> String getOverriddenTableName(Class<T> domainClass, String tableName);
3837

src/main/java/org/socialsignin/spring/data/dynamodb/core/DynamoDBTemplate.java

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
44
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
5+
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.FailedBatch;
56
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig;
67
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperTableModel;
78
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression;
@@ -25,7 +26,6 @@
2526
import org.springframework.context.ApplicationContextAware;
2627
import org.springframework.context.ApplicationEventPublisher;
2728

28-
import java.util.Iterator;
2929
import java.util.List;
3030
import java.util.Map;
3131

@@ -99,7 +99,7 @@ public <T> T load(Class<T> domainClass, Object hashKey, Object rangeKey) {
9999
T entity = dynamoDBMapper.load(domainClass, hashKey,rangeKey);
100100
if (entity != null)
101101
{
102-
maybeEmitEvent(new AfterLoadEvent<Object>(entity));
102+
maybeEmitEvent(new AfterLoadEvent<>(entity));
103103
}
104104
return entity;
105105
}
@@ -109,7 +109,7 @@ public <T> T load(Class<T> domainClass, Object hashKey) {
109109
T entity = dynamoDBMapper.load(domainClass, hashKey);
110110
if (entity != null)
111111
{
112-
maybeEmitEvent(new AfterLoadEvent<Object>(entity));
112+
maybeEmitEvent(new AfterLoadEvent<>(entity));
113113
}
114114
return entity;
115115
}
@@ -137,58 +137,38 @@ public Map<String, List<Object>> batchLoad(Map<Class<?>, List<KeyPair>> itemsToG
137137

138138
@Override
139139
public void save(Object entity) {
140-
maybeEmitEvent(new BeforeSaveEvent<Object>(entity));
140+
maybeEmitEvent(new BeforeSaveEvent<>(entity));
141141
dynamoDBMapper.save(entity);
142-
maybeEmitEvent(new AfterSaveEvent<Object>(entity));
142+
maybeEmitEvent(new AfterSaveEvent<>(entity));
143143

144144
}
145-
146-
@Override
147-
@Deprecated
148-
public void batchSave(List<?> entities) {
149-
Iterable<?> iterableEntities = entities;
150-
batchSave(iterableEntities);
151-
}
152145

153146
@Override
154-
public void batchSave(Iterable<?> entities) {
155-
Iterator<?> iteratorBefore = entities.iterator();
156-
while( iteratorBefore.hasNext() ){
157-
maybeEmitEvent(new BeforeSaveEvent<Object>(iteratorBefore.next()));
158-
}
159-
dynamoDBMapper.batchSave(entities);
160-
Iterator<?> iteratorAfter = entities.iterator();
161-
while( iteratorAfter.hasNext() ){
162-
maybeEmitEvent(new BeforeSaveEvent<Object>(iteratorAfter.next()));
163-
}
164-
}
147+
public List<FailedBatch> batchSave(Iterable<?> entities) {
148+
entities.forEach(it -> maybeEmitEvent(new BeforeSaveEvent<>(it)));
149+
150+
List<FailedBatch> result = dynamoDBMapper.batchSave(entities);
151+
152+
entities.forEach(it -> maybeEmitEvent(new AfterSaveEvent<>(it)));
153+
return result;
154+
}
165155

166156
@Override
167157
public void delete(Object entity) {
168-
maybeEmitEvent(new BeforeDeleteEvent<Object>(entity));
158+
maybeEmitEvent(new BeforeDeleteEvent<>(entity));
169159
dynamoDBMapper.delete(entity);
170-
maybeEmitEvent(new AfterDeleteEvent<Object>(entity));
160+
maybeEmitEvent(new AfterDeleteEvent<>(entity));
171161

172162
}
173-
174-
@Override
175-
@Deprecated
176-
public void batchDelete(List<?> entities) {
177-
Iterable<?> iterableEntities = entities;
178-
batchDelete(iterableEntities);
179-
}
180163

181164
@Override
182-
public void batchDelete(Iterable<?> entities) {
183-
Iterator<?> iteratorBefore = entities.iterator();
184-
while( iteratorBefore.hasNext() ){
185-
maybeEmitEvent(new BeforeDeleteEvent<Object>(iteratorBefore.next()));
186-
}
187-
dynamoDBMapper.batchDelete(entities);
188-
Iterator<?> iteratorAfter = entities.iterator();
189-
while( iteratorAfter.hasNext() ){
190-
maybeEmitEvent(new AfterDeleteEvent<Object>(iteratorAfter.next()));
191-
}
165+
public List<FailedBatch> batchDelete(Iterable<?> entities) {
166+
entities.forEach(it -> maybeEmitEvent(new BeforeDeleteEvent<>(it)));
167+
168+
List<FailedBatch> result = dynamoDBMapper.batchDelete(entities);
169+
170+
entities.forEach(it -> maybeEmitEvent(new AfterDeleteEvent<>(it)));
171+
return result;
192172
}
193173

194174
@Override
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.socialsignin.spring.data.dynamodb.exception;
2+
3+
import org.springframework.dao.DataAccessException;
4+
5+
@SuppressWarnings("serial")
6+
public class BatchWriteException extends DataAccessException {
7+
8+
public BatchWriteException(String msg, Throwable cause) {
9+
super(msg, cause);
10+
}
11+
12+
}

src/main/java/org/socialsignin/spring/data/dynamodb/repository/support/SimpleDynamoDBCrudRepository.java

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,23 @@
1515
*/
1616
package org.socialsignin.spring.data.dynamodb.repository.support;
1717

18+
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.FailedBatch;
1819
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression;
1920
import com.amazonaws.services.dynamodbv2.datamodeling.KeyPair;
21+
22+
import org.socialsignin.spring.data.dynamodb.exception.BatchWriteException;
2023
import org.socialsignin.spring.data.dynamodb.core.DynamoDBOperations;
2124
import org.socialsignin.spring.data.dynamodb.repository.DynamoDBCrudRepository;
25+
import org.springframework.dao.DataAccessException;
2226
import org.springframework.dao.EmptyResultDataAccessException;
2327
import org.springframework.util.Assert;
2428

2529
import java.util.Collections;
30+
import java.util.LinkedList;
2631
import java.util.List;
2732
import java.util.Map;
2833
import java.util.Optional;
34+
import java.util.Queue;
2935
import java.util.concurrent.atomic.AtomicInteger;
3036
import java.util.stream.Collectors;
3137
import java.util.stream.StreamSupport;
@@ -34,8 +40,6 @@
3440
* Default implementation of the
3541
* {@link org.springframework.data.repository.CrudRepository} interface.
3642
*
37-
* @author Michael Lavelle
38-
*
3943
* @param <T>
4044
* the type of the entity to handle
4145
* @param <ID>
@@ -63,7 +67,6 @@ public SimpleDynamoDBCrudRepository(
6367
this.dynamoDBOperations = dynamoDBOperations;
6468
this.domainType = entityInformation.getJavaType();
6569
this.enableScanPermissions = enableScanPermissions;
66-
6770
}
6871

6972
@Override
@@ -119,10 +122,35 @@ public <S extends T> S save(S entity) {
119122
return entity;
120123
}
121124

125+
/**
126+
* {@inheritDoc}
127+
*
128+
* @throws BatchWriteException in case of an error during saving
129+
*/
122130
@Override
123-
public <S extends T> Iterable<S> saveAll(Iterable<S> entities) {
124-
dynamoDBOperations.batchSave(entities);
125-
return entities;
131+
public <S extends T> Iterable<S> saveAll(Iterable<S> entities) throws BatchWriteException, IllegalArgumentException {
132+
133+
Assert.notNull(entities, "The given Iterable of entities not be null!");
134+
List<FailedBatch> failedBatches = dynamoDBOperations.batchSave(entities);
135+
136+
if (failedBatches.isEmpty()) {
137+
// Happy path
138+
return entities;
139+
} else {
140+
// Error handling:
141+
Queue<Exception> allExceptions = failedBatches.stream()
142+
.map(it ->it.getException())
143+
.collect(Collectors.toCollection(LinkedList::new));
144+
145+
// The first exception is hopefully the cause
146+
Exception cause = allExceptions.poll();
147+
DataAccessException e = new BatchWriteException("Saving of entities failed!", cause);
148+
// and all other exceptions are 'just' follow-up exceptions
149+
allExceptions.stream()
150+
.forEach(e::addSuppressed);
151+
152+
throw e;
153+
}
126154
}
127155

128156
@Override

src/test/java/org/socialsignin/spring/data/dynamodb/repository/support/SimpleDynamoDBCrudRepositoryUnitTest.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@
1515
*/
1616
package org.socialsignin.spring.data.dynamodb.repository.support;
1717

18+
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.FailedBatch;
19+
1820
import org.junit.Assert;
1921
import org.junit.Before;
22+
import org.junit.Rule;
2023
import org.junit.Test;
24+
import org.junit.rules.ExpectedException;
2125
import org.junit.runner.RunWith;
2226
import org.mockito.ArgumentCaptor;
2327
import org.mockito.Mock;
@@ -27,13 +31,17 @@
2731
import org.socialsignin.spring.data.dynamodb.domain.sample.Playlist;
2832
import org.socialsignin.spring.data.dynamodb.domain.sample.PlaylistId;
2933
import org.socialsignin.spring.data.dynamodb.domain.sample.User;
34+
import org.socialsignin.spring.data.dynamodb.exception.BatchWriteException;
3035
import org.springframework.dao.EmptyResultDataAccessException;
3136

37+
import java.util.ArrayList;
38+
import java.util.Collections;
39+
import java.util.List;
3240
import java.util.Optional;
3341
import java.util.Random;
3442

3543
import static org.junit.Assert.assertEquals;
36-
import static org.mockito.ArgumentMatchers.anyLong;
44+
import static org.mockito.ArgumentMatchers.anyIterable;
3745
import static org.mockito.Mockito.verify;
3846
import static org.mockito.Mockito.when;
3947

@@ -50,6 +58,9 @@ public class SimpleDynamoDBCrudRepositoryUnitTest {
5058

5159
SimpleDynamoDBCrudRepository<Playlist, PlaylistId> repoForEntityWithHashAndRangeKey;
5260

61+
@Rule
62+
public ExpectedException expectedException = ExpectedException.none();
63+
5364
@Mock
5465
DynamoDBOperations dynamoDBOperations;
5566

@@ -153,4 +164,38 @@ public void throwsExceptionIfEntityWithHashAndRangeKeyToDeleteDoesNotExist() {
153164

154165
repoForEntityWithHashAndRangeKey.deleteById(playlistId);
155166
}
167+
168+
@Test
169+
public void testBatchSave() {
170+
171+
List<User> entities = new ArrayList<>();
172+
entities.add(new User());
173+
entities.add(new User());
174+
when(dynamoDBOperations.batchSave(anyIterable())).thenReturn(Collections.emptyList());
175+
176+
repoForEntityWithOnlyHashKey.saveAll(entities);
177+
178+
verify(dynamoDBOperations).batchSave(anyIterable());
179+
}
180+
181+
@Test
182+
public void testBatchSaveFailure() {
183+
List<FailedBatch> failures = new ArrayList<>();
184+
FailedBatch e1 = new FailedBatch();
185+
e1.setException(new Exception("First exception"));
186+
failures.add(e1);
187+
FailedBatch e2 = new FailedBatch();
188+
e2.setException(new Exception("Followup exception"));
189+
failures.add(e2);
190+
191+
List<User> entities = new ArrayList<>();
192+
entities.add(new User());
193+
entities.add(new User());
194+
when(dynamoDBOperations.batchSave(anyIterable())).thenReturn(failures);
195+
196+
expectedException.expect(BatchWriteException.class);
197+
expectedException.expectMessage("Saving of entities failed!; nested exception is java.lang.Exception: First exception");
198+
199+
repoForEntityWithOnlyHashKey.saveAll(entities);
200+
}
156201
}

0 commit comments

Comments
 (0)