使用 Jackson 實現高效的 POJO 與 Java MongoDBObject 之間的映射
1. 引言
在 Java 應用程式中使用 MongoDB 時,我們經常需要在普通 Java 物件 (POJO) 和 MongoDB 文件格式之間進行轉換。雖然 MongoDB 的 Java 驅動程式提供了原生的 POJO 編解碼器,但許多專案已經在程式碼庫的其他部分利用 Jackson 進行 JSON 處理。
在本教程中,我們將探討兩個基於 Jackson 的庫,它們提供高效的 POJO 映射:MongoJack 和 bson4jackson。根據專案需求的不同,這兩種方法各有優勢。
2. 理解問題
MongoDB 以 BSON(二進位 JSON)格式儲存數據,其中包含標準 JSON 中不存在的類型,例如ObjectId, Date和Decimal128.標準的 Jackson 會將 POJO 序列化為 JSON 字串,但將這些字串轉換為 MongoDB 文件需要效率低下的兩步驟過程。
首先,讓我們將基礎MongoDB驅動程式依賴項新增到pom.xml:
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>5.6.1</version>
</dependency>
現在,讓我們定義一個將在整個範例中使用的類別:
public class Product {
private String id;
private String name;
private double price;
// getters and setters
}
挑戰在於如何有效地將此 POJO 映射到 MongoDB 的Document格式,同時保留類型資訊並避免不必要的字串中間體。
3. 使用 MongoJack 進行 POJO 映射
MongoJack提供了一個高階抽象層,它使用 Jackson 的序列化功能封裝了 MongoDB 集合。它透明地處理 BSON 轉換,使 CRUD 操作變得簡單直接。
3.1. MongoJack 設定和服務類
現在,讓我們新增所需的依賴項:
<dependency>
<groupId>org.mongojack</groupId>
<artifactId>mongojack</artifactId>
<version>5.0.3</version>
</dependency>
接下來,讓我們更新 POJO 以使用 MongoJack 的ObjectId註解:
public class Product {
@ObjectId
@Id
private String id;
private String name;
private double price;
// getters and setters
}
@ObjectId註解告訴 MongoJack 將id字段序列化為 MongoDB ObjectId而不是純字串。
接下來,我們新增一個服務類別來示範基本的 CRUD 操作:
public class ProductService {
private final JacksonMongoCollection<Product> collection;
public ProductService(MongoDatabase database) {
this.collection = JacksonMongoCollection.builder()
.build(database, "products", Product.class, UuidRepresentation.STANDARD);
}
public void save(Product product) {
collection.insertOne(product);
}
public Product findById(String id) {
return collection.findOneById(id);
}
public long count() {
return collection.countDocuments();
}
}
MongoJack 的JacksonMongoCollection封裝了標準的 MongoDB 集合,並自動處理序列化。我們無需手動在 POJO 和Document's之間進行轉換。
3.2. 測試 MongoJack 集成
因此,讓我們驗證 MongoJack 是否能正確持久化和檢索我們的 POJO 物件。為此,我們將使用 Flapdoodle 啟動一個記憶體中的 MongoDB 實例:
@Test
void whenSavingProduct_thenProductIsPersisted() {
Product product = new Product("Laptop", 999.99);
productService.save(product);
assertNotNull(product.getId());
assertEquals(1, productService.count());
}
此測試表明 MongoJack 可以無縫處理序列化(保存時)和反序列化(檢索時)。因此,POJO 字段可以正確地對應到 BSON 文件字段,反之亦然,無需任何手動轉換程式碼。
4. 使用 bson4jackson 進行直接 BSON 序列化
雖然 MongoJack 提供了便利性,但bson4jackson透過擴展 Jackson 的核心功能直接處理 BSON,從而提供了更強大的控制力。因此,當我們需要自訂序列化邏輯或希望與現有的 Jackson 配置整合時,這種方法非常有用。
4.1. bson4jackson 實現
考慮到這一點,讓我們來編寫程式碼並添加依賴項:
<dependency>
<groupId>de.undercouch</groupId>
<artifactId>bson4jackson</artifactId>
<version>2.18.0</version>
</dependency>
現在,讓我們建立一個處理轉換的映射器類別:
public class BsonProductMapper {
private final ObjectMapper objectMapper;
public BsonProductMapper() {
this.objectMapper = new ObjectMapper(new BsonFactory());
}
public byte[] toBytes(Product product) throws JsonProcessingException {
return objectMapper.writeValueAsBytes(product);
}
public Product fromBytes(byte[] bson) throws IOException {
return objectMapper.readValue(bson, Product.class);
}
public Document toDocument(Product product) throws IOException {
byte[] bytes = toBytes(product);
RawBsonDocument bsonDoc = new RawBsonDocument(bytes);
return Document.parse(bsonDoc.toJson());
}
public Product fromDocument(Document document) throws IOException {
BsonDocument bsonDoc = document.toBsonDocument();
BasicOutputBuffer buffer = new BasicOutputBuffer();
new BsonDocumentCodec().encode(
new BsonBinaryWriter(buffer),
bsonDoc, EncoderContext
.builder()
.build()
);
return fromBytes(buffer.toByteArray());
}
}
BsonProductMapper類別封裝了我們的 bson4jackson 轉換邏輯。下面我們來解釋一下它的主要特性。
在建構函數中, BsonFactory BsonFactory Jackson 的預設 JSON 工廠,從而實現了直接 BSON 序列化。這繞過了 JSON 字串中間層,為高吞吐量應用程式帶來了更好的效能。
由於BsonFactory建構函數的配置, toBytes()方法直接序列化為二進位 BSON。
類似地, fromBytes()使用BsonFactory來處理二進位解析,將 BSON 類型對應回其 Java 等效類型。
` toDocument()方法將我們的 BSON 位元組流與 MongoDB 的Document類別連接起來。我們首先將 POJO 序列化為位元組流,然後將其封裝在 ` RawBsonDocument中(該物件直接解析原始 BSON 資料)。接下來,透過 JSON 將其轉換為標準的Document 。
fromDocument()方法會反向執行此程序。我們將Document轉換為BsonDocument ,然後使用 MongoDB 的BsonDocumentCodec將其編碼為BasicOutputBuffer 。此緩衝區保存原始 BSON 位元組,我們將這些位元組傳遞給fromBytes()以進行最終的反序列化。
4.2. 測試 bson4jackson 序列化
讓我們驗證一下 bson4jackson 是否能正確地將我們的 POJO 序列化為二進位 BSON 格式:
@Test
void whenSerializingProduct_thenReturnsByteArray() throws IOException {
Product product = new Product("Test Product", 29.99);
byte[] bytes = mapper.toBytes(product);
assertNotNull(bytes);
assertTrue(bytes.length > 0);
}
從測試結果可以看出,bson4jackson 為我們的 POJO 產生了一個有效的位元組陣列表示。與 JSON 序列化不同,這種二進位格式與 MongoDB 的內部儲存格式直接相容。
現在,讓我們測試一下反序列化過程:
@Test
void givenSerializedProduct_whenDeserializing_thenReturnsProduct() throws IOException {
Product product = new Product("Test Product", 29.99);
byte[] bytes = mapper.toBytes(product);
Product deserializedProduct = mapper.fromBytes(bytes);
assertEquals(product.getName(), deserializedProduct.getName());
assertEquals(product.getPrice(), deserializedProduct.getPrice(), 0.01);
}
此測試驗證二進位 BSON 資料是否能正確反序列化回 POJO 類型BsonFactory並保留所有欄位值。 BsonFactory 會自動處理類型對應。
4.3. 測試文檔轉換
為了與 MongoDB 集合集成,我們需要在 POJO 和 Document 物件之間進行轉換:
@Test
void whenConvertingProductToDocument_thenReturnsValidDocument() throws IOException {
Product product = new Product("Test Product", 29.99);
Document document = mapper.toDocument(product);
assertNotNull(document);
assertEquals(product.getName(), document.getString("name"));
assertEquals(product.getPrice(), document.getDouble("price"), 0.01);
}
此測試表明,我們的映射器能夠正確地將 POJO 轉換為 MongoDB 文件。因此,可以使用標準驅動程式將產生的文件直接插入 MongoDB 集合中。
最後,讓我們驗證一下Document格式的完整往返流程:
@Test
void whenRoundTrippingProduct_thenDataIsPreserved() throws IOException {
Product product = new Product("Round Trip Product", 149.99);
Document document = mapper.toDocument(product);
Product roundTrippedProduct = mapper.fromDocument(document);
assertEquals(product.getName(), roundTrippedProduct.getName());
assertEquals(product.getPrice(), roundTrippedProduct.getPrice(), 0.01);
}
該測試證實,在整個轉換週期中,資料完整性都得到了維護:從 POJO 到 bson 位元組,再到Document ,再到 bson 位元組,最後回到 POJO。
5. 性能考量
選擇 MongoJack 還是 bson4jackson 取決於我們的特定需求。
MongoJack 最適合需要快速開發且樣板程式碼量最少的場景。它提供了一套完整的解決方案,內建集合包裝器和查詢支援。缺點是序列化過程的自訂彈性較低。
bson4jackson 更適合需要對序列化進行更深入控制的應用,或是已經擁有 Jackson 基礎架構的應用。由於它在底層運行,我們可以微調特定的轉換路徑並使用自訂類型處理程序。
對於大多數應用而言,這些方法之間的效能差異可以忽略不計。主要考慮因素應該是開發人員的效率以及每個庫與我們現有程式碼庫的整合程度。
6. 結論
在本文中,我們探討了使用 Jackson 將 POJO 對應到 MongoDB 文件的兩種高效方法。
MongoJack 提供了一個進階且方便的 API,只需極少的配置即可處理常見用例。它能夠自動產生ObjectId ,並且與 MongoDB 集合無縫整合。
另一方面,bson4jackson 透過擴展 Jackson 的功能,使其能夠直接序列化為 BSON 格式,從而提供更底層的控制。這種方法更適合需要自訂序列化邏輯或與現有 Jackson 配置整合的應用。
最後,這兩個函式庫都消除了效率低下的 JSON 字串中間層,並為 POJO 到 MongoDB 的映射提供了高效的選項。
和往常一樣,程式碼可以在 GitHub 上找到。