Using Dates in CRUD Operations in MongoDB – 在MongoDB的CRUD操作中使用日期

最后修改: 2022年 7月 8日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll use the MongoDB Java Driver to execute date-related CRUD operations, such as creating and updating documents with date fields, and querying, updating, and deleting documents whose date fields fall within a given range.

在本教程中,我们将使用MongoDBJava 驱动程序来执行与日期相关的 CRUD 操作,例如创建和更新具有日期字段的文档,以及查询、更新和删除其日期字段位于给定范围内的文档。

2. Setup

2.设置

Before diving into the implementation, let’s set up our work environment.

在深入实施之前,让我们先设置一下我们的工作环境。

2.1. Maven Dependency

2.1.Maven的依赖性

First, you should have MongoDB installed. If you don’t, you can follow the official MongoDB installation guide to do so.

首先,你应该已经安装了MongoDB。如果你没有,你可以按照官方的MongoDB安装指南来进行安装。

Next, let’s add the MongoDB Java Driver as a dependency to our pom.xml file:

接下来,让我们将MongoDB Java驱动程序作为一个依赖项添加到我们的pom.xml文件中。

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

2.2. POJO Data Model

2.2 POJO数据模型

Let’s define a POJO to represent the documents contained in our database:

让我们定义一个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
}

Note that we’ve declared two constructors. MongoDB uses the no-argument constructor by default. The other constructor is for our own use throughout this tutorial.

请注意,我们已经声明了两个构造函数。MongoDB 通过default使用无参数构造器。另一个构造函数是供我们在本教程中自己使用的。

Let’s also note that, while dateTime could have been a String variable, the best practice is to use date/time-specific JDK classes for date fields. Using String fields to represent dates requires extra effort to ensure the values are formatted correctly.

我们还要注意,虽然dateTime 可以是一个String变量,但最佳做法是为日期字段使用特定于日期/时间的JDK类。使用String字段来表示日期需要额外的努力来确保值的格式正确。

We are now ready to connect a client to our database.

我们现在已经准备好将一个客户连接到我们的数据库。

2.3. MongoDB Client

2.3 MongoDB客户端

In order for MongoDB to serialize/deserialize our Event POJO, we need to register PojoCodecProvider with MongoDB’s CodecRegistry:

为了使MongoDB能够序列化/反序列化我们的Event POJO,我们需要在MongoDB的CodecRegistry中注册PojoCodecProvider

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

Let’s create a database, collection, and client that will use the PojoCodecProvider we registered:

让我们创建一个数据库、集合和客户端,它将使用我们注册的PojoCodecProvider

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

We’re now ready to create documents and perform date-related CRUD operations.

我们现在已经准备好创建文档和执行与日期有关的CRUD操作。

3. Creating Documents With Date Fields

3.创建有日期字段的文件

In our POJO, we used LocalDateTime instead of a String in order to make it easier to work with date values. Let’s take advantage of that now by constructing Event objects using LocalDateTime‘s convenient API:

在我们的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));

We can insert the new Events into our database as follows:

我们可以将新的Events插入我们的数据库,如下所示。

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

Let’s verify the insertions were successful by checking the id of the inserted documents:

让我们通过检查插入的文件的ID来验证插入是否成功。

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

4. Querying Documents Matching Date Criteria

4.查询符合日期标准的文件

Now that we have Events in our database, let’s retrieve them based on their date fields.

现在我们的数据库中有了Events,让我们根据它们的日期字段来检索它们。

We can use the equality filter (eq) to retrieve documents matching a specific date and time:

我们可以使用平等过滤器(eq)来检索匹配特定日期和时间的文件:

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

Let’s check the individual fields of the resulting Event:

让我们检查一下产生的Event的各个字段。

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

We can also use the MongoDB BasicDBObject class along with the gte and lte operators to build more complex queries using Date ranges:

我们还可以使用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 list = new ArrayList(collection.find(object).into(new ArrayList()));

Since the soccer game is the only Event within the date range of our query, we should only see one Event object in the list, with the piano lesson excluded:

由于足球比赛是我们查询的日期范围内唯一的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. Updating Documents

5.更新文件

Let’s explore two use cases for updating documents based on their date fields. First, we’ll update the date field of a single document, and then we’ll update multiple documents matching a date range.

让我们探讨一下基于日期字段更新文档的两个用例。首先,我们将更新单个文档的日期字段,然后我们将更新符合日期范围的多个文档。

5.1. Updating a Document’s Date Field

5.1.更新文档的日期字段

To update a MongoDB document, we can use the updateOne() method. Let’s also use the currentDate() method to set the dateTime field of our piano lesson event:

为了更新一个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);

Note that the first argument to updateOne() is a Document object that MongoDB will use to match a single entry in our database. If multiple documents are a match, MongoDB will only update the first document it encounters. Let’s also note that we passed false to the upsert() method. If we had instead passed in true, MongoDB would insert a new document if none of the existing documents matched.

请注意,updateOne()的第一个参数是一个Document对象,MongoDB将用来匹配我们数据库中的一个条目。如果多个文档是匹配的,MongoDB将只更新它遇到的第一个文档。我们还要注意,我们向upsert()方法传递了false。如果我们传入了true,MongoDB将在现有文档都不匹配的情况下插入一个新文档。

We can confirm the operation was successful by checking how many documents were modified:

我们可以通过检查有多少文件被修改来确认该操作是成功的。

assertEquals(1, result.getModifiedCount());

5.2. Updating Documents Matching Date Criteria

5.2.更新符合日期标准的文件

To update multiple documents, MongoDB provides the updateMany method. In this example, we’ll update multiple events matching the date range from our query.

为了更新多个文档,MongoDB提供了updateMany 方法。在这个例子中,我们将更新与我们查询的日期范围相匹配的多个事件。

Unlike updateOne(), the updateMany() method expects a second Bson object to encapsulate the query criteria that will define which documents we want to update. In this case, we’ll specify a date range covering all events in 2022 by introducing the lt field operator:

updateOne()不同,updateMany()方法希望有第二个Bson对象来封装查询条件,以定义我们要更新的文件。在这种情况下,我们将通过引入lt字段操作符来指定一个涵盖2022年所有事件的日期范围。

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

Just like with updateOne(), we can confirm this operation updated multiple events by checking the update count of our result object:

就像updateOne()一样,我们可以通过检查我们的result对象的更新计数来确认这个操作更新了多个事件:

assertEquals(2, result.getModifiedCount());

6. Deleting Documents Matching Date Criteria

6.删除符合日期标准的文件

As with updates, we can delete one or multiple documents from our database at a time. Suppose we need to remove all events from 2022. Let’s use a Bson date range query and the deleteMany() method to do that:

与更新一样,我们可以一次从数据库中删除一个或多个文件。假设我们需要删除2022年的所有事件。让我们使用Bson日期范围查询和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);

Since all of the events we have created in this tutorial have a 2022 dateTime field value, deleteMany() removed them all from our collection. We can confirm this by checking the delete count:

由于我们在本教程中创建的所有事件都有一个2022年的dateTime字段值,deleteMany()将它们从我们的集合中全部删除。我们可以通过检查删除次数来确认这一点。

assertEquals(2, result.getDeletedCount());

7. Using Time Zones

7.使用时区

MongoDB stores dates in UTC, and this cannot be changed. Thus, if we want our date fields to be specific to a time zone, we can store the time zone offset in a separate field and do the conversion ourselves. Let’s add that field as a String:

MongoDB以UTC方式存储日期,这一点无法改变。因此,如果我们希望我们的日期字段特定于某个时区,我们可以在一个单独的字段中存储时区偏移,并自己进行转换。让我们将该字段添加为String

public String timeZoneOffset;

We’ll need to adjust our constructor so we can set the new field when creating events:

我们需要调整我们的构造函数,以便我们可以在创建事件时设置新的字段。

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

We can now create and insert events for specific time zones into our database. Let’s use the ZoneOffset class to avoid having to manually format the time zone offset String:

我们现在可以为特定的时区创建和插入事件到我们的数据库。让我们使用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());

Note that since the offset is relative to UTC, the dateTime member variable must represent UTC time so we can correctly convert it later. Once we retrieve the document from the collection, we can make the conversion using the offset field and the OffsetDateTime class:

请注意,由于偏移量是相对于UTC而言的,所以dateTime 成员变量必须代表UTC时间,这样我们才能在稍后正确转换。一旦我们从集合中检索到文档,我们就可以使用偏移量字段和OffsetDateTime类进行转换了。

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

8. Conclusion

8.结语

In this article, we learned how to perform date-related CRUD operations using Java and a MongoDB database.

在这篇文章中,我们学习了如何使用Java和MongoDB数据库执行与日期有关的CRUD操作。

We used date values to create, retrieve, update, or remove documents in our database. Throughout our examples, we covered various helper classes and introduced MongoDB operators that are helpful when dealing with dates. Finally, to work around how MongoDB only stores dates in UTC, we learned how to work with date/time values that need to be time zone specific.

我们使用日期值来创建、检索、更新或删除我们数据库中的文档。在整个例子中,我们涵盖了各种辅助类,并介绍了MongoDB操作符,这些操作符在处理日期时很有帮助。最后,为了解决MongoDB只存储UTC日期的问题,我们学习了如何处理需要特定时区的日期/时间值。

As always, the sample code used in this tutorial is available over on GitHub.

一如既往,本教程中所使用的示例代码可在GitHub上获得