Skip to content

Commit f5bf637

Browse files
committed
feat: Use AWS SDK v2; Update to Spring v2.7.2
1 parent ed229c9 commit f5bf637

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+359
-223
lines changed

pom.xml

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>org.springframework.boot</groupId>
77
<artifactId>spring-boot-starter-parent</artifactId>
8-
<version>2.1.7.RELEASE</version>
8+
<version>2.7.2</version>
99
<relativePath />
1010
</parent>
1111

@@ -70,17 +70,18 @@
7070
<maven.compiler.target>1.8</maven.compiler.target>
7171
<project.scm.id>github</project.scm.id>
7272

73-
<version.dynamodb.local>1.13.5</version.dynamodb.local>
74-
<version.dynamodb.java.sdk>1.11.899</version.dynamodb.java.sdk>
75-
<version.dynamodb.lock.client>1.1.0</version.dynamodb.lock.client>
73+
<version.dynamodb.local>1.16.0</version.dynamodb.local>
74+
<version.awssdk>2.17.253</version.awssdk>
7675
</properties>
7776

7877
<dependencyManagement>
7978
<dependencies>
8079
<dependency>
81-
<groupId>com.amazonaws</groupId>
82-
<artifactId>aws-java-sdk-dynamodb</artifactId>
83-
<version>${version.dynamodb.java.sdk}</version>
80+
<groupId>software.amazon.awssdk</groupId>
81+
<artifactId>bom</artifactId>
82+
<version>${version.awssdk}</version>
83+
<type>pom</type>
84+
<scope>import</scope>
8485
</dependency>
8586
<dependency>
8687
<groupId>com.amazonaws</groupId>
@@ -90,6 +91,7 @@
9091
</dependency>
9192
</dependencies>
9293
</dependencyManagement>
94+
9395
<dependencies>
9496
<dependency>
9597
<groupId>org.springframework.boot</groupId>
@@ -108,8 +110,13 @@
108110
<artifactId>spring-oxm</artifactId>
109111
</dependency>
110112
<dependency>
111-
<groupId>com.amazonaws</groupId>
112-
<artifactId>aws-java-sdk-dynamodb</artifactId>
113+
<groupId>software.amazon.awssdk</groupId>
114+
<artifactId>dynamodb</artifactId>
115+
</dependency>
116+
<!-- necessary for Jackson2Json serializer -->
117+
<dependency>
118+
<groupId>com.fasterxml.jackson.core</groupId>
119+
<artifactId>jackson-databind</artifactId>
113120
</dependency>
114121
<dependency>
115122
<groupId>commons-beanutils</groupId>
@@ -254,9 +261,9 @@
254261
</profiles>
255262
<repositories>
256263
<repository>
257-
<id>dynamodb-local-oregon</id>
264+
<id>dynamodb-local-frankfurt</id>
258265
<name>DynamoDB Local Release Repository</name>
259-
<url>https://s3-us-west-2.amazonaws.com/dynamodb-local/release</url>
266+
<url>https://s3.eu-central-1.amazonaws.com/dynamodb-local-frankfurt/release</url>
260267
</repository>
261268
</repositories>
262269
</project>

src/main/java/com/dasburo/spring/cache/dynamo/DefaultDynamoCacheWriter.java

Lines changed: 87 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019 the original author or authors.
2+
* Copyright 2019-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,48 +15,48 @@
1515
*/
1616
package com.dasburo.spring.cache.dynamo;
1717

18-
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
19-
import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
20-
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
21-
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
22-
import com.amazonaws.services.dynamodbv2.model.DeleteItemRequest;
23-
import com.amazonaws.services.dynamodbv2.model.GetItemRequest;
24-
import com.amazonaws.services.dynamodbv2.model.GetItemResult;
25-
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
26-
import com.amazonaws.services.dynamodbv2.model.KeyType;
27-
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
28-
import com.amazonaws.services.dynamodbv2.model.PutItemRequest;
29-
import com.amazonaws.services.dynamodbv2.model.ResourceNotFoundException;
30-
import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;
31-
import com.amazonaws.services.dynamodbv2.model.ScanRequest;
32-
import com.amazonaws.services.dynamodbv2.model.TimeToLiveSpecification;
33-
import com.amazonaws.services.dynamodbv2.model.UpdateTimeToLiveRequest;
34-
import com.amazonaws.services.dynamodbv2.util.TableUtils;
3518
import com.dasburo.spring.cache.dynamo.rootattribute.RootAttribute;
36-
import com.dasburo.spring.cache.dynamo.util.ByteUtils;
19+
import com.dasburo.spring.cache.dynamo.util.TableUtils;
3720
import org.springframework.dao.PessimisticLockingFailureException;
3821
import org.springframework.lang.Nullable;
3922
import org.springframework.util.Assert;
23+
import software.amazon.awssdk.core.SdkBytes;
24+
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
25+
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
26+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
27+
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
28+
import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest;
29+
import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest;
30+
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
31+
import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
32+
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
33+
import software.amazon.awssdk.services.dynamodb.model.KeyType;
34+
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
35+
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
36+
import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException;
37+
import software.amazon.awssdk.services.dynamodb.model.TimeToLiveSpecification;
38+
import software.amazon.awssdk.services.dynamodb.model.UpdateTimeToLiveRequest;
4039

41-
import java.nio.ByteBuffer;
4240
import java.time.Duration;
4341
import java.time.Instant;
44-
import java.util.ArrayList;
4542
import java.util.Collections;
4643
import java.util.HashMap;
4744
import java.util.List;
4845
import java.util.Map;
4946
import java.util.NoSuchElementException;
47+
import java.util.Objects;
5048
import java.util.function.Function;
5149

50+
import static software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType.S;
51+
5252
/**
5353
* {@link DynamoCacheWriter} implementation capable of reading/writing binary data from/to DynamoDB in {@literal standalone}
54-
* and {@literal cluster} environments. Works upon a given {@link AmazonDynamoDB} holds the actual connection.
54+
* and {@literal cluster} environments. Works upon a given {@link DynamoDbClient} holds the actual connection.
5555
* <p>
5656
* {@link DefaultDynamoCacheWriter} can be used in
57-
* {@link DynamoCacheWriter#lockingDynamoCacheWriter(AmazonDynamoDB) locking} or
58-
* {@link DynamoCacheWriter#nonLockingDynamoCacheWriter(AmazonDynamoDB) non-locking} mode. While
59-
* {@literal non-locking} aims for maximum performance it may result in overlapping, non atomic, command execution for
57+
* {@link DynamoCacheWriter#lockingDynamoCacheWriter(DynamoDbClient) locking} or
58+
* {@link DynamoCacheWriter#nonLockingDynamoCacheWriter(DynamoDbClient) non-locking} mode. While
59+
* {@literal non-locking} aims for maximum performance it may result in overlapping, non-atomic, command execution for
6060
* operations spanning multiple DynamoDB interactions like {@code putIfAbsent}. The {@literal locking} counterpart prevents
6161
* command overlap by setting an explicit lock key and checking against presence of this key which leads to additional
6262
* requests and potential command wait times.
@@ -69,13 +69,13 @@ public class DefaultDynamoCacheWriter implements DynamoCacheWriter {
6969
public static final String ATTRIBUTE_VALUE = "value";
7070
public static final String ATTRIBUTE_TTL = "ttl";
7171

72-
private final AmazonDynamoDB dynamoTemplate;
72+
private final DynamoDbClient dynamoTemplate;
7373
private final Duration sleepTime;
7474

7575
/**
7676
* @param dynamoTemplate must not be {@literal null}.
7777
*/
78-
DefaultDynamoCacheWriter(AmazonDynamoDB dynamoTemplate) {
78+
DefaultDynamoCacheWriter(DynamoDbClient dynamoTemplate) {
7979
this(dynamoTemplate, Duration.ZERO);
8080
}
8181

@@ -84,7 +84,7 @@ public class DefaultDynamoCacheWriter implements DynamoCacheWriter {
8484
* @param sleepTime sleep time between lock request attempts. Must not be {@literal null}. Use {@link Duration#ZERO}
8585
* to disable locking.
8686
*/
87-
DefaultDynamoCacheWriter(AmazonDynamoDB dynamoTemplate, Duration sleepTime) {
87+
DefaultDynamoCacheWriter(DynamoDbClient dynamoTemplate, Duration sleepTime) {
8888
Assert.notNull(dynamoTemplate, "ConnectionFactory must not be null!");
8989
Assert.notNull(sleepTime, "SleepTime must not be null!");
9090

@@ -93,7 +93,7 @@ public class DefaultDynamoCacheWriter implements DynamoCacheWriter {
9393
}
9494

9595
@Override
96-
public AmazonDynamoDB getNativeCacheWriter() {
96+
public DynamoDbClient getNativeCacheWriter() {
9797
return dynamoTemplate;
9898
}
9999

@@ -157,23 +157,24 @@ public void clear(String name) {
157157
Assert.notNull(name, "Name must not be null!");
158158

159159
execute(name, connection -> {
160-
161160
try {
162161
if (isLockingCacheWriter()) {
163162
doLock(name);
164163
}
165164

166-
ScanRequest req = new ScanRequest(name);
167-
List<Map<String, AttributeValue>> items = dynamoTemplate.scan(req).getItems();
165+
List<Map<String, AttributeValue>> items = dynamoTemplate.scan(req -> req.tableName(name)).items();
168166

169167
items.parallelStream()
170168
.forEach(map -> {
171169
Map<String, AttributeValue> keyToDelete = new HashMap<>();
172170
keyToDelete.put(ATTRIBUTE_KEY, map.get(ATTRIBUTE_KEY));
173-
DeleteItemRequest delReq = new DeleteItemRequest(name, keyToDelete);
171+
DeleteItemRequest delReq = DeleteItemRequest.builder()
172+
.tableName(name)
173+
.key(keyToDelete)
174+
.build();
174175
dynamoTemplate.deleteItem(delReq);
175176
});
176-
} catch(ResourceNotFoundException ignored) {
177+
} catch (ResourceNotFoundException ignored) {
177178
// ignore table not found
178179
} finally {
179180
if (isLockingCacheWriter()) {
@@ -191,8 +192,10 @@ public boolean createIfNotExists(String name, Duration ttl, Long readCapacityUni
191192

192193
boolean created = false;
193194
try {
194-
dynamoTemplate.describeTable(name);
195-
} catch(ResourceNotFoundException e) {
195+
dynamoTemplate.describeTable(DescribeTableRequest.builder()
196+
.tableName(name)
197+
.build());
198+
} catch (ResourceNotFoundException e) {
196199
created = TableUtils.createTableIfNotExists(dynamoTemplate, createTableRequest(name, readCapacityUnits, writeCapacityUnits));
197200
if (created && !ttl.isZero()) {
198201
dynamoTemplate.updateTimeToLive(updateTimeToLiveRequest(name));
@@ -202,62 +205,65 @@ public boolean createIfNotExists(String name, Duration ttl, Long readCapacityUni
202205
}
203206

204207
private byte[] getInternal(String name, String key) {
205-
final GetItemRequest request = new GetItemRequest()
206-
.withAttributesToGet(ATTRIBUTE_VALUE)
207-
.withTableName(name)
208-
.withKey(Collections.singletonMap(ATTRIBUTE_KEY, new AttributeValue(key)));
209-
210-
final GetItemResult result = dynamoTemplate.getItem(request);
211-
if (result.getItem() != null) {
208+
final GetItemRequest request = GetItemRequest.builder()
209+
.attributesToGet(ATTRIBUTE_VALUE)
210+
.tableName(name)
211+
.key(Collections.singletonMap(ATTRIBUTE_KEY, AttributeValue.fromS(key)))
212+
.build();
213+
214+
final GetItemResponse result = dynamoTemplate.getItem(request);
215+
if (result.hasItem()) {
212216
return getAttributeValue(result);
213217
} else {
214218
throw new NoSuchElementException(String.format("No entry found for '%s'.", key));
215219
}
216220
}
217221

218-
private byte[] getAttributeValue(GetItemResult result) {
219-
final AttributeValue attribute = result.getItem().get(ATTRIBUTE_VALUE);
222+
private byte[] getAttributeValue(GetItemResponse result) {
223+
final AttributeValue attribute = result.item().get(ATTRIBUTE_VALUE);
220224
if (attribute == null) {
221225
throw new IllegalStateException(String.format("Attribute value does not match the expected '%s'.", ATTRIBUTE_VALUE));
222226
}
223227

224-
ByteBuffer element = attribute.getB();
225-
if (element == null && attribute.getNULL()) {
228+
SdkBytes element = attribute.b();
229+
if (element == null && attribute.nul()) {
226230
// TODO to return null is bad style, but how to distinct between null value from getInternal and no entry at all?
227231
return null;
228232
} else {
229-
return ByteUtils.getBytes(element);
233+
return Objects.requireNonNull(element).asByteArray();
230234
}
231235
}
232236

233237
private void putInternal(String name, String key, @Nullable byte[] value, @Nullable Duration ttl, @Nullable List<RootAttribute> rootAttributes) {
234238
Map<String, AttributeValue> attributeValues = new HashMap<>();
235-
attributeValues.put(ATTRIBUTE_KEY, new AttributeValue().withS(key));
239+
attributeValues.put(ATTRIBUTE_KEY, AttributeValue.fromS(key));
236240

237241
if (value == null) {
238-
attributeValues.put(ATTRIBUTE_VALUE, new AttributeValue().withNULL(true));
242+
attributeValues.put(ATTRIBUTE_VALUE, AttributeValue.fromNul(true));
239243
} else {
240-
attributeValues.put(ATTRIBUTE_VALUE, new AttributeValue().withB(ByteBuffer.wrap(value)));
244+
attributeValues.put(ATTRIBUTE_VALUE, AttributeValue.fromB(SdkBytes.fromByteArray(value)));
241245
}
242246

243247
if (shouldExpireWithin(ttl)) {
244-
attributeValues.put(ATTRIBUTE_TTL, new AttributeValue().withN(String.valueOf(Instant.now().plus(ttl).getEpochSecond())));
248+
attributeValues.put(ATTRIBUTE_TTL, AttributeValue.fromN(String.valueOf(Instant.now().plus(ttl).getEpochSecond())));
245249
}
246250

247251
if (rootAttributes != null) {
248252
rootAttributes.forEach(rootAttribute -> attributeValues.put(rootAttribute.getName(), rootAttribute.getAttributeValue()));
249253
}
250254

251-
PutItemRequest putItemRequest = new PutItemRequest()
252-
.withTableName(name)
253-
.withItem(attributeValues);
255+
PutItemRequest putItemRequest = PutItemRequest.builder()
256+
.tableName(name)
257+
.item(attributeValues)
258+
.build();
254259
dynamoTemplate.putItem(putItemRequest);
255260
}
256261

257262
private void removeInternal(String name, String key) {
258-
dynamoTemplate.deleteItem(new DeleteItemRequest()
259-
.withTableName(name)
260-
.withKey(Collections.singletonMap(ATTRIBUTE_KEY, new AttributeValue(key))));
263+
dynamoTemplate.deleteItem(DeleteItemRequest.builder()
264+
.tableName(name)
265+
.key(Collections.singletonMap(ATTRIBUTE_KEY, AttributeValue.fromS(key)))
266+
.build());
261267
}
262268

263269
private void doLock(String name) {
@@ -289,7 +295,7 @@ private boolean isLockingCacheWriter() {
289295
return !sleepTime.isZero() && !sleepTime.isNegative();
290296
}
291297

292-
private <T> T execute(String name, Function<AmazonDynamoDB, T> callback) {
298+
private <T> T execute(String name, Function<DynamoDbClient, T> callback) {
293299
checkAndPotentiallyWaitUntilUnlocked(name);
294300
return callback.apply(dynamoTemplate);
295301
}
@@ -321,25 +327,31 @@ private static String createCacheLockKey(String name) {
321327
}
322328

323329
private CreateTableRequest createTableRequest(String name, Long readCapacityUnits, Long writeCapacityUnits) {
324-
List<AttributeDefinition> attributeDefinitions = new ArrayList<>();
325-
attributeDefinitions.add(new AttributeDefinition().withAttributeName(ATTRIBUTE_KEY).withAttributeType(ScalarAttributeType.S));
326-
327-
List<KeySchemaElement> keySchema = new ArrayList<>();
328-
keySchema.add(new KeySchemaElement().withAttributeName(ATTRIBUTE_KEY).withKeyType(KeyType.HASH));
329-
330-
return new CreateTableRequest()
331-
.withTableName(name)
332-
.withKeySchema(keySchema)
333-
.withAttributeDefinitions(attributeDefinitions)
334-
.withProvisionedThroughput(new ProvisionedThroughput(readCapacityUnits, writeCapacityUnits));
330+
return CreateTableRequest.builder()
331+
.tableName(name)
332+
.attributeDefinitions(AttributeDefinition.builder()
333+
.attributeName(ATTRIBUTE_KEY)
334+
.attributeType(S)
335+
.build())
336+
.keySchema(KeySchemaElement.builder()
337+
.attributeName(ATTRIBUTE_KEY)
338+
.keyType(KeyType.HASH)
339+
.build())
340+
.provisionedThroughput(ProvisionedThroughput.builder()
341+
.readCapacityUnits(readCapacityUnits)
342+
.writeCapacityUnits(writeCapacityUnits)
343+
.build())
344+
.build();
335345
}
336346

337347
// TODO to be tested (not implemented in AmazonDynamoDB local)
338348
private UpdateTimeToLiveRequest updateTimeToLiveRequest(String name) {
339-
return new UpdateTimeToLiveRequest()
340-
.withTableName(name)
341-
.withTimeToLiveSpecification(new TimeToLiveSpecification()
342-
.withEnabled(true)
343-
.withAttributeName(ATTRIBUTE_TTL));
349+
return UpdateTimeToLiveRequest.builder()
350+
.tableName(name)
351+
.timeToLiveSpecification(TimeToLiveSpecification.builder()
352+
.enabled(true)
353+
.attributeName(ATTRIBUTE_TTL)
354+
.build())
355+
.build();
344356
}
345357
}

src/main/java/com/dasburo/spring/cache/dynamo/DynamoCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019 the original author or authors.
2+
* Copyright 2019-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.

0 commit comments

Comments
 (0)