笔者之前写过一篇有关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