๐Ÿงฉ SpringBoot + MapStruct๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฐ์ฒด ๋งคํ•‘ ์ž๋™ํ™”

Dev96ยท2025๋…„ 7์›” 30์ผ
post-thumbnail

ํŒ€ ํ”„๋กœ์ ํŠธ์—์„œ ๋งค๋ฒˆ DTO ๋งคํ•‘์„ ์ˆ˜๋™์œผ๋กœ ํ•˜๋‹ค ๋ณด๋‹ˆ ํ”ผ๋กœ๊ฐ์ด ์Œ“์˜€์Šต๋‹ˆ๋‹ค.
๊ทธ๋ž˜์„œ ๋„์ž…ํ•œ MapStruct! ์ด ๊ธ€์—์„œ๋Š” ๊ทธ ๊ฐœ๋…๋ถ€ํ„ฐ ์‹ค์ „ ์ฝ”๋“œ๊นŒ์ง€ ์†Œ๊ฐœํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

MapStruct๋กœ ๋น ๋ฅด๊ณ  ๊น”๋”ํ•˜๊ฒŒ ๋งคํ•‘ ์ž๋™ํ™”ํ•˜๊ธฐ!


๐Ÿ“Œ MapStruct๋ž€?

MapStruct๋Š” Java Bean ๊ฐ„์˜ ๋งคํ•‘ ์ฝ”๋“œ๋ฅผ ์ปดํŒŒ์ผ ํƒ€์ž„์— ์ž๋™ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๊ฐ์ฒด ๋งคํ•‘ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.
Lombok์ฒ˜๋Ÿผ Annotation ๊ธฐ๋ฐ˜์œผ๋กœ ์ •์˜ํ•˜๋ฉด, @Mapper ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„์ฒด๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

โœ”๏ธ ์ˆ˜๋™ ๋งคํ•‘์—์„œ ํ•ด๋ฐฉ๋˜๊ณ , ์„ฑ๋Šฅ๋„ ๋›ฐ์–ด๋‚œ ๊ฐ์ฒด ๋งคํผ์ž…๋‹ˆ๋‹ค.


โœ… MapStruct์˜ ์ฃผ์š” ํŠน์ง•

  • ์ปดํŒŒ์ผ ํƒ€์ž„ ์ƒ์„ฑ: ๋ฆฌํ”Œ๋ ‰์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„ ์„ฑ๋Šฅ์ด ๋น ๋ฆ„
  • ํƒ€์ž… ์•ˆ์ •์„ฑ: ์ปดํŒŒ์ผ ์˜ค๋ฅ˜๋กœ ์ž˜๋ชป๋œ ๋งคํ•‘์„ ์‚ฌ์ „์— ๋ฐฉ์ง€
  • ๊ฐ„๊ฒฐํ•œ ์ฝ”๋“œ: ๋ฐ˜๋ณต์ ์ธ ๋งคํ•‘ ๋กœ์ง ์ œ๊ฑฐ
  • ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๊ฐ€๋Šฅ: ์ปค์Šคํ…€ ๋งคํ•‘, ์กฐ๊ฑด๋ถ€ ๋งคํ•‘ ๋“ฑ ์œ ์—ฐํ•œ ์„ค์ • ์ง€์›

๐ŸŒŸ MapStruct์˜ ์žฅ์ 

์žฅ์ ์„ค๋ช…
โœ… ํผํฌ๋จผ์Šค ์šฐ์ˆ˜์ปดํŒŒ์ผ ํƒ€์ž„์— ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑ โ†’ ๋Ÿฐํƒ€์ž„ ์˜ค๋ฒ„ํ—ค๋“œ ์—†์Œ
โœ… ๋ฐ˜๋ณต ์ œ๊ฑฐDTO โ†” Entity ๊ฐ„ ๋งคํ•‘ ์ฝ”๋“œ ์ž๋™ ์ƒ์„ฑ
โœ… ์˜ค๋ฅ˜ ๋ฐฉ์ง€ํ•„๋“œ ๋ˆ„๋ฝ, ํƒ€์ž… ๋ถˆ์ผ์น˜ ์‹œ ์ปดํŒŒ์ผ ์˜ค๋ฅ˜ ์œ ๋„
โœ… ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด๋ช…์‹œ์  ๋งคํ•‘์œผ๋กœ ์ถ”์  ๋ฐ ํ…Œ์ŠคํŠธ ์šฉ์ด

โš ๏ธ MapStruct์˜ ๋‹จ์ 

๋‹จ์ ๋ณด์™„ ๋ฐฉ๋ฒ•
โŒ ๋Ÿฐํƒ€์ž„ ์œ ์—ฐ์„ฑ ๋ถ€์กฑ์ปดํŒŒ์ผ ํƒ€์ž„ ๊ธฐ๋ฐ˜์ด๋ผ ๋™์  ๋งคํ•‘์€ ์–ด๋ ค์›€
โŒ Lombok๊ณผ์˜ ๊ถํ•ฉ ์ด์Šˆ์ผ๋ถ€ IDE ์„ค์ • ๋ฐ ๋ฒ„์ „์— ๋”ฐ๋ผ ์ปดํŒŒ์ผ ์ˆœ์„œ ์ถฉ๋Œ ๊ฐ€๋Šฅ
โŒ ๊ณ ๊ธ‰ ๋กœ์ง ์ฒ˜๋ฆฌ ์–ด๋ ค์›€๋ณต์žกํ•œ ๋งคํ•‘์€ @AfterMapping, expression ๋“ฑ์„ ์ง์ ‘ ์ž‘์„ฑํ•ด์•ผ ํ•จ

๐Ÿ› ๏ธ ์‹ค๋ฌด ์ ์šฉ ์˜ˆ์‹œ

์•„๋ž˜๋Š” ์‹ค๋ฌด์—์„œ ์‚ฌ์šฉํ•œ MapStruct ๊ธฐ๋ฐ˜ DTO โ†” Entity ๋งคํ•‘ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.


// com.example.demo.member.adapter.in.web


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

    Member toDomain(CreateMemberRequest createMemberRequest);

    CreateMemberResponse toCreateMemberResponse(Member member);

    GetMemberResponse toGetMemberResponse(Member member);

    default Member toDomain(String memberId) {
        return Member.builder()
                .id(memberId)
                .build();
    }
}

// com.example.demo.member.adapter.out.persistence


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

    MemberJpaEntity toJpaEntity(Member member);

    Member toDomain(MemberJpaEntity memberJpaEntity);

}

๐Ÿ“‚ ํ—ฅ์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜ ๊ธฐ๋ฐ˜ ์ฑ…์ž„๋ถ„๋ฆฌ

Persistence ์˜์—ญ๊ณผ ์‚ฌ์šฉ์ž useCase์˜์—ญ์„ ๋ถ„๋ฆฌ ์ดํ›„ MapStruct ์‚ฌ์šฉ


๐Ÿงช ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์˜ˆ์‹œ


@DisplayName("Member Domain Unit Test")
public class MemberTest {

    private final MemberWebMapper mapper = new MemberWebMapperImpl();

    @Test
    void ํšŒ์›_์ •์ƒ_์ƒ์„ฑ_์š”์ฒญ์—์„œ_๋„๋ฉ”์ธ_๋ณ€ํ™˜() {
        // given
        CreateMemberRequest request = new CreateMemberRequest(
                "user@example.com",
                "QWERasdf1234!",
                "ํ™๊ธธ๋™",
                GenderEnum.MALE,
                "010-1234-5678",
                "์„œ์šธํŠน๋ณ„์‹œ ๊ฐ•๋‚จ๊ตฌ ํ…Œํ—ค๋ž€๋กœ 123"
        );

        // when
        Member member = mapper.toDomain(request);

        // then
        assertAll(
                () -> assertEquals("user@example.com", member.getEmail()),
                () -> assertEquals("QWERasdf1234!", member.getPassword()),
                () -> assertEquals("ํ™๊ธธ๋™", member.getName()),
                () -> assertEquals(GenderEnum.MALE, member.getGender()),
                () -> assertEquals("010-1234-5678", member.getPhoneNumber()),
                () -> assertEquals("์„œ์šธํŠน๋ณ„์‹œ ๊ฐ•๋‚จ๊ตฌ ํ…Œํ—ค๋ž€๋กœ 123", member.getAddress())
        );

    }
}

๐Ÿ“š ๋งˆ๋ฌด๋ฆฌ

MapStruct๋Š” ๋ฐ˜๋ณต์ ์ธ DTO โ†” Entity ๋งคํ•‘ ์ฝ”๋“œ๋ฅผ ์ปดํŒŒ์ผ ํƒ€์ž„์— ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์คŒ์œผ๋กœ์จ
๊ฐœ๋ฐœ์ž์˜ ์ƒ์‚ฐ์„ฑ์„ ๋†’์ด๊ณ , ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋™์‹œ์— ์žก์„ ์ˆ˜ ์žˆ๋Š” ํ›Œ๋ฅญํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.
ํŠนํžˆ Spring Boot์™€ ๊ฒฐํ•ฉํ•˜๋ฉด @Mapper(componentModel = "spring") ์„ค์ • ํ•˜๋‚˜๋กœ DI๊นŒ์ง€ ์—ฐ๋™๋˜๊ธฐ ๋•Œ๋ฌธ์—
๋ณต์žกํ•œ ๋ณ€ํ™˜ ๋กœ์ง ์—†์ด ๊น”๋”ํ•˜๊ณ  ๋ช…ํ™•ํ•œ ์ฝ”๋“œ ๊ตฌ์กฐ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

profile
๋‹ค์–‘ํ•œ ๊ฒฝํ—˜๊ณผ ์‹ค๋ฌด์˜ ๊นŠ์ด๋กœ ํ‰๊ฐ€๋ฐ›๊ณ  ์‹ถ์€ ์‚ฌ๋žŒ๋“ค์„ ์œ„ํ•ด ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. ์‹ค๋ฌด์—์„œ ๋ถ€๋”ชํžˆ๋ฉฐ ๋ฐฐ์šด ๊ฒƒ๋“ค์ด ๊ฐ€์žฅ ์˜ค๋ž˜ ๋‚จ๋Š”๋‹ค๊ณ  ๋ฏฟ์Šต๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€