Entity 클래스에서는 setter 를 왜 지양 해야하는가?
Intro
Spring boot 에서 JPA 로 개발을 할때면 Entity Class 에서는 Setter 메서드를 생성하는 것이 바람직하지 않다고 흔히들 말한다.
바람직하지 않다고 하니 Setter 의 사용을 지양 하긴 하는데 “왜 지양하는가?” 에 대한 궁금증이 문득 들었다.
왜 지양해야하는가?
우선 Setter 를 무지성으로 사용하게 되면 발생하는 문제점을 살펴보자.
1. 사용한 의도를 쉽게 파악하기 어렵다.
1
2
3
4
5
Post post = new Post();
post.setId(1L);
post.setUserId("member1");
post.setTitle("제목입니다.");
post.setCont("내용입니다.");
상기 코드의 경우 게시글 정보 Entity 인 Post 클래스에 대해 Settet 메서드를 통해 값을 변경하는 코드이다. 코드만 딱 봤을땐 이게 생성하는 코드인지 특정 파라미터를 변경하는 코드인지 구분하기가 쉽지 않다. 물론 간단한 코드면 눈치껏 파악이 되지만 객체의 내부값이 많거나, 코드의 구조가 복잡하다면 한눈에 파악하기 어려울 것이다.
2. 일관성을 유지하기 어렵다.
1
2
3
4
5
6
public Post updatePost(Long id) {
Post post = findById(id);
post.setTitle("제목을 수정합니다.");
post.setCont("내용을 수정합니다,");
return post;
}
상기의 코드의 경우 게시글을 변경하는 메소드인데, public 으로 작성된 Setter 메서드를 통해 어디서든 접근이 가능하기에 의도치 않게 Post 의 값을 변경하는 경우가 발생하여 Post 객체의 일관성이 쉽게 무너지게 될 수 있다.
그렇다면 Setter 없이 Entity 데이터를 어떻게 수정 할 수 있나?
settet 의 경우 JPA Transaction 안에서 Entity 의 변경사항을 감지하여 Update 쿼리를 생성한다. 즉, setter 메소드는 update 기능을 수행한다.
여러곳에서 Entity 를 생성하여 setter 를 통해 update 를 수행한다면 복잡한 시스템일 경우 해당 update 쿼리의 출처를 파악하는 것을 대단히 피곤할 것이다.
그렇다면 어떤 방식으로 setter 를 대체할지 알아보자.
1. 사용한 의도나 의미를 알 수 있는 메서드를 작성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Getter
@Entity
public class Post {
private Long id;
private String userId;
private String title;
private String cont;
// ... 이하 생략
public void updatePost(Long id, String title, String cont) {
this.id = id;
this.title = title;
this.cont = cont;
}
}
post.updatePost(1L, "수정할 제목입니다.", "수정할 내용입니다.");
위와 같이 Entity 내부에 updatePost라는 메서드를 작성하여 사용한다면, setter 메소드를 작성하여 사용하는 것보다 행위의 의도를 한눈에 알기 쉽다.
따라서 setter를 public으로 열어두고 사용하는 것보다는, 별도로 변경이라는 의미가 담긴 메서드를 통해 update 처리를 객체지향스럽게 쓰는 게 좋다.
2. 생성자를 통해 값을 넣어 일관성을 유지하도록 하자. (feat. @Builder)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Getter
@Entity
public class Post {
private Long id;
private String userId;
private String title;
private String cont;
@Builder
public Post(Long id, String userId, String title, String cont) {
this.id = id;
this.userId = userId;
this.title = title;
this.cont = cont;
}
// ... 이하 생략
}
Post post = Post.builder()
.id(1L)
.userId("member1")
.title("제목입니다.")
.cont("내용입니다.")
.build();
setter를 배제하기 위해 여러 생성자를 작성할 수도 있는데, 위와 같이 lombok의 @Builder 애노테이션을 통해 많은 생성자를 사용할 필요 없이 setter의 사용을 줄일 수 있다. 이로 인해, 빌더 패턴을 통해 post 객체의 값을 세팅할 수 있게 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Post {
private Long id;
private String userId;
private String title;
private String cont;
protected Post(Long id, String userId, String title, String cont) {
this.id = id;
this.userId = userId;
this.title = title;
this.cont = cont;
}
}
그리고 위와 같이 생성자의 접근제어자를 protected로 선언하면 new Post() 작성이 불가하기 때문에 객체 자체의 일관성 유지력을 높일 수 있다.
그리고 lombok에서 제공하는 @NoArgsConstructor 애노테이션을 사용하여 더 편하게 작성할 수 있다. 위 코드에서는 access = AccessLevel.PROTECTED 옵션을 부여하여 무분별한 객체 생성에 대해 한번 더 체크할 수 있도록 하였다.
생성자를 private으로 선언하면 안되는 이유
private 접근제한자로 선언하게 된다면 JPA에서 오류를 발생시킨다. 이로 인해 JPA에서 프록시 객체를 만들어 해당 프록시 객체가 직접 만든 class 객체를 상속하기 때문에 public 이나 protected 까지 허용된다.
Outro
사실 혼자 개발한다면 크게 개의치 않을 일이라고 생각되어 지지만, 동료와 함께 개발하는 협업의 단계에서는 코드의 가독성을 최대한 높여 불필요한 코드 리딩시간을 줄이고 업무의 효율을 성을 높이는 방향이라는 점에 있어서 매우 참조 할 만한 내용이라는 생각이 들었다.