Esercitazione: Creare un'applicazione Web Java usando Azure Cosmos DB e API for NoSQL

SI APPLICA A: NoSQL

Questa esercitazione sull'applicazione Web Java illustra come usare il servizio Microsoft Azure Cosmos DB per archiviare i dati e accedervi da un'applicazione Java ospitata in app Web del servizio app di Azure. È possibile configurare un account Azure Cosmos DB di prova gratuito anche senza una carta di credito o una sottoscrizione di Azure. Contenuto dell'articolo:

  • Come creare un'applicazione JavaServer Pages (JSP) di base in Eclipse.
  • Come usare il servizio Azure Cosmos DB tramite Azure Cosmos DB Java SDK.

Questa esercitazione illustra come creare un'applicazione di gestione delle attività basata su Web che consente di creare, recuperare e contrassegnare le attività come completate, come illustrato nella figura seguente. Tutte le attività nell'elenco attività vengono archiviate come documenti JSON in Azure Cosmos DB.

My ToDo List Java application

Suggerimento

Questa esercitazione sullo sviluppo dell’applicazione presuppone che l'utente abbia già acquisito familiarità con l'uso di Java. Se non si ha alcuna esperienza riguardo a Java o agli strumenti richiesti come prerequisiti, è consigliabile scaricare il progetto todo completo da GitHub e creare l'applicazione usando le istruzioni alla fine di questo articolo. Una volta creata la soluzione, è possibile leggere l'articolo per approfondire il codice nel contesto del progetto.

Prerequisiti per questa esercitazione sull'applicazione Web Java

Prima di iniziare questa esercitazione sullo sviluppo dell’applicazione, è necessario disporre di quanto segue:

Se questi strumenti vengono installati per la prima volta, coreservlets.com fornisce una procedura dettagliata del processo di installazione nella sezione di avvio rapido dell'articolo relativo all' esercitazione sull'installazione di TomCat7 e il relativo uso con Eclipse.

Creare un account Azure Cosmos DB

Creare prima di tutto un account Azure Cosmos DB. Se si ha già un account o si usa l'emulatore Azure Cosmos DB per questa esercitazione, è possibile proseguire con il Passaggio 2: Creare l'applicazione Java JSP.

  1. Nel menu del portale di Azure o dalla pagina Home selezionare Crea una risorsa.

  2. Cercare Azure Cosmos DB. Selezionare Crea>Azure Cosmos DB.

  3. Nella pagina Creare un account Azure Cosmos DB selezionare l'opzione Crea all'interno della sezione Azure Cosmos DB for NoSQL.

    Azure Cosmos DB offre diverse API:

    • NoSQL, per i dati del documento
    • PostgreSQL
    • MongoDB, per i dati del documento
    • Apache Cassandra
    • Tabella
    • Apache Gremlin, per i dati del grafo

    Per altre informazioni sulla API per NoSQL, vedere Introduzione ad Azure Cosmos DB.

  4. Nella pagina Crea account Azure Cosmos DB immettere le impostazioni base per il nuovo account Azure Cosmos DB.

    Impostazione valore Descrizione
    Abbonamento Nome della sottoscrizione Selezionare la sottoscrizione di Azure da usare per l'account Azure Cosmos DB.
    Gruppo di risorse Nome gruppo di risorse Selezionare un gruppo di risorse oppure fare clic su Crea nuovo, quindi immettere un nome univoco per il nuovo gruppo di risorse.
    Nome conto Un nome univoco Immettere un nome per identificare l'account Azure Cosmos DB. Dato che al nome specificato viene aggiunto documents.azure.com per creare l'URI, usare un nome univoco. Il nome può contenere solo lettere minuscole, numeri e il trattino (-). Deve essere di 3-44 caratteri.
    Ufficio Area più vicina ai propri utenti Selezionare una posizione geografica in cui ospitare l'account Azure Cosmos DB. Usare la località più vicina agli utenti per offrire loro la massima velocità di accesso ai dati.
    Modalità di capacità Velocità effettiva con provisioning o Serverless Selezionare Provisioning velocità effettiva per creare un account in modalità Provisioning velocità effettiva. Selezionare Serverless per creare un account in modalità Serverless.
    Applicare lo sconto in base al livello gratuito di Azure Cosmos DB Applicare o non applicare Il livello gratuito di Azure Cosmos DB offre i primi 1000 UR/s e 25 GB di spazio di archiviazione gratuiti per account. Altre informazioni sul livello gratuito.
    Limitare la velocità effettiva totale dell'account Selezionato o non selezionato È possibile limitare la misura totale di velocità effettiva di provisioning in questo account. Questo limite impedisce addebiti imprevisti correlati alla velocità effettiva con provisioning. È possibile aggiornare o rimuovere questo limite dopo la creazione dell'account.

    È possibile avere fino a un account Azure Cosmos DB del livello gratuito per ogni sottoscrizione di Azure ed è necessario acconsentire esplicitamente durante la creazione dell'account. Se l'opzione per l'applicazione dello sconto per il livello gratuito non è visualizzata, significa che un altro account nella sottoscrizione è già stato abilitato per il livello gratuito.

    Screenshot shows the Create Azure Cosmos DB Account page.

    Nota

    Le opzioni seguenti non sono disponibili se si seleziona Serverless come modalità di capacità:

    • Applica sconto livello gratuito
    • Limitare la velocità effettiva totale dell'account
  5. Nella scheda Distribuzione globale configurare i dettagli seguenti. Per questo avvio rapido è possibile lasciare i valori predefiniti:

    Impostazione valore Descrizione
    Ridondanza geografica Disabilita Abilitare o disabilitare la distribuzione globale nell'account associando la propria area a un'altra area. È possibile aggiungere altre aree al proprio account in un secondo momento.
    Scritture in più aree Disabilita La funzionalità Scritture in più aree consente di sfruttare la velocità effettiva di cui è stato effettuato il provisioning per i database e i contenitori in tutto il mondo.
    Zone di disponibilità Disabilita Le zone di disponibilità consentono di migliorare ulteriormente la disponibilità e la resilienza dell'applicazione.

    Nota

    Le opzioni seguenti non sono disponibili se si seleziona Serverless come modalità di capacità nella precedente pagina Informazioni di base:

    • Ridondanza geografica
    • Scritture in più aree
  6. Facoltativamente, è possibile configurare altri dettagli nelle schede seguenti:

    • Reti. Configurare accesso da una rete virtuale.
    • Criterio di backup. Configurare criteri di backup periodici o continui.
    • Crittografia. Usare una chiave gestita dal servizio o una chiave gestita dal cliente.
    • Tag. I tag sono coppie nome-valore che consentono di classificare le risorse e visualizzare dati di fatturazione consolidati tramite l'applicazione dello stesso tag a più risorse e gruppi di risorse.
  7. Selezionare Rivedi e crea.

  8. Esaminare le impostazioni dell'account e quindi selezionare Crea. La creazione dell'account richiede alcuni minuti. Attendere che la pagina del portale visualizzi La distribuzione è stata completata.

    Screenshot shows that your deployment is complete.

  9. Selezionare Vai alla risorsa per passare alla pagina dell'account Azure Cosmos DB.

    Screenshot shows the Azure Cosmos DB account page.

Passare alla pagina dell'account Azure Cosmos DB e selezionare Chiavi. Copiare i valori da usare nell'applicazione Web che verrà creata successivamente.

Screenshot of the Azure portal with the Keys button highlighted on the Azure Cosmos DB account page

Creare l'applicazione Java JSP

Per creare l'applicazione JSP:

  1. Si inizierà con la creazione di un progetto Java. Avviare Eclipse, quindi selezionare File, Nuovo e quindi Progetto Web dinamico. Se Progetto Web dinamico non è elencato come progetto disponibile, eseguire le operazioni seguenti: selezionare File, selezionare Nuovo, selezionare Progetto..., espandere Web, scegliere Progetto Web dinamico e selezionare Avanti.

    JSP Java Application Development

  2. Immettere un nome di progetto nella casella Nome progetto e dal menu a discesa Runtime di destinazione selezionare facoltativamente un valore, ad esempio Apache Tomcat v7.0, e quindi selezionare Fine. Se si seleziona un runtime di destinazione, sarà possibile eseguire il progetto in locale tramite Eclipse.

  3. Nella vista Project Explorer di Eclipse espandere il progetto. Fare clic con il pulsante destro del mouse su WebContent, scegliere Nuovo e quindi file JSP.

  4. Nella finestra di dialogo New JSP File (Nuovo file JSP) assegnare al file il nome index.jsp. Mantenere il nome WebContent per la cartella padre, come illustrato di seguito, e quindi selezionare Avanti.

    Make a New JSP File - Java Web Application Tutorial

  5. Per le finalità di questa esercitazione, nella finestra di dialogo Seleziona modello JSP selezionare Nuovo file JSP (html), quindi selezionare Fine.

  6. Quando si apre il file index.jsp in Eclipse, aggiungere il testo per visualizzare Hello World! all'interno dell'elemento esistente <body>. Il contenuto <body> aggiornato dovrebbe avere un aspetto analogo al codice seguente:

    <body>
      <% out.println("Hello World!"); %>
    </body>
    
  7. Salvare il file index.jsp.

  8. Se nel passaggio 2 è stato impostato un runtime di destinazione, è possibile selezionare Progetto, quindi Esegui per eseguire l'applicazione JSP in locale:

    Hello World – Java Application Tutorial

Installare Java SDK SQL

Il modo più semplice per inserire Java SDK SQL e le relative dipendenze è tramite Apache Maven. A tale scopo, sarà necessario convertire il progetto in un progetto Maven completando i passaggi seguenti:

  1. Fare clic con il pulsante destro del mouse sul progetto in Esplora progetti, selezionare Configura e quindi selezionare Converti in progetto Maven.

  2. Nella finestra Crea nuovo POM accettare le impostazioni predefinite e selezionare Fine.

  3. In Project Explorer, aprire il file pom.xml.

  4. Nella scheda Dipendenze, nel riquadro Dipendenze, selezionare Aggiungi.

  5. Nella finestra Select Dependency (Seleziona dipendenza) eseguire le operazioni seguenti:

    • Nella casella ID gruppo immettere com.azure.
    • Nella casella ID artefatto immettere azure-cosmos.
    • Nella casella Versione immettere 4.11.0.

    In alternativa, aggiungere l'XML della dipendenza per ID gruppo e ID artefatto direttamente nel file pom.xml:

    <dependency>
      <groupId>com.azure</groupId>
      <artifactId>azure-cosmos</artifactId>
      <version>4.11.0</version>
    </dependency>
    
  6. Selezionare OK e Maven installerà SQL Java SDK o salverà il file pom.xml.

Usare il servizio Azure Cosmos DB in un'applicazione Java

Aggiungere ora i modelli, le visualizzazioni e i controller all'applicazione Web.

Aggiungi un modello

Per prima cosa, definire un modello all'interno di un nuovo file TodoItem.java. La classe TodoItem definisce lo schema di un elemento insieme ai metodi 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;
    }
    
    
}

Aggiungere le classi DAO (Data Access Object)

Creare un oggetto DAO (Data Access Object) per astrarre in modo persistente gli elementi ToDo in Azure Cosmos DB. Per salvare gli elementi ToDo in una raccolta, il client deve conoscere i database e le raccolte da rendere permanenti (in base al riferimento dei collegamenti automatici). È in genere preferibile memorizzare nella cache database e raccolte appena possibile, in modo da evitare episodi di round trip del database.

  1. Per richiamare il servizio Azure Cosmos DB, è necessario creare un'istanza di un nuovo oggetto cosmosClient. È in genere preferibile riutilizzare l'oggetto cosmosClient invece di creare un nuovo client per ogni richiesta successiva. È possibile riutilizzare il client definendolo all'interno della classe cosmosClientFactory. Aggiornare i valori HOST e MASTER_KEY salvati nel passaggio 1. Sostituire la variabile HOST con il proprio URI, e sostituire MASTER_KEY con la propria chiave primaria. Usare il codice seguente per creare la classe CosmosClientFactory nel file di 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. Creare un nuovo file TodoDao.java e aggiungere la classe TodoDao per creare, aggiornare, leggere ed eliminare gli elementi ToDo:

    package com.microsoft.azure.cosmos.sample.dao;
    
    import java.util.List;
    
    import com.microsoft.azure.cosmos.sample.model.TodoItem;
    
    public interface TodoDao {
        /**
         * @return A list of TodoItems
         */
        public List<TodoItem> readTodoItems();
    
        /**
         * @param todoItem
         * @return whether the todoItem was persisted.
         */
        public TodoItem createTodoItem(TodoItem todoItem);
    
        /**
         * @param id
         * @return the TodoItem
         */
        public TodoItem readTodoItem(String id);
    
        /**
         * @param id
         * @return the TodoItem
         */
        public TodoItem updateTodoItem(String id, boolean isComplete);
    
        /**
         *
         * @param id
         * @return whether the delete was successful.
         */
        public boolean deleteTodoItem(String id);
    }
    
  3. Creare un nuovo file MockDao.java e aggiungere la classe MockDao, che implementa la classe TodoDao per eseguire operazioni CRUD sugli elementi:

    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. Creare un nuovo file DocDbDao.java e aggiungere la classe DocDbDao. Questa classe definisce il codice per la persistenza di elementi TodoItem nel contenitore, recupera il database e la raccolta, se esistono, o ne crea di nuovi in caso contrario. Questo esempio usa Gson per serializzare e deserializzare gli oggetti POJO (Plain Old Java Object) di TodoItem in documenti JSON. Per salvare gli elementi ToDo in una raccolta, il client deve conoscere i database e le raccolte da rendere permanenti (in base al riferimento dei collegamenti automatici). Questa classe definisce anche la funzione helper per recuperare i documenti in base a un altro attributo, ad esempio "ID") invece che con il collegamento automatico. È possibile usare il metodo helper per recuperare un documento JSON TodoItem in base all'ID e quindi deserializzarlo in POJO.

    È inoltre possibile usare l'oggetto client cosmosClient per ottenere una raccolta o un elenco di elementi TodoItem tramite una query SQL. Infine, definire il metodo delete per eliminare un elemento TodoItem dall'elenco. Il codice seguente illustra il contenuto della classe 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. Successivamente, creare un nuovo file TodoDaoFactory.java e aggiungere la classe TodoDaoFactory, che crea un nuovo oggetto DocDbDao:

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

Aggiungere un controller

Aggiungere il controller TodoItemController all'applicazione. In questo progetto viene usato Project Lombok per generare il costruttore, getter, setter e un generatore. In alternativa, è possibile scrivere il codice manualmente o farlo generare mediante IDE:

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

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

import lombok.NonNull;

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

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

    private static TodoItemController todoItemController;

    private final TodoDao todoDao;

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

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

        return todoDao.createTodoItem(todoItem);
    }

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

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

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

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

Creare un servlet

Creare quindi un servlet per instradare le richieste HTTP al controller. Creare il file ApiServlet.java e definire il codice seguente al suo interno:

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

Collegare il resto dell'app Java

Dopo aver completato questi passaggi, è necessario creare un'interfaccia utente intuitiva e collegarla all'oggetto DAO.

  1. Sarà necessaria un'interfaccia utente Web da visualizzare all'utente. A tale scopo, riscrivere il file index.jsp creato in precedenza con il codice seguente:

    <%@ 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. Infine, scrivere codice JavaScript lato client per il collegamento dell'interfaccia utente Web con il 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. è necessario testare l'applicazione. Eseguire l'applicazione in locale e aggiungere alcuni elementi Todo specificando i valori relativi al nome e alla categoria dell'elemento e selezionando Add Task (Aggiungi attività). Quando l'elemento viene visualizzato, è possibile aggiornarne lo stato attivando o disattivando la relativa casella di controllo e facendo clic su Update Tasks (Aggiorna attività).

Distribuire l'applicazione Java in Siti Web di Azure

Con Siti Web di Azure la procedura di distribuzione di applicazioni Java è molto semplice e consiste nell'esportazione di un'applicazione come file con WAR e nel relativo caricamento tramite controllo del codice sorgente (ad esempio Git) o FTP.

  1. Per esportare l'applicazione come file WAR, fare clic con il pulsante destro del mouse sul progetto in Esplora progetti, selezionare Esporta e quindi selezionare File WAR.

  2. Nella finestra WAR Export eseguire le operazioni seguenti:

    • Nella casella Progetto Web immettere azure-cosmos-java-sample.
    • Nella casella Destination scegliere una destinazione in cui salvare il file WAR.
    • Selezionare Fine.
  3. A questo punto è sufficiente caricare il file nella directory webapps di Siti Web di Azure. Per istruzioni sul caricamento del file, vedere Distribuire l'applicazione di esempio nelle app Web di Servizio app di Azure. Dopo aver caricato il file WAR nella directory webapps, l'ambiente di runtime rileverà che il file è stato aggiunto e lo caricherà automaticamente.

  4. Per visualizzare il prodotto finito, passare a http://YOUR\_SITE\_NAME.azurewebsites.net/azure-cosmos-java-sample/ e iniziare ad aggiungere le attività.

Ottenere il progetto da GitHub

Tutti gli esempi in questa esercitazione sono inclusi nel progetto todo su GitHub. Per importare il progetto todo in Eclipse, assicurarsi di avere il software e le risorse elencate nella sezione Prerequisiti , quindi eseguire le operazioni seguenti:

  1. Installare Project Lombok. Lombok viene usato per generare costruttori, getter e setter nel progetto. Dopo aver scaricato il file lombok.jar, fare doppio clic per installarlo o eseguire l'installazione dalla riga di comando.

  2. Se Eclipse è aperto, chiuderlo e riavviarlo per caricare Lombok.

  3. In Eclipse dal menu File, selezionare Importa.

  4. Nella finestra Importa selezionare Git, selezionare Progetti da Git e quindi selezionare Avanti.

  5. Nella schermata Seleziona origine repository selezionare Clona URI.

  6. Nella schermata Repository Git di origine, nella casella URI, immettere https://github.com/Azure-Samples/azure-cosmos-java-sql-api-todo-app e quindi selezionare Avanti.

  7. Nella schermata Selezione ramo assicurarsi che sia selezionata l'opzione principale e quindi selezionare Avanti.

  8. Nella schermata Destinazione locale selezionare Sfoglia per selezionare una cartella in cui sia possibile copiare il repository e quindi selezionare Avanti.

  9. Nella schermata Selezionare una procedura guidata per l'importazione di progetti assicurarsi che l'opzione Importa progetti esistenti sia selezionata e quindi selezionare Avanti.

  10. Nella schermata Importa progettideselezionare il progetto DocumentDB e quindi selezionare Fine. Il progetto DocumentDB contiene Azure Cosmos DB Java SDK, che verrà aggiunto invece come dipendenza.

  11. In Esplora progetti passare ad azure-cosmos-java-sample\src\com.microsoft.azure.cosmos.sample.dao\DocumentClientFactory.java e sostituire i valori HOST e MASTER_KEY con i valori URI e PRIMARY KEY dell'account Azure Cosmos DB, quindi salvare il file. Per altre informazioni, vedere Passaggio 1. Creare un account di database Azure Cosmos DB.

  12. In Esplora progetti fare clic con il pulsante destro del mouse su azure-cosmos-java-sample, selezionare Percorso compilazione e quindi selezionare Configura percorso compilazione.

  13. Nella schermata Percorso compilazione Java, nel riquadro a destra selezionare la scheda Librerie e quindi selezionare Aggiungi JAR esterni. Passare al percorso del file lombok.jar e selezionareApri e quindi selezionare OK.

  14. Vedere il passaggio 12 per aprire nuovamente la finestra Proprietà e quindi nel riquadro a sinistra selezionare Runtime di destinazione.

  15. Nella schermata Runtime di destinazione selezionare Nuovo, selezionare Apache Tomcat v7.0 e quindi selezionare OK.

  16. Vedere il passaggio 12 per aprire nuovamente la finestra Proprietà e quindi nel riquadro a sinistra selezionare Facet di progetto.

  17. Nella schermata Facet di progetto selezionare Modulo Web dinamico e Java e quindi selezionare OK.

  18. Nella scheda Server nella parte inferiore della schermata fare clic con il pulsante destro del mouse su Tomcat v7.0 Server in localhost e quindi selezionare Aggiungi e rimuovi.

  19. Nella finestra Aggiungi e rimuovi spostare azure-cosmos-java-sample nella casella Configurato e quindi selezionare Fine.

  20. Nella scheda Server fare clic con il pulsante destro del mouse su Tomcat v7.0 Server in localhost e quindi selezionare Riavvia.

  21. In un browser passare a http://localhost:8080/azure-cosmos-java-sample/ e iniziare ad aggiungere voci all'elenco attività. Si noti che se sono stati modificati i valori di porta predefiniti, è necessario modificare la porta 8080 con il valore selezionato.

  22. Per distribuire il progetto in un sito web di Azure, vedere il passaggio 6. Distribuire l'applicazione in Siti Web di Azure.

Passaggi successivi

Si sta tentando di pianificare la capacità per una migrazione ad Azure Cosmos DB? È possibile usare le informazioni del cluster di database esistente per la pianificazione della capacità.