DTO 생성에 대한 의문이 있어서 찾아봄
비즈니스 로직에서 가변 DTO와 불변 DTO에 대한 역할 분리의 필요성을 느꼈다.
로직내에서 유동적으로 변해야 하는것은 한번 생성한 DTO에서 필드 값만 변경하는게 좋을지 새 DTO를 생성해서 각자의 역할 분담을 해주는게 좋을지 그게 궁금했었다.

나는 가변 DTO를 쓸 때는 Builder 어노테이션을 사용해서 객체 생성하는 것을 선호하는 편인데 빌더로 설정한 dto 클래스는 마이바티스에서 응답값 전달을 받을때 제대로 실행이 안되고 오류가 난다는 문제가 있다.
왜냐 ?
이유는 마이바티스 실행 로직상 응답값 dto 를 매칭할 때 dto를 기본 생성자와 setter로 객체 생성을 하기 때문이다.
dto클래스를 빌더로 설정해 버리면 기본 생성자로 객체를 생성할 수 없어서 충돌이 나는 것임...
❗ 왜 @Builder가 안 되나?
MyBatis는 다음과 같은 방식으로 DTO를 채웁니다:
UserDTO dto = new UserDTO();
dto.setName(rs.getString("name"));
dto.setAge(rs.getInt("age"));
즉, 기본 생성자를 통해 객체를 만들고, Setter로 값 주입한다.
그런데 @Builder를 쓰면 다음과 같은 생성자가 생기고
public UserDTO(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
➡️ 기본 생성자가 사라지거나, MyBatis가 무시하게 됨
결과적으로 빌더 생성자는 MyBatis 입장에선 쓸 수 없는 생성자가 된다.
암튼 지피티 햄이 이렇게 정리해쥼
그래서 실무에서는 마이바티스용과 내부 비즈니스로직에서 사용할 dto분리해서 구현하는걸 선호한다고 한다.
// MyBatis Result 전용 DTO
@Data
public class UserResultDTO {
private String name;
private int age;
private boolean active;
}
// 내부 로직에 사용할 빌더 DTO
@Data
@Builder
public class UserDTO {
private String name;
private int age;
private boolean active;
}
그리고 핵심적으로 이 내용을 정리하게된 이유는 !!!
가변 DTO를 생성하게 될때 두개의 dto를 생성해야하는데 그 두개가 컬럼 하나로만 나뉜다면 ?
이때 하나의 dto를 돌려쓸지 담당 기능을 분리할지가 고민이었는데 분리해서 역할을 명확하게 나누는게 좋다고 한다.
=> 즉 setter로 값만 바꾸는게 아니라 그냥 새 dto 생성하셈ㅇㅇ 어차피 dto는 가볍기때문에 무시가능한 수준임.
❗동일 dto를 가변적으로 사용할 때 단점
불변성이 깨짐 (값 변경을 의도하지 않았더라도 변경 가능)
멀티스레드 상황에서 위험 (공유 DTO라면 더 위험)
의도가 불명확해짐 ("왜 갑자기 status가 바뀌었지?" 혼란 유발 가능)
근데 이제 이거를 빌더패턴으로 또 생성해주기는 번거로우니 toBuilder를 사용해서 새로운 dto를 만들어내는 것이다
UserSearchDTO inactiveDto = dto.toBuilder()
.status("INACTIVE")
.build();
장점
불변성 유지
명확한 의도 표현: 두 개의 다른 조회 조건
나중에 디버깅하거나 유지보수할 때 혼란 없음
단점
객체가 한 개 더 생김 (하지만 DTO는 가볍기 때문에 무시 가능한 수준)
그리고 toBuilder를 쓰기 위해서는
@Builder(toBuilder = true)
public class UserSearchDTO {
// ...
}
요렇코롬 toBuilder = true 를 설정해줘야 사용 가능하다는 점 !
✅ 결론: 실무에서는 Builder로 새 DTO를 생성하는 방식 권장
✔ 의도를 명확하게 표현하고,
✔ 불변성을 유지하면서,
✔ 잠재적인 Side Effect를 방지하기 위해
toBuilder()로 새 DTO를 만드는 방식이 안전하고 유지보수에 좋습니다.