1. 概述

在Java中处理日期和时间时,我们经常遇到不同的格式,比如LocalDateTimeInstantLocalDateTime表示不带时区的日期时间,而Instant表示特定的时间点,通常以纪元(1970年1月1日00:00:00 UTC)为参考。在很多场景下,我们需要在这两种类型之间进行映射。幸运的是,MapStruct这个强大的Java映射框架可以让我们轻松实现这一点。

在本教程中,我们将学习如何在MapStruct中将LocalDateTime映射为Instant

2. 理解LocalDateTime和Instant

我们可能需要将LocalDateTime映射为Instant的原因有几个:

LocalDateTime

  • 适用于表示发生在特定本地时间的事件,不考虑时区
  • 常用于存储数据库和日志文件中的时间戳
  • 适合所有用户都在同一时区操作的应用程序

Instant

  • 非常适合跟踪全球事件,确保时区一致性
  • 为与外部系统或API交互提供可靠的格式
  • 适合在需要时区一致性的数据库中存储时间戳

⚠️ 在实际开发中,我们会频繁处理这两种类型并在它们之间进行转换。

3. 映射场景

假设我们正在实现一个订单处理服务。我们有两种订单类型——订单和本地订单:

  • Order使用Instant支持全球订单处理
  • LocalOrder使用LocalDateTime表示本地时间

以下是订单模型的实现:

public class Order {
    private Long id;
    private Instant created;
    // other fields
    // getters and setters
}

本地订单的实现:

public class LocalOrder {
    private Long id;
    private LocalDateTime created;
    // other fields
    // getters and setters
}

4. 将LocalDateTime映射为Instant

现在我们来实现映射器,将LocalDateTime转换为Instant。先看OrderMapper接口:

@Mapper
public interface OrderMapper {
    OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);
 
    ZoneOffset DEFAULT_ZONE = ZoneOffset.UTC;

    @Named("localDateTimeToInstant")
    default Instant localDateTimeToInstant(LocalDateTime localDateTime) {
        return localDateTime.toInstant(DEFAULT_ZONE);
    }

    @Mapping(target = "id", source = "id")
    @Mapping(target = "created", source = "created", qualifiedByName = "localDateTimeToInstant")
    Order toOrder(LocalOrder source);
}

这个映射器做了几件事:

  1. 定义了UTC时区常量DEFAULT_ZONE
  2. 通过@Named注解标记了转换方法localDateTimeToInstant()
  3. toOrder()方法中:
    • 直接映射id字段
    • 通过qualifiedByName指定使用自定义方法转换created字段

🚀 关键点

  • MapStruct默认不支持LocalDateTimeInstant的转换
  • 使用默认方法定义显式转换是最佳实践
  • 这种方法能处理复杂类型转换,避免运行时错误

测试用例验证映射逻辑:

class OrderMapperUnitTest {
    private OrderMapper mapper = OrderMapper.INSTANCE;
    
    @Test
    void whenLocalOrderIsMapped_thenGetsOrder() {
        LocalDateTime localDateTime = LocalDateTime.now();
        long sourceEpochSecond = localDateTime.toEpochSecond(OrderMapper.DEFAULT_ZONE);
        LocalOrder localOrder = new LocalOrder();
        localOrder.setCreated(localDateTime);
      
        Order target = mapper.toOrder(localOrder);
      
        Assertions.assertNotNull(target);
        long targetEpochSecond = target.getCreated().getEpochSecond();
        Assertions.assertEquals(sourceEpochSecond, targetEpochSecond);
    }
}

测试验证了:

  1. 创建LocalDateTime并计算其纪元秒
  2. 通过映射器转换为Order对象
  3. 验证转换后的Instant纪元秒与原始值一致

5. 将Instant映射为LocalDateTime

现在看反向映射,将Instant转换回LocalDateTime

@Mapper
public interface OrderMapper {
    OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);
    ZoneOffset DEFAULT_ZONE = ZoneOffset.UTC;

    @Named("instantToLocalDateTime")
    default LocalDateTime instantToLocalDateTime(Instant instant) {
        return LocalDateTime.ofInstant(instant, DEFAULT_ZONE);
    }
  
    @Mapping(target = "id", source = "id")
    @Mapping(target = "created", source = "created", qualifiedByName = "instantToLocalDateTime")
    LocalOrder toLocalOrder(Order source);
}

关键变化:

  1. 新增instantToLocalDateTime()转换方法
  2. toLocalOrder()中:
    • 直接映射id字段
    • 通过qualifiedByName指定使用反向转换方法

验证反向映射的测试:

@Test
void whenOrderIsMapped_thenGetsLocalOrder() {
    Instant source = Instant.now();
    long sourceEpochSecond = source.getEpochSecond();
    Order order = new Order();
    order.setCreated(source);
    
    LocalOrder target = mapper.toLocalOrder(order);
    
    Assertions.assertNotNull(target);
    long targetEpochSecond = target.getCreated().toEpochSecond(OrderMapper.DEFAULT_ZONE);
    Assertions.assertEquals(sourceEpochSecond, targetEpochSecond);
}

测试流程:

  1. 创建Instant并获取其纪元秒
  2. 映射为LocalOrder对象
  3. 验证转换后的LocalDateTime纪元秒匹配原始值

6. 结论

在本文中,我们掌握了在MapStruct中处理日期时间映射的核心技巧:

关键收获

  • 使用@Named注解标记自定义转换方法
  • 通过qualifiedByName在映射中引用转换方法
  • 正确处理时区转换(使用UTC作为基准)
  • 双向映射保持数据一致性

⚠️ 踩坑提醒

  • 忘记指定时区会导致转换结果不一致
  • 未使用qualifiedByName会触发MapStruct的自动映射失败
  • 复杂类型转换必须显式定义方法

这种实现方式确保了Java应用程序中日期时间转换的可靠性和时区一致性,特别适合需要同时处理本地时间和全局时间的业务场景。

完整代码示例可在GitHub仓库中获取。


原始标题:Map LocalDateTime to Instant in MapStruct | Baeldung