Realización de operaciones CRUD en Amazon DocumentDB con Java - Amazon DocumentDB

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

Realización de operaciones CRUD en Amazon DocumentDB con Java

En esta sección se describe cómo realizar las operaciones CRUD (crear, leer, actualizar, eliminar) en Amazon DocumentDB con los controladores Java de MongoDB.

Creación e inserción de documentos en una colección de DocumentDB

La inserción de documentos en Amazon DocumentDB le permite añadir nuevos datos a sus colecciones. Existen varias formas de realizar las inserciones, en función de sus necesidades y del volumen de datos con el que esté trabajando. El método más básico para insertar un documento individual en la colección es insertOne(). Para insertar varios documentos a la vez, puede utilizar el método de insertMany(), que le permite añadir una serie de documentos en una sola operación. Otro método para insertar muchos documentos en una colección de DocumentDB es bulkWrite(). En esta guía, analizamos todos estos métodos para crear documentos en una colección de DocumentDB.

insertOne()

Empecemos por examinar cómo insertar un documento individual en una colección de Amazon DocumentDB. La inserción de un único documento se realiza mediante el método insertOne(). Este método toma un objeto BsonDocumentpara insertarlo y devuelve un InsertOneResultobjeto que se puede utilizar para obtener el identificador de objeto del nuevo documento insertado. El siguiente código de ejemplo muestra la inserción de un documento de restaurante en la colección:

Document article = new Document() .append("restaurantId", "REST-21G145") .append("name", "Future-proofed Intelligent Bronze Hat") .append("cuisine", "International") .append("rating", new Document() .append("average", 1.8) .append("totalReviews", 267)) .append("features", Arrays.asList("Outdoor Seating", "Live Music")); try { InsertOneResult result = collection.insertOne(article); System.out.println("Inserted document with the following id: " + result.getInsertedId()); } catch (MongoWriteException e) { // Handle duplicate key or other write errors System.err.println("Failed to insert document: " + e.getMessage()); throw e; } catch (MongoException e) { // Handle other MongoDB errors System.err.println("MongoDB error: " + e.getMessage()); throw e; }

Cuando use insertOne(), asegúrese de incluir una gestión de errores adecuada. Por ejemplo, en el código anterior, «restaurantId» tiene un índice único y, por lo tanto, cuando vuelva a ejecutar este código, se generará la siguiente MongoWriteException:

Failed to insert document: Write operation error on server docdbCluster.docdb.amazonaws.com:27017. Write error: WriteError{code=11000, message='E11000 duplicate key error collection: Restaurants index: restaurantId_1', details={}}.

InsertMany()

Los métodos principales utilizados para insertar muchos documentos en una colección son insertMany() y bulkWrite().

El método insertMany() es la forma más sencilla de insertar varios documentos en una sola operación. Acepta una lista de documentos y los inserta en la colección. Este método es ideal cuando se inserta un lote de documentos nuevos que son independientes entre sí y no requieren ningún procesamiento especial ni operaciones mixtas. El siguiente código muestra la lectura de documentos JSON de un archivo y su inserción en la colección. La insertMany() función devuelve un InsertManyResultInsertManyResultobjeto que se puede utilizar para obtener todos IDs los documentos insertados.

// Read JSON file content String content = new String(Files.readAllBytes(Paths.get(jsonFileName))); JSONArray jsonArray = new JSONArray(content); // Convert JSON articles to Documents List < Document > restaurants = new ArrayList < > (); for (int i = 0; i < jsonArray.length(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); Document doc = Document.parse(jsonObject.toString()); restaurants.add(doc); } //insert documents in collection InsertManyResult result = collection.insertMany(restaurants); System.out.println("Count of inserted documents: " + result.getInsertedIds().size());

bulkWrite()

El método bulkWrite() permite realizar múltiples operaciones de escritura (insertar, actualizar, eliminar) en un solo lote. Puede usar bulkWrite() cuando necesite realizar diferentes tipos de operaciones en un solo lote, como insertar algunos documentos y actualizar otros. bulkWrite() admite dos tipos de escritura por lotes, ordenada y desordenada:

  • Operaciones ordenadas (predeterminado): Amazon DocumentDB procesa las operaciones de escritura de forma secuencial y se detiene ante el primer error que detecta. Esto resulta útil cuando el orden de las operaciones es importante, por ejemplo, cuando las operaciones posteriores dependen de las anteriores. Sin embargo, las operaciones ordenadas suelen ser más lentas que las desordenadas. En el caso de las operaciones ordenadas, debe abordar el caso de que el lote se detenga ante el primer error, lo que podría dejar algunas operaciones sin procesar.

  • Operaciones desordenadas: permite a Amazon DocumentDB procesar las inserciones como una sola ejecución en la base de datos. Si se produce un error en un documento, la operación continúa con el resto de los documentos. Esto resulta especialmente útil cuando se insertan grandes cantidades de datos y se pueden tolerar algunos errores, como durante la migración de datos o las importaciones masivas, donde algunos documentos pueden fallar debido a la duplicación de claves. En el caso de operaciones desordenadas, debe abordar las situaciones de éxito parcial, en los que algunas operaciones se realizan correctamente y otras no.

Al trabajar con el método bulkWrite(), se requieren algunas clases esenciales. En primer lugar, la clase WriteModel sirve como clase base para todas las operaciones de escritura y tiene implementaciones específicas como InsertOneModel, UpdateOneModel, UpdateManyModel, DeleteOneModel y DeleteManyModel, que manejan diferentes tipos de operaciones.

La BulkWriteOptionsclase es necesaria para configurar el comportamiento de las operaciones masivas, como configurar la ordered/unordered ejecución o omitir la validación de los documentos. La clase BulkWriteResult proporciona información detallada sobre los resultados de la ejecución, incluidos los recuentos de documentos insertados, actualizados y eliminados.

Para la gestión de errores, la clase MongoBulkWriteException es fundamental, ya que contiene información sobre los posibles errores que se produzcan durante la operación masiva, mientras que la clase BulkWriteError proporciona detalles específicos sobre los errores de las operaciones individuales. El siguiente código muestra un ejemplo de inserción de una lista de documentos, así como de actualización y eliminación de un solo documento, todo ello mediante la ejecución de una llamada a un único método bulkWrite(). El código también muestra cómo trabajar con BulkWriteOptions y BulkWriteResult, así como la correcta gestión de errores de la operación de bulkWrite().

List < WriteModel < Document >> bulkOperations = new ArrayList < > (); // get list of 10 documents representing 10 restaurants List < Document > restaurantsToInsert = getSampleData(); for (Document doc: restaurantsToInsert) { bulkOperations.add(new InsertOneModel < > (doc)); } // Update operation bulkOperations.add(new UpdateOneModel < > ( new Document("restaurantId", "REST-Y2E9H5"), new Document("", new Document("stats.likes", 20)) .append("", new Document("rating.average", 4.5)))); // Delete operation bulkOperations.add(new DeleteOneModel < > (new Document("restaurantId", "REST-D2L431"))); // Perform bulkWrite operation try { BulkWriteOptions options = new BulkWriteOptions() .ordered(false); // Allow unordered inserts BulkWriteResult result = collection.bulkWrite(bulkOperations, options); System.out.println("Inserted: " + result.getInsertedCount()); System.out.println("Updated: " + result.getModifiedCount()); System.out.println("Deleted: " + result.getDeletedCount()); } catch (MongoBulkWriteException e) { System.err.println("Bulk write error occurred: " + e.getMessage()); // Log individual write errors for (BulkWriteError error: e.getWriteErrors()) { System.err.printf("Error at index %d: %s (Code: %d)%n", error.getIndex(), error.getMessage(), error.getCode()); // Log the problematic document Document errorDoc = new Document(error.getDetails()); if (errorDoc != null) { System.err.println("Problematic document: " + errorDoc); } } } catch (Exception e) { System.err.println("Error during bulkWrite: " + e.getMessage()); }

Escrituras reintentables

A diferencia de MongoDB, Amazon DocumentDB no admite el reintento de las escrituras. En consecuencia, debe implementar una lógica de reintento personalizada en sus aplicaciones, especialmente para solucionar problemas de red o la falta temporal de disponibilidad del servicio. Por lo general, una estrategia de reintentos bien implementada implica aumentar la demora entre los reintentos y limitar el número total de reintentos. Consulte Gestión de errores con lógica de reintento a continuación para conocer un ejemplo de código sobre cómo crear una lógica de reintentos con gestión de errores.

Lectura y recuperación de datos de una colección de DocumentDB

La consulta de documentos en Amazon DocumentDB gira en torno a varios componentes clave que permiten recuperar y manipular datos con precisión. El find()método es la consulta fundamental APIs en los controladores Java de MongoDB. Permite la recuperación de datos complejos con numerosas opciones para filtrar, ordenar y proyectar resultados. Además del método find(), Filters y FindIterable son otros dos componentes fundamentales que proporcionan los componentes básicos para las operaciones de consulta en los controladores Java de MongoDB.

La clase Filters es una clase de utilidad del controlador Java de MongoDB que proporciona una API fluida para generar un constructo de filtros de consultas. Esta clase ofrece métodos de fábrica estáticos que crean instancias de objetos Bson que representan diversas condiciones de consulta. Los métodos más comúnmente utilizados son eq() para las comparaciones de igualdad, gt(), lt(), gte() y lte() para las comparaciones numéricas, and() y or() para las combinaciones de condiciones múltiples, in() y nin() para las pruebas de suscripción a matrices y regex() para la coincidencia de patrones. La clase está diseñada para ser segura en relación con los tipos y proporciona una mejor comprobación en tiempo de compilación en comparación con las consultas basadas en documentos sin procesar, lo que la convierte en el enfoque preferido para crear consultas de DocumentDB en aplicaciones Java. La gestión de errores es sólida, con claras excepciones en el caso de constructos de filtros no válidas.

FindIterable es una interfaz especializada diseñada para gestionar el resultado del método find(). Proporciona un amplio conjunto de métodos para refinar y controlar la ejecución de consultas, y ofrece una API fluida para el encadenamiento de métodos. La interfaz incluye métodos esenciales de modificación de consultas, como limit() para restringir el número de documentos devueltos, skip() para paginar, sort() para ordenar los resultados, projection() para seleccionar campos específicos y hint() para seleccionar índices. Las operaciones de agrupar en lotes, omitir y limitar en FindIterable son herramientas esenciales de paginación y administración de datos que ayudan a controlar la forma en que se recuperan y procesan los documentos de la base de datos.

Agrupar en lotes (batchSize) controla el número de documentos que DocumentDB devuelve al cliente en una única red de ida y vuelta. Cuando establece un tamaño de lote, DocumentDB no devuelve todos los documentos coincidentes a la vez, sino que los devuelve en grupos del tamaño de lote especificado.

Omitir le permite desplazar el punto de partida de los resultados y, básicamente, le indica a DocumentDB que omita un número específico de documentos antes de empezar a devolver las coincidencias. Por ejemplo, skip(20) omitirá los primeros 20 documentos coincidentes. Esto se suele utilizar en situaciones de paginación en los que se desean recuperar páginas de resultados posteriores.

Limitar restringe el número total de documentos que se pueden devolver a partir de una consulta. Si especifica limit(n), DocumentDB dejará de devolver documentos después de haber devuelto «n» documentos, incluso si hay más coincidencias en la base de datos.

FindIterable admite patrones de iterador y cursor cuando recupera documentos de Amazon DocumentDB. La ventaja de usar FindIterable como iterador es que permite la carga diferida de los documentos y solo los recupera cuando la aplicación los solicita. Otra ventaja de usar el iterador es que no es responsable de mantener la conexión con el clúster y, por lo tanto, no es necesario cerrar la conexión de forma explícita.

FindIterable también proporciona soporte para MongoCursor, que permite utilizar patrones de cursor cuando trabaja con consultas de Amazon DocumentDB. El MongoCursor es una implementación específica del controlador Java de MongoDB que proporciona control sobre las operaciones de la base de datos y la administración de recursos. Implementa la AutoCloseable interfaz, lo que permite una gestión explícita de los recursos mediante try-with-resources bloques, lo cual es crucial para cerrar correctamente las conexiones de las bases de datos y liberar los recursos del servidor. De forma predeterminada, el tiempo de espera del cursor es de 10 minutos y DocumentDB no le da la opción de cambiar este comportamiento de tiempo de espera. Cuando trabaje con datos agrupados, asegúrese de recuperar el siguiente lote de datos antes de que se agote el tiempo de espera del cursor. Una consideración clave a la hora de usar el MongoCursor es que requiere un cierre explícito para evitar la pérdida de recursos.

En esta sección, se presentan varios ejemplos de find(), Filters y FindIterable.

En el siguiente ejemplo de código se muestra cómo usar find() para recuperar un solo documento con su campo «restaurantId»:

Document filter = new Document("restaurantId", "REST-21G145"); Document result = collection.find(filter).first();

Si bien el uso de Filters permite comprobar mejor los errores en el momento de la compilación, el controlador java también permite especificar un filtro de Bson directamente en el método find(). El siguiente código de ejemplo pasa el documento Bson a find():

result = collection.find(new Document("$and", Arrays.asList( new Document("rating.totalReviews", new Document("$gt", 1000)), new Document("priceRange", "$$"))))

El siguiente código de ejemplo muestra varios ejemplos del uso de la clase Filters con find():

FindIterable < Document > results; // Exact match results = collection.find(Filters.eq("name", "Thai Curry Palace")); // Not equal results = collection.find(Filters.ne("cuisine", "Thai")); // find an element in an array results = collection.find(Filters.in("features", Arrays.asList("Private Dining"))); // Greater than results = collection.find(Filters.gt("rating.average", 3.5)); // Between (inclusive) results = collection.find(Filters.and( Filters.gte("rating.totalReviews", 100), Filters.lte("rating.totalReviews", 200))); // AND results = collection.find(Filters.and( Filters.eq("cuisine", "Thai"), Filters.gt("rating.average", 4.5))); // OR results = collection.find(Filters.or( Filters.eq("cuisine", "Thai"), Filters.eq("cuisine", "American"))); // All document where the Field exists results = collection.find(Filters.exists("michelin")); // Regex results = collection.find(Filters.regex("name", Pattern.compile("Curry", Pattern.CASE_INSENSITIVE))); // Find all document where the array contain the list of value regardless of its order results = collection.find(Filters.all("features", Arrays.asList("Private Dining", "Parking"))); // Array size results = collection.find(Filters.size("features", 4));

En el siguiente ejemplo se muestra cómo encadenar las operaciones de sort(), skip(), limit() y batchSize() en un objeto de FindIterable. El orden en que se proporcionen estas operaciones influirá en el rendimiento de la consulta. Como práctica recomendada, el orden de estas operaciones debe ser sort(), projection(), skip(), limit() y batchSize().

FindIterable < Document > results = collection.find(Filters.gt("rating.totalReviews", 1000)) // Sorting .sort(Sorts.orderBy( Sorts.descending("address.city"), Sorts.ascending("cuisine"))) // Field selection .projection(Projections.fields( Projections.include("name", "cuisine", "priceRange"), Projections.excludeId())) // Pagination .skip(20) .limit(10) .batchSize(2);

El siguiente ejemplo de código muestra cómo crear un iterador en FindIterable. Usa el constructo forEach de Java para recorrer el conjunto de resultados.

collection.find(Filters.eq("cuisine", "American")).forEach(doc -> System.out.println(doc.toJson()));

En el último ejemplo de código de find(), se muestra cómo usar el cursor() para la recuperación de documentos. Crea el cursor en el bloque de prueba, lo que garantiza que el cursor se cierre cuando el código salga del bloque de prueba.

try (MongoCursor < Document > cursor = collection.find(Filters.eq("cuisine", "American")) .batchSize(25) .cursor()) { while (cursor.hasNext()) { Document doc = cursor.next(); System.out.println(doc.toJson()); } } // Cursor automatically closed

Actualización de documentos existentes en una colección de DocumentDB

Amazon DocumentDB proporciona mecanismos flexibles y potentes para modificar los documentos existentes e insertar otros nuevos cuando no existen. El controlador Java de MongoDB ofrece varios métodos de actualización: updateOne() para actualizaciones de un solo documento, updateMany() para actualizaciones de varios documentos y replaceOne() para la sustitución completa de documentos. Además de estos tres métodos, Updates, UpdateOptions y UpdateResult son otros componentes fundamentales que proporcionan los componentes básicos para las operaciones de actualización en los controladores Java de MongoDB.

La clase Updates del controlador Java de MongoDB es una clase de utilidad que proporciona métodos de fábrica estáticos para crear operadores de actualización. Sirve como el compilador principal para crear operaciones de actualización de forma legible y segura. Métodos básicos como set(), unset() y inc() permiten la modificación directa de los documentos. El poder de esta clase se hace evidente cuando se combinan varias operaciones mediante el método Updates.combine(), que permite ejecutar varias operaciones de actualización de forma atómica, lo que garantiza la coherencia de datos.

UpdateOptions es una potente clase de configuración del controlador Java de MongoDB que proporciona capacidades de personalización esenciales para las operaciones de actualización de documentos. Dos aspectos importantes de esta clase son la compatibilidad con actualizaciones y filtros de matriz para las operaciones de actualización. La característica de actualización, habilitada mediante upsert(true), permite crear nuevos documentos cuando no se encuentra ningún documento coincidente durante una operación de actualización. Mediante arrayFilters(), la operación de actualización puede actualizar con precisión los elementos de la matriz que cumplan criterios específicos.

UpdateResult en el controlador Java de MongoDB proporciona el mecanismo de retroalimentación que detalla el resultado de una operación de actualización. Esta clase agrupa tres métricas clave: el número de documentos que cumplen los criterios de actualización (matchedCount), el número de documentos realmente modificados (modifiedCount) y la información sobre cualquier documento alterado (upsertedId). Comprender estas métricas es esencial para gestionar correctamente los errores, verificar las operaciones de actualización y mantener la coherencia de datos en las aplicaciones.

Actualización y sustitución de un solo documento

En DocumentDB, la actualización de un solo documento se puede realizar mediante el método updateOne(). Este método utiliza un parámetro de filtro, normalmente proporcionado por la clase Filters, para identificar el documento que se va a actualizar, un parámetro de Updat que determina qué campos se van a actualizar y un parámetro de UpdateOptions opcional para establecer diferentes opciones de actualización. El uso del método updateOne() solo actualizará el primer documento que coincida con los criterios de selección. El siguiente código de ejemplo actualiza un solo campo de un documento:

collection.updateOne(Filters.eq("restaurantId", "REST-Y2E9H5"), Updates.set("name", "Amazing Japanese sushi"));

Para actualizar varios campos de un documento, use updateOne() con Update.combine() como se muestra en el siguiente ejemplo. En este ejemplo también se muestra cómo agregar un elemento a una matriz del documento.

List<Bson> updates = new ArrayList<>(); // Basic field updates updates.add(Updates.set("name", "Shanghai Best")); // Array operations updates.add(Updates.addEachToSet("features", Arrays.asList("Live Music"))); // Counter updates updates.add(Updates.inc("rating.totalReviews", 10)); // Combine all updates Bson combinedUpdates = Updates.combine(updates); // Execute automic update with one call collection.updateOne(Filters.eq("restaurantId","REST-1J83NH"), combinedUpdates);

En el siguiente ejemplo de código se muestra cómo actualizar un documento de la base de datos. Si el documento especificado no existe, la operación lo insertará automáticamente como documento nuevo. Este código también muestra cómo utilizar las métricas disponibles a través del objeto de UpdateResult.

Bson filter = Filters.eq("restaurantId", "REST-0Y9GL0"); Bson update = Updates.set("cuisine", "Indian"); // Upsert operation UpdateOptions options = new UpdateOptions().upsert(true); UpdateResult result = collection.updateOne(filter, update, options); if (result.getUpsertedId() != null) { System.out.println("Inserted document with _id: " + result.getUpsertedId()); } else { System.out.println("Updated " + result.getModifiedCount() + " document(s)"); }

El siguiente ejemplo de código muestra cómo reemplazar completamente un documento existente por uno nuevo mediante el método replaceOne(), en lugar de actualizar campos individuales. El método replaceOne() sobrescribe todo el documento y conserva únicamente el campo _id del original. Si varios documentos coinciden con los criterios del filtro, solo se reemplaza el primer documento encontrado.

Document newDocument = new Document() .append("restaurantId", "REST-0Y9GL0") .append("name", "Bhiryani Adda") .append("cuisine", "Indian") .append("rating", new Document() .append("average", 4.8) .append("totalReviews", 267)) .append("features", Arrays.asList("Outdoor Seating", "Live Music")); UpdateResult result = collection.replaceOne( Filters.eq("restaurantId", "REST-0Y9GL0"), newDocument); System.out.printf("Modified %d document%n", result.getModifiedCount());

Actualización de documentos múltiples

Hay dos formas de actualizar varios documentos de una colección de forma simultánea. Puede utilizar el método de updateMany() o usar el UpdateManyModel con el método bulkWrite(). El método updateMany() utiliza un parámetro de filtro para seleccionar los documentos que se van a actualizar, el parámetro Update para identificar los campos que se van a actualizar y un parámetro UpdateOptions opcional para especificar las opciones de actualización.

El siguiente ejemplo de código demuestra el uso del método updateMany():

Bson filter = Filters.and( Filters.in("features", Arrays.asList("Private Dining")), Filters.eq("cuisine", "Thai")); UpdateResult result1 = collection.updateMany(filter, Updates.set("priceRange", "$$$"));

El siguiente ejemplo de código demuestra el método de bulkWrite() con la misma actualización:

BulkWriteOptions options = new BulkWriteOptions().ordered(false); List < WriteModel < Document >> updates = new ArrayList < > (); Bson filter = Filters.and( Filters.in("features", Arrays.asList("Private Dining")), Filters.eq("cuisine", "Indian")); Bson updateField = Updates.set("priceRange", "$$$"); updates.add(new UpdateManyModel < > (filter, updateField)); BulkWriteResult result = collection.bulkWrite(updates, options); System.out.printf("Modified %d document%n", result.getModifiedCount());

Eliminación de documentos de una colección de DocumentDB

El controlador Java de MongoDB ofrece deleteOne() para eliminar un solo documento y deleteMany() para eliminar varios documentos que coincidan con criterios específicos. Al igual que la actualización, la operación de eliminación también se puede utilizar con el método bulkWrite(). Ambos deleteOne() y deleteMany() devuelven un objeto DeleteResult que proporciona información sobre el resultado de la operación, incluido el recuento de documentos eliminados. A continuación, se muestra un ejemplo de cómo usar deleteMany() para eliminar varios documentos:

Bson filter = Filters.and( Filters.eq("cuisine", "Thai"), Filters.lt("rating.totalReviews", 50)); DeleteResult result = collection.deleteMany(filter); System.out.printf("Deleted %d document%n", result.getDeletedCount());

Gestión de errores con lógica de reintento

Una estrategia sólida de gestión de errores con Amazon DocumentDB debería incluir la categorización de los errores en reintentables (como tiempos de espera de la red o problemas de conexión) y no reintentables (como errores de autenticación o consultas no válidas). En el caso de los errores de operación debidos a errores que deben reintentarse, se debe implementar un intervalo de tiempo entre cada reintento, así como un máximo de reintentos. Las operaciones CRUD deben realizarse en un bloque try-catch que capture MongoException y sus subclases. Además, debe incluir la supervisión y el registro de los errores para garantizar la visibilidad operativa. El siguiente es un ejemplo de código que muestra cómo implementar la gestión de errores de reintentos:

int MAX_RETRIES = 3; int INITIAL_DELAY_MS = 1000; int retryCount = 0; while (true) { try { crud_operation(); //perform crud that will throw MongoException or one of its subclass break; } catch (MongoException e) { if (retryCount < MAX_RETRIES) { retryCount++; long delayMs = INITIAL_DELAY_MS * (long) Math.pow(2, retryCount - 1); try { TimeUnit.MILLISECONDS.sleep(delayMs); } catch (InterruptedException t) { Thread.currentThread().interrupt(); throw new RuntimeException("Retry interrupted", t); } continue; } else throw new RuntimeException("Crud operation failed", e); } }