AWS Database Blog

Integrate your Spring Boot application with Amazon DocumentDB (with MongoDB compatibility)

Amazon DocumentDB (with MongoDB compatibility) is a scalable, highly durable, and fully managed database service for operating mission-critical MongoDB workloads. You can use the same MongoDB application code, drivers, and tools to run, manage, and scale workloads on Amazon DocumentDB without having to worry about managing the underlying infrastructure.

Spring Boot provides a quick and easy way of building production-grade Spring Framework-based applications. To accomplish this, Spring Boot comes pre-packaged with auto configuration modules for most libraries typically used with the Spring Framework. In a nutshell, open-source Spring Boot adds auto-configuration on top of the Spring framework by following convention over configuration.

In this post, you explore the basics of integrating a Spring Boot application with Amazon DocumentDB using the Spring Data MongoDB API. You also create a sample data model and repository class to perform database operations.

Solution overview

The Spring Boot and Amazon DocumentDB configuration is relatively simple and only involves a few steps for configuration.

Spring Boot allows applications to interact with Amazon DocumentDB using the MongoTemplate class and MongoRepository interface. MongoTemplate follows the standard template pattern in Spring and provides a ready-to-go basic API to the underlying persistence engine. MongoTemplate provides ready-to-use APIs for operations like aggregations, streams, updates, and custom queries. MongoRepository follows the Spring Data-centric approach and comes with more flexible and simple API operations, based on the well-known create, read, update, and delete (CRUD) access patterns in all Spring Data projects.

For both the options, you begin by defining the dependency in the pom.xml for Maven projects.

This post focuses on interacting with Amazon DocumentDB using MongoRepository.

Prerequisites

You need the following prerequisites:

You may incur costs in your account related to Amazon DocumentDB and AWS Cloud9 resources. You can use the AWS Pricing Calculator to estimate the cost.

Create a Spring Boot application with the Spring Initializr

Use the following steps to create a new Spring Boot application project with Spring Data MongoDB support. As an alternative, you can use the spring-boot-docdb-sample application from the GitHub repo.

  1. Browse to https://start.spring.io/.
  2. Specify the following options:
    1. Choose Maven project with Language as Java.
    2. Select your Spring Boot version as 3.0.0.
    3. Specify the group and artifact names for your application.
    4. Specify Java version 17.
    5. Choose ADD DEPENDENCIES, and search and choose Spring Data MongoDB. You use the Spring Data MongoDB dependency to interact with the Amazon DocumentDB cluster from this application.
  3. Choose GENERATE to generate a Spring Boot project with all the necessary files for bootstrapping.
  4. Download the ZIP file to a path on your local machine and extract the files.

Spring initializr

If you’re using the AWS Cloud9 IDE, upload the ZIP file to the AWS Cloud9 environment and extract the files.

Verify Maven dependencies

Locate the pom.xml file in the directory of your application and verify that the Spring Data MongoDB dependencies are added as follows:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

Define your data model

To store and retrieve the data from an Amazon DocumentDB database, let’s create a POJO model or entity class. This entity represents a collection in Amazon DocumentDB and uses annotations to define the collection name, attributes, keys, and other aspects.

In this example, you create a products collection and a corresponding model object, which stores the details of products in a catalog database. You create a product model with six attributes: id, name, sku, description, inventory, and category.

Amazon DocumentDB stores data in collections. Spring Data maps the Product class or model to a collection called product by default. If you want to change the name of the collection, you can use Spring Data MongoDB’s @Document annotation on the class. In the following example, we use @Document(collection = "products") to specify the collection name as products.

You can specify the document’s primary key _id using the @Id annotation. If you don’t specify anything, Amazon DocumentDB generates an _id field while creating the document. The other attributes are left unannotated. It is assumed that they are mapped to fields that share the same name as the attributes themselves. Inside the project’s directory, create a file Product.java with the following contents:

@Document(collection = "products")
public class Product {

    @Id
    private String id;    
    
    /** 
     * Set up data Members that correspond to fields in the products collection
     */
     
    private String name;
    private String sku;
    private String description;
    private int inventory;
    private String category;
    

    /**
     * @param id
     * @param name
     * @param sku
     * @param description
     * @param inventory
     * @param category
     */
    public Product(String name, String sku, String description, int inventory, String category) {
        
        this.name = name;
        this.sku = sku;
        this.description = description;
        this.inventory = inventory;
        this.category = category;
    }
}

Add the getter and setter method for each attribute. You can generate the getter and setter method with IDE-specific shortcuts. For example, right-click on code editor pane, choose Refactoring, then choose Generate Getter and Setter in Cloud9.

Also, it is recommended to overwrite the toString method to print the object, For example, right-click on code editor pane, choose Refactoring, then choose Generate toString() in Cloud9.

Cloud9

Connect with TLS enabled

To connect to a TLS-enabled Amazon DocumentDB cluster from a Java-based Spring Boot application, your program must use the AWS-provided certificate authority (CA) file to validate the connection. To use the Amazon RDS CA certificate, do the following:

  1. Create a temporary certs folder under the tmp folder. You can create the folder for storing the certificates based on your organization’s security policies. For this post, create the certs folder under tmp:
    mkdir /tmp/certs/
  2. Create a trust store with the CA certificate contained in the file by running the following commands. Be sure to update <truststorePassword>. The following is a sample shell script that imports the certificate bundle into a trust store on a Linux operating system. For other options, see Connecting with TLS Enabled.
    mydir=/tmp/certs
    truststore=${mydir}/rds-truststore.jks
    storepassword=<truststorePassword>
    
    curl -sS "https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem" > ${mydir}/rds-combined-ca-bundle.pem
    awk 'split_after == 1 {n++;split_after=0} /-----END CERTIFICATE-----/ {split_after=1}{print > "rds-ca-" n ".pem"}' < ${mydir}/rds-combined-ca-bundle.pem
    
    for CERT in rds-ca-*; do
      alias=$(openssl x509 -noout -text -in $CERT | perl -ne 'next unless /Subject:/; s/.*(CN=|CN = )//; print')
      echo "Importing $alias"
      keytool -import -file ${CERT} -alias "${alias}" -storepass ${storepassword} -keystore ${truststore} -noprompt
      rm $CERT
    done
    
    rm ${mydir}/rds-combined-ca-bundle.pem
    
    echo "Trust store content is: "
    
    keytool -list -v -keystore "$truststore" -storepass ${storepassword} | grep Alias | cut -d " " -f3- | while read alias 
    do
       expiry=`keytool -list -v -keystore "$truststore" -storepass ${storepassword} -alias "${alias}" | grep Valid | perl -ne 'if(/until: (.*?)\n/) { print "$1\n"; }'`
       echo " Certificate ${alias} expires in '$expiry'" 
    done
    

Database and connection configurations

Two configuration files are required to set up the connection details and SSL trust store. First, you define your Amazon DocumentDB connection parameters, such as database, URI or host , port, user name, and password in application.properties. Then you set the keystore in your configuration class.

Configure the Spring Boot application properties file

To connect your Spring Boot application with Amazon DocumentDB, let’s define the database configuration in the application.properties file. You configure properties like database URI, database name or host name, user name, password, and other connection-related properties. To connect to the Amazon DocumentDB cluster, you specify the connection URI string in the application.properties file located in the src/main/resources folder.

To retrieve your Amazon DocumentDB cluster endpoint and configure application.properties, complete the following steps:

  1. On the Amazon DocumentDB console, choose Clusters in the navigation pane.
  2. Find your cluster and choose the Regional cluster identifier.
  3. On the Connectivity & security tab, copy the command for connecting to your cluster with an application.DocumentDB connection string
  4. Remove &ssl_ca_certs=rds-combined-ca-bundle.pem from the copied connection string, because you have already imported the AWS-provided CA file in to trust store.
  5. Add the connection URI to the application.properties file located in the src/main/resources folder in your project. The key for the connection URI is spring.data.mongodb.uri. Make sure your copied connection string is in the following format:
    spring.data.mongodb.uri=mongodb://<user name>:<password>@<cluster end point>: 27017/?ssl=true&replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false

    Alternatively, you can provide the connection details separately like host name, port, user name, and password with corresponding property keys, for example spring.data.mongodb.host or spring.data.mongodb.port. See Data Properties under Common Application Properties for a complete list of available Spring Boot parameter configuration options.

  6. Optionally, you can also specify the database name in the application.properties file :
    spring.data.mongodb.database=catalog

Create a configuration class and set the keystore

Now that the properties are configured, you need to define the configuration class for setting the keystore properties for establishing a secure connection.

Use the keystore in your application by setting the following system properties in your configuration class:

javax.net.ssl.trustStore: <truststore>
javax.net.ssl.trustStorePassword: <truststorePassword> 

Refer to the following example configuration class. Use the @Configuration annotation to mark the class as a configuration class. The class annotated with @Configuration is used by Spring containers as a source of bean definitions.

@Configuration
public class DocumentDBConf {
	
	private MongoProperties properties;
	
	public static final String KEY_STORE_TYPE = "/tmp/certs/rds-truststore.jks";
    	public static final String DEFAULT_KEY_STORE_PASSWORD = "changeit";

        public DocumentDBConf(final MongoProperties properties) {
            super();
            this.properties = properties;
        }

        @Bean
        public MongoClientSettings mongoClientSettings() { 
             setSslProperties();
	     return MongoClientSettings.builder()
                    .applyToSslSettings(builder -> builder.enabled(true))
                    .build();
	}

        private static void setSslProperties() { 
    	      System.setProperty("javax.net.ssl.trustStore", KEY_STORE_TYPE);
    	      System.setProperty("javax.net.ssl.trustStorePassword",           
                    DEFAULT_KEY_STORE_PASSWORD);
        }
	@Bean
        public MongoPropertiesClientSettingsBuilderCustomizer mongoPropertiesCustomizer(final MongoProperties properties) {
			return new MongoPropertiesClientSettingsBuilderCustomizer(properties);
	}
}

That is all for Amazon DocumentDB configuration in Spring Boot. Now you can start defining your interface-based repository class by extending MongoRepository.

Configure your repository interface

Now you use MongoRepository to access data from the database. MongoRepository provides common functionalities like create, read, update, and delete (CRUD) operations. It acts as a link between the model and the database. It takes the domain class (Product) to manage as well as the ID type of the domain class as type arguments. You can write a handful of methods and the queries are generated for you by the repository.

Create a repository interface that queries Product documents as the following:

public interface ProductRepository extends MongoRepository<Product, String> {

}

ProductRepository extends the MongoRepository interface. This interface comes with many operations, including standard CRUD operations. You define additional custom operations in the next sections.

Define Service Class

Service class takes advantage of the Spring Data repository interface. Let’s define it with a reference to your repository created in the previous step:

@Service
public class ProductService {
    
    @Autowired
    private ProductRepository productRepo;
}

You build upon this class in the next sections by adding additional methods, covering CRUD examples.

Using CrudRepository

When using MongoRepository, which extends CrudRepository, your repository has access to basic functionalities, including save, count, findAll, and delete methods from an implementation class of the CrudRepository interface. Your ProductRepository interface extends MongoRepository and has access to all basic CRUD operations.

Let’s explore and test each of these operations one by one.

Save or create

First, you add a few documents to the collection using the save method. The save method takes a Product object as input and saves the product document in an Amazon DocumentDB collection. Add the following code snippet to your service class:

public void saveProducts() {
 
	productRepo.save(new Product("RayBan Sunglass Pro", "1590234","RayBan Sunglasses for professional sports people", 100, "fashion"));
	productRepo.save(new Product("GUCCI Handbag", "3451290", "Fashion Hand bags for all ages", 75, "fashion"));
	productRepo.save(new Product("Round hat", "8976045", "", 200, "fashion"));
	productRepo.save(new Product("Polo shirt", "6497023", "Cool shirts for hot summer", 25, "cloth"));
	productRepo.save(new Product("Swim shorts", "8245352", "Designer swim shorts for athletes", 200, "cloth"));
	productRepo.save(new Product("Running shoes", "3243662", "Shoes for work out and trekking", 20, "footware"));
	
	System.out.println(" Save complete ");
        
}

Count

In next example, without any methods in your repository, you call the count() method in the service class to count the number of documents in the collection:

public long getCount() {
    long count = productRepo.count();
    System.out.println(" Total number of products : "+count);
    return count;
}

Read

In this example, you perform three different read operations. You fetch a product by name or SKU and find a list of products based on the category.

You add three simple methods in the repository (ProductRepository). The first method, findProductByName, queries the collection based on the name attribute. The query filter is defined with an annotation @Query and in our example, the annotation is @Query("{name:'?0'}"). The second method, findProductBySKU, queries the collection based on the sku attribute and projects only the name and inventory attributes in the query response. The third method, findAll, retrieves all the documents of a particular category. See the following code:

public interface ProductRepository extends MongoRepository<Product, String> {
    
    @Query("{name:'?0'}")
    Product findProductByName(String name);
    
    @Query(value="{sku:'?0'}", fields="{'name' : 1, 'inventory' : 1}")
    Product findProductBySKU (String sku);   

    @Query("{category:'?0'}")
    List<Product> findAllByCategory(String category);
                                                        
}

You invoke these three methods from the repository in the service class to find documents by name, SKU, and category.

public Product getProductByName(String name) {
	System.out.println(" Getting product by name : " + name);
	Product product = productRepo.findProductByName(name);
	System.out.println(product);
	return product; 
}

public Product getProductBySKU(String sku) {
	System.out.println(" Getting product by SKU : " + sku);
	Product product = productRepo.findProductBySKU(sku);
	System.out.println(product);
 	return product; 
}

public List<Product> findAllProductsByCategory(String category) {
	List<Product> productList = productRepo.findAllByCategory(category);
	productList.forEach(product -> System.out.println(product));
	return productList;
}

Update

You can update an existing document with the save method by passing the updated entity object. In this example, you query an existing product by SKU and increment the inventory by 10. Add the following method to your service:

public void updateInventory(String sku) {
	Product product =  getProductBySKU(sku);
	System.out.println(" Updating Inventory for product by sku: " + sku);	
	product.setInventory(product.getInventory()+10);
	Product updatedProd = productRepo.save(product);
}

Delete

In this example, you create two delete operations. First, you delete one product by ID; in the second method, you delete all documents (products) in the collection. Add the following methods to your service class:

public void deleteProduct(String id) {
 	productRepo.deleteById(id);
	System.out.println("Product with id " + id + " deleted");
}

public void deleteAll() {
	productRepo.deleteAll();
	System.out.println("All Products deleted.");
}

Build your Spring Boot application

The default Spring Boot application is already created by the Spring Initializr in your root package (for example, com.example.documentdb). Open the default application in your IDE:

@SpringBootApplication
public class DocumentdbApplication {

	public static void main(String[] args) {
		SpringApplication.run(DocumentdbApplication.class, args);
	}
}

The CommandLineRunner interface indicates that a bean should run when it is contained in a SpringApplication to view the output on the console. Implement the CommandLineRunner interface and provide the implantation for the run method. Let’s define it with a reference to your service with the @Autowired annotation. Spring initializes the application context using the @SpringBootApplication annotation:

@SpringBootApplication
public class DocumentdbApplication implements CommandLineRunner{

    @Autowired
    private ProductService prodService;

	public static void main(String[] args) {
		SpringApplication.run(DocumentdbApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		
		System.out.printf("%n Insert few products : %n");
		prodService.saveProducts();
		
		System.out.printf("%n Count all products : %n");
		prodService.getCount();
		
		
		System.out.printf("%n Get product by name : %n");
		prodService.getProductByName("GUCCI Handbag");
		
		System.out.printf("%n Get product by sku : %n");
		prodService.getProductBySKU("8976045");
		
		System.out.printf("%n Get all products by category : %n");
		prodService.findAllProductsByCategory("fashion");
		
		System.out.printf("%n Update Inventory for Product by sku :  %n");
		prodService.updateInventory("3451290");
		
		System.out.printf("%n Delete product id  %n");
		prodService.deleteProduct("639a0046efe46b7343dd5004"); 
		
		System.out.printf("%n Deleting all products/documents  %n");
		prodService.deleteAll(); 
	}
}

Run and test your application

Run your Spring Boot application with the following Maven command:

mvn spring-boot:run

The following screenshot is the sample output of your Spring Boot application.

Output

You’re done! You have successfully connected to Amazon DocumentDB from a Spring Boot application.

Conclusion

In this post, you learned about integrating Amazon DocumentDB with a Spring Boot application with a simple application that uses Spring Data MongoDB to save objects to and fetch them from a database, all without writing a concrete repository implementation and with simple configurations.

The example used in this post is available as a sample project on GitHub.

If you have questions or suggestions, leave a comment.


About the Author

Gururaj S BayariGururaj S Bayari is a Senior DocumentDB Specialist Solutions Architect at AWS. He enjoys helping customers adopt Amazon’s purpose-built databases. He helps customers design, evaluate, and optimize their internet scale and high performance workloads powered by NoSQL and/or Relational databases.