Руководство. Создание веб-приложения Java с помощью Azure Cosmos DB и API для NoSQL

ОБЛАСТЬ ПРИМЕНЕНИЯ: NoSQL

В этом руководстве по разработке веб-приложения Java показано, как использовать службу Microsoft Azure Cosmos DB для хранения данных и обеспечения доступа к ним из приложения Java, размещенного в Azure при помощи функции "Веб-приложения службы приложений". Без кредитной карты или подписки Azure можно настроить бесплатную пробную учетную запись Azure Cosmos DB. В этой статье вы узнаете следующее:

В данном руководстве рассказывается о том, как создать веб-приложение для управления задачами, с помощью которого можно создавать и извлекать задачи, а также помечать их как завершенные, как показано на рисунке ниже. Каждая задача из списка ToDo будет храниться в виде документов JSON в службе Azure Cosmos DB.

Приложение My ToDo List на Java

Совет

В данном учебнике по разработке приложения предполагается, что у вас имеется некоторый опыт использования Java. Если вы никогда не работали с Java или необходимыми инструментами, мы рекомендуем скачать полный учебный проект todo с портала GitHub и создать его, следуя инструкциям, приведенным в конце этой статьи. После создания ознакомьтесь со статьей, чтобы разобраться в коде этого проекта.

Необходимые условия для изучения этого учебника по разработке веб-приложения Java

Для работы с этим учебником необходимы:

При первой установке этих средств воспользуйтесь пошаговыми инструкциями, представленными в разделе "Быстрый запуск" статьи Tutorial: Installing TomCat7 and Using it with Eclipse (Руководство по установке TomCat7 и его использованию с Eclipse) на сайте coreservlets.com.

Создание учетной записи 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 для NoSQL .

    Azure Cosmos DB предоставляет несколько API:

    • NoSQL для данных документа
    • PostgreSQL
    • MongoDB для данных документа
    • Apache Cassandra
    • Таблица
    • Apache Gremlin для данных графа

    Дополнительные сведения об API для NoSQL см. в статье Добро пожаловать в Azure Cosmos DB.

  4. На странице Создание учетной записи Azure Cosmos DB введите основные параметры для новой учетной записи Azure Cosmos DB.

    Параметр Значение Описание
    Подписка имя подписки; Вы подписку Azure, которую нужно использовать для этой учетной записи Azure Cosmos DB.
    Группа ресурсов Имя группы ресурсов Выберите группу ресурсов или Создать, затем введите уникальное имя для новой группы ресурсов.
    Имя учетной записи Уникальное имя Введите имя для идентификации учетной записи Azure Cosmos DB. Так как элемент documents.azure.com добавляется к указанному вами имени для создания URI, используйте уникальное имя. Имя может содержать только строчные буквы, цифры и дефис (-). Он должен содержать от 3 до 44 символов.
    Расположение Ближайший к пользователям регион Выберите географическое расположение для размещения учетной записи Azure Cosmos DB. Используйте ближайшее к пользователям расположение, чтобы предоставить им максимально быстрый доступ к данным.
    Режим емкости Подготовленная пропускная способность или Бессерверный режим Выберите Подготовленная пропускная способность, чтобы создать учетную запись в режиме подготовленной пропускной способности. Выберите Бессерверный, чтобы создать учетную запись в режиме Бессерверный.
    Применение скидки на основе категории "Бесплатный" для Azure Cosmos DB Применить или не применять В категории "Бесплатный" Azure Cosmos DB для учетной записи бесплатно предоставляются первые 1000 единиц запросов в секунду и 25 ГБ свободного места в хранилище. Ознакомьтесь с дополнительными сведениями о категории "Бесплатный".
    Ограничение общей пропускной способности учетной записи Выбрано или нет Ограничьте общую пропускную способность, которую можно подготовить для этой учетной записи. Это ограничение предотвращает непредвиденные расходы, связанные с подготовленной пропускной способностью. Это ограничение можно обновить или удалить после создания учетной записи.

    Вы можете использовать до одной учетной записи Azure Cosmos DB уровня "Бесплатный" для каждой подписки Azure. При создании учетной записи необходимо согласиться на нее. Если вы не видите варианта подачи заявки на скидку на основе категории "Бесплатный", это означает, что в подписке уже включена другая учетная запись категории "Бесплатный".

    Снимок экрана: страница

    Примечание

    Следующие параметры недоступны, если вы выбрали значение Бессерверный для параметра Режим емкости:

    • Применение скидки на основе категории "Бесплатный"
    • Ограничение общей пропускной способности учетной записи
  5. На вкладке Глобальное распределение настройте следующие сведения. При работе с этим кратким руководством можно оставить значения по умолчанию.

    Параметр Значение Описание
    Геоизбыточность Отключить Включает или отключает глобальное распределение в вашей учетной записи, связывая ваш регион с парным регионом. В дальнейшем в учетную запись можно добавить дополнительные регионы.
    Операции записи с поддержкой нескольких регионов Отключить Поддержка записи в несколько регионов позволяет использовать подготовленную пропускную способность для баз данных и контейнеров по всему миру.
    зоны доступности; Отключить Зоны доступности помогают повысить доступность и устойчивость приложения.

    Примечание

    Следующие параметры недоступны, если выбрать бессерверный режим в качестве режима емкости на предыдущей странице Основные сведения .

    • Геоизбыточность
    • Выполнение операций записи в нескольких регионах
  6. При необходимости можно настроить дополнительные сведения на следующих вкладках:

  7. Выберите Review + create (Просмотреть и создать).

  8. Проверьте параметры учетной записи, а затем нажмите кнопку Создать. Создание учетной записи занимает несколько минут. Дождитесь, пока на странице портала появится сообщение Развертывание выполнено.

    Снимок экрана: развертывание завершено.

  9. Выберите Перейти к ресурсу, чтобы перейти на страницу учетной записи Azure Cosmos DB.

    Снимок экрана: страница учетной записи Azure Cosmos DB.

Перейдите на страницу учетной записи Azure Cosmos DB и нажмите на кнопку Ключи. Скопируйте значения, которые понадобятся в создаваемом веб-приложении.

Снимок экрана: портал Azure с выделенной кнопкой

Создание приложения Java JSP

Для создания приложения JSP:

  1. Сначала необходимо создать проект Java. Запустите Eclipse, а затем выберите File (Файл), New (Создать) и Dynamic Web Project (Динамический веб-проект). Если элемента Dynamic Web Project (Динамический веб-проект) нет в списке доступных проектов, нажмите File (Файл), выберите New (Создать), затем нажмите Project (Проект)..., разверните список Web (Интернет), выберите Dynamic Web Project (Динамический веб-проект) и нажмите Next (Далее).

    Разработка приложений JSP Java

  2. Введите имя проекта в поле Project name (Имя проекта), и в раскрывающемся меню Target Runtime (Целевая среда выполнения), если необходимо, выберите значение (например, Apache Tomcat v7.0), а затем нажмите Finish (Готово). Выбор целевой среды выполнения позволит вам запустить локальный проект через Eclipse.

  3. В обозревателе проектов Eclipse разверните проект. Щелкните правой кнопкой мыши WebContent, а затем нажмите New (Создать) и выберите JSP File (JSP-файл).

  4. В диалоговом окне New JSP File (Новый JSP-файл) укажите для файла имя index.jsp. Сохраните родительскую папку с именем WebContent, как показано на следующей иллюстрации, и нажмите Next (Далее).

    Создание файла JSP — учебник по разработке веб-приложений Java

  5. В диалоговом окне Select JSP Template (Выбор шаблона JSP) в целях обучения выберите New JSP File (html) (Новый JSP-файл (html)) и нажмите Finish (Готово).

  6. Когда файл index.jsp откроется в Eclipse, добавьте текст для отображения фразы Hello World! в существующий элемент <body>. Обновленное содержимое элемента <body> должно отображаться следующим образом:

    <body>
      <% out.println("Hello World!"); %>
    </body>
    
  7. Сохраните файл index.jsp.

  8. Если вы задали целевую среду выполнения в рамках шага 2, можете нажать Project (Проект), а затем Run (Запуск) для локального запуска приложения JSP:

    Hello World — руководство по разработке приложений Java

Установка пакета Java SDK для SQL

Самый простой способ извлечь данные из пакета Java SDK для SQL и его зависимости — использовать Apache Maven. Для этого необходимо преобразовать проект в проект Maven, выполнив приведенные ниже действия.

  1. Щелкните правой кнопкой мыши проект в обозревателе проектов, выберите Configure (Настроить) и нажмите Convert to Maven Project (Преобразовать в проект Maven).

  2. В окне Create new POM (Создать новый POM) примите параметры по умолчанию и нажмите Finish (Готово).

  3. В обозревателе проектовоткройте файл pom.xml.

  4. На вкладке Dependencies (Зависимости) в соответствующей колонке щелкните Add (Добавить).

  5. В окне Select Dependency (Выбор зависимости) выполните следующие действия.

    • В поле Group Id (Идентификатор группы) введите com.azure.
    • В поле Artifact Id (Идентификатор артефакта) введите azure-cosmos.
    • В поле Version (Версия) введите 4.11.0.

    Либо можно добавить зависимость XML для идентификаторов группы и артефакта непосредственно в файл pom.xml:

    <dependency>
      <groupId>com.azure</groupId>
      <artifactId>azure-cosmos</artifactId>
      <version>4.11.0</version>
    </dependency>
    
  6. Нажмите OK, и Maven установит пакет Java SDK для SQL или сохранит файл pom.xml.

Использование службы Azure Cosmos DB в приложении Java

Теперь мы добавим в веб-приложение модели, представления и контроллеры.

Добавление модели

Сначала определим модель в новом файле TodoItem.java. Класс TodoItem определяет схему элемента вместе с методами получения и задания:

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. Обновите значения HOST и MASTER_KEY, сохраненные на шаге 1. Замените переменную HOST значением URI, а MASTER_KEY — значением ПЕРВИЧНОГО КЛЮЧА. Используйте следующий код, чтобы создать класс CosmosClientFactory в файле CosmosClientFactory.java:

    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 для создания, обновления, чтения и удаления элементов 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 и добавьте класс MockDao. Этот класс реализует класс TodoDao для выполнения операций CRUD с элементами:

    package com.microsoft.azure.cosmos.sample.dao;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import lombok.NonNull;
    
    import com.microsoft.azure.cosmos.sample.model.TodoItem;
    
    public class MockDao implements TodoDao {
        private final Map<String, TodoItem> todoItemMap;
    
        public MockDao() {
            todoItemMap = new HashMap<String, TodoItem>();
        }
    
        @Override
        public TodoItem createTodoItem(@NonNull TodoItem todoItem) {
            if (todoItem.getId() == null || todoItem.getId().isEmpty()) {
                todoItem.setId(generateId());
            }
            todoItemMap.put(todoItem.getId(), todoItem);
            return todoItem;
        }
    
        @Override
        public TodoItem readTodoItem(@NonNull String id) {
            return todoItemMap.get(id);
        }
    
        @Override
        public List<TodoItem> readTodoItems() {
            return new ArrayList<TodoItem>(todoItemMap.values());
        }
    
        @Override
        public TodoItem updateTodoItem(String id, boolean isComplete) {
            todoItemMap.get(id).setComplete(isComplete);
            return todoItemMap.get(id);
        }
    
        @Override
        public boolean deleteTodoItem(@NonNull String id) {
            todoItemMap.remove(id);
            return true;
        }
    
        private String generateId() {
            return new Integer(todoItemMap.size()).toString();
        }
    }
    
  4. Создайте файл DocDbDao.java и добавьте класс DocDbDao. Этот класс определяет код для сохранения TodoItems в контейнере, извлекает базу данных и коллекцию, если она существует, или создает новую, если она не существует. В этом примере мы используем Gson для сериализации и десериализации элементов TodoItem POJO в документы JSON. Для сохранения элементов ToDo в коллекции клиент должен знать, какие базы данных и коллекции необходимо связать (с помощью самостоятельных ссылок). Этот класс также определяет вспомогательную функцию для получения документов с определенным атрибутом (например, ID) без использования ссылок на себя. Вспомогательный метод можно использовать для извлечения документа TodoItem JSON по идентификатору и затем десериализовать его в POJO.

    Вы также можете использовать клиентский объект cosmosClient для получения коллекции или перечня элементов TodoItems с помощью SQL-запроса. Наконец, определите метод 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. В этом проекте вы используете проект Lombok для создания конструктора, получателя, задания и построителя. Кроме того, можно написать следующий код вручную или создать его в IDE:

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

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

import lombok.NonNull;

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

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

    private static TodoItemController todoItemController;

    private final TodoDao todoDao;

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

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

        return todoDao.createTodoItem(todoItem);
    }

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

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

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

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

Создание сервлета

Далее создайте сервлет, чтобы перенаправлять запросы HTTP к контроллеру. Создайте файл ApiServlet.java и определите в нем приведенный ниже код.

package com.microsoft.azure.cosmos.sample;

import java.io.IOException;

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

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

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

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

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

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

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

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

        String apiResponse = MESSAGE_ERROR_INVALID_METHOD;

        TodoItemController todoItemController = TodoItemController
                .getInstance();

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

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

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

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

Подключите оставшуюся часть приложения Java

Теперь нам необходимо создать пользовательский интерфейс и привязать его к нашему объекту DAO.

  1. Вам потребуется пользовательский веб-интерфейс, который будет отображаться для пользователя. Повторно напишем файл 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 на стороне клиента, чтобы связать пользовательский веб-интерфейс и сервлет.

    /**
     * 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, заполнив имя элемента и категории и нажав кнопку Add Task(Добавить задачу). После появления элемента вы можете проверить его завершение, отметив флажком и щелкнув команду Обновить задачи.

Развертывание приложения Java на веб-сайтах Azure

Веб-сайты Azure упрощают развертывание приложений Java с помощью простого экспорта приложения в виде WAR-файла либо с помощью загрузки через систему управления версиями (например, GIT) или через клиент FTP.

  1. Чтобы экспортировать приложение в виде WAR-файла, щелкните правой кнопкой мыши проект в обозревателе проектов, выберите Export (Экспорт) и нажмите WAR File (WAR-файл).

  2. В окне WAR Export (Экспорт WAR-файла) выполните следующие действия:

    • В поле веб-проекта введите azure-cosmos-java-sample.
    • В поле Destination (Назначение) выберите место назначения для сохранения WAR-файла.
    • Нажмите кнопку Готово.
  3. Теперь, когда вы создали WAR-файл, можно просто передать его на веб-сайт Azure в каталог webapps. Инструкции по передаче файла см. в статье Добавление приложения Java в веб-приложения службы приложений Azure. После загрузки WAR-файла в каталог веб-приложения среда выполнения обнаруживает, что вы добавили его, и автоматически его загрузит.

  4. Чтобы оценить готовый продукт, перейдите к http://YOUR\_SITE\_NAME.azurewebsites.net/azure-cosmos-java-sample/ и приступайте к добавлению задач.

Получение проекта из GitHub

Все примеры в этом учебнике включены в проект todo на портале GitHub. Чтобы импортировать проект todo в Eclipse, убедитесь, что у вас есть программное обеспечение и ресурсы, перечисленные в разделе Необходимые условия , а затем выполните следующие действия.

  1. Установите проект Lombok. Lombok используется для формирования конструкторов, получателей и заданий в проекте. Дважды щелкните загруженный файл lombok.jar для установки или установите его из командной строки.

  2. Если открыта рабочая область Eclipse, закройте ее и запустите снова для загрузки проекта Lombok.

  3. В Eclipse откройте меню File (Файл) и нажмите Import (Импорт).

  4. В окне Import (Импорт) выберите Git, щелкните Projects from Git (Проекты из Git), а затем нажмите Next (Далее).

  5. На экране Select Repository Source (Выбрать источник репозитория) нажмите Clone URI (Клонировать URI).

  6. На экране Source Git Repository (Исходный репозиторий Git) в поле URI введите https://github.com/Azure-Samples/azure-cosmos-java-sql-api-todo-app, а затем нажмите Next (Далее).

  7. На экране Branch Selection (Выбор ветви) выберите значение main и затем нажмите Next (Далее).

  8. На экране Local Destination (Локальная папка назначения) щелкните Browse (Обзор), выберите папку, в которую можно копировать репозитории, и нажмите Next (Далее).

  9. Проверьте, установлен ли на экране Select a wizard to use for importing projects (Выбор мастера для импорта проектов) флажок Import existing projects (Импортировать существующие проекты), и нажмите Next (Далее).

  10. На экране Import Projects (Импорт проектов) снимите флажок возле проекта DocumentDB и нажмите Finish (Готово). В проекте DocumentDB содержится пакет Java SDK для Azure Cosmos DB, который будет добавлен в качестве зависимости.

  11. В обозревателе проектов перейдите в расположение azure-cosmos-java-sample\src\com.microsoft.azure.cosmos.sample.dao\DocumentClientFactory.java и замените значения HOST и MASTER_KEY значениями URI и первичного ключа своей учетной записи Azure Cosmos DB, а затем сохраните файл. Дополнительные сведения см. в разделе Шаг 1. Создайте учетную запись базы данных Azure Cosmos DB.

  12. В обозревателе проектов щелкните правой кнопкой элемент azure-cosmos-java-sample, а затем выберите Build Path (Путь сборки) и нажмите Configure Build Path (Настроить путь сборки).

  13. На экране Java Build Path (Путь сборки Java) на панели справа откройте вкладку Libraries (Библиотеки) и нажмите Add External JAR (Добавить внешние JAR-файлы). Перейдите в расположение файла lombok.jar, выберите Open (Открыть), а затем нажмите ОК.

  14. Используйте инструкцию из шага 12, то есть снова откройте окно Properties (Свойства), а затем на панели слева нажмите Targeted Runtimes (Целевые среды выполнения).

  15. На экране Targeted Runtimes (Целевые среды выполнения) щелкните New (Создать), выберите Apache Tomcat v7.0 и нажмите ОК.

  16. Используйте инструкцию из шага 12, чтобы снова открыть окно Properties (Свойства), а затем на панели слева щелкните Project Facets (Аспекты проекта).

  17. На экране Project Facets (Аспекты проекта) выберите Dynamic Web Module (Динамический веб-модуль) и Java, а затем нажмите ОК.

  18. На вкладке Servers (Серверы) в нижней части экрана щелкните правой кнопкой мыши Tomcat v7.0 Server at localhost (Tomcat v7.0 Server на localhost), а затем нажмите Add and Remove (Добавить и удалить).

  19. В окне Add and Remove (Добавить и удалить) переместите элемент azure-cosmos-java-sample в поле Configured (Настроено), а затем нажмите Finish (Готово).

  20. На вкладке Server (Сервер) щелкните правой кнопкой мыши Tomcat v7.0 Server at localhost (Сервер Tomcat версии 7.0 на localhost), а затем нажмите Restart (Перезапуск).

  21. В браузере перейдите к http://localhost:8080/azure-cosmos-java-sample/ и приступите к добавлению задач в список. Обратите внимание, что в случае изменения заданных по умолчанию значений портов необходимо изменить значение 8080 выбранным вами значением.

  22. Инструкции по развертыванию проекта на веб-сайтах Azure см. в пункте Шаг 6. Развертывание приложений на веб-сайтах Azure.

Дальнейшие действия

Пытаетесь выполнить планирование ресурсов для миграции в Azure Cosmos DB? Можете использовать для этого сведения о существующем кластере базы данных.