정확히는 JPA Entity를 만드는 것에 대한 글이다.
아래와 같은 클래스가 있다고 하자.
@Entity
@Getter
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long no;
private String title;
private String content;
private String writer;
@CreationTimestamp
private Timestamp createdAt;
@UpdateTimestamp
private Timestamp updatedAt;
}
나는 생성자로 객체를 만든 뒤 Setter를 사용하여 객체를 완성시키는 방법을 싫어한다. 도메인 모델의 상태를 변경하기 위해 Setter를 사용하는 자바 빈즈 방식을 사용하면 고통스러운 유지보수만 남을 뿐이다.
Setter를 사용하지 않고 객체를 완성시키려면 다음 방법 중 하나를 선택해야 한다.
객체 완성을 위한 Parameter를 받는 생성자
객체를 한 번에 만들 수 있다는 것은 장점이나, 이것도 별로다.
매개변수가 많아지면 개발자의 실수가 생기기 쉽다. 깃헙 코드 리뷰 시 가독성도 좋지 않다.
Static factory method
메서드 이름을 가진다는 장점은 있지만, 위의 단점을 가지고 있다.
Builder
일단 답은 빌더다. 나는 롬복을 아직 까지는 아주 좋아하기 때문에 롬복 기준으로 설명하겠다.
클래스 선언부에 @Builder를 달자.
롬복은 모든 파라미터를 받는 생성자를 자동으로 추가해준다. (하지만 개발자는 그 생성자를 사용하지 말아야 한다.)
아래와 같이 가독성이 좋으면서 한 번에 객체를 완성시키는 코드가 탄생한다. 그리고 DB에 저장한다.
Article article = Article.builder().title("title")
.content("content")
.writer("writer1").build();
repo.save(article);
이제 게시글을 조회해보자.
DB 데이터를 객체에 바인딩하기 위해서는 기본 생성자가 필요하다. (그렇지 않으면 InstantiationException이 발생한다.)
그 기본 생성자도 개발자가 사용하지 않을 것이기 때문에 최대한 사용 기회를 줄이기 위해 아래와 같이 사용할 것이다.
@NoArgsConstructor(access = AccessLevel.PROTECTED)
기본 생성자를 추가하는 순간 컴파일 에러가 발생하는데, 이유는 이 시점에서 위에서 자동 추가되는 모든 파라미터를 받는 생성자를 자동으로 추가해주지 않기 때문이다.
이제 @AllArgsConstructor를 추가해주면 컴파일 에러는 사라진다. 그런데 이렇게 하는 게 맞는 걸까? 이는 개발자가 명시적으로 모든 파라미터를 받는 생성자를 사용하겠다고 말하는 것과 같다. 아주 간단한 객체가 아닌라면, 모든 파라미터가 필요한 경우는 아마 없을 것이다.
그러면 어떻게 해야할까?
객체 생성에 필요한 파라미터만 명시한 생성자를 만들고 메서드 단에 @Builder를 선언한다.
@Builder
public Article(String title, String content, String writer) {
this.title = title;
this.content = content;
this.writer = writer;
}
전체 코드는 아래와 같다.
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long no;
private String title;
private String content;
private String writer;
@CreationTimestamp
private Timestamp createdAt;
@UpdateTimestamp
private Timestamp updatedAt;
@Builder
public Article(String title, String content, String writer) {
this.title = title;
this.content = content;
this.writer = writer;
}
}
여기까지 오는데도 참 많은 생각이 필요하다.
@Builder가 붙은 생성자는 DB 데이터를 바인딩할 때 쓰는 거라면 id, createdAt, updatedAt에 대한 설정은 없는데 어떻게 모든 값이 바인딩될까?
정답은 해당 생성자를 사용하지 않는다는 것이다.
JPA는 기본 생성자로 객체를 만들고 Reflection을 통해 값을 주입한다.
객체 상태 수정
이제 객체 상태를 수정하고 싶은 경우는 어떻게 해야할까?
가장 간단한 방법은 다시 Setter 방식을 사용하는 것이다. 다만 메서드명을 set 대신에 change 등으로 바꿔 써 Setter와는 다르다는 것을 명시하는 것이 중요하다.
public void changeTitle(String title) {
this.title = title;
}
참고
'Java' 카테고리의 다른 글
| Java > Local 개발 환경을 위한 애플리케이션에 TLS 설정 (keytool) (0) | 2025.03.19 |
|---|---|
| Java > Docker 컨테이너로 실행 시 bash: warning: setlocale: LC_ALL: cannot change locale (ko_KR.UTF-8) (0) | 2024.10.29 |