Spring/JPA

JPA 객체 생성 방법

묠니르묘묘 2022. 3. 10. 01:58

객체를 생성할 때는 다음 3가지 방법 중 사용하게 된다.

  • 생성자
  • 정적 팩토리 메서드
  • Builder 패턴

엔티티 상황에 따라서 이 중 한가지를 선택하고, 파라미터에 객체 생성에 필요한 데이터를 다 넘기는 방법을 사용한다.

정적 팩토리 메서드와 Builder패턴을 사용할 때는 생성자를 private 처리를 하는데 JPA가 사용을 해야한다면 protected로 처리한다.

객체 생성이 간단하면 단순 생성자를 사용하지만, 의미가 있거나 복잡하다면 나머지 방법을 사용하는 것이 좋다.

 

엔티티를 만들 때는 외부에서 값을 쉽게 변경할 수 없게 @Setter 를 사용하지 않는다.

Setter는 의도가 분명치 않고, 변경하면 안되는 중요한 값임에도 변경가능한 값으로 착각할 수 있다. (안전성 보장x)

그러면 어떻게 값을 변경해야할지 당황스러운데 이 때는 Setter와 같은 기능을 하는 메서드를 만들면 된다.

setName으로 보통 만들어지는데 이렇게 하지말고 nameChange 같이 의미있는 이름인 메서드로 만들면 되겠다.

또 다른 예시로는 회원의 레벨이 오른다면 setLevel 보다는 levelUp 이라는 이름이 좋다는 것이다.

그렇다고 무조건 setter를 사용하지 않는게 아니라 쉽게 풀리지 않는 일부분에는 사용해도 좋다.

(e.g. 양방향 연관관계일 때 억지로 푸는것 보다 setter를 사용하는게 편할 때도 있다.)

 

 

 

1. 생성자

기본 생성자

단순하게 만들 수 있는 생성자이다.

 

 

2. 정적 팩토리 메서드

정적 팩토리 메서드

여기서 Setter를 쓰지 않는다면 아래와 같이 좀 더 의미있는 메서드로 변경할 수 있다.

setter 메서드를 고친 정적 팩토리 메서드

하지만 여기서 의문점이 든다. 외부에서 생성할 수 없는 private 생성자를 생성하여 값을 변경하여 반환한다?

이런 상황에서는 사실상 객체를 생성할 때 setter 메서드를 사용한 거나 다름이 없다.

Member를 생성할 때 id와 name값을 파라미터로 넘겨줘서 3가지 생성 방법중 하나로 생성하면 setter 메서드를 제거할 수 있다.

즉, 생성 시점에서 전부 끝낼 수 있는 것이다.

setter를 제거한 정적 팩토리 메서드1
setter를 제거한 정적 팩토리 메서드2

 

 

 

3. Builder 패턴

Builder 패턴

 

1번 생성자와 비슷해보이는데 이 Builder 패턴은 왜 쓰는 것인가?

 

보통은 1번 생성자처럼 많이 쓴다. 하지만 매개변수가 많아질수록 힘들어지게 된다.

이럴 경우 점층적 생성자 패턴(Telescoping Constructor Pattern)을 쓰게 된다.

예를 들면 필수 매개변수 id, name와 선택 매개변수 city, street가 있다면

점층적 생성자 패턴

이렇게 선택 매개변수가 없는 생성자부터 선택 매개변수를 전부 받는 생성자까지 늘려나가는 방식을 점층적 생성자 패턴이라 한다.

이 경우 객체를 생성하고 변경하지 않는 장점이 있지만,

street을 넣기위해 city도 넣어줘야하는 불편함이 생긴다. 즉, 원치 않는 매개변수까지 값을 지정해야한다.

거기다가 매개변수에 따라 생성자가 엄청 늘어나게 된다.

 

이럴 때 다른 디자인 패턴인 자바빈즈 패턴(JavaBeans Pattern)을 생각할 수 있다.

이것은 매개변수가 없는 생성자로 객체를 만든 후 Setter 메서드로 원하는 매개변수 값을 설정하는 방식이다.

자바빈즈 패턴

정적 팩토리 메서드에서도 말했듯이 이 경우 인자의 의미를 파악하기 쉬워지고 여러개의 생성자를 만들지 않아도 된다.

하지만 함수 호출 한번으로 객체 생성을 끝내지 못하고 여러번 메서드를 호출해야한다. 따라서 객체 일관성(Consistency)이 깨진다.

그러므로 불변(immutable) 객체를 만들 수 없다는 큰 단점이 생긴다.

 

위 두 가지 패턴의 단점을 보완한 것이 빌더(Builder) 패턴이다.

점층적 생성자 패턴의 안전성 + 자바빈즈 패턴의 가독성을 합친 빌더 패턴이다.

필수 매개변수로 생성자를 호출해 빌더 객체를 얻은 후, 빌더 객체가 제공하는 일종의 Setter 메서드들로 원하는 선택 매개변수를 설정한다.

마지막으로 build() 메서드를 호출해서 우리가 원하는 객체를 얻는다.

빌더 패턴

이렇게 Member 클래스 안에 정적 멤버 클래스로 Builder 클래스가 있는 구조이다.

Builder 클래스는 필수 매개변수를 가진 생성자가 있다.

그리고 Builder의 Setter 메서드들은 빌더 자신을 반환하기에 연쇄적으로 호출이 가능하다.

빌더 호출 방법

빌더 패턴의 장점

점층적 생성자 패턴의 안전성 + 자바빈즈 패턴의 가독성

 

빌더 패턴의 단점

객체를 만드려면 빌더를 만들어야한다.

빌더 생성 비용이 크지는 않지만 민감한 상황에서는 문제가 될 수 있다. 왜냐하면 빌더 객체를 계속 생성하기 때문이다.

점층적 생성자 패턴보다는 코드가 장황해서 매개변수가 4개 이상은 되어야 값어치를 한다.

 

 

이러한 빌더 패턴을 Lombok(롬복)에서 @Builder 어노테이션으로 지원한다.

@Builder 패턴 - 클래스

그러면 저 장황한 코드가 이렇게 작게 써지게 된다.

이 어노테이션은 class와 생성자 위치에 따라 차이점이 조금 있다.

지금 위 코드처럼 class 위치에 쓴다면 @AllArgsConstructor를 붙인 것 같은 효과를 본다.

@AllArgsConstructor는 모든 필드에 대한 매개변수를 받는 기본 생성자를 만든다는 것이다.

즉, id, name, city, street을 매개변수로 한 생성자가 생성되는 것이다.

만약 id가 자동으로 숫자가 매겨지는 필드라면 좋지않으므로 매개변수를 최소화해주는 것이 좋다.

@Builder 패턴 - 생성자

@NoArgsConstructor(access = AccessLevel.PROTECTED)기본 생성자 접근 제한자를 PROTECTED로 바꿔준다.

외부에서 개발자는 직접 호출 못하지만 JPA는 접근이 가능하다.

최종적으로 위와같이 기본 생성자를 제한해주고, Class위가 아닌 생성자에 @Builder를 붙여주는것이 좋다.

@Builder 속성인 builderMethodName을 지정하여 해당 Builder의 이름을 부여하여 쓸 수 있다.

@Builder 패턴 - 생성자 호출

 

 

 

3가지 방법을 봤는데 정적 팩토리 메서드 안에서는 생성자를 생성하여 반환해주고 있다.

이 때 정적 팩토리 메서드 없이 생성자 또는 Builder 패턴을 통해서 외부에서 바로 객체 생성을 해줘도 된다.

여기서 중요한 것은 생성자에 파라미터를 넘겨서 변경이 필요없는 객체를 생성하는 것이 핵심이다.

생성 이후 변경할 필요가 없는데 setter를 외부에 노출시키면 의도가 분명치 않아 호출해도 되는지 고민된다.