개요
아래 글에서는 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으로 설정하였다.
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;
}
참고
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. 매개변수가 많다면 빌더 패턴을 고려하라
'JPA' 카테고리의 다른 글
[JPA] Fetch join과 pagination (0) | 2024.08.12 |
---|---|
[JPA] JPA Entity 클래스에서의 @builder 위치 : Constructor vs Class (0) | 2024.04.05 |