在 Java 中读取 MongoDB 文档,哪个 Document 类最合适?
Source: Dev.to
TL;DR
答案就在标题中——使用 Document。
概述
BSON 是一种二进制序列化格式(类似于 protobuf),MongoDB 用它来实现高效的存储和网络传输。
与其在读取或写入字段时扫描并重写整个字节序列,不如使用 内存中的对象,它提供了方便的方法。
在服务器端,MongoDB 使用可变的 BSON 对象;在客户端,Java 驱动提供了多个实现 Bson 接口的类。
下面是 MongoDB Java 驱动中 五种文档类 的简要指南、它们的特性以及何时选择它们。
1. Document (大多数应用的推荐选择)
- 类型:
Map(由LinkedHashMap支持,以保留插入顺序) - 关键特性
- 松散类型:值是普通的 Java 对象(
String、Integer、Date,……) - 灵活:易于处理结构动态的文档
- Map API:提供所有标准的
Map操作
- 松散类型:值是普通的 Java 对象(
- 何时使用
- 需要简洁、灵活且“开箱即用”的表示。
- 不需要严格的 BSON 类型安全。
2. BsonDocument
- 类型:
Map(同样由LinkedHashMap支持) - 关键特性
- 类型安全:每个值必须是 BSON 库类型(
BsonString、BsonInt32、BsonDocument,……) - 更严格的 API:编译时安全,但代码更冗长
- Map API:与
Document相同,只是使用BsonValue
- 类型安全:每个值必须是 BSON 库类型(
- 何时使用
- 需要显式控制 BSON 类型(例如精确的类型处理,或与要求
BsonDocument的 API 交互)。
- 需要显式控制 BSON 类型(例如精确的类型处理,或与要求
3. RawBsonDocument
- 类型:对 原始字节数组(BSON 文档)的不可变包装器
- 关键特性
- 不可变:变更操作会抛出
UnsupportedOperationException - 惰性解析:仅在访问时才解析数据——非常适合透传场景
- 内存高效:不会创建中间的 Java 对象
- 转换:
decode(codec)可在需要时将其转换为其他文档类型
- 不可变:变更操作会抛出
- 何时使用
- 需要在整体文档操作中实现最高的性能和内存效率。
- 适用场景包括:
- 读取文档后原样转发(例如变更流、客户端加密)。
- 处理非常大的文档且不需要逐字段检查。
- 高吞吐量的管道,解析开销成为瓶颈时。
4. JsonObject
- 类型:对 JSON 字符串(
String)的简单包装器 - 关键特性
- 不实现
Map;它仅是一个字符串持有者,可选地进行验证。 - 不进行解析——原始 JSON 保持不变。
- 支持 MongoDB 扩展 JSON 格式。
- 不实现
- 何时使用
- 应用主要处理 JSON(例如 REST API、日志、以 JSON 形式持久化文档)。
- 想避免在
Map与 JSON 之间转换的开销。
5. BasicDBObject (遗留)
- 类型:扩展自
BasicBSONObject并实现DBObject(不实现Map)。 - 关键特性
- 仅为 向后兼容 MongoDB 驱动 3.0 之前的版本而存在。
- 缺少现代
Map的便利方法。 - 可能导致二进制兼容性问题。
- 何时使用
- 正在迁移已经使用
BasicDBObject的旧代码。 - 不要在新开发中使用——驱动文档建议避免使用它。
- 正在迁移已经使用
类间转换
所有五个类都实现了 Bson 接口,因此可以在驱动操作中互换使用(尽管性能可能有所不同)。
| From → To | 示例 |
|---|---|
| JSON → BsonDocument | BsonDocument.parse(jsonString) |
| Raw bytes → other type | RawBsonDocument.decode(codec) |
| Document → BsonDocument | 使用 CodecRegistry/DocumentCodec(或 document.toBsonDocument(... )) |
| BsonDocument → Document | documentCodec.decode(bsonDocument.asBsonReader()) |
| JsonObject → JSON string | jsonObject.getJson() |
| RawBsonDocument ↔ Document | 通过 Codec(例如 DocumentCodec)进行编码/解码 |
快速决策指南
| 要求 | 首选类 |
|---|---|
| 通用、灵活、易于使用 | Document |
| 需要严格的 BSON 类型安全 | BsonDocument |
| 想要不可变的原始字节表示以提升性能 | RawBsonDocument |
| 仅使用 JSON 字符串 | JsonObject |
| 维护遗留代码(3.0 之前的驱动) | BasicDBObject |
摘要
Document是大多数应用的默认选择——它在灵活性、简洁性和功能性之间取得了平衡。- 当需要编译时类型安全时,选择
BsonDocument。 - 对于高性能、内存高效的直通场景,使用
RawBsonDocument。 - 当你的工作流围绕原始 JSON 时,使用
JsonObject。 - 仅在遗留迁移时保留
BasicDBObject。
所有类都可以通过驱动的 codec 相互转换,这使你可以先使用最方便的类型,随后根据性能或类型安全需求进行切换。
Overview
本文解释了 RawBsonDocument 与 Document 在解析策略、内存使用和可变性方面的差异。它还比较了 Oracle 和 PostgreSQL 中类似概念的可用性,并强调了 MongoDB 对现代应用的优势。
RawBsonDocument vs. Document
RawBsonDocument
- 解析策略 – 逐序读取 BSON 文档 顺序,检查每个字段的 类型 和 名称,直到找到请求的键。
- 字段处理 – 仅对匹配的字段使用
RawBsonValueHelper.decode进行解码;所有其他字段 跳过,不进行解析。 - 嵌套结构 – 对于嵌套的文档和数组,只读取它们的 大小,并将相应的字节范围包装成新的
RawBsonDocument或RawBsonArray实例,内容保持为 原始字节。 - 性能 – 提供 快速的单字段查找,同时保持 内存高效 并使文档 不可变。
- 使用场景 – 适用于 大文档 但只需要少数字段,或大多数文档在不检查的情况下直接传递的情况。
Document
- 解析策略 – 使用完全反序列化的
LinkedHashMap。 - 急切解析 – 在创建
Document时,所有字段都会被解析为 Java 对象。 - 字段访问 –
containsKey等查找只是普通的HashMap操作。 - 可变性 – 文档 完全可变,支持
put、remove、clear等操作。 - 内存使用 – 消耗更多内存,因为每个字段都被实例化为 Java 对象。
- 使用场景 – 适用于 中小型文档、需要频繁访问多个字段,或文档需要经常修改的情况。
注意:
Document并 不 使用RawBsonDocument进行解析或字段访问,因为那样效率低下;这两个类的设计目标不同。
BSON‑like 支持在其他数据库中的实现
Oracle
- 没有 BSON – Oracle 将 JSON 存储为 OSON(Oracle 的二进制 JSON 格式),而不是 BSON。
- 最接近的等价物 –
OracleJsonObject(OracleJsonValue类型之一),可以作为javax.json.JsonObject暴露或映射到领域对象。 - 效率 – OSON API 直接在底层字节上操作,而不需要完整解析文档。它维护一个 本地字典(字段名)、一个 排序的哈希码数组,以及 紧凑的字段‑ID/值偏移数组,使驱动能够通过二分查找 原位定位 字段。
- JSON 文本 – 若需要 JSON 文本,只需调用
ResultSet.getString(),该方法会在客户端将 OSON 镜像转换为 JSON。
PostgreSQL
- 没有原生的 Java JSON 对象 API –
json和jsonb列通过 JDBC 驱动返回为 文本。 - 解析责任 – 应用程序必须使用独立的 JSON 库来解析返回的字符串。
- 二进制存储 – 虽然
jsonb在服务器端以二进制格式存储数据,但这种效率 不会 通过网络传输;客户端仍然收到文本,并在访问字段前进行完整解析。
MongoDB 的优势
- 面向领域对象 – MongoDB 让你直接使用领域对象 无需中间 ORM 层。
Document类 – 充当 文档对象模型 (DOM),提供灵活的 map‑style 访问和自然的 Java 接口。- 透明转换 – 驱动自动将网络上的 BSON 转换为可用的 Java 对象,使其能够在应用中立即使用。