Entity 개발을 하다가 문득 @NoArgsConstructor을 아무 생각없이 붙이고 있는 나 자신을 발견했다. 그러면서 @AllArgsConstructor과 @RequiredArgsConstructor을 잘 구분해서 알맞게 사용하고 있는 것인지 헷갈리기 시작했다...😶🌫️ 정리가 필요할 것 같아서 이렇게 글을 써본다 !
@NoArgsConstructor
JPA에서는 프록시를 생성을 위해서 기본 생성자를 반드시 하나를 생성해야한다.
사실 기본 생성자는 @Entity
어노테이션만 붙여도 자동으로 생성해주지만, @NoArgsConstructor
를 붙여주는 이유는 AccessLevel 옵션값을 부여해서 접근 제한을 하도록 해 기본 생성자의 무분별한 생성을 막아서 의도하지 않은 엔티티를 생성하는 것을 막을 수 있기 때문이다.
근데 보통 @NoArgsConstructor(access = AccessLevel.PROTECTED)
와 같이 접근 제한을 PROTECTED로 해주는데 그건 왜 그런걸까 ? 안전하게 PRIVATE으로 하면 안돼 ?
JPA에서 연관 관계에 있는 엔티티를 조회할 때 보통 지연 로딩(LAZY)으로 값을 조회하여 리소스 낭비를 줄인다. 이때 지연 로딩은 프록시 객체를 생성해서 엔티티 값을 참조할 수 있게 하는데 접근 제한이 PRIVATE이라면 이 프록시 객체를 생성할 수 없기 때문이다.
사용 시 고려해야 할 점
@Builder
와 @NoArgsConstructor
의 사용Builder 개념이 궁금하다면 다음 포스팅을 참고하자 !
@Builder
의 경우, 해당 어노테이션이 붙은 클래스에 생성자가 없는 경우 모든 멤버 변수를 파라미터로 받는 기본 생성자를 생성하고, 생성자가 있다면 따로 생성자를 생성하지 않는다.
이 때 @NoArgsConstructor
어노테이션이 붙어 있다면 기본 생성자가 이미 생성이 되는 것이므로, 따로 생성자를 생성하지 않는데 이렇게 되면 매개변수를 일치하게 받는 생성자가 없어 에러가 발생한다. 따라서 모든 필드를 파라미터로 가지는 @AllArgsConstructor
를 붙여주어 해결한다.
또 다른 방법으로는 직접 클래스 내에 생성자를 만들고, 클래스에 @Builder
를 붙여서 선언하는 것이 아니라 생성자에 붙여서 선언하는 방법이 있다. 코드로 설명하면 다음과 같다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String nickname;
@Column(nullable = false)
private String email;
@Column(nullable = false)
private String password;
@Builder
private User(String nickname, String email, String password) {
this.nickname = nickname;
this.email = email;
this.password = password;
}
}
사실 후자의 방법처럼 생성자를 직접 만들어서 생성자에 @Builder 어노테이션을 붙이는 것을 더 지향하는 편이다. 그 이유는 @AllArgsConstructor
의 사용은 되도록 지양하고, 생성자를 직접 선언해주고 필요에 따라 Builder 패턴을 사용할 것을 권장하기 때문이다. 자세한 건 아래에서 설명하겠다 !
클래스에 존재하는 모든 필드를 파라미터로 받는 생성자를 만들어주는 어노테이션이다.
굉장히 간단하게 생성자를 만들어주는 것 같아 오호 좋은데 ~? 할 수 있지만 지양 해야하는 어노테이션 중 하나이다.
가장 흔히 문제가 될 수 있는 케이스를 봐보자.
다음 코드를 보면 문제점을 확실히 알 수 있다 !
@AllArgsConstructor
public static class Member {
private String firstName;
private String lastName;
}
// 성이 남, 이름이 주혁이라면
Member boy = new Person("남", "주혁");
여기서 만일 두 멤버 변수, firstName과 lastName의 선언 순서가 바뀐다면 ?
@AllArgsConstructor
는 선언된 필드의 순서대로 생성자의 파라미터 순서를 정해 만들어주기 때문에 "주혁남"이 될 수 있다..
이런 문제를 미연에 방지하기 위해 다음과 같이 @builder 패턴으로 파라미터의 순서가 아닌 필드 명으로 값을 설정하도록 한다. 단순히 유연한 생성을 위해서만 builder를 사용하는 것이 아니라는 것을 알 수 있다 !
public static class Person {
private String firstName;
private String lastName;
@Builder
private Person(String firstName, String lastName){
this.firstName = firstName;
this.lastName = lastName;
}
}
// 필드 순서를 변경해도 한국식 이름이 만들어진다.
Person me = Person.builder().lastName("현수").firstName("권").build();
System.out.println(me);
그렇기 때문에 위에서 설명한 @AllArgsConstructor
과 @Builder
를 같이 쓰는 것보다, @NoArgsConstuctor
과 생성자를 직접 만들어 그 생성자에 @Builder
을 붙이는 전략을 권장하는 것이다 !
@RequiredConstructor
'Required' 즉, 꼭 필요한 객체의 변수를 인수로 받는 생성자를 구현해준다. 여기서 꼭 필요한 객체의 변수는 final 또는 @NotNull 어노테이션이 붙은 변수를 의미한다.
이 어노테이션과 함께 보면 좋을 개념은 바로 @Autowired
어노테이션이다.
@Autowired
을 사용해서 의존성을 주입해주는 것을 필드 주입이라고 한다. @Service
public class MemberService {
@Autowired private MemberRepository memberRepository;
/* 이하 생략 */
}
따라서 생성자 주입을 해주어야 하는데 이때 필드 객체에 final 키워드를 붙이면 컴파일 시점에 해당 필드를 주입하지 않을 시에 누락된 의존성 오류를 체크할 수 있다.
여기서 final 키워드와 지금 설명하고 있는 @RequiredConstructor
를 같이 쓰면 좋은 이유가 설명되는 것이다 !
@RequiredConstructor
가 final 변수를 위한 필수 필드를 정의하는 생성자를 대신 생성해주기 때문에 다음과 같이 코드를 작성하는 것이 바람직한 방법이다.
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final MemberRepository memberRepository;
public Long order(Long memberId, Long itemId) {
Member member = memberRepository.findById(memberId).get();
...
}
}
Spring 컨테이너는 생성자가 1개인 경우 @Autowired를 생략하면 자동으로 생성자 주입을 해준다. 위의 OrderService 클래스는 필수 필드를 가지는 @RequiredArgsConstructor
생성자가 1개 있으므로 @Autowired
를 생략하고 다음과 같은 코드로 생성자 주입을 해주는 것이 가능하다.
즉, 다른 클래스의 의존성이 주입되어야 하는 클래스 (예를 들면 Service나 Controller..) 는 위와 같이 final
키워드와 @RequiredArgsConstructor
을 함께 사용해주는 것이 좋다 !
💡 다음과 같이 Lombok을 이용해서 생성자를 쉽게 만들 수 있는 어노테이션에 대해 알아보았다.
그냥 무작정 어노테이션부터 붙이고 개발을 하는 것이 아니라, 현재 내가 설계하고 있는 클래스의 특징과 상황을 고려하여 (Entity인지 Service인지..등등) 어떤 부분을 주의해야하는지 판단하고, 알맞고 유연한 설계를 하는 것이 중요한 것 같다 !
멋진 글입니다