1. 개요
이 사용방법(예제)에서는 MapStruct를 사용하여 개체 컬렉션을 매핑하는 방법을 배웁니다.
이 기사는 이미 MapStruct에 대한 기본적인 이해를 가정하고 있으므로 초보자는 먼저 MapStruct에 대한 빠른 사용방법(예제)를 확인해야 합니다 .
2. 컬렉션 매핑
일반적으로 MapStruct를 사용한 매핑 컬렉션은 단순 유형과 동일한 방식으로 작동합니다 .
기본적으로 간단한 인터페이스나 추상 클래스를 만들고 매핑 메서드를 선언해야 합니다. 선언에 따라 MapStruct는 매핑 코드를 자동으로 생성합니다. 일반적으로 생성된 코드는 소스 컬렉션을 반복하고 각 요소를 대상 유형으로 변환하고 각 요소를 대상 컬렉션에 포함합니다 .
간단한 예를 살펴보겠습니다.
2.1. 매핑 List
먼저 매퍼의 매핑 소스로 간단한 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
}
이 경우 Employee List에서 EmployeeFullNameDTO List으로의 매핑 방법을 선언하면 컴파일 타임 오류 또는 경고 가 표시 됩니다.
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가 매핑을 자동으로 생성할 수 없음을 의미합니다 . 따라서 Employee 와 EmployeeFullNameDTO 간의 매핑을 수동으로 정의해야 합니다 .
이러한 점을 감안하여 수동으로 정의해 보겠습니다.
@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;
}
}
생성된 코드는 소스 List 의 요소를 대상 List 에 매핑하기 위해 정의한 메서드를 사용합니다 .
이는 일반적으로도 적용됩니다. 소스 요소 유형을 대상 요소 유형에 매핑하는 메서드를 정의한 경우 MapStruct에서 이를 사용합니다.
2.2. 매핑 세트 및 맵
MapStruct를 사용한 매핑 세트는 List과 동일한 방식으로 작동합니다. 예를 들어 Employee 인스턴스 집합 을 EmployeeDTO 인스턴스 집합 에 매핑한다고 가정해 보겠습니다 .
이전과 마찬가지로 매퍼가 필요합니다.
@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;
}
}
Map에도 동일하게 적용됩니다. Map<String, Employee> 를 Map <String, 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 어노테이션에는 ACCESSOR_ONLY , SETTER_PREFERRED , ADDER_PREFERRED 또는 TARGET_IMMUTABLE 일 수 있는 collectionMappingStrategy 속성이 있습니다 .
이러한 모든 값은 자식이 설정되거나 부모 유형에 추가되는 방식을 나타냅니다. 기본값은 ACCESSOR_ONLY이며 이는 접근자만 자식 컬렉션 을 설정하는 데 사용할 수 있음을 의미합니다.
이 옵션은 Collection 필드의 세터를 사용할 수 없지만 가산기가 있는 경우에 유용합니다. 이것이 유용한 또 다른 경우 는 컬렉션 이 상위 유형에서 변경 불가능한 경우 입니다. 일반적으로 이러한 경우는 생성된 대상 유형에서 발생합니다.
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);
}
}
setter setEmployees 와 adder addEmployee를 모두 사용할 수 있습니다. 또한 adder의 경우 컬렉션 초기화를 담당합니다.
이제 회사 를 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는 Setter setEmployees를 사용하여 EmployeeDTO 인스턴스 List 을 설정 합니다 . 이는 기본 collectionMappingStrategy인 ACCESSOR_ONLY를 사용했기 때문에 발생합니다.
MapStruct는 또한 EmployeeMapper에서 List<Employee> 를 List < EmployeeDTO > 에 매핑하는 메서드를 찾아 재사용했습니다.
3.2. ADDER_PREFERRED 컬렉션 매핑 전략
반대로 ADDER_PREFERRED 를 collectionMappingStrategy 로 사용했다고 가정해 보겠습니다 .
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
uses = EmployeeMapper.class)
public interface CompanyMapperAdderPreferred {
CompanyDTO map(Company company);
}
다시, 우리는 EmployeeMapper 를 재사용하고 싶습니다 . 그러나 먼저 단일 Employee 를 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 인스턴스에 하나씩 추가하기 때문입니다 .
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가 지원하는 인터페이스의 전체 List과 각 인터페이스에 대해 사용하는 기본 구현을 찾을 수 있습니다 .
5. 결론
이 기사에서는 MapStruct를 사용하여 컬렉션을 매핑하는 방법을 살펴보았습니다.
먼저 다양한 유형의 컬렉션을 매핑하는 방법을 살펴보았습니다. 그런 다음 컬렉션 매핑 전략을 사용하여 부모-자식 관계 매퍼를 사용자 지정하는 방법을 배웠습니다.
그 과정에서 MapStruct를 사용하여 컬렉션을 매핑하는 동안 명심해야 할 핵심 사항과 사항을 강조했습니다.
평소와 같이 전체 코드는 GitHub 에서 사용할 수 있습니다 .