使用实体方法作为 MapStruct 源

Use entity method as MapStruct source

背景

我们目前正在使用六边形架构实现一个应用程序。我们的 REST API DTO 通过 MapStruct 映射到我们的实体。这很好用。 (不过,如果 MapStruct 支持层次结构,那就更好了。)

问题

但是,我们面临一个问题,下面的例子最能说明问题:

假设您有一个存储出生日期的实体 Person。现在,这个 entity 有一个方法可以称为 int calculateAge()。 REST API 的 PersonDto 将获得属性 int age.

现在,我们希望 MapStruct 为我们生成这个映射。我们的方法是尝试配置 @Mapping(target = "age", ...) 以使用 int calculateAge() 方法作为源,但我们没有成功。 相信这可能是 MapStruct 的一个简单应用,我们很失望在搜索这个主题几个小时后没有想出一个干净的解决方案。

解决方案

我们发现了两种可行的解决方法,但(在我们看来)并不是真正可维护的:

  1. 使用@Mapping(expression = "java(...)")
  2. 使用@AfterMapping对构造的DTO进行post处理,并在注解方法中实现需要的映射

问题

是否有更简洁的方法来实现我们的目标,可能看起来像这样 @Mapping(sourceMethod = "calculateAge", target = "age)

据我所知,Mapstruct 依赖于标准的 getter 和 setter。如果你想使用特定的方法,那么 Mapstruct 确实可以使用 @qualifiers,但我不认为该方法可以在实体中。根据我的经验,最好的解决方案是使用@AfterMapping,正如你提到的。

Is there a cleaner way to achieve our goal, something which might look like this...

不,截至撰写此答案时,还没有 MapStruct 最新稳定版 (1.4.1.Final)。您基本上有两种选择,这在很大程度上取决于您想要映射字段的确切内容和方式。我将简要描述每种解决方案适用于什么情况:

  1. 第一个使用表达式的解决方案引入了方法在注释中硬编码的问题。只有在不调用自定义方法的情况下(仅来自现有的 Java API),我才更喜欢这种解决方案。无论如何,即使使用您提出的解决方案,它仍然是 硬编码 。语法是唯一改变的东西。在可维护性方面实际上没有区别:

    • @Mapping(target = "age", expression = "java(...)")        // current API
      
    • @Mapping(sourceMethod = "calculateAge", target = "age")   // hypothetical
      

    欢迎使用 request for 这样的功能。在任何情况下,此解决方案还需要映射器 (@Mapper(imports = Another.class)) 以及“假设的”映射器中的导入。

  2. 注解@AfterMapping在更复杂的转换和计算的情况下很有用。它不像单个注解调用那么干净,实际上您仍然 写入 映射 手动 ,但是,它带来了对被调用方法的更多控制IDE 编译前的亮点(至少你不需要额外的 IDE-specific 插件)。如果我需要调用我的自定义方法和逻辑,我会选择这个解决方案。