チュートリアル: Azure Cosmos DB および NoSQL 用 API を使った Java Web アプリケーションの作成

適用対象: NoSQL

この Java Web アプリケーション チュートリアルでは、Microsoft Azure Cosmos DB サービスを使用して、Azure App Service Web Apps でホストされる Java アプリケーションからデータを格納する方法やデータにアクセスする方法について説明します。 クレジット カードまたは Azure サブスクリプションがない場合は、無料の Azure Cosmos DB 試用版アカウントをセットアップできます。 この記事では、次のことについて説明します。

  • Eclipse で基本的な JavaServer Pages (JSP) アプリケーションを作成する方法。
  • Azure Cosmos DB Java SDK を使って Azure Cosmos DB サービスを操作する方法。

この Java Web アプリケーション チュートリアルでは、次の図に示すように、タスクを作成、取得、完了済みとしてマークできる、Web ベースのタスク管理アプリケーションを作成する方法について説明します。 ToDo リストの各タスクは、JSON ドキュメントとして Azure Cosmos DB に格納されます。

My ToDo List Java アプリケーション

ヒント

このアプリケーション開発チュートリアルは、Java を使用した経験がある読者を対象としています。 Java や前提条件となるツールを初めて扱う方は、完全な todo プロジェクトを GitHub からダウンロードし、この記事の最後にある手順に従ってプロジェクトをビルドすることをお勧めします。 プロジェクトをビルドした後でこの記事を見直すと、プロジェクトのコンテキストのコードについての洞察を得ることができます。

この Java Web アプリケーション チュートリアルの前提条件

このアプリケーション開発チュートリアルを読み始める前に、次の項目を用意する必要があります。

これらのツールを初めてインストールする場合は、coreservlets.com の「Tutorial: Installing TomCat7 and Using it with Eclipse」 (チュートリアル: TomCat7 のインストールと Eclipse での使用) の「Quick Start」 (クイック スタート) セクションで、インストール プロセスの手順を参照してください。

Azure Cosmos DB アカウントを作成する

まず最初に、Azure Cosmos DB アカウントを作成します。 アカウントが既にある場合、またはこのチュートリアルで Azure Cosmos DB Emulator を使用する場合は、「手順 2: Java JSP アプリケーションを作成する」に進むことができます。

  1. Azure portal のメニューまたは [ホーム] ページで、 [リソースの作成] を選択します。

  2. Azure Cosmos DB を検索します。 [作成]>[Azure Cosmos DB] を選択します。

  3. [Azure Cosmos DB アカウントの作成] ページで、 Azure Cosmos DB for NoSQL セクション内の [作成] オプションを選択します。

    Azure Cosmos DB には、いくつかの API が用意されています:

    • NoSQL (ドキュメント データ用)
    • PostgreSQL
    • MongoDB (ドキュメント データ用)
    • Apache Cassandra
    • テーブル
    • Apache Gremlin (グラフ データ用)

    NoSQL 用 API の詳細については、「Azure Cosmos DB へようこそ」を参照してください。

  4. [Azure Cosmos DB アカウントの作成] ページで、新しい Azure Cosmos DB アカウントの基本的な設定を入力します。

    設定 説明
    サブスクリプション サブスクリプション名 この Azure Cosmos DB アカウントに使用する Azure サブスクリプションを選択します。
    リソース グループ リソース グループ名 リソース グループを選択するか、 [新規作成] を選択し、新しいリソース グループの一意の名前を入力します。
    アカウント名 一意の名前 自分の Azure Cosmos DB アカウントを識別するための名前を入力します。 指定した名前に documents.azure.com が付加されて URI が作成されるので、一意の名前を使用してください。 名前に含めることができるのは、英小文字、数字、ハイフン (-) のみです。 3 から 44 文字にする必要があります。
    場所 ユーザーに最も近いリージョン Azure Cosmos DB アカウントをホストする地理的な場所を選択します。 データに最も高速にアクセスできるよう、お客様のユーザーに最も近い場所を使用します。
    容量モード プロビジョニング スループットまたはサーバーレス プロビジョニング スループット モードでアカウントを作成するには、 [Provisioned throughput](プロビジョニング スループット) を選択します。 サーバーレス モードでアカウントを作成するには、 [サーバーレス] を選択します。
    Apply Azure Cosmos DB free tier discount (Azure Cosmos DB Free レベル割引を適用する) [適用] または [適用しない] Azure Cosmos DB Free レベルのアカウントでは、最初の 1000 RU/s と 25 GB のストレージを無料でご利用いただけます。 Free レベルの詳細を確認してください。
    合計アカウント スループットを制限する 選択済みかどうか このアカウントでプロビジョニングできるスループットの総量を制限する。 この制限により、プロビジョニングされたスループットに関連する予期しない料金が回避されます。 この制限は、アカウントの作成後に更新または削除できます。

    Azure サブスクリプションにつき所有できる Free サービス レベルの Azure Cosmos DB アカウントは 1 つまでです。また、アカウントの作成時にオプトインする必要があります。 Free レベル割引を適用するオプションが表示されない場合は、サブスクリプション内の別のアカウントが Free レベルで既に有効になっています。

    [Azure Cosmos DB アカウントの作成] ページを示すスクリーンショット。

    Note

    [Capacity mode](容量モード) として [サーバーレス] を選択した場合、以下のオプションは利用できません。

    • Apply Free Tier Discount (Free レベルの割引の適用)
    • 合計アカウント スループットを制限する
  5. [グローバル分散] タブで、次の詳細を構成します。 このクイックスタートでは、既定値のままにしておいてかまいません。

    設定 説明
    geo 冗長性 無効化 リージョンをペア リージョンとペアリングすることによる、アカウントでのグローバル配信を有効または無効にします。 アカウントには、後でさらにリージョンを追加できます。
    マルチリージョン書き込み 無効化 マルチリージョン書き込み機能を使用すると、世界中のデータベースとコンテナーで、プロビジョニングされたスループットを利用できます。
    可用性ゾーン 無効にする Availability Zones は、アプリケーションの可用性と回復性をさらに向上させるのに役立ちます。

    注意

    前の [基本] ページで [容量モード] として [サーバーレス] を選択した場合、次のオプションは使用できません:

    • geo 冗長
    • マルチリージョン ライター
  6. 必要に応じて、次のタブでさらに詳細を構成できます:

    • ネットワーク仮想ネットワークからのアクセスを構成します。
    • Backup Policy定期的または継続的のいずれかのバックアップ ポリシーを構成します。
    • 暗号化。 サービス マネージド キーまたはカスタマー マネージド キーのいずれかを使用します。
    • タグ。 タグは名前と値のペアで、同じタグを複数のリソースやリソース グループに適用することでリソースを分類したり、統合した請求を表示したりできるようにします。
  7. [Review + create](レビュー + 作成) を選択します。

  8. アカウントの設定を確認し、 [作成] を選択します。 アカウントの作成には数分かかります。 ポータル ページに "デプロイが完了しました" と表示されるまで待ちます。

    デプロイが完了したことを示すスクリーンショット。

  9. [リソースに移動] を選択し、Azure Cosmos DB アカウント ページに移動します。

    Azure Cosmos DB アカウント ページを示すスクリーンショット。

Azure Cosmos DB アカウント ページに移動し、[キー] を選択します。 次に作成する Web アプリケーションで使用する値をコピーします。

[Azure Cosmos DB アカウント] ページで [キー] ボタンが強調表示されている Azure portal のスクリーンショット

Java JSP アプリケーションを作成する

JSP アプリケーションを作成するには:

  1. 最初に、Java プロジェクトを作成します。 Eclipse を起動し、[ファイル][新規][動的 Web プロジェクト] の順に選択します。 使用可能なプロジェクトとして [動的 Web プロジェクト] が表示されない場合は、[ファイル] を選択、[新規] を選択、[プロジェクト] を選択し、[Web] を展開して、[動的 Web プロジェクト]、そして [次へ] を選択します。

    JSP Java アプリケーション開発

  2. [プロジェクト名] ボックスに、プロジェクト名を入力します。必要に応じて、[ターゲット ランタイム] ボックスの一覧で値 (たとえば、Apache Tomcat v7.0) を選択し、[完了] を選択します。 ターゲット ランタイムを選択すると、Eclipse でプロジェクトをローカルに実行できます。

  3. Eclipse の Project Explorer ビューで、プロジェクトを展開します。 [WebContent] を右クリックし、[新規] を選択し、次に [JSP ファイル] を選択します。

  4. [New JSP File] ダイアログ ボックスで、ファイルに index.jsp という名前を付けます。 次の図に示すように、親フォルダーは WebContent のままにしておきます。そして [次へ] を選択します。

    新しい JSP ファイルの作成 - Java Web アプリケーションのチュートリアル

  5. [JSP テンプレートの選択] ダイアログ ボックスで、このチュートリアルのために [新規 JSP ファイル (html)] を選択し、次に [完了] を選択します。

  6. Eclipse で index.jsp ファイルが開いたら、Hello World! を表示するためのテキストを既存の <body> 要素内に追加します。 更新した <body> の内容は次のようになります。

    <body>
      <% out.println("Hello World!"); %>
    </body>
    
  7. index.jsp ファイルを保存します。

  8. 手順 2. でターゲットのランタイムを設定している場合、[プロジェクト]、次に [実行] を選択して、JSP アプリケーションをローカルで実行できます。

    Hello World - Java アプリケーションのチュートリアル

SQL Java SDK をインストールする

SQL Java SDK とその依存関係をインストールするには、Apache Maven を使うのが最も簡単です。 そのためには、次の手順に従ってプロジェクトを Maven プロジェクトに変換する必要があります。

  1. プロジェクト エクスプローラーでプロジェクトを右クリックして、[構成] を選択し、[Maven プロジェクトに変換] を選択します。

  2. [新規 POM の作成] ウィンドウで、既定値を受け入れ、[完了] を選択します。

  3. Project Explorerで、pom.xml ファイルを開きます。

  4. [依存関係] タブの [依存関係] ウィンドウで、[追加] を選択します。

  5. [Select Dependency] ウィンドウで、次の操作を行います。

    • [Group Id] ボックスに、「com.azure」と入力します。
    • [Artifact Id] ボックスに、「azure-cosmos」と入力します。
    • [Version] ボックスに「4.11.0」と入力します。

    または、Group ID および Artifact ID の依存関係 XML を直接 pom.xml に追加します。

    <dependency>
      <groupId>com.azure</groupId>
      <artifactId>azure-cosmos</artifactId>
      <version>4.11.0</version>
    </dependency>
    
  6. [OK] を選択すると、Maven によって、SQL Java SDK がインストールされるか、pom.xml ファイルが保存されます。

Java アプリケーションで Azure Cosmos DB サービスを使用する

次に、モデル、ビュー、およびコントローラーを Web アプリに追加してみましょう。

モデルを追加する

まず、新しいファイル TodoItem.java 内にモデルを定義します。 TodoItem クラスで、getter および setter メソッドと共に項目のスキーマを定義します。

package com.microsoft.azure.cosmos.sample.model;

//@Data
//@Builder
public class TodoItem {
    private String entityType;
    private String category;
    private boolean complete;
    private String id;
    private String name;

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public String getEntityType() {
        return entityType;
    }

    public void setEntityType(String entityType) {
        this.entityType = entityType;
    }

    public boolean isComplete() {
        return complete;
    }

    public void setComplete(boolean complete) {
        this.complete = complete;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    
}

データ アクセス オブジェクト (DAO) クラスを追加する

ToDo 項目の永続化を Azure Cosmos DB に抽出するためのデータ アクセス オブジェクト (DAO) を作成します。 ToDo 項目をコレクションに保存するために、クライアントはどのデータベースとコレクションが永続化するかを知っている必要があります (自己リンクによって参照されます)。 一般に、データベースへの追加のラウンドト リップを回避するために、可能な場合は、データベースとコレクションをキャッシュすることをお勧めします。

  1. Azure Cosmos DB サービスを呼び出すには、新しい cosmosClient オブジェクトをインスタンス化する必要があります。 一般に、後続の要求ごとに新しいクライアントを構築するのではなく、cosmosClient オブジェクトを再利用することをお勧めします。 クライアントを再利用するには、cosmosClientFactory クラス内で定義します。 手順 1 で保存した HOST と MASTER_KEY の値を更新します。 HOST 変数と MASTER_KEY をそれぞれ、ご自分の URI とプライマリ キーに置き換えます。 次のコードを使用して、CosmosClientFactory.java ファイル内に CosmosClientFactory クラスを作成します。

    package com.microsoft.azure.cosmos.sample.dao;
    
    import com.azure.cosmos.ConsistencyLevel;
    import com.azure.cosmos.CosmosClient;
    import com.azure.cosmos.CosmosClientBuilder;
    
    public class CosmosClientFactory {
        private static final String HOST = "[ACCOUNT HOST NAME]";
        private static final String MASTER_KEY = "[ACCOUNT KEY]";
    
        private static CosmosClient cosmosClient = new CosmosClientBuilder()
                .endpoint(HOST)
                .key(MASTER_KEY)
                .consistencyLevel(ConsistencyLevel.EVENTUAL)
                .buildClient();
    
        public static CosmosClient getCosmosClient() {
            return cosmosClient;
        }
    
    }
    
  2. 新しい TodoDao.java ファイルを作成し、ToDo 項目を作成、更新、読み取り、削除するための TodoDao クラスを追加します。

    package com.microsoft.azure.cosmos.sample.dao;
    
    import java.util.List;
    
    import com.microsoft.azure.cosmos.sample.model.TodoItem;
    
    public interface TodoDao {
        /**
         * @return A list of TodoItems
         */
        public List<TodoItem> readTodoItems();
    
        /**
         * @param todoItem
         * @return whether the todoItem was persisted.
         */
        public TodoItem createTodoItem(TodoItem todoItem);
    
        /**
         * @param id
         * @return the TodoItem
         */
        public TodoItem readTodoItem(String id);
    
        /**
         * @param id
         * @return the TodoItem
         */
        public TodoItem updateTodoItem(String id, boolean isComplete);
    
        /**
         *
         * @param id
         * @return whether the delete was successful.
         */
        public boolean deleteTodoItem(String id);
    }
    
  3. 新しい MockDao.java ファイルを作成し、MockDao クラスを追加します。このクラスは TodoDao クラスを実装して、項目に対して CRUD 操作を実行します。

    package com.microsoft.azure.cosmos.sample.dao;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import lombok.NonNull;
    
    import com.microsoft.azure.cosmos.sample.model.TodoItem;
    
    public class MockDao implements TodoDao {
        private final Map<String, TodoItem> todoItemMap;
    
        public MockDao() {
            todoItemMap = new HashMap<String, TodoItem>();
        }
    
        @Override
        public TodoItem createTodoItem(@NonNull TodoItem todoItem) {
            if (todoItem.getId() == null || todoItem.getId().isEmpty()) {
                todoItem.setId(generateId());
            }
            todoItemMap.put(todoItem.getId(), todoItem);
            return todoItem;
        }
    
        @Override
        public TodoItem readTodoItem(@NonNull String id) {
            return todoItemMap.get(id);
        }
    
        @Override
        public List<TodoItem> readTodoItems() {
            return new ArrayList<TodoItem>(todoItemMap.values());
        }
    
        @Override
        public TodoItem updateTodoItem(String id, boolean isComplete) {
            todoItemMap.get(id).setComplete(isComplete);
            return todoItemMap.get(id);
        }
    
        @Override
        public boolean deleteTodoItem(@NonNull String id) {
            todoItemMap.remove(id);
            return true;
        }
    
        private String generateId() {
            return new Integer(todoItemMap.size()).toString();
        }
    }
    
  4. 新しい DocDbDao.java ファイルを作成し、DocDbDao クラスを追加します。 このクラスで TodoItems をコンテナーに永続化するコードを定義し、データベースとコレクションが存在する場合はそれを取得し、存在しない場合は新規に作成します。 この例では、Gson を使用して、TodoItem Plain Old Java Objects (POJO) を JSON ドキュメントにシリアル化および逆シリアル化します。 ToDo 項目をコレクションに保存するために、クライアントはどのデータベースとコレクションが永続化するかを知っている必要があります (自己リンクによって参照されます)。 このクラスで、別の属性 ("ID" など) により、ドキュメントを取得するヘルパー関数も定義します。セルフ リンクは使用しません。 ヘルパー メソッドを使用して、ID により、TodoItem の JSON ドキュメントを取得して POJO に逆シリアル化できます。

    cosmosClient クライアント オブジェクトを使って、SQL クエリにより、TodoItems のコレクションまたはリストを取得することもできます。 最後に、リストから TodoItem を削除するための削除メソッドを定義します。 以下のコードは、DocDbDao クラスの内容を示しています。

    package com.microsoft.azure.cosmos.sample.dao;
    
    import com.azure.cosmos.CosmosClient;
    import com.azure.cosmos.CosmosContainer;
    import com.azure.cosmos.CosmosDatabase;
    import com.azure.cosmos.CosmosException;
    import com.azure.cosmos.implementation.Utils;
    import com.azure.cosmos.models.CosmosContainerProperties;
    import com.azure.cosmos.models.CosmosContainerResponse;
    import com.azure.cosmos.models.CosmosDatabaseResponse;
    import com.azure.cosmos.models.CosmosItemRequestOptions;
    import com.azure.cosmos.models.CosmosQueryRequestOptions;
    import com.azure.cosmos.models.FeedResponse;
    import com.azure.cosmos.models.PartitionKey;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.node.ObjectNode;
    import com.google.gson.Gson;
    import com.microsoft.azure.cosmos.sample.model.TodoItem;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class DocDbDao implements TodoDao {
        // The name of our database.
        private static final String DATABASE_ID = "TestDB";
    
        // The name of our collection.
        private static final String CONTAINER_ID = "TestCollection";
    
        // We'll use Gson for POJO <=> JSON serialization for this example.
        private static Gson gson = new Gson();
    
        // The Cosmos DB Client
        private static CosmosClient cosmosClient = CosmosClientFactory
            .getCosmosClient();
    
        // The Cosmos DB database
        private static CosmosDatabase cosmosDatabase = null;
    
        // The Cosmos DB container
        private static CosmosContainer cosmosContainer = null;
    
        // For POJO/JsonNode interconversion
        private static final ObjectMapper OBJECT_MAPPER = Utils.getSimpleObjectMapper();
    
        @Override
        public TodoItem createTodoItem(TodoItem todoItem) {
            // Serialize the TodoItem as a JSON Document.
    
            JsonNode todoItemJson = OBJECT_MAPPER.valueToTree(todoItem);
    
            ((ObjectNode) todoItemJson).put("entityType", "todoItem");
    
            try {
                // Persist the document using the DocumentClient.
                todoItemJson =
                    getContainerCreateResourcesIfNotExist()
                        .createItem(todoItemJson)
                        .getItem();
            } catch (CosmosException e) {
                System.out.println("Error creating TODO item.\n");
                e.printStackTrace();
                return null;
            }
    
    
            try {
    
                return OBJECT_MAPPER.treeToValue(todoItemJson, TodoItem.class);
                //return todoItem;
            } catch (Exception e) {
                System.out.println("Error deserializing created TODO item.\n");
                e.printStackTrace();
    
                return null;
            }
    
        }
    
        @Override
        public TodoItem readTodoItem(String id) {
            // Retrieve the document by id using our helper method.
            JsonNode todoItemJson = getDocumentById(id);
    
            if (todoItemJson != null) {
                // De-serialize the document in to a TodoItem.
                try {
                    return OBJECT_MAPPER.treeToValue(todoItemJson, TodoItem.class);
                } catch (JsonProcessingException e) {
                    System.out.println("Error deserializing read TODO item.\n");
                    e.printStackTrace();
    
                    return null;
                }
            } else {
                return null;
            }
        }
    
        @Override
        public List<TodoItem> readTodoItems() {
    
            List<TodoItem> todoItems = new ArrayList<TodoItem>();
    
            String sql = "SELECT * FROM root r WHERE r.entityType = 'todoItem'";
            int maxItemCount = 1000;
            int maxDegreeOfParallelism = 1000;
            int maxBufferedItemCount = 100;
    
            CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
            options.setMaxBufferedItemCount(maxBufferedItemCount);
            options.setMaxDegreeOfParallelism(maxDegreeOfParallelism);
            options.setQueryMetricsEnabled(false);
    
            int error_count = 0;
            int error_limit = 10;
    
            String continuationToken = null;
            do {
    
                for (FeedResponse<JsonNode> pageResponse :
                    getContainerCreateResourcesIfNotExist()
                        .queryItems(sql, options, JsonNode.class)
                        .iterableByPage(continuationToken, maxItemCount)) {
    
                    continuationToken = pageResponse.getContinuationToken();
    
                    for (JsonNode item : pageResponse.getElements()) {
    
                        try {
                            todoItems.add(OBJECT_MAPPER.treeToValue(item, TodoItem.class));
                        } catch (JsonProcessingException e) {
                            if (error_count < error_limit) {
                                error_count++;
                                if (error_count >= error_limit) {
                                    System.out.println("\n...reached max error count.\n");
                                } else {
                                    System.out.println("Error deserializing TODO item JsonNode. " +
                                        "This item will not be returned.");
                                    e.printStackTrace();
                                }
                            }
                        }
    
                    }
                }
    
            } while (continuationToken != null);
    
            return todoItems;
        }
    
        @Override
        public TodoItem updateTodoItem(String id, boolean isComplete) {
            // Retrieve the document from the database
            JsonNode todoItemJson = getDocumentById(id);
    
            // You can update the document as a JSON document directly.
            // For more complex operations - you could de-serialize the document in
            // to a POJO, update the POJO, and then re-serialize the POJO back in to
            // a document.
            ((ObjectNode) todoItemJson).put("complete", isComplete);
    
            try {
                // Persist/replace the updated document.
                todoItemJson =
                    getContainerCreateResourcesIfNotExist()
                        .replaceItem(todoItemJson, id, new PartitionKey(id), new CosmosItemRequestOptions())
                        .getItem();
            } catch (CosmosException e) {
                System.out.println("Error updating TODO item.\n");
                e.printStackTrace();
                return null;
            }
    
            // De-serialize the document in to a TodoItem.
            try {
                return OBJECT_MAPPER.treeToValue(todoItemJson, TodoItem.class);
            } catch (JsonProcessingException e) {
                System.out.println("Error deserializing updated item.\n");
                e.printStackTrace();
    
                return null;
            }
        }
    
        @Override
        public boolean deleteTodoItem(String id) {
            // CosmosDB refers to documents by self link rather than id.
    
            // Query for the document to retrieve the self link.
            JsonNode todoItemJson = getDocumentById(id);
    
            try {
                // Delete the document by self link.
                getContainerCreateResourcesIfNotExist()
                    .deleteItem(id, new PartitionKey(id), new CosmosItemRequestOptions());
            } catch (CosmosException e) {
                System.out.println("Error deleting TODO item.\n");
                e.printStackTrace();
                return false;
            }
    
            return true;
        }
    
        /*
        
        private CosmosDatabase getTodoDatabase() {
            if (databaseCache == null) {
                // Get the database if it exists
                List<CosmosDatabase> databaseList = cosmosClient
                        .queryDatabases(
                                "SELECT * FROM root r WHERE r.id='" + DATABASE_ID
                                        + "'", null).getQueryIterable().toList();
    
                if (databaseList.size() > 0) {
                    // Cache the database object so we won't have to query for it
                    // later to retrieve the selfLink.
                    databaseCache = databaseList.get(0);
                } else {
                    // Create the database if it doesn't exist.
                    try {
                        CosmosDatabase databaseDefinition = new CosmosDatabase();
                        databaseDefinition.setId(DATABASE_ID);
    
                        databaseCache = cosmosClient.createDatabase(
                                databaseDefinition, null).getResource();
                    } catch (CosmosException e) {
                        // TODO: Something has gone terribly wrong - the app wasn't
                        // able to query or create the collection.
                        // Verify your connection, endpoint, and key.
                        e.printStackTrace();
                    }
                }
            }
    
            return databaseCache;
        }
    
        */
    
        private CosmosContainer getContainerCreateResourcesIfNotExist() {
    
            try {
    
                if (cosmosDatabase == null) {
                    CosmosDatabaseResponse cosmosDatabaseResponse = cosmosClient.createDatabaseIfNotExists(DATABASE_ID);
                    cosmosDatabase = cosmosClient.getDatabase(cosmosDatabaseResponse.getProperties().getId());
                }
    
            } catch (CosmosException e) {
                // TODO: Something has gone terribly wrong - the app wasn't
                // able to query or create the collection.
                // Verify your connection, endpoint, and key.
                System.out.println("Something has gone terribly wrong - " +
                    "the app wasn't able to create the Database.\n");
                e.printStackTrace();
            }
    
            try {
    
                if (cosmosContainer == null) {
                    CosmosContainerProperties properties = new CosmosContainerProperties(CONTAINER_ID, "/id");
                    CosmosContainerResponse cosmosContainerResponse = cosmosDatabase.createContainerIfNotExists(properties);
                    cosmosContainer = cosmosDatabase.getContainer(cosmosContainerResponse.getProperties().getId());
                }
    
            } catch (CosmosException e) {
                // TODO: Something has gone terribly wrong - the app wasn't
                // able to query or create the collection.
                // Verify your connection, endpoint, and key.
                System.out.println("Something has gone terribly wrong - " +
                    "the app wasn't able to create the Container.\n");
                e.printStackTrace();
            }
    
            return cosmosContainer;
        }
    
        private JsonNode getDocumentById(String id) {
    
            String sql = "SELECT * FROM root r WHERE r.id='" + id + "'";
            int maxItemCount = 1000;
            int maxDegreeOfParallelism = 1000;
            int maxBufferedItemCount = 100;
    
            CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
            options.setMaxBufferedItemCount(maxBufferedItemCount);
            options.setMaxDegreeOfParallelism(maxDegreeOfParallelism);
            options.setQueryMetricsEnabled(false);
    
            List<JsonNode> itemList = new ArrayList();
    
            String continuationToken = null;
            do {
                for (FeedResponse<JsonNode> pageResponse :
                    getContainerCreateResourcesIfNotExist()
                        .queryItems(sql, options, JsonNode.class)
                        .iterableByPage(continuationToken, maxItemCount)) {
    
                    continuationToken = pageResponse.getContinuationToken();
    
                    for (JsonNode item : pageResponse.getElements()) {
                        itemList.add(item);
                    }
                }
    
            } while (continuationToken != null);
    
            if (itemList.size() > 0) {
                return itemList.get(0);
            } else {
                return null;
            }
        }
    
    }
    
  5. 次に、新しい TodoDaoFactory.java ファイルを作成し、新しい DocDbDao オブジェクトを作成する TodoDaoFactory クラスを追加します。

    package com.microsoft.azure.cosmos.sample.dao;
    
    public class TodoDaoFactory {
        private static TodoDao myTodoDao = new DocDbDao();
    
        public static TodoDao getDao() {
            return myTodoDao;
        }
    }
    

コントローラーを追加する

TodoItemController コントローラーをアプリケーションに追加します。 このプロジェクトでは、Project Lombok を使用して、コンストラクター、getter、setter、ビルダーを生成します。 または、このコードを手動で作成するか、次のように IDE で自動的に生成することもできます。

package com.microsoft.azure.cosmos.sample.controller;

import java.util.List;
import java.util.UUID;

import lombok.NonNull;

import com.microsoft.azure.cosmos.sample.dao.TodoDao;
import com.microsoft.azure.cosmos.sample.dao.TodoDaoFactory;
import com.microsoft.azure.cosmos.sample.model.TodoItem;

public class TodoItemController {
    public static TodoItemController getInstance() {
        if (todoItemController == null) {
            todoItemController = new TodoItemController(TodoDaoFactory.getDao());
        }
        return todoItemController;
    }

    private static TodoItemController todoItemController;

    private final TodoDao todoDao;

    TodoItemController(TodoDao todoDao) {
        this.todoDao = todoDao;
    }

    public TodoItem createTodoItem(@NonNull String name,
            @NonNull String category, boolean isComplete) {
        TodoItem todoItem = new TodoItem();
        
        todoItem.setName(name);
        todoItem.setCategory(category);
        todoItem.setComplete(isComplete);
        todoItem.setId(UUID.randomUUID().toString());

        return todoDao.createTodoItem(todoItem);
    }

    public boolean deleteTodoItem(@NonNull String id) {
        return todoDao.deleteTodoItem(id);
    }

    public TodoItem getTodoItemById(@NonNull String id) {
        return todoDao.readTodoItem(id);
    }

    public List<TodoItem> getTodoItems() {
        return todoDao.readTodoItems();
    }

    public TodoItem updateTodoItem(@NonNull String id, boolean isComplete) {
        return todoDao.updateTodoItem(id, isComplete);
    }
}

サーブレットを作成する

次に、サーブレットを作成して HTTP 要求をコントローラーにルーティングします。 ApiServlet.java ファイルを作成し、その下に次のコードを定義します。

package com.microsoft.azure.cosmos.sample;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.gson.Gson;
import com.microsoft.azure.cosmos.sample.controller.TodoItemController;

/**
 * API Frontend Servlet
 */
@WebServlet("/api")
public class ApiServlet extends HttpServlet {
    // API Keys
    public static final String API_METHOD = "method";

    // API Methods
    public static final String CREATE_TODO_ITEM = "createTodoItem";
    public static final String GET_TODO_ITEMS = "getTodoItems";
    public static final String UPDATE_TODO_ITEM = "updateTodoItem";

    // API Parameters
    public static final String TODO_ITEM_ID = "todoItemId";
    public static final String TODO_ITEM_NAME = "todoItemName";
    public static final String TODO_ITEM_CATEGORY = "todoItemCategory";
    public static final String TODO_ITEM_COMPLETE = "todoItemComplete";

    public static final String MESSAGE_ERROR_INVALID_METHOD = "{'error': 'Invalid method'}";

    private static final long serialVersionUID = 1L;
    private static final Gson gson = new Gson();

    @Override
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {

        String apiResponse = MESSAGE_ERROR_INVALID_METHOD;

        TodoItemController todoItemController = TodoItemController
                .getInstance();

        String id = request.getParameter(TODO_ITEM_ID);
        String name = request.getParameter(TODO_ITEM_NAME);
        String category = request.getParameter(TODO_ITEM_CATEGORY);
        String itemComplete = request.getParameter(TODO_ITEM_COMPLETE);
        boolean isComplete = itemComplete!= null && itemComplete.equalsIgnoreCase("true");

        switch (request.getParameter(API_METHOD)) {
        case CREATE_TODO_ITEM:
            apiResponse = gson.toJson(todoItemController.createTodoItem(name,
                    category, isComplete));
            break;
        case GET_TODO_ITEMS:
            apiResponse = gson.toJson(todoItemController.getTodoItems());
            break;
        case UPDATE_TODO_ITEM:
            apiResponse = gson.toJson(todoItemController.updateTodoItem(id,
                    isComplete));
            break;
        default:
            break;
        }

        response.setCharacterEncoding("UTF-8");
        response.getWriter().println(apiResponse);
    }

    @Override
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

Java アプリの残りの部分を連結する

これで、楽しい作業が終わりました。残っているのは簡単なユーザー インターフェイスの作成と DAO への連結です。

  1. ユーザーに表示する Web ユーザー インターフェイスが必要です。 次のコードを使用して、先ほど作成した index.jsp を書き換えてみましょう。

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge;" />
      <title>Azure Cosmos Java Sample</title>
    
      <!-- Bootstrap -->
      <link href="//ajax.aspnetcdn.com/ajax/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
    
      <style>
        /* Add padding to body for fixed nav bar */
        body {
          padding-top: 50px;
        }
      </style>
    </head>
    <body>
      <!-- Nav Bar -->
      <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
        <div class="container">
          <div class="navbar-header">
            <a class="navbar-brand" href="#">My Tasks</a>
          </div>
        </div>
      </div>
    
      <!-- Body -->
      <div class="container">
        <h1>My ToDo List</h1>
    
        <hr/>
    
        <!-- The ToDo List -->
        <div class = "todoList">
          <table class="table table-bordered table-striped" id="todoItems">
            <thead>
              <tr>
                <th>Name</th>
                <th>Category</th>
                <th>Complete</th>
              </tr>
            </thead>
            <tbody>
            </tbody>
          </table>
    
          <!-- Update Button -->
          <div class="todoUpdatePanel">
            <form class="form-horizontal" role="form">
              <button type="button" class="btn btn-primary">Update Tasks</button>
            </form>
          </div>
    
        </div>
    
        <hr/>
    
        <!-- Item Input Form -->
        <div class="todoForm">
          <form class="form-horizontal" role="form">
            <div class="form-group">
              <label for="inputItemName" class="col-sm-2">Task Name</label>
              <div class="col-sm-10">
                <input type="text" class="form-control" id="inputItemName" placeholder="Enter name">
              </div>
            </div>
    
            <div class="form-group">
              <label for="inputItemCategory" class="col-sm-2">Task Category</label>
              <div class="col-sm-10">
                <input type="text" class="form-control" id="inputItemCategory" placeholder="Enter category">
              </div>
            </div>
    
            <button type="button" class="btn btn-primary">Add Task</button>
          </form>
        </div>
    
      </div>
    
      <!-- Placed at the end of the document so the pages load faster -->
      <script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.1.min.js"></script>
      <script src="//ajax.aspnetcdn.com/ajax/bootstrap/3.2.0/bootstrap.min.js"></script>
      <script src="assets/todo.js"></script>
    </body>
    </html>
    
  2. 最後に、Web ユーザー インターフェイスとサーブレットを結び付けるためにクライアント側の JavaScript を記述します。

    /**
     * ToDo App
     */
    
    var todoApp = {
      /*
       * API methods to call Java backend.
       */
      apiEndpoint: "api",
    
      createTodoItem: function(name, category, isComplete) {
        $.post(todoApp.apiEndpoint, {
            "method": "createTodoItem",
            "todoItemName": name,
            "todoItemCategory": category,
            "todoItemComplete": isComplete
          },
          function(data) {
            var todoItem = data;
            todoApp.addTodoItemToTable(todoItem.id, todoItem.name, todoItem.category, todoItem.complete);
          },
          "json");
      },
    
      getTodoItems: function() {
        $.post(todoApp.apiEndpoint, {
            "method": "getTodoItems"
          },
          function(data) {
            var todoItemArr = data;
            $.each(todoItemArr, function(index, value) {
              todoApp.addTodoItemToTable(value.id, value.name, value.category, value.complete);
            });
          },
          "json");
      },
    
      updateTodoItem: function(id, isComplete) {
        $.post(todoApp.apiEndpoint, {
            "method": "updateTodoItem",
            "todoItemId": id,
            "todoItemComplete": isComplete
          },
          function(data) {},
          "json");
      },
    
      /*
       * UI Methods
       */
      addTodoItemToTable: function(id, name, category, isComplete) {
        var rowColor = isComplete ? "active" : "warning";
    
        todoApp.ui_table().append($("<tr>")
          .append($("<td>").text(name))
          .append($("<td>").text(category))
          .append($("<td>")
            .append($("<input>")
              .attr("type", "checkbox")
              .attr("id", id)
              .attr("checked", isComplete)
              .attr("class", "isComplete")
            ))
          .addClass(rowColor)
        );
      },
    
      /*
       * UI Bindings
       */
      bindCreateButton: function() {
        todoApp.ui_createButton().click(function() {
          todoApp.createTodoItem(todoApp.ui_createNameInput().val(), todoApp.ui_createCategoryInput().val(), false);
          todoApp.ui_createNameInput().val("");
          todoApp.ui_createCategoryInput().val("");
        });
      },
    
      bindUpdateButton: function() {
        todoApp.ui_updateButton().click(function() {
          // Disable button temporarily.
          var myButton = $(this);
          var originalText = myButton.text();
          $(this).text("Updating...");
          $(this).prop("disabled", true);
    
          // Call api to update todo items.
          $.each(todoApp.ui_updateId(), function(index, value) {
            todoApp.updateTodoItem(value.name, value.value);
            $(value).remove();
          });
    
          // Re-enable button.
          setTimeout(function() {
            myButton.prop("disabled", false);
            myButton.text(originalText);
          }, 500);
        });
      },
    
      bindUpdateCheckboxes: function() {
        todoApp.ui_table().on("click", ".isComplete", function(event) {
          var checkboxElement = $(event.currentTarget);
          var rowElement = $(event.currentTarget).parents('tr');
          var id = checkboxElement.attr('id');
          var isComplete = checkboxElement.is(':checked');
    
          // Togle table row color
          if (isComplete) {
            rowElement.addClass("active");
            rowElement.removeClass("warning");
          } else {
            rowElement.removeClass("active");
            rowElement.addClass("warning");
          }
    
          // Update hidden inputs for update panel.
          todoApp.ui_updateForm().children("input[name='" + id + "']").remove();
    
          todoApp.ui_updateForm().append($("<input>")
            .attr("type", "hidden")
            .attr("class", "updateComplete")
            .attr("name", id)
            .attr("value", isComplete));
    
        });
      },
    
      /*
       * UI Elements
       */
      ui_createNameInput: function() {
        return $(".todoForm #inputItemName");
      },
    
      ui_createCategoryInput: function() {
        return $(".todoForm #inputItemCategory");
      },
    
      ui_createButton: function() {
        return $(".todoForm button");
      },
    
      ui_table: function() {
        return $(".todoList table tbody");
      },
    
      ui_updateButton: function() {
        return $(".todoUpdatePanel button");
      },
    
      ui_updateForm: function() {
        return $(".todoUpdatePanel form");
      },
    
      ui_updateId: function() {
        return $(".todoUpdatePanel .updateComplete");
      },
    
      /*
       * Install the TodoApp
       */
      install: function() {
        todoApp.bindCreateButton();
        todoApp.bindUpdateButton();
        todoApp.bindUpdateCheckboxes();
    
        todoApp.getTodoItems();
      }
    };
    
    $(document).ready(function() {
      todoApp.install();
    });
    
  3. 後はアプリケーションをテストするだけです。 アプリケーションをローカルで実行し、項目の名前とカテゴリを入力して、 [Add Task] をクリックして、いくつかの Todo 項目を追加します。 項目が表示されたら、チェックボックスを切り替え、 [Update Tasks] をクリックして、その項目が完了しているかどうかを確認できます。

Azure Web Sites に Java アプリケーションをデプロイする

Azure Web Sites での Java アプリケーションのデプロイは簡単です。アプリケーションを WAR ファイルとしてエクスポートし、ソース管理 (例: Git) または FTP を使用してアップロードするだけです。

  1. アプリケーションを WAR ファイルとしてエクスポートするには、プロジェクト エクスプローラーでプロジェクトを右クリックし、[エクスポート] を選択し、次に [WAR ファイル] を選択します。

  2. [WAR Export] ウィンドウで、次の操作を行います。

    • [Web project](Web プロジェクト) ボックスに、「azure-cosmos-java-sample」と入力します。
    • [Destination] ボックスでエクスポート先を選択し、WAR ファイルを保存します。
    • [完了] を選択します。
  3. これで WAR ファイルを Azure Web サイトの webapps ディレクトリにアップロードできます。 ファイルのアップロード手順については、「Azure App Service Web Apps への Java アプリケーションの追加」を参照してください。 WAR ファイルを webapps ディレクトリにアップロードすると、ランタイム環境により、ファイルの追加が検出され、ファイルが自動的に読み込まれます。

  4. 完成した製品を表示するには、http://YOUR\_SITE\_NAME.azurewebsites.net/azure-cosmos-java-sample/ に移動し、タスクの追加を開始します。

GitHub からのプロジェクトの入手

このチュートリアルのサンプルはすべて、GitHub の todo プロジェクトに含まれています。 todo プロジェクトを Eclipse にインポートするには、「 前提条件 」セクションに記載されているソフトウェアおよびリソースがあることを確認したうえで、以下の手順に従います。

  1. Project Lombokをインストールします。 Lombok は、プロジェクトのコンストラクター、getter、setter の生成に使用します。 lombok.jar ファイルをダウンロードしたら、ファイルをダブルクリックしてコマンド ラインからインストールします。

  2. Eclipse が開いている場合は、いったん終了してから再起動して Lombok を読み込みます。

  3. Eclipse で、[ファイル] メニューの [インポート] を選択します。

  4. [インポート] ウィンドウで [Git] を選択し、[Git のプロジェクト] を選択し、次に [次へ] を選択します。

  5. [リポジトリ ソースの選択] 画面で、 [URI の複製] を選択します。

  6. [ソース Git リポジトリ] 画面の [URI] ボックスに「https://github.com/Azure-Samples/azure-cosmos-java-sql-api-todo-app」と入力し、[次へ] を選択します。

  7. [分岐選択] 画面で、[メイン] が選択されていることを確認し、[次へ] を選択します。

  8. [ローカル コピー先] 画面で、[参照] を選択してリポジトリをコピーするフォルダーを選択し、[次へ] を選択します。

  9. [プロジェクトをインポートするために使用するウィザードの選択] 画面で、[既存のプロジェクトのインポート] が選択されていることを確認し、[次へ] を選択します。

  10. [プロジェクトのインポート] 画面で、DocumentDB プロジェクトを選択解除し、次に [完了] を選択します。 DocumentDB プロジェクトには、依存関係として追加される Azure Cosmos DB Java SDK が含まれています。

  11. Project Explorer で、azure-cosmos-java-sample\src\com.microsoft.azure.cosmos.sample.dao\DocumentClientFactory.java を表示し、[HOST] 値と [MASTER_KEY] 値を Azure Cosmos DB アカウントの URI とプライマリ キーで置き換え、ファイルを保存します。 詳細については、手順 1 の Azure Cosmos DB データベース アカウントの作成に関する記事を参照してください。

  12. プロジェクト エクスプローラー で、azure-cosmos-java-sample を右クリックし、[ビルド パス] を選択し、次に [ビルド パスの構成] を選択します。

  13. [Java ビルド パス] 画面の右ウィンドウで [ライブラリ] タブを選択し、次に [外部 JAR の追加] をクリックします。 lombok.jar ファイルの場所を参照し、[開く] を選択し、次に [OK] を選択します。

  14. 手順 12. を使用してもう一度 [プロパティ] ウィンドウを開き、左ウィンドウの [ターゲット ランタイム] を選択します。

  15. [ターゲット ランタイム] 画面で、[新規] を選択、[Apache Tomcat v7.0] を選択し、次に [OK] を選択します。

  16. 手順 12. を使用してもう一度 [プロパティ] ウィンドウを開き、左ウィンドウの [プロジェクト ファセット] を選択します。

  17. [プロジェクト ファセット] 画面で、[動的 Web モジュール][Java] を選択し、次に [OK] を選択します。

  18. 画面の下部の [サーバー] タブで、[ローカルホストの Tomcat v7.0 サーバー] を右クリックし、[追加と削除] を選択します。

  19. [追加と削除] ウィンドウで、azure-cosmos-java-sample[構成済み] ボックスに移動し、[完了] を選択します。

  20. [サーバー] タブで、[ローカルホストの Tomcat v7.0 サーバー] を右クリックし、次に [再起動] を選択します。

  21. ブラウザーで http://localhost:8080/azure-cosmos-java-sample/ に移動し、タスク一覧への追加を開始します。 既定のポート値を変更している場合は、8080 に代えて、使用している値を指定してください。

  22. プロジェクトを Azure Web サイトにデプロイする方法については、「手順 6: Azure Web Sites にアプリケーションをデプロイする」を参照してください。

次のステップ

Azure Cosmos DB への移行のための容量計画を実行しようとしていますか? 容量計画のために、既存のデータベース クラスターに関する情報を使用できます。