티스토리 뷰
Setter 를 지양하는 이유
아마 모두가 알고 있을것이다.
setter 를 사용(남용) 하다 보면 객체의 불변성, 무결성, 캡슐화가 깨지기 쉽다.
객체가 불변성을 가지면 상태가 변하지 않아 객체의 상태에 대한 예측 가능성이 높아진다. 객체가 불변하고 무결하면 여러 곳에서 동시에 접근했을때의 동시성 문제 부터로도 안전해진다.
캡슐화-> 엔티티는 비지니스 로직을 가진 도메인 객체이다. setter 를 이용하면 단순 dto 처럼 이용될 가능성이 생긴다.
그래서?
그래서 당시엔 프로젝트를 진행할때 쓰지 말라니깐 다른 방법들로 엔티티의 값을 업데이트 했다.
주로 사용한 방식은
1. dto에다 setter 를 두고 그 dto를 엔티티로 변경해 save 하기
2. 엔티티의 constructor 를 이용해 새로운 객체를 만들어 save하기
정도가 있었던것같다.
그 당시엔 배움의 열정에 그것들이 귀찮다는 생각보단 당연한 방법이라고 생각했다.
하지만 지금 코딩하며 내 스타일이 생기고 약간의 꼼수와 귀찮음이 더해져 다른 방법을 찾고 있었다.
entity에 setter 를 직접 만들긴 양심에 가책을 느껴 함수의 이름만 조금 바꾸고 안에 약간의 validation을 넣어 사용했다.
아래의 코드를
void setName(String name) {
this.name = name;
}
아래처럼 바꿔서 엔티티에 두고 사용했다는 뜻이다.
void updateName(String name) {
if(name.isEmpty()) {
throw new RuntimeException(-);
}
this.name = name;
}
이렇게 사용하다 보니 이게 과연 좋을 방식인가? 지금까진 아무문젠 없었지만 이게 아주 배척받는 방법은 아닐까? 의문이 들었다.
무엇이 좋은 방식일까?
1. update(newName)
+ setter 와 같은 기능을 할지언정 좀 더 명확하게 명시된 메서드임
+ jpa 를 이용시 더티체킹(변경감지)를 활용해 변경된 필드만 업데이트 할 수 있다.
+ 트랜잭션 범위 내에서 엔티티의 상태가 유지되어 성능상 유리하다.
- 객체가 불변이 아니다( 불변이 아니면 위에 setter 의 지양 이유에서 말한 이유들이 생길 수 있다)
- 변경해야할 필드가 많으면 그만큼 많은 메서드가 필요하다.
2. 새로운 객체 생성 후 저장 (업데이트)
+ 불변객체를 유지할 수 있다.
+ 일관성이 보장된다.
+jpa 변경감지를 감안하지 않아도 된다.
- 불필요한 쿼리가 발생할 가능성이 있다.
- 객체 전체를 새로 생성하다보니 불필요한 메모리 할당이 필요하다
- jpa 영속성 컨텍스트와의 연동이 어렵다
이렇게 적어놓고 보니 변경감지(dirty checking) 과 병합(merge) 방식의 차이 처럼 느껴지기도 해서
그 둘의 차이점도 한번 찾아보았다.
변경감지와 병합 방식의 차이
변경감지(dirty checking)
실행 흐름
- userRepository.findById(id) 로 엔티티 조회 → 영속 상태 유지됨
- user.updateName("newName") 호출 → 필드 값 변경 (setName과 유사하지만 의미 있는 메서드 제공)
- 트랜잭션 종료 시점에 변경 감지로 인해 UPDATE 쿼리 실행됨
실행되는 SQL
SELECT * FROM user WHERE id = ?; -- 엔티티 조회
UPDATE user SET name = ? WHERE id = ?; -- Dirty Checking을 통한 변경 감지
장단점
+ 변경된 필드만 업데이트 → 최적화된 쿼리 실행
+ 추가적인 insert/update 없이 최소한의 쿼리로 처리
+ JPA가 자동으로 관리 → 직관적이고 유지보수 용이
- 영속성 컨텍스트 내에서 관리되고 있어야 함
- 조회 후 변경하는 구조이므로, 기존 엔티티를 조회하는 SELECT 쿼리는 필요함
병합(Merge) 방식 (save()를 통한 새로운 객체 생성 후 저장)
실행 흐름
- new User()로 새로운 객체를 생성
- userRepository.save(updatedUser) 호출
- ID가 존재하면 SELECT로 기존 엔티티 조회 후, 필드 값 비교 → UPDATE 실행
- ID가 존재하지 않으면 INSERT 실행
실행되는 SQL
SELECT * FROM user WHERE id = ?; -- 병합할 기존 데이터 조회
UPDATE user SET name = ?, address = ? WHERE id = ?; -- 모든 필드 업데이트
모든 필드를 업데이트함! (변경된 필드만 반영하는 것이 아님)
장단점
+ 영속성 컨텍스트를 고려할 필요 없이 새 객체 생성 가능
+ 비영속 상태의 엔티티도 처리 가능
- 모든 필드가 업데이트됨 (불필요한 필드까지 포함될 가능성 높음)
- SELECT 후 UPDATE가 실행되므로 불필요한 쿼리 발생 가능
- 변경 감지 방식보다 성능이 떨어질 가능성이 높음
결론
ddd 같은 아키텍쳐를 따르고 있거나, jpa 없이 개발하는 경우엔 병합방식을 따르는게 맞지만
그것이 아니라면 굳이 setter 를 절대로 금기시 하며 더티체킹을 피하기 위해 새로운 객체를 이용해 업데이트 하는게 꼭 정답은 아닌듯하다.
gpt에 의하면 updateName 을 사용하는게 더 효율적이라고 한다.
'Java > JPA' 카테고리의 다른 글
JPA - @JoinColumn 과 @MappedBy 의 차이 (0) | 2024.03.25 |
---|