使用 Java 在 Amazon DocumentDB 中執行 CRUD 操作 - Amazon DocumentDB

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

使用 Java 在 Amazon DocumentDB 中執行 CRUD 操作

本節討論如何使用 MongoDB Java 驅動程式在 Amazon DocumentDB 中執行 CRUD (建立、讀取、更新、刪除) 操作。

在 DocumentDB 集合中建立和插入文件

將文件插入 Amazon DocumentDB 可讓您將新資料新增至集合。有幾種方式可以執行插入,取決於您的需求和正在使用的資料量。將個別文件插入集合的最基本方法是 insertOne()。若要一次插入多個文件,您可以使用 insertMany()方法,這可讓您在單一操作中新增文件陣列。在 DocumentDB 集合中插入許多文件的另一個方法是 bulkWrite()。在本指南中,我們會討論在 DocumentDB 集合中建立文件的所有這些方法。

insertOne()

讓我們從檢查如何將個別文件插入 Amazon DocumentDBB 集合開始。使用 insertOne()方法完成插入單一文件。此方法採用 BsonDocument 進行插入,並傳回可用來取得新插入文件物件 ID 的InsertOneResult物件。以下範例程式碼顯示將一個餐廳文件插入集合:

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

使用 時insertOne(),請務必包含適當的錯誤處理。例如,在上述程式碼中,「restaurantId」具有唯一的索引,因此再次執行此程式碼將引發下列 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()

用於將許多文件插入集合的主要方法是 insertMany() 和 bulkWrite()

insertMany() 方法是在單一操作中插入多個文件的最簡單方法。它接受文件清單並將其插入集合。當您插入一批彼此獨立且不需要任何特殊處理或混合操作的新文件時,此方法很理想。下列程式碼顯示從檔案讀取 JSON 文件並將其插入集合。insertMany() 函數會傳回 InsertManyResultInsertManyResult 物件,可用來取得所有插入文件IDs。

// 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()

bulkWrite() 方法允許 在單一批次中執行多個寫入操作 (插入、更新、刪除)。當您需要在單一批次中執行不同類型的操作bulkWrite()時,可以使用 ,例如插入一些文件,同時更新其他文件。 bulkWrite()支援兩種類型的批次寫入,有序和無序:

  • 排序操作 — (預設) Amazon DocumentDB 會依序處理寫入操作,並在遇到的第一個錯誤時停止。這在操作順序很重要時很有用,例如稍後的操作取決於較早的操作。不過,排序操作通常比未排序的操作慢。使用有序操作時,您必須解決批次在第一次錯誤時停止的情況,這可能會導致某些操作未處理。

  • 未排序的操作 — 允許 Amazon DocumentDB 將插入處理為資料庫中的單一執行。如果一個文件發生錯誤,操作會繼續其餘文件。當您插入大量資料時,這特別有用,可以容忍一些失敗,例如在資料遷移或大量匯入期間,某些文件可能因重複的金鑰而失敗。使用未排序的操作時,您必須解決部分成功案例,其中某些操作成功,而其他操作失敗。

使用 bulkWrite()方法時,需要一些基本類別。首先, WriteModel類別會做為所有寫入操作的基本類別,並搭配 InsertOneModelDeleteOneModel、、 UpdateOneModel UpdateManyModel和 等特定實作DeleteManyModel來處理不同類型的操作。

BulkWriteOptions 類別是設定大量操作行為的必要項目,例如設定排序/未排序的執行或略過文件驗證。BulkWriteResult 類別提供執行結果的詳細資訊,包括插入、更新和刪除文件的計數。

對於錯誤處理, MongoBulkWriteException類別至關重要,因為它包含大量操作期間任何失敗的資訊,而 BulkWriteError類別提供個別操作失敗的特定詳細資訊。下列程式碼顯示插入文件清單,以及更新和刪除單一文件的範例,全部都在單一bulkWrite()方法呼叫的執行中。此程式碼也會示範如何使用 BulkWriteOptionsBulkWriteResult,以及操作的適當錯誤處理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()); }

可重試寫入

與 MongoDB 不同,Amazon DocumentDB 不支援可重試的寫入。因此,您必須在其應用程式中實作自訂重試邏輯,特別是在處理網路問題或暫時服務無法使用時。實作良好的重試策略通常涉及增加重試嘗試之間的延遲,以及限制重試總數。如需使用錯誤處理建置重試邏輯的程式碼範例,請參閱使用重試邏輯處理錯誤下文。

從 DocumentDB 集合讀取和擷取資料

在 Amazon DocumentDB 中查詢文件圍繞著數個關鍵元件,可讓您精確擷取和操作資料。find() 方法是 MongoDB Java 驅動程式中的基本查詢 APIs。它允許複雜的資料擷取,具有許多篩選、排序和投影結果的選項。除了 find()方法之外, FiltersFindIterable也是提供 MongoDB Java 驅動程式中查詢操作之建置區塊的兩個其他基本元件。

Filters 類別是 MongoDB Java 驅動程式中的公用程式類別,可提供建構查詢篩選條件的流暢 API。此類別提供靜態原廠方法,可建立代表各種查詢條件的Bson物件執行個體。最常使用的方法包括eq()用於等式比較、gt()lt()gte()、 和 lte() 用於數值比較,and()以及or()用於合併多個條件,in()以及nin()用於陣列成員資格測試,以及regex()用於模式比對。類別設計為類型安全,並提供比原始文件型查詢更好的編譯時間檢查,使其成為在 Java 應用程式中建構 DocumentDB 查詢的首選方法。錯誤處理功能強大,因篩選條件建構無效而擲回明顯例外狀況。

FindIterable 是一種專門的界面,旨在處理 find()方法的結果。它提供一組豐富的方法來精簡和控制查詢執行,為方法鏈結提供流暢的 API。界面包含必要的查詢修改方法,例如limit()用於限制傳回的文件數量、skip()用於分頁、sort()用於排序結果、projection()用於選取特定欄位,以及hint()用於索引選取。中的批次、略過和限制操作FindIterable是重要的分頁和資料管理工具,可協助控制如何從資料庫擷取和處理文件。

批次處理 (batchSize) 控制 DocumentDB 在單一網路往返中傳回給用戶端的文件數量。當您設定批次大小時,DocumentDB 不會一次傳回所有相符的文件,而是在指定批次大小的群組中傳回它們。

略過可讓您偏移結果的起點,基本上是在開始傳回相符項目之前,告知 DocumentDB 略過指定數量的文件。例如, skip(20)會略過前 20 個相符的文件。這通常用於您想要擷取後續結果頁面的分頁案例。

限制會限制可從查詢傳回的文件總數。當您指定 時limit(n),DocumentDB 會在傳回「n」文件後停止傳回文件,即使資料庫中有更多相符項目。

FindIterable 從 Amazon DocumentDB 擷取文件時, 同時支援迭代器和游標模式。使用 FindIterable作為迭代器的好處是它允許延遲載入文件,並且僅在應用程式請求時擷取文件。使用迭代器的另一個好處是,您不需要負責維護叢集的連線,因此不需要明確關閉連線。

FindIterable 也提供支援MongoCursor,允許在使用 Amazon DocumentDB 查詢時使用游標模式。 MongoCursor 是一種 MongoDB Java 驅動程式特定的實作,可讓您控制資料庫操作和資源管理。它實作 AutoCloseable界面,允許透過 try-with-resources 區塊進行明確的資源管理,這對於正確關閉資料庫連線和釋放伺服器資源至關重要。根據預設,游標會在 10 分鐘內逾時,而 DocumentDB 不會讓您選擇變更此逾時行為。使用批次資料時,請務必在游標逾時之前擷取下一批次的資料。使用 時的一個關鍵考量MongoCursor是,它需要明確關閉以防止資源洩漏。

在本節中,針對 find()Filters和 提供數個範例FindIterable

下列程式碼範例示範如何使用其「restaurantId」欄位find()來擷取單一文件:

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

即使使用 Filters 可提供更好的編譯時間錯誤檢查,java 驅動程式也可讓您直接在 find()方法中指定Bson篩選條件。下列範例程式碼會將Bson文件傳遞給 find()

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

下一個範例程式碼顯示將 Filters類別與 搭配使用的幾個範例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));

下列範例示範如何在 FindIterable 物件batchSize()上鏈結 sort()limit()skip()和 的操作。提供這些操作的順序將影響查詢的效能。最佳實務是,這些操作的順序應為 sort()skip()projection()limit()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);

下列範例程式碼顯示在 上建立迭代器FindIterable。它使用 Java forEach 的建構來周遊結果集。

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

在最後一個find()程式碼範例中,它示範如何使用 cursor() 進行文件擷取。它會在嘗試區塊中建立游標,以確保在程式碼結束嘗試區塊時游標會關閉。

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

更新 DocumentDB 集合中的現有文件

Amazon DocumentDB 提供靈活且強大的機制,用於修改現有文件,並在文件不存在時插入新文件。MongoDB Java 驅動程式提供數種更新方法:updateOne()適用於單一文件更新、updateMany()適用於多個文件更新,以及replaceOne()適用於完整文件取代。除了這三種方法之外,UpdatesUpdateOptionsUpdateResult是為 MongoDB Java 驅動程式中的更新操作提供建置區塊的其他基本元件。

MongoDB Java 驅動程式中的 Updates類別是公用程式類別,可提供建立更新運算子的靜態原廠方法。它可做為主要建置器,以類型安全且可讀取的方式建構更新操作。set()unset()和 等基本方法inc()允許直接修改文件。使用 Updates.combine()方法結合多個操作時,此類別的強大功能會變得明顯,這允許以原子方式執行多個更新操作,以確保資料一致性。

UpdateOptions 是 MongoDB Java 驅動程式中功能強大的組態類別,可為文件更新操作提供基本的自訂功能。此類別的兩個重要層面是為更新操作提供 upsert 和陣列篩選條件支援。透過 啟用的 upsert 功能upsert(true)允許在更新操作期間找不到相符的文件時建立新的文件。透過 arrayFilters(),更新操作可以精確更新符合特定條件的陣列元素。

UpdateResult MongoDB Java 驅動程式中的 提供回饋機制,詳細說明更新操作的結果。此類別會封裝三個關鍵指標:符合更新條件 () 的文件數量matchedCount、實際修改的文件數量 (modifiedCount),以及任何備份文件的相關資訊 ()upsertedId。了解這些指標對於正確處理錯誤、驗證更新操作,以及維護應用程式中的資料一致性至關重要。

更新和取代單一文件

在 DocumentDB 中,可以使用 updateOne() 方法完成更新單一文件。此方法採用篩選參數 (通常由 Filters類別提供) 來識別要更新的文件、決定要更新哪些欄位的 Update 參數,以及用於設定更新不同選項的選用UpdateOptions參數。使用 updateOne()方法只會更新第一個符合選取條件的文件。下列範例程式碼會更新一個文件的單一欄位:

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

若要更新一個文件中的多個欄位,請使用 updateOne()搭配 Update.combine(),如下列範例所示。此範例也會示範如何將項目新增至文件中的 陣列。

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

下列程式碼範例示範如何更新資料庫中的文件。如果指定的文件不存在,操作會自動將其插入為新文件。此程式碼也會示範如何使用透過 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)"); }

下列程式碼範例示範如何使用 replaceOne()方法將現有文件完全取代為新文件,而不是更新個別欄位。replaceOne() 方法會覆寫整個文件,只保留原始_id文件的欄位。如果多個文件符合篩選條件,則只會取代第一個遇到的文件。

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

更新多個文件

有兩種方式可以同時更新集合中的多個文件。您可以使用 updateMany()方法,或使用 UpdateManyModel搭配 bulkWrite()方法。updateMany() 方法使用篩選參數來選取要更新的文件、用來識別要更新之欄位的Update參數,以及用來指定更新選項的選用UpdateOptions參數。

下列範例程式碼示範 updateMany()方法的使用情況:

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

下列範例程式碼示範使用相同更新bulkWrite()的方法:

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

從 DocumentDB 集合移除文件

MongoDB Java 驅動程式deleteOne()提供移除單一文件和deleteMany()移除多個符合特定條件的文件。如同更新,刪除操作也可以與 bulkWrite()方法搭配使用。deleteOne() 和 都會deleteMany()傳回DeleteResult物件,提供操作結果的相關資訊,包括已刪除的文件計數。以下是使用 deleteMany()移除多個文件的範例:

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

使用重試邏輯處理錯誤

Amazon DocumentDB 的強大錯誤處理策略應該將錯誤分類為可重試 (例如網路逾時、連線問題) 和不可重試 (例如身分驗證失敗、無效的查詢)。對於因應重試的錯誤而導致的操作失敗,它應該在每次重試以及最大重試嘗試之間實作時間延遲。CRUD 操作應位於擷取 MongoException及其子類別的 try-catch 區塊中。此外,還應包括監控和記錄錯誤,以實現操作可見性。以下是示範如何實作重試錯誤處理的範例程式碼:

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