笔者之前写过一篇有关mongo 简介的博客 , 里面写着有关 mongo 数据类型的相关解释 MongoDB( 一 ) MongoDB 数据类型
目录
(一):自定义转换器
1 . 写转换器样例
2 . 读类型转换器
(二): 注册自定义转换器
(三): ObjectId序列化
ObjectId
1 . 序列化方式一: 使用 ResponseBodyAdvice 拦截Controller方法默认返回参数,统一处理返回值/响应体
2 . 序列化方式二: 将ObjectId序列化类用作Mongo读转换器注册至MongoTemplate
2 . 序列化方式三: 与方式二基本一致 , 唯一区别在于弃用了fastJson
(一):自定义转换器
Mongodb 3.4 就开始支持Decimal128 类型,解决double的精度问题,但是不太好用, 故而在查询有关Decimal128类型时候还需要专门定义一个有关mongo的Decimal128类型和java的BigDecimal 类型的转换器.
1 . 写转换器样例
- @WritingConverter 声明该类为写类型转换器
- implements Converter< T , Z需要转换的类型(写入之前的类型) , Z : 转换目标类型
import org.bson.types.Decimal128;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* @author huang
*/
// 写转换器
@WritingConverter
public class BigDecimalToDecimal128Converter implements Converter<BigDecimal, Decimal128> {
@Override
public Decimal128 convert(BigDecimal source) {
// 具体类型转换处理逻辑
BigDecimal value = source != null ? source : BigDecimal.ZERO;
return new Decimal128(value.setScale(2, RoundingMode.HALF_UP));
}
}
2 . 读类型转换器
- @ReadingConverter 声明该类为读类型转换器
- implements Converter< T , Z需要转换的类型(读取到的类型) , Z : 转换目标类型
import org.bson.types.Decimal128;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* @author huang
*/
// 声明该类为读类型转换器
@ReadingConverter
public class Decimal128ToBigDecimalConverter implements Converter<Decimal128, BigDecimal> {
@Override
public BigDecimal convert(Decimal128 source) {
// 具体类型转换处理逻辑
return source == null ? BigDecimal.ZERO
: source.bigDecimalValue().setScale(2, RoundingMode.HALF_UP);
}
}
(二): 注册自定义转换器
创建好自定义类型转换器之后 , 需要将其注册至 MongoTemplate
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import java.util.ArrayList;
import java.util.List;
/**
* @author huang
*/
@Configuration
public class MongoConfiguration {
@Autowired
MongoSettingsProperties properties;
@Primary
@Bean(name = "mongoTemplate")
public MongoTemplate getMongoTemplate() {
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory(properties));
// 获取mongo类型转换器
MappingMongoConverter mongoMapping = (MappingMongoConverter) mongoTemplate.getConverter();
// set 自定义转换器
mongoMapping.setCustomConversions(customConverter());
mongoMapping.afterPropertiesSet();
return mongoTemplate;
}
@Bean
public MongoDbFactory mongoDbFactory(MongoSettingsProperties properties) {
// 客户端配置(连接数,副本集群验证)
MongoClientOptions.Builder builder = new MongoClientOptions.Builder();
MongoClientOptions mongoClientOptions = builder.build();
String host = properties.getHost();
Integer port = properties.getPort();
ServerAddress serverAddress = new ServerAddress(host, port);
MongoCredential mongoCredential = MongoCredential.createScramSha1Credential(properties.getUsername(),
properties.getAuthenticationDatabase() != null ? properties.getAuthenticationDatabase() : properties.getDatabase(),
properties.getSecretcode().toCharArray());
// 创建非认证客户端
MongoClient mongoClient = new MongoClient(serverAddress, mongoCredential, mongoClientOptions);
// 创建MongoDbFactory
MongoDbFactory mongoDbFactory = new SimpleMongoDbFactory(mongoClient, properties.getDatabase());
return mongoDbFactory;
}
// 注册自定义转换器
@Bean
public MongoCustomConversions customConverter() {
List<Converter<?, ?>> converters = new ArrayList<>();
// 添加写类型转换器
converters.add(new BigDecimalToDecimal128Converter());
// 添加读类型转换器
converters.add(new Decimal128ToBigDecimalConverter());
return new MongoCustomConversions(converters);
}
}
(三): ObjectId序列化
笔者在之前的文章中提到过有关ObjectId这个Mongo特殊类型的相关解释
ObjectId
ObjectId 类似唯一主键,可以很快的去生成和排序,包含 12 bytes,含义是:
- 前 4 个字节表示创建 unix 时间戳,格林尼治时间 UTC 时间,比北京时间晚了 8 个小时
- 接下来的 3 个字节是机器标识码
- 紧接的两个字节由进程 id 组成 PID
- 最后三个字节是随机数
有趣的是这个类型在使用java代码查询时 , 查询出来的数据返回到前端会被解析成一个对象 , 分别是由上面几个含义组成的一个对象
, 这个对象是没法直接使用的 , 所以此时就需要到了类型转换器 , 对ObjectId进行序列化 , 再次笔者提供三种序列化方式 , 可根据实际项目情况自行选择使用
1 . 序列化方式一: 使用 ResponseBodyAdvice
拦截Controller方法默认返回参数,统一处理返回值/响应体
针对所有返回体中的ObjectId进行统一序列化 , 在使用该方法的前提是 , 若果进行了统一类型序列化 , 需要考虑该功能是否回影响项目中的其他功能使用
- 创建ObjectId序列化类
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.ObjectSerializer;
import com.alibaba.fastjson.serializer.SerializeWriter;
import org.bson.types.ObjectId;
import java.io.IOException;
import java.lang.reflect.Type;
/**
* @author huang
* 这里选择使用fastJson 的 ObjectSerializer
*/
public class IObjectIdJsonSerializer implements ObjectSerializer {
@Override
public void write(JSONSerializer jsonSerializer, Object o, Object o1, Type type, int i) throws IOException {
// 序列化逻辑实现
SerializeWriter out = jsonSerializer.getWriter();
if (o == null) {
jsonSerializer.getWriter().writeNull();
return;
}
out.write("\"" + ((ObjectId) o).toString() + "\"");
}
}
- 创建顶层Controller控制器,统一处理所有返回参数
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializeConfig;
import org.bson.types.ObjectId;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* @author huang
*/
@ControllerAdvice
public class IResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
// 自定义返回类型序列化config
SerializeConfig config = new SerializeConfig();
// 添加入序列化规则 序列化目标:ObjectId.class , 序列化方式为自定义的IObjectIdJsonSerializer
config.put(ObjectId.class, new IObjectIdJsonSerializer());
return JSONObject.parse(JSON.toJSONString(body, config));
}
}
2 . 序列化方式二: 将ObjectId序列化类用作Mongo读转换器注册至MongoTemplate
方式一的弊端在于序列化范围太广 , 不过在SerializeConfig进行配置时候 , 也表明了只针对ObjectId.class进行转换 , 各取所需即可...
方式二只针对mongo查询数据使用 , 相较于第一种大幅度缩小了范围
这里仍然采用第一种的序列化类 IObjectIdJsonSerializer
- 自定义Mongo读类型转换器
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializeConfig;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
/**
* @author HUANGXIAOKUAN
*/
@ReadingConverter
public class DocumentToDocumentConverter implements Converter<Document, Document> {
@Override
public Document convert(Document document) {
SerializeConfig config = new SerializeConfig();
config.put(ObjectId.class, new IObjectIdJsonSerializer());
// 序列化_id 参后 , 保留原参 , 在返回数据源里put新参名为 id
document.put("id" , JSONObject.parse(JSON.toJSONString(document.get("_id"), config)));
return document;
}
}
- 注册转换器
public MongoCustomConversions customConverter() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new BigDecimalToDecimal128Converter());
converters.add(new Decimal128ToBigDecimalConverter());
// 在原有注册处新添加一个转换器 用于转换ObjectId
converters.add(new DocumentToDocumentConverter());
return new MongoCustomConversions(converters);
}
2 . 序列化方式三: 与方式二基本一致 , 唯一区别在于弃用了fastJson
由于2020前段时间 阿里的fastJson出现了漏洞 , 公司代码扫描禁止使用fastJson包下的类进行有关json的一切操作 , 需要更换原有序列化方式
查了下mongo提供的一些类 , 找到了一个mongo提供的序列化类
com.mongodb.util.ObjectSerializer
- 修改后序列化类 IObjectIdJsonSerializer
import com.mongodb.util.ObjectSerializer;
import org.bson.types.ObjectId;
/**
* @author huang
*/
public class IObjectIdJsonSerializer implements ObjectSerializer {
/**
* Serializes {@code obj} into {@code buf}.
*
* @param obj object to serialize
* @param buf buffer to serialize into
*/
@Override
public void serialize(Object obj, StringBuilder buf) {
buf.append(((ObjectId) obj).toString());
}
/**
* Serializes {@code obj}.
*
* @param obj object to serialize
* @return the serialized object
*/
@Override
public String serialize(Object obj) {
return null;
}
}
- 修改后的DocumentToDocumentConverter转换器
import org.bson.Document;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
/**
* @author HUANGXIAOKUAN
*/
@ReadingConverter
public class DocumentToDocumentConverter implements Converter<Document, Document> {
@Override
public Document convert(Document document) {
IObjectIdJsonSerializer iObjectIdJsonSerializer = new IObjectIdJsonSerializer();
StringBuilder stringBuffer = new StringBuilder();
// 序列化旧 _id 并输出至 stringBuffer
iObjectIdJsonSerializer.serialize(document.get("_id"), stringBuffer);
// 在返回数据结构中新添加一个 属性名为 id 的字段 , 值为序列化后的ObjectId
document.put("id", stringBuffer);
return document;
}
}
同理 此处还需将转换器注册至MongoTemplate