Boot

Spring Boot CRUD with AWS DynamoDB

Welcome, in this tutorial, we will see how to configure a spring boot application to use the localhost DynamoDB instance using the spring data. As the DynamoDb AWS service incur changes after frequent HTTP requests so in this tutorial we will use the dynamodb-local on docker (provided by AWS) for learning purpose.

1. Introduction

Before going further in this tutorial, we will look at the common terminology such as introduction to Spring Boot, DynamoDb, and Lombok.

1.1 Spring Boot

  • Spring boot is a module that provides rapid application development feature to the spring framework including auto-configuration, standalone-code, and production-ready code
  • It creates applications that are packaged as jar and are directly started using embedded servlet container (such as Tomcat, Jetty or, Undertow). Thus, no need to deploy the war files
  • It simplifies the maven configuration by providing the starter template and helps to resolve the dependency conflicts. It automatically identifies the required dependencies and imports them into the application
  • It helps in removing the boilerplate code, extra annotations, and XML configurations
  • It provides powerful batch processing and manages the rest endpoints
  • It provides an efficient JPA-starter library to effectively connect the application with the relational databases
  • It offers a Microservice architecture and cloud configuration that manages all the application related configuration properties in a centralized manner

1.2 DynamoDb

  • DynamoDb is a fast and flexible NoSQL database service for all applications that need consistent, single-digit ms latency
  • It is a fully managed cloud database and supports both document and key-value store
  • It also offers a flexible data model, reliable performance, and automatic scaling of the throughput capacity making it the best fit for applications that have inconsistent data for storage
  • It is similar to other NoSQL databases like Cassandra or MongoDB

1.3 Lombok

  • Lombok is nothing but a small library that reduces the amount of boilerplate Java code from the project
  • Automatically generates the getters and setters for the object by using the Lombok annotations
  • Hooks in via the Annotation processor API
  • Raw source code is passed to Lombok for code generation before the Java Compiler continues. Thus, produces properly compiled Java code in conjunction with the Java Compiler
  • Under the target/classes folder you can view the compiled class files
  • Can be used with Maven, Gradle IDE, etc.

1.3.1 Lombok features

FeatureDetails
valLocal variables are declared as final
varMutable local variables
@Slf4JCreates an SLF4J logger
@CleanupWill call close() on the resource in the finally block
@GetterCreates getter methods for all properties
@SetterCreates setter for all non-final properties
@EqualsAndHashCode
  • Generates implementations of equals(Object other) and hashCode()
  • By default will use all non-static, non-transient properties
  • Can optionally exclude specific properties
@ToString
  • Generates String of class name, and each field separated by commas
  • Optional parameter to include field names
  • Optional parameter to include a call to the super toString method
@NoArgsConstructor
  • Generates no-args constructor
  • Will cause compiler error if there are final fields
  • Can optionally force, which will initialize final fields with 0/false/null var – mutable local variables
@RequiredArgsContructor
  • Generates a constructor for all fields that are final or marked @NonNull
  • The constructor will throw a NullPointerException if any @NonNull fields are null val – local variables are declared final
@AllArgsConstructor
  • Generates a constructor for all properties of the class
  • Any @NotNull properties will have null checks
@Data
  • Generates typical boilerplate code for POJOs
  • Combines – @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor
  • No constructor is generated if constructors have been explicitly declared
@Builder
  • Implements the Builder pattern for object creation
@Value
  • The immutable variant of @Data
  • All fields are made private and final by default

 

Let us go ahead with the tutorial implementation but before going any further I’m assuming that you’re aware of the Spring boot basics.

2. Spring Boot CRUD with AWS DynamoDB

2.1 Application Pre-requisite

To start with this Spring Boot CRUD with AWS DynamoDB tutorial, I am hoping you have the dynamodb-local up and running in your localhost environment. For easy setup, I have the dynamodb-local and dynamodb-admin-gui up and running on the Docker environment. You can execute the below script using the docker-compose command to get the dynamodb-local and dynamodb-admin-gui containers running on Docker in minutes. If you’re doing it for the first time the docker image will be downloaded from the docker hub.

docker-compose.yml

services:
  dynamodb-local-admin-gui:
    container_name: dynamodb-local-admin-gui
    image: instructure/dynamo-local-admin
    ports:
      - '8000:8000'
version: '3.7'

If everything goes well the dynamodb-local and dynamodb-admin-gui containers would be started successfully as shown in Fig. 1. You can use the docker ps -a command to confirm that the containers are started successfully. For further information on docker basics, you can navigate to this tutorial.

Spring Boot AWS DynamoDB - admin gui containers on Docker
Fig. 1: Dynamodb and Dynamodb admin gui containers on Docker

2.2 Tools Used for Spring boot application and Project Structure

We are using Eclipse Kepler SR2, JDK 8, and Maven. In case you’re confused about where you should create the corresponding files or folder, let us review the project structure of the spring boot application.

Spring Boot AWS DynamoDB - Project structure
Fig. 2: Project structure

Let us start building the application!

3. Create a table in Dynamodb

Once the vault server is up and running, head over to the administration console by typing the following address in the browser – http://localhost:8080/. The administration console will open having the Create table button. Click on the button and enter the details as shown in Fig. 3.

Fig. 3: Enter table details

Once done click on the Submit button. If everything goes well the table (named – books) would be created as shown in Fig. 4.

Fig. 4: Table created

4. Creating a Spring Boot application

Below are the steps involved in developing the application.

4.1 Maven Dependency

Here, we specify the dependency for the Spring boot (Web), Java Faker, AWS Dynamodb SDK, Lombok, and Spring Data JPA (to perform the crud operations). Maven will automatically resolve the other dependencies. The updated file will have the following code.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.springboot.dynamodb</groupId>
    <artifactId>SpringbootandDynamodb</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringbootandDynamodb</name>
    <description>Springboot and Dynamodb</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- aws-java-dynamodb-sdk -->
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-dynamodb</artifactId>
            <version>1.11.926</version>
        </dependency>
        <!-- spring-data-dynamodb-support -->
        <dependency>
            <groupId>com.github.derjust</groupId>
            <artifactId>spring-data-dynamodb</artifactId>
            <version>5.1.0</version>
        </dependency>
        <!-- java-faker -->
        <dependency>
            <groupId>com.github.javafaker</groupId>
            <artifactId>javafaker</artifactId>
            <version>1.0.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

4.2 Application YML file

Create a new yml file at the location: SpringbootandDynamodb/src/main/resources/ and add the following code to it. Here we will define the application and aws dynamodb configuration. In this tutorial as we’re using the localhost instance of dynamodb so, we will pass the static information but in the real world, this would be replaced with the actual AWS configuration.

application.yml

amazon:
  aws:
    accesskey: key
    region: us-east-1
    secretkey: ''
  dynamodb:
    endpoint: 'http://localhost:8000/'
server:
  port: 9500
spring:
  application:
    name: springboot-aws-dynamodb

4.3 Java Classes

Let us write the important java class(es) involved in this application. For brevity, we will skip the following classes –

  • DefaultBooksLoader.java – Bootstrap class to populate dummy data to the dynamodb table (named – books) once the application is started successfully. You can use the get-all-books endpoint to fetch all the books
  • BookService.java – Service class that provide some business facilities and interact with the DAO layer methods
  • EntityNotFound.java – Exception class to return the 404 status code if an item does not exist in the database
  • BookDto.java – Model class used in the controller to map the incoming request body attributes during the POST and PUT operations

4.3.1 Implementation/Main class

Add the following code to the main class to bootstrap the application from the main method. Always remember, the entry point of the spring boot application is the class containing @SpringBootApplication annotation and the static main method.

SpringbootandDynamodbApplication.java

package com.springboot.dynamodb;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

//lombok annotation for logger
@Slf4j
//spring annotation
@SpringBootApplication
public class SpringbootandDynamodbApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootandDynamodbApplication.class, args);
        log.info("Springboot and dynamodb application started successfully.");
    }
}

4.3.2 Configuration class

Add the following code to the configuration class. The class will be annotated with the @EnableDynamoDBRepositories and will contain the @Bean annotated methods to create the AmazonDynamodbDB instance.

BeanConfig.java

package com.springboot.dynamodb.config;

import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.github.javafaker.Faker;
import org.socialsignin.spring.data.dynamodb.repository.config.EnableDynamoDBRepositories;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Locale;

@Configuration
//annotation enables the dynamodb repositories
@EnableDynamoDBRepositories(basePackages = "com.springboot.dynamodb.repo")
public class BeanConfig {

    @Value("${amazon.dynamodb.endpoint}")
    String endpoint;
    @Value("${amazon.aws.accesskey}")
    String accesskey;
    @Value("${amazon.aws.secretkey}")
    String secretkey;
    @Value("${amazon.aws.region}")
    String region;

    public AwsClientBuilder.EndpointConfiguration endpointConfiguration() {
        return new AwsClientBuilder.EndpointConfiguration(endpoint, region);
    }

    public AWSCredentialsProvider awsCredentialsProvider() {
        return new AWSStaticCredentialsProvider(new BasicAWSCredentials(accesskey, secretkey));
    }

    @Bean
    public AmazonDynamoDB amazonDynamoDB() {
        return AmazonDynamoDBClientBuilder
                .standard()
                .withEndpointConfiguration(endpointConfiguration())
                .withCredentials(awsCredentialsProvider())
                .build();
    }

    @Bean
    public Faker faker() {
        return new Faker(new Locale("en-US"));
    }
}

4.3.3 Entity class

Add the following code to the model class that will be stored in the dynamodb.

Book.java

package com.springboot.dynamodb.entity;

import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAutoGeneratedKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

//annotation validates if the dynamodb table exists or not
//if not throws 'ResourceNotFoundException'
//note - dynamodb does not create collection automatically like mongodb so it
//is important to create dynamodb before hand
@DynamoDBTable(tableName = "books")
//lombok annotations
//annotation helps to generate toString(), equals(), hashcode(), getter(), setter()
@Data
//annotation helps to generate a no-argument constructor
@NoArgsConstructor
//annotation helps to generate a constructor with 1 parameter for each field in the class
@AllArgsConstructor
//annotation helps to implement the builder design pattern
//usage can be seen in BookService.java
@Builder
//spring stereotype annotation
@Component
public class Book {

	//annotation for marking the property as the hashkey
	@DynamoDBHashKey(attributeName = "id")
	//annotation for making the hashkey property to autogenerate
	//the key. supports string datatype only
	@DynamoDBAutoGeneratedKey
	String id;
	//describes the field name as it will be represented in dynamodb table
	//offers the name to be different than the field name of the class
	@DynamoDBAttribute
	String title;
	@DynamoDBAttribute
	String author;
	@DynamoDBAttribute
	String genre;
	@DynamoDBAttribute
	String publisher;
	@DynamoDBAttribute
	int quantity;
}

4.3.4 Repository interface

Add the following code to the repository interface to define the SQL CRUD functionality.

BookRepository.java

package com.springboot.dynamodb.repo;

import com.springboot.dynamodb.entity.Book;
import org.socialsignin.spring.data.dynamodb.repository.EnableScan;
import org.socialsignin.spring.data.dynamodb.repository.EnableScanCount;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

//annotation enables the scan operations
@EnableScan
//spring annotation
@Repository
public interface BookRepository extends CrudRepository<Book, String> {

    @EnableScanCount
    long countByGenre(String genre);

    List<Book> findAllByGenre(String genre);
}

4.3.5 Controller class

Add the following code to the controller class. The class is injected with the service dependency whose method will call the DAO layer methods to persist the data into the database or fetch from it.

BookController.java

package com.springboot.dynamodb.controller;

import com.springboot.dynamodb.entity.Book;
import com.springboot.dynamodb.entity.BookDto;
import com.springboot.dynamodb.exception.EntityNotFound;
import com.springboot.dynamodb.service.BookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.List;

//lombok annotation for logger
@Slf4j
//spring annotations
@RestController
@RequestMapping("/api")
public class BookController {

    @Autowired
    BookService service;

    // HTTP GET URL - http://localhost:9500/api/books
    @GetMapping("/books")
    @ResponseStatus(HttpStatus.OK)
    public List<Book> getBooks() {
        log.info("Getting all books from the db");
        return service.getBooks();
    }

    // HTTP GET URL - http://localhost:9500/api/books/<book_genre>
    @GetMapping("/books/{genre}")
    @ResponseStatus(HttpStatus.OK)
    public List<Book> getBooksByGenre(@PathVariable("genre") final String genre) {
        log.info("Getting books by genre = {} from the db", genre);
        return service.getBooksByGenre(genre);
    }

    // HTTP GET URL - http://localhost:9500/api/book/<book_id>
    @GetMapping("/book/{id}")
    @ResponseStatus(HttpStatus.OK)
    public Book getBookById(@PathVariable("id") final String id) throws EntityNotFound {
        log.info("Getting book id = {} from the db", id);
        return service.getBookById(id);
    }

    // HTTP DELETE URL - http://localhost:9500/api/book/<book_id>
    @DeleteMapping("/book/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteBook(@PathVariable("id") final String id) throws EntityNotFound {
        log.info("Delete book id = {} from the db", id);
        service.delete(id);
    }

    // HTTP PUT URL  - http://localhost:9500/api/book/<book_id>
    // Sample request body
    /*
    {
        "author": "J. K. Rowling",
        "genre": "Fantasy Fiction",
        "publisher": "Bloomsbury Publishing",
        "title": "Harry Potter",
        "quantity": 100
    }
    */
    @PutMapping("/book/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void update(@PathVariable("id") final String id, @RequestBody final BookDto dto)
            throws EntityNotFound {
        log.info("Updating book id = {} into the db", id);
        service.update(id, dto);
    }

    // HTTP POST URL  - http://localhost:9500/api/book
    // Sample request body
    /*
    {
        "author": "Vasdev Mohi",
        "genre": "Ghazals",
        "publisher": "Central Sahitya Akademi",
        "title": "Cheque book",
        "quantity": 5
    }
    */
    @PostMapping("/book")
    @ResponseStatus(HttpStatus.CREATED)
    public void save(@RequestBody final BookDto dto) {
        log.info("Saving new book = {} into the db", dto.toString());
        service.save(dto);
    }

    // HTTP POST URL - http://localhost:9500/api/books/count/<book_genre>
    @GetMapping("/books/count/{genre}")
    @ResponseStatus(HttpStatus.OK)
    public long getCountByGenre(@PathVariable("genre") final String genre) {
        return service.getCountByGenre(genre);
    }
}

5. Run the Application

To execute the application, right-click on the SpringbootandDynamodbApplication.java class, Run As -> Java Application.

Spring Boot AWS DynamoDB - Run the Application
Fig. 5: Run the Application

6. Project Demo

When the application is started, open the Postman tool to hit the application endpoints to persist the data into the database or fetch from it. You are free to use any other tool of your choice to make the post and get requests to the endpoints.

Application endpoints

-- HTTP GET endpoint (to fetch all the books) –
http://localhost:9500/api/books

-- HTTP GET endpoint (to fetch all books by genre) --
http://localhost:9500/api/books/<book_genre>

-- HTTP GET endpoint (to fetch book by id) --
http://localhost:9500/api/book/<book_id>

-- HTTP DELETE endpoint (to delete book by id) –
http://localhost:9500/api/book/<book_id>

-- HTTP PUT endpoint (to update an existing book into the database) –
http://localhost:9500/api/book/<book_id>

-- sample request body –
{
        "author": "Harry Potter",
        "genre": "Fantasy Fiction",
        "publisher": "Bloomsbury Publishing",
        "title": "J. K. Rowling",
        "quantity": 1,
}

-- HTTP POST endpoint (to save a new book into the database) –
http://localhost:9500/api/book

-- sample request body –
{
        "author": "Vasdev Mohi",
        "genre": "Ghazals",
        "publisher": "Central Sahitya Akademi",
        "title": "Cheque book",
        "quantity": 1,
}

-- HTTP GET endpoint (to fetch books count by genre) --
http://localhost:9500/api/books/count/<book_genre>

That is all for this tutorial and I hope the article served you whatever you were looking for. Happy Learning and do not forget to share!

7. Summary

In this section, you learned,

  • Spring boot, Lombok, and Dynamodb introduction
  • Steps to setup dynamodb and dynamodb-admin-gui on docker
  • Steps to create a dynamodb table through admin-gui
  • Steps to configure dynamodb in a spring-boot application through spring data

You can download the sample application as an Eclipse project in the Downloads section.

8. Download the Project

This was an example of how to configure Dynamodb in a Spring Boot application.

Download
You can download the full source code of this example here: Spring Boot CRUD with AWS DynamoDB

Yatin

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Srihari Sridharan
Srihari Sridharan
3 years ago

Hi,

The dynamodb admin GUI is running in port http://localhost:8000/, it is stated as http://localhost:8080/ in Section 3. Kindly confirm. Thanks.

Back to top button