1. 概要

このチュートリアルでは、MapStructを使用してオブジェクトのコレクションをマップする方法を見ていきます。

この記事はすでにMapStructの基本を理解していることを前提としているため、初心者はまずMapStructのクイックガイドを確認する必要があります。

2. コレクションのマッピング

一般に、 MapStructを使用したコレクションのマッピングは、単純型の場合と同じように機能します。

基本的に、単純なインターフェイスまたは抽象クラスを作成し、マッピングメソッドを宣言する必要があります。 宣言に基づいて、MapStructはマッピングコードを自動的に生成します。 通常、生成されたコードは、ソースコレクションをループし、各要素をターゲットタイプに変換し、それぞれをターゲットコレクションに含めます。

簡単な例を見てみましょう。

2.1. マッピングリスト

まず、この例では、マッパーのマッピングソースとして単純なPOJOを考えてみましょう。

public class Employee {
    private String firstName;
    private String lastName;

    // constructor, getters and setters
}

ターゲットは単純なDTOになります。

public class EmployeeDTO {

    private String firstName;
    private String lastName;

    // getters and setters
}

次に、マッパーを定義しましょう。

@Mapper
public interface EmployeeMapper {
    List<EmployeeDTO> map(List<Employee> employees);
}

最後に、EmployeeMapperインターフェイスから生成されたコードMapStructを見てみましょう。

public class EmployeeMapperImpl implements EmployeeMapper {

    @Override
    public List<EmployeeDTO> map(List<Employee> employees) {
        if (employees == null) {
            return null;
        }

        List<EmployeeDTO> list = new ArrayList<EmployeeDTO>(employees.size());
        for (Employee employee : employees) {
            list.add(employeeToEmployeeDTO(employee));
        }

        return list;
    }

    protected EmployeeDTO employeeToEmployeeDTO(Employee employee) {
        if (employee == null) {
            return null;
        }

        EmployeeDTO employeeDTO = new EmployeeDTO();

        employeeDTO.setFirstName(employee.getFirstName());
        employeeDTO.setLastName(employee.getLastName());

        return employeeDTO;
    }
}

注意すべき重要なことがあります。 具体的には、 MapStructが、EmployeeからEmployeeDTOへのマッピングを自動的に生成しました。

これが不可能な場合があります。 たとえば、Employeeモデルを次のモデルにマップするとします。

public class EmployeeFullNameDTO {

    private String fullName;

    // getter and setter
}

この場合、 EmployeeListからEmployeeFullNameDTOListへのマッピングメソッドを宣言すると、次のようなコンパイル時のエラーまたは警告:

Warning:(11, 31) java: Unmapped target property: "fullName". 
  Mapping from Collection element "com.baeldung.mapstruct.mappingCollections.model.Employee employee" to 
  "com.baeldung.mapstruct.mappingCollections.dto.EmployeeFullNameDTO employeeFullNameDTO".

基本的に、これは、 MapStructが、この場合のマッピングを自動的に生成できなかったことを意味します。 したがって、EmployeeEmployeeFullNameDTO。の間のマッピングを手動で定義する必要があります。

これらの点を考慮して、手動で定義しましょう。

@Mapper
public interface EmployeeFullNameMapper {

    List<EmployeeFullNameDTO> map(List<Employee> employees);

    default EmployeeFullNameDTO map(Employee employee) {
        EmployeeFullNameDTO employeeInfoDTO = new EmployeeFullNameDTO();
        employeeInfoDTO.setFullName(employee.getFirstName() + " " + employee.getLastName());

        return employeeInfoDTO;
    }
}

生成されたコードは、ソースリストの要素をターゲットリストにマップするために定義したメソッドを使用します。

これは一般的にも当てはまります。 ソース要素タイプをターゲット要素タイプにマップするメソッドを定義した場合、MapStructはそれを使用します。

2.2. マッピングセットとマップ

MapStructを使用したセットのマッピングは、リストの場合と同じように機能します。 たとえば、EmployeeインスタンスのSetEmployeeDTOインスタンスSetにマップするとします。

前と同じように、マッパーが必要です。

@Mapper
public interface EmployeeMapper {

    Set<EmployeeDTO> map(Set<Employee> employees);
}

そして、MapStructは適切なコードを生成します:

public class EmployeeMapperImpl implements EmployeeMapper {

    @Override
    public Set<EmployeeDTO> map(Set<Employee> employees) {
        if (employees == null) {
            return null;
        }

        Set<EmployeeDTO> set = 
          new HashSet<EmployeeDTO>(Math.max((int)(employees.size() / .75f ) + 1, 16));
        for (Employee employee : employees) {
            set.add(employeeToEmployeeDTO(employee));
        }

        return set;
    }

    protected EmployeeDTO employeeToEmployeeDTO(Employee employee) {
        if (employee == null) {
            return null;
        }

        EmployeeDTO employeeDTO = new EmployeeDTO();

        employeeDTO.setFirstName(employee.getFirstName());
        employeeDTO.setLastName(employee.getLastName());

        return employeeDTO;
    }
}

同じことがマップにも当てはまります。 マップしたいことを考えてみましょう地図地図

次に、前と同じ手順を実行できます。

@Mapper
public interface EmployeeMapper {

    Map<String, EmployeeDTO> map(Map<String, Employee> idEmployeeMap);
}

そしてMapStructはその仕事をします:

public class EmployeeMapperImpl implements EmployeeMapper {

    @Override
    public Map<String, EmployeeDTO> map(Map<String, Employee> idEmployeeMap) {
        if (idEmployeeMap == null) {
            return null;
        }

        Map<String, EmployeeDTO> map = new HashMap<String, EmployeeDTO>(Math.max((int)(idEmployeeMap.size() / .75f) + 1, 16));

        for (java.util.Map.Entry<String, Employee> entry : idEmployeeMap.entrySet()) {
            String key = entry.getKey();
            EmployeeDTO value = employeeToEmployeeDTO(entry.getValue());
            map.put(key, value);
        }

        return map;
    }

    protected EmployeeDTO employeeToEmployeeDTO(Employee employee) {
        if (employee == null) {
            return null;
        }

        EmployeeDTO employeeDTO = new EmployeeDTO();

        employeeDTO.setFirstName(employee.getFirstName());
        employeeDTO.setLastName(employee.getLastName());

        return employeeDTO;
    }
}

3. コレクションマッピング戦略

多くの場合、親子関係を持つデータ型をマップする必要があります。 通常、フィールドとして別のデータ型(子)のコレクションを持つデータ型(親)があります。

このような場合、 MapStructは、子を親タイプに設定または追加する方法を選択する方法を提供します。特に、@MapperアノテーションにはcollectionMappingStrategyがあります。 ACCESSOR_ONLY SETTER_PREFERRED ADDER_PREFERRED 、またはTARGET_IMMUTABLEの属性。

これらの値はすべて、子を設定または親タイプに追加する方法を示しています。 デフォルト値はACCESSOR_ONLY、です。これは、子のコレクションを設定するためにアクセサーのみを使用できることを意味します。

このオプションは、 コレクションフィールドのセッターは使用できませんが、加算器があります。 これが役立つもう1つのケースはコレクションが親タイプで不変の場合 。 通常、これらのケースは、生成されたターゲットタイプで発生します。

3.1. ACCESSOR_ONLYコレクションマッピング戦略

これがどのように機能するかをよりよく理解するために例を見てみましょう。

この例では、マッピングソースとしてCompanyクラスを作成しましょう。

public class Company {

    private List<Employee> employees;

   // getter and setter
}

そして、マッピングのターゲットは単純なDTOになります。

public class CompanyDTO {

    private List<EmployeeDTO> employees;

    public List<EmployeeDTO> getEmployees() {
        return employees;
    }

    public void setEmployees(List<EmployeeDTO> employees) {
        this.employees = employees;
    }

    public void addEmployee(EmployeeDTO employeeDTO) {
        if (employees == null) {
            employees = new ArrayList<>();
        }

        employees.add(employeeDTO);
    }
}

セッターsetEmployees、と加算器 addEmployee、の両方が使用可能であることに注意してください。 また、加算器のは、コレクションの初期化を担当します。

さて、マップしたいとしましょう会社 CompanyDTO。 次に、前と同じように、マッパーが必要です。

@Mapper(uses = EmployeeMapper.class)
public interface CompanyMapper {
    CompanyDTO map(Company company);
}

EmployeeMapperとデフォルトのcollectionMappingStrategy。を再利用したことに注意してください。

それでは、MapStructが生成したコードを見てみましょう。

public class CompanyMapperImpl implements CompanyMapper {

    private final EmployeeMapper employeeMapper = Mappers.getMapper(EmployeeMapper.class);

    @Override
    public CompanyDTO map(Company company) {
        if (company == null) {
            return null;
        }

        CompanyDTO companyDTO = new CompanyDTO();

        companyDTO.setEmployees(employeeMapper.map(company.getEmployees()));

        return companyDTO;
    }
}

ご覧のとおり、 MapStructは、セッターsetEmployeesを使用して、EmployeeDTOインスタンスのリストを設定します。 これは、ここではデフォルトの collectionMappingStrategy、ACCESSOR_ONLY。を使用しているために発生します。

また、MapStructは、 リストリスト EmployeeMapper そしてそれを再利用しました。

3.2. ADDER_PREFERREDコレクションマッピング戦略

対照的に、ADDER_PREFERREDcollectionMappingStrategyとして使用したとしましょう。

@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
        uses = EmployeeMapper.class)
public interface CompanyMapperAdderPreferred {
    CompanyDTO map(Company company);
}

ここでも、EmployeeMapperを再利用します。 ただし、最初に単一の従業員をEmployeeDTOに変換できるメソッドを明示的に追加する必要があります

@Mapper
public interface EmployeeMapper {
    EmployeeDTO map(Employee employee);
    List map(List employees);
    Set map(Set employees);
    Map<String, EmployeeDTO> map(Map<String, Employee> idEmployeeMap);
}

これは、MapStructが加算器を使用してEmployeeDTOインスタンスをターゲットのCompanyDTOインスタンスに1つずつ追加するためです

public class CompanyMapperAdderPreferredImpl implements CompanyMapperAdderPreferred {

    private final EmployeeMapper employeeMapper = Mappers.getMapper( EmployeeMapper.class );

    @Override
    public CompanyDTO map(Company company) {
        if ( company == null ) {
            return null;
        }

        CompanyDTO companyDTO = new CompanyDTO();

        if ( company.getEmployees() != null ) {
            for ( Employee employee : company.getEmployees() ) {
                companyDTO.addEmployee( employeeMapper.map( employee ) );
            }
        }

        return companyDTO;
    }
}

加算器が利用できない場合は、セッターが使用されます。

すべてのコレクションマッピング戦略の完全な説明は、MapStructのリファレンスドキュメントにあります。

4. ターゲットコレクションの実装タイプ

MapStructは、マッピングメソッドのターゲットタイプとしてコレクションインターフェイスをサポートしています。

この場合、生成されたコードでいくつかのデフォルトの実装が使用されます。 たとえば、上記の例からわかるように、Listのデフォルトの実装はArrayListです。

MapStructがサポートするインターフェースの完全なリストと、MapStructが各インターフェースに使用するデフォルトの実装については、リファレンスドキュメントを参照してください。

5. 結論

この記事では、MapStructを使用してコレクションをマップする方法について説明しました。

最初に、さまざまなタイプのコレクションをマップする方法を確認しました。 次に、コレクションマッピング戦略を使用して、親子関係マッパーをカスタマイズする方法を確認しました。

その過程で、MapStructを使用してコレクションをマッピングする際に留意すべき重要なポイントと事項を強調しました。

いつものように、完全なコードはGitHubから入手できます。