如何在MongoDB中查询createdAt大于指定时间的Java Calendar实例
解决MongoDB中查询Java Calendar类型字段的问题
问题根源
Spring Data默认会把java.util.Calendar实例序列化为完整的Java对象嵌套结构(就像你展示的那样),而不是MongoDB原生的Date类型,这导致常规的日期查询无法直接生效。我们有两种解决方案,优先推荐第一种(规范存储),第二种用于无法修改现有数据的场景。
方案1:修改实体类,将Calendar序列化为MongoDB原生Date(推荐)
这是最合理的长期解决方案,让MongoDB存储原生日期类型,后续查询会非常便捷。
方法A:使用@DateTimeFormat自动转换
在实体类的Calendar字段上添加@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME),Spring Data会自动将其转换为MongoDB的Date类型:
@Document(collection = "attachment") class Attachment { @Id private String id; private String fileName, attachment; @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) private Calendar createdAt; @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) private Calendar updatedAt; }
方法B:自定义Converter(精细控制)
如果需要更灵活的转换逻辑,可以编写自定义的序列化/反序列化转换器:
// Calendar转MongoDB Date的转换器 @Component public class CalendarToDateConverter implements Converter<Calendar, Date> { @Override public Date convert(Calendar source) { return source != null ? source.getTime() : null; } } // MongoDB Date转Calendar的转换器 @Component public class DateToCalendarConverter implements Converter<Date, Calendar> { @Override public Calendar convert(Date source) { if (source == null) return null; Calendar calendar = Calendar.getInstance(); calendar.setTime(source); return calendar; } }
然后在MongoDB配置类中注册这些转换器:
@Configuration public class MongoConfig extends AbstractMongoClientConfiguration { // ... 其他配置 @Override public MappingMongoConverter mappingMongoConverter() throws Exception { MappingMongoConverter converter = super.mappingMongoConverter(); converter.setCustomConversions(customConversions()); return converter; } @Override protected CustomConversions customConversions() { return new CustomConversions(Arrays.asList( new CalendarToDateConverter(), new DateToCalendarConverter() )); } }
修改后,MongoDB中会存储原生的ISODate类型,查询就可以用常规的日期条件了:
// Spring Data Repository查询示例 List<Attachment> findByCreatedAtAfter(Calendar cutoffDate); // 调用示例 Calendar cutoff = Calendar.getInstance(TimeZone.getTimeZone("UTC")); cutoff.set(2018, Calendar.JANUARY, 1, 0, 0, 0); cutoff.set(Calendar.MILLISECOND, 0); List<Attachment> results = attachmentRepository.findByCreatedAtAfter(cutoff);
方案2:针对现有存储结构编写查询(临时过渡方案)
如果无法修改现有数据,只能针对存储的嵌套结构查询。观察你的createdAt存储结构,日期信息在createdAt.gdate下,其中month字段是从1开始计数的(示例中month:3对应3月)。
MongoDB Shell查询示例
db.attachment.find({ $or: [ // 年份大于2018的文档 {"createdAt.gdate.year": {$gt: 2018}}, // 年份等于2018且月份大于1的文档 {"createdAt.gdate.year": 2018, "createdAt.gdate.month": {$gt: 1}}, // 年份等于2018、月份等于1且日期大于等于1的文档 {"createdAt.gdate.year": 2018, "createdAt.gdate.month": 1, "createdAt.gdate.dayOfMonth": {$gte: 1}} ] })
Spring Data MongoDB查询示例
用@Query注解在Repository中定义查询:
@Repository public interface AttachmentRepository extends MongoRepository<Attachment, String> { @Query("{$or: [" + "{'createdAt.gdate.year': {$gt: ?0}}, " + "{'createdAt.gdate.year': ?0, 'createdAt.gdate.month': {$gt: ?1}}, " + "{'createdAt.gdate.year': ?0, 'createdAt.gdate.month': ?1, 'createdAt.gdate.dayOfMonth': {$gte: ?2}}]}") List<Attachment> findByCreatedAtAfter(int year, int month, int day); } // 调用示例 List<Attachment> results = attachmentRepository.findByCreatedAtAfter(2018, 1, 1);
注意:这种方法无法处理时区、时分秒的精确查询,仅建议作为临时过渡方案,最终还是要迁移到原生Date类型存储。
内容的提问来源于stack exchange,提问作者OTUser
相关产品推荐
相关产品推荐

