1. 概述

本文将演示如何使用 MongoDB Java Driver 执行日期相关的 CRUD 操作,包括:

  • 创建和更新包含日期字段的文档
  • 查询、更新和删除日期字段在指定范围内的文档

2. 环境准备

2.1 Maven 依赖

确保已安装 MongoDB(未安装可参考官方指南)。在 pom.xml 中添加依赖:

<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongodb-driver-sync</artifactId>
    <version>4.6.0</version>
</dependency>

2.2 POJO 数据模型

定义 POJO 表示数据库文档:

public class Event {
    private String title;
    private String location;
    private LocalDateTime dateTime;

    public Event() {}
    public Event(String title, String location, LocalDateTime dateTime) {
        this.title = title;
        this.location = location;
        this.dateTime = dateTime;
    }
    
    // 标准 setter/getter
}

关键点:

  • 必须有无参构造函数(MongoDB 默认使用)
  • 日期字段使用 LocalDateTime 而非 String(避免格式化问题)

2.3 MongoDB 客户端配置

注册 PojoCodecProvider 实现 POJO 序列化:

CodecProvider codecProvider = PojoCodecProvider.builder().automatic(true).build();
CodecRegistry codecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(codecProvider));

创建数据库连接:

MongoClient mongoClient = MongoClients.create(uri);
MongoDatabase db = mongoClient.getDatabase("calendar").withCodecRegistry(codecRegistry);
MongoCollection<Event> collection = db.getCollection("my_events", Event.class);

3. 创建含日期字段的文档

利用 LocalDateTime 的便捷 API 构造对象:

Event pianoLessonsEvent = new Event("Piano lessons", "Foo Blvd",
  LocalDateTime.of(2022, 6, 4, 11, 0, 0));
Event soccerGameEvent = new Event("Soccer game", "Bar Avenue",
  LocalDateTime.of(2022, 6, 10, 17, 0, 0));

插入数据库:

InsertOneResult pianoLessonsInsertResult = collection.insertOne(pianoLessonsEvent);
InsertOneResult soccerGameInsertResult = collection.insertOne(soccerGameEvent);

验证插入结果:

assertNotNull(pianoLessonsInsertResult.getInsertedId());
assertNotNull(soccerGameInsertResult.getInsertedId());

4. 按日期条件查询文档

精确匹配日期时间

使用 eq 过滤器:

LocalDateTime dateTime = LocalDateTime.of(2022, 6, 10, 17, 0, 0);
Event event = collection.find(eq("dateTime", dateTime)).first();

验证查询结果:

assertEquals("Soccer game", event.title);
assertEquals("Bar Avenue", event.location);
assertEquals(dateTime, event.dateTime);

日期范围查询

使用 BasicDBObject$gte/$lte 操作符:

LocalDateTime from = LocalDateTime.of(2022, 06, 04, 12, 0, 0);
LocalDateTime to = LocalDateTime.of(2022, 06, 10, 17, 0, 0);
BasicDBObject object = new BasicDBObject();
object.put("dateTime", BasicDBObjectBuilder.start("$gte", from).add("$lte", to).get());
List<Event> events = new ArrayList<>(collection.find(object).into(new ArrayList<>()));

验证结果(仅返回足球比赛):

assertEquals(1, events.size());
assertEquals("Soccer game", events.get(0).title);
assertEquals("Bar Avenue", events.get(0).location);
assertEquals(dateTime, events.get(0).dateTime);

5. 更新文档

5.1 更新单个文档的日期字段

使用 updateOne()currentDate()

Document document = new Document().append("title", "Piano lessons");
Bson update = Updates.currentDate("dateTime");
UpdateOptions options = new UpdateOptions().upsert(false);
UpdateResult result = collection.updateOne(document, update, options);

关键点:

  • upsert(false) 禁用自动插入(无匹配时不创建新文档)
  • 验证更新数量:
assertEquals(1, result.getModifiedCount());

5.2 批量更新日期范围内的文档

使用 updateMany() 和日期范围查询:

LocalDate updateManyFrom = LocalDate.of(2022, 1, 1);
LocalDate updateManyTo = LocalDate.of(2023, 1, 1);
Bson query = and(gte("dateTime", updateManyFrom), lt("dateTime", updateManyTo));
Bson updates = Updates.currentDate("dateTime");
UpdateResult result = collection.updateMany(query, updates);

验证更新数量:

assertEquals(2, result.getModifiedCount());

6. 删除日期范围内的文档

使用 deleteMany() 和日期范围查询:

LocalDate deleteFrom = LocalDate.of(2022, 1, 1);
LocalDate deleteTo = LocalDate.of(2023, 1, 1);
Bson query = and(gte("dateTime", deleteFrom), lt("dateTime", deleteTo));
DeleteResult result = collection.deleteMany(query);

验证删除数量:

assertEquals(2, result.getDeletedCount());

7. 处理时区问题

MongoDB 强制使用 UTC 存储日期。若需时区支持,需额外存储时区偏移量:

public String timeZoneOffset;

更新构造函数:

public Event(String title, String location, LocalDateTime dateTime, String timeZoneOffset) {
    this.title = title;
    this.location = location;
    this.dateTime = dateTime;
    this.timeZoneOffset = timeZoneOffset;
}

创建带时区的事件(注意 dateTime 必须为 UTC 时间):

LocalDateTime utcDateTime = LocalDateTime.of(2022, 6, 20, 11, 0, 0);
Event pianoLessonsTZ = new Event("Piano lessons", "Baz Bvld", utcDateTime, ZoneOffset.ofHours(2).toString());
InsertOneResult pianoLessonsTZInsertResult = collection.insertOne(pianoLessonsTZ);
assertNotNull(pianoLessonsTZInsertResult.getInsertedId());

查询后转换时区:

OffsetDateTime dateTimeWithOffset = OffsetDateTime.of(
    pianoLessonsTZ.dateTime, 
    ZoneOffset.of(pianoLessonsTZ.timeZoneOffset)
);

8. 总结

本文完整演示了使用 Java 操作 MongoDB 日期数据的最佳实践:

  • 使用 LocalDateTime 替代 String 避免格式化问题
  • 通过 PojoCodecProvider 实现 POJO 自动映射
  • 灵活运用日期范围查询($gte/$lte
  • 批量更新/删除文档的技巧
  • 时区处理的解决方案(存储偏移量 + 手动转换)

完整示例代码见 GitHub 仓库


原始标题:Using Dates in CRUD Operations in MongoDB