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:
- An Amazon DocumentDB cluster – This post begins with an existing Amazon DocumentDB cluster that you integrate with Spring Boot application. If you don’t already have an Amazon DocumentDB cluster, see Get Started with Amazon DocumentDB to create a new cluster.
- An integrated development environment (IDE) – For example, Eclipse or AWS Cloud9. This post use AWS Cloud9 for demonstration, which is a cloud-based IDE that lets you write, run, and debug your code with just a browser. It includes a code editor, debugger, and terminal. Enable Enhanced support for Java development to improve your development experience when working with Java.
- Java 17 – See Amazon Corretto 17 Installation Instructions to install or upgrade Java in AWS Cloud9.
- Maven – See Set up with Maven to install Maven in AWS Cloud9.
- Spring Initializr
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.
- Browse to https://start.spring.io/.
- Specify the following options:
- Choose Maven project with Language as Java.
- Select your Spring Boot version as 3.0.0.
- Specify the group and artifact names for your application.
- Specify Java version 17.
- 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.
- Choose GENERATE to generate a Spring Boot project with all the necessary files for bootstrapping.
- Download the ZIP file to a path on your local machine and extract the files.
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.
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:
- Create a temporary
certs
folder under thetmp
folder. You can create the folder for storing the certificates based on your organization’s security policies. For this post, create thecerts
folder undertmp
:mkdir /tmp/certs/
- 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:
- On the Amazon DocumentDB console, choose Clusters in the navigation pane.
- Find your cluster and choose the Regional cluster identifier.
- On the Connectivity & security tab, copy the command for connecting to your cluster with an application.
- 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. - Add the connection URI to the
application.properties
file located in thesrc/main/resources
folder in your project. The key for the connection URI isspring.data.mongodb.uri
. Make sure your copied connection string is in the following format: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
orspring.data.mongodb.port
. See Data Properties under Common Application Properties for a complete list of available Spring Boot parameter configuration options. - Optionally, you can also specify the database name in the
application.properties
file :
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:
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.
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 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.