Tutorial: Criar uma aplicação Web Java com o Azure Cosmos DB e a API para NoSQL

APLICA-SE A: NoSQL

Este tutorial de aplicação Web do Java mostra-lhe como utilizar o serviço do Microsoft Azure Cosmos DB para armazenar e aceder a dados a partir de uma aplicação Java alojada em Aplicações Web do Serviço de Aplicações do Azure. Sem um cartão de crédito ou uma subscrição do Azure, pode configurar uma conta do Azure Cosmos DB gratuita. Neste artigo, vai aprender:

  • Como criar uma aplicação JSP (JavaServer Pages) básica no Eclipse.
  • Como trabalhar com o serviço do Azure Cosmos DB com o SDK Java do Azure Cosmos DB.

Este tutorial de aplicação Java mostra-lhe como criar uma aplicação de gestão de tarefas baseada na Web que lhe permite criar, obter e marcar tarefas como concluídas, conforme mostrado na imagem que se segue. Cada uma das tarefas da lista ToDo é armazenada como documentos JSON no Azure Cosmos DB.

A minha aplicação Java da ToDo List

Dica

Este tutorial do desenvolvimento de aplicações parte do princípio que tem experiência na utilização do Java. Se estiver familiarizado com o Java ou com as ferramentas dos pré-requisitos, recomendamos-lhe transferir todo o projeto todo a partir do GitHub e criá-lo utilizando as instruções no final deste artigo. Assim que o tiver criado, pode rever o artigo para obter conhecimentos aprofundados sobre o código no contexto do projeto.

Pré-requisitos para este tutorial de aplicação Web de Java

Antes de começar este tutorial de desenvolvimento de aplicação, tem de ter o seguinte:

Se estiver a instalar estas ferramentas pela primeira vez, coreservlets.com fornece instruções sobre o processo de instalação na secção de início rápido do artigo Tutorial: Instalar o TomCat7 e Utilizá-lo com o Eclipse .

Criar uma conta do Azure Cosmos DB

Comecemos por criar uma conta do Azure Cosmos DB. Se já tiver uma conta ou se estiver a utilizar o Emulador do Azure Cosmos DB para este tutorial, pode avançar para o Passo 2: Criar a aplicação Java JSP.

  1. No menu portal do Azure ou na Home page, selecione Criar um recurso.

  2. Procure Azure Cosmos DB. Selecione Criar>Azure Cosmos DB.

  3. Na página Criar uma conta do Azure Cosmos DB , selecione a opção Criar na secção Azure Cosmos DB para NoSQL .

    O Azure Cosmos DB fornece várias APIs:

    • NoSQL, para dados de documentos
    • PostgreSQL
    • MongoDB, para dados de documentos
    • Apache Cassandra
    • Tabela
    • Apache Gremlin, para dados de gráficos

    Para saber mais sobre a API para NoSQL, veja Bem-vindo ao Azure Cosmos DB.

  4. Na página Criar Conta do Azure Cosmos DB , introduza as definições básicas para a nova conta do Azure Cosmos DB.

    Definição Valor Descrição
    Subscrição Nome da subscrição Selecione a subscrição do Azure que quer utilizar para esta conta do Azure Cosmos DB.
    Grupo de Recursos Nome do grupo de recursos Selecione um grupo de recursos ou selecione Criar novo e, em seguida, introduza um nome exclusivo para o novo grupo de recursos.
    Nome da Conta Um nome exclusivo Introduza um nome para identificar a sua conta do Azure Cosmos DB. Uma vez que documents.azure.com é anexado ao nome que indicar para criar o URI, utilize um nome exclusivo. O nome só pode conter letras minúsculas, números e o caráter de hífen (-). Tem de ter entre 3 e 44 carateres.
    Localização A região mais próxima dos seus utilizadores Selecione a localização geográfica para alojar a sua conta do Azure Cosmos DB. Utilize a localização mais próxima dos utilizadores para lhes dar o acesso mais rápido aos dados.
    Modo de capacidade Débito aprovisionado ou Sem servidor Selecione Débito aprovisionado para criar uma conta no modo de débito aprovisionado . Selecione Sem servidor para criar uma conta no modo sem servidor .
    Aplicar o desconto de escalão gratuito do Azure Cosmos DB Aplicar ou Não aplicar Com o escalão gratuito do Azure Cosmos DB, obtém gratuitamente as primeiras 1000 RU/s e 25 GB de armazenamento numa conta. Saiba mais sobre o escalão gratuito.
    Limitar débito total da conta Selecionado ou não Limite a quantidade total de débito que pode ser aprovisionada nesta conta. Este limite impede custos inesperados relacionados com o débito aprovisionado. Pode atualizar ou remover este limite após a criação da sua conta.

    Pode ter até uma conta do Azure Cosmos DB de escalão gratuito por subscrição do Azure e tem de optar ativamente por participar ao criar a conta. Se não vir a opção para aplicar o desconto de escalão gratuito, outra conta na subscrição já foi ativada com o escalão gratuito.

    Captura de ecrã a mostrar a página Criar Conta do Azure Cosmos DB.

    Nota

    As seguintes opções não estão disponíveis se selecionar Sem servidor como modo de Capacidade:

    • Aplicar Desconto de Escalão Gratuito
    • Limitar débito total da conta
  5. No separador Distribuição Global , configure os seguintes detalhes. Pode deixar os valores predefinidos para este início rápido:

    Definição Valor Descrição
    Redundância Geográfica Desativar Ative ou desative a distribuição global na sua conta ao emparelhar a região com uma região de par. Pode adicionar mais regiões à sua conta mais tarde.
    Escritas de várias regiões Desativar A capacidade de escrita de várias regiões permite-lhe tirar partido do débito aprovisionado para as suas bases de dados e contentores em todo o mundo.
    Zonas de Disponibilidade Desativar Zonas de Disponibilidade ajudar a melhorar ainda mais a disponibilidade e a resiliência da sua aplicação.

    Nota

    As seguintes opções não estão disponíveis se selecionar Sem servidor como o modo de Capacidade na página Noções Básicas anterior:

    • Georredundância
    • Escritas de várias regiões
  6. Opcionalmente, pode configurar mais detalhes nos seguintes separadores:

    • Rede. Configurar o acesso a partir de uma rede virtual.
    • Política de Cópia de Segurança. Configure uma política de cópia de segurança periódica ou contínua .
    • Encriptação. Utilize uma chave gerida pelo serviço ou uma chave gerida pelo cliente.
    • Etiquetas. As etiquetas são pares nome/valor que lhe permitem categorizar recursos e ver a faturação consolidada ao aplicar a mesma etiqueta a vários recursos e grupos de recursos.
  7. Selecione Rever + criar.

  8. Reveja as definições da conta e, em seguida, selecione Criar. A criação da conta demora alguns minutos. Aguarde até que a página do portal apresente A implementação está concluída.

    Captura de ecrã a mostrar que a implementação está concluída.

  9. Selecione Ir para recurso para aceder à página da conta do Azure Cosmos DB.

    Captura de ecrã a mostrar a página da conta do Azure Cosmos DB.

Aceda à página conta do Azure Cosmos DB e selecione Chaves. Copie os valores a utilizar na aplicação Web que criar a seguir.

Captura de ecrã do portal do Azure com o botão Chaves realçado na página da conta do Azure Cosmos DB

Criar a aplicação Java JSP

Para criar a aplicação JSP:

  1. Primeiro, vamos começar por criar um projeto Java. Inicie o Eclipse, selecione Ficheiro, selecione Novo e, em seguida, selecione Projeto Web Dinâmico. Se não vir o Projeto Web Dinâmico listado como um projeto disponível, faça o seguinte: Selecione Ficheiro, selecione Novo, selecione Projeto..., expanda Web, selecione Projeto Web Dinâmico e selecione Seguinte.

    Desenvolvimento de aplicações de Java JSP

  2. Introduza um nome de projeto na caixa Nome do projeto e, no menu pendente Runtime de Destino , selecione opcionalmente um valor (por exemplo, Apache Tomcat v7.0) e, em seguida, selecione Concluir. Selecionar um tempo de execução de destino permite-lhe executar o seu projeto localmente através do Eclipse.

  3. No Eclipse, na vista do Explorador de Projeto, expanda o seu projeto. Clique com o botão direito do rato em WebContent, selecione Novo e, em seguida, selecione Ficheiro JSP.

  4. Na caixa de diálogo Novo ficheiro JSP, atribua o nome index.jsp ao ficheiro. Mantenha a pasta principal como WebContent, conforme mostrado na ilustração seguinte, e, em seguida, selecione Seguinte.

    Criar um novo ficheiro JSP - Tutorial de aplicações Web de Java

  5. Na caixa de diálogo Selecionar Modelo JSP , para efeitos deste tutorial, selecione Novo Ficheiro JSP (html) e, em seguida, selecione Concluir.

  6. Quando o ficheiro index.jsp for aberto no Eclipse, adicione texto para apresentar Hello World! dentro do elemento existente<body>. O conteúdo <body> atualizado deverá ser semelhante ao seguinte código:

    <body>
      <% out.println("Hello World!"); %>
    </body>
    
  7. Guarde o ficheiro index.jsp .

  8. Se definir um runtime de destino no passo 2, pode selecionar Project e, em seguida , Executar para executar a aplicação JSP localmente:

    Olá, Mundo – Tutorial de Aplicações Java

Instalar o SDK Java do SQL

A forma mais fácil para obter o SDK do SQL Java e as respetivas dependências é através do Apache Maven. Para tal, tem de converter o seu projeto num projeto maven com os seguintes passos:

  1. Clique com o botão direito do rato no seu projeto no Explorador de Projetos, selecione Configurar e selecioneConverter no Projeto Maven.

  2. Na janela Criar novo POM , aceite as predefinições e selecione Concluir.

  3. No Explorador de Projeto, abra o ficheiro pom.xml.

  4. No separador Dependências , no painel Dependências , selecione Adicionar.

  5. Na janela Selecionar Dependência, faça o seguinte:

    • Na caixa ID do Grupo , introduza com.azure.
    • Na caixa ID do Artefacto , introduza azure-cosmos.
    • Na caixa Versão , introduza 4.11.0.

    Em alternativa, pode adicionar o XML de dependência para o ID de Grupo e o ID do Artefacto diretamente ao ficheiro depom.xml :

    <dependency>
      <groupId>com.azure</groupId>
      <artifactId>azure-cosmos</artifactId>
      <version>4.11.0</version>
    </dependency>
    
  6. Selecione OK e o Maven irá instalar o SDK Java do SQL ou guardar o ficheiro de pom.xml.

Utilizar o serviço Azure Cosmos DB na sua aplicação Java

Agora, vamos adicionar os modelos, as vistas e os controladores à sua aplicação Web.

Adicionar um modelo

Primeiro, vamos definir um modelo dentro de um novo ficheiro TodoItem.java. A TodoItem classe define o esquema de um item juntamente com os métodos getter e 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;
    }
    
    
}

Adicionar as classes de Objetos de Acesso a Dados (DAO)

Crie um Objeto de Acesso a Dados (DAO) para abstrair os itens ToDo persistentes no Azure Cosmos DB. Para guardar os itens ToDo numa coleção, o cliente tem de saber qual a base de dados e a coleção a manter (conforme referenciado em auto-ligações). Em geral, é melhor colocar em cache a base de dados e a coleção sempre que possível, para evitar tempos de retorno adicionais para a base de dados.

  1. Para invocar o serviço Azure Cosmos DB, tem de instanciar um novo cosmosClient objeto. Em geral, é melhor reutilizar o cosmosClient objeto em vez de construir um novo cliente para cada pedido subsequente. Pode reutilizar o cliente ao defini-lo na cosmosClientFactory classe . Atualize os valores HOST e MASTER_KEY que guardou no passo 1. Substitua a variável HOST pelo URI e substitua o MASTER_KEY pela CHAVE PRIMÁRIA. Utilize o seguinte código para criar a CosmosClientFactory classe no ficheiro 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. Crie um novo ficheiro TodoDao.java e adicione a TodoDao classe para criar, atualizar, ler e eliminar os itens de tarefas:

    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. Crie um novo ficheiro MockDao.java e adicione a MockDao classe . Esta classe implementa a TodoDao classe para realizar operações CRUD nos itens:

    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. Crie um novo ficheiro DocDbDao.java e adicione a DocDbDao classe . Esta classe define o código para manter os TodoItems no contentor, obtém a base de dados e a coleção, se existir, ou criar um novo, se não existir. Este exemplo utiliza Gson para serializar e des serializar os Objetos Java Antigos (POJOs) TodoItem Plain para documentos JSON. Para guardar os itens ToDo numa coleção, o cliente tem de saber qual a base de dados e a coleção a manter (conforme referenciado em auto-ligações). Esta classe também define a função auxiliar para obter os documentos por outro atributo (por exemplo, "ID") em vez de autoligação. Pode utilizar o método auxiliar para obter um documento TodoItem JSON por ID e, em seguida, anular a serialização para um POJO.

    Também pode utilizar o cosmosClient objeto de cliente para obter uma coleção ou lista de TodoItems com uma consulta SQL. Por fim, defina o método delete para eliminar um TodoItem da sua lista. O código seguinte mostra os conteúdos da DocDbDao classe :

    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. Em seguida, crie um novo ficheiro TodoDaoFactory.java e adicione a TodoDaoFactory classe que cria um novo objeto DocDbDao:

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

Adicionar um controlador

Adicione o controlador TodoItemController à sua aplicação. Neste projeto, está a utilizar o Project Lombok para gerar o construtor, os getters, os setters e um construtor. Em alternativa, pode escrever este código manualmente ou fazer com que o IDE o gere:

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

Criar um servlet

Em seguida, crie um servlet para encaminhar pedidos HTTP para o controlador. Crie o ficheiro ApiServlet.java e defina o seguinte código abaixo do mesmo:

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

Ligar o resto da aplicação Java

Agora que terminámos os momentos divertidos, tudo o que resta é criar uma interface de utilizador rápida e transferi-la para o seu DAO.

  1. Precisa de uma interface de utilizador Web para apresentar ao utilizador. Vamos voltar a escrever o index.jsp que criámos anteriormente com o seguinte código:

    <%@ 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. Por fim, escreva algum JavaScript do lado do cliente para associar a interface de utilizador Web e o 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. Agora, só falta testar a aplicação. Execute localmente a aplicação e adicione alguns itens Todo ao indicar o nome e a categoria do item. De seguida, clique em Adicionar Tarefa. Depois de o item aparecer, pode atualizar se está concluído ao alternar a caixa de verificação e ao clicar em Atualizar Tarefas.

Implementar a sua aplicação Java em Web Sites do Azure

Os Sites do Azure tornam a implementação de aplicações Java tão simples como a exportação da sua aplicação com um ficheiro WAR e ao carregá-lo através do controlo de código fonte (por exemplo, Git) ou FTP.

  1. Para exportar a sua aplicação como um ficheiro WAR, clique com o botão direito do rato no seu projeto no Explorador de Projetos, selecione Exportar e, em seguida, selecione Ficheiro WAR.

  2. Na janela Exportar WAR, efetue o seguinte procedimento:

    • Na caixa Projeto Web, introduza azure-cosmos-java-sample.
    • Na caixa Destino, selecione um destino para guardar o ficheiro WAR.
    • Selecione Concluir.
  3. Agora que tem um ficheiro WAR, pode simplesmente carregá-lo no diretório webapps do seu Site do Azure. Para obter instruções sobre o carregamento do ficheiro, veja Adicionar uma aplicação Java às Aplicações Web do Serviço de Aplicações do Azure. Depois de o ficheiro WAR ser carregado para o diretório de aplicações Web, o ambiente de runtime irá detetar que o adicionou e irá carregá-lo automaticamente.

  4. Para ver o seu produto concluído, navegue para http://YOUR\_SITE\_NAME.azurewebsites.net/azure-cosmos-java-sample/ e comece a adicionar as suas tarefas!

Obter o projeto a partir do GitHub

Todos os exemplos deste tutorial estão incluídos no projeto todo no GitHub. Para importar o projeto todo para o Eclipse, certifique-se de que o software e os recursos estão listados na secção Pré-requisitos e, de seguida, efetue o seguinte procedimento:

  1. Instalar Project Lombok. Lombok é utilizado para gerar construtores, getters e setters no projeto. Depois de ter transferido o ficheiro lombok.jar, faça duplo clique para instalá-lo ou instale-o a partir da linha de comandos.

  2. Se o Eclipse estiver aberto, feche-o e reinicie-o para carregar o Lombok.

  3. No Eclipse, no menu Ficheiro , selecione Importar.

  4. Na janela Importar , selecione Git, selecione Projetos no Git e, em seguida, selecione Seguinte.

  5. No ecrã Selecionar Origem do Repositório , selecione Clonar URI.

  6. No ecrã Repositório Git de Origem , na caixa URI , introduza https://github.com/Azure-Samples/azure-cosmos-java-sql-api-todo-appe, em seguida, selecione Seguinte.

  7. No ecrã Seleção de Ramo , certifique-se de que a opção principal está selecionada e, em seguida, selecione Seguinte.

  8. No ecrã Destino Local , selecione Procurar para selecionar uma pasta onde o repositório possa ser copiado e, em seguida, selecione Seguinte.

  9. No ecrã Selecionar um assistente a utilizar para importar projetos , certifique-se de que a opção Importar projetos existentes está selecionada e, em seguida, selecione Seguinte.

  10. No ecrã Importar Projetos , anule a seleção do projeto DocumentDB e, em seguida, selecione Concluir. O projeto do DocumentDB contém o SDK Java do Azure Cosmos DB, que iremos adicionar como uma dependência.

  11. No Explorador de Projetos, navegue para azure-cosmos-java-sample\src\com.microsoft.azure.cosmos.sample.dao\DocumentClientFactory.java e substitua os valores HOST e MASTER_KEY pelo URI e CHAVE PRIMÁRIA da sua conta do Azure Cosmos DB e, em seguida, guarde o ficheiro. Para obter mais informações, consulte o Passo 1. Crie uma conta de base de dados do Azure Cosmos DB.

  12. No Explorador de Projetos, clique com o botão direito do rato no exemplo azure-cosmos-java, selecione Caminho da Compilação e, em seguida, selecione Configurar Caminho de Compilação.

  13. No ecrã Caminho da Compilação java , no painel direito, selecione o separador Bibliotecas e, em seguida, selecione Adicionar JARs Externos. Navegue para a localização do ficheiro lombok.jar, selecione Abrir e, em seguida, selecione OK.

  14. Utilize o passo 12 para abrir novamente a janela Propriedades e, em seguida, no painel esquerdo, selecione Runtimes Direcionados.

  15. No ecrã Runtimes Direcionados , selecione Novo, selecione Apache Tomcat v7.0 e, em seguida, selecione OK.

  16. Utilize o passo 12 para abrir novamente a janela Propriedades e, em seguida, no painel esquerdo, selecione Facetas do Projeto.

  17. No ecrã Facetas do Projeto , selecione Módulo Web Dinâmico e Java e, em seguida, selecione OK.

  18. No separador Servidores na parte inferior do ecrã, clique com o botão direito do rato em Tomcat v7.0 Server no localhost e, em seguida, selecione Adicionar e Remover.

  19. Na janela Adicionar e Remover , mova azure-cosmos-java-sample para a caixa Configurado e, em seguida, selecione Concluir.

  20. No separador Servidores , clique com o botão direito do rato em Tomcat v7.0 Server no localhost e, em seguida, selecione Reiniciar.

  21. Num browser, navegue para http://localhost:8080/azure-cosmos-java-sample/ e comece a adicionar à sua lista de tarefas. Tenha em atenção que se tiver alterado os valores de porta predefinidos, altere 8080 para o valor que selecionou.

  22. Para implementar o seu projeto num web site do Azure, veja Passo 6. Implemente a sua aplicação em Web Sites do Azure.

Passos seguintes

Está a tentar planear a capacidade de uma migração para o Azure Cosmos DB? Pode utilizar informações sobre o cluster de bases de dados existentes para o planeamento de capacidade.