김영한님의 JPA 책에서 상속 관련된 내용 읽다가 JPA에 직접 등록하는 것이 아닌 JpaRepository를 이용하는 경우 어떨까라는 궁금증에 글을 써봅니다.
목차
개요
김영한님의 JPA 책 중에 코드에서의 객체와 데이터베이스 사이의 패러다임 불균형을 JPA가 해결해준다라는 내용이 있습니다. 그에 관한 예시 중 상속에 관한 이야기가 있었습니다. '상속은 객체의 대표적인 속성인데 데이터베이스로 표현하려면 불편해진다. 이러한것들을 JPA가 해결해준다.' 라는 내용이었습니다. 이 때 들어준 예시 코드가 JPA를 이용하여 직접 등록해주는 방식 등 정석적인 방법처럼 보였는데 현재 저는 데이터베이스에 저장할 객체는 @Entity를 사용해주고 JpaRepository를 상속시켜 DB와 연동시키는 방법을 사용합니다. 이러한 과정에서 현재 제가 사용하는 방법에서도 상속을 이용한 뒤 DB에 저장시키는 방법론이 동일하게 적용되는지 궁금하여 실험을 해보았습니다.
(빠른 실험을 위해 만든 코드이기에 DTO 클래스도 없고 db 생성 시 데이터 정확한지 확인하는 과정도 없습니다.)
실험 시작
우선 책의 예시대로 부모 클래스를 만들어준 뒤 자식 클래스를 만들었습니다.
@Entity
@Getter
abstract class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
int price;
}
@Entity
@NoArgsConstructor
@Getter
public class Album extends Item{
String artist;
}
@Entity
public class Movie extends Item{
String director;
String actor;
}
Album 클래스만 통해서 테스트 해볼 것이기 때문에 Album과 관련하여 Repository, Service, Controller를 생성했습니다.
public interface AlbumRepository extends JpaRepository<Album, Long> {
}
@Service
@RequiredArgsConstructor
public class AlbumService {
private final AlbumRepository albumRepository;
public Album createAlbum(Album album) {
return albumRepository.save(album);
}
}
@RestController
@RequiredArgsConstructor
public class AlbumController {
private final AlbumService albumService;
@PostMapping("/album")
public ResponseEntity postAlbum(@RequestBody Album album) {
albumService.createAlbum(album);
return new ResponseEntity(HttpStatus.CREATED);
}
}
위 과정들을 진행한 후, 코드를 실행하여 DB를 확인해보았습니다.
문제 발생
확인해본 DB 테이블의 결과는 신기했습니다.
'오 진짜로 영한님 책에서 나온 것처럼 DTYPE이 생기네.'
하지만 문제가 발생했음을 알 수 있었습니다.
'어? 책에서는 ITEM 테이블 따로 ALBUM 테이블 따로 생성되고 필요 데이터 조회할 때 Join 된다고 쓰여져있었는데? 왜 ITEM 테이블만 생성되고 한 테이블 안에 모든 칼럼이 들어가있지? 우선 값을 넣어보자'
'이렇게 되면 상황에 따라 null이 많아질텐데 정규화에 문제 생기지 않나?'
그래서 문제의 원인을 찾아보았습니다.
문제 해결
문제 해결법은 @Inheritance라는 애너테이션의 존재였습니다.
@Inheritance를 부모 클래스에 달아주고 상속 전략을 Joined로 선택해주면 모든게 해결되는거였습니다.
@Entity
@Getter
@Inheritance(strategy = InheritanceType.JOINED)
abstract class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
int price;
}
이렇게 하면 각 Entity가 각 테이블로 잘 쪼개어진 것을 확인하실 수 있고요.
앞선 예시와 동일한 예시를 넣어주면 아래와 같이 깔끔하게 들어가는 것을 확인하실 수 있습니다.
문제는 문제가 아니었음을
앞선 설명들을 보시면 한 테이블에 모든게 나오는 것이 문제고 깔끔하게 나오는게 정답처럼 써놓았습니다.
하지만 해당 해결 과정을 위해 참고한 블로그를 공부하다보니 각각의 장단점과 함께 전략을 활용하는 방법이었음을 알게 되었습니다.
상속 전략을 정하는 방식과 각 장단점 그리고 DTYPE 이름도 정할 수 있음을 공부하고 싶으신 분들은 아래의 Reference 블로그 주소를 참조해주시면 좋을 것 같습니다.
reference