教程:使用 Azure Cosmos DB 和 SQL API 构建 Java Web 应用程序

适用于: SQL API

本 Java Web 应用程序教程介绍了如何使用 Azure Cosmos DB 服务通过 Azure 应用服务 Web 应用上托管的 Java 应用程序来存储和访问数据。 在本文中,将学习以下内容:

  • 如何在 Eclipse 中构建基本 JavaServer Pages (JSP) 程序。
  • 如何使用 Azure Cosmos DB Java SDK 来处理 Azure Cosmos DB 服务。

此 Java 应用程序教程演示了如何创建一个基于 web 的任务管理应用程序,可以使用此应用程序创建、检索任务,以及将任务标记为已完成,如下图所示。 ToDo 列表中的每个任务都存储为 Azure Cosmos DB 中的 JSON 文档。

我的待办事项列表 Java 应用程序

提示

此应用程序开发教程假定你之前有使用 Java 的经验。 如果不熟悉 Java 或必备工具,我们建议从 GitHub 下载完整的 todo 项目,并按照本文末尾的说明生成该项目。 构建之后,可以回顾本文以深入了解项目上下文中的代码。

此 Java Web 应用程序教程的先决条件

在开始本应用程序开发教程前,必须具有:

创建 Azure Cosmos DB 帐户

让我们首先创建一个 Azure Cosmos DB 帐户。 如果已有一个帐户,或者要在本教程中使用 Azure Cosmos DB 模拟器,可以跳到步骤 2:创建 Java JSP 应用程序

  1. 在 Azure 门户菜单或主页中,选择“创建资源” 。

  2. 在“新建”页面中搜索“Azure Cosmos DB”,然后选择它。

  3. 在“Azure Cosmos DB”页上,选择“创建”。

  4. 在“创建 Azure Cosmos DB 帐户”页中,输入新 Azure Cosmos 帐户的基本设置。

    设置 说明
    订阅 订阅名称 选择要用于此 Azure Cosmos 帐户的 Azure 订阅。
    资源组 资源组名称 选择一个资源组,或者选择“新建”,然后输入新资源组的唯一名称。
    帐户名 唯一的名称 输入标识此 Azure Cosmos 帐户的名称。 由于 documents.azure.cn 将追加到所提供的名称以创建 URI,因此,请使用唯一的名称。

    名称只能包含小写字母、数字和连字符 (-)。 它的长度必须介于 3-44 个字符之间。
    API 要创建的帐户的类型 选择“Core (SQL)”,以便使用 SQL 语法创建文档数据库并进行查询。

    API 确定要创建的帐户的类型。 Azure Cosmos DB 提供五种 API:适用于文档数据的 Core (SQL) 和 MongoDB、适用于图形数据的 Gremlin、Azure 表和 Cassandra。 目前,你必须为每种 API 创建单独的帐户。

    详细了解 SQL API
    位置 离用户最近的区域 选择用于托管 Azure Cosmos DB 帐户的地理位置。 使用离用户最近的位置,使他们能够以最快的速度访问数据。
    容量模式 预配吞吐量或无服务器 选择“预配吞吐量”以在预配吞吐量模式下创建帐户。 选择“无服务器”以在无服务器模式下创建帐户。
    应用免费层折扣 “应用”或“不应用” 使用 Azure Cosmos DB 免费层,你将在帐户中获得每秒前 1000 RU 的免费吞吐量和 25 GB 的免费存储。 了解免费层的详细信息。

    注意

    每个 Azure 订阅最多可以有一个免费层 Azure Cosmos DB 帐户,并且你必须在创建帐户时选择加入使用。 如果看不到用于应用免费层折扣的选项,这意味着订阅中的另一个帐户已启用免费层。

    Azure Cosmos DB 的“新建帐户”页面

  5. 在“全局分发”选项卡中,配置以下详细信息。 对于本快速入门,可以保留默认值:

    设置 说明
    异地冗余 禁用 通过将你的区域与某个配对区域进行配对来启用或禁用帐户的多区域分发。 稍后可以将更多区域添加到帐户。
    多区域写入 禁用 借助多区域写入功能,可以利用全中国的数据库和容器的预配吞吐量。

    注意

    如果选择“无服务器”作为“容量模式”,则以下选项不可用 :

    • 应用免费层折扣
    • 异地冗余
    • 多区域写入
  6. (可选)可以在以下选项卡中配置其他详细信息:

    • 网络 - 配置来自虚拟网络的访问

    • 备份策略 - 配置定期备份策略。

    • 加密 - 使用服务管理的密钥或客户管理的密钥

    • 标记 - 标记是名称/值对,通过将相同的标记应用到多个资源和资源组,可以对资源进行分类并查看合并的账单。

  7. 选择“查看 + 创建”。

  8. 检查帐户设置,然后选择“创建”。 创建帐户需要几分钟时间。 等待门户页显示“你的部署已完成”消息。

    Azure 门户“通知”窗格

  9. 选择“转到资源”,转到 Azure Cosmos DB 帐户页。

    Azure Cosmos DB 帐户页面

转到 Azure Cosmos DB 帐户页,并选择“密钥”。 复制要在下一步创建的 Web 应用程序中使用的值。

Azure 门户的 Azure Cosmos DB 帐户页上突出显示密钥按钮的屏幕截图

创建 Java JSP 应用程序

若要创建 JSP 应用程序,请执行以下步骤:

  1. 首先,我们将从创建 Java 项目开始。 启动 Eclipse,并依次单击“文件”、“新建”和“动态 Web 项目”。 如果未看到“动态 Web 项目”作为可用项目列出,请执行下列操作:依次单击“文件”、“新建”和“项目”,展开“Web”,单击“动态 Web 项目”,并单击“下一步”。

    JSP Java 应用程序开发

  2. 在“项目名称”框中输入项目名称,在“目标运行时”下拉菜单中随意选择一个值(例如 Apache Tomcat v7.0),并单击“完成”。 选择目标运行可通过 Eclipse 在本地运行项目。

  3. 在 Eclipse 的项目资源管理器视图中,展开项目。 右键单击“WebContent”,单击“新建”,并单击“JSP 文件”。

  4. 在“新建 JSP 文件”对话框中,将文件命名为 index.jsp。 将父文件夹保留为 WebContent,如下图所示,然后单击“下一步”。

    创建新的 JSP 文件 - Java Web 应用程序教程

  5. 对于本教程,请在“选择 JSP 模板”对话框中选择“新建 JSP 文件(html)”,并单击“完成”。

  6. index.jsp 文件在 Eclipse 中打开时,请添加文本以Hello World 元素中的 索引! 。 更新后的 <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. 在“项目资源管理器” 中,打开 pom.xml 文件。

  4. 在“依赖项”选项卡上,在“依赖项”窗格中单击“添加”。

  5. 在“选择依赖项” 窗口中,执行以下操作:

    • 在" 组 ID" 框中,输入
    • 在" 项目 ID" 框中,输入
    • 在" 版本" 框中,输入

    或者,可以直接将组 ID 和项目 ID 的依赖项 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),将 ToDo 项保存到 Azure Cosmos DB 的过程进行抽象。 要将 ToDo 项保存到集合中,客户端需要知道保存到哪个数据库和集合(通过自链接引用) 通常,如果可能的话最好缓存数据库和集合,以避免额外的往返访问数据库。

  1. 若要调用 Azure Cosmos DB 服务,必须实例化一个新的 cosmosClient 对象。 一般情况下,最好是重用 cosmosClient,而不是为每个后续请求构造新的客户端。 可以通过在 cosmosClientFactory 类中定义客户端来重用它。 更新在步骤 1 中保存的 HOST 和 MASTER_KEY 值。 将 HOST 替换为 URI,将 MASTER_KEY 替换为主密钥。 使用以下代码在 CosmosClientFactoryCosmosClientFactory 文件中创建 类:

    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 项:

    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 文件并添加 类,此类实现 类以 对项执行 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 文件并添加 类。 此类定义的代码用于将 TodoItem 保存到容器中,在存在数据库和集合时检索数据库和集合或是在不存在时创建新的数据库和集合。 此示例使用 Gson 将 TodoItem 普通旧 Java 对象 (POJO) 序列化到 JSON 文档和从中反序列化 POJO。 要将 ToDo 项保存到集合中,客户端需要知道保存到哪个数据库和集合(通过自链接引用) 此类还定义帮助程序函数以便按另一个属性(例如“ID”)而不是自链接来检索文档。 可使用帮助程序方法按 ID 检索 TodoItem JSON 文档,然后将其反序列化到 POJO。

    我们还可以通过 cosmosClient 客户端对象使用 SQL 查询获取 TodoItem 的集合或列表。 最后,定义删除方法以从列表中删除 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 对象:

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

添加控制器

将 TodoItemController 控制器添加到应用程序。 在此项目中,将使用项目 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);
    }
}

创建 servlet

接下来,创建 servlet 以将 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. 最后,编写一些客户端 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. 现在剩下的就是测试此应用程序。 在本地运行此应用程序,并添加一些 Todo 项,方法是填充项名称和类别,并单击“添加任务” 。 显示项之后,可以通过切换复选框,然后单击“更新任务”来更新项是否已完成。

将 Java 应用程序部署到 Azure 网站

要在 Azure 网站上部署 Java 应用程序,只需将应用程序导出为 WAR 文件,然后通过源控件(例如 Git)或 FTP 上传此文件。

  1. 要将应用程序导出为 WAR 文件,请在“项目资源管理器”中右键单击项目,然后依次单击“导出”和“WAR 文件”。

  2. 在“WAR 导出” 窗口中,执行以下操作:

    • 在“Web 项目”框中,输入 azure-cosmos-java-sample。
    • 在“目标”框中,选择一个目标以保存 WAR 文件。
    • 单击“完成” 。
  3. 现在已经具有 WAR 文件,只需将它上传到 Azure 网站的 webapps 目录。 有关上传此文件的说明,请参阅将 Java 应用程序添加到 Azure 应用服务 Web 应用。 将 WAR 文件上传到 webapps 目录之后,运行时环境将检测到已经添加了此文件,并会自动加载它。

  4. 若要查看已完成的产品,请导航到 http://YOUR\_SITE\_NAME.chinacloudsites.cn/azure-cosmos-java-sample/ 并开始添加任务!

从 GitHub 获取项目

GitHub 上的 todo 项目包含本教程中的所有示例。 要将 todo 项目导入 Eclipse,请确保具有 先决条件 部分中所列的软件和资源,并执行以下操作:

  1. 安装 项目 Lombok。 Lombok 用于生成项目中的构造函数、getter 和 setter。 下载 lombok.jar 文件之后,双击此文件进行安装,或者从命令行安装。

  2. 如果 Eclipse 处于打开状态,请将其关闭并重新启动以加载 Lombok。

  3. 在 Eclipse 中,在“文件”菜单上单击“导入”。

  4. 在“导入”窗口中,依次单击“Git”、“来自 Git 的项目”和“下一步”。

  5. 在“选择存储库源”屏幕上,单击“克隆 URI”。

  6. 在"源 Git 存储库"屏幕"URI"框中,输入 ,然后单击"下一步"。

  7. 在“分支选择”屏幕上,确保已选择“main”,然后单击“下一步” 。

  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 和主密钥,然后保存该文件。 有关更多信息,请参阅步骤 1.创建 Azure Cosmos 数据库帐户

  12. 在“项目资源管理器”中,右键单击“azure-cosmos-java-sample”,单击“生成路径”,并单击“配置生成路径” 。

  13. 在“Java 生成路径”屏幕上,在右侧窗格中,选择“库”选项卡,并单击“添加外部 JAR”。 导航至 lombok.jar 文件的位置,单击“打开”,并单击“确定”。

  14. 使用步骤 12 再次打开“属性”窗口,并在左窗格中单击“目标运行时”。

  15. 在“目标运行时”屏幕上,单击“新建”,选择“Apache Tomcat v7.0”,并单击“确定”。

  16. 使用步骤 12 再次打开“属性”窗口,并在左窗格中单击“项目方面”。

  17. 在“项目方面”屏幕上,选择“动态 Web 模块”和“Java”,并单击“确定”。

  18. 在此屏幕下面的“服务器”选项卡上,右键单击“localhost 上的 Tomcat v7.0 服务器”,并单击“添加和删除”。

  19. 在“添加和删除”窗口中,将 azure-cosmos-java-sample 移到“配置”框,然后单击“完成” 。

  20. 在“服务器”选项卡上,右键单击“localhost 上的 Tomcat v7.0 服务器”,并单击“重新启动”。

  21. 在浏览器中,导航到 http://localhost:8080/azure-cosmos-java-sample/ 并开始向任务列表添加内容。 请注意,如果更改了默认端口值,请将 8080 更改成选择的值。

  22. 要将项目部署到 Azure 网站,请参阅步骤 6. 将应用程序部署到 Azure 网站

后续步骤

尝试为迁移到 Azure Cosmos DB 进行容量计划? 可以使用有关现有数据库群集的信息进行容量规划。