MongoDB+SpringBoot兼容两种createdOn存储格式的解决方案
解决方案:兼容MongoDB中两种格式的
createdOn字段转换 针对数据库中createdOn字段同时存在日期字符串(yyyy-dd-MM HH:mm:ssZ)和时间戳(epoch)两种格式,导致Spring/Mongo驱动转换Date类型失败的问题,提供以下两种优雅的兼容方案:
方案一:自定义MongoDB读取转换器(推荐)
通过注册MongoDB的自定义读取转换器,覆盖默认的类型转换逻辑,直接处理从MongoDB读取的各种类型的日期值。
1. 实现自定义转换器
创建@ReadingConverter注解的转换器,处理Object到Date的转换,兼容多种输入类型:
import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.ReadingConverter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @ReadingConverter public class ObjectToDateConverter implements Converter<Object, Date> { private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-dd-MM HH:mm:ssZ"); @Override public Date convert(Object source) { if (source == null) { return null; } // 处理Mongo原生Date类型 if (source instanceof Date) { return (Date) source; } // 处理字符串格式日期 if (source instanceof String) { try { return DATE_FORMAT.parse((String) source); } catch (ParseException e) { // 兼容时间戳字符串(如数据库中直接存的数字字符串) try { return new Date(Long.parseLong((String) source)); } catch (NumberFormatException ex) { throw new IllegalArgumentException("无法解析日期字符串: " + source, ex); } } } // 处理数字类型(Double/Long,对应$numberLong转换后的值) if (source instanceof Number) { long timestamp = ((Number) source).longValue(); return new Date(timestamp); } throw new IllegalArgumentException("不支持的日期输入类型: " + source.getClass().getName()); } }
2. 注册转换器到Mongo配置
通过配置类将自定义转换器注入MongoTemplate:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.core.convert.MongoCustomConversions; import java.util.Arrays; @Configuration public class MongoConfig { @Bean public MongoCustomConversions mongoCustomConversions() { return new MongoCustomConversions(Arrays.asList(new ObjectToDateConverter())); } }
方案二:自定义Jackson反序列化器
通过自定义Jackson反序列化器,在Jackson绑定阶段处理多种格式的日期输入。
1. 实现多格式反序列化器
创建支持字符串、数字类型输入的反序列化器:
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class MultiFormatDateDeserializer extends JsonDeserializer<Date> { private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-dd-MM HH:mm:ssZ"); @Override public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { Object currentValue = p.getCurrentValue(); // 处理Mongo驱动转换后的数字类型(Double/Long) if (currentValue instanceof Number) { return new Date(((Number) currentValue).longValue()); } // 处理字符串类型 String strValue = p.getText().trim(); try { return DATE_FORMAT.parse(strValue); } catch (ParseException e) { try { return new Date(Long.parseLong(strValue)); } catch (NumberFormatException ex) { throw new IOException("无法解析日期值: " + strValue, ex); } } } }
2. 绑定到实体类字段
在BaseModel的createdOn字段上添加@JsonDeserialize注解:
@Data public class BaseModel { @Id @JsonSerialize(using= ObjectIdSerializer.class) protected ObjectId _id; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-dd-MM HH:mm:ssZ") @JsonProperty("createdAt") @JsonDeserialize(using = MultiFormatDateDeserializer.class) Date createdOn; }
说明
- 方案一直接作用于MongoDB数据读取阶段,覆盖驱动默认转换逻辑,适配性更强;
- 方案二作用于Jackson序列化/反序列化阶段,适合需要同时处理API请求和MongoDB数据的场景;
- 两种方案均无需修改实体类的
Date类型,也无需修复数据库数据,符合限制条件。
内容的提问来源于stack exchange,提问作者Xhiro Yui
相关产品推荐
相关产品推荐

