들어가며
빌더 패턴은 개인적으로 좋아하는 방식이다.
생성자는 필드 값이 길어지면 순서에 의존하는 경향이 강하고 자바 빈 방식은 데이터가 명확하게 세팅되지만 변경 가능하다는 단점이 있다. 빌더 패턴은 불변성 때문에 귀찮은 경우가 발생하지만, 위 두 가지 단점을 한 번에 해결할 수 있다.
그래서 이제는 다들 적극적으로 사용하고 있는 것 같다. 그럼 스프링 + Mybatis 환경에서 빌더를 적용하는 중 발생했던 오류를 보면서 무엇을 조심해야 하는지 살펴보자.
여기에는 Lombok 라이브러리를 사용했다.
문제 발생 상황
책의 제목을 변경하는 API를 개발한 BookRestController가 있다. 변경할 책의 ID와 변경하고자 하는 제목을 title 필드에 담아서 전달받는다. 기존에 잘 사용하던 기능이었으나 빌더 패턴을 적용하려고 한다. Mabatis에 전달하던 BookReqVO 객체를 아래와 같이 코드를 변경하였다.
BookReqVO 객체
@Getter
@Setter
@Builder // 빌더패턴 적용을 위해 추가
public class BookReqVO {
private Long id;
private String title;
}
그랬더니 이제는 java.lang.IndexOutOfBoundsException 오류가 발생한다. 원인이 뭘까?
문제 분석
마이바티스는 DTO에 담긴 데이터를 아래와 같은 순서로 DB에 전달한다.
- 기본 생성자를 통해 객체를 생성
- Setter를 사용하여 데이터를 변경
그래서 빌더를 적용하기 전에는 BookReqVO를 통해 데이터를 전달하는데 문제가 없었다. 그러나 @Builder를 사용하면 이야기가 다르다. @Builder는 빌더 클래스가 사용할 수 있도록 private 생성자를 생성하는데, 이는 모든 필드 값을 포함한다. 이로 인해 BookReqVO는 기본 생성자가 사라지게 된다.
따라서 아래와 같이 코드를 변경하면 마이바티스에서도 정상적으로 동작하면서 빌더 패턴을 적용할 수 있다.
@Getter
@Setter
@Builder
@NoArgsConstructor
public class BookReqVO {
private Long id;
private String title;
@Builder
public BookReqVO(Long id, String title) {
super();
this.id = id;
this.title = title;
}
}
기본 생성자를 추가해주고 빌더를 위한 생성자도 수동으로 생성해주었다. IDE를 잘 찾아보면 생성자를 만들어주는 기능이 있으니 3초면 만들 수 있다.
여담이지만 마이바티스의 DTO로 사용되는 객체는 빌더 패턴을 사용하면 안 된다. 위에서 설명했듯이 마이바티스는 Setter를 강제한다. 글의 처음으로 돌아가 빌더 패턴을 왜 사용했는지 이유를 다시 보면 "불변성"을 위해 사용한다는 내용이 있었다.
마이바티스의 Setter와 빌더 패턴의 불변성이 양립할 수 없기 때문에 꼭 필요한 것인지 고민해봐야 한다.
마치며
lombok 없는 개발은 상상할 수 없을 정도로 간단하면서 강력한 라이브러리다. 그렇지만 애노테이션을 활용하여 코드를 주입하기 때문에 사용자가 전체 코드를 놓칠 수 있다는 문제가 있다.
특히 @Data는 간단하지만 많은 기능을 담고 있지만, 여기에 @Getter, @Setter, @RequiredArgsConstructor, @ToString, @EqualsAndHashCode 모두 사용된다는 사실을 모르는 분들도 많을 것이다.
편의를 위해 제공되는 기능이 때때론 개발자 모르게 버그를 만들어 낼 때가 있으니 적재적소에 필요한 기능만 추가하기를 권장한다.