te___ho
NO RULES
te___ho
전체 방문자
오늘
어제
  • 분류 전체보기 (93)
    • 주니어의 실무 개발일지 (2)
    • My project (29)
      • High Traffic Lab (5)
      • Nanaland in Jeju (8)
      • Univey (3)
      • inflearn_clone? (13)
    • Spring (19)
    • Network & CS (9)
    • Java (1)
    • Front_End (8)
    • Algorithm (11)
    • ETC (6)
    • Scribble (8)

인기 글

최근 글

티스토리

hELLO · Designed By 정상우.
te___ho

NO RULES

Spring Builder 패턴 제대로 알고 어노테이션 사용하기 (@AllArgsConstructor, @NoArgsConstructor가 필요한 이유)
Spring

Spring Builder 패턴 제대로 알고 어노테이션 사용하기 (@AllArgsConstructor, @NoArgsConstructor가 필요한 이유)

2024. 3. 12. 19:25

 처음 스프링으로 개발을 할 때는 생성자를 사용한 객체 생성만 사용했다. 여러 코드를 보다 보니 자연스레 builder 패턴에 대해 알게 되었고 지금은 Builder 패턴으로만 객체를 생성하는 것 같다. 이때 @NoArgsConstructor, @AllArgsConstructor를 @Builder와 함께 항상 작성했는데 원리를 정확하게 알고 싶어 공부할 겸 글을 작성한다.
(builder와 생성자 방법의 차이점, builder의 장점 등은 생략한다. )

 어노테이션 없이 builder 사용하기

 @Builder를 사용하기 전에 어노테이션 없이 builder 패턴을 사용하는 예제를 확인하겠다

public class User {
    private final String username; 
    private final String email; 
    private int age; 

    private User(Builder builder) { // 1. User의 생성자
        this.username = builder.username;
        this.email = builder.email;
        this.age = builder.age;
    }

    public static class Builder {
        private final String username; 
        private final String email; 
        private int age; 

        public Builder(String username, String email) { // 2. 이름과 이메일은 필수값으로 설정
            this.username = username;
            this.email = email;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public User build() { 3. builder 마무리하며 User 객체 생성
            if (username == null || email == null) { // 에러 상황 작성
                throw new IllegalStateException("username과 email은 필수값입니다.");
            }
            return new User(this);
        }
    }
}

 

1. User의 생성자에서 Builder로 설정 할 필드 값을 설정한다.
2. Builder 클래스를 만들어서 Builder 생성자에서 필수로 설정해야 하는 값이 있다면 설정한다. (필수로 설정해야 할 값이 없다면 Builder생성자는 작성하지 않아도 된다.)
3. 우리가 Builder 패턴을 사용할 때 마지막에 .build()로 끝내는 부분이다. 확인해 보니 내부에서 User의 생성자를 통해 User 객체를 return 한다.
 모든 엔티티마다 Builder 클래스를 작성하기는 너무 귀찮으니 @Builder 어노테이션이 존재한다.

 생성자에 @Builder 사용하기

 나는 보통 @Builder를 클래스에 붙였다.(다른 분들도 지금까지는 그렇게 작성하는 것 같았다.) 하지만 이번에 찾아보면서 생성자를 만들어 @Builder를 붙이는 것을 보았다. 보다 보니 이 방법도 중요한 것 같았다. 
 
 클래스에 @Builder를 사용할 경우 편리하게 모든 필드 값을 설정할 수 있고 필요 없는 것은 따로 값을 설정하지 않아도 된다. 여기서 문제점이 생기는데 엔티티에는 @Id를 사용한 db에 저장될 id값을 설정한다. 이 말은 id값도 개발자가 코드로 설정할 수 있게 되는데 이는 매우 위험하다. 보통은 빌더를 사용할 때 알아서 id값을 제외하고 코드를 작성하지만 id를 임의로 개발자가 설정할 수 있는 옵션이 열려있다는 것 자체가 위험하다.

id 값을 설정할 수 있다.

 이렇게 기능 자체를 막고 싶다면 생성자에 @Builder를 작성해주면 된다.

@Getter
@Entity
@NoArgsConstructor
@Table(name = "User")
public class User extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Column(name = "student_number")
    private String studentNumber;

    private String password;


    @Builder
    public User(String name, String studentNumber, String password) {
//	    Assert.hasText(name, "name은 필수"); 필수값을 설정하고 싶다면 작성
        this.name = name;
        this.studentNumber = studentNumber;
        this.password = password;

    }
}

생성자에서 builder로 값을 넣을 필드만 설정하였기에 id 설정이 불가능하다.

 클래스에 @Builder 사용하기 (@NoArgsConstructor, @AllArgsConstructor와 사용하는 이유)

 보통 많이 사용하는 방식은 class에 @NoArgsConstructor, @AllArgsConstructor, @Builder를 붙여 사용하는 방법이다.

클래스 레벨에 @Builder 작성

 이때 왜 생성자 관련 어노테이션을 작성해야하는지 알아봤다.

@Builder의 성질

 우선 @Builder의 성질을 파악해야 한다. 공식 문서에 따르면 @Builder는 클래스 단위에 작성할 경우 해당 클래스에 @~ArgsConstructor 주석을 추가하지 않은 경우 (생성자 관련 어노테이션) 자동으로 @AllArgsConstructor를 생성해서 빌더 패턴을 적용한다고 한다. 그렇다면 @NoArgsConstructor을 작성 안 하면 되는 게 아닌가???

 @NoArgsConstructor가 필요한 이유

 아쉽지만 그럴 수 없다. 보통 스프링 프로젝트를 사용할 때는 JPA를 사용한다. 여기서 문제가 발생하는데 Jpa가 객체를 생성해서 값을 주입할 때 Reflection API를 사용한다. 이때 Reflection API는 기본 생성자로 객체를 생성 한 후에 값을 매핑한다.
 그러므로 JPA를 사용하려면 엔티티로 사용되는 클래스에 @NoArgsConstructor는 필수이다.
 
(추가적인 내용으로 @NoArgsConstructor은 public or protected이어야 한다. 이유는 우리가 jpa의 지연 로딩을 사용할 때 필요하기 때문이다. 지연 로딩으로 객체에 접근할 경우 프록시 객체를 생성한다. 프록시 객체는 실제 엔티티 클래스를 상속받은 객체이므로 실제 엔티티 클래스의 기본 생성자를 호출해야 한다. -> private 사용하면 지연 로딩 사용 불가)

결론

 1. JPA를 사용하려면 엔티티로 사용될 클래스에 @NoArgsConstrcutor 필수

 2. Builder를 사용하려면 매개 변수가 있는 생성자가 필요한데 @Builder는 클래스에 생성자 관련 어노테이션이 있을 경우 추가적인 생성자를 생성하지 않음 
 
 3. 그러므로 @NoArgsConstructor, @AllArgsConstructor, @Builder를 함께 사용한다.
 
 4. (@AllArgsConstructor를 사용하기 때문에 빌더를 사용해 모든 필드의 값을 설정할 수 있다. 이것이 싫다면 접근이 가능한 필드 값을 매개변수로 갖는 생성자를 만들고 해당 생성자에 @Builder를 붙인다.)

 

728x90
반응형
저작자표시 (새창열림)

'Spring' 카테고리의 다른 글

@ModelAttribute, @SessionAttribute name 속성 차이  (0) 2023.09.26
[Spring] 나만 보는 스프링 사전(스프링 어노테이션)  (1) 2023.06.30
[Spring JPA] 영속성 컨텍스트 완벽 이해!, 연관 관계 주인 mapping (cascade와 차이?)  (0) 2023.03.30
[Spring] JPA란? (간단정리)  (0) 2023.02.04
[Spring] @JoinColumn & 연관관계 맵핑 @Mappedby (외래키 주인? 연관관계 주인?)  (2) 2023.01.13

    티스토리툴바