教學課程:使用 Azure Cosmos DB 和 API for NoSQL 來建置 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 為基礎的工作管理應用程式,方便您建立、抓取以及將工作標示為完成,如下圖所示。 在 Azure Cosmos DB 中,[待辦事項] 清單中的每項工作都會以 JSON 文件的形式儲存。

My ToDo List Java application

提示

本應用程式開發教學課程假設您先前已有使用 Java 的經驗。 如果您不熟悉 Java 或必備工具,我們建議您從 GitHub 下載完整的待辦事項專案,並使用本文結尾的指示開始建置。 建置完成後,您可以檢閱文件,以加深對專案內容中程式碼的了解。

針對此 Java Web 應用程式教學課程的必要條件

開始進行本應用程式開發教學課程之前,您必須具備下列條件:

如果您是第一次安裝這些工具,coreservlets.com 提供了安裝程序的的逐步解說,請參閱其 教學課程:安裝 TomCat7 並與 Eclipse 搭配使用一文中的 [快速入門] 區段。

建立 Azure Cosmos DB 帳戶

我們將從建立 Azure Cosmos DB 帳戶開始著手。 如果您已經擁有帳戶,或如果您正在使用 Azure Cosmos DB 模擬器來進行本教學課程,可以跳到步驟 2:建立 Java JSP 應用程式

  1. 從 Azure 入口網站功能表或 [首頁] 頁面,選取 [建立資源]

  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
    • Table
    • Apache Gremlin,適用於圖形資料

    若要深入瞭解適用於 NoSQL 的 API,請參閱歡迎使用 Azure Cosmos DB

  4. 在 [建立 Azure Cosmos DB 帳戶] 頁面中,輸入新 Azure Cosmos DB 帳戶的基本設定。

    設定 Description
    訂用帳戶 訂用帳戶名稱 選取您要用於此 Azure Cosmos DB 帳戶的 Azure 訂用帳戶。
    資源群組 資源群組名稱 選取資源群組,或選取 [新建],然後輸入新資源群組的唯一名稱。
    客戶名稱 唯一名稱 輸入名稱來識別您的 Azure Cosmos DB 帳戶。 因為 documents.azure.com 會附加到您所提供的名稱以建立 URI,請使用唯一名稱。 名稱只能包含小寫字母、數字及連字號 (-) 字元。 其必須是 3-44 個字元。
    Location 最接近使用者的區域 選取用來裝載 Azure Cosmos DB 帳戶的地理位置。 使用最接近使用者的位置,讓他們能以最快速度存取資料。
    容量模式 佈建輸送量無伺服器 選取 [佈建的輸送量],以佈建的輸送量模式建立帳戶。 選取 [無伺服器],以無伺服器模式建立帳戶。
    申請 Azure Cosmos DB 免費階層折扣 適用不適用 使用 Azure Cosmos DB 免費層,您便能在帳戶中免費取得前 1000 RU/秒和 25 GB 的儲存體。 深入了解免費層
    限制帳戶總輸送量 是否選取 限制可在此帳戶上佈建的總輸送量。 此限制可防止與佈建輸送量相關的非預期費用。 建立您的帳戶之後,您可以更新或移除此限制。

    每個 Azure 訂用帳戶最多可以有一個免費層的 Azure Cosmos DB 帳戶,而且必須在建立帳戶時選擇加入。 若您並未看到套用免費層折扣的選項,則訂用帳戶中的另一個帳戶已透過免費層啟用。

    Screenshot shows the Create Azure Cosmos DB Account page.

    注意

    如果您選取 [無伺服器] 作為容量模式,則無法使用下列選項:

    • 套用免費層折扣
    • 限制帳戶總輸送量
  5. 在 [全域散發] 索引標籤中,設定下列詳細資料。 您可以保留預設值以用於本快速入門:

    設定 Description
    異地備援 停用 藉由將您的區域與配對區域進行配對,在您的帳戶上啟用或停用全域散發。 您可以在稍後將更多區域新增至您的帳戶。
    多重區域寫入 停用 多重區域寫入功能可讓您利用在全球為資料庫及容器佈建的輸送量。
    可用性區域 停用 可用性區域可協助您進一步改善應用程式的可用性和復原能力。

    注意

    如果您在上一個 [基本] 頁面中選取 [無伺服器] 作為 [容量模式],則無法使用下列選項:

    • 異地備援
    • 多重區域寫入
  6. 您可以選擇在下列索引標籤中設定其他詳細資料:

    • 網路功能。 設定從虛擬網路存取
    • 備份原則。 設定定期連續備份原則。
    • 加密。 使用服務受控金鑰或客戶自控金鑰
    • 標籤。 籤標籤為成對的名稱和數值,可讓您透過將相同標籤套用至多個資源與資源群組,進而分類資源並檢視合併的帳單。
  7. 選取 [檢閱 + 建立]。

  8. 檢閱帳戶設定,然後選取 [建立]。 建立帳戶需要幾分鐘的時間。 請等候入口網站頁面顯示 [您的部署已完成]

    Screenshot shows that your deployment is complete.

  9. 選取 [前往資源] 前往 Azure Cosmos DB 帳戶頁面。

    Screenshot shows the Azure Cosmos DB account page.

移至 Azure Cosmos DB 帳戶頁面,然後選取 [金鑰]。 複製要在您接下來建立的 Web 應用程式中使用的值。

Screenshot of the Azure portal with the Keys button highlighted on the Azure Cosmos DB account page

建立 Java JSP 應用程式

建立 JSP 應用程式:

  1. 首先,我們將從建立 Java 專案開始。 啟動 Eclipse,然後選取 [檔案]、[新增],然後選取 [動態 Web 專案]。 如果您在可用專案中沒有看到 [動態 Web 專案],請執行下列動作:依序選取 [檔案]、[新增]、[專案],展開 [Web],選取 [動態 Web 專案],然後選取 [下一步]

    JSP Java Application Development

  2. 在 [專案名稱] 方塊中輸入專案名稱,然後在 [目標執行階段] 下拉式選單中,選擇性地選取值 (例如 Apache Tomcat v7.0),然後選取 [完成]。 選取目標執行階段可讓您透過 Eclipse 在本機執行專案。

  3. 在 Eclipse 的 [專案總管] 檢視中,展開您的專案。 在 [WebContent] 上按一下滑鼠右鍵,接著選取 [新增],然後選取 [JSP 檔案]

  4. 在 [新增 JSP 檔案] 對話方塊中,將檔案命名為 index.jsp。 將父資料夾保持為 WebContent,如下圖所示,然後選取 [下一步]

    Make a New JSP File - Java Web Application Tutorial

  5. 在 [選取 JSP 範本] 對話方塊中,基於本教學課程的目的,選取 [新增 JSP 檔案 (html)],然後選取 [完成]

  6. 在 Eclipse 中開啟 index.jsp 檔案後,請在現有的 <body> 元素內加入文字來顯示 Hello World!。 已更新的 <body> 內容看起來應該與下列程式碼類似:

    <body>
      <% out.println("Hello World!"); %>
    </body>
    
  7. 儲存 index.jsp 檔案。

  8. 如果您在步驟 2 中已設定目標執行階段,就可以依序選取 [專案] 和 [執行],即可在本機執行您的 JSP 應用程式:

    Hello World – Java Application Tutorial

安裝 SQL Java SDK

導入 SQL Java SDK 及其相依性的最簡單方式就是透過 Apache Maven。 若要這樣做,您必須使用下列步驟將專案轉換成 Maven 專案:

  1. 在 [專案總管] 中,以滑鼠右鍵按一下您的專案、選取 [設定],然後選取 [轉換成 Maven 專案]

  2. 在 [建立新的 POM] 視窗中,接受預設值,然後選取 [完成]

  3. 在 [專案總管] 中,開啟 pom.xml 檔案。

  4. 在 [相依性] 窗格的 [相依性] 索引標籤中,選取 [新增]

  5. 在 [選取相依性] 視窗中,執行下列動作:

    • 在 [群組識別碼] 方塊中,輸入 com.azure
    • 在 [成品識別碼] 方塊中,輸入 azure-cosmos
    • 在 [版本] 方塊中,輸入 4.11.0

    或者,您可以將群組識別碼和成品識別碼的相依性 XML 直接新增至 pom.xml 檔案:

    <dependency>
      <groupId>com.azure</groupId>
      <artifactId>azure-cosmos</artifactId>
      <version>4.11.0</version>
    </dependency>
    
  6. 選取 [確定],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) 類別

建立資料存取物件 (DAO),將待辦事項提取保存至 Azure Cosmos DB。 為了將 ToDo 項目儲存至集合,用戶端必須知道要保存至哪個資料庫和集合 (會被自我連結參照)。 一般而言,最好是儘可能快取資料庫和集合,以避免對資料庫進行額外的來回存取。

  1. 若要叫用 Azure Cosmos DB 服務,您必須將新的 cosmosClient物件具現化。 一般而言,最好是重複使用 cosmosClient 物件,而不要針對每個後續要求建構新的用戶端。 您可以在 cosmosClientFactory 類別內定義用戶端,以便重複使用。 更新您在步驟 1儲存的 HOST 和 MASTER_KEY 值。 使用您的 URI 取代 HOST 變數,並以您的 PRIMARY KEY 取代 MASTER_KEY。 使用下列程式碼,在 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 檔案並新增 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 類別。 此類別會定義程式碼以將 TodoItem 保存到容器中、擷取您的資料庫和集合 (如果存在),或建立新的 (如果不存在的話)。 此範例會使用 Gson 將 TodoItem Plain Old Java Objects (POJO) 序列化及還原序列化成 JSON 文件。 為了將 ToDo 項目儲存至集合,用戶端必須知道要保存至哪個資料庫和集合 (會被自我連結參照)。 這個類別也會定義協助程式函式,依另一個屬性 (例如"ID") 來擷取文件 (而不是自我連結)。 您可以使用協助程式方法來依 ID 擷取 TodoItem JSON 文件,然後再將其還原序列化成 POJO。

    您也可使用 cosmosClient 用戶端物件,利用 SQL 查詢取得 TodoItem 的集合或清單。 最後,您可定義 delete 方法以刪除清單中的 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 檔案,然後新增 TodoDaoFactory 類別,以建立新的 DocDbDao 物件:

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

建立 Serlet

接著,建立一個可將 HTTP 要求遞送至控制器的 Servlet。 建立 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. 最後,撰寫一些用戶端 JavaScript,以將 Web 使用者介面與 Servlet 繫結在一起:

    /**
     * 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. 現在只剩下測試應用程式。 在本機執行應用程式,並填入項目名稱和類別,然後按一下 [ 新增工作] 來新增一些待辦事項。 在項目出現後,您可以切換勾選核取方塊,然後按一下 [更新工作],來更新其完成狀態。

將 Java 應用程式部署至 Azure 網站

Azure 網站讓部署 Java 應用程式變得相當簡單,您只需將應用程式匯出成 WAR 檔案,然後透過原始檔控制 (例如 Git) 或 FTP 上傳它即可。

  1. 若要將應用程式匯出成 WAR 檔案,請以滑鼠右鍵按一下您在專案總管中的專案、選取 [匯出],然後選取 [WAR 檔案]

  2. 在 [WAR 匯出] 視窗中,執行下列動作:

    • 在 [Web 專案] 方塊中,輸入 azure-cosmos-java-sample。
    • 在 [目的地] 方塊中,選擇用來儲存 WAR 檔案的目的地。
    • 選取 [完成]。
  3. 現在您手上已經有了 WAR 檔案,您只需將它上傳至您 Azure 網站的 webapps 目錄即可。 如需上傳檔案的相關指示,請參閱將 Java 應用程式新增至 Azure App Service Web Apps。 將 WAR 檔案上傳至 webapps 目錄之後,執行階段環境便會偵測到您已新增該檔案並自動將其載入。

  4. 若要檢視您已完成的產品,請瀏覽至 http://YOUR\_SITE\_NAME.azurewebsites.net/azure-cosmos-java-sample/ 並開始新增您的工作!

從 GitHub 取得的專案

本教學課程中的所有範例都包含在 GitHub 上的 待辦事項 專案中。 若要將 todo 專案匯入 Eclipse,請確認您擁有 必要條件 區段中所列出的軟體和資源,然後執行下列動作:

  1. 安裝 專案 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. 在 [專案總管] 中,瀏覽至 azure-cosmos-java-sample\src\com.microsoft.azure.cosmos.sample.dao\DocumentClientFactory.java,並將 HOST 和 MASTER_KEY 值換成您的 Azure Cosmos DB 帳戶的 URI 和 PRIMARY KEY,然後儲存檔案。 如需詳細資訊,請參閱步驟 1。建立 Azure Cosmos DB 資料庫帳戶

  12. 在 [專案總管] 中,以滑鼠右鍵按一下 [azure-cosmos-java-sample],選取 [組建路徑],然後選取 [設定組建路徑]

  13. 在 [Java 組建路徑] 畫面的右窗格中,選取 [程式庫] 索引標籤,然後選取 [新增外部 JAR]。 瀏覽至 lombok.jar 檔案的位置,選取 [開啟],然後選取 [確定]

  14. 使用步驟 12 重新開啟 [屬性] 視窗,然後在左窗格中選取 [目標執行階段]

  15. 在 [目標執行階段] 畫面上,依序選取 [新增]、[Apache Tomcat v7.0],然後選取 [確定]

  16. 使用步驟 12 重新開啟 [屬性] 視窗,然後在左窗格中選取 [專案 Facet]

  17. 在 [專案 Facet] 畫面上,選取 [動態 Web 模組] 和 [Java],然後選取 [確定]

  18. 在螢幕底部的 [伺服器] 索引標籤上,以滑鼠右鍵按一下 [在 localhost 的 Tomcat v7.0 伺服器],然後選取 [新增和移除]

  19. 在 [新增和移除] 視窗中,將 [azure-cosmos-java-sample] 移至 [已設定] 方塊,然後選取 [完成]

  20. 在 [伺服器] 索引標籤上,以滑鼠右鍵按一下 [Tomcat v7.0 Server at localhost] \(在 localhost 的 Tomcat v7.0 伺服器),然後選取 [重新啟動]

  21. 在瀏覽器中,瀏覽至 http://localhost:8080/azure-cosmos-java-sample/,並開始新增到工作清單。 請注意,如果您之前變更預設的連接埠值,請將 8080 變更為您所選取的值。

  22. 若要將您的專案部署至 Azure 網站,請參閱步驟 6:將應用程式部署至 Azure 網站

下一步

正在嘗試為遷移至 Azure Cosmos DB 進行容量規劃嗎? 您可以使用現有資料庫叢集的相關資訊進行容量規劃。