-
[Spring boot] NoArgsConstructor에 Protected를 쓰는 이유프로젝트/아카이뷰 2024. 1. 18. 16:26
NoArgsConstructor에 Protected를 쓰는 이유가 왜 궁금했을까❓
ArchiVIEW 프로젝트에서 JPA Entity를 설정하기 위해 공부를 하다보니 대부분의 블로그에서 NoArgsConstructor에 Protected 옵션을 주고 사용하고 있었다. 단순히 코드를 따라치기보다는 이해하고 사용하기 위해 학습해보려고 한다.
NoArgsConstructor(AccessLevel.PROTECTED)를 왜 사용하는 걸까?
일반적으로 객체를 생성하는 방법에는 3가지 방법이 존재한다.
- 기본 생성자를 통해 객체를 생성하고 Setter를 통해 값을 주입하는 방법
- 각 매개변수를 가지는 생성자를 통해 객체의 생성과 초기화를 동시에 하는 방법
- 정적 팩토리 메소드 / 빌더 패턴을 통해 객체 생성과 초기화를 동시에 하는 방법
첫 번째의 경우 Setter를 통해서 언제 어디서든 객체의 값을 변경할 수 있기 때문에 객체의 일관성이 무너지고 개발자의 의도를 파악하기 힘들어서 지양하는 것이 좋다.
두 번째의 경우 여러 개의 생성자를 만들어서 관리해야 하며 객체를 생성할 때 어떤 필드에 값을 넣는지 협업이나 개발자 측면에서 봤을 때 이해하기 힘들 것이다.
따라서, 세 번째 방법을 사용하는 것을 지향하곤 한다. 이러한 객체 생성이랑 JPA Entity와 무슨 연관이 있는 것일까?
JPA는 지연 로딩을 할 때, 프록시 객체를 사용하는데 해당 객체의 경우 실제 객체의 참조 변수를 가지고 있어야 하기 때문에 기본 생성자의 접근 권한을 private으로 설정하면 super를 호출할 수 없어 public 이나 protected로 설정해야 한다.
하지만, public으로 생성할 경우 무분별한 객체 생성과 Setter를 통해 값 초기화가 이뤄지기 때문에 객체의 일관성이 무너질 수 있다는 문제점이 존재한다.
이러한 이유에서 private와 public이 아닌 protected를 사용하는 것이다. 그리고 객체를 생성하기 위해서는 생성자를 직접 만들어줘야 하므로 세 번째 방법 중 하나인 빌더 패턴을 사용해서 구현해보았다. (추후 정적 팩토리 메소드와 빌더 패턴의 차이점을 비교할 예정)
@Builder 활용
- JPA Entity class 위에 @Builder를 설정하면 에러가 발생하게 된다.
- @Builder는 생성자가 없는 경우에는 모든 멤버 변수를 파라미터로 받는 기본 생성자를 생성하지만 생성자가 존재하는 경우에는 따로 생성자를 생성하지 않는다.
- 이후, 아래 코드와 같이 모든 멤버 변수를 설정할 수 있는 Builder Class를 생성하게 된다.
- 이 과정에서 @NoArgsConstructor(access = AccessLevel.PROTECTED)에 의해 생성된 생성자와 @Builder가 만들어낸 build() 메소드의 생성자와 일치하지 않아 에러가 발생하게 된다.
@NoArgsConstructor(access = AccessLevel.PROTECTED) @Builder public class User implements Persistable<String> { @Id @Column(name = "id", length = 16) private String id; ... // @NoArgsConstructor(access = AccessLevel.PROTECTED)로 생성된 생성자 protected User() {} public static User.UserBuilder builder() { return new User.UserBuilder(); } public static class UserBuilder { private String id; UserBuilder() { } public User.UserBuilder name(String id) { this.name = id; return this; } public User build() { /// 일치하는 생성자 X return new User(this.id, this.name, this.email, ... ); } } }
위 문제를 해결하기 위한 방법은 두 가지 존재한다.
1. @AllArgsConstructor
- 단순히 모든 멤버 변수를 받는 생성자를 만들어주면 된다.
@NoArgsConstructor(access = AccessLevel.PROTECTED) @Builder @AllArgsConstructor public class User implements Persistable<String> { @Id @Column(name = "id", length = 16) private String id; ... }
2. 생성자에 @Builder 설정
- 필요한 생성자를 만들고 해당 생성자 위에 @Builder를 설정하면 의미있는 객체만 생성할 수 있게 된다.
@NoArgsConstructor(access = AccessLevel.PROTECTED) @Builder public class User implements Persistable<String> { @Id @Column(name = "id", length = 16) private String id; ... @Builder public User(String id, ... ) { this.id = id; ... } }
'프로젝트 > 아카이뷰' 카테고리의 다른 글
[Spring boot] Query DSL (0) 2024.01.20 [Spring boot] JPA save() & Dirty Checking (0) 2024.01.19 [Spring boot] Controller Payload 유효성 검증 (0) 2024.01.17 [Spring boot] Service와 ServiceImpl의 분리 (0) 2024.01.16 [Spring boot] 유저 인증 처리 (0) 2024.01.15