JPA

[JPA] JPA Entity 생성자의 접근 제어자 with @Builder

kidmillionaire1998 2024. 4. 5. 14:52

개요 

아래 글에서는 lombok의 @Builder를 사용하여 JPA Entity 클래스를 구성하는 방법을 살펴 보았다. 

https://minjun98.tistory.com/103

 

[JPA] lombok @builder 위치 : Constructor vs Class

개요 해당 글에서는 다음과 같은 내용을 다룬다. - @builder의 동작 원리 - lombok의 @builder 어노테이션의 위치 선택 @builder 동작 원리 사용하려는 예시는 jpa entity 클래스를 예시로 한다. 사용하려는 Us

minjun98.tistory.com

- 오늘은 생성자의 접근 제어자를 어떻게 설정할 것인지에 대해 다룬다. 

 

고려 대상 및 기준 

선택 기준은 빌더 패턴을 적용하려는 목적과 관련이 깊으며 new 연산자를 사용하여 객체를 생성하는 대신, 최대한 lombok의 @Builder을 이용한 builder 패턴을 적용하여 인스턴스를 생성하고자한다. 빌더 패턴의 자세한 장점에 대한 설명은 해당 글에서는 생략한다. 

 

접근 제어자의 고려 대상은 다음과 같이 4가지이다. 

1. public 

- builder 패턴을 적용하더라도 생성자의 접근 제어자를 public으로 지정 시 builder 패턴을 사용하지 않고, 외부에서 new 연산자를 사용하여 객체를 생성하는 것을 막을 수 없다. 

- outer class의 객체의 생성은 inner builder class에서만 허용하는 것이 불가능해진다. (inner builder class의 build())

 

2. package private (default) 

 

- 같은 패키지 내에서 Entity 객체를 사용할 일이 없다고 판단하였다. 

 

 

-참고로, JPA Entity는 기본 생성자가 필요하므로 해당 케이스는 아니지만, 사용자가 어떤 생성자도 만들지 않았다면, lombok에서 자동으로 만들어주는 생성자가 package-private이다. 

https://projectlombok.org/api/lombok/Builder

If a class is annotated, then a private constructor is generated with all fields as arguments (as if @AllArgsConstructor(access = AccessLevel.PRIVATE) is present on the class), and it is as if this constructor has been annotated with @Builder instead.  

 

 

3. Protected 

- JPA Entity간의 상속 매핑을 적용하지 않는다면 적용할 필요가 없다. 

 

4. private 

- inner builder class에서만 해당 생성자를 호출 할 수 있게 허용하게 해주는 효과가 있다. 

- 외부에서 new 연산자를 통해 생성자 호출이 불가능하게 할 수 있다.  

 

- cf) effective java에서의 builder 패턴과 관련된 코드에서도 생성자를 private으로 설정하였다. 

https://github.com/WegraLee/effective-java-3e-source-code/blob/master/src/effectivejava/chapter2/item2/builder/NutritionFacts.java

public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // 필수 매개변수
        private final int servingSize;
        private final int servings;

        // 선택 매개변수 - 기본값으로 초기화한다.
        private int calories      = 0;
        private int fat           = 0;
        private int sodium        = 0;
        private int carbohydrate  = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings    = servings;
        }

        public Builder calories(int val)
        { calories = val;      return this; }
        public Builder fat(int val)
        { fat = val;           return this; }
        public Builder sodium(int val)
        { sodium = val;        return this; }
        public Builder carbohydrate(int val)
        { carbohydrate = val;  return this; }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) { //접근 제어자가 private이다! 
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
                .calories(100).sodium(35).carbohydrate(27).build();
    }
}

 

 

결론 및 적용 

- 애초에 생성자 대신에 builder pattern (정적 팩토리도 마찬가지)은 new 연산자를 사용하여 객체를 생성했을 때의 단점을 극복하기 위한 대안책이다. 

 

- 생성자를 외부에서 호출하지 않으려고 이러한 대안책을 사용하려 했는데 생성자 접근제어자를 넓게 설정하여 외부에서 호출 가능하게 한다면 이는 빌더 패턴을 쓰는 의미가 사라진다.  

 

[최종 코드] 

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE) //private하게 설정 
public class User {

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

    @NotNull
    @Column(unique = true)
    private String email;

    @NotNull
    private String snsType;

}

 

참고 

- https://github.com/WegraLee/effective-java-3e-source-code/blob/master/src/effectivejava/chapter2/item2/builder/NutritionFacts.java

 

effective-java-3e-source-code/src/effectivejava/chapter2/item2/builder/NutritionFacts.java at master · WegraLee/effective-java-

『이펙티브 자바, 3판』(인사이트, 2018). Contribute to WegraLee/effective-java-3e-source-code development by creating an account on GitHub.

github.com

 

- https://www.baeldung.com/java-private-constructors

 

- [Effective Java] 아이템 4. 인스턴스화를 막으려거든 private 생성자를 사용하라

 

- [Effective Java] 아이템15. 클래스와 멤버의 접근권한을 최소화해라

- [Effective Java] 아이템2. 매개변수가 많다면 빌더 패턴을 고려하라