영호
[JPA] 상속관계 사용 시 주의점 본문
들어가면서,
우테코에서 프로젝트를 진행하면서 엔티티 상속 구조를 JOINED 전략을 사용한 적이 있었는데, 이 과정에서 경험한 상속 구조 사용 시 주의점에 대해 간략하게 정리할 예정이다.
상속 구조를 적용한 상황
우리 서비스는 2종류의 회원이 있다.
- 전화번호를 통해 가입한 임시회원
- 실제 우리 서비스에 접속해 회원가입을 한 정식 회원
그리고 임시회원인 상태에서도 여러 쿠폰을 발급 받을 수 있다.
결론
결론부터 빠르게 정리하자면 abstract 클래스의 dtype 을 변경할 일이 있을 때는 사용하기 부적절한 것 같다.
위에 작성한 dtype 변경 상황을 예로 들자면, 임시회원 상태에서 서비스에 접속해 회원가입 하면 해당 고객은 정식 회원 타입으로 변경해야 한다. 이 과정에서 customer 테이블의 dtype 을 수정해야 하는데, 이를 위해서는 자바 코드가 아닌 sql 을 직접 작성해야 한다.
아래 예시를 통해 구체적으로 살펴볼 예정이다.
처음 엔티티 구조
처음 이러한 구조를 택한 이유는 하나의 단일 테이블에 Temporary, Register 에 필요한 컬럼을 몰아넣으면 null 값이 존재하기 때문에 이를 피하기 위함이 가장 컸다.
그리고 JPA의 상속에 대해 잘 모르는 상태에서 dtype 값을 자바 코드를 통해 변경할 수 있다고 생각했었다.
문제상황
임시회원인 상태에서 정식회원으로 가입 이후 임시회원의 데이터를 정식으로 가입한 Customer 에 맞게 연동하는 과정에서 문제가 발생했다.
우리 서비스는 쿠폰 관련 서비스라 임시회원 상태에서 여러 쿠폰을 발급 받을 수 있다. 그래서 정식회원 가입 시 임시회원 때 받은 쿠폰 데이터를 그대로 정식회원으로 옮기는 작업이 필요하다.
이 과정에서 우리 팀은 다양한 시도를 했지만, 결국 상속구조를 제거하는 방향으로 엔티티 구조를 수정하기로 결정했다.
시도1 (fk 수정) 채택 X
임시회원 상태일 때 발급받은 Coupon 의 fk 를 새롭게 가입한 회원의 id 로 업데이트 한다.
문제점
즉, Coupon 에 있는 fk 값들을 우리가 조절하는 것이다. 이 방법을 선택하지 않은 이유는 아래와 같다.
- 개발자의 실수로 fk 를 업데이트 하는 로직을 잘못 짜면 데이터 무결성에 문제가 생길 수 있다.
- fk 수정 시 관련된 index 데이터를 저장하는 구조가 바뀌기 때문에 mysql 내부적으로 추가적인 작업이 벌어질 것으로 예상했다. (mysql 은 인덱스 데이터를 정렬해서 저장하기 때문에 내부 값이 변경되면 전체적인 재정렬 작업이 발생한다.)
시도2 (단일 테이블 전략 + dtype 수정 sql 작성) 채택 X
JOINED 전략을 버리고 단일 테이블 전략을 고민해봤다. 이 방법은 테이블의 fk 값을 변경하지 않아도 된다는 장점이 있다.
그러나 dtype 을 수정하기 위한 별도의 sql 을 작성해야 한다는 단점이 있다. 더불어, JPA 영속성 컨텍스트의 쓰기 지연 저장소에 있는 쿼리가 발생하는 시점 중 하나가 jpql 을 실행할 때도 있기 때문에 dtype 을 수정하는 쿼리의 실행 시점도 신경써야 했다.
데이터 연동 흐름
이번 시도의 흐름은 아래와 같다.
- 정식 회원 가입한 새로운 customer 레코드가 생긴다.
- 이와 맞는 임시회원의 컬럼에 새롭게 가입한 Customer 의 정보를 업데이트한다.
- 수정된 임시회원의 dtype 을 register 로 변경하는 쿼리를 실행한다.
- 정식 회원 가입하면서 생긴 새로운 레코드를 제거한다.
3번과 4번의 실행 순서를 바꾸는게 영속성 컨텍스트의 쓰기 지연 저장소를 더 잘 활용하는 방법인 것 같다.
데이터 연동 흐름을 디비 테이블과 함께 살펴보면 아래와 같다.
처음 1번(정식 회원 가입 시 새로운 레코드 생성) 까지의 상황은 아래 그림과 같다.
이후 2, 3 번 작업을 수행하면 아래와 같이 변경된다. (정식 회원의 값을 임시 회원 레코드로 덮어쓰기)
이후 4번 작업을 실행하면 아래와 같다. (정식 회원 레코드 제거)
문제점
위에서 잠깐 언급했듯이 dtype 을 수정하기 위한 sql 쿼리를 별도로 작성해야 한다. 이것을 단점이라고 생각한 이유는 아래와 같다.
- 데이터 연동 과정에서 갑자기 sql 을 활용해 dtype 을 바꾸는 코드가 있다면 추후 유지보수성이 떨어질 수 있다. 왜냐하면 이 모든 상황을 아는 우리는 이해할 수 있겠지만 이 코드를 처음 본 사람은 이해하는데 조금의 시간이 걸릴 수도 있다.
- 그리고, dtype 은 상속구조를 사용할 때 JPA 가 해당 엔티티의 타입을 판별하는데 쓰이는 컬럼이다. 그런데 이 컬럼을 우리가 임의로 수정했을 때 발생할 영향을 예측하기 어려웠다.
이러한 이유로 인해 이 방법도 선택하지 않았다.
시도3 (상속 구조 제거) 채택 O
우리는 결국 상속 구조를 제거하고 CustomerType 이라는 enum 을 통해 Customer 를 구분하기로 했다. 이후 데이터 연동 작업은 시도2와 동일하게 가져가기로 했다.
이 방법을 통해 시도2 에서 단점으로 생각했던 dtype 을 변경하기 위해 sql 을 작성한다는 점을 보완할 수 있었다. 단순히 Customer 의 customerType 만 REGISTER 로 변경하면 JPA의 변경감지를 통해 Customer 의 타입을 손쉽게 바꿀 수 있기 때문이다.
마무리
프로젝트에서 상속 구조를 사용하면서 겪은 어려움과 이를 해결하기 위해 시도한 방법들을 정리해봤다.
이를 통해, 중복되는 컬럼이 있고, 비슷한 성격의 엔티티라고 해서 무작정 상속 구조를 쓰기보단 이러한 조건에 더해 엔티티 간 타입 변환이 있는지도 같이 고려해서 상속 구조를 적용 해야겠다는 생각을 하게 되었다.
혹시 잘못된 점 있으면 댓글로 알려주시면 감사하겠습니다~
'JPA' 카테고리의 다른 글
[JPA] 엔티티 구조 변경을 통한 로직 복잡성 낮추기, 쿼리 개선기 (2) | 2023.11.11 |
---|---|
[JPA] 엔티티 delete 시 발생하는 N+1 개선 (6) | 2023.10.04 |
[JPA] service 테스트 코드 개선하기 (3) | 2023.08.13 |
[JPA] 테스트에서 repository.save()해도 createdAt이 null일 때 (0) | 2023.08.06 |
[JPA] 다양한 기본 키 자동 생성 전략 (6) | 2023.07.02 |