인프런 커뮤니티 질문&답변

시큐웨어님의 프로필 이미지
시큐웨어

작성한 질문수

스프링 데이터 JPA

JPA 프로그래밍 5. 엔티티 상태와 Cascade

연관관계 매핑 어떤식으로 해야될지 감이 안잡힙니다.

작성

·

421

0

기선님 안녕하십니까. ^^

기존 mybatis만 사용했다가 기선님의  JPA강의를 보고  감명받아 신규프로젝트에 처음으로 jpa를 도입해보고자 하는데 엔티티 구현 중 막히는 부분이 있어 질문드립니다.

 

개발중인 시스템의 사용자 구분은 '소방'과 '의사'로 구분되고 추후 더 늘어날수 있는 상황입니다.

 

사용자(USERS)라는 테이블이 있고 여기엔 계정정보 및 공통적인 데이터들이 있습니다.

소방 사용자의 경우 소방과 관련된 추가 데이터, 의사 사용자의 경우 또 관련된 추가 데이터가 있어야 되는 상황입니다.

 

그래서 각각 USER_PROFILE_FIRE,  USER_PROFILE_DOCTOR 라고 테이블을 만들어서 그곳에 관련 데이터를 담고싶습니다.

사용자(USERS) 테이블에 각각의 프로필에 필요한 모든 컬럼을 넣기에는 양이 너무 많고 확장성도 좋지 않다고 생각해서 입니다.

 

USER_PROFILE_FIRE,  USER_PROFILE_DOCTOR 테이블들은 추가 데이터를 담기위한 용도일뿐 각각의 테이블이 별도 조회될 일은 없고 모두 USERS 테이블을 조회하면서 상황에 맞게 같이 불러와지면 됩니다.

이를테면 user.profile로 접근하면 소방일때는 소방프로필, 의사일때는 의사프로필을 접근할수 있으면 좋을것 같은데 이게 JPA로 가능한지 모르겠습니다.

 

public class User {
    @Id @GeneratedValue
    private Long id;

    private String userId;

   private String password;

   private String name;

   private UserType type;   //  의사 or 소방 

  @OneOnOne
   private ???   profile;   // 의사일때는 USER_PROFILE_DOCTOR,  소방일땐 USER_PROFILE_FIRE의 데이터

}

요약하면 위 코드 같은 구현이 가능한지 입니다.

 

 

안된다면 사용자 엔티티에 

private ProfileHosp profileHosp; // 의사 프로필
private ProfileFire profileFire;  // 소방 프로필 

이런식으로 놓고 모두 연관관계를 맺어야 하는건가요?
이러면 사용자 불러올때 두 테이블 모두 조인해서 좋지 않은것같은데..

상속관계매핑은 아닌것 같고 어떻게 해야될지 감이 안잡힙니다 ㅜㅜ 

 

답변 5

1

백기선님의 프로필 이미지
백기선
지식공유자

1. 로그인 시에 구체적인 타입 (소방회원인지 병원회원인지)을 알아야 한다면 형변환을 하는 수밖에 없어 보입니다.

2. 두번째 경우도 마찬가지로 읽어올 때 User 타입으로 읽어왔는데 중간에 구체적인 타입으로 해야 할 일이 있다면 변환해야 합니다.

두 경우 모두 구체적인 타입으로 일을 해야 하는거라면, 결국에 instanceof를 써서 변환해야 하지만 그런 일이 너무 빈번하다면, 상속 구조로 맵핑하는게 과연 적절한가.. 고민해 보셔야 할 것 같습니다.

1

백기선님의 프로필 이미지
백기선
지식공유자

안녕하세요.

질문을 여러번 잘 읽어봤으나.. 상속 관계로 구현하는게 맞는 상황인지, 구체적인 타입(Movie, Book)으로 코드를 작성하고 싶은건지, 아니면 상위 타입(Item)으로 코드를 하고 싶은것인지 잘 이해가 되지 않습니다.

쿼리는 Item 타입으로 해오지만 읽어온 객체를 사용할 때는 Movie나  Book과 괕은 구체적인 타입으로 쓰고 싶다는 말씀이신가요? 아니면 ItemRepository를 통해 조회해올 때 List<Book>이나 List<Movie>처럼 특정 타입의 객체만 읽어오고 싶다는 것인가요?

0

시큐웨어님의 프로필 이미지
시큐웨어
질문자

 

회원(User) 엔티티가 있고 회원은  소방회원(UserFire) , 병원회원(UserDoctor)으로 나뉩니다

그래서 회원 테이블엔 회원과 관련된 공통된 정보를

소방회원 테이블은 소방 관련 회원 정보를 

병원회원 테이블엔 병원 관련 회원 정보를 넣고 싶습니다.

 

여기까진 상속 연관관계로 하면 된다는걸 알고 있고

소방회원이 필요한 기능에선 소방회원을 불러오고 병원회원이 필요한 기능에선 병원회원을 불러오면 되겠지만

상속구조로 할경우 다음과 같은것은 어떻게 해야될까에 대해서 모르겠습니다.

 

 

1. 로그인

로그인할때는 이 사람이 소방인지 병원인지 모르므로 User로 받아 온뒤

if(user instanceof Fire)  {

}

와 같은 과정으로 누구인지까지 알아낸다고 해도 매번  로그인된 사용자를 확인해야되는 로직에서

instanceof 하는 과정으로 형변환이 User ->  UserFire or UserDoctor로 이루어지는 로직이 있어야되는건가? 라는 고민이 있습니다.

 

그냥 User에 이사람이 소방인지 병원인지 하는 정보가 변수로 들어가있어서 확인은 어렵지 않다 한들 결국 그 상세 정보(소방, 병원)에 접근하기 위해서는 형변환이 이뤄져야 하는것 맞나요?

 

2. 사용자 리스트 불러올때

소방 사용자 목록 이라는 기능이 있거나 병원 사용자 목록 이라는 기능이 있으면 애초에 

리스트를 조회할때 각각 받으면 되는 문제지만 

 

List<User>  전체사용자 = userRepository.findAll()  해야되는 경우도 존재합니다.

이때도 받아온 사용자를 

for(User user : 전체사용자){

   if(user instanceof UserFire){

  }

   if(user instanceof UserDoctor){

   }

}

이런 로직을 돌면서 형 변환을 해줘야 하나요.

소방사용자 목록 , 병원사용자 목록  분기해서 리턴하는게 아니라

전체사용자(각각 형변환 되어있는)로 리턴 하고 싶은데 이럴땐 어떻게 하는지를 모르겠습니다

 

그냥 List<User>로 리턴하면  User 엔티티에 있는 정보들까지는 접근이 가능하지만 

나머지 정보들은 접근이 안되니까요

 

이것도 설명이 횡설수설 같은데

users : {[

         0: {   사용자정보...,   소방정보 },

         1: {   사용자정보...,   소방정보 },

         2: {   사용자정보...,   의사정보 },

         3: {   사용자정보...,   소방정보 },

         4: {   사용자정보...,   의사정보 },

]}

 

이런 형태로 리턴할수 있는가 입니다.

 

 

0

삭제된 글입니다

시큐웨어님의 프로필 이미지
시큐웨어
질문자

답변감사합니다만 제 질문하고는 맞지 않는 성격인거같습니다

상속과 관련된 질문입니다.

음...제 짧은 식견으로 저 강의가 답변이 될 거라 생각했는데 아니라면 괜히 참견 드린 점 사과 드립니다.

 

덧 붙이자면 상속 조인 전략의 경우 조회 쿼리가 복잡한점, 등록할 때 insert sql이 두번 실행되는 점, 조인이 많아서 성능저하 이유 등등의 단점으로 성능을 중시하시는 질문자분께서 사용하지 않을 전략인 거 같았습니다. 

나아가 글 말미에 다른 방식으로 접근 할 시에 두 테이블 모두 조인하는 것에 대해 성능상 문제로 좋지 않다 하셔서 위의 강의를 보시면 해결될 거 같다 말씀 드렸습니다.

 

그냥 지나가다가 글을 봤는데 고민을 많이 하신 거 같길래 백지장도 맞들면 낫다고, 작은 도움이라도 되길 바라서 작성했을 뿐이니 제 글은 그냥 신경 안 쓰셔도 됩니다.

 

강사님 답변 기다리시는 와중에 그저 이런 방법도 있지 않겠냐 정도의 참견이었으니 강사님께 원하시는 답변을 들으셔서 고민하시는 문제가 해결되길 바랍니다.

 

수고하세요.

생각해보니까 강사님 답변을 기다리셨을텐데 제가 답변을 다는게 이상하게 보이셨을 수도 있겠네요

글 자삭 하겠습니다.

0

시큐웨어님의 프로필 이미지
시큐웨어
질문자

질문이 정리가 안되고 횡설수설 한것같아 다시 정리해서 질문드립니다 ㅜㅜ

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {

    @Id
    @GeneratedValue
    @Column(name = "item_id")
    private Long id;

    private String name;
    private int price;

}

 

@Entity
@DiscriminatorValue("B")
public class Book extends Item {
    private String author;
    private String isbn;
}

@Entity
@DiscriminatorValue("A")
public class Album extends Item {
    private String artist;
}

@Entity
@DiscriminatorValue("M")
public class Movie extends Item {
    private String director;
    private String actor;
}

 

 

상속 연관관계에서 가장 많은 예시로 사용되는 Item 과  Movie, Album, Book 예시로 질문드립니다

위의 예시에서 각각 Book, Movie, Album의 Repository가 만들어지고 

 

@Repository
public interface ItemRepository<T extends Item> extends JpaRepository<T, String> {

}

 

ItemRepository는 위와 같이 구현 되었다고 볼때  

이런식으로 사용될수도 있지만 

List<Book> bookList = bookRepository.findAll();
List<Movie> movieList = movieRepository.findAll();
List<Album> bookList = albumRepository.findAll();

 

아래와 같이 Item으로 받을수도 있는데, 그때 아래와 같이 getClass하거나 instansof를 통해 어떤 자식클래스인지 확인 가능하고 형변환도 가능한것을 확인했습니다.

List<Item> itemList = itemRepository.findAll();

for(Item item : itemList){
    System.out.println(item.getClass().toString());  //  Book, Album,  Movie 클래스임을 확인.
}

 

질문: 

로직상 Book, Album, Movie를 직접 조회할일은 거의 없고
Item 만으로 조회해야되는 일이 빈번할 경우는 어떤식으로 로직을 짜야할지모르겠습니다.

시스템입장에선 조회 전에 내가 조회할게 Book인지 Movie인지 모르는 상황이라 Item으로 조회해야만 하는 상황이고

조회 후에 아래 코드 처럼 instanceof를 통해 형변환 해주면 된다 치지만 

// 단일 조회일 경우
Item item = itemRepository.findById(id);   
Book book;
if(item instanceof Book){
   book = (Book) item;   
}

 

단일 조회가 아닌 전체 리스트 조회일경우는 어떤식으로 해야하는지 감이 안잡힙니다 ㅜㅜ

// 리스트 조회할경우 ?
List<Item> itemList = itemRepository.findAll();   
for(Item item : itemList){
  ???
}

 

리스트로 조회할때 ORDER BY나 페이징 까지 고려되서 소트된 데이터가 올거라 

포문 돌면서 형변환 하고 각각의 리스트에 다시 담기도 어렵고 

 

JPA를 이용해 애초에 받아올때 각각 맞는 클래스에 맞게 받아와서 하나의 리스트에 담는 방법이 없나요?
bookList, movieList가 아닌 ItemList 하나만 존재해야 할경우 어떤식으로 해야하나요?

List<T extends Item>  itemList ;   <<-   이런식으로 item을 상속받는 객체를 담은 리스트를 만들수 있나요?


 

=================

위의 예를 제 상황에 맞게 변경하자면 

Item = 사용자 (계정정보)

Book = 소방사용자 (소방관련 정보 포함)

Movie = 의사사용자 (의사관련 정보 포함)

Album = 기타 사용자 (그외 필요 정보 포함)

 

로 둘수가 있겠죠.

 

그런데 시스템 로직상   소방사용자만 조회한다던가 의사사용자만 조회한다던가 하는 기능도 있을수 있지만

전체사용자를 계정정보 관련 소트로 불러온뒤 ( Item 리스트를 Item에 해당하는 소트로 불러온뒤)

소방사용자1, 소방사용자2, 의사사용자1, 소방사용자3,  의사사용자2   . ...  이런식으로 불러와야 되기때문에

List<사용자> 로 불러와야 되기도하고 

 

로그인, 계정정보수정과 같은 공통된 '사용자' 관련 기능들 구현시에도 추상클래스로 정의되었지만 '사용자' 객체를 가지고 접근해야 되는 상황이라고 사료됩니다.

 

이런 경우에도 상속관계가 맞는건지 아니면 다른 방법을 생각해야 되는건지 조차 햇갈립니다ㅠㅠ

 

 

시큐웨어님의 프로필 이미지
시큐웨어

작성한 질문수

질문하기