MongoDB Java API跨年度日期范围查询无结果问题排查
问题原因分析
我一眼就看出问题所在了——你现在是把日期当成字符串来做范围查询的,这在跨年度场景下必然会失效!
MongoDB对字符串的比较是按字典序来的,举个例子:你输入的"12/24/2017"和"01/04/2018",第一个字符'1'比'0'大,所以MongoDB会判定"12/24/2017" > "01/04/2018"。这时候你同时用gte("12/24/2017")和lte("01/04/2018"),相当于找一个既大于等于前者又小于等于后者的文档,自然没有结果返回。
而同年度的查询能正常工作,是因为同年度内的日期字符串字典序和实际日期顺序是一致的(比如"01/01/2018" < "01/08/2018"),所以不会出问题。
解决方案
我给你两个方案,优先推荐第一种(长期最优解),第二种是临时兼容方案:
方案1:将MongoDB中的日期存储改为Date类型(强烈推荐)
这是从根源解决问题的方式,MongoDB的Date类型是基于时间戳的,比较逻辑完全符合日期的自然顺序,不管跨不跨年度都能正常工作。
步骤1:批量转换现有数据的日期格式
先通过MongoDB Shell把已存储的字符串日期转成Date类型:
db.data_company.updateMany( { run_date: { $type: "string" } }, // 只更新字符串类型的日期字段 [ { $set: { run_date: { $dateFromString: { dateString: "$run_date", format: "%m/%d/%Y" } } } } ] )
步骤2:修改Java代码中的查询逻辑
把输入的字符串日期解析成Java的日期对象,再构建MongoDB的查询条件:
public List<DataCompanyVO> filterByAndCriteriaWithDateRange(String fromDate, String toDate, Map<String, String> andCriteria) { List<DataCompanyVO> dataCompanyList = new ArrayList<DataCompanyVO>(); if (andCriteria == null || andCriteria.isEmpty() || StringUtils.isBlank(fromDate) || StringUtils.isBlank(toDate)) { throw new IllegalArgumentException("Invalid Arguments"); } String dateFilter = FilterType.RUN_DATE.value(); try { MongoCollection<Document> dataCompanyCollection = getMongoDatabase().getCollection(DATA_COMPANY_COLLECTION); List<Bson> bsonList = new ArrayList<Bson>(); // 处理andCriteria条件 for (String criteria : andCriteria.keySet()) { bsonList.add(eq(criteria, andCriteria.get(criteria))); } // 日期解析与条件构建 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy"); LocalDate fromLocalDate = LocalDate.parse(fromDate, formatter); LocalDate toLocalDate = LocalDate.parse(toDate, formatter); // 转换为MongoDB兼容的Date对象(适配Java驱动) Date fromMongoDate = Date.from(fromLocalDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); // 为了包含toDate当天的所有数据,把结束时间设为当天的最后一秒 Date toMongoDate = Date.from(toLocalDate.atStartOfDay(ZoneId.systemDefault()) .plusDays(1) .minusSeconds(1) .toInstant()); if (fromDate.equals(toDate)) { bsonList.add(eq(dateFilter, fromMongoDate)); } else { bsonList.add(gte(dateFilter, fromMongoDate)); bsonList.add(lte(dateFilter, toMongoDate)); } FindIterable<Document> docIterable = dataCompanyCollection.find(and(bsonList)); MongoCursor<Document> iterator = docIterable.iterator(); while (iterator.hasNext()) { Document doc = iterator.next(); dataCompanyList.add(wrapIntoModel(doc)); } } catch (IllegalArgumentException e) { logger.error(e); } catch (Exception e) { logger.error("message", e); } return dataCompanyList; }
方案2:临时兼容方案(不修改存储类型)
如果暂时无法修改MongoDB的存储格式,可以通过判断日期的年份来调整查询逻辑:
- 当跨年度时(fromDate的年份 > toDate的年份),用
$or组合两个范围:run_date >= fromDate或run_date <= toDate - 同年度时,保持原来的
$and逻辑
修改后的核心代码片段:
// 日期解析与条件构建 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy"); LocalDate fromLocalDate = LocalDate.parse(fromDate, formatter); LocalDate toLocalDate = LocalDate.parse(toDate, formatter); if (fromDate.equals(toDate)) { bsonList.add(eq(dateFilter, fromDate)); } else { if (fromLocalDate.getYear() > toLocalDate.getYear()) { // 跨年度场景,使用OR条件 Bson condition1 = gte(dateFilter, fromDate); Bson condition2 = lte(dateFilter, toDate); bsonList.add(or(condition1, condition2)); } else { // 同年度场景,保持原AND条件 bsonList.add(gte(dateFilter, fromDate)); bsonList.add(lte(dateFilter, toDate)); } }
总结
方案1是长期最优解,因为字符串日期不仅有跨年度问题,还会存在其他逻辑错误(比如"12/01/2017"字符串上比"02/01/2018"小,但实际日期更大),而且Date类型支持更多MongoDB的日期聚合、筛选功能。
内容的提问来源于stack exchange,提问作者eccpr
相关产品推荐
相关产品推荐

