VO ↔ DTO 변환 라이브러리 MapStruct 사용법

MapStruct 라이브러리 추가 방법

pom.xml 수정

<dependencies>
  <!-- MapStruct 라이브러리 의존성 추가 -->
  <dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.4.2.Final</version>
  </dependency>
</dependencies>
<build>
  <!-- 생략 -->
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.10.1</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <encoding>UTF-8</encoding>

        <!-- 항상 전체 컴파일 설정 : DTO, VO 필드 변경 시 MapStruct impl 갱신 -->
        <useIncrementalCompilation>false</useIncrementalCompilation>

        <annotationProcessorPaths>
          <!-- Lombok annotationProcessor -->
          <path>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
          </path>
          <!-- MapStruct annotationProcessor 추가 -->
          <path>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.4.2.Final</version>
          </path>
        </annotationProcessorPaths>
      </configuration>
    </plugin>
  </plugins>
</build>

pom.xml에 MapStruct 라이브러리 및 어노테이션 프로세서를 추가하고, Maven을 리로드하여 반영해줍니다.
Lombok으로 VO 및 DTO Getter, Setter 함수가 생성된 이후 사용되어야 하므로, Lombok 아래에 추가합니다.

VO → DTO 변환 담당 ResponseMapper 생성

import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface AiLrnProfClasStnsResponseMapper {

  // VO → DTO 변환 (필드명이 서로 다른 경우만 명시)
  @Mapping(source = "DTO필드명1", target = "VO필드명1")
  @Mapping(source = "DTO필드명2", target = "VO필드명2")
  AiLrnProfClasStnsResponseDto voToDto(AiLrnProfClasStns vo);

  // VO 리스트 → DTO 리스트 변환
  // MapStruct가 내부적으로 voToDto를 호출하여 자동 매핑
  List<AiLrnProfClasStnsResponseDto> voListToDtoList(List<AiLrnProfClasStns> voList);
}

DB 쿼리 조회용 Mapper 폴더 안에 dto 폴더 생성 후, DTO 변환용 Mapper를 추가 생성합니다.

서비스에서 Mapper 생성자 주입 후 사용 예시

@Service ("AiLrnProfLrnnService")
public class AiLrnProfLrnnServiceImpl extends EgovAbstractServiceImpl implements AiLrnProfLrnnService {
    private final AiLrnProfLrnnMapper aiLrnProfLrnnMapper;
    private final AiLrnProfClasStnsResponseMapper aiLrnProfClasStnsResponseMapper;
    private final Ehcache ehCache;

    // 생성자 주입
    public AiLrnProfLrnnServiceImpl(
        AiLrnProfLrnnMapper aiLrnProfLrnnMapper,
        AiLrnProfClasStnsResponseMapper aiLrnProfClasStnsResponseMapper,
        Ehcache ehCache
    ) {
        this.aiLrnProfLrnnMapper = aiLrnProfLrnnMapper;
        this.aiLrnProfClasStnsResponseMapper = aiLrnProfClasStnsResponseMapper;
        this.ehCache = ehCache;
    }

    public List<AiLrnProfClasStnsResponseDto> selectProfClasStnsList(AiLrnClassStnsCondition condition) throws Exception {

        // 클래스 ID 리스트 세팅
        IMLoginVO user = (IMLoginVO) EgovUserDetailsHelper.getAuthenticatedUser();
        List<ClassInfo> classList = IMLoginAPIUtil.classList(ehCache, user.getId());
        condition.setClassInfos(classList);

        List<AiLrnProfClasStns> voList = aiLrnProfLrnnMapper.selectProfClasStnsList(condition);
        return aiLrnProfClasStnsResponseMapper.voListToDtoList(voList);
    }
}

@Autowired, @Resources 대신 생성자 주입으로 Mapper Bean을 주입받아 사용하는 것이 권장됩니다.
MapStruct 라이브러리가 적용된 Mapper를 통해 DB에서 조회한 VO를 DTO로 변환할 수 있습니다.

MapStruct 정상 적용 확인

package com.intermorph.lrn.mapper.dto;

import com.intermorph.lrn.domain.AiLrnProfClasStns;
import com.intermorph.lrn.dto.AiLrnProfClasStnsResponseDto;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import org.springframework.stereotype.Component;

@Generated(
  value = "org.mapstruct.ap.MappingProcessor",
  date = "2025-11-25T09:16:14+0900",
  comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_401 (Oracle Corporation)"
)
@Component
public class AiLrnProfClasStnsResponseMapperImpl implements AiLrnProfClasStnsResponseMapper {

  @Override
  public AiLrnProfClasStnsResponseDto voToDto(AiLrnProfClasStns vo) {
    if ( vo == null ) {
      return null;
    }

    AiLrnProfClasStnsResponseDto aiLrnProfClasStnsResponseDto = new AiLrnProfClasStnsResponseDto();

    aiLrnProfClasStnsResponseDto.setClasId( vo.getClasId() );
    aiLrnProfClasStnsResponseDto.setShcYear( vo.getShcYear() );
    aiLrnProfClasStnsResponseDto.setShcTrm( vo.getShcTrm() );
    aiLrnProfClasStnsResponseDto.setRcmCtareaCd( vo.getRcmCtareaCd() );
    aiLrnProfClasStnsResponseDto.setRcmCtareaNm( vo.getRcmCtareaNm() );

    return aiLrnProfClasStnsResponseDto;
  }

  @Override
  public List<AiLrnProfClasStnsResponseDto> voListToDtoList(List<AiLrnProfClasStns> voList) {
    if ( voList == null ) {
      return null;
    }

    List<AiLrnProfClasStnsResponseDto> list = new ArrayList<AiLrnProfClasStnsResponseDto>( voList.size() );
    for ( AiLrnProfClasStns aiLrnProfClasStns : voList ) {
      list.add( voToDto( aiLrnProfClasStns ) );
    }

    return list;
  }
}

톰캣 실행 시 프로젝트폴더/target/generated-sources/annotations 이하에 생성된 MapperImpl.java 파일입니다.
VO 내 각 필드 값을 DTO 내 각 필드에 매핑해주는 로직이 자동 구현된 것을 확인할 수 있습니다.
만약, 필드 매핑 로직이 생성되지 않았다면 프로젝트 Java 버전에 맞게 MapStruct 버전을 변경하면 됩니다.