I usually post about Persistence on Twitter - you can follow me there:

1. Overview

In this article, we’ll explore the basics of integrating DynamoDB into a Spring Boot Application with a hands-on, practical example project.

We’ll demonstrate how to configure an application to use a local DynamoDB instance using Spring Data. We’ll also create an example data model and repository class as well as perform actual database operations using an integration test.

2. DynamoDB

DynamoDB is a fully-managed hosted NoSQL database on AWS, similar to other NoSQL databases such as Cassandra or MongoDB. DynamoDB offers fast, consistent and predictable performance and is massively scalable.

You can learn more about DynamoDB on the AWS Documentation.

Let’s install a local instance of DynamoDB to avoid incurring the cost of running a live instance.

For development, running DynamoDB locally makes more sense than running on AWS; the local instance will be run as an executable JAR file.

You can find instructions on how to run DynamoDB locally here.

3. Maven Dependencies

Add the following dependencies to start working with DynamoDB using Spring Data:

<dependencyManagement>
    <dependencies>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-releasetrain</artifactId>
        <version>Hopper-SR10</version>
        <type>pom</type>
        <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-dynamodb</artifactId>
        <version>1.11.34</version>
    </dependency>
    <dependency>
        <groupId>com.github.derjust</groupId>
        <artifactId>spring-data-dynamodb</artifactId>
        <version>4.3.1</version>
    </dependency>
</dependencies>

4. Configuration

Next, let’s define the following properties in the application.properties file:

amazon.dynamodb.endpoint=http://localhost:8000/
amazon.aws.accesskey=key
amazon.aws.secretkey=key2

The access and secret keys listed above are just arbitrary values for your local config. When accessing a local instance of DynamoDB these fields need to be populated by some values but are not needed to actually authenticate.

The properties will be dynamically pulled out of the application.properties file in the Spring config:

@Configuration
@EnableDynamoDBRepositories
  (basePackages = "com.baeldung.spring.data.dynamodb.repositories")
public class DynamoDBConfig {

    @Value("${amazon.dynamodb.endpoint}")
    private String amazonDynamoDBEndpoint;

    @Value("${amazon.aws.accesskey}")
    private String amazonAWSAccessKey;

    @Value("${amazon.aws.secretkey}")
    private String amazonAWSSecretKey;

    @Bean
    public AmazonDynamoDB amazonDynamoDB() {
        AmazonDynamoDB amazonDynamoDB 
          = new AmazonDynamoDBClient(amazonAWSCredentials());
        
        if (!StringUtils.isEmpty(amazonDynamoDBEndpoint)) {
            amazonDynamoDB.setEndpoint(amazonDynamoDBEndpoint);
        }
        
        return amazonDynamoDB;
    }

    @Bean
    public AWSCredentials amazonAWSCredentials() {
        return new BasicAWSCredentials(
          amazonAWSAccessKey, amazonAWSSecretKey);
    }
}

5. The Data Model

Let’s now create a POJO model to represent the data stored in DynamoDB.

This POJO will use annotations similar to those used in Hibernate to define the table name, attributes, keys and other aspects of the table.

5.1. Data Model Attributes

The following class, ProductInfo, represents a table with items that contains 3 attributes:

  1. ID
  2. MSRP
  3. Cost

5.2 Java Data Model Class

Let’s create a file called ProductInfo.java in your data model folder:

@DynamoDBTable(tableName = "ProductInfo")
public class ProductInfo {
    private String id;
    private String msrp;
    private String cost;

    @DynamoDBHashKey
    @DynamoDBAutoGeneratedKey
    public String getId() {
        return id;
    }

    @DynamoDBAttribute
    public String getMsrp() {
        return msrp;
    }

    @DynamoDBAttribute
    public String getCost() {
        return cost;
    }

    // standard setters/constructors
}

6. CRUD Repository

Next, we need to create a ProductRepository interface to define the CRUD functionality we want to build out. Repositories used to read and persist data to and from DynamoDB will implement this interface:

@EnableScan
public interface ProductInfoRepository extends 
  CrudRepository<ProductInfo, String> {
    
    List<ProductInfo> findById(String id);
}

7. Integration Test

Next, let’s create an integration test to ensure we can successfully connect to the local instance of DynamoDB:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
@WebAppConfiguration
@ActiveProfiles("local")
@TestPropertySource(properties = { 
  "amazon.dynamodb.endpoint=http://localhost:8000/", 
  "amazon.aws.accesskey=test1", 
  "amazon.aws.secretkey=test231" })
public class ProductInfoRepositoryIntegrationTest {

    private DynamoDBMapper dynamoDBMapper;

    @Autowired
    private AmazonDynamoDB amazonDynamoDB;

    @Autowired
    ProductInfoRepository repository;

    private static final String EXPECTED_COST = "20";
    private static final String EXPECTED_PRICE = "50";

    @Before
    public void setup() throws Exception {
        dynamoDBMapper = new DynamoDBMapper(amazonDynamoDB);
        
        CreateTableRequest tableRequest = dynamoDBMapper
          .generateCreateTableRequest(ProductInfo.class);
        tableRequest.setProvisionedThroughput(
          new ProvisionedThroughput(1L, 1L));
        amazonDynamoDB.createTable(tableRequest);
        
        //...

        dynamoDBMapper.batchDelete(
          (List<ProductInfo>)repository.findAll());
    }

    @Test
    public void sampleTestCase() {
        ProductInfo dave = new ProductInfo(EXPECTED_COST, EXPECTED_PRICE);
        ProductInfoRepository.save(dave);

        List<ProductInfo> result 
          = (List<ProductInfo>) repository.findAll();
        
        assertTrue("Not empty", result.size() > 0);
        assertTrue("Contains item with expected cost", 
          result.get(0).getCost().equals(EXPECTED_COST));
    }
}

8. Conclusion

And we’re done – we can now connect to DynamoDB from a Spring Boot Application.

Of course, after completing testing locally, we should be able to transparently use a live instance of DynamoDB on AWS and run the deployed code with only minor configuration changes.

As always, the example used in this article is available as a sample project over on GitHub.

I usually post about Persistence on Twitter - you can follow me there:


Sort by:   newest | oldest | most voted
Pop Vasile
Guest

Can you start DynamoDB instance locally automatically from Spring context, from integration test?

Grzegorz Piwowarek
Guest

If you need to have a local DynamoDB instance, http://stackoverflow.com/a/37780083/2229438 this should help

Felipe Pacheco
Guest

Help!, I’ve just cloned the project and the method in the repository:

List findById(String id);

Is throwing:
Caused by: java.lang.AbstractMethodError: org.socialsignin.spring.data.dynamodb.repository.query.DynamoDBQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(Ljava/lang/reflect/Method;Lorg/springframework/data/repository/core/RepositoryMetadata;Lorg/springframework/data/repository/core/NamedQueries;)Lorg/springframework/data/repository/query/RepositoryQuery;

Grzegorz Piwowarek
Guest

Are you running any particular test? It will be hard to debug it without crucial info

Grzegorz Piwowarek
Guest

It’s fixed now. There was a version mismatch: https://github.com/eugenp/tutorials/pull/1720

Abhishek Kumar Singh
Guest

I downloaded the project from the repository and when I run the integration test i keep getting this error
Error creating bean with name ‘productInfoRepository’: Invocation of init method failed; nested exception is java.lang.AbstractMethodError: org.socialsignin.spring.data.dynamodb.repository.query.DynamoDBQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(Ljava/lang/reflect/Method;Lorg/springframework/data/repository/core/RepositoryMetadata;Lorg/springframework/data/repository/core/NamedQueries;)Lorg/springframework/data/repository/query/RepositoryQuery;

Grzegorz Piwowarek
Guest

Thanks, we will have a look

Grzegorz Piwowarek
Guest

We managed to reproduce this one and fix by simply updating versions of artifacts: https://github.com/eugenp/tutorials/pull/1720

wpDiscuz