1. 概述

Spring Data MongoDB 模块极大地提升了在 Spring 项目中与 MongoDB 数据库交互的可读性和易用性。

本篇文章将重点介绍如何在使用 Spring Data MongoDB 读写 MongoDB 数据库时,正确处理 Java 中的 ZonedDateTime 对象。

2. 环境准备

要使用 Spring Data MongoDB 模块,首先需要添加以下 Maven 依赖:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-mongodb</artifactId>
    <version>3.4.7</version>
</dependency>

最新版本可以在 Maven Central 查找。

接下来定义一个简单的模型类 Action,其中包含一个 ZonedDateTime 类型的字段:

@Document
public class Action {
    @Id
    private String id;

    private String description;
    private ZonedDateTime time;
    
    // 构造函数、getter 和 setter 省略
}

为了与 MongoDB 交互,我们还需要创建一个继承自 MongoRepository 的接口:

public interface ActionRepository extends MongoRepository<Action, String> { }

然后我们编写一个测试方法,插入一个 Action 对象并验证其时间字段是否被正确存储。由于 MongoDB 的 Date 类型精度为毫秒,因此我们在断言中移除了纳秒部分:

@Test
public void givenSavedAction_TimeIsRetrievedCorrectly() {
    String id = "testId";
    ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);

    actionRepository.save(new Action(id, "click-action", now));
    Action savedAction = actionRepository.findById(id).get();

    Assert.assertEquals(now.withNano(0), savedAction.getTime().withNano(0)); 
}

如果直接运行测试,会抛出如下异常:

org.bson.codecs.configuration.CodecConfigurationException:
  Can't find a codec for class java.time.ZonedDateTime

这是因为 Spring Data MongoDB 默认没有为 ZonedDateTime 提供编解码器。 接下来我们将介绍如何配置自定义转换器来解决这个问题。

3. 自定义 MongoDB 转换器

我们可以通过定义两个转换器(读取转换器和写入转换器)来全局处理 ZonedDateTime 类型的字段。

3.1 读取转换器:Date → ZonedDateTime

public class ZonedDateTimeReadConverter implements Converter<Date, ZonedDateTime> {
    @Override
    public ZonedDateTime convert(Date date) {
        return date.toInstant().atZone(ZoneOffset.UTC);
    }
}

由于 MongoDB 的 Date 类型不包含时区信息,这里我们统一使用 UTC 时区。

3.2 写入转换器:ZonedDateTime → Date

public class ZonedDateTimeWriteConverter implements Converter<ZonedDateTime, Date> {
    @Override
    public Date convert(ZonedDateTime zonedDateTime) {
        return Date.from(zonedDateTime.toInstant());
    }
}

将这两个转换器注册到 MongoCustomConversions 中之后,测试即可通过。

此时存储的对象打印出来大致如下:

Action{id='testId', description='click', time=2018-11-08T08:03:11.257Z}

⚠️ 注意:如果需要保留原始时区信息,可以考虑将时区单独存储在一个额外字段中。

如需了解更多关于 MongoDB 转换器的注册方式,可参考 Spring Data MongoDB 转换器配置教程

4. 小结

本文简要介绍了如何通过自定义 MongoDB 转换器来支持 Java 中的 ZonedDateTime 类型,解决 Spring Data MongoDB 默认不支持该类型的问题。

所有示例代码均可在 GitHub 上获取:GitHub 项目地址


原始标题:ZonedDateTime with Spring Data MongoDB