Persisting data with MongoDB

duration 25 minutes

Prerequisites:

Learn how to persist data in your microservices to MongoDB, a document-oriented NoSQL database.

What you’ll learn

You will learn how to use MongoDB to build and test a simple microservice that manages the members of a crew. The microservice will respond to POST, GET, PUT, and DELETE requests that manipulate the database.

The crew members will be stored in MongoDB as documents in the following JSON format:

{
  "_id": {
    "$oid": "5dee6b079503234323db2ebc"
  },
  "Name": "Member1",
  "Rank": "Captain",
  "CrewID": "000001"
}

This microservice connects to MongoDB by using Transport Layer Security (TLS) and injects a MongoDatabase instance into the service with a Contexts and Dependency Injection (CDI) producer. Additionally, MicroProfile Config is used to easily configure the MongoDB driver.

For more information about CDI and MicroProfile Config, see the guides on Injecting dependencies into microservices and Separating configuration from code in microservices.

Additional prerequisites

You will use Docker to run an instance of MongoDB for a fast installation and setup. Install Docker by following the instructions in the official Docker documentation, and start your Docker environment.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-mongodb-intro.git
cd guide-mongodb-intro

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Before you begin, make sure you have all the necessary prerequisites.

Setting up MongoDB

This guide uses Docker to run an instance of MongoDB. A multi-stage Dockerfile is provided for you. This Dockerfile uses the mongo image as the base image of the final stage and gathers the required configuration files. The resulting mongo image runs in a Docker container, and you must set up a new database for the microservice. Lastly, the truststore that’s generated in the Docker image is copied from the container and placed into the Open Liberty server.

You can find more details and configuration options on the MongoDB website. For more information about the mongo image, see mongo in Docker Hub.

Running MongoDB in a Docker container

Run the following commands to use the Dockerfile to build the image, run the image in a Docker container, and map port 27017 from the container to your host machine:

docker build -t mongo-sample -f assets/Dockerfile .
docker run --name mongo-guide -p 27017:27017 -d mongo-sample

Adding the truststore to the Open Liberty server

The truststore that’s created in the container needs to be added to the Open Liberty server so that the server can trust the certificate that MongoDB presents when they connect. Run the following command to copy the truststore.p12 file from the container to the start and finish directories:

docker cp ^
  mongo-guide:/home/mongodb/certs/truststore.p12 ^
  start/src/main/liberty/config/resources/security
docker cp ^
  mongo-guide:/home/mongodb/certs/truststore.p12 ^
  finish/src/main/liberty/config/resources/security
docker cp \
  mongo-guide:/home/mongodb/certs/truststore.p12 \
  start/src/main/liberty/config/resources/security
docker cp \
  mongo-guide:/home/mongodb/certs/truststore.p12 \
  finish/src/main/liberty/config/resources/security
docker cp \
  mongo-guide:/home/mongodb/certs/truststore.p12 \
  start/src/main/liberty/config/resources/security
docker cp \
  mongo-guide:/home/mongodb/certs/truststore.p12 \
  finish/src/main/liberty/config/resources/security

Try what you’ll build

The finish directory in the root of this guide contains the finished application. Give it a try before you proceed.

To try out the application, first go to the finish directory and run the following Maven goal to build the application and deploy it to Open Liberty:

cd finish
mvn liberty:run

After you see the following message, your application server is ready:

The defaultServer server is ready to run a smarter planet.

You can now check out the service by going to the http://localhost:9080/mongo/ URL.

After you are finished checking out the application, stop the Open Liberty server by pressing CTRL+C in the command-line session where you ran the server. Alternatively, you can run the liberty:stop goal from the finish directory in another shell session:

mvn liberty:stop

Providing a MongoDatabase

Navigate to the start directory to begin.

When you run Open Liberty in development mode, known as dev mode, the server listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following goal to start Open Liberty in dev mode:

mvn liberty:dev

After you see the following message, your application server in dev mode is ready:

**************************************************************
*    Liberty is running in dev mode.

Dev mode holds your command-line session to listen for file changes. Open another command-line session to continue, or open the project in your editor.

With a CDI producer, you can easily provide a MongoDatabase to your microservice.

Create the MongoProducer class.
src/main/java/io/openliberty/guides/mongo/MongoProducer.java

MongoProducer.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2020, 2022 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License v1.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-v10.html
  8 *
  9 * Contributors:
 10 *     IBM Corporation - Initial implementation
 11 *******************************************************************************/
 12// end::copyright[]
 13package io.openliberty.guides.mongo;
 14
 15import jakarta.enterprise.context.ApplicationScoped;
 16import jakarta.enterprise.inject.Disposes;
 17import jakarta.enterprise.inject.Produces;
 18import jakarta.inject.Inject;
 19import javax.net.ssl.SSLContext;
 20
 21import com.ibm.websphere.ssl.JSSEHelper;
 22import com.ibm.websphere.ssl.SSLException;
 23import org.eclipse.microprofile.config.inject.ConfigProperty;
 24
 25import com.ibm.websphere.crypto.PasswordUtil;
 26import com.mongodb.MongoClient;
 27import com.mongodb.MongoClientOptions;
 28import com.mongodb.MongoCredential;
 29import com.mongodb.ServerAddress;
 30import com.mongodb.client.MongoDatabase;
 31
 32import java.util.Collections;
 33
 34@ApplicationScoped
 35public class MongoProducer {
 36
 37    // tag::mongoProducerInjections[]
 38    @Inject
 39    @ConfigProperty(name = "mongo.hostname", defaultValue = "localhost")
 40    String hostname;
 41
 42    @Inject
 43    @ConfigProperty(name = "mongo.port", defaultValue = "27017")
 44    int port;
 45
 46    @Inject
 47    @ConfigProperty(name = "mongo.dbname", defaultValue = "testdb")
 48    String dbName;
 49
 50    @Inject
 51    @ConfigProperty(name = "mongo.user")
 52    String user;
 53
 54    @Inject
 55    @ConfigProperty(name = "mongo.pass.encoded")
 56    String encodedPass;
 57    // end::mongoProducerInjections[]
 58
 59    // tag::produces1[]
 60    @Produces
 61    // end::produces1[]
 62    // tag::createMongo[]
 63    public MongoClient createMongo() throws SSLException {
 64        // tag::decode[]
 65        String password = PasswordUtil.passwordDecode(encodedPass);
 66        // end::decode[]
 67        // tag::createCredential[]
 68        MongoCredential creds = MongoCredential.createCredential(
 69                user,
 70                dbName,
 71                password.toCharArray()
 72        );
 73        // end::createCredential[]
 74
 75        // tag::sslContext[]
 76        SSLContext sslContext = JSSEHelper.getInstance().getSSLContext(
 77                // tag::outboundSSLContext[]
 78                "outboundSSLContext",
 79                // end::outboundSSLContext[]
 80                Collections.emptyMap(),
 81                null
 82        );
 83        // end::sslContext[]
 84
 85        // tag::mongoClient[]
 86        return new MongoClient(
 87                // tag::serverAddress[]
 88                new ServerAddress(hostname, port),
 89                // end::serverAddress[]
 90                // tag::creds[]
 91                creds,
 92                // end::creds[]
 93                // tag::optionsBuilder[]
 94                new MongoClientOptions.Builder()
 95                        .sslEnabled(true)
 96                        .sslContext(sslContext)
 97                        .build()
 98                // end::optionsBuilder[]
 99        );
100        // end::mongoClient[]
101    }
102    // end::createMongo[]
103
104    // tag::produces2[]
105    @Produces
106    // end::produces2[]
107    // tag::createDB[]
108    public MongoDatabase createDB(
109            // tag::injectMongoClient[]
110            MongoClient client) {
111            // end::injectMongoClient[]
112        // tag::getDatabase[]
113        return client.getDatabase(dbName);
114        // end::getDatabase[]
115    }
116    // end::createDB[]
117
118    // tag::close[]
119    public void close(
120            // tag::disposes[]
121            @Disposes MongoClient toClose) {
122            // end::disposes[]
123        // tag::toClose[]
124        toClose.close();
125        // end::toClose[]
126    }
127    // end::close[]
128}

microprofile-config.properties

 1# tag::hostname[]
 2mongo.hostname=localhost
 3# end::hostname[]
 4# tag::port[]
 5mongo.port=27017
 6# end::port[]
 7# tag::dbname[]
 8mongo.dbname=testdb
 9# end::dbname[]
10# tag::mongoUser[]
11mongo.user=sampleUser
12# end::mongoUser[]
13# tag::passEncoded[]
14mongo.pass.encoded={aes}APtt+/vYxxPa0jE1rhmZue9wBm3JGqFK3JR4oJdSDGWM1wLr1ckvqkqKjSB2Voty8g==
15# tag::passEncoded[]

pom.xml

  1<?xml version="1.0" encoding="UTF-8"?>
  2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3
  4    <modelVersion>4.0.0</modelVersion>
  5
  6    <groupId>io.openliberty.guides</groupId>
  7    <artifactId>guide-mongodb-intro</artifactId>
  8    <version>1.0-SNAPSHOT</version>
  9    <packaging>war</packaging>
 10
 11    <properties>
 12        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 13        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 14        <maven.compiler.source>1.8</maven.compiler.source>
 15        <maven.compiler.target>1.8</maven.compiler.target>
 16        <!-- OpenLiberty runtime -->
 17        <version.openliberty-runtime>RELEASE</version.openliberty-runtime>
 18        <!-- tag::defaultHttpPort[] -->
 19        <liberty.var.default.http.port>9080</liberty.var.default.http.port>
 20        <!-- end::defaultHttpPort[] -->
 21        <!-- tag::defaultHttpsPort[] -->
 22        <liberty.var.default.https.port>9443</liberty.var.default.https.port>
 23        <!-- end::defaultHttpsPort[] -->
 24        <app.name>${project.artifactId}</app.name>
 25        <!-- tag::appContextRoot[] -->
 26        <liberty.var.app.context.root>/mongo</liberty.var.app.context.root>
 27        <!-- end::appContextRoot[] -->
 28        <package.file>${project.build.directory}/${app.name}.zip</package.file>
 29    </properties>
 30
 31    <dependencies>
 32        <!-- Provided dependencies -->
 33        <dependency>
 34            <groupId>jakarta.platform</groupId>
 35            <artifactId>jakarta.jakartaee-api</artifactId>
 36            <version>9.1.0</version>
 37            <scope>provided</scope>
 38        </dependency>
 39        <dependency>
 40            <groupId>org.eclipse.microprofile</groupId>
 41            <artifactId>microprofile</artifactId>
 42            <version>5.0</version>
 43            <type>pom</type>
 44            <scope>provided</scope>
 45        </dependency>
 46        <dependency>
 47            <groupId>javax.validation</groupId>
 48            <artifactId>validation-api</artifactId>
 49            <version>2.0.1.Final</version>
 50            <scope>provided</scope>
 51        </dependency>
 52        <!-- tag::passwordUtilDependency[] -->
 53        <dependency>
 54            <groupId>com.ibm.websphere.appserver.api</groupId>
 55            <artifactId>com.ibm.websphere.appserver.api.passwordUtil</artifactId>
 56            <version>1.0.59</version>
 57            <scope>provided</scope>
 58        </dependency>
 59        <!-- end::passwordUtilDependency[] -->
 60        <!-- tag::sslDependency[] -->
 61        <dependency>
 62            <groupId>com.ibm.websphere.appserver.api</groupId>
 63            <artifactId>com.ibm.websphere.appserver.api.ssl</artifactId>
 64            <version>1.4.59</version>
 65            <scope>provided</scope>
 66        </dependency>
 67        <!-- end::sslDependency[] -->
 68        <!-- Required dependencies -->
 69        <!-- tag::mongoDriver[] -->
 70        <dependency>
 71            <groupId>org.mongodb</groupId>
 72            <artifactId>mongo-java-driver</artifactId>
 73            <version>3.12.10</version>
 74        </dependency>
 75        <!-- end::mongoDriver[] -->
 76        <!-- For tests -->
 77        <dependency>
 78            <groupId>org.jboss.resteasy</groupId>
 79            <artifactId>resteasy-client</artifactId>
 80            <version>6.0.0.Final</version>
 81            <scope>test</scope>
 82        </dependency>
 83        <dependency>
 84            <groupId>org.jboss.resteasy</groupId>
 85            <artifactId>resteasy-json-binding-provider</artifactId>
 86            <version>6.0.0.Final</version>
 87            <scope>test</scope>
 88        </dependency>
 89        <dependency>
 90            <groupId>org.glassfish</groupId>
 91            <artifactId>jakarta.json</artifactId>
 92            <version>2.0.1</version>
 93            <scope>test</scope>
 94        </dependency>
 95        <dependency>
 96            <groupId>org.junit.jupiter</groupId>
 97            <artifactId>junit-jupiter</artifactId>
 98            <version>5.8.2</version>
 99            <scope>test</scope>
100        </dependency>
101        <dependency>
102            <groupId>javax.xml.bind</groupId>
103            <artifactId>jaxb-api</artifactId>
104            <version>2.3.1</version>
105            <scope>test</scope>
106        </dependency>
107    </dependencies>
108
109    <build>
110        <defaultGoal>clean package liberty:run-server</defaultGoal>
111        <finalName>${project.artifactId}</finalName>
112        <plugins>
113            <plugin>
114                <groupId>org.apache.maven.plugins</groupId>
115                <artifactId>maven-war-plugin</artifactId>
116                <version>3.3.2</version>
117            </plugin>
118            <!-- Enable liberty-maven plugin -->
119            <plugin>
120                <groupId>io.openliberty.tools</groupId>
121                <artifactId>liberty-maven-plugin</artifactId>
122                <version>3.5.1</version>
123            </plugin>
124            <!-- Plugin to run unit tests -->
125            <plugin>
126                <groupId>org.apache.maven.plugins</groupId>
127                <artifactId>maven-surefire-plugin</artifactId>
128                <version>2.22.2</version>
129            </plugin>
130            <!-- Plugin to run functional tests -->
131            <plugin>
132                <groupId>org.apache.maven.plugins</groupId>
133                <artifactId>maven-failsafe-plugin</artifactId>
134                <version>2.22.2</version>
135                <configuration>
136                    <!-- tag::testsysprops[] -->
137                    <systemPropertyVariables>
138                        <app.http.port>${liberty.var.default.http.port}</app.http.port>
139                        <app.context.root>${liberty.var.app.context.root}</app.context.root>
140                    </systemPropertyVariables>
141                    <!-- end::testsysprops[] -->
142                </configuration>
143            </plugin>
144        </plugins>
145    </build>
146</project>

server.xml

 1<server description="Sample Liberty server">
 2    <!-- tag::featureManager[] -->
 3    <featureManager>
 4        <!-- tag::cdiFeature[] -->
 5        <feature>cdi-3.0</feature>
 6        <!-- end::cdiFeature[] -->
 7        <!-- tag::sslFeature[] -->
 8        <feature>ssl-1.0</feature>
 9        <!-- end::sslFeature[] -->
10        <!-- tag::mpConfigFeature[] -->
11        <feature>mpConfig-3.0</feature>
12        <!-- end::mpConfigFeature[] -->
13        <!-- tag::passwordUtilFeature[] -->
14        <feature>passwordUtilities-1.0</feature>
15        <!-- end::passwordUtilFeature[] -->
16        <feature>beanValidation-3.0</feature>           
17        <feature>restfulWS-3.0</feature>
18        <feature>jsonb-2.0</feature>
19        <feature>mpOpenAPI-3.0</feature>
20    </featureManager>
21    <!-- end::featureManager[] -->
22
23    <variable name="default.http.port" defaultValue="9080"/>
24    <variable name="default.https.port" defaultValue="9443"/>
25    <variable name="app.context.root" defaultValue="/mongo"/>
26
27    <!-- tag::httpEndpoint[] -->
28    <httpEndpoint
29        host="*" 
30        httpPort="${default.http.port}" 
31        httpsPort="${default.https.port}" 
32        id="defaultHttpEndpoint"
33    />
34    <!-- end::httpEndpoint[] -->
35
36    <!-- tag::webApplication[] -->
37    <webApplication 
38        location="guide-mongodb-intro.war" 
39        contextRoot="${app.context.root}"
40    />
41    <!-- end::webApplication[] -->
42    <!-- tag::sslContext[] -->
43    <!-- tag::keyStore[] -->
44    <keyStore
45        id="outboundTrustStore" 
46        location="${server.output.dir}/resources/security/truststore.p12"
47        password="mongodb"
48        type="PKCS12" 
49    />
50    <!-- end::keyStore[] -->
51    <!-- tag::ssl[] -->
52    <ssl 
53        id="outboundSSLContext" 
54        keyStoreRef="defaultKeyStore" 
55        trustStoreRef="outboundTrustStore" 
56        sslProtocol="TLS" 
57    />
58    <!-- end::ssl[] -->
59    <!-- end::sslContext[] -->
60</server>

The values from the microprofile-config.properties file are injected into the MongoProducer class. The MongoProducer class requires the following methods for the MongoClient:

  • The createMongo() producer method returns an instance of MongoClient. In this method, the username, database name, and decoded password are passed into the MongoCredential.createCredential() method to get an instance of MongoCredential. The JSSEHelper gets the SSLContext from the outboundSSLContext in the server.xml file. Then, a MongoClient instance is created.

  • The createDB() producer method returns an instance of MongoDatabase that depends on the MongoClient. This method injects the MongoClient in its parameters and passes the database name into the MongoClient.getDatabase() method to get a MongoDatabase instance.

  • The close() method is a clean-up function for the MongoClient that closes the connection to the MongoDatabase instance.

Implementing the Create, Retrieve, Update, and Delete operations

You are going to implement the basic create, retrieve, update, and delete (CRUD) operations in the CrewService class. The com.mongodb.client and com.mongodb.client.result packages are used to help implement these operations for the microservice. For more information about these packages, see the com.mongodb.client and com.mongodb.client.result Javadoc. For more information about creating a RESTful service with JAX-RS, JSON-B, and Open Liberty, see the guide on Creating a RESTful web serivce.

Create the CrewService class.
src/main/java/io/openliberty/guides/application/CrewService.java

CrewService.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2020, 2022 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License v1.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-v10.html
  8 *
  9 * Contributors:
 10 *     IBM Corporation - Initial implementation
 11 *******************************************************************************/
 12// end::copyright[]
 13package io.openliberty.guides.application;
 14
 15import java.util.Set;
 16
 17import java.io.StringWriter;
 18
 19import jakarta.enterprise.context.ApplicationScoped;
 20import jakarta.inject.Inject;
 21import jakarta.json.JsonArray;
 22import jakarta.json.JsonArrayBuilder;
 23import jakarta.json.Json;
 24import jakarta.ws.rs.GET;
 25import jakarta.ws.rs.POST;
 26import jakarta.ws.rs.PUT;
 27import jakarta.ws.rs.Path;
 28import jakarta.ws.rs.Consumes;
 29import jakarta.ws.rs.DELETE;
 30import jakarta.ws.rs.Produces;
 31import jakarta.ws.rs.PathParam;
 32import jakarta.ws.rs.core.MediaType;
 33import jakarta.ws.rs.core.Response;
 34
 35import jakarta.validation.Validator;
 36import jakarta.validation.ConstraintViolation;
 37
 38import com.mongodb.client.FindIterable;
 39// tag::bsonDocument[]
 40import org.bson.Document;
 41// end::bsonDocument[]
 42import org.bson.types.ObjectId;
 43
 44// tag::mongoImports1[]
 45import com.mongodb.client.MongoCollection;
 46import com.mongodb.client.MongoDatabase;
 47// end::mongoImports1[]
 48// tag::mongoImports2[]
 49import com.mongodb.client.result.DeleteResult;
 50import com.mongodb.client.result.UpdateResult;
 51// end::mongoImports2[]
 52
 53import org.eclipse.microprofile.openapi.annotations.Operation;
 54import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
 55import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
 56import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
 57
 58@Path("/crew")
 59@ApplicationScoped
 60public class CrewService {
 61
 62    // tag::dbInjection[]
 63    @Inject
 64    MongoDatabase db;
 65    // end::dbInjection[]
 66
 67    // tag::beanValidator[]
 68    @Inject
 69    Validator validator;
 70    // end::beanValidator[]
 71
 72    // tag::getViolations[]
 73    private JsonArray getViolations(CrewMember crewMember) {
 74        Set<ConstraintViolation<CrewMember>> violations = validator.validate(
 75                crewMember);
 76
 77        JsonArrayBuilder messages = Json.createArrayBuilder();
 78
 79        for (ConstraintViolation<CrewMember> v : violations) {
 80            messages.add(v.getMessage());
 81        }
 82
 83        return messages.build();
 84    }
 85    // end::getViolations[]
 86
 87    @POST
 88    @Path("/")
 89    @Consumes(MediaType.APPLICATION_JSON)
 90    @Produces(MediaType.APPLICATION_JSON)
 91    @APIResponses({
 92        @APIResponse(
 93            responseCode = "200",
 94            description = "Successfully added crew member."),
 95        @APIResponse(
 96            responseCode = "400",
 97            description = "Invalid crew member configuration.") })
 98    @Operation(summary = "Add a new crew member to the database.")
 99    // tag::add[]
100    public Response add(CrewMember crewMember) {
101        JsonArray violations = getViolations(crewMember);
102
103        if (!violations.isEmpty()) {
104            return Response
105                    .status(Response.Status.BAD_REQUEST)
106                    .entity(violations.toString())
107                    .build();
108        }
109
110        // tag::getCollection[]
111        MongoCollection<Document> crew = db.getCollection("Crew");
112        // end::getCollection[]
113
114        // tag::crewMemberCreation[]
115        Document newCrewMember = new Document();
116        newCrewMember.put("Name", crewMember.getName());
117        newCrewMember.put("Rank", crewMember.getRank());
118        newCrewMember.put("CrewID", crewMember.getCrewID());
119        // end::crewMemberCreation[]
120
121        // tag::insertOne[]
122        crew.insertOne(newCrewMember);
123        // end::insertOne[]
124
125        return Response
126            .status(Response.Status.OK)
127            .entity(newCrewMember.toJson())
128            .build();
129    }
130    // end::add[]
131
132    @GET
133    @Path("/")
134    @Produces(MediaType.APPLICATION_JSON)
135    @APIResponses({
136        @APIResponse(
137            responseCode = "200",
138            description = "Successfully listed the crew members."),
139        @APIResponse(
140            responseCode = "500",
141            description = "Failed to list the crew members.") })
142    @Operation(summary = "List the crew members from the database.")
143    // tag::retrieve[]
144    public Response retrieve() {
145        StringWriter sb = new StringWriter();
146
147        try {
148            // tag::getCollectionRead[]
149            MongoCollection<Document> crew = db.getCollection("Crew");
150            // end::getCollectionRead[]
151            sb.append("[");
152            boolean first = true;
153            // tag::find[]
154            FindIterable<Document> docs = crew.find();
155            // end::find[]
156            for (Document d : docs) {
157                if (!first) {
158                    sb.append(",");
159                } else {
160                    first = false;
161                }
162                sb.append(d.toJson());
163            }
164            sb.append("]");
165        } catch (Exception e) {
166            e.printStackTrace(System.out);
167            return Response
168                .status(Response.Status.INTERNAL_SERVER_ERROR)
169                .entity("[\"Unable to list crew members!\"]")
170                .build();
171        }
172
173        return Response
174            .status(Response.Status.OK)
175            .entity(sb.toString())
176            .build();
177    }
178    // end::retrieve[]
179
180    @PUT
181    @Path("/{id}")
182    @Consumes(MediaType.APPLICATION_JSON)
183    @Produces(MediaType.APPLICATION_JSON)
184    @APIResponses({
185        @APIResponse(
186            responseCode = "200",
187            description = "Successfully updated crew member."),
188        @APIResponse(
189            responseCode = "400",
190            description = "Invalid object id or crew member configuration."),
191        @APIResponse(
192            responseCode = "404",
193            description = "Crew member object id was not found.") })
194    @Operation(summary = "Update a crew member in the database.")
195    // tag::update[]
196    public Response update(CrewMember crewMember,
197        @Parameter(
198            description = "Object id of the crew member to update.",
199            required = true
200        )
201        @PathParam("id") String id) {
202
203        JsonArray violations = getViolations(crewMember);
204
205        if (!violations.isEmpty()) {
206            return Response
207                    .status(Response.Status.BAD_REQUEST)
208                    .entity(violations.toString())
209                    .build();
210        }
211
212        ObjectId oid;
213
214        try {
215            oid = new ObjectId(id);
216        } catch (Exception e) {
217            return Response
218                .status(Response.Status.BAD_REQUEST)
219                .entity("[\"Invalid object id!\"]")
220                .build();
221        }
222
223        // tag::getCollectionUpdate[]
224        MongoCollection<Document> crew = db.getCollection("Crew");
225        // end::getCollectionUpdate[]
226
227        // tag::queryUpdate[]
228        Document query = new Document("_id", oid);
229        // end::queryUpdate[]
230
231        // tag::crewMemberUpdate[]
232        Document newCrewMember = new Document();
233        newCrewMember.put("Name", crewMember.getName());
234        newCrewMember.put("Rank", crewMember.getRank());
235        newCrewMember.put("CrewID", crewMember.getCrewID());
236        // end::crewMemberUpdate[]
237
238        // tag::replaceOne[]
239        UpdateResult updateResult = crew.replaceOne(query, newCrewMember);
240        // end::replaceOne[]
241
242        // tag::getMatchedCount[]
243        if (updateResult.getMatchedCount() == 0) {
244        // end::getMatchedCount[]
245            return Response
246                .status(Response.Status.NOT_FOUND)
247                .entity("[\"_id was not found!\"]")
248                .build();
249        }
250
251        newCrewMember.put("_id", oid);
252
253        return Response
254            .status(Response.Status.OK)
255            .entity(newCrewMember.toJson())
256            .build();
257    }
258    // end::update[]
259
260    @DELETE
261    @Path("/{id}")
262    @Produces(MediaType.APPLICATION_JSON)
263    @APIResponses({
264        @APIResponse(
265            responseCode = "200",
266            description = "Successfully deleted crew member."),
267        @APIResponse(
268            responseCode = "400",
269            description = "Invalid object id."),
270        @APIResponse(
271            responseCode = "404",
272            description = "Crew member object id was not found.") })
273    @Operation(summary = "Delete a crew member from the database.")
274    // tag::remove[]
275    public Response remove(
276        @Parameter(
277            description = "Object id of the crew member to delete.",
278            required = true
279        )
280        @PathParam("id") String id) {
281
282        ObjectId oid;
283
284        try {
285            oid = new ObjectId(id);
286        } catch (Exception e) {
287            return Response
288                .status(Response.Status.BAD_REQUEST)
289                .entity("[\"Invalid object id!\"]")
290                .build();
291        }
292
293        // tag::getCollectionDelete[]
294        MongoCollection<Document> crew = db.getCollection("Crew");
295        // end::getCollectionDelete[]
296
297        // tag::queryDelete[]
298        Document query = new Document("_id", oid);
299        // end::queryDelete[]
300
301        // tag::deleteOne[]
302        DeleteResult deleteResult = crew.deleteOne(query);
303        // end::deleteOne[]
304
305        // tag::getDeletedCount[]
306        if (deleteResult.getDeletedCount() == 0) {
307        // end::getDeletedCount[]
308            return Response
309                .status(Response.Status.NOT_FOUND)
310                .entity("[\"_id was not found!\"]")
311                .build();
312        }
313
314        return Response
315            .status(Response.Status.OK)
316            .entity(query.toJson())
317            .build();
318    }
319    // end::remove[]
320}

CrewMember.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2020, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.application;
14
15import jakarta.validation.constraints.NotEmpty;
16import jakarta.validation.constraints.Pattern;
17
18// tag::crewMember[]
19public class CrewMember {
20
21    @NotEmpty(message = "All crew members must have a name!")
22    private String name;
23
24    @Pattern(regexp = "(Captain|Officer|Engineer)",
25            message = "Crew member must be one of the listed ranks!")
26    private String rank;
27
28    @Pattern(regexp = "^\\d+$",
29            message = "ID Number must be a non-negative integer!")
30    private String crewID;
31
32    public String getName() {
33        return name;
34    }
35
36    public void setName(String name) {
37        this.name = name;
38    }
39
40    public String getRank() {
41        return rank;
42    }
43
44    public void setRank(String rank) {
45        this.rank = rank;
46    }
47
48    public String getCrewID() {
49        return crewID;
50    }
51
52    public void setCrewID(String crewID) {
53        this.crewID = crewID;
54    }
55
56}
57// end::crewMember[]

In this class, a Validator is used to validate a CrewMember before the database is updated. The CDI producer is used to inject a MongoDatabase into the CrewService class.

Implementing the Create operation

The add() method handles the implementation of the create operation. An instance of MongoCollection is retrieved with the MongoDatabase.getCollection() method. The Document type parameter specifies that the Document type is used to store data in the MongoCollection. Each crew member is converted into a Document, and the MongoCollection.insertOne() method inserts a new crew member document.

Implementing the Retrieve operation

The retrieve() method handles the implementation of the retrieve operation. The Crew collection is retrieved with the MongoDatabase.getCollection() method. Then, the MongoCollection.find() method retrieves a FindIterable object. This object is iterable for all the crew members documents in the collection, so each crew member document is concatenated into a String array and returned.

Implementing the Update operation

The update() method handles the implementation of the update operation. After the Crew collection is retrieved, a document is created with the specified object id and is used to query the collection. Next, a new crew member Document is created with the updated configuration. The MongoCollection.replaceOne() method is called with the query and new crew member document. This method updates all of the matching queries with the new document. Because the object id is unique in the Crew collection, only one document is updated. The MongoCollection.replaceOne() method also returns an UpdateResult instance, which determines how many documents matched the query. If there are zero matches, then the object id doesn’t exist.

Implementing the Delete operation

The remove() method handles the implementation of the delete operation. After the Crew collection is retrieved, a Document is created with the specified object id and is used to query the collection. Because the object id is unique in the Crew collection, only one document is deleted. After the document is deleted, the MongoCollection.deleteOne() method returns a DeleteResult instance, which determines how many documents were deleted. If zero documents were deleted, then the object id doesn’t exist.

Configuring the MongoDB driver and the server

MicroProfile Config makes configuring the MongoDB driver simple because all of the configuration can be set in one place and injected into the CDI producer.

Create the configuration file.
src/main/webapp/META-INF/microprofile-config.properties

microprofile-config.properties

 1# tag::hostname[]
 2mongo.hostname=localhost
 3# end::hostname[]
 4# tag::port[]
 5mongo.port=27017
 6# end::port[]
 7# tag::dbname[]
 8mongo.dbname=testdb
 9# end::dbname[]
10# tag::mongoUser[]
11mongo.user=sampleUser
12# end::mongoUser[]
13# tag::passEncoded[]
14mongo.pass.encoded={aes}APtt+/vYxxPa0jE1rhmZue9wBm3JGqFK3JR4oJdSDGWM1wLr1ckvqkqKjSB2Voty8g==
15# tag::passEncoded[]

Values such as the hostname, port, and database name for the running MongoDB instance are set in this file. The user’s username and password are also set here. For added security, the password was encoded by using the securityUtility encode command.

To create a CDI producer for MongoDB and connect over TLS, the Open Liberty server needs to be correctly configured.

Replace the server configuration file.
src/main/liberty/config/server.xml

server.xml

 1<server description="Sample Liberty server">
 2    <!-- tag::featureManager[] -->
 3    <featureManager>
 4        <!-- tag::cdiFeature[] -->
 5        <feature>cdi-3.0</feature>
 6        <!-- end::cdiFeature[] -->
 7        <!-- tag::sslFeature[] -->
 8        <feature>ssl-1.0</feature>
 9        <!-- end::sslFeature[] -->
10        <!-- tag::mpConfigFeature[] -->
11        <feature>mpConfig-3.0</feature>
12        <!-- end::mpConfigFeature[] -->
13        <!-- tag::passwordUtilFeature[] -->
14        <feature>passwordUtilities-1.0</feature>
15        <!-- end::passwordUtilFeature[] -->
16        <feature>beanValidation-3.0</feature>           
17        <feature>restfulWS-3.0</feature>
18        <feature>jsonb-2.0</feature>
19        <feature>mpOpenAPI-3.0</feature>
20    </featureManager>
21    <!-- end::featureManager[] -->
22
23    <variable name="default.http.port" defaultValue="9080"/>
24    <variable name="default.https.port" defaultValue="9443"/>
25    <variable name="app.context.root" defaultValue="/mongo"/>
26
27    <!-- tag::httpEndpoint[] -->
28    <httpEndpoint
29        host="*" 
30        httpPort="${default.http.port}" 
31        httpsPort="${default.https.port}" 
32        id="defaultHttpEndpoint"
33    />
34    <!-- end::httpEndpoint[] -->
35
36    <!-- tag::webApplication[] -->
37    <webApplication 
38        location="guide-mongodb-intro.war" 
39        contextRoot="${app.context.root}"
40    />
41    <!-- end::webApplication[] -->
42    <!-- tag::sslContext[] -->
43    <!-- tag::keyStore[] -->
44    <keyStore
45        id="outboundTrustStore" 
46        location="${server.output.dir}/resources/security/truststore.p12"
47        password="mongodb"
48        type="PKCS12" 
49    />
50    <!-- end::keyStore[] -->
51    <!-- tag::ssl[] -->
52    <ssl 
53        id="outboundSSLContext" 
54        keyStoreRef="defaultKeyStore" 
55        trustStoreRef="outboundTrustStore" 
56        sslProtocol="TLS" 
57    />
58    <!-- end::ssl[] -->
59    <!-- end::sslContext[] -->
60</server>

The features that are required to create the CDI producer for MongoDB are Contexts and Dependency Injection (cdi-2.0), Secure Socket Layer (ssl-1.0), MicroProfile Config (mpConfig-1.4), and Password Utilities (passwordUtilities-1.0). These features are specified in the featureManager element. The Secure Socket Layer (SSL) context is configured in the server.xml file so that the application can connect to MongoDB with TLS. The keyStore element points to the truststore.p12 keystore file that was created in one of the previous sections. The ssl element specifies the defaultKeyStore as the keystore and outboundTrustStore as the truststore.

After you replace the server.xml file, the Open Liberty configuration is automatically reloaded.

Running the application

You started the Open Liberty server in dev mode at the beginning of the guide, so all the changes were automatically picked up.

Go to the http://localhost:9080/openapi/ui/ URL to see the OpenAPI user interface (UI) that provides API documentation and a client to test the API endpoints that you create after you see a message similar to the following example:

CWWKZ0001I: Application guide-mongodb-intro started in 5.715 seconds.

Try the Create operation

From the OpenAPI UI, test the create operation at the POST /api/crew endpoint by using the following code as the request body:

{
  "name": "Member1",
  "rank": "Officer",
  "crewID": "000001"
}

This request creates a new document in the Crew collection with a name of Member1, rank of Officer, and crew ID of 000001.

You’ll receive a response that contains the JSON object of the new crew member, as shown in the following example:

{
  "Name": "Member1",
  "Rank": "Officer",
  "CrewID": "000001",
  "_id": {
    "$oid": "<<ID>>"
  }
}

The <<ID>> that you receive is a unique identifier in the collection. Save this value for future commands.

Try the Retrieve operation

From the OpenAPI UI, test the read operation at the GET /api/crew endpoint. This request gets all crew member documents from the collection.

You’ll receive a response that contains an array of all the members in your crew. The response might include crew members that were created in the Try what you’ll build section of this guide:

[
  {
    "_id": {
      "$oid": "<<ID>>"
    },
    "Name": "Member1",
    "Rank": "Officer",
    "CrewID": "000001"
  }
]

Try the Update operation

From the OpenAPI UI, test the update operation at the PUT /api/crew/{id} endpoint, where the {id} parameter is the <<ID>> that you saved from the create operation. Use the following code as the request body:

{
  "name": "Member1",
  "rank": "Captain",
  "crewID": "000001"
}

This request updates the rank of the crew member that you created from Officer to Captain.

You’ll receive a response that contains the JSON object of the updated crew member, as shown in the following example:

{
  "Name": "Member1",
  "Rank": "Captain",
  "CrewID": "000001",
  "_id": {
    "$oid": "<<ID>>"
  }
}

Try the Delete operation

From the OpenAPI UI, test the delete operation at the DELETE/api/crew/{id} endpoint, where the {id} parameter is the <<ID>> that you saved from the create operation. This request removes the document that contains the specified crew member object id from the collection.

You’ll receive a response that contains the object id of the deleted crew member, as shown in the following example:

{
  "_id": {
    "$oid": "<<ID>>"
  }
}

Now, you can check out the microservice that you created by going to the http://localhost:9080/mongo/ URL.

Testing the application

Next, you’ll create integration tests to ensure that the basic operations you implemented function correctly.

Create the CrewServiceIT class.
src/test/java/it/io/openliberty/guides/application/CrewServiceIT.java

CrewServiceIT.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2020, 2022 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License v1.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-v10.html
  8 *
  9 * Contributors:
 10 *     IBM Corporation - Initial implementation
 11 *******************************************************************************/
 12// end::copyright[]
 13package it.io.openliberty.guides.application;
 14
 15import static org.junit.jupiter.api.Assertions.assertEquals;
 16
 17import java.io.StringReader;
 18import java.util.ArrayList;
 19
 20import org.junit.jupiter.api.AfterAll;
 21import org.junit.jupiter.api.BeforeAll;
 22import org.junit.jupiter.api.Order;
 23import org.junit.jupiter.api.Test;
 24import org.junit.jupiter.api.MethodOrderer;
 25import org.junit.jupiter.api.TestMethodOrder;
 26
 27import jakarta.json.Json;
 28import jakarta.json.JsonArray;
 29import jakarta.json.JsonArrayBuilder;
 30import jakarta.json.JsonObject;
 31import jakarta.json.JsonObjectBuilder;
 32import jakarta.json.JsonReader;
 33import jakarta.json.JsonValue;
 34import jakarta.ws.rs.client.Client;
 35import jakarta.ws.rs.client.ClientBuilder;
 36import jakarta.ws.rs.core.Response;
 37import jakarta.ws.rs.client.Entity;
 38
 39@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 40public class CrewServiceIT {
 41
 42    private static Client client;
 43    private static JsonArray testData;
 44    private static String rootURL;
 45    private static ArrayList<String> testIDs = new ArrayList<>(2);
 46
 47    @BeforeAll
 48    public static void setup() {
 49        client = ClientBuilder.newClient();
 50
 51        String port = System.getProperty("app.http.port");
 52        String context = System.getProperty("app.context.root");
 53        rootURL = "http://localhost:" + port + context;
 54
 55        // test data
 56        JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
 57        JsonObjectBuilder jsonBuilder = Json.createObjectBuilder();
 58        jsonBuilder.add("name", "Member1");
 59        jsonBuilder.add("crewID", "000001");
 60        jsonBuilder.add("rank", "Captain");
 61        arrayBuilder.add(jsonBuilder.build());
 62        jsonBuilder = Json.createObjectBuilder();
 63        jsonBuilder.add("name", "Member2");
 64        jsonBuilder.add("crewID", "000002");
 65        jsonBuilder.add("rank", "Engineer");
 66        arrayBuilder.add(jsonBuilder.build());
 67        testData = arrayBuilder.build();
 68    }
 69
 70    @AfterAll
 71    public static void teardown() {
 72        client.close();
 73    }
 74
 75    // tag::testAddCrewMember[]
 76    // tag::test1[]
 77    @Test
 78    // end::test1[]
 79    @Order(1)
 80    public void testAddCrewMember() {
 81        System.out.println("   === Adding " + testData.size()
 82                + " crew members to the database. ===");
 83
 84        for (int i = 0; i < testData.size(); i++) {
 85            JsonObject member = (JsonObject) testData.get(i);
 86            String url = rootURL + "/api/crew";
 87            Response response = client.target(url).request().post(Entity.json(member));
 88            this.assertResponse(url, response);
 89
 90            JsonObject newMember = response.readEntity(JsonObject.class);
 91            testIDs.add(newMember.getJsonObject("_id").getString("$oid"));
 92
 93            response.close();
 94        }
 95        System.out.println("      === Done. ===");
 96    }
 97    // end::testAddCrewMember[]
 98
 99    // tag::testUpdateCrewMember[]
100    // tag::test2[]
101    @Test
102    // end::test2[]
103    @Order(2)
104    public void testUpdateCrewMember() {
105        System.out.println("   === Updating crew member with id " + testIDs.get(0)
106                + ". ===");
107
108        JsonObject oldMember = (JsonObject) testData.get(0);
109
110        JsonObjectBuilder newMember = Json.createObjectBuilder();
111        newMember.add("name", oldMember.get("name"));
112        newMember.add("crewID", oldMember.get("crewID"));
113        newMember.add("rank", "Officer");
114
115        String url = rootURL + "/api/crew/" + testIDs.get(0);
116        Response response = client.target(url).request()
117                .put(Entity.json(newMember.build()));
118
119        this.assertResponse(url, response);
120
121        System.out.println("      === Done. ===");
122    }
123    // end::testUpdateCrewMember[]
124
125    // tag::testGetCrewMembers[]
126    // tag::test3[]
127    @Test
128    // end::test3[]
129    @Order(3)
130    public void testGetCrewMembers() {
131        System.out.println("   === Listing crew members from the database. ===");
132
133        String url = rootURL + "/api/crew";
134        Response response = client.target(url).request().get();
135
136        this.assertResponse(url, response);
137
138        String responseText = response.readEntity(String.class);
139        JsonReader reader = Json.createReader(new StringReader(responseText));
140        JsonArray crew = reader.readArray();
141        reader.close();
142
143        int testMemberCount = 0;
144        for (JsonValue value : crew) {
145            JsonObject member = (JsonObject) value;
146            String id = member.getJsonObject("_id").getString("$oid");
147            if (testIDs.contains(id)) {
148                testMemberCount++;
149            }
150        }
151
152        assertEquals(testIDs.size(), testMemberCount,
153                "Incorrect number of testing members.");
154
155        System.out.println("      === Done. There are " + crew.size()
156                + " crew members. ===");
157
158        response.close();
159    }
160    // end::testGetCrewMembers[]
161
162    // tag::testDeleteCrewMember[]
163    // tag::test4[]
164    @Test
165    // end::test4[]
166    @Order(4)
167    public void testDeleteCrewMember() {
168        System.out.println("   === Removing " + testIDs.size()
169                + " crew members from the database. ===");
170
171        for (String id : testIDs) {
172            String url = rootURL + "/api/crew/" + id;
173            Response response = client.target(url).request().delete();
174            this.assertResponse(url, response);
175            response.close();
176        }
177
178        System.out.println("      === Done. ===");
179    }
180    // end::testDeleteCrewMember[]
181
182    private void assertResponse(String url, Response response) {
183        assertEquals(200, response.getStatus(), "Incorrect response code from " + url);
184    }
185}

The test methods are annotated with the @Test annotation.

The following test cases are included in this class:

  • testAddCrewMember() verifies that new members are correctly added to the database.

  • testUpdateCrewMember() verifies that a crew member’s information is correctly updated.

  • testGetCrewMembers() verifies that a list of crew members is returned by the microservice API.

  • testDeleteCrewMember() verifies that the crew members are correctly removed from the database.

Running the tests

Because you started Open Liberty in dev mode, you can run the tests by pressing the enter/return key from the command-line session where you started dev mode.

You’ll see the following output:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.application.CrewServiceIT
   === Adding 2 crew members to the database. ===
      === Done. ===
   === Updating crew member with id 5df8e0a004ccc019976c7d0a. ===
      === Done. ===
   === Listing crew members from the database. ===
      === Done. There are 2 crew members. ===
   === Removing 2 crew members from the database. ===
      === Done. ===
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.411 s - in it.io.openliberty.guides.application.CrewServiceIT
Results:
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0

Tearing down the environment

When you are done checking out the service, exit dev mode by pressing CTRL+C in the command-line session where you ran the server, or by typing q and then pressing the enter/return key.

Then, run the following commands to stop and remove the mongo-guide container and to remove the mongo-sample and mongo images.

docker stop mongo-guide
docker rm mongo-guide
docker rmi mongo-sample
docker rmi mongo

Great work! You’re done!

You’ve successfully accessed and persisted data to a MongoDB database from a Java microservice using Contexts and Dependency Injection (CDI) and MicroProfile Config with Open Liberty.

Learn more about MicroProfile.

Guide Attribution

Persisting data with MongoDB by Open Liberty is licensed under CC BY-ND 4.0

Copied to clipboard
Copy code block
Copy file contents

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star