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

1.2 DynamoDb

1.3 Lombok

1.3.1 Lombok features

Feature Details
val Local variables are declared as final
var Mutable local variables
@Slf4J Creates an SLF4J logger
@Cleanup Will call close() on the resource in the finally block
@Getter Creates getter methods for all properties
@Setter Creates 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.

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.

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 –

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.

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,

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
Exit mobile version