1. 概要

Javaアプリケーションでは、あるタイプのJava Beanから別のタイプに値をコピーしたい場合があります。エラーが発生しやすい長いコードを回避するために、MapStructなどのBeanマッパーを使用できます。

同一のフィールドを同一のフィールド名でマッピングすることは非常に簡単ですが、Beanの不一致に遭遇することがよくあります。このチュートリアルでは、MapStructが部分的なマッピングを処理する方法を見ていきます。

2. マッピング

MapStructはJavaアノテーションプロセッサです。 したがって、必要なのは、マッパーインターフェイスを定義し、マッピングメソッドを宣言することだけです。 MapStructは、コンパイル中にこのインターフェイスの実装を生成します。

簡単にするために、同じフィールド名を持つ2つのクラスから始めましょう。

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

次に、マッパーインターフェイスを作成しましょう。

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

最後に、マッパーをテストしましょう。

@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. マップされていないプロパティ

MapStructはコンパイル時に動作するため、動的マッピングフレームワークよりも高速になる可能性があります。 また、マッピングが不完全な場合エラーレポートを生成することもできます。つまり、すべてのターゲットプロパティがマッピングされていない場合:

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

これは事故の際に役立つ警告ですが、フィールドが意図的に欠落している場合は、別の方法で処理することをお勧めします。

2つの単純なオブジェクトをマッピングする例を使用して、これを調べてみましょう。

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;
}

マッピング中に入力されることは想定されていない、両方のクラスに固有のフィールドがあります。 彼らです:

  • DocumentDTOコメント
  • DocumentDTOauthor
  • ドキュメントmodificationTime

マッパーインターフェイスを定義すると、ビルド中に警告メッセージが表示されます。

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

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

これらのフィールドをマップしたくないので、いくつかの方法でマッピングから除外できます。

4. 特定のフィールドを無視する

特定のマッピングメソッドでいくつかのプロパティをスキップするには、@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);
}

ここでは、フィールド名を target として指定し、ignoretrueに設定して、マッピングに必要ないことを示しています。

ただし、この手法は便利でない場合があります。 たとえば、フィールド数の多い大きなモデルを使用する場合など、使用が難しい場合があります。

5. マップされていないターゲットポリシー

物事をより明確にし、コードを読みやすくするために、マップされていないターゲットポリシーを指定できます。

これを行うには、MapStruct unmappedTargetPolicy を使用して、マッピングのソースフィールドがない場合に目的の動作を提供します。

  • エラー:マップされていないターゲットプロパティはビルドに失敗します-これは、誤ってマップされていないフィールドを回避するのに役立ちます
  • WARN :(デフォルト)ビルド中の警告メッセージ
  • IGNORE :出力またはエラーなし

マップされていないプロパティを無視して出力警告を受け取らないようにするには、IGNORE値をunmappedTargetPolicyに割り当てる必要があります。目的に応じていくつかの方法があります。

5.1. 各マッパーにポリシーを設定する

unmappedTargetPolicyをに設定できます@Mapperアノテーション。 その結果、そのすべてのメソッドはマップされていないプロパティを無視します。

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

5.2. 共有MapperConfigを使用する

を介してunmappedTargetPolicyを設定することにより、いくつかのマッパーのマップされていないプロパティを無視できます。@MapperConfig 複数のマッパー間で設定を共有します。

まず、注釈付きのインターフェイスを作成します。

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

次に、その共有構成をマッパーに適用します。

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

これは、 @ MapperConfig、の最小限の使用法を示す簡単な例であり、各マッパーにポリシーを設定するよりもはるかに優れているとは思えないことに注意してください。 共有構成は、複数のマッパー間で標準化する複数の設定がある場合に非常に役立ちます。

5.3. 構成オプション

最後に、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>

この例では、プロジェクト全体でマップされていないプロパティを無視しています。

6. 優先順位

部分的なマッピングを処理し、マッピングされていないプロパティを完全に無視するのに役立ついくつかの方法を検討しました。 マッパーに個別に適用する方法も見てきましたが、それらを組み合わせることもできます。

デフォルトのMapStruct構成を持つBeanとマッパーの大規模なコードベースがあると仮定しましょう。 いくつかの場合を除いて、部分的なマッピングを許可したくありません。 Beanまたはそのマップされた対応物にフィールドを簡単に追加して、気付かないうちに部分的なマッピングを取得する可能性があります。

したがって、部分的なマッピングの場合にビルドが失敗するように、Maven構成を介してグローバル設定を追加することをお勧めします。

ここで、一部のマッパーでマップされていないプロパティを許可し、グローバル動作をオーバーライドするために、優先順位(最高から最低)を念頭に置いて、テクニックを組み合わせることができます。

  • マッパーメソッドレベルで特定のフィールドを無視する
  • マッパーのポリシー
  • 共有MapperConfig
  • グローバル構成

7. 結論

このチュートリアルでは、マップされていないプロパティを無視するようにMapStructを構成する方法を確認しました。

最初に、マッピングされていないプロパティがマッピングにとって何を意味するかを調べました。 次に、いくつかの異なる方法で、エラーなしで部分的なマッピングを許可する方法を確認しました。

最後に、優先順位を念頭に置いて、これらの手法を組み合わせる方法を学びました。

いつものように、このチュートリアルのコードはGitHubから入手できます。