Tutorial: Compilación de una aplicación web de Java mediante Azure Cosmos DB y la API para NoSQL.

SE APLICA A: NoSQL

En este tutorial de aplicación web Java aprenderá a usar el servicio Microsoft Azure Cosmos DB para almacenar datos y acceder a ellos desde una aplicación de Java hospedada en Azure App Service Web Apps. Puede configurar una cuenta gratuita de Azure Cosmos DB de prueba sin una tarjeta de crédito o una suscripción de Azure. En este artículo, aprenderá lo siguiente:

  • Cómo crear una aplicación de JavaServer Pages (JSP) básica en Eclipse.
  • Cómo trabajar con el servicio Azure Cosmos DB con el SDK de Java de Azure Cosmos DB.

Este tutorial de aplicación Java muestra cómo crear una aplicación de administración de tareas basadas en web que permite crear, recuperar y marcar tareas como completadas, tal como se muestra en la siguiente imagen. Las tareas de la lista de tareas pendientes se almacenan como documentos JSON en Azure Cosmos DB.

Aplicación Java My ToDo List

Sugerencia

En este tutorial de desarrollo de aplicaciones se supone que tiene experiencia anterior en el uso de Java. Si no está familiarizado con Java o con las herramientas de requisitos previos, se recomienda que descargue el proyecto completo todo de GitHub y lo compile mediante las instrucciones que se encuentran al final de este artículo. Una vez compilado, puede revisar el artículo para obtener información sobre el código en el contexto del proyecto.

Requisitos previos para este tutorial de aplicación web de Java

Antes de comenzar este tutorial de desarrollo de aplicaciones, debe disponer de lo siguiente:

Si va a instalar estas herramientas por primera vez, coreservlets.com proporciona un ejemplo paso a paso del proceso de instalación en la sección de inicio rápido de su artículo Tutorial: Instalación de TomCat7 y uso con Eclipse.

Creación de una cuenta de Azure Cosmos DB

Para comenzar, creemos una cuenta de Azure Cosmos DB. Si ya tiene una cuenta o si usa el Emulador de Azure Cosmos DB para este tutorial, puede ir directamente al Paso 2: Creación de la aplicación JSP de Java.

  1. En el menú de Azure Portal o en la página principal, seleccione Crear un recurso.

  2. Busque Azure Cosmos DB. Seleccione Crear>Azure Cosmos DB.

  3. En la página Crear una cuenta de Azure Cosmos DB, seleccione la opción Crear en la sección Azure Cosmos DB for NoSQL.

    Azure Cosmos DB proporciona varias API:

    • NoSQL, para datos de documento
    • PostgreSQL
    • MongoDB, para datos de documento
    • Apache Cassandra
    • Tabla
    • Apache Gremlin, para datos de grafo

    Para obtener más información sobre la API para NoSQL, consulte Bienvenido a Azure Cosmos DB.

  4. En la página Crear una cuenta de Azure Cosmos DB, especifique la configuración básica de la nueva cuenta de Azure Cosmos DB.

    Configuración Valor Descripción
    Subscription Nombre de suscripción Seleccione la suscripción de Azure que quiere usar para esta cuenta de Azure Cosmos DB.
    Grupo de recursos Definición de un nombre de grupo de recursos Seleccione un grupo de recursos o seleccione Crear nuevo y escriba un nombre único para el grupo de recursos nuevo.
    Nombre de cuenta Un nombre único Escriba un nombre para identificar la cuenta de Azure Cosmos DB. Dado que documents.azure.com se anexa al nombre que se proporciona para crear el identificador URI, debe usar un nombre único. El nombre solo puede contener letras minúsculas, números y el carácter de guion (-). Debe tener entre 3 y 44 caracteres.
    Location Región más cercana a los usuarios Seleccione una ubicación geográfica para hospedar la cuenta de Azure Cosmos DB. Use la ubicación más cercana a los usuarios para que puedan acceder de la forma más rápida posible a los datos.
    Capacity mode (Modo de capacidad) Rendimiento aprovisionado o Sin servidor Seleccione Provisioned throughput (Rendimiento aprovisionado) para crear una cuenta en modo de rendimiento aprovisionado. Seleccione Serverless (Sin servidor) para crear una cuenta en modo sin servidor.
    Aplicar el descuento del nivel Gratis de Azure Cosmos DB Aplicar o No aplicar Con el nivel Gratis de Azure Cosmos DB, recibe los primeros 1000 RU/s y 25 GB de almacenamiento gratis en una cuenta. Más información acerca del nivel Gratis.
    Límite del rendimiento total de la cuenta Seleccionado o no Limite la cantidad total de rendimiento que se puede aprovisionar en esta cuenta. Este límite evita cargos inesperados relacionados con el rendimiento aprovisionado. Puede actualizar o quitar este límite después de crear la cuenta.

    Puede tener una cuenta de Azure Cosmos DB de nivel gratis por cada suscripción de Azure y debe optar por recibirla al crear la cuenta. Si no ve la opción para aplicar el descuento por nivel Gratis, significa que el nivel Gratis ya se habilitó en otra cuenta de la suscripción.

    Captura de pantalla que muestra la página Crear cuenta de Azure Cosmos DB.

    Nota

    Las siguientes opciones no están disponibles si selecciona Serverless (Sin servidor) en Capacity mode (Modo de capacidad):

    • Aplicación de descuento por nivel Gratis
    • Límite del rendimiento total de la cuenta
  5. En la pestaña Distribución global, configure los detalles siguientes. Puede dejar los valores predeterminados para este inicio rápido:

    Configuración Valor Descripción
    Redundancia geográfica Deshabilitar Habilite o deshabilite la distribución global en su cuenta. Para ello, debe emparejar su región con una región de par. Puede agregar más regiones a su cuenta más adelante.
    Escrituras en varias regiones Deshabilitar La funcionalidad de escrituras en varias regiones le permite aprovechar el rendimiento aprovisionado para sus bases de datos y contenedores de todo el mundo.
    Zonas de disponibilidad Deshabilitar Las zonas de disponibilidad le ayudan a mejorar aún más la disponibilidad y la resistencia de una aplicación.

    Nota

    Las siguientes opciones no están disponibles si selecciona Sin servidor en Modo de capacidad en la página anterior Básico:

    • Redundancia geográfica
    • Escrituras en varias regiones
  6. De manera opcional, puede configurar más detalles en las pestañas siguientes:

    • Funciones de red. Configure el acceso desde una red virtual.
    • Directiva de copia de seguridad. Configure una directiva de copia de seguridad periódica o continua.
    • Cifrado. Use una clave administrada por el servicio o una clave administrada por el cliente.
    • Etiquetas. Las etiquetas son pares nombre-valor que permiten categorizar los recursos y ver una facturación consolidada mediante la aplicación de la misma etiqueta en varios recursos y grupos de recursos.
  7. Seleccione Revisar + crear.

  8. Revise la configuración de la cuenta y seleccione Crear. La operación de creación de la cuenta tarda unos minutos. Espere hasta que la página del portal muestre Se completó la implementación .

    Captura de pantalla que muestra que se completó la implementación.

  9. Seleccione Ir al recurso para ir a la página de la cuenta de Azure Cosmos DB.

    Captura de pantalla que muestra la página de cuenta de Azure Cosmos DB.

Vaya a la página de la cuenta de Azure Cosmos DB y seleccione Claves. Copie los valores que se van a usar en la aplicación web que creará a continuación.

Captura de pantalla de Azure Portal con el botón Claves resaltado en la página de la cuenta de Azure Cosmos DB

Creación de la aplicación JSP de Java

Para crear la aplicación JSP:

  1. En primer lugar, empezaremos por la creación de un proyecto de Java. Inicie Eclipse, seleccione File (Archivo), New (Nuevo) y, después, Dynamic Web Project (Proyecto web dinámico). Si Dynamic Web Project (Proyecto web dinámico) no aparece como proyecto disponible, haga lo siguiente: seleccione File (Archivo), New (Nuevo), Project (Proyecto), expanda Web, seleccione Dynamic Web Project (Proyecto web dinámico) y, después, Next (Siguiente).

    Desarrollo de aplicaciones Java JSP

  2. Escriba un nombre de proyecto en el cuadro Project name (Nombre de proyecto) y en el menú desplegable Target Runtime (Tiempo de ejecución de destino), seleccione opcionalmente un valor (por ejemplo, Apache Tomcat v7.0) y, después, seleccione Finish (Finalizar). Si selecciona un tiempo de ejecución de destino, puede ejecutar el proyecto localmente a través de Eclipse.

  3. En Eclipse, en la vista del explorador de proyectos, expanda el proyecto. Haga clic con el botón derecho en WebContent, seleccione New (Nuevo) y, después, JSP File (Archivo JSP).

  4. En el cuadro de diálogo New JSP File (Nuevo archivo JSP), asigne al archivo el nombre index.jsp. Mantenga la carpeta principal como WebContent, como se muestra en la ilustración siguiente y, después, seleccione Next (Siguiente).

    Tutorial de creación de un nuevo archivo JSP: aplicación web de Java

  5. En el cuadro de diálogo Select JSP Template (Seleccionar plantilla JSP), para cumplir con el objetivo de este tutorial, seleccione New JSP File (html) (Nuevo archivo JSP [html]) y seleccione Finish (Finalizar).

  6. Cuando se abre el archivo index.jsp en Eclipse, agregue texto para que muestre Hola mundo dentro del elemento <body> existente. El contenido actualizado <body> debe ser similar al código siguiente:

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

  8. Si ha establecido un tiempo de ejecución de destino en el paso 2, puede seleccionar Project (Proyecto) y, a continuación, en Run (Ejecutar) para ejecutar su aplicación de JSP localmente:

    Hola mundo – Tutorial de aplicación de Java

Instalación del SDK de Java para SQL

La manera más sencilla de insertar el SDK de Java para SQL y sus dependencias es a través de Apache Maven. Para ello, deberá convertir el proyecto en un proyecto de Maven realizando los pasos siguientes:

  1. Haga clic con el botón derecho en el proyecto en Explorador de proyectos, seleccione Configure (Configurar) y, después, Convert to Maven Project (Convertir en proyecto Maven).

  2. En la ventana Create new POM (Crear POM), acepte los valores predeterminados y seleccione Finish (Finalizar).

  3. En Explorador de proyectos, abra el archivo pom.xml.

  4. En la ficha Dependencies (Dependencias), en el panel Dependencies (Dependencias), seleccione Add (Agregar).

  5. En la ventana Seleccionar dependencia , haga lo siguiente:

    • En el cuadro Id. de grupode, escriba com.azure.
    • En el cuadro Id. de artefacto, escriba azure-cosmos.
    • En el cuadro Versión, escriba 4.11.0.

    O bien, puede agregar el XML de dependencia para el identificador de grupo y el identificador de artefacto directamente al archivo pom.xml:

    <dependency>
      <groupId>com.azure</groupId>
      <artifactId>azure-cosmos</artifactId>
      <version>4.11.0</version>
    </dependency>
    
  6. Seleccione OK (Aceptar) y Maven instalará el SDK de SQL para Java o guarde el archivo pom.xml.

Uso del servicio Azure Cosmos DB en una aplicación de Java

Ahora vamos a agregar los modelos, las vistas y los controladores a la aplicación web.

Adición de un modelo

En primer lugar, vamos a definir un modelo en un nuevo archivo TodoItem. Java. La clase TodoItem define el esquema de un elemento junto con los métodos Getter y 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;
    }
    
    
}

Incorporación de clases de objetos de acceso a datos (DAO)

Cree un objeto de acceso a datos (DAO) para abstraer la conservación de los elementos ToDo en Azure Cosmos DB. Para guardar los elementos ToDo en una colección, el cliente necesitará saber en qué base de datos y colección se conservarán (como se hace referencia por los self-links). En general, es mejor almacenar en memoria caché la base de datos y la colección cuando sea posible para evitar recorridos de ida y vuelta adicionales a la base de datos.

  1. Para invocar el servicio Azure Cosmos DB, debe crear una nueva instancia del objeto cosmosClient. En general, es mejor volver a utilizar el objeto cosmosClient, en lugar de construir un nuevo cliente para cada solicitud posterior. Puede volver a usar el cliente si lo define dentro de la clase cosmosClientFactory. Actualice el HOST y los valores de MASTER_KEY que guardó en el paso 1. Reemplace la variable HOST por su identificador URI y reemplace el valor de MASTER_KEY por su clave principal. Use el código siguiente para crear la clase CosmosClientFactory en el archivo 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. Cree un nuevo archivo TodoDao.java y agregue la clase TodoDao para crear, actualizar, leer y eliminar los elementos de la lista de tareas:

    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. Cree un nuevo archivo MockDao.java y agregue la clase MockDao, esta clase implementa la clase TodoDao para realizar operaciones CRUD en los elementos:

    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. Cree un nuevo archivo DocDbDao.java y agregue la clase DocDbDao. Esta clase define el código para conservar los elementos de TodoItems en el contenedor, recupera la base de datos y la colección, si existe, o las crean en caso contrario. En este ejemplo se usa Gson para serializar y deserializar los objetos de Java antiguos sin formato de la tabla TodoItem (POJO) en los documentos JSON. Para guardar los elementos ToDo en una colección, el cliente necesitará saber en qué base de datos y colección se conservarán (como se hace referencia por los self-links). Esta clase también define la función auxiliar para recuperar los documentos por otro atributo (por ejemplo, "ID") en lugar de Self-Link. Podemos usar el método auxiliar para recuperar un documento JSON de TodoItem por identificador y deserializarlo en un objeto de Java antiguo sin formato de la tabla TodoItem.

    También puede utilizar el objeto cliente cosmosClient para obtener una colección o lista de TodoItems con una consulta SQL. Por último, defina el método Delete para eliminar un elemento TodoItem de la lista. El siguiente código muestra el contenido de la clase 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. A continuación, cree un nuevo archivo TodoDaoFactory.java y agregue la clase TodoDaoFactory que crea un nuevo objeto DocDbDao:

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

Adición de un controlador

Agregue el controlador TodoItemController a la aplicación. En este proyecto, va a usar Project Lombok para generar el constructor, los captadores, los establecedores y un generador. Como alternativa, puede escribir este código manualmente o dejar que el IDE lo genere:

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

Creación de un servlet

A continuación, cree un servlet para enrutar las solicitudes HTTP al controlador. Cree el archivo ApiServlet.java y defina el código siguiente en él:

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

Conexión del resto de la aplicación de Java

Ahora que hemos terminado la parte divertida, todo lo que queda por hacer es crear una interfaz de usuario rápida y conectarla a nuestro DAO.

  1. Necesita una interfaz de usuario web para mostrar al usuario. Vamos a volver a escribir el archivo index.jsp que creamos anteriormente con el código siguiente:

    <%@ 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 último, escriba algo de JavaScript del lado del cliente para vincular juntos la interfaz de usuario web y el 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. Ahora todo lo que queda por hacer es probar la aplicación. Ejecute la aplicación localmente y agregue algunos elementos Todo rellenando el nombre del elemento y la categoría y haciendo clic en Agregar tarea. Una vez que aparece el elemento, puede actualizar si está completo. Para ello, alterne la casilla y haga clic en Actualizar tareas.

Implementación de la aplicación Java en Azure WebSites

Azure WebSites consigue que la implementación de aplicaciones de Java sea tan sencilla como exportar su aplicación como un archivo WAR y cargarla mediante el control de código fuente (por ejemplo, Git) o FTP.

  1. Para exportar la aplicación como un archivo WAR, haga clic con el botón derecho en el proyecto en Explorador de proyectos, seleccione Export (Exportar) y, después, seleccione WAR File (Archivo WAR).

  2. En la ventana Exportar WAR , haga lo siguiente:

    • En el cuadro Proyecto web, escriba azure-cosmos-java-sample.
    • En el cuadro de destino, seleccione un destino para guardar el archivo WAR.
    • Seleccione Finalizar.
  3. Ahora que tiene a mano un archivo WAR, simplemente puede cargarlo en el directorio webapps del sitio web de Azure. Para obtener instrucciones sobre cómo cargar el archivo, consulte Adición de una aplicación Java a Azure App Service Web Apps. Una vez que se cargue el archivo WAR en el directorio webapps, el entorno de tiempo de ejecución detectará que lo ha agregado y lo cargará automáticamente.

  4. Para ver el producto finalizado, vaya a http://YOUR\_SITE\_NAME.azurewebsites.net/azure-cosmos-java-sample/ y empiece a agregar sus tareas.

Obtenga el proyecto desde GitHub

Todos los ejemplos de este tutorial se incluyen en el proyecto todo en GitHub. Para importar el proyecto todo en Eclipse, asegúrese de disponer del software y los recursos que aparecen en la sección Requisitos previos y haga lo siguiente:

  1. Instale Project Lombok. Lombok se utiliza para generar constructores, captadores y establecedores en el proyecto. Una vez haya descargado el archivo lombok.jar, haga doble clic en él para instalarlo o instálelo desde la línea de comandos.

  2. Si Eclipse está abierto, ciérrelo y reinícielo para cargar Lombok.

  3. En Eclipse, en el menú File (Archivo), seleccione Import (Importar).

  4. En la ventana Import (Importar), seleccione Git, Projects from Git (Proyectos de Git) y luego Next (Siguiente).

  5. En la pantalla Select Repository Source (Seleccionar origen del repositorio), seleccione Clone URI (Clonar URI).

  6. En la pantalla Source Git Repository (Repositorio Git de origen), en el cuadro URI, escriba https://github.com/Azure-Samples/azure-cosmos-java-sql-api-todo-app y, después, seleccione Next (Siguiente).

  7. En la pantalla Branch Selection (Selección de rama), asegúrese de que se ha seleccionado main (principal) y después seleccione Next (Siguiente).

  8. En la pantalla Local Destination (Destino local), haga clic en Browse (Examinar) para seleccionar una carpeta donde se pueda copiar el repositorio y, después, seleccione Next (Siguiente).

  9. En la pantalla Select a wizard to use for importing projects (Seleccionar el asistente que se va a usar en la importación de proyectos), asegúrese de que la opción Import existing projects (Importar proyectos existentes) esté seleccionada y luego seleccione Next (Siguiente).

  10. En la pantalla Import Projects (Proyectos de importación), anule la selección del proyecto DocumentDB y luego seleccione Finish (Finalizar). El proyecto DocumentDB contiene el SDK de Java de Azure Cosmos DB, que se agregará como dependencia.

  11. En Project Explorer (Explorador de proyectos), vaya a azure-cosmos-java-sample\src\com.microsoft.azure.cosmos.sample.dao\DocumentClientFactory.java y reemplace los valores de HOST y MASTER_KEY por el identificador URI y la CLAVE PRINCIPAL de la cuenta de Azure Cosmos DB y, a continuación, guarde el archivo. Para obtener más información, consulte Paso 1: Creación de una cuenta de base de datos de Azure Cosmos DB.

  12. En Project Explorer (Explorador de proyectos), haga clic con el botón derecho en azure-cosmos-java-sample, seleccione Build Path (Ruta de acceso de compilación) y, después Configure Build Path (Configurar ruta de acceso de compilación).

  13. En la pantalla Java Build Path (Ruta de compilación de Java), en el panel derecho, seleccione la pestaña Bibliotecas y luego seleccione Add External JARs (Agregar JAR externos). Desplácese hasta la ubicación del archivo lombok.jar, seleccione Open (Abrir) y, después, OK (Aceptar).

  14. Use el paso 12 para volver a abrir la ventana Properties (Propiedades) y, después, en el panel izquierdo seleccione Targeted Runtimes (Tiempos de ejecución de destino).

  15. En la pantalla Targeted Runtimes (Tiempos de ejecución de destino), seleccione New (Nuevo), seleccione Apache Tomcat v7.0 y luego seleccione OK (Aceptar).

  16. Use el paso 12 para volver a abrir la ventana Properties (Propiedades) y, después, en el panel izquierdo, seleccione Project Facets (Facetas del proyecto).

  17. En la pantalla Project Facets (Facetas del proyecto), seleccione Dynamic Web Module (Módulo web dinámico) y Java y luego seleccione OK (Aceptar).

  18. En la pestaña Servers (Servidores) en la parte inferior de la pantalla, haga clic con el botón derecho en Tomcat v7.0 Server at localhost (Tomcat v7.0 Server en localhost) y luego seleccione Add and Remove (Agregar y quitar).

  19. En la ventana Add and Remove (Agregar y quitar), mueva azure-cosmos-java-sample al cuadro Configured (Configurado) y, después, seleccione Finish (Finalizar).

  20. En la pestaña Servidor, haga clic en Tomcat v7.0 Server at localhost (Tomcat v7.0 Server en localhost) y luego seleccione Restart (Reiniciar).

  21. En un explorador, vaya a http://localhost:8080/azure-cosmos-java-sample/ y empiece a agregar a la lista de tareas. Tenga en cuenta que si ha cambiado los valores de puerto predeterminados, debe cambiar 8080 por el valor seleccionado.

  22. Para implementar el proyecto en un sitio web de Azure, consulte el paso 6. Implementación de la aplicación para Azure WebSites.

Pasos siguientes

¿Intenta planear la capacidad de una migración a Azure Cosmos DB? Para ello, puede usar información sobre el clúster de bases de datos existente.