ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 3월 17일 공부 값타입 컬렉션
    공부/JPA 2023. 3. 17. 21:12

    김영한 님의 강의를 들으면서 정리를 해야겠다.!


    값 타입 컬렉션

    값 타입 컬렉션을 DB 테이블로 구현할 때 문제가 발생한다.

    컬렉션이 DB에 들어가야하는대 관계형 DB는 컬렉션을 담을 수 있는 구조가 존재하지 않는다.

    따라서 1:n관계일때 n의 테이블을 따로 테이블로 뽑아내야 한다.

     

    user(1) : reply(n)

    내가 지금 진행하고 있는 프로젝트를 예로 들면 사용자는 여러 개의 댓글을 작성할 수 있는대 

    user Entity에서는 List <reply> 필드로 값타입 컬렉션을 가지고 있다.

    강의에서 설명과 같이 RDB는 컬렉션을 담을 수 있는 구조가 아니기 때문에 Reply 테이블을 새로 만들어 데이터를 관리해주고 있다.

     

     

    @Entity
    public class Member
    {
    	
        @Id
        @GenerateValue
        @Column(name = "MEMBER_ID")
        private Long id;
        
        @Embedded
        private Address HomeAddress;
    
        @ElementCollection
        @CollectionTable(name = "FAVORITE_FOOD", 
        joinColumns = @JoinColumn(name = "MEMBER_ID"))
        @Column(name = "FOOD_NAME")                 
        private Set<String>favoriteFoods = new HashSet<>();
    
    
        @ElementCollection
        @CollectionTable(name = "ADDRESS", 
        joinColumns =  @JoinColumn(name = "MEMBER_ID"))
        private List<Address>addressHistory = new ArrayList<>();
    }

    @ElementCollection, @CollectionTable을 이용해 Entity안 컬렉션에 어노테이션을 지정해 주면

    JPA가 자동으로 Table을 생성해 준다. 내가 프로젝트를 하면서 reply엔티티를 따로 빼서 table을 생성해 준 것과는 다른 방법이어서 새롭다.

    name을 이용해 생성되는 컬렉션 테이블의 pk값을 지정해 주면 컬렉션 테이블이 생성되면서 pk를 name으로 지정된 값으로 설정한다.

     

     

    Member member = new Member();
    member.serUserName("member1");
    member.setHomeAddress(new Address("city1","street1","10000"));
    
    member.getFavoriteFoods().add("치킨");
    member.getFavoriteFoods().add("족발");
    member.getFavoriteFoods().add("피자"); //Member 엔티티가 가지고있는 Food 컬렉션에 음식들 add
    
    member.getAddressHistory.add(new Address("old1","street1","20000"));
    member.getAddressHistory.add(new Address("old2","street1","30000"));
    
    em.persisit(member);

    FavoriteFoods, AddressHistoty들은 컬렉션이어서 DB table이 분리되어 있음에도 불구하고

    member안에 값을 세팅해 주고 컬렉션 안에 값을 넣어준 뒤 member만 persist해도 분리된 컬랙션 테이블에 insert쿼리가 날아간다.

     

    지금 프로젝트에 활용할 수 있을까 라는 생각이 든다. 1:n연관 관계가 맺어져 있는 Entity들 중 

    임베디드로 설정할 수 있거나 속성을 하나만 가지고 있는 객체가 지금 당장은 떠오르지 않는다.

    하지만 엔티티들을 좀 더 찾아보고 분석해서  적용할 수 있는 1:n 관계의 엔티티들은 위와 같은 기능을 적용해 보아야겠다.

     

    김영한 님의 강의는 기본 편이어서 EntityManager를 쓰지만 지금 프로젝트는 JPA Repository를 사용하다 보니

    모든 엔티티들이 각자의 Repository를 가지고 있어서 컬렉션 테이블의 Repository을 계속 호출해 줘야 되는 불편함이 있다.

     

    이 기능은 CascadeType.ALL과 OrphsRemoval = true와 같은 기능과 유사하다. 엔티티와 생명주기를 함께하니까 값 타입 컬렉션은 영송성 전이와 고아 객체 제거 기능을 필수로 가진다고 볼 수 있다.

     

    값타입 컬렉션들은 지연로딩 처리되어 사용되는 시점에 쿼리가 나간 뒤 DB에서 조회해서 가져온다.

     

    Member findMember = em.find(Member.class,member.getId());
    
    //findMember.getHomdAddress().setCity("newCity"); 이렇게 하면 안된다
    
    
    Addresss a = findMember.getAddress();
    findMember.setHomeAddress(new Address("newCity",a.getStreet(),a.getZipcode());
    
    findMember.getFavoriteFoods().remove("치킨");
    findMember.getFavoriteFoods().add("한식");

    값 타입을 수정할 때는 엔티티 안에 기존에 존재하던 컬렉션의 값을 그대로 바꿔버리면 부작용이 생길 수 있다.

    저번 강의에서 들었던 것처럼 Member Entity안에 Address가 임베디드로 들어가 있기 때문에 

    만에 하나 다른 Member가 Address객체를 공유하고 있다면 주소가 같이 바뀌어 버리기 때문에

    아예 새로운 값타입 객체를 생성해서 데이터를 수정해줘야 한다.

     

    List <String>인 FavoriteFoods객체도 수정하고 싶을 때 아예 기존의 값 타입 객체를 삭제한 뒤 새로운 String 객체를 add 해줘야 한다.

     

    수정도 마찬가지로 Member객체 안에 컬렉션만 수정했을 뿐 인대 JPA가 변경을 감지하고 update쿼리를 날려준다.

     

    Member member = new Member();
    
    member.getAddressHistory.add(new Address("old1","street1","20000"));
    member.getAddressHistory.add(new Address("old2","street1","30000"));
    
    findMember.getAddress().remove()(new Address("old1","street","10000"));
    findMember.getAddress().add()(new Address("newCity1","street","10000"));
    
    //old1의 주소를 가진 Address객체를 newCity1주소로 바꾸고 싶은 경우.

     메서드 실행 결과 쿼리의 모습

    컬랙션에서 remove()를 호출하면 내부적으로 equlas메서드를 호출해 제거하고자 하는 객체와 일치하는 객체를 찾는다.

    equals를 이용하기 때문에 컬렉션을 사용할 때 equals메서드를 재정의하면 hashCode메서드도 재정의해야

    정확하게 일치하는 객체를 찾을 수가 있다.

    값타입이기 때문에 지우고자 하는 객체를 아예 통째로 삭제해 버리고 새로운 객체를 add 해준다.

     

    쿼리를 보면 의아한 점이 든다. 삭제를 시도했기 때문에 삭제 쿼리가 나가는 것은 이해가 가지만

    add()는 한 번만 호출했는데 insert쿼리가 2개가 날아갔다. 이게 의미하는 바는

    테이블을 아예 통째로 삭제했다가 다시 데이터를 넣었다는 의미가 된다. 이 이유는 값 타입 컬렉션에 제약사항이 있기 때문이다.

     

    값 타입 컬렉션은 변경사항이 발생하면 주인 엔티티와 연관된 모든 데이터를 삭제하고

    값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.

    위와 같은 제약사항 때문에 사용하면 안 된다. 주인과 연관된 모든 데이터를 삭제해 버리기 때문에 위험도가 높고

    의도치 않은 데이터까지 삭제해 버릴 수 있다.

     

    이렇게 복잡하게 컬렉션으로 연관관계를 매핑할 상황이 생기면 1:n관계로 엔티티를 풀어내야 한다.

    값타입 컬렉션은 값이 단순할 때만 사용해야 한다.

     

    요약: 값 타입 컬렉션을 사용해야 할 경우 엔티티로 따로 설정해서 1:n 연관관계로 설정해야 되고 데이터가 아주 간단할 경우에만 

              값타입 컬렉션을 사용한다.

     

     

     

     

     

     

     

     

     

     

    '공부 > JPA' 카테고리의 다른 글

    3월 14일 공부  (0) 2023.03.15
    김영한님의 JPA프록시 강의 정리  (0) 2023.01.02
    김영한님의 상속 관계매핑 강의정리  (0) 2022.12.31
    JPA annotation 정리  (0) 2022.12.22

    댓글

Designed by Tistory.