본문 바로가기
SPRING

[Spring] @Setter vs @Builder

by 킹명주 2023. 2. 20.

Entity, Dto를 개발하다보면 @Setter를 써야할지 @Builder를 써야할지 고민된적이 있을 것이다. 그리고 강의나 블로그에 보면 Setter 사용을 지양하라고 되어있다. 

 

"그래 Setter 사용 안할게,, 근데 왜 사용하면 안돼?"

"..."

 

이처럼 이유에 대해 명확하게 정리한 곳이 없었다. 그래서 내가 개발을 진행하면서 개인적으로 느꼈던 이유에 대해 소개하고자 한다.


 이게 수정이야? 생성이야?

 

우선 코드로 설명을 하겠다.

@Entity
@Setter
@Getter
public class Temp {
    @Id @GeneratedValue
    private Long id;
    private String name;
}

해당 코드는 entity로 database의 table이다. 즉, 해당 테이블은 id와 name이라는 열을 가지고 있다.

@Service
public class TempService {
    public void update(Long id, String name){
        Temp temp=tempRepository.findById(id).orElse(null);
        temp.setName(name);
        tempRepository.save(temp);
    }
    public void create(String name){
        Temp temp=new Temp();
        temp.setName(name);
        tempRepository.save(temp);
    }
}

해당 코드는 service로 table에 대한 create, read, update, delete 로직을 작성한다. 여기서 메서드의 이름인 update와 create를 가리고 본다면 둘의 차이가 느껴지는가..? 뭐가 생성인지 수정인지 코드만 보고 알기 힘들다.

 

또한, Setter를 사용한 코드들은 .setName과 같이 수정이 매우 간단하기 때문에 사용자가 수정에 대해 개방적이라는 문제점을 가지고 있다. 이는 객체지향원리인 OCP(Open-Closed Principle)를 지키지 않은 것이다. OCP에 대해서 간단하게 설명하자면, 확장에 대해서는 열려있어야하고 수정에 대해서는 닫혀있어야 한다는 것이다.

 

이를 해결하기 위해 @Builder를 사용한 코드를 소개하겠다.

@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Temp {
    @Id @GeneratedValue
    private Long id;
    private String name;
    
    public void changeName(String name){
        this.name=name;
    }
}
@Service
public class TempService {
    public void update(Long id, String name){
        Temp temp=tempRepository.findById(id).orElse(null);
        temp.changeName(name);
        tempRepository.save(temp);
    }
    public void create(String name){
        Temp temp=Temp.builder()
                .name(name)
                .build();
        tempRepository.save(temp);
    }
}

위의 코드들을 보면 확실하게 Setter와 구별할 수 있을 것이다.

 

우선, 생성과 수정이 명확해졌다. 생성의 경우 Builder를 사용하기 때문에 코드만으로도 수정과 구별이 가능하다. 그리고 수정의 경우 entity class에서 changeName과 같이 함수를 지정하여 수정하고 있다. Setter를 사용했을 때보다 확실히 까다롭다. 이 말은 수정에 좀 더 닫혀있는 코드가 되었다는 뜻이다. 그러므로 OCP의 문제도 해결할 수 있다.

 

 

👀 심화학습 

Entity에 Builder를 사용하기 위해서 @NoArgsConstructor(access = AccessLevel.PROTECTED), @AllArgsConstructor 어노테이션을 추가한 것을 확인할 수 있다. 이에 대해 왜 추가하였는지 소개하겠다. 

 

일단 NoArgsConstructor에 대해 간단하게 소개하면 Temp temp=new Temp(); 와 같이 매개변수 입력을 받지 않는 기본 생성자를 자동으로 생성해주는 어노테이션이다.

그렇다면 AllArgsConstructor은 무엇일까? All이니까 Temp temp=new Temp("kingmj"); 와 같이 매개변수 입력을 받는 생성자들을 자동으로 생성해주는 어노테이션이다.

 

그래서 이것들을 왜 사용할까?

@Entity와 @Builder를 동시에 사용하기 때문에 해당 어노테이션들을 사용해야 한다. @Entity의 경우 매개변수가 없는 기본 생성자가 없다면 자동으로 만들어 준다. 그리고 @Builder의 경우 매개변수가 있는 생성자가 없다면 자동으로 만들어 준다.

 

만약, 둘 다 사용한다면 entity는 builder에서 자동으로 만든 생성자 때문에 생성자가 이미 만들어졌다고 판단하고 builder는 entity에서 만든 기본 생성자 때문에 생성자가 이미 만들어졌다고 판단한다. 

그런데 entity는 매개변수가 없는 생성자가 필요하고 builder의 경우 매개변수가 있는 생성자를 필요로 한다. 그래서 서로 충돌이 발생하는 것이다. 이를 방지하기 위해 @NoArgsConstructor, @AllArgsConstructor 어노테이션을 사용하여 entity와 builder의 생성자를 직접 지정해준다.

 

또한, @NoArgsConstructor(access = AccessLevel.PROTECTED) 에서 AccessLevel 옵션은 올바르지 않은 객체 생성에 대해 한번 더 체크할 수 있는 옵션이다. 예를 들어 User라는 Entity에 name, age, address 라는 열들이 있을 때 실수로 하나를 누락하여 객체를 생성한다면 IDE 단계에서 체크하여 올바르지 않은 객체 생성을 막을 수 있다.


결론

 

Setter 사용을 지양해야 하는 이유를 두 줄로 요약하자면

1. 수정, 생성의 구별이 힘들다. (코드 가독성 문제)

2. 수정에 개방적이다. (OCP 문제)

 

 

오늘은 Setter와 Builder의 차이점과 개인적으로 Setter 사용을 하지 않는 이유에 대해 소개했다. Setter와 Builder의 차이점에 대해 공부할 정도라면 이제 API 개발은 어려움 없이 진행하시는 분들이라고 생각한다. 그래서 이제는 작성한 코드가 잘 작성되었는지 아니면 🐶판인지 고민하고 있을 것이다.

 

많은 동료들의 코드를 보아도 정답은 없다. 원칙은 지켜도 자신의 코딩 스타일이 묻어나있다 ㅋㅋㅋㅋ 나같은 경우에는 C++로 작성한 코드 같다는 말을 많이 들었다. 익숙함은 어쩔수없나보다.. 그래도 개선하고 있으니 다행 ^ 3^

'SPRING' 카테고리의 다른 글

[Spring] CORS 쉽게 처리하기  (4) 2023.02.22
[Spring] AOP 맛보기  (7) 2023.02.21
[Spring] Exception Custom  (0) 2023.02.13
[Spring] Google Login API 사용하기  (12) 2022.10.10
[Spring] JPA CASCADE  (2) 2022.08.31