Skip to content

Commit e76fd51

Browse files
Configure DynamoDb TableSchema as a bean (#957)
Fixes #674 Co-authored-by: Gladîș Vladlen <[email protected]>
1 parent 1c9f28f commit e76fd51

File tree

7 files changed

+146
-53
lines changed

7 files changed

+146
-53
lines changed

docs/src/main/asciidoc/dynamodb.adoc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ To use a custom implementation, declare a bean of type `DynamoDbTableNameResolve
7070

7171
To resolve a table schema for an entity, `DynamoDbTemplate` uses a bean of type `DynamoDbTableSchemaResolver`. The default implementation caches `TableSchema` objects in internal map.
7272
To use custom implementation, declare a bean of type `DynamoDbTableSchemaResolver` and it will get injected into `DynamoDbTemplate` automatically during auto-configuration.
73+
To register a custom table schema for a DynamoDB entity a bean of type `TableSchema` should be created:
74+
[source, java]
75+
----
76+
@Configuration
77+
public class MyTableSchemaConfiguration {
78+
79+
@Bean
80+
public TableSchema<MyEntity> myEntityTableSchema() {
81+
// create and return a TableSchema object for the MyEntity class
82+
}
83+
}
84+
----
7385

7486
IMPORTANT: Because of classloader related https://github.com/aws/aws-sdk-java-v2/issues/2604[issue] in AWS SDK DynamoDB Enhanced client, to use Spring Cloud AWS DynamoDB module together with Spring Boot DevTools you must create a custom table schema resolver and define schema using `StaticTableSchema`.
7587

spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbAutoConfiguration.java

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration;
2222
import io.awspring.cloud.dynamodb.*;
2323
import java.io.IOException;
24-
import java.util.Optional;
24+
import java.util.List;
2525
import org.springframework.beans.factory.ObjectProvider;
2626
import org.springframework.boot.autoconfigure.AutoConfiguration;
2727
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@@ -37,6 +37,7 @@
3737
import org.springframework.context.annotation.Configuration;
3838
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
3939
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
40+
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
4041
import software.amazon.awssdk.regions.providers.AwsRegionProvider;
4142
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
4243
import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder;
@@ -47,6 +48,7 @@
4748
*
4849
* @author Matej Nedic
4950
* @author Arun Patra
51+
* @author Maciej Walkowiak
5052
* @since 3.0.0
5153
*/
5254
@AutoConfiguration
@@ -115,17 +117,23 @@ public DynamoDbEnhancedClient dynamoDbEnhancedClient(DynamoDbClient dynamoDbClie
115117
return DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build();
116118
}
117119

120+
@ConditionalOnMissingBean(DynamoDbTableSchemaResolver.class)
121+
@Bean
122+
public DefaultDynamoDbTableSchemaResolver dynamoDbTableSchemaResolver(List<TableSchema<?>> tableSchemas) {
123+
return new DefaultDynamoDbTableSchemaResolver(tableSchemas);
124+
}
125+
126+
@ConditionalOnMissingBean(DynamoDbTableNameResolver.class)
127+
@Bean
128+
public DefaultDynamoDbTableNameResolver dynamoDbTableNameResolver(DynamoDbProperties properties) {
129+
return new DefaultDynamoDbTableNameResolver(properties.getTablePrefix());
130+
}
131+
118132
@ConditionalOnMissingBean(DynamoDbOperations.class)
119133
@Bean
120-
public DynamoDbTemplate dynamoDBTemplate(DynamoDbProperties properties,
121-
DynamoDbEnhancedClient dynamoDbEnhancedClient, Optional<DynamoDbTableSchemaResolver> tableSchemaResolver,
122-
Optional<DynamoDbTableNameResolver> tableNameResolver) {
123-
DynamoDbTableSchemaResolver tableSchemaRes = tableSchemaResolver
124-
.orElseGet(DefaultDynamoDbTableSchemaResolver::new);
125-
126-
DynamoDbTableNameResolver tableNameRes = tableNameResolver
127-
.orElseGet(() -> new DefaultDynamoDbTableNameResolver(properties.getTablePrefix()));
128-
return new DynamoDbTemplate(dynamoDbEnhancedClient, tableSchemaRes, tableNameRes);
134+
public DynamoDbTemplate dynamoDBTemplate(DynamoDbEnhancedClient dynamoDbEnhancedClient,
135+
DynamoDbTableSchemaResolver tableSchemaResolver, DynamoDbTableNameResolver dynamoDbTableNameResolver) {
136+
return new DynamoDbTemplate(dynamoDbEnhancedClient, tableSchemaResolver, dynamoDbTableNameResolver);
129137
}
130138

131139
static class MissingDaxUrlCondition extends NoneNestedConditions {

spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbAutoConfigurationTest.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2022 the original author or authors.
2+
* Copyright 2013-2023 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.
@@ -22,6 +22,7 @@
2222
import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer;
2323
import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration;
2424
import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration;
25+
import io.awspring.cloud.dynamodb.DefaultDynamoDbTableSchemaResolver;
2526
import io.awspring.cloud.dynamodb.DynamoDbTableNameResolver;
2627
import io.awspring.cloud.dynamodb.DynamoDbTableSchemaResolver;
2728
import io.awspring.cloud.dynamodb.DynamoDbTemplate;
@@ -38,6 +39,7 @@
3839
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
3940
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
4041
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
42+
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
4143
import software.amazon.awssdk.http.SdkHttpClient;
4244
import software.amazon.awssdk.http.apache.ApacheHttpClient;
4345
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
@@ -48,6 +50,7 @@
4850
* Tests for {@link DynamoDbAutoConfiguration}.
4951
*
5052
* @author Matej Nedic
53+
* @author Maciej Walkowiak
5154
*/
5255
class DynamoDbAutoConfigurationTest {
5356

@@ -125,6 +128,18 @@ void customDynamoDbClientConfigurer() {
125128
assertThat(dynamoDbClient.getSyncHttpClient()).isNotNull();
126129
});
127130
}
131+
132+
@Test
133+
void tableSchemaBeansRegistered() {
134+
contextRunner.withUserConfiguration(DynamoDbAutoConfigurationTest.TableSchemaConfiguration.class)
135+
.run(context -> {
136+
DefaultDynamoDbTableSchemaResolver schemaResolver = context
137+
.getBean(DefaultDynamoDbTableSchemaResolver.class);
138+
TableSchema<TableSchemaConfiguration.Person> personTableSchema = schemaResolver
139+
.resolve(TableSchemaConfiguration.Person.class);
140+
assertThat(context.getBean("personTableSchema")).isEqualTo(personTableSchema);
141+
});
142+
}
128143
}
129144

130145
@Nested
@@ -254,15 +269,15 @@ DynamoDbTableSchemaResolver tableSchemaResolver() {
254269
DynamoDbTableNameResolver tableNameResolver() {
255270
return new CustomDynamoDBDynamoDbTableNameResolver();
256271
}
272+
257273
}
258274

259275
static class CustomDynamoDBDynamoDbTableSchemaResolver implements DynamoDbTableSchemaResolver {
260276

261277
@Override
262-
public <T> TableSchema resolve(Class<T> clazz, String tableName) {
278+
public <T> TableSchema resolve(Class<T> clazz) {
263279
return null;
264280
}
265-
266281
}
267282

268283
static class CustomDynamoDBDynamoDbTableNameResolver implements DynamoDbTableNameResolver {
@@ -297,4 +312,16 @@ public SdkHttpClient httpClient() {
297312

298313
}
299314

315+
@Configuration(proxyBeanMethods = false)
316+
static class TableSchemaConfiguration {
317+
318+
@Bean
319+
TableSchema<Person> personTableSchema() {
320+
return StaticTableSchema.builder(Person.class).build();
321+
}
322+
323+
static class Person {
324+
}
325+
}
326+
300327
}

spring-cloud-aws-dynamodb/src/main/java/io/awspring/cloud/dynamodb/DefaultDynamoDbTableSchemaResolver.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2022 the original author or authors.
2+
* Copyright 2013-2023 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,6 +15,8 @@
1515
*/
1616
package io.awspring.cloud.dynamodb;
1717

18+
import java.util.Collections;
19+
import java.util.List;
1820
import java.util.Map;
1921
import java.util.concurrent.ConcurrentHashMap;
2022
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
@@ -23,12 +25,27 @@
2325
* Default implementation with simple cache for {@link TableSchema}.
2426
*
2527
* @author Matej Nedic
28+
* @author Maciej Walkowiak
2629
*/
2730
public class DefaultDynamoDbTableSchemaResolver implements DynamoDbTableSchemaResolver {
28-
private final Map<String, TableSchema> tableSchemaCache = new ConcurrentHashMap<>();
31+
private final Map<Class<?>, TableSchema> tableSchemaCache = new ConcurrentHashMap<>();
32+
33+
public DefaultDynamoDbTableSchemaResolver() {
34+
this(Collections.emptyList());
35+
}
36+
37+
public DefaultDynamoDbTableSchemaResolver(List<TableSchema<?>> tableSchemas) {
38+
for (TableSchema<?> tableSchema : tableSchemas) {
39+
tableSchemaCache.put(tableSchema.itemType().rawClass(), tableSchema);
40+
}
41+
}
2942

3043
@Override
31-
public <T> TableSchema<T> resolve(Class<T> clazz, String tableName) {
32-
return tableSchemaCache.computeIfAbsent(tableName, entityClassName -> TableSchema.fromBean(clazz));
44+
public <T> TableSchema<T> resolve(Class<T> clazz) {
45+
return tableSchemaCache.computeIfAbsent(clazz, TableSchema::fromBean);
46+
}
47+
48+
Map<Class<?>, TableSchema> getTableSchemaCache() {
49+
return tableSchemaCache;
3350
}
3451
}

spring-cloud-aws-dynamodb/src/main/java/io/awspring/cloud/dynamodb/DynamoDbTableSchemaResolver.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2022 the original author or authors.
2+
* Copyright 2013-2023 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.
@@ -18,21 +18,33 @@
1818
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
1919

2020
/**
21-
* Resolving Class and TableName to {@link TableSchema} class. Should be cached since creating {@link TableSchema} is
22-
* expensive.
21+
* Resolving table schema and table name from a class.
2322
*
2423
* @author Matej Nedic
24+
* @author Maciej Walkowiak
2525
* @since 3.0
2626
*/
2727
public interface DynamoDbTableSchemaResolver {
2828

2929
/**
30-
* Resolving Class and TableName to {@link TableSchema} class.
30+
* Resolves {@link TableSchema} from {@link Class}.
31+
*
32+
* @param clazz - the class from which table schema is resolved
33+
* @return table schema
34+
* @param <T> - type
35+
*/
36+
<T> TableSchema<T> resolve(Class<T> clazz);
37+
38+
/**
39+
* Resolves {@link TableSchema} from {@link Class}.
3140
*
3241
* @param clazz - the class from which table schema is resolved
3342
* @param tableName - the table name
3443
* @return table schema
3544
* @param <T> - type
3645
*/
37-
<T> TableSchema resolve(Class<T> clazz, String tableName);
46+
@Deprecated
47+
default <T> TableSchema resolve(Class<T> clazz, String tableName) {
48+
return resolve(clazz);
49+
}
3850
}

spring-cloud-aws-dynamodb/src/main/java/io/awspring/cloud/dynamodb/DynamoDbTemplate.java

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package io.awspring.cloud.dynamodb;
1717

18+
import java.util.Collections;
1819
import org.springframework.lang.Nullable;
1920
import org.springframework.util.Assert;
2021
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
@@ -29,29 +30,29 @@
2930
*
3031
* @author Matej Nedic
3132
* @author Arun Patra
33+
* @author Maciej Walkowiak
3234
* @since 3.0
3335
*/
3436
public class DynamoDbTemplate implements DynamoDbOperations {
3537
private final DynamoDbEnhancedClient dynamoDbEnhancedClient;
3638
private final DynamoDbTableSchemaResolver dynamoDbTableSchemaResolver;
37-
private final DynamoDbTableNameResolver dynamoDbTableNameResolver;
39+
private final DynamoDbTableNameResolver tableNameResolver;
3840

3941
public DynamoDbTemplate(@Nullable String tablePrefix, DynamoDbEnhancedClient dynamoDbEnhancedClient) {
40-
this(dynamoDbEnhancedClient, new DefaultDynamoDbTableSchemaResolver(),
42+
this(dynamoDbEnhancedClient, new DefaultDynamoDbTableSchemaResolver(Collections.emptyList()),
4143
new DefaultDynamoDbTableNameResolver(tablePrefix));
4244
}
4345

4446
public DynamoDbTemplate(DynamoDbEnhancedClient dynamoDbEnhancedClient) {
45-
this(dynamoDbEnhancedClient, new DefaultDynamoDbTableSchemaResolver(),
46-
new DefaultDynamoDbTableNameResolver(null));
47+
this(dynamoDbEnhancedClient, new DefaultDynamoDbTableSchemaResolver(Collections.emptyList()),
48+
new DefaultDynamoDbTableNameResolver());
4749
}
4850

4951
public DynamoDbTemplate(DynamoDbEnhancedClient dynamoDbEnhancedClient,
50-
DynamoDbTableSchemaResolver dynamoDbTableSchemaResolver,
51-
DynamoDbTableNameResolver dynamoDbTableNameResolver) {
52+
DynamoDbTableSchemaResolver dynamoDbTableSchemaResolver, DynamoDbTableNameResolver tableNameResolver) {
5253
this.dynamoDbEnhancedClient = dynamoDbEnhancedClient;
5354
this.dynamoDbTableSchemaResolver = dynamoDbTableSchemaResolver;
54-
this.dynamoDbTableNameResolver = dynamoDbTableNameResolver;
55+
this.tableNameResolver = tableNameResolver;
5556
}
5657

5758
public <T> T save(T entity) {
@@ -114,15 +115,15 @@ public <T> PageIterable<T> query(QueryEnhancedRequest queryEnhancedRequest, Clas
114115

115116
private <T> DynamoDbTable<T> prepareTable(T entity) {
116117
Assert.notNull(entity, "entity is required");
117-
String tableName = dynamoDbTableNameResolver.resolve(entity.getClass());
118-
return dynamoDbEnhancedClient.table(tableName,
119-
dynamoDbTableSchemaResolver.resolve(entity.getClass(), tableName));
118+
String tableName = tableNameResolver.resolve(entity.getClass());
119+
return (DynamoDbTable<T>) dynamoDbEnhancedClient.table(tableName,
120+
dynamoDbTableSchemaResolver.resolve(entity.getClass()));
120121
}
121122

122123
private <T> DynamoDbTable<T> prepareTable(Class<T> clazz) {
123124
Assert.notNull(clazz, "clazz is required");
124-
String tableName = dynamoDbTableNameResolver.resolve(clazz);
125-
return dynamoDbEnhancedClient.table(tableName, dynamoDbTableSchemaResolver.resolve(clazz, tableName));
125+
String tableName = tableNameResolver.resolve(clazz);
126+
return dynamoDbEnhancedClient.table(tableName, dynamoDbTableSchemaResolver.resolve(clazz));
126127
}
127128

128129
}

0 commit comments

Comments
 (0)