1. 概述
在本教程中,我们将使用 MongoDB Java Driver 来执行与日期相关的 CRUD 操作,例如创建和更新带有日期字段的文档,以及查询, 更新和删除日期字段在给定范围内的文档。
2. 设置
在深入实施之前,让我们设置我们的工作环境。
2.1. Maven 依赖
首先,您应该安装了 MongoDB。 如果您不这样做,您可以按照官方 MongoDB 安装 指南 进行操作。
接下来,让我们将 MongoDB Java 驱动程序 作为依赖项添加到我们的 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;
}
// standard setters and getters
}
请注意,我们已经声明了两个构造函数。 MongoDB 使用无参数构造函数默认。 在本教程中,另一个构造函数供我们自己使用。
我们还要注意,虽然 dateTime 可能是 String 变量,但最佳实践是对日期字段使用特定于日期/时间的 JDK 类。 使用 String 字段来表示日期需要额外的努力来确保值的格式正确。
我们现在准备将客户端连接到我们的数据库。
2.3. MongoDB 客户端
为了让 MongoDB 序列化/反序列化我们的 Event POJO,我们需要向 MongoDB 的 CodecRegistry 注册 PojoCodecProvider:
CodecProvider codecProvider = PojoCodecProvider.builder().automatic(true).build();
CodecRegistry codecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(codecProvider));
让我们创建一个将使用我们注册的 PojoCodecProvider 的数据库、集合和客户端:
MongoClient mongoClient = MongoClients.create(uri);
MongoDatabase db = mongoClient.getDatabase("calendar").withCodecRegistry(codecRegistry);
MongoCollection<Event> collection = db.getCollection("my_events", Event.class);
我们现在已准备好创建文档并执行与日期相关的 CRUD 操作。
3. 创建带有日期字段的文档
在我们的 POJO 中,我们使用 LocalDateTime 而不是 String,以便更轻松地处理日期值。 现在让我们利用 LocalDateTime 的便捷 API 构造 Event 对象来利用它:
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));
我们可以将新的 Events 插入到我们的数据库中,如下所示:
InsertOneResult pianoLessonsInsertResult = collection.insertOne(pianoLessonsEvent);
InsertOneResult soccerGameInsertResult = collection.insertOne(soccerGameEvent);
让我们通过检查插入文档的 id 来验证插入是否成功:
assertNotNull(pianoLessonsInsertResult.getInsertedId());
assertNotNull(soccerGameInsertResult.getInsertedId());
4. 查询符合日期条件的文档
现在我们的数据库中有 Events,让我们根据它们的日期字段检索它们。
我们可以使用相等过滤器 (eq) 来检索与特定日期和时间匹配的文档:
LocalDateTime dateTime = LocalDateTime.of(2022, 6, 10, 17, 0, 0);
Event event = collection.find(Filters.eq("dateTime", dateTime)).first();
让我们检查生成的 Event 的各个字段:
assertEquals("Soccer game", event.title);
assertEquals("Bar Avenue", event.location);
assertEquals(dateTime, event.dateTime);
我们还可以使用 MongoDB 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 = collection.find(object, Event.class).into(new ArrayList<Event>());
由于足球比赛是我们查询的日期范围内唯一的 Event,我们应该在 list 中只看到一个 Event 对象,不包括钢琴课:
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. 更新文档的日期字段
要更新 MongoDB 文档,我们可以使用 updateOne() 方法。 让我们也使用 currentDate() 方法来设置钢琴课事件的 dateTime 字段:
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);
请注意,updateOne() 的第一个参数是一个 Document 对象,MongoDB 将使用它来匹配我们数据库中的单个条目。 如果多个文档匹配,MongoDB 将只更新它遇到的第一个文档。 我们还要注意我们将 false 传递给 upsert() 方法。 如果我们改为传入 true,如果现有文档都不匹配,MongoDB 将插入一个新文档。
我们可以通过检查修改了多少文档来确认操作是否成功:
assertEquals(1, result.getModifiedCount());
5.2. 更新符合日期条件的文档
为了更新多个文档,MongoDB 提供了 updateMany 方法。 在此示例中,我们将更新与查询中的日期范围匹配的多个events 。
与 updateOne() 不同,updateMany() 方法需要第二个 Bson 对象来封装查询条件,该条件将定义我们要更新的文档。 在这种情况下,我们将通过引入 lt 字段运算符来指定涵盖 2022 年所有事件的日期范围:
LocalDate updateManyFrom = LocalDate.of(2022, 1, 1);
LocalDate updateManyTo = LocalDate.of(2023, 1, 1);
Bson query = Filters.and(Filters.gte("dateTime", updateManyFrom), Filters.lt("dateTime", updateManyTo));
Bson updates = Updates.currentDate("dateTime");
UpdateResult result = collection.updateMany(query, updates);
就像 updateOne() 一样,我们可以通过检查 result 对象的更新计数来确认此操作更新了多个events:
assertEquals(2, result.getModifiedCount());
6. 删除符合日期条件的文档
与更新一样,我们可以一次从数据库中删除一个或多个文档。 假设我们需要删除 2022 年的所有event。让我们使用 Bson 日期范围查询和 deleteMany() 方法来做到这一点:
LocalDate deleteFrom = LocalDate.of(2022, 1, 1);
LocalDate deleteTo = LocalDate.of(2023, 1, 1);
Bson query = Filters.and(Filters.gte("dateTime", deleteFrom), Filters.lt("dateTime", deleteTo));
DeleteResult result = collection.deleteMany(query);
由于我们在本教程中创建的所有事件都有一个 2022 dateTime 字段值,deleteMany() 将它们全部从我们的集合中删除。 我们可以通过检查删除计数来确认这一点:
assertEquals(2, result.getDeletedCount());
7. 使用 时区(Time Zones)
MongoDB 以 UTC 存储日期,并且无法更改。 因此,如果我们希望我们的日期字段特定于一个时区,我们可以将时区偏移存储在一个单独的字段中并自己进行转换。 让我们将该字段添加为 String:
public String timeZoneOffset;
我们需要调整构造函数,以便在创建events时设置新字段:
public Event(String title, String location, LocalDateTime dateTime, String timeZoneOffset) {
this.title = title;
this.location = location;
this.dateTime = dateTime;
this.timeZoneOffset = timeZoneOffset;
}
我们现在可以创建特定时区的events并将其插入到我们的数据库中。 让我们使用 ZoneOffset 类来避免手动格式化时区偏移 String:
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());
请注意,由于偏移量是相对于 UTC 的,所以 dateTime 成员变量必须表示 UTC 时间,以便我们以后可以正确转换它。 从集合中检索文档后,我们可以使用偏移字段和 OffsetDateTime 类进行转换:
OffsetDateTime dateTimeWithOffset = OffsetDateTime.of(pianoLessonsTZ.dateTime, ZoneOffset.of(pianoLessonsTZ.timeZoneOffset));
8. 结尾
在本文中,我们学习了如何使用 Java 和 MongoDB 数据库执行与日期相关的 CRUD 操作。
我们使用日期值来创建、检索、更新或删除数据库中的文档。 在我们的示例中,我们介绍了各种帮助程序类并介绍了在处理日期时很有帮助的 MongoDB 运算符。 最后,为了解决 MongoDB 如何仅以 UTC 存储日期的问题,我们学习了如何处理需要特定时区的日期/时间值。