Skip to content

Commit 6937de8

Browse files
authored
Merge pull request #330 from eclipse-jnosql/dynamodb-enhancement
Changes on the document entities persistence operations at the Eclipse JNoSQL DynamoDB Database
2 parents ce6c39e + d5c85a9 commit 6937de8

File tree

34 files changed

+791
-143
lines changed

34 files changed

+791
-143
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-arangodb/src/main/java/org/eclipse/jnosql/databases/arangodb/mapping/ArangoDBDocumentRepositoryProxy.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ class ArangoDBDocumentRepositoryProxy<T, K> extends AbstractSemiStructuredReposi
4848

4949
private final EntityMetadata entityMetadata;
5050

51+
private final EntitiesMetadata entitiesMetadata;
52+
5153
ArangoDBDocumentRepositoryProxy(ArangoDBTemplate template,
5254
Class<?> type,
5355
Converters converters,
@@ -57,6 +59,7 @@ class ArangoDBDocumentRepositoryProxy<T, K> extends AbstractSemiStructuredReposi
5759
.getActualTypeArguments()[0]);
5860
this.type = type;
5961
this.converters = converters;
62+
this.entitiesMetadata = entitiesMetadata;
6063
this.entityMetadata = entitiesMetadata.get(typeClass);
6164
this.repository = SemiStructuredRepositoryProxy.SemiStructuredRepository.of(template, entityMetadata);
6265
}
@@ -82,6 +85,11 @@ protected EntityMetadata entityMetadata() {
8285
return entityMetadata;
8386
}
8487

88+
@Override
89+
protected EntitiesMetadata entitiesMetadata() {
90+
return this.entitiesMetadata;
91+
}
92+
8593
@Override
8694
protected DocumentTemplate template() {
8795
return template;

jnosql-cassandra/src/main/java/org/eclipse/jnosql/databases/cassandra/mapping/CassandraRepositoryProxy.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ class CassandraRepositoryProxy<T, K> extends AbstractSemiStructuredRepositoryPro
4343

4444
private final Converters converters;
4545

46+
private final EntitiesMetadata entitiesMetadata;
47+
4648
private final EntityMetadata entityMetadata;
4749

4850
private final Class<?> repositoryType;
@@ -55,6 +57,7 @@ class CassandraRepositoryProxy<T, K> extends AbstractSemiStructuredRepositoryPro
5557
.getActualTypeArguments()[0]);
5658

5759
this.converters = converters;
60+
this.entitiesMetadata = entitiesMetadata;
5861
this.entityMetadata = entitiesMetadata.get(typeClass);
5962
this.repositoryType = repositoryType;
6063
this.repository = SemiStructuredRepositoryProxy.SemiStructuredRepository.of(template, entityMetadata);
@@ -80,6 +83,11 @@ protected EntityMetadata entityMetadata() {
8083
return entityMetadata;
8184
}
8285

86+
@Override
87+
protected EntitiesMetadata entitiesMetadata() {
88+
return this.entitiesMetadata;
89+
}
90+
8391
@Override
8492
protected ColumnTemplate template() {
8593
return template;

jnosql-couchbase/src/main/java/org/eclipse/jnosql/databases/couchbase/mapping/CouchbaseDocumentRepositoryProxy.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ class CouchbaseDocumentRepositoryProxy<T, K> extends AbstractSemiStructuredRepos
4343

4444
private final Converters converters;
4545

46+
private final EntitiesMetadata entitiesMetadata;
47+
4648
private final EntityMetadata entityMetadata;
4749

4850
private final Class<?> repositoryType;
@@ -54,6 +56,7 @@ class CouchbaseDocumentRepositoryProxy<T, K> extends AbstractSemiStructuredRepos
5456
this.typeClass = Class.class.cast(ParameterizedType.class.cast(repositoryType.getGenericInterfaces()[0])
5557
.getActualTypeArguments()[0]);
5658
this.converters = converters;
59+
this.entitiesMetadata = entitiesMetadata;
5760
this.entityMetadata = entitiesMetadata.get(typeClass);
5861
this.repositoryType = repositoryType;
5962
this.repository = SemiStructuredRepositoryProxy.SemiStructuredRepository.of(template, entityMetadata);
@@ -80,6 +83,11 @@ protected EntityMetadata entityMetadata() {
8083
return entityMetadata;
8184
}
8285

86+
@Override
87+
protected EntitiesMetadata entitiesMetadata() {
88+
return entitiesMetadata;
89+
}
90+
8391
@Override
8492
protected DocumentTemplate template() {
8593
return template;

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

0 commit comments

Comments
 (0)