0
0

More than 3 years have passed since last update.

Spring Boot で Azure Cosmos DBにアクセスする

Posted at

Spring Bootを利用して、Azure CosmosDB(MongoDBコア)にアクセスしたので、その記録です。

コード書くのが怠かったので、https://spring.pleiades.io/guides/gs/accessing-data-mongodb/ を参考にしました。

まずは、普通にサンプルコードをgit cloneします。

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

チュートリアル通りにローカルにMongoDBを立てて、

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

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

mongoコマンドで中を覗いてみます。

$ 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" }
> 

入ってますね。

この時点で、チュートリアルでは接続先のMongoDBへのアクセスは行っていません。実装されているデータアクセスに関するものも、以下の2つのみです。

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);
}

高レベルAPIであるRepositoryを使用しているので、実装は非常に簡単です。実際にはSpring Data MongoDBによってCustomerRepojitoryインターフェースを実装するクラスが自動生成されて、saveやfindなどのメソッドも利用できるようになっています。便利ですね。(低レベルAPIであるTemplateMongoを使用すると、もっと複雑なこともできます。)

今回は、MongoDBコアで作成したCosmosDBにアクセスするのが主目的なので、高レベルAPIを利用したこのサンプルを利用します。

Azure側に、CosmosDBを作成します。こちらは https://docs.microsoft.com/ja-jp/azure/cosmos-db/create-mongodb-java#create-a-database-account を参考にしてサクサクとPortalから作成します。コレクションの作成は不要です。

作成されたら、Portalから接続先のURIを確認します。今回はJavaですので、"クイックスタート"の"Java"タブに表示されているプライマリ接続文字列をコピーします。
image.png

サンプルコードに、main/resources/application.propertisを追加し、"spring.data.mongodb.uri"を追加して、先ほどコピーした値を貼り付け、Mongo DB Driverが3.6以降の場合は末尾に"&retrywrites=false"を追加します。Cosmos DBが現時点でRetryable Writesに対応していないためです。

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

定義したURLを使用するため、Configurationを作成します。

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);
    }
}

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

成功しました。Azureポータルのデータエクスプローラーでcustomerコレクションが追加されていることを確認しましょう。
image.png

データが作成されていることが確認できますね。

Cosmos DBは最近Free Tierが提供されたので、手軽に機能を試すことができます。
是非、触ってみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0