[JAVA] Access Azure Cosmos DB with Spring Boot

Since I accessed Azure CosmosDB (MongoDB core) using Spring Boot, it is a record.

I was lazy to write the code, so I referred to https://spring.pleiades.io/guides/gs/accessing-data-mongodb/.

First, git clone the sample code normally.

$ git clone https://github.com/spring-guides/gs-accessing-data-mongodb.git

Set up MongoDB locally according to the tutorial,

$ sudo apt install mongodb
$ mkdir -p data/db
$ mongod --dbpath=./data/db

Run in Gradle.

$ gradle bootRun

Welcome to Gradle 6.5.1!

Here are the highlights of this release:
 - Experimental file-system watching
 - Improved version ordering
 - New samples

For more details see https://docs.gradle.org/6.5.1/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)

> Task :bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.0.RELEASE)

2020-07-02 03:12:00.912  INFO 32643 --- [           main] c.e.a.AccessingDataMongodbApplication    : Starting AccessingDataMongodbApplication on DESKTOP-N9T4CN3 with PID 32643 (XXX/gs-accessing-data-mongodb/complete/build/classes/java/main started by XXX in XXX/gs-accessing-data-mongodb/complete)
2020-07-02 03:12:00.917  INFO 32643 --- [           main] c.e.a.AccessingDataMongodbApplication    : No active profile set, falling back to default profiles: default
2020-07-02 03:12:01.274  INFO 32643 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2020-07-02 03:12:01.315  INFO 32643 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 36ms. Found 1 repository interfaces.
2020-07-02 03:12:01.589  INFO 32643 --- [           main] org.mongodb.driver.cluster               : Cluster created with settings {hosts=[localhost:27017], mode=SINGLE, requiredClusterType=UNKNOWN, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500}
2020-07-02 03:12:01.674  INFO 32643 --- [localhost:27017] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:1, serverValue:1}] to localhost:27017
2020-07-02 03:12:01.678  INFO 32643 --- [localhost:27017] org.mongodb.driver.cluster               : Monitor thread successfully connected to server with description ServerDescription{address=localhost:27017, type=STANDALONE, state=CONNECTED, ok=true, version=ServerVersion{versionList=[3, 6, 8]}, minWireVersion=0, maxWireVersion=6, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=2881100}
2020-07-02 03:12:01.745  WARN 32643 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.LocalDateTime to class java.time.Instant as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 03:12:01.745  WARN 32643 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.Instant to class java.time.LocalDateTime as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 03:12:01.771  WARN 32643 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.LocalDateTime to class java.time.Instant as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 03:12:01.771  WARN 32643 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.Instant to class java.time.LocalDateTime as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 03:12:02.134  INFO 32643 --- [           main] c.e.a.AccessingDataMongodbApplication    : Started AccessingDataMongodbApplication in 1.552 seconds (JVM running for 1.853)
2020-07-02 03:12:02.157  INFO 32643 --- [           main] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:2, serverValue:2}] to localhost:27017
Customers found with findAll():
-------------------------------
Customer[id=5efcd1f2b15c223a7dc2a9ce, firstName='Alice', lastName='Smith']
Customer[id=5efcd1f2b15c223a7dc2a9cf, firstName='Bob', lastName='Smith']

Customer found with findByFirstName('Alice'):
--------------------------------
Customer[id=5efcd1f2b15c223a7dc2a9ce, firstName='Alice', lastName='Smith']
Customers found with findByLastName('Smith'):
--------------------------------
Customer[id=5efcd1f2b15c223a7dc2a9ce, firstName='Alice', lastName='Smith']
Customer[id=5efcd1f2b15c223a7dc2a9cf, firstName='Bob', lastName='Smith']
2020-07-02 03:12:02.270  INFO 32643 --- [extShutdownHook] org.mongodb.driver.connection            : Closed connection [connectionId{localValue:2, serverValue:2}] to localhost:27017 because the pool has been closed.

BUILD SUCCESSFUL in 53s
2 actionable tasks: 2 executed

Take a peek inside with the mongo command.

$ mongo
MongoDB shell version v3.6.8
connecting to: mongodb://127.0.0.1:27017
Implicit session: session { "id" : UUID("2648d101-f236-4f56-bc3d-7250d32ef9a2") }
MongoDB server version: 3.6.8
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
        http://docs.mongodb.org/
Questions? Try the support group
        http://groups.google.com/group/mongodb-user
Server has startup warnings: 
2020-07-02T02:54:56.366+0900 I STORAGE  [initandlisten] 
2020-07-02T02:54:56.366+0900 I STORAGE  [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2020-07-02T02:54:56.366+0900 I STORAGE  [initandlisten] **          See http://dochub.mongodb.org/core/prodnotes-filesystem
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] 
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] 
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] ** WARNING: This server is bound to localhost.
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] **          Remote systems will be unable to connect to this server. 
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] **          Start the server with --bind_ip <address> to specify which IP 
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] **          addresses it should serve responses from, or with --bind_ip_all to
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] **          bind to all interfaces. If this behavior is desired, start the
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] **          server with --bind_ip 127.0.0.1 to disable this warning.
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] 
2020-07-02T02:54:57.071+0900 I CONTROL  [initandlisten] 
2020-07-02T02:54:57.071+0900 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2020-07-02T02:54:57.071+0900 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2020-07-02T02:54:57.071+0900 I CONTROL  [initandlisten] 
> db.customer.find()
{ "_id" : ObjectId("5efcd1f2b15c223a7dc2a9ce"), "firstName" : "Alice", "lastName" : "Smith", "_class" : "com.example.accessingdatamongodb.Customer" }
{ "_id" : ObjectId("5efcd1f2b15c223a7dc2a9cf"), "firstName" : "Bob", "lastName" : "Smith", "_class" : "com.example.accessingdatamongodb.Customer" }
> 

It's in.

At this point, the tutorial is not accessing the connected MongoDB. There are only two things related to data access implemented.

Customer.java


package com.example.accessingdatamongodb;

import org.springframework.data.annotation.Id;

public class Customer {
    @Id
    public String id;
    public String firstName;
    public String lastName;
    public Customer() {}
    public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    @Override
    public String toString() {
        return String.format(
                "Customer[id=%s, firstName='%s', lastName='%s']",
                id, firstName, lastName);
    }
}

CustomerRepository.java


package com.example.accessingdatamongodb;

import java.util.List;
import org.springframework.data.mongodb.repository.MongoRepository;

public interface CustomerRepository extends MongoRepository<Customer, String> {
    public Customer findByFirstName(String firstName);
    public List<Customer> findByLastName(String lastName);
}

It's very easy to implement because it uses the high-level API Repository. In fact, Spring Data MongoDB automatically generates a class that implements the CustomerRepojitory interface, and methods such as save and find are also available. It's convenient. (You can do more complicated things with the low-level API Template Mongo.)

This time, the main purpose is to access Cosmos DB created in MongoDB core, so we will use this sample using high level API.

Create Cosmos DB on the Azure side. This is created from crispy and portal by referring to https://docs.microsoft.com/ja-jp/azure/cosmos-db/create-mongodb-java#create-a-database-account. No need to create a collection.

Once created, check the URI of the connection destination from Portal. Since this time it is Java, copy the primary connection string displayed in the "Java" tab of "Quick Start". image.png

Add main / resources / application.propertis to the sample code, add "spring.data.mongodb.uri", paste the value you copied earlier, and if Mongo DB Driver is 3.6 or later, add "& retrywrites" at the end. Add = false ". This is because Cosmos DB does not currently support Retryable Writes.

application.properties


spring.data.mongodb.uri=<your_connection_string>&retrywrites=false

Create a Configuration to use the defined URL.

CustomerRepogitoryConfig.java


package com.example.accessingdatamongodb;

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CustomerRepositoryConfig {
    @Value("${spring.data.mongodb.uri:mongodb://localhost:27017}")
    public String connectionString;
    public @Bean MongoClient mongoClient() {
        return MongoClients.create(this.connectionString);
    }
}

Run in Gradle.

$ gradle bootRun -Pargs=--debug

> Task :bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.0.RELEASE)

2020-07-02 04:54:56.451  INFO 3822 --- [           main] c.e.a.AccessingDataMongodbApplication    : Starting AccessingDataMongodbApplication on DESKTOP-N9T4CN3 with PID 3822 (XXX/gs-accessing-data-mongodb/complete/build/classes/java/main started by XXX in XXX/gs-accessing-data-mongodb/complete)
2020-07-02 04:54:56.456  INFO 3822 --- [           main] c.e.a.AccessingDataMongodbApplication    : No active profile set, falling back to default profiles: default
2020-07-02 04:54:57.370  INFO 3822 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2020-07-02 04:54:57.497  INFO 3822 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 118ms. Found 1 repository interfaces.
2020-07-02 04:54:58.011  INFO 3822 --- [           main] org.mongodb.driver.cluster               : Cluster created with settings {hosts=[XXX:YYY], mode=MULTIPLE, requiredClusterType=REPLICA_SET, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500, requiredReplicaSetName='globaldb'}
2020-07-02 04:54:58.011  INFO 3822 --- [           main] org.mongodb.driver.cluster               : Adding discovered server XXX:YYY to client view of cluster
2020-07-02 04:54:58.244  WARN 3822 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.LocalDateTime to class java.time.Instant as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 04:54:58.245  WARN 3822 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.Instant to class java.time.LocalDateTime as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 04:54:58.316  WARN 3822 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.LocalDateTime to class java.time.Instant as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 04:54:58.317  WARN 3822 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.Instant to class java.time.LocalDateTime as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 04:54:58.912  INFO 3822 --- [           main] c.e.a.AccessingDataMongodbApplication    : Started AccessingDataMongodbApplication in 3.129 seconds (JVM running for 3.84)
2020-07-02 04:54:58.947  INFO 3822 --- [           main] org.mongodb.driver.cluster               : No server chosen by com.mongodb.client.internal.MongoClientDelegate$1@210386e0 from cluster description ClusterDescription{type=REPLICA_SET, connectionMode=MULTIPLE, serverDescriptions=[ServerDescription{address=XXX:YYY, type=UNKNOWN, state=CONNECTING}]}. Waiting for 30000 ms before timing out
2020-07-02 04:54:59.082  INFO 3822 --- [azure.com:10255] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:1, serverValue:1162392059}] to XXX:YYY
2020-07-02 04:54:59.097  INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster               : Monitor thread successfully connected to server with description ServerDescription{address=XXX:YYY, type=REPLICA_SET_PRIMARY, state=CONNECTED, ok=true, version=ServerVersion{versionList=[3, 6, 0]}, minWireVersion=0, maxWireVersion=6, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=12402600, setName='globaldb', canonicalAddress=XXX:YYY, hosts=[XXX:YYY], passives=[], arbiters=[], primary='XXX:YYY', tagSet=TagSet{[Tag{name='region', value='Japan East'}]}, electionId=null, setVersion=1, lastWriteDate=null, lastUpdateTimeNanos=134393982328436}
2020-07-02 04:54:59.100  INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster               : Adding discovered server XXX:YYY to client view of cluster
2020-07-02 04:54:59.103  INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster               : Server XXX:YYY is no longer a member of the replica set.  Removing from client view of cluster.
2020-07-02 04:54:59.105  INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster               : Canonical address XXX:YYY does not match server address.  Removing XXX:YYY from client view of cluster
2020-07-02 04:54:59.338  INFO 3822 --- [azure.com:10255] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:2, serverValue:689958399}] to XXX:YYY
2020-07-02 04:54:59.348  INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster               : Monitor thread successfully connected to server with description ServerDescription{address=XXX:YYY, type=REPLICA_SET_PRIMARY, state=CONNECTED, ok=true, version=ServerVersion{versionList=[3, 6, 0]}, minWireVersion=0, maxWireVersion=6, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=8755400, setName='globaldb', canonicalAddress=XXX:YYY, hosts=[XXX:YYY], passives=[], arbiters=[], primary='XXX:YYY', tagSet=TagSet{[Tag{name='region', value='Japan East'}]}, electionId=null, setVersion=1, lastWriteDate=null, lastUpdateTimeNanos=134394233832236}
2020-07-02 04:54:59.349  INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster               : Setting max set version to 1 from replica set primary XXX:YYY
2020-07-02 04:54:59.349  INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster               : Discovered replica set primary XXX:YYY
2020-07-02 04:54:59.603  INFO 3822 --- [           main] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:3, serverValue:1139365834}] to XXX:YYY
Customers found with findAll():
-------------------------------
Customer[id=5efcea13cac85c79009dba55, firstName='Alice', lastName='Smith']
Customer[id=5efcea15cac85c79009dba56, firstName='Bob', lastName='Smith']

Customer found with findByFirstName('Alice'):
--------------------------------
Customer[id=5efcea13cac85c79009dba55, firstName='Alice', lastName='Smith']
Customers found with findByLastName('Smith'):
--------------------------------
Customer[id=5efcea13cac85c79009dba55, firstName='Alice', lastName='Smith']
Customer[id=5efcea15cac85c79009dba56, firstName='Bob', lastName='Smith']
2020-07-02 04:55:01.231  INFO 3822 --- [extShutdownHook] org.mongodb.driver.connection            : Closed connection [connectionId{localValue:3, serverValue:1139365834}] to XXX:YYY because the pool has been closed.

BUILD SUCCESSFUL in 8s
3 actionable tasks: 2 executed, 1 up-to-date

Succeeded. Let's verify that the customer collection has been added in the Data Explorer of the Azure portal. image.png

You can see that the data has been created.

Cosmos DB has recently been offered Free Tier, so you can easily try out its features. Please feel it.

Recommended Posts

Access Azure Cosmos DB with Spring Boot
Tips for developing apps with Azure Cosmos DB in Python
Try to extract Azure document DB document with pydocumentdb
Hit Nature Remo's API to record room temperature with Azure Functions and Cosmos DB
Azure Cosmos DB starting in 2020 --Azure Synapse Link Demo (Part. 1)
mongodb access with pymongo
Spring damper with PyODE
Hello World with Google App Engine (Java 8) + Spring Boot + Gradle
I tried Google Sign-In with Spring Boot + Spring Security REST API
Hello World with Google App Engine (Java 11) + Spring Boot + Gradle