프로젝트/게시판 프로젝트

@Builder 제대로 알고 사용해보기

Junuuu 2022. 4. 30. 02:27
반응형

Lombok의 @Builder를 사용하게 되면 빌더 패턴을 매우 간단하게 적용할 수 있습니다.

 

빌더 패턴이란?

빌더 패턴이란 인스턴스(객체)를 생성할 때 생성자만을 통해서 생성하는데 어려움이 있기 때문에 고안된 생성 패턴입니다.

 

예를 들어 클래스에 생성자 인자가 많으면 각 인자들이 어떠한 값을 나타내는지 알기 어렵기 때문입니다.

 

빌더 패턴에 대해서 잘 모르신다면 다음 글을 보고 오시면 좋을 것 같습니다.

https://junuuu.tistory.com/247?category=968252 

 

[Java] 빌더 패턴

빌더 패턴이란? 빌더 패턴은 인스턴스를 생성할 때 생성자(Constructor)만을 통해서 생성하는데 어려움이 있어서 고안된 패턴입니다. 클래스에 생성자 인자가 많다면 어떠한 인자가 어떠한 값을 나

junuuu.tistory.com

 

LombokTest라는 클래스를 만들고 @Builder를 적용해보겠습니다.

정말 간단하게 다음과 같은 코드를 작성해 보았습니다.

@Entity
@Builder
public class LombokTest {
    @Id @GeneratedValue
    Long lombokTest_id;

}

 

LombokTest 클래스에 PK를 잡아주고 @Builder를 적용해 보았습니다.

그러면 다음과 같은 컴파일 에러가 발생합니다.

컴파일 에러 발생

Class 'LombokTest' Sholud have [public, protected] no-arg constructor

@Entity만 사용하는 경우 에러가 발생하지 않고, @Builder만 사용하는 경우에도 에러가 발생하지 않습니다.

하지만 둘을 같이 사용하는 경우에는 컴파일 에러가 발생하게 됩니다.

 

음.. 일단 기본 생성자가 없다고 하니까 만들어 보겠습니다.

@NoArgsConstructor는 파라미터가 존재하지 않는 기본 생성자를 생성해주는 어노테이션입니다.

@Entity
@Builder
@NoArgsConstructor
public class LombokTest {
    @Id @GeneratedValue
    Long lombokTest_id;

}

여전히 에러가 발생합니다.

컴파일 에러 발생

Lombok @Builder needs a proper constructor for this class

이번에는 @Builder를 사용하려면 클래스에 적절한 생성자가 있어야 한다고 합니다.

@AllArgsConstructor를 추가하여 해결해보겠습니다.

@AllArgsConstructor 어노테이션은 모든 필드 값을 파라미터로 받는 생성자를 만들어줍니다.

 

드디어 모든 것이 해결되었습니다

더 이상 에러를 뱉지 않음

왜 그럴까요?!!

둘의 존재 자체가 양립하기 때문입니다.

 

JPA의 Entity객체에는 기본 생성자가 있어야 합니다.

 

@Builder를 사용하면 생성자의 유무에 따라 아래와 같이 동작합니다.

- 생성자가 없는 경우 : 모든 멤버 변수를 파라미터로 받는 기본 생성자 생성(기본 생성자는 사라짐)

- 생성자가 있을 경우 : 따로 생성자 생성 X

 

즉 @Entity와 @Builder를 같이 사용하는 순간 @Entity는 기본 생성자가 필요한데 @Builder 어노테이션 때문에 기본 생성자가 사라져서 class Sholud have no-arg constructor라는 기본 생성자가 필요하다는 컴파일 에러가 나오게 된 것입니다.

 

이후에 @NoArgsConstructor 어노테이션을 명시해주면 기본 생성자가 생기기 때문에 생성자가 존재하므로 @Builder에서 따로 생성자가 생기지 않습니다.

여기에서  클래스에 적절한 생성자가 필요하다고 컴파일 에러가 나오는 이유입니다.

 

따라서 @AllArgsConstructor 어노테이션을 통해 모든 필드 값을 파라미터로 받는 생성자를 만들어주면 모든 것이 해결됩니다!

 

 

@Entity는 기본 생성자가 왜 필요할까요?

Spring Data JPA가 동적으로 객체 생성 시 Reflection API를 활용하기 때문입니다.

 

우선 java Reflection에 대해서 조금 알아야 하는데 간단하게 소개하자면 구체적인 클래스 타입을 알지 못해도 그 클래스의 메서드, 타입 변수에 접근할 수 있도록 해주는 API입니다.

 

하지만 이때 가져올 수 없는 정보 중 하나가 생성자의 인자 정보이기 때문에 기본 생성자가 꼭 필요합니다.

 

@Builder는 모든 멤버 변수를 파라미터로 받는 기본 생성자가 왜 필요할까요?

빌더 패턴에 대해 떠올려보면 좋습니다.

마지막에 build()를 호출하면서 모든 파라미터를 받는 생성자로 객체를 build 하는 과정이 필요합니다.

이때 알맞은 생성자를 찾을 수 없게 되므로 당연하게도 모든 멤버 변수를 파라미터로 받는 기본 생성자가 필요합니다.

build() 메서드 활용 예시

 

한발 더 나아가서

NoargsConstructor(AccessLevel.PROTECTED)를 사용하자

 

기본 생성자의 접근 제어를 PROTECTED로 설정해놓게 되면 무분별한 객체 생성에 대해 한번 더 체크할 수 있는 수단이 되기 때문입니다.

 

즉, 객체 생성 시 안정성을 어느 정도 보장받을 수 있습니다.

 

어 그러면 왜 PRIVATE으로 사용하지 않나요?

만약 AccessLevel의 PRIVATE로 설정한다면 다음과 같은 에러가 발생합니다.

Class 'LombokTest' should have [public, protected] no-arg constructor 

아까 봤던 에러네요..

public 또는 protected만 사용하라고 합니다.

이 또한 리플렉션과 관련 있어 보입니다.

기본 생성자가 PRIVATE으로 막히게 되면 외부에서 접근할 수 없기 때문에 제일 안전한 protected를 사용합니다.

 

@AllArgsConstructor(access = AccessLevel.PRIVATE)를 사용하자

 

@AllArgsConstructor는 AccessLevel을 PRIVATE으로 설정해도 문제가 없기 때문에 PRIVATE로 설정함으로써 외부에서 접근할 수 없게 됩니다.

 

@Builder. default와 @NonNull

특정 필드를 특정 값으로 초기화하고 싶다면 @Builder.Default를 사용하면 됩니다.

특정 필드에 Null을 허용하고 싶지 않다면 @NonNull을 사용하면 됩니다.

 

@NonNull의 아쉬운점은 컴파일 시점에 에러를 뱉어주지 않습니다.

@NoArgsConstructor
@Entity
@ToString
@Builder
@AllArgsConstructor
public class LombokTest {
    @Id
    @GeneratedValue
    Long lombokTest_id;

    @NonNull String 필수1;
    @NonNull String 필수2;
    //@NonNull String 필수3;
    @Builder.Default
    String 기본값 = "기본값입니다";

    public static void main(String[] args) {
        LombokTest lombokTest = LombokTest.builder()
		.필수1("")
		.필수2("필수2")
		.build();
        System.out.println(lombokTest);
    }
}

결과

LombokTest(lombokTest_id=null, 필수 1=, 필수 2=필수 2, 기본값=기본값입니다)

 

여기에서 만약 필수3의 주석을 풀면 어떻게 될까요?!

NPE 발생

컴파일 타임에서는 해결해주지 않고 실행 시에 런타임으로 NPE를 발생시킵니다.

 

@Builder를 클래스 선언부에 @Builder를 사용하지 말자 

 

클래스 선언부에 @Builder 사용 예시

@Builder
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;
}

생성자에 @Builder 사용 예시

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;

    @Builder
    public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize = servingSize;
        this.servings = servings;
        // ...
    }
}

@Builder를 클래스에 달아주면 @AllArgsConstructor도 같이 달아주는 것과 같기 때문에 바람직하지 않습니다.

가급적 직접 만든 생성자에 달아주는 것이 더 좋습니다.

 

결론

빌더 패턴을 쉽게 적용할 수 있는 @Builder에 대해서 알아보았습니다.

하지만 @NonNull 어노테이션이 컴파일 타임에서 에러를 발생시키지 않는 것이 조금 아쉽습니다.

경우에 따라 직접 빌더패턴을 작성해서 사용하는 것도 생각해 봐야 할 것 같습니다.

 

 

 

출처

https://cobbybb.tistory.com/14

 

@NoargsConstructor(AccessLevel.PROTECTED) 와 @Builder

@NoargsConstructor(AccessLevel.PROTECTED) 와 @Builder를 함께 사용할때 주의할 점에 대해서 서술합니다. "왜" 안되는지와 "왜" 이렇게 해결 할 수 있는지에 대해 집중하여 서술합니다. 1. 왜 NoargsConstructor..

cobbybb.tistory.com

https://velog.io/@yebali/Spring-JPA%EC%97%90-%EA%B8%B0%EB%B3%B8-%EC%83%9D%EC%84%B1%EC%9E%90%EA%B0%80-%ED%95%84%EC%9A%94%ED%95%9C-%EC%9D%B4%EC%9C%A0

 

Spring, JPA에 기본 생성자가 필요한 이유

인프런에서 김영한님의 강의를 들으면서 “JPA의 Entity객체에는 기본 생성자가 있어야 한다” 라는 말을 들은적이 있다.왜 그런 걸까? 알아보도록 하자.위의 이유를 알기 위해서는 먼저 java Reflect

velog.io

 

https://velog.io/@njhyuk/Mybatis-Entity%EC%97%90-Builder-%EB%A5%BC-%EC%82%AC%EC%9A%A9-%ED%96%88%EC%9D%84%EB%95%8C-%EC%98%A4%EB%A5%98

 

Mybatis Entity에 @Builder 를 사용 했을때 오류

엔티티를 DB에서 조회하여 생성하는 것이 아닌 직접 생성할 일이 생김컬럼이 너무 많아 다음과 같이 @Builder를 사용 하기로 결정직접 Builder 로 생성하여 개발한 기능은 정상적으로 동작 하였음그

velog.io

https://erjuer.tistory.com/106

 

[JPA] Entity Class의 @NoargsConstructor (access = AccessLevel.PROTECTED)

실무에서 JPA를 활용하다보면 Entity 생성시 @NoargsConstructor (access = AccessLevel.PROTECTED) 이라는 Annotation을 붙여서 개발을 하게 된다. 이에 조금 더 정확히 이해하고자 이번 블로그 글로 언급하고자..

erjuer.tistory.com

https://velog.io/@hsbang_thom/Lombok-Builder.Default

 

[Lombok] @Builder.Default

Lombok에서 제공하는 이 어노테이션은 생성자 인자를 메서드 형식으로 명시적으로 대입하여 생성자를 호출할 수 있게 해준다.

velog.io

https://johngrib.github.io/wiki/pattern/builder/#%EC%A3%BC%EC%9D%98%ED%95%A0-%EC%A0%90-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%84%A0%EC%96%B8%EB%B6%80%EC%97%90-builder%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80-%EB%A7%90-%EA%B2%83

 

빌더 패턴(Builder Pattern)

객체의 생성 방법과 표현 방법을 분리한다

johngrib.github.io