Skip to content

Commit 1046602

Browse files
committed
chore: Changes on the document entities persistence operations at the Eclipse JNoSQL DynamoDB Database
Signed-off-by: Maximillian Arruda <dearrudam@gmail.com>
1 parent ce6c39e commit 1046602

25 files changed

+724
-141
lines changed

CHANGELOG.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ and this project adheres to https://semver.org/spec/v2.0.0.html[Semantic Version
1212

1313
- Update query at Oracle NoSQL to support parameter with enum type
1414

15+
=== Changes
16+
17+
- Changes on the document entities persistence operations at the Eclipse JNoSQL DynamoDB Database
18+
19+
1520
== [1.1.8] - 2025-05-21
1621

1722
=== Changed

README.adoc

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -597,27 +597,6 @@ jnosql.keyvalue.database=heroes
597597

598598
=== Using the Document API
599599

600-
The DynamoDB's Document API implementation follows the *SINGLE TABLE* strategy, it means, the table will store multiple entity types. To satisfy this strategy, the implementation assumes that the target table will have a composed primary key:
601-
602-
- The `entityType` field as the partitioning key;
603-
- The `id` field as the sort key;
604-
605-
To customize the partitioning key field name, you can define the following configuration
606-
607-
[source,properties]
608-
----
609-
jnosql.dynamodb.entity.pk=entityType
610-
----
611-
612-
By default, the implementation doesn't create the table on-the-fly, letting this requirement for the users. If you prefer, the implementation is able to create the table on-the-fly as well. To activate this capability you should define explicitly the following configuration:
613-
614-
[source,properties]
615-
----
616-
jnosql.dynamodb.create.tables=true
617-
----
618-
619-
The table will be created with the composed primary key mentioned previously.
620-
621600
Here's an example using DynamoDB's Document API with MicroProfile Config.
622601

623602
[source,properties]
@@ -648,7 +627,7 @@ public class ManagerSupplier implements Supplier<DynamoDBDocumentManager> {
648627

649628
=== Repository
650629

651-
The ```DynamoDBRepository``` interface is an extension of the ```Repository``` interface that allows execution of PartiQL via the ```@PartiQL``` annotation.
630+
The `DynamoDBRepository` interface is an extension of the `Repository` interface that allows execution of PartiQL via the `@PartiQL` annotation.
652631

653632
WARNING: DynamoDB supports a limited subset of
654633
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-reference.html[PartiQL].
@@ -669,10 +648,9 @@ List<Person> findByName(@Param("") String name);
669648
}
670649
----
671650

672-
673651
=== Template
674652

675-
The ```DynamoDBTemplate``` interface is a specialization of the ```DocumentTemplate``` interface that allows using PartiQL queries.
653+
The `DynamoDBTemplate` interface is a specialization of the `DocumentTemplate` interface that allows using PartiQL queries.
676654

677655
WARNING: DynamoDB supports a limited subset of
678656
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-reference.html[PartiQL].
@@ -681,9 +659,44 @@ NOTE: This implementation doesn't provide pagination on the queries.
681659

682660
[source,java]
683661
----
684-
List<Person> people = template.partiQL("select * from Person where name = ? ", params);
662+
List<Person> people = template.partiQL("select * from Person where name = ? ", Person.class, params);
685663
----
686664

665+
==== Creating the tables on-the-fly
666+
667+
[IMPORTANT]
668+
====
669+
It's highly recommended to create the tables in a proper way, paying attention to the partition key and sort key, as well as the indexes.
670+
====
671+
672+
The DynamoDB implementation allows you to create tables on-the-fly, which can be useful for development and testing purposes. However, this feature should be used with caution in production environments, as it may lead to unexpected behavior or performance issues if not properly configured.
673+
674+
To create tables on-the-fly, you need to define the following properties:
675+
676+
Please note that you can establish properties using the https://microprofile.io/microprofile-config/[MicroProfile Config] specification.
677+
678+
[cols="DynamoDB"]
679+
|===
680+
|Configuration property |Description | Default value
681+
682+
|`jnosql.dynamodb.create.tables`
683+
| If set to true, the implementation will create the tables on-the-fly when the application starts. This is useful for development and testing purposes, but should be used with caution in production environments.
684+
| false
685+
686+
|`jnosql.dynamodb.<table>.pk`
687+
| The partition key field name for the table. This is used to define the primary key of the table. The `<table>` part should be replaced with the actual table name.
688+
| _id
689+
690+
|`jnosql.dynamodb.<table>.read.capacity.units`
691+
| The read capacity units for the table. This defines the number of strongly consistent reads per second that the table can support.The `<table>` part should be replaced with the actual table name. It's optional.
692+
| none
693+
694+
|`jnosql.dynamodb.<table>.write.capacity.units`
695+
| The write capacity units for the table. This defines the number of strongly consistent writes per second that the table can support.The `<table>` part should be replaced with the actual table name. It's optional.
696+
| none
697+
698+
|===
699+
687700
== Elasticsearch
688701

689702
image::https://jnosql.github.io/img/logos/elastic.svg[Elasticsearch Project,align="center"width=25%,height=25%]

jnosql-dynamodb/src/main/java/org/eclipse/jnosql/databases/dynamodb/communication/DefaultDynamoDBDatabaseManager.java

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@
5757
import java.util.stream.StreamSupport;
5858

5959
import static java.util.Objects.requireNonNull;
60-
import static org.eclipse.jnosql.databases.dynamodb.communication.DynamoDBConverter.entityAttributeName;
6160
import static org.eclipse.jnosql.databases.dynamodb.communication.DynamoDBConverter.toAttributeValue;
6261
import static org.eclipse.jnosql.databases.dynamodb.communication.DynamoDBConverter.toCommunicationEntity;
6362
import static org.eclipse.jnosql.databases.dynamodb.communication.DynamoDBConverter.toItem;
@@ -81,10 +80,6 @@ public DefaultDynamoDBDatabaseManager(String database, DynamoDbClient dynamoDbCl
8180
this.dynamoDbClient = dynamoDbClient;
8281
}
8382

84-
private String resolveEntityNameAttributeName(String entityName) {
85-
return this.settings.get(DynamoDBConfigurations.ENTITY_PARTITION_KEY, String.class).orElse(entityName);
86-
}
87-
8883
public DynamoDbClient dynamoDbClient() {
8984
return dynamoDbClient;
9085
}
@@ -99,7 +94,7 @@ public CommunicationEntity insert(CommunicationEntity documentEntity) {
9994
requireNonNull(documentEntity, "documentEntity is required");
10095
dynamoDbClient().putItem(PutItemRequest.builder()
10196
.tableName(createTableIfNeeded(documentEntity.name()).table().tableName())
102-
.item(toItem(this::resolveEntityNameAttributeName, documentEntity))
97+
.item(toItem(documentEntity))
10398
.build());
10499
return documentEntity;
105100
}
@@ -141,11 +136,12 @@ private DescribeTableResponse getDescribeTableResponse(String tableName) {
141136

142137
private DescribeTableResponse createTable(String tableName) {
143138
try (var waiter = dynamoDbClient().waiter()) {
139+
144140
dynamoDbClient().createTable(CreateTableRequest.builder()
145141
.tableName(tableName)
146-
.keySchema(defaultKeySchemaFor())
147-
.attributeDefinitions(defaultAttributeDefinitionsFor())
148-
.provisionedThroughput(defaultProvisionedThroughputFor())
142+
.keySchema(defaultKeySchemaFor(tableName))
143+
.attributeDefinitions(defaultAttributeDefinitionsFor(tableName))
144+
.provisionedThroughput(defaultProvisionedThroughputFor(tableName))
149145
.build());
150146

151147
var tableRequest = DescribeTableRequest.builder().tableName(tableName).build();
@@ -154,34 +150,40 @@ private DescribeTableResponse createTable(String tableName) {
154150
}
155151
}
156152

157-
private ProvisionedThroughput defaultProvisionedThroughputFor() {
158-
return DynamoTableUtils.createProvisionedThroughput(null, null);
153+
private ProvisionedThroughput defaultProvisionedThroughputFor(String tableName) {
154+
return DynamoTableUtils.createProvisionedThroughput(
155+
this.settings.get(DynamoDBConfigurations.ENTITY_READ_CAPACITY_UNITS.get().formatted(tableName), Long.class)
156+
.orElse(null),
157+
this.settings.get(DynamoDBConfigurations.ENTITY_WRITE_CAPACITY_UNITS.get().formatted(tableName), Long.class)
158+
.orElse(null));
159159
}
160160

161-
private Collection<AttributeDefinition> defaultAttributeDefinitionsFor() {
161+
private Collection<AttributeDefinition> defaultAttributeDefinitionsFor(String tableName) {
162162
return List.of(
163-
AttributeDefinition.builder().attributeName(getEntityAttributeName()).attributeType(ScalarAttributeType.S).build(),
164-
AttributeDefinition.builder().attributeName(DynamoDBConverter.ID).attributeType(ScalarAttributeType.S).build()
163+
AttributeDefinition.builder()
164+
.attributeName(partitionKeyName(tableName, DynamoDBConverter.ID)).attributeType(ScalarAttributeType.S).build()
165165
);
166166
}
167167

168-
private Collection<KeySchemaElement> defaultKeySchemaFor() {
168+
private Collection<KeySchemaElement> defaultKeySchemaFor(String tableName) {
169169
return List.of(
170-
KeySchemaElement.builder().attributeName(getEntityAttributeName()).keyType(KeyType.HASH).build(),
171-
KeySchemaElement.builder().attributeName(DynamoDBConverter.ID).keyType(KeyType.RANGE).build()
170+
KeySchemaElement.builder()
171+
.attributeName(partitionKeyName(tableName, DynamoDBConverter.ID)).keyType(KeyType.HASH).build()
172172
);
173173
}
174174

175+
private String partitionKeyName(String table, String defaultName) {
176+
return this.settings
177+
.get(DynamoDBConfigurations.ENTITY_PARTITION_KEY.name().formatted(table), String.class)
178+
.orElse(defaultName);
179+
}
180+
175181
private boolean shouldCreateTables() {
176182
return this.settings
177183
.get(DynamoDBConfigurations.CREATE_TABLES, Boolean.class)
178184
.orElse(false);
179185
}
180186

181-
private String getEntityAttributeName() {
182-
return entityAttributeName(this::resolveEntityNameAttributeName);
183-
}
184-
185187
@Override
186188
public CommunicationEntity insert(CommunicationEntity documentEntity, Duration ttl) {
187189
requireNonNull(documentEntity, "documentEntity is required");
@@ -233,12 +235,11 @@ private Map<String, AttributeValue> getItemKey(CommunicationEntity documentEntit
233235
a.putAll(b);
234236
return a;
235237
});
236-
itemKey.put(getEntityAttributeName(), toAttributeValue(documentEntity.name()));
237238
return itemKey;
238239
}
239240

240241
private Map<String, AttributeValueUpdate> asItemToUpdate(CommunicationEntity documentEntity) {
241-
return toItemUpdate(this::resolveEntityNameAttributeName, documentEntity);
242+
return toItemUpdate(documentEntity);
242243
}
243244

244245
@Override
@@ -278,22 +279,29 @@ public void delete(DeleteQuery deleteQuery) {
278279
public Stream<CommunicationEntity> select(SelectQuery query) {
279280
Objects.requireNonNull(query, "query is required");
280281
DynamoDBQuery dynamoDBQuery = DynamoDBQuery
281-
.builderOf(query.name(), getEntityAttributeName(), query)
282+
.builderOf(query.name(), query)
282283
.get();
283284

284285
ScanRequest.Builder selectRequest = ScanRequest.builder()
285286
.consistentRead(true)
286-
.tableName(dynamoDBQuery.table())
287+
.tableName(createTableIfNeeded(dynamoDBQuery.table()).table().tableName())
287288
.projectionExpression(dynamoDBQuery.projectionExpression())
288-
.filterExpression(dynamoDBQuery.filterExpression())
289-
.expressionAttributeNames(dynamoDBQuery.expressionAttributeNames())
290-
.expressionAttributeValues(dynamoDBQuery.expressionAttributeValues())
291289
.select(dynamoDBQuery.projectionExpression() != null ? Select.SPECIFIC_ATTRIBUTES : Select.ALL_ATTRIBUTES);
292290

291+
if (!dynamoDBQuery.filterExpression().isBlank()) {
292+
selectRequest = selectRequest.filterExpression(dynamoDBQuery.filterExpression());
293+
}
294+
295+
if (!dynamoDBQuery.expressionAttributeNames().isEmpty()) {
296+
selectRequest = selectRequest
297+
.expressionAttributeNames(dynamoDBQuery.expressionAttributeNames())
298+
.expressionAttributeValues(dynamoDBQuery.expressionAttributeValues());
299+
}
300+
293301
return StreamSupport
294302
.stream(dynamoDbClient().scanPaginator(selectRequest.build()).spliterator(), false)
295303
.flatMap(scanResponse -> scanResponse.items().stream()
296-
.map(item -> toCommunicationEntity(this::resolveEntityNameAttributeName, item)));
304+
.map(item -> toCommunicationEntity(dynamoDBQuery.table(), item)));
297305
}
298306

299307
@Override
@@ -314,7 +322,7 @@ public void close() {
314322
}
315323

316324
@Override
317-
public Stream<CommunicationEntity> partiQL(String query, Object... params) {
325+
public Stream<CommunicationEntity> partiQL(String query, String entityName, Object... params) {
318326
Objects.requireNonNull(query, "query is required");
319327
List<AttributeValue> parameters = Stream.of(params).map(DynamoDBConverter::toAttributeValue).toList();
320328
ExecuteStatementResponse executeStatementResponse = dynamoDbClient()
@@ -323,15 +331,15 @@ public Stream<CommunicationEntity> partiQL(String query, Object... params) {
323331
.parameters(parameters)
324332
.build());
325333
List<CommunicationEntity> result = new LinkedList<>();
326-
executeStatementResponse.items().forEach(item -> result.add(toCommunicationEntity(this::resolveEntityNameAttributeName, item)));
334+
executeStatementResponse.items().forEach(item -> result.add(toCommunicationEntity(entityName, item)));
327335
while (executeStatementResponse.nextToken() != null) {
328336
executeStatementResponse = dynamoDbClient()
329337
.executeStatement(ExecuteStatementRequest.builder()
330338
.statement(query)
331339
.parameters(parameters)
332340
.nextToken(executeStatementResponse.nextToken())
333341
.build());
334-
executeStatementResponse.items().forEach(item -> result.add(toCommunicationEntity(this::resolveEntityNameAttributeName, item)));
342+
executeStatementResponse.items().forEach(item -> result.add(toCommunicationEntity(entityName, item)));
335343
}
336344
return result.stream();
337345
}

jnosql-dynamodb/src/main/java/org/eclipse/jnosql/databases/dynamodb/communication/DynamoDBConfigurations.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ public enum DynamoDBConfigurations implements Supplier<String> {
2323
PROFILE("jnosql.dynamodb.profile"),
2424
AWS_ACCESSKEY("jnosql.dynamodb.awsaccesskey"),
2525
AWS_SECRET_ACCESS("jnosql.dynamodb.secretaccess"),
26-
ENTITY_PARTITION_KEY("jnosql.dynamodb.entity.pk"),
27-
CREATE_TABLES("jnosql.dynamodb.create.tables");
26+
CREATE_TABLES("jnosql.dynamodb.create.tables"),
27+
ENTITY_PARTITION_KEY("jnosql.dynamodb.%s.pk"),
28+
ENTITY_READ_CAPACITY_UNITS("jnosql.dynamodb.%s.read.capacity.units"),
29+
ENTITY_WRITE_CAPACITY_UNITS("jnosql.dynamodb.%s.write.capacity.units"),
30+
;
2831

2932
private final String configuration;
3033

jnosql-dynamodb/src/main/java/org/eclipse/jnosql/databases/dynamodb/communication/DynamoDBConverter.java

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,14 @@
2929
import java.util.List;
3030
import java.util.Map;
3131
import java.util.Objects;
32-
import java.util.Optional;
3332
import java.util.function.Consumer;
34-
import java.util.function.UnaryOperator;
3533
import java.util.stream.StreamSupport;
3634

3735
import static java.util.Collections.singletonMap;
3836

3937
class DynamoDBConverter {
4038

41-
static final String ENTITY = "@entity";
42-
static final String ID = "id";
39+
static final String ID = "_id";
4340

4441
private DynamoDBConverter() {
4542
}
@@ -64,18 +61,12 @@ private static Object convertValue(Object value) {
6461
return value;
6562
}
6663

67-
static Map<String, Object> getMap(UnaryOperator<String> entityNameResolver, CommunicationEntity entity) {
68-
var nameResolver = Optional.ofNullable(entityNameResolver).orElse(UnaryOperator.identity());
64+
static Map<String, Object> getMap(CommunicationEntity entity) {
6965
Map<String, Object> jsonObject = new HashMap<>();
7066
entity.elements().forEach(feedJSON(jsonObject));
71-
jsonObject.put(entityAttributeName(nameResolver), entity.name());
7267
return jsonObject;
7368
}
7469

75-
public static String entityAttributeName(UnaryOperator<String> nameResolver) {
76-
return Optional.ofNullable(nameResolver.apply(ENTITY)).orElse(ENTITY);
77-
}
78-
7970
@SuppressWarnings({"rawtypes", "unchecked"})
8071
private static Consumer<Element> feedJSON(Map<String, Object> jsonObject) {
8172
return d -> {
@@ -111,9 +102,8 @@ private static boolean isSudDocumentList(Object value) {
111102
allMatch(d -> d instanceof Iterable && isSudDocument(d));
112103
}
113104

114-
public static Map<String, AttributeValue> toItem(UnaryOperator<String> entityNameResolver, CommunicationEntity entity) {
115-
UnaryOperator<String> resolver = Optional.ofNullable(entityNameResolver).orElse(UnaryOperator.identity());
116-
Map<String, Object> documentAttributes = getMap(resolver, entity);
105+
public static Map<String, AttributeValue> toItem(CommunicationEntity entity) {
106+
Map<String, Object> documentAttributes = getMap(entity);
117107
return toItem(documentAttributes);
118108
}
119109

@@ -156,9 +146,8 @@ public static AttributeValue toAttributeValue(Object value) {
156146
return AttributeValue.builder().s(String.valueOf(value)).build();
157147
}
158148

159-
public static Map<String, AttributeValueUpdate> toItemUpdate(UnaryOperator<String> entityNameResolver, CommunicationEntity entity) {
160-
UnaryOperator<String> resolver = Optional.ofNullable(entityNameResolver).orElse(UnaryOperator.identity());
161-
Map<String, Object> documentAttributes = getMap(resolver, entity);
149+
public static Map<String, AttributeValueUpdate> toItemUpdate(CommunicationEntity entity) {
150+
Map<String, Object> documentAttributes = getMap(entity);
162151
return toItemUpdate(documentAttributes);
163152
}
164153

@@ -178,19 +167,15 @@ public static AttributeValueUpdate toAttributeValueUpdate(Object value) {
178167
}
179168

180169

181-
public static CommunicationEntity toCommunicationEntity(UnaryOperator<String> entityNameResolver, Map<String, AttributeValue> item) {
170+
public static CommunicationEntity toCommunicationEntity(String entityName, Map<String, AttributeValue> item) {
182171
if (item == null) {
183172
return null;
184173
}
185174
if (item.isEmpty()) {
186175
return null;
187176
}
188-
UnaryOperator<String> resolver = Optional.ofNullable(entityNameResolver).orElse(UnaryOperator.identity());
189-
String entityAttribute = resolver.apply(ENTITY);
190-
var entityName = item.containsKey(entityAttribute) ? item.get(entityAttribute).s() : entityAttribute;
191177
var elements = item.entrySet()
192178
.stream()
193-
.filter(entry -> !Objects.equals(entityAttribute, entry.getKey()))
194179
.map(entry -> Element.of(entry.getKey(), convertValue(entry.getValue())))
195180
.toList();
196181
return CommunicationEntity.of(entityName, elements);

jnosql-dynamodb/src/main/java/org/eclipse/jnosql/databases/dynamodb/communication/DynamoDBDatabaseManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public interface DynamoDBDatabaseManager extends DatabaseManager {
3535
* @return a {@link Stream} of {@link CommunicationEntity} representing the query result
3636
* @throws NullPointerException when the query is null
3737
*/
38-
Stream<CommunicationEntity> partiQL(String query, Object... params);
38+
Stream<CommunicationEntity> partiQL(String query, String entityName, Object... params);
3939

4040

4141
/**

jnosql-dynamodb/src/main/java/org/eclipse/jnosql/databases/dynamodb/communication/DynamoDBQuery.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ public record DynamoDBQuery(String table,
2828
Map<String, AttributeValue> expressionAttributeValues) {
2929

3030
public static Supplier<DynamoDBQuery> builderOf(String table,
31-
String partitionKey,
3231
SelectQuery query) {
33-
return new DynamoDBQuerySelectBuilder(table, partitionKey, query);
32+
return new DynamoDBQuerySelectBuilder(table, query);
3433
}
3534
}

0 commit comments

Comments
 (0)