教程:使用 Azure Cosmos DB 和 NoSQL API 构建 Java Web 应用程序
适用范围: NoSQL
本 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 或必备工具,我们建议从 GitHub 下载完整的 todo 项目,并按照本文末尾的说明生成该项目。 构建之后,可以回顾本文以深入了解项目上下文中的代码。
此 Java Web 应用程序教程的先决条件
在开始本应用程序开发教程前,必须具有:
可以创建一个 Azure Cosmos DB 免费层帐户,你将在帐户中获得前 1000 RU/s 的免费吞吐量和 25 GB 的免费存储。 还可以使用 URI 为 https://localhost:8081
的 Azure Cosmos DB 模拟器。 有关在模拟器中使用的密钥,请参阅对请求进行身份验证。
- Java 开发工具包 (JDK) 7+。
- Eclipse IDE for Java EE Developers。
- 已启用 Java 运行时环境(例如 Tomcat 或 Jetty)的 Azure 网站。
创建 Azure Cosmos DB 帐户
让我们首先创建一个 Azure Cosmos DB 帐户。 如果已有一个帐户,或者要在本教程中使用 Azure Cosmos DB 模拟器,可以跳到步骤 2:创建 Java JSP 应用程序。
在 Azure 门户菜单或主页中,选择“创建资源” 。
搜索 Azure Cosmos DB。 选择“创建”>“Azure Cosmos DB”。
在“创建 Azure Cosmos DB 帐户”页中,输入新 Azure Cosmos DB 帐户的基本设置。
设置 值 说明 订阅 订阅名称 选择要用于此 Azure Cosmos DB 帐户的 Azure 订阅。 资源组 资源组名称 选择一个资源组,或者选择“新建”,然后输入新资源组的唯一名称。 帐户名 唯一的名称 输入标识 Azure Cosmos 帐户的名称。 由于 documents.azure.cn 将追加到所提供的名称以创建 URI,因此,请使用唯一的名称。 名称只能包含小写字母、数字和连字符 (-)。 它必须是 3-44 个字符。 API NoSQL API 确定要创建的帐户的类型。 Azure Cosmos DB 提供五种 API:适用于文档数据库的 NoSQL、适用于图形数据库的 Gremlin、适用于文档数据库的 MongoDB、Azure 表和 Cassandra。 若要详细了解 API for NoSQL,请参阅欢迎使用 Azure Cosmos DB。 位置 离用户最近的区域 选择用于托管 Azure Cosmos DB 帐户的地理位置。 使用离用户最近的位置,使他们能够以最快的速度访问数据。 容量模式 预配吞吐量或无服务器 选择“预配吞吐量”以在预配吞吐量模式下创建帐户。 选择“无服务器”以在无服务器模式下创建帐户。 应用 Azure Cosmos DB 免费层折扣 “应用”或“不应用” 如果使用 Azure Cosmos DB 免费层,在帐户中使用的前 1000 RU/s 和 25 GB 存储空间是免费的。 了解免费层的详细信息。 限制总帐户吞吐量 已选中或未选中 限制可在此帐户上预配的总吞吐量。 此限制可防止与预配吞吐量相关的意外费用。 创建帐户后,可以更新或删除此限制。 每个 Azure 订阅最多可以有一个免费层 Azure Cosmos DB 帐户,并且你必须在创建帐户时选择加入。 如果看不到用于应用免费层折扣的选项,那么订阅中的另一个帐户已启用免费层。
注意
如果选择“无服务器”作为“容量模式”,则以下选项不可用:
- 应用免费层折扣
- 限制总帐户吞吐量
在“全局分发”选项卡中,配置以下详细信息。 在本快速入门中,你可以保留默认值:
设置 值 说明 异地冗余 禁用 通过将你的区域与某个配对区域进行配对来启用或禁用帐户的多区域分发。 稍后可以将更多区域添加到帐户。 多区域写入 禁用 借助多区域写入功能,可以利用全中国的数据库和容器的预配吞吐量。 可用性区域 禁用 可用性区域有助于进一步提高应用程序的可用性和复原能力。 注意
如果在前面的“基本信息”页中选择“无服务器”作为“容量模式”,则以下选项不可用:
- 异地冗余
- 多区域写入
(可选)可以在以下选项卡中配置更多详细信息:
选择“查看 + 创建”。
检查帐户设置,然后选择“创建”。 创建帐户需要几分钟时间。 等待门户页显示“你的部署已完成”消息。
选择“转到资源”,转到 Azure Cosmos DB 帐户页。
转到 Azure Cosmos DB 帐户页,并选择“密钥”。 复制要在下一步创建的 Web 应用程序中使用的值。
创建 Java JSP 应用程序
若要创建 JSP 应用程序,请执行以下步骤:
首先,我们将从创建 Java 项目开始。 启动 Eclipse,并依次选择“文件”、“新建”和“动态 Web 项目”。 如果未看到“动态 Web 项目”作为可用项目列出,请执行下列操作:依次选择“文件”、“新建”和“项目”…,展开“Web”,选择“动态 Web 项目”,并选择“下一步”。
在“项目名称”框中输入项目名称,在“目标运行时”下拉菜单中随意选择一个值(例如 Apache Tomcat v7.0),并选择“完成”。 选择目标运行可通过 Eclipse 在本地运行项目。
在 Eclipse 的项目资源管理器视图中,展开项目。 右键单击“WebContent”,依次选择“新建”和“JSP 文件”。
在“新建 JSP 文件”对话框中,将文件命名为 index.jsp。 将父文件夹保留为 WebContent,如下图所示,然后选择“下一步”。
对于本教程,请在“选择 JSP 模板”对话框中选择“新建 JSP 文件(html)”,并选择“完成”。
在 Eclipse 中打开 index.jsp 文件后,添加文本以便在现有
<body>
元素中显示 Hello World!。 更新后的<body>
内容应类似于以下代码:<body> <% out.println("Hello World!"); %> </body>
保存 index.jsp 文件。
如果在步骤 2 中设置了目标运行时,则可以选择“项目”,并选择“运行”,在本地运行 JSP 应用程序:
安装 SQL Java SDK
提取 SQL Java SDK 及其依赖项的最简单方法是使用 Apache Maven。 要执行此操作,需要按照以下步骤将项目转换为 Maven 项目:
在项目资源管理器中右键单击项目,选择“配置”,并选择“转换为 Maven 项目”。
在“创建新 POM”窗口中,接受默认值,并选择“完成”。
在“项目资源管理器” 中,打开 pom.xml 文件。
在“依赖项”选项卡上,在“依赖项”窗格中选择“添加”。
在“选择依赖项” 窗口中,执行以下操作:
- 在“组 ID”框中,输入
com.azure
。 - 在“项目 ID”框中,输入
azure-cosmos
。 - 在“版本”框中输入
4.11.0
。
或者,可以直接将组 ID 和项目 ID 的依赖项 XML 添加到 pom.xml 文件:
<dependency> <groupId>com.azure</groupId> <artifactId>azure-cosmos</artifactId> <version>4.11.0</version> </dependency>
- 在“组 ID”框中,输入
选择“确定”,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 项保存到集合中,客户端需要知道保存到哪个数据库和集合(通过自链接引用) 通常,如果可能的话最好缓存数据库和集合,以避免额外的往返访问数据库。
- 若要调用 Azure Cosmos DB 服务,必须实例化一个新的
cosmosClient
对象。 一般情况下,最好是重用cosmosClient
,而不是为每个后续请求构造新的客户端。 可以通过在cosmosClientFactory
类中定义客户端来重用它。 更新在步骤 1 中保存的 HOST 和 MASTER_KEY 值。 将 HOST 替换为 URI,将 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;
}
}
- 创建新 TodoDao.java 文件,并添加
TodoDao
类以创建、更新、读取和删除 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);
}
- 创建新 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();
}
}
创建新 DocDbDao.java 文件,并添加
DocDbDao
类。 此类定义的代码用于将 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;
}
}
}
- 接下来,创建新 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 控制器添加到应用程序。 在此项目中,将使用项目 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 进行绑定。
- 需要一个 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>
- 最后,编写一些客户端 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();
});
- 现在剩下的就是测试此应用程序。 在本地运行此应用程序,并添加一些 Todo 项,方法是填充项名称和类别,并单击“添加任务” 。 显示项之后,可以通过切换复选框,然后单击“更新任务”来更新项是否已完成。
将 Java 应用程序部署到 Azure 网站
要在 Azure 网站上部署 Java 应用程序,只需将应用程序导出为 WAR 文件,然后通过源控件(例如 Git)或 FTP 上传此文件。
要将应用程序导出为 WAR 文件,请在“项目资源管理器”中右键单击项目,然后依次选择“导出”和“WAR 文件”。
在“WAR 导出” 窗口中,执行以下操作:
- 在“Web 项目”框中,输入 azure-cosmos-java-sample。
- 在“目标”框中,选择一个目标以保存 WAR 文件。
- 选择“完成”。
现在已经具有 WAR 文件,只需将它上传到 Azure 网站的 webapps 目录。 有关上传此文件的说明,请参阅将 Java 应用程序添加到 Azure 应用服务 Web 应用。 将 WAR 文件上传到 webapps 目录之后,运行时环境将检测到已经添加了此文件,并会自动加载它。
若要查看已完成的产品,请导航到
http://YOUR\_SITE\_NAME.chinacloudsites.cn/azure-cosmos-java-sample/
并开始添加任务!
从 GitHub 获取项目
GitHub 上的 todo 项目包含本教程中的所有示例。 要将 todo 项目导入 Eclipse,请确保具有 先决条件 部分中所列的软件和资源,并执行以下操作:
安装 项目 Lombok。 Lombok 用于生成项目中的构造函数、getter 和 setter。 下载 lombok.jar 文件之后,双击此文件进行安装,或者从命令行安装。
如果 Eclipse 处于打开状态,请将其关闭并重新启动以加载 Lombok。
在 Eclipse 中,在“文件”菜单上选择“导入”。
在“导入”窗口中,依次选择“Git”、“来自 Git 的项目”和“下一步”。
在“选择存储库源”屏幕上,选择“克隆 URI”。
在“源 Git 存储库”屏幕上的“URI”框中,输入 https://github.com/Azure-Samples/azure-cosmos-java-sql-api-todo-app,然后选择“下一步”。
在“分支选择”屏幕上,确保已选择“main”,然后选择“下一步”。
在“本地目标”屏幕上,选择“浏览”以选择要将存储库复制到的文件夹,然后选择“下一步”。
在“选择要用于导入项目的向导”屏幕上,确保已选择“导入现有项目”,并选择“下一步”。
在“导入项目”屏幕上,取消选择“DocumentDB”项目,并选择“完成”。 DocumentDB 项目包含 Azure Cosmos DB Java SDK,我们会将其添加为依赖项。
在“项目资源管理器”中,导航到 azure-cosmos-java-sample\src\com.microsoft.azure.cosmos.sample.dao\DocumentClientFactory.java,并将 HOST 和 MASTER_KEY 值替换为 Azure Cosmos DB 帐户的 URI 和主密钥,然后保存该文件。 有关详细信息,请参阅步骤 1. 创建 Azure Cosmos DB 数据库容器。
在“项目资源管理器”中,右键单击“azure-cosmos-java-sample”,选择“生成路径”,并选择“配置生成路径”。
在“Java 生成路径”屏幕上,在右侧窗格中,选择“库”选项卡,并选择“添加外部 JAR”。 导航至 lombok.jar 文件的位置,选择“打开”,并选择“确定”。
使用步骤 12 再次打开“属性”窗口,并在左窗格中选择“目标运行时”。
在“目标运行时”屏幕上,依次选择“新建”、“Apache Tomcat v7.0”和“确定”。
使用步骤 12 再次打开“属性”窗口,并在左窗格中选择“项目方面”。
在“项目方面”屏幕上,选择“动态 Web 模块”和“Java”,并选择“确定”。
在此屏幕下面的“服务器”选项卡上,右键单击“localhost 上的 Tomcat v7.0 服务器”,并选择“添加和删除”。
在“添加和删除”窗口中,将 azure-cosmos-java-sample 移到“配置”框,然后选择“完成”。
在“服务器”选项卡上,右键单击“localhost 上的 Tomcat v7.0 服务器”,并选择“重新启动”。
在浏览器中,导航到
http://localhost:8080/azure-cosmos-java-sample/
并开始向任务列表添加内容。 请注意,如果更改了默认端口值,请将 8080 更改成选择的值。要将项目部署到 Azure 网站,请参阅步骤 6. 将应用程序部署到 Azure 网站。
后续步骤
正在尝试为迁移到 Azure Cosmos DB 进行容量计划? 可以使用有关现有数据库群集的信息进行容量规划。
- 如果你只知道现有数据库群集中的 vCore 和服务器数量,请阅读根据 vCore 或 vCPU 数量估算请求单位数
- 若知道当前数据库工作负载的典型请求速率,请阅读使用 Azure Cosmos DB 容量计划工具估算请求单位