Ignoring Unmapped Properties with MapStruct – 用MapStruct忽略未映射的属性

最后修改: 2019年 9月 30日

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

1. Overview

1.概述

In Java applications, we may wish to copy values from one type of Java bean to another. To avoid long, error-prone code, we can use a bean mapper such as MapStruct.

在Java应用程序中,我们可能希望将数值从一种类型的Java Bean复制到另一种类型。为了避免冗长的、容易出错的代码,我们可以使用Bean mapper,如MapStruct

While mapping identical fields with identical field names is very straightforward, we often encounter mismatched beans. In this tutorial, we’ll look at how MapStruct handles partial mapping.

虽然用相同的字段名来映射相同的字段是非常直接的,但我们经常会遇到不匹配的Bean。在本教程中,我们将看看MapStruct如何处理部分映射。

2. Mapping

2.制图

MapStruct is a Java annotation processor. Therefore, all we need to do is to define the mapper interface and to declare mapping methods. MapStruct will generate an implementation of this interface during compilation.

MapStruct是一个Java注释处理器。因此,我们需要做的就是定义映射器接口和声明映射方法。MapStruct将在编译过程中生成该接口的实现。

For simplicity, let’s start with two classes with the same field names:

为了简单起见,让我们从两个具有相同字段名的类开始。

public class CarDTO {
    private int id;
    private String name;
}
public class Car {
    private int id;
    private String name;
}

Next, let’s create a mapper interface:

接下来,让我们创建一个映射器接口。

@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    CarDTO carToCarDTO(Car car);
}

Finally, let’s test our mapper:

最后,让我们测试一下我们的映射器。

@Test
public void givenCarEntitytoCar_whenMaps_thenCorrect() {
    Car entity = new Car();
    entity.setId(1);
    entity.setName("Toyota");

    CarDTO carDto = CarMapper.INSTANCE.carToCarDTO(entity);

    assertThat(carDto.getId()).isEqualTo(entity.getId());
    assertThat(carDto.getName()).isEqualTo(entity.getName());
}

3. Unmapped Properties

3.未映射的属性

As MapStruct operates at compile time, it can be faster than a dynamic mapping framework. It can also generate error reports if mappings are incomplete — that is, if not all target properties are mapped:

由于MapStruct是在编译时操作的,所以它比动态映射框架要快。它还可以在映射不完整时生成错误报告–也就是说,如果不是所有的目标属性都被映射了。

Warning:(X,X) java: Unmapped target property: "propertyName".

While this is is a helpful warning in the case of an accident, we may prefer to handle things differently if the fields are missing on purpose.

虽然这在发生事故的情况下是一个有用的警告,但如果这些字段是故意缺失的,我们可能更愿意以不同的方式处理事情。

Let’s explore this with an example of mapping two simple objects:

让我们通过一个映射两个简单对象的例子来探讨这个问题。

public class DocumentDTO {
    private int id;
    private String title;
    private String text;
    private List<String> comments;
    private String author;
}
public class Document {
    private int id;
    private String title;
    private String text;
    private Date modificationTime;
}

We have unique fields in both classes that are not supposed to be filled during mapping. They are:

我们在两个类中都有独特的字段,这些字段在映射过程中不应该被填写。它们是。

  • comments in DocumentDTO
  • author in DocumentDTO
  • modificationTime in Document

If we define a mapper interface, it will result in warning messages during the build:

如果我们定义了一个映射器接口,就会导致在构建过程中出现警告信息。

@Mapper
public interface DocumentMapper {
    DocumentMapper INSTANCE = Mappers.getMapper(DocumentMapper.class);

    DocumentDTO documentToDocumentDTO(Document entity);
    Document documentDTOToDocument(DocumentDTO dto);
}

As we do not want to map these fields, we can exclude them from mapping in a few ways.

由于我们不想映射这些字段,我们可以通过几种方式将它们排除在映射之外。

4. Ignoring Specific Fields

4.忽略特定领域

To skip several properties in a particular mapping method, we can use the ignore property in the @Mapping annotation:

要跳过某个映射方法中的几个属性,我们可以使用@Mapping注解中的ignore属性

@Mapper
public interface DocumentMapperMappingIgnore {

    DocumentMapperMappingIgnore INSTANCE =
      Mappers.getMapper(DocumentMapperMappingIgnore.class);

    @Mapping(target = "comments", ignore = true)
    @Mapping(target = "author", ignore = true)
    DocumentDTO documentToDocumentDTO(Document entity);

    @Mapping(target = "modificationTime", ignore = true)
    Document documentDTOToDocument(DocumentDTO dto);
}

Here, we’ve provided the field name as the target and set ignore to true to show that it’s not required for mapping.

在这里,我们提供了字段名作为target,并将ignore设置为true,以表明它不是映射所需的。

However, this technique is not convenient for some cases. We may find it difficult to use, for example, when using big models with a large number of fields.

然而,这种技术在某些情况下并不方便。我们可能会发现它难以使用,例如,当使用有大量字段的大模型时。

5. Unmapped Target Policy

5.未映射的目标政策

To make things clearer and the code more readable, we can specify the unmapped target policy.

为了使事情更清楚,代码更易读,我们可以指定未映射的目标策略

To do this, we use the MapStruct unmappedTargetPolicy to provide our desired behavior when there is no source field for the mapping:

为了做到这一点,我们使用MapStruct unmappedTargetPolicy 来提供我们所期望的行为,当没有映射的源字段时。

  • ERROR: any unmapped target property will fail the build – this can help us avoid accidentally unmapped fields
  • WARN: (default) warning messages during the build
  • IGNORE: no output or errors

In order to ignore unmapped properties and get no output warnings, we should assign the IGNORE value to the unmappedTargetPolicy. There are several ways to do it depending on the purpose.

为了忽略未映射的属性并获得无输出警告,我们应该IGNORE值分配给unmappedTargetPolicy根据目的,有几种方法可以做到这一点。

5.1. Set a Policy on Each Mapper

5.1.在每个Mapper上设置一个策略

We can set the unmappedTargetPolicy to the @Mapper annotation. As a result, all its methods will ignore unmapped properties:

我们可以将unmappedTargetPolicy设置为@Mapper注释。因此,它的所有方法将忽略未映射的属性:

@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DocumentMapperUnmappedPolicy {
    // mapper methods
}

5.2. Use a Shared MapperConfig

5.2.使用一个共享的MapperConfig

We can ignore unmapped properties in several mappers by setting the unmappedTargetPolicy via @MapperConfig to share a setting across several mappers.

我们可以通过设置unmappedTargetPolicy在几个映射器中忽略未映射的属性,通过@MapperConfig在几个映射器中共享一个设置。

First we create an annotated interface:

首先,我们创建一个注解的接口。

@MapperConfig(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface IgnoreUnmappedMapperConfig {
}

Then we apply that shared configuration to a mapper:

然后我们将该共享配置应用于映射器。

@Mapper(config = IgnoreUnmappedMapperConfig.class)
public interface DocumentMapperWithConfig { 
    // mapper methods 
}

We should note that this is a simple example showing the minimal usage of @MapperConfig, which might not seem much better than setting the policy on each mapper. The shared config becomes very useful when there are multiple settings to standardize across several mappers.

我们应该注意到这是一个简单的例子,显示了@MapperConfig的最小用法,这可能比在每个映射器上设置策略看起来好不了多少。当有多个设置需要在多个映射器上进行标准化时,共享配置就变得非常有用。

5.3. Configuration Options

5.3.配置选项

Finally, we can configure the MapStruct code generator’s annotation processor options. When using Maven, we can pass processor options using the compilerArgs parameter of the processor plug-in:

最后,我们可以配置MapStruct代码生成器的注释处理器选项。当使用Maven时,我们可以使用处理器插件的compilerArgs参数传递处理器选项。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>${maven-compiler-plugin.version}</version>
            <configuration>
                <source>${maven.compiler.source}</source>
                <target>${maven.compiler.target}</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
                <compilerArgs>
                    <compilerArg>
                        -Amapstruct.unmappedTargetPolicy=IGNORE
                    </compilerArg>
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

In this example, we’re ignoring the unmapped properties in the whole project.

在这个例子中,我们忽略了整个项目中未映射的属性。

6. The Order of Precedence

6.优先顺序

We’ve looked at several ways that can help us to handle partial mappings and completely ignore unmapped properties. We’ve also seen how to apply them independently on a mapper, but we can also combine them.

我们已经研究了几种可以帮助我们处理部分映射和完全忽略未映射属性的方法。我们也看到了如何在映射器上独立应用这些方法,但我们也可以将它们结合起来。

Let’s suppose we have a large codebase of beans and mappers with the default MapStruct configuration. We don’t want to allow partial mappings except in a few cases. We might easily add more fields to a bean or its mapped counterpart and get a partial mapping without even noticing it.

假设我们有一个由Bean和映射器组成的大型代码库,其配置为默认的MapStruct。除了少数情况,我们不希望允许部分映射。我们可能会很容易地在一个Bean或其映射的对应物上添加更多的字段,并在不知不觉中得到一个部分映射。

So, it’s probably a good idea to add a global setting through Maven configuration to make the build fail in case of partial mappings.

因此,通过Maven配置增加一个全局设置,使构建在部分映射的情况下失败,可能是个好主意。

Now, in order to allow unmapped properties in some of our mappers and override the global behavior, we can combine the techniques, keeping in mind the order of precedence (from highest to lowest):

现在,为了在我们的一些映射器中允许未映射的属性并覆盖全局行为,我们可以结合这些技术,记住优先顺序(从高到低)。

  • Ignoring specific fields at the mapper method-level
  • The policy on the mapper
  • The shared MapperConfig
  • The global configuration

7. Conclusion

7.结语

In this tutorial, we looked at how to configure MapStruct to ignore unmapped properties.

在本教程中,我们研究了如何配置MapStruct以忽略未映射的属性。

First, we looked at what unmapped properties mean for mapping. Then we saw how partial mappings could be allowed without errors, in a few different ways.

首先,我们看了未映射的属性对映射意味着什么。然后,我们看到了如何以几种不同的方式允许部分映射而不出错。

Finally, we learned how to combine these techniques, keeping in mind their order of precedence.

最后,我们学习了如何结合这些技术,并牢记其优先顺序。

As always, the code from this tutorial is available over on GitHub.

一如既往,本教程中的代码可在GitHub上获得