前言
在 MongoDB
中 ObjectId
是一个 12
字节的 BSON
类型数据(我们在可视化数据库工具中看到的是 24
位的 16
进制形式),其具体数据结构如下:
- 前
4
个字节为时间戳(timestamp
); - 然后的
3
个字节机器标识码(randomValue1
); - 再之后的
2
个字节为进程id
(randomValue2
); - 最后的
3
个字节是随机计数器值 (counter
)。
讲完 ObjectId
的存储格式后,再来说下如何解决序列化问题,本文使用了基于 Gson
的 Java
序列化工具包,本文使用的完整代码也已上传到 GitHub,下面就介绍具体的解决办法。
基础
由于接下来的解决办法需要自定义 ObjectId
的序列化类,因此需要从底层的具体存储格式开始:
Gson gson = new Gson();
TestEntity id = new TestEntity(new ObjectId("600a47a0076abd67f0d588f6"));
System.out.println(gson.fromJson(gson.toJson(id), Document.class));
// TestEntity 的类结构如下
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TestEntity {
private ObjectId _id;
}
/**
* output:
* Document{{_id=
* {
* timestamp=1.611286432E9,
* counter=1.399423E7,
* randomValue1=486077.0,
* randomValue2=26608.0
* }
* }}
*/
从上面的代码和输出结果可以看出,如果为了插入数据在序列化实体时转换为 Document
类型,就会出现不和预期的结果,下面就结合前言中介绍的 ObjectId
底层存储结构来介绍 600a47a0 076abd 67f0 d588f6
这个十六进制字符串和 {timestamp=1.611286432E9, counter=1.399423E7, randomValue1=486077.0, randomValue2=26608.0}
的对应关系:
可以看到 24
位的 16
进制字符串正好和对象里面的对应字段的值相等,下面就正式开始序列化的解决办法。
序列化问题解决
在前面介绍了本文使用了基于 Gson
的 Java
序列化工具包,下面的解决方法也需要使用里面的 @JsonAdapter
这个注解,这个注解需要传入一个自定义的序列化解析类,这个自定义的类需要继承 TypeAdapter<T>
类,下面是具体的代码展示:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TestEntity {
// 自定义一个名为 MongoObjectId 的类进行 ObjectId 的自定义序列化处理
@JsonAdapter(MongoObjectId.class)
private ObjectId _id;
}
下面是这个自定义序列化解析类的代码:
public class MongoObjectId extends TypeAdapter<ObjectId> {
@Override
public void write(JsonWriter out, ObjectId value) throws IOException {
// 对于需要被序列化的 value
// 如果为空就在 out 中写入空值
// 否则写入 ObjectId 的 16 进制字符串形式
// tips: ObjectId 类的 toString 方法被重写实现了返回 ObjectId 的 16 进制字符串形式
if (value == null) {
out.nullValue();
return;
}
out.value(value.toString());
}
@Override
public ObjectId read(JsonReader in) throws IOException {
// in.beginObject() 代表读取的是一个对象
// 即 {timestamp=1.611286432E9, counter=1.399423E7, randomValue1=486077.0, randomValue2=26608.0}
in.beginObject();
int i = 0;
// 将接下来要读取的四个数据先保存在数组中
long[] nums = new long[4];
while (in.hasNext()) {
// 先读取对象名
in.nextName();
// 然后将值存储到数组中
nums[i++] = in.nextLong();
}
// in.endObject() 表示对象读取完毕
in.endObject();
if (i == 0) {
return null;
}
// 将获取的数据转换为 16 进制的字符串形式用于获取 ObjectId 对象
return new ObjectId(String.format("%08x%06x%04x%06x", nums[0], nums[2], nums[3], nums[1]));
}
}
然后再使用上述的测试代码:
Gson gson = new Gson();
TestEntity id = new TestEntity(new ObjectId("600a47a0076abd67f0d588f6"));
System.out.println(gson.fromJson(gson.toJson(id), Document.class));
/**
* 输出结果如下:
* Document{{_id=600a47a0076abd67f0d588f6}}
*/
可以发现解决了 ObjectId
的序列化问题,之后只要是 ObjectId
类型的变量,加上 @JsonAdapter(MongoObjectId.class)
注解即可。
总结
本文简单介绍了 MongoDB
的 ObjectId
数据类型的底层存储格式,以及如何通过 Gson
中的一个注解解决序列化问题,希望能够对你有所帮助。