@Builder 패턴으로 객체 수정시 값이 초기화 되는 문제

알파로그·2023년 4월 4일
0

Spring Boot

목록 보기
36/57

✏️ 발단

  • 지난번 객체를 생성할 때 연관관계에 있는 List 속성이 null 로 생성되는 문제를 해결한 후 발생한 문제이다.
  • 생성한 객체를 수정하면 특정 부분만 수정되는 것이 아닌 기존 객체에 수정한 객체가 덮어- 씌워지는 문제를 확인했다.
    • 문제를 해결하고 알게된 사실이지만
      Builder 는 객체를 수정하는 기능이 아니고 new 로 무조건 생성하는 생성자같은 기능이였다.

📍 Service 계층 - 수정 로직

  • 아래 코드에서는 content 속성과 수정된 날짜 속성만 수정하고있다.
    • 즉, 생성 날짜는 처음 생성했을 때와 동일한 값이 유지되어야 하지만,
      null 값으로 초기화가 되고있었다.
public void modify(Comment comment, String content) {
    Comment comment1 = Comment.builder()
            .id(comment.getId())
            .content(content)
            .modifyDate(LocalDateTime.now())
            .build();
    this.commentRepository.save(comment1);
}

✏️ 문제 원인

📍 builder 로 객체 수정시 id 값을 특정했기 때문일 경우

  • id 값을 특정했기 때문에 @Id 어노테이션에 의해 기존에 있던 data 가 수정되지 않고, 삭제가 된다고 생각했다.
    • 즉, 기존 data 가 유실되고 수정이 아닌 덮어쓰기가 된다고 생각했다.

✏️ 문제 해결

📍 1차 시도 - 실패

  • 변수로 받은 객체에 다시 builder 를 사용하는 방식으로 수정했다.
    • id 값은 제거했다.
public void modify(Comment comment, String content) {
    comment = Comment.builder()
            .content(content)
            .modifyDate(LocalDateTime.now())
            .build();
    this.commentRepository.save(comment1);
}
  • 이렇게 실행해보니 수정으로 작동되는것이 아닌 새로운 객체가 새로운 id 값을 부여받고 생성이 되어버렸다.
    • 기존 객체는 그대로 유지되었다.
  • 해당 build 로직 자체에 new 라는 속성이 생략되어있어 변수로 받은 comment 에 수정되는 것이 아닌 새로운 객체가 생성되 입력되 작동되고 있다.

📍 2차 시도 - 실패

  • 이번엔 save 로직을 제거하고 변경감지를 사용하는 방식을 생각해봤다.
    • 하지만 지금 로직 제체가 수정 로직이 아닌 new 로 새롭게 객체를 생성하는 로직이기 때문에
      당연히 변경감지가 일어나지 않았다.
public void modify(Comment comment, String content) {
    comment = Comment.builder()
            .content(content)
            .modifyDate(LocalDateTime.now())
            .build();
}

📍 3차 시도 - 실패

  • 변경감지를 사용하는 방법이 정답이라 생각해 gpt 에 @builder 의 변경감지 사용법을 물어봤다.
    • ToBuiler 라는 mehod 를 사용해 문제를 해결할 수 있다고 답변받았다.
    • 하지만 ToBuilder 라는 method 는 존재하지 않았다.
      • 어노테이션 객체까지 들어가서 찾아봤지만 비슷한 의미의 mehod 조차 발견해지 못했음

📍 4차 시도 - 실패

  • 계속해서 gpt 와 구글링을 통해 정보를 수집하던중 merge 라는 method 를 발견했다.
    • 로직을 살펴보면 기존 객체를 수정할 때 갑이 존재하면 유지, 수정되면 덮어쓰기 방식으로 작동된다는 걸 알 수 있다.
    • 사실 변경감지는 아니고 덮어쓰는 방식이지만 결과만 같다면 이 방식으로 문제를 해결할 수 있다고 생각했다.
    return this.toBuilder()
       .name(other.getName() != null ? other.getName() : this.getName())
       .age(other.getAge() > 0 ? other.getAge() : this.getAge())
       .address(other.getAddress() != null ? other.getAddress() : this.getAddress())
       .build();
  • merge 를 적용하기위해서 method 를 찾아봤지만 merge 로 정의 된 mehod 는 물론, 비슷한 로직의 method 또한 @builder 에는 준비되어있지 않았다.
    • 수정기능은 아주 많이 사용되는 로직이기 때문에 이런 로직을 절대로 직접 만들어 사용하지 안을거라고 생각했고 계속 방법을 찾아봤다.

📍 5차 시도 - 성공

  • gpt 와 구글링으로 정보를 찾다 toBuilde 라는 method 를 찾았다.
    • 이 method 는 앞서 발견한 merge 와 같은 기능이라는 사실도 알아냈다.
  • toBuilder 를 사용해기 위해선 @Builder 어노테이션에 toBuilder =true 라는 속성을 추가해주어야 사용할 수 있었다.
    • 지금까지 우리가 수정 method 를 찾지 못했던 이유이다.
@Entity
@Builder(toBuilder = true)
  • 이제 Service 계층에서 toBuilder 메서드를 사용할 수 있게 되었다.
    • test 결과 같은 id 값에 객체가 새로 생성되므로 이전 객체는 삭제되고 수정된 객체가 덮어쓰기 된걸 확인햇다.
      • 참고로 삼항연산자에 의해 수정하지 않은 이전 객체의 data 들은 고스란히 새로운 객체에 입력된다.
public void modify(Comment comment, String contents) {
    Comment comment1 = comment.toBuilder()   // toBuilder 로 수정!!!
            .content(contents)
            .modifyDate(LocalDateTime.now())
            .build();
    this.commentRepository.save(comment1);
}
profile
잘못된 내용 PR 환영

2개의 댓글

comment-user-thumbnail
2024년 2월 20일

@Builder(toBuilder = true)
테스트 내용 공유 감사합니다!

답글 달기
comment-user-thumbnail
7일 전

저도 setter를 지양하고 빌더패턴 도입하려고 시도하던중 오류가 발생해서
@Builder(toBuilder = true) 도입했더니 해결됐네요 감사합니다.

답글 달기