Mapper가 제대로 작동이 안되어 팀원이 4시간이 넘는 트러블 슈팅을 겪었다

 

 

문제 상황

분명 Mapper 문법에 맞게 작성했는데 구현체가 생성되지 않고, 코드가 제대로 실행되지 않음

결론부터 말하자면 Target 에 Setter가 누락되어서 생기는 오류였다.

Mapper가 어떻게 작동하는지 부터 다시 되짚어보며 문제를 해결해봤다.

 

Mapper, 그럼 어떻게 구현되는가?

@Mapper
public interface TestMapper{
    void updateHuman(TestDto testDto, @MappingTarget Test test);
}

이렇게 코드를 작성하면 testDto 의 내용이 Test 객체로 매핑된다!

어떻게 작동되는 것일까?

공식 문서를 보자

Mapper는 구현될 때 target에 setter를 이용하여 매핑을 해준다는 것을 알 수 있다.

즉 직접 setter를 이용해서 필드의 값을 하나씩 매핑하는 과정을 생략해주는 것이라고 볼 수 있다!

 

코드로 확인해보자
코드로 DTO를 target(엔티티)로 매핑하는 과정을 보면서 Setter가 있는경우와 없는 경우를 비교해 보자

 

testUse(Target이 될 클래스)

@Getter
public class TestUse {
    private String f1;
    private String f2;
}

 

DTO

public record Dto (
    String f1, 
    String f2
){}

 

Mapper Interface 정의

@Mapper
public interface TestMapper {
    TestMapper INSTANCE = Mappers.getMapper(TestMapper.class);
    TestUse toTestUse(Dto dto);
}

 

테스트코드

public class UserMapperTest {
    @Test
    public void testEntityToDTOWithoutSetters() {
        Dto dto = new Dto("f1","f2");
        TestUse testUse = TestMapper.INSTANCE.toTestUse(dto);
        System.out.println(testUse.getF1());
    }
}

dto 를 생성해주고 TestUse와 매핑, 그 결과를 출력해보자

 

결과

null

 

예상처럼 null이 반환된다.

Setter를 정의해주지 않아 객체에 값을 넣어주지 못해 null을 반환하는 것.

 

구현체도 확인해보자

public class TestMapperImpl implements TestMapper {

    @Override
    public TestUse toTestUse(Dto dto) {
        if ( dto == null ) {
            return null;
        }

        TestUse testUse = new TestUse();

        return testUse;
    }
}

 

구현체에 TestUse를 설정하는 코드가 생성되지 않는다! 

즉 setF1, setF2를 해 주지 못해 빈 객체를 반환하게 된다.

 

Setter 대신 Builder로도 확인해볼까?

@Override
    public TestUse toTestUse(Dto dto) {
        if ( dto == null ) {
            return null;
        }

        TestUse.TestUseBuilder testUse = TestUse.builder();

        testUse.f1( dto.f1() );
        testUse.f2( dto.f2() );

        return testUse.build();
    }

 

Builder 어노테이션이 있다면 이를 감지하여 자동으로 구현체를 생성해준다.

 

생성자를 정의해 주어도 이렇게 잘 감지해서 동작하는 것을 알 수 있다.

@Override
    public TestUse toTestUse(Dto dto) {
        if ( dto == null ) {
            return null;
        }

        String f1 = null;
        String f2 = null;

        f1 = dto.f1();
        f2 = dto.f2();

        TestUse testUse = new TestUse( f1, f2 );

        return testUse;
    }

 

마무리

MapStruct 사용하면 편하게 객체사이를 매핑 할 수 있지만 이 또한 자동이라고 생각하지 말자.

MapStruct의 역할은 객체의 Setter, Builder, 생성자를 이용해 매핑을 자동화 해 주는 것이므로 매핑이 제대로 되지 않는것 같다면

클래스에 Setter, Builder가 선언되어있는지 확인해 보면 좋을것 같다.

rootTiket