ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • #JPA-16 값 타입
    JPA 2021. 2. 22. 15:56

    [출처] 인프런 김영한 강사님 -자바 ORM 표준 JPA 프로그래밍 기본

     

    <기본값 타입>

     

    JPA의 데이터 최상위 타입 2가지 분류

     

    1. 엔티티 타입 

        • @Entity로 정의하는 객체 

        • 데이터가 변해도 식별자로 지속해서 추적 가능

        • 예) 회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식 가능 

    2. 값 타입 

        • int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체 

        • 식별자가 없고 값만 있으므로 변경시 추적 불가 

        • 예) 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체

     

     

    값 타입 분류 

    • 기본값 타입 

        • 자바 기본 타입(int, double) 

        • 래퍼 클래스(Integer, Long) 

        • String 

    • 임베디드 타입(embedded type, 복합 값 타입) ex) x,y 좌표

    • 컬렉션 값 타입(collection value type)

     

     

    기본값 타입

    • 예): String name, int age 

    • 생명주기를 엔티티의 의존 

        • 예) 회원을 삭제하면 이름, 나이 필드도 함께 삭제 

    • 값 타입은 공유하면X (부수 효과가 일어나면 안된다.)

        • 예) 회원 이름 변경시 다른 회원의 이름도 함께 변경되면 안됨

     

     

    참고: 자바의 기본 타입은 절대 공유X

    • int, double 같은 기본 타입(primitive type)은 절대 공유X 

    • 기본 타입은 항상 값을 복사함 

    • Integer같은 래퍼 클래스나 String 같은 특수한 클래스는 공유 가능한 객체이지만 변경X

       참조(주소)값이 넘어가는것이기 때문에 같은 인스턴스로 공유할수있다.

       하지만 set~할수없기때문에 사이드 임팩트가 일어날 수 없다.

     

     

     

    <임베디드 타입(복합 값 타입)>

     

    임베디드 타입

    • 새로운 값 타입을 직접 정의할 수 있음 

    • JPA는 임베디드 타입(embedded type)이라 함 

    • 주로 기본 값 타입을 모아서 만들어서 복합 값 타입이라고도 함 

    • int, String과 같은 값 타입(즉, 추적도 안되고 변경도 안된다.)

     

    임베디드 타입

    • 회원 엔티티는 이름, 근무 시작일, 근무 종료일, 주소 도시, 주소 번지, 주소 우편번호를 가진다.

     

    공통적인 두요소가 보인다.

    임베디드 타입

    • 회원 엔티티는 이름, 근무 기간, 집 주소를 가진다.

     

    이런식으로 묶어서 사용하는게 임베디드 타입이다.

    임베디드 타입 사용법

    • @Embeddable: 값 타입을 정의하는 곳에 표시 

    • @Embedded: 값 타입을 사용하는 곳에 표시 

    • 기본 생성자 필수

     

     

    임베디드 타입의 장점

    • 재사용

     

    • 높은 응집도

     

    • Period.isWork()처럼 해당 값 타입만 사용하는 의미 있는 메소 드를 만들 수 있음(객체 지향적)

     

    • 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티 티에 생명주기를 의존함

       값 타입 -> 엔티티 죽으면 다죽는것이고 생성되면 다같이 생성되는 것이다.

     

     

    임베디드 타입과 테이블 매핑

    DB에서 회원 테이블은 똑같고 객체와 메핑만 잘해주면된다.

    객체는 데이터뿐만아니라 메서드라는 기능 행위까지 다가지고 있다.

     

    Period class

    package hello.jpa;
    
    import javax.persistence.Embeddable;
    import java.time.LocalDateTime;
    
    @Embeddable
    public class Period {
    
        private LocalDateTime startDate;
        private LocalDateTime endDate;
    
        public Period() {
        }
    
        public LocalDateTime getStartDate() {
            return startDate;
        }
    
        public void setStartDate(LocalDateTime startDate) {
            this.startDate = startDate;
        }
    
        public LocalDateTime getEndDate() {
            return endDate;
        }
    
        public void setEndDate(LocalDateTime endDate) {
            this.endDate = endDate;
        }
    }
    

    Address class

    package hello.jpa;
    
    import javax.persistence.Embeddable;
    
    @Embeddable
    public class Address {
    
        private String city;
        private String street;
        private String zipcode;
    
        public Address(String city, String street, String zipcode) {
            this.city = city;
            this.street = street;
            this.zipcode = zipcode;
        }
    
        public String getCity() {
            return city;
        }
    
        public void setCity(String city) {
            this.city = city;
        }
    
        public String getStreet() {
            return street;
        }
    
        public void setStreet(String street) {
            this.street = street;
        }
    
        public String getZipcode() {
            return zipcode;
        }
    
        public void setZipcode(String zipcode) {
            this.zipcode = zipcode;
        }
    }
    

    Member class

    package hello.jpa;
    
    import javax.persistence.*;
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    import java.util.Date;
    @Entity
    public class Member {
    
        @Id @GeneratedValue
        @Column(name = "MEMBER_ID")
        private Long id;
    
        @Column(name = "USERNAME")
        private String username;
    
        @ManyToOne
        @JoinColumn(name = "TEAM_ID")
        private Team team;
    
        @Embedded
        private Period workPeriod;
    
        @Embedded
        private Address homeAddress;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public Team getTeam() {
            return team;
        }
    
        public void setTeam(Team team) {
            this.team = team;
       }
    
        public Period getWorkPeriod() {
            return workPeriod;
        }
    
        public void setWorkPeriod(Period workPeriod) {
            this.workPeriod = workPeriod;
        }
    
        public Address getHomeAddress() {
            return homeAddress;
        }
    
        public void setHomeAddress(Address homeAddress) {
            this.homeAddress = homeAddress;
        }
    }

    임베디드 타입과 테이블 매핑

    • 임베디드 타입은 엔티티의 값일 뿐이다.

     

    임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다.

     

    • 객체와 테이블을 아주 세밀하게(find-grained) 매핑하는 것이 가능 

      (프로젝트 큰걸하면 Address ,period같은경우에는 class로 뽑아 그안에 method를 만들어서 활용할 수 있는 것이 많다.

    • 잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많음

     

    임베디드 타입과 연관관계

    임베디드 안에 임베디드 , 임베디드 안에 객체가 가능하다.

     

     

    이렇게 쓰면 중복문제 때문에 에러가 난다 

    이럴때 쓰는게 AttributeOverride 이다.

    @AttributeOverride: 속성 재정의

    • 한 엔티티에서 같은 값 타입을 사용하면?

    • 컬럼 명이 중복됨

    • @AttributeOverrides(여러개), @AttributeOverride(하나)를 사용해서 컬러 명 속성을 재정의

     

    임베디드 타입과 null

    • 임베디드 타입의 값이 null이면 매핑한 컬럼 값은 모두 null

     

     

     

     

     

    <값 타입과 불변 객체>

    값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고 만든 개념이다.

    따라서 값 타입은 단순하고 안전하게 다 룰 수 있어야 한다.

     

     

    <값 타입 공유 참조>

    • 임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험함

    • 부작용(side effect) 발생

     

    JpaMain Class

     			//Member1과 Member2가 같은 Address를 쓰고있다.
    
                Address address = new Address("city","street","zipcod");
    
                Member member = new Member();
                member.setUsername("member1");
                member.setHomeAddress(address);
                em.persist(member);
    
                Member member2 = new Member();
                member2.setUsername("member1");
                member2.setHomeAddress(address);
                em.persist(member2);
    
                tx.commit();

    query result select * from member;

    둘다똑같은 주소를 들고있어 아무런 문제가 안생긴다 . 그런데....

     

    JpaMain Class

                //Member1과 Member2가 같은 Address를 쓰고있다.
    
                Address address = new Address("city","street","zipcod");
    
                Member member = new Member();
                member.setUsername("member1");
                member.setHomeAddress(address);
                em.persist(member);
    
                Member member2 = new Member();
                member2.setUsername("member1");
                member2.setHomeAddress(address);
                em.persist(member2);
    
                //첫 번째 member의 주소를 newCity로 바꾸고 싶어.
                member.getHomeAddress().setCity("newCity");
    
                tx.commit();

    console

    Hibernate: 
        /* update
            hello.jpa.Member */ update
                Member 
            set
                city=?,
                street=?,
                zipcode=?,
                USERNAME=?,
                HOME_CITY=?,
                HOME_STREET=?,
                HOME_ZIPCODE=?,
                endDate=?,
                startDate=? 
            where
                MEMBER_ID=?
    Hibernate: 
        /* update
            hello.jpa.Member */ update
                Member 
            set
                city=?,
                street=?,
                zipcode=?,
                USERNAME=?,
                HOME_CITY=?,
                HOME_STREET=?,
                HOME_ZIPCODE=?,
                endDate=?,
                startDate=? 
            where
                MEMBER_ID=?

    업데이트 쿼리가 두번 나가는 것을 볼 수있고 

    query result select * from member;

    둘다 newCity로 바뀐것을 알 수 있다. 이런버그는 실무에서 못잡는다 . 사이드 이팩트 버그는 잡기가 힘들다..

    이럴땐 값타입을 쓰면 안되고 ENTITY를 써야한다. 같이 뭔가 공유에서 하고싶으면 Address를 Entity로 만들어야한다.

     

    값 타입 복사

    • 값 타입의 실제 인스턴스인 값을 공유하는 것은 위험 ->부작용이 발생할 위험이있다.

     

    대신 값(인스턴스)를 복사해서 사용

     

    JpaMain Class

               Address address= new Address(
                        "city"
                        ,"street"
                        ,"zipcod");
    
                Member member = new Member();
                member.setUsername("member1");
                member.setHomeAddress(address);
                em.persist(member);
    
                Address copyAddress = new Address(
                        address.getCity()
                        ,address.getStreet()
                        ,address.getZipcode());
    
                Member member2 = new Member();
                member2.setUsername("member2");
                member2.setHomeAddress(copyAddress);
                em.persist(member2);
    
                //첫 번째 member의 주소를 newCity로 바꾸고 싶어.
                member.getHomeAddress().setCity("newCity");
    
                tx.commit();

    query result -select * from member;

     

    첫 번째만 newCity이고 copy해서 address를 사용한 두번째는 바뀌지 않은걸 확인 할 수 있다.

     

    객체 타입의 한계

    • 항상 값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용 을 피할 수 있다.

     -> 문제는 임베디드 타입처럼 직접 정의한 값 타입은 자바의 기본 타입이 아니라 객체 타입이다.

    • 자바 기본 타입에 값을 대입하면 값을 복사한다.

    객체 타입은 참조 값을 직접 대입하는 것을 막을 방법이 없다.

    결론 : 객체의 공유 참조는 피할 수 없다.(타입만 맞으면 다들어간다.)

     

    객체 타입의 한계

    기본 타입(primitive type)

    int a = 10;

    int b = a;//기본 타입은 값을 복사 b = 4;

     

    객체 타입

    Address a = new Address(“Old”);

    Address b = a; //객체 타입은 참조를 전달

    b. setCity(“New”)

    그냥 둘다 결론적으로 바뀌어버림

     

     

    불변 객체

    • 객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단

    값 타입은 불변 객체(immutable object)로 설계해야함

    불변 객체: 생성 시점 이후 절대 값을 변경할 수 없는 객체

    • 생성자로만 값을 설정하고 수정자(Setter)를 만들지 않으면 됨

    • setter를 private으로 만들면 됨.

    • 참고: Integer, String은 자바가 제공하는 대표적인 불변 객체

     

    불변이라는 작은 제약으로 부작용이라는 큰 재앙을 막을 수 있다.

     

    값타입은 일단 다 불변으로 만들어야한다.

     

     

     

    <값 타입의 비교>

    값 타입의 비교

    • 값 타입: 인스턴스가 달라도 그 안에 값이 같으면 같은 것으로 봐 야 함

     

    int a = 10;

    int b = 10;

    a == b : true;

     

    Address a = new Address(“서울시”)

    Address b = new Address(“서울시”)

    a == b : false; (x)

    값 타입의 비교는

    equals를 써야한다.

     

    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Address address = (Address) o;
            return Objects.equals(getCity(), address.getCity()) && Objects.equals(getStreet(), address.getStreet()) && Objects.equals(getZipcode(), address.getZipcode());
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(getCity(), getStreet(), getZipcode());
        }

    equals를 구현한면 항상 hashCode도 같이 구연해야한다. hashMap이라던가 자바 컬렉션에서 효율적으로 사용할 수 있다.

     

    equals 만들 때 유의 사항

    Use getter during code generation

    이걸 체크 안하게 되면 getter를 생성하지 않고 필드에 직접 접근하게된다. 직접접근하면 어떤문제가 생기냐면

    프록시일때는 계산이 되지 않는다. getter를 호출해야 프록시일때도 getter가 진짜 객체에 접근함.

     

    값 타입의 비교

    동일성(identity) 비교: 인스턴스의 참조 값을 비교, == 사용

    동등성(equivalence) 비교: 인스턴스의 값을 비교, equals() 사용

    • 값 타입은 a.equals(b)를 사용해서 동등성 비교를 해야 함

    • 값 타입의 equals() 메소드를 적절하게 재정의(주로 모든 필드 사용)

     

     

    값 타입 컬렉션

    값 타입 컬렉션

    FAVORITE_fOOD같은경우는 FOOD_NAME+ MEMBER_ID 로 PK구성
    ADDRESS같은 경우에는 CITY, STREET,ZIPCODE를 묶어서 PK로 사용해야한다.

    왜냐하면 이들은 값타입이고 여기 만약 식별자 아이디 같은것을 넣어서 PK로 사용하게 되면 얘들은 값 타입이아니라 ENTITY이게 된다.

     

    값타입은 테이블의 값들을 묶어서 PK로 사용.!!

     

     

    값 타입 컬렉션

     

    JpaMain Class

        @ElementCollection //값 타입 컬렉션
        @CollectionTable(name = "FAVORITE_FOODS" //테이블명 지정
                , joinColumns = @JoinColumn(name = "MEMBER_ID"))// @JoinColumn = ""외래키로 잡게된다.
        @Column(name = "FOOD_NAME") //얘는 string 하나이기때문에 특이하게 얘만 매핑하게 허용을 해줌. 값이하나고 정의한게 아니기떄문에 예외적으로 설정.
        private Set<String> favoriteFoods = new HashSet<>();
    
        @ElementCollection //값 타입 컬렉션
        @CollectionTable(name = "ADDRESS" //테이블명 지정
                , joinColumns = @JoinColumn(name = "MEMBER_ID"))// @JoinColumn = ""외래키로 잡게된다.
        private List<Address> addressHistory =  new ArrayList<>();

     

    MEMBER

    Hibernate: 
        
        create table Member (
           MEMBER_ID bigint not null,
            city varchar(255),
            street varchar(255),
            zipcode varchar(255),
            USERNAME varchar(255),
            endDate timestamp,
            startDate timestamp,
            primary key (MEMBER_ID)
        )

           city varchar(255),
            street varchar(255),
            zipcode varchar(255),

    homeAddress인 embeded 값타입이 들어가 있다.

     

    ADDRESS

        create table ADDRESS (
           MEMBER_ID bigint not null,
            city varchar(255),
            street varchar(255),
            zipcode varchar(255)
        )
    Hibernate: 
        
        alter table ADDRESS 
           add constraint FKsuulxb5rmrxvb83yr43ox86wn 
           foreign key (MEMBER_ID) 
           references Member

    MEMBER_ID City, street,zipcode 로 잡힌걸 확인 할 수 있다.

    MEMBER_ID 가 있는 것은 내가 어떤 맴버아이디에 소속되있는지 확인하기 위해

     

    FAVORITE_FOODS

    Hibernate: 
        
        create table FAVORITE_FOODS (
           MEMBER_ID bigint not null,
            FOOD_NAME varchar(255)
        )
    Hibernate: 
        
        alter table FAVORITE_FOODS 
           add constraint FK168ye6o5txsleofyk17h9sr3h 
           foreign key (MEMBER_ID) 
           references Member

    MEMBER_ID ,FOOD_NAME으로 잡힌걸 확인 할 수 있다.

     

     

     

    • 값 타입을 하나 이상 저장할 때 사용

     

    • @ElementCollection, @CollectionTable 사용

     

    • 데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다. 

       -> 컬렌션들은 1:N 개념이기 때문에 DB의 한테이블에 넣을 수 있는 방법은 없다.

           1:N 로 풀어내서 별도의 테이블로 만들어내야한다. ADDRESS처럼 JOIN 할 수 있게  MEMBER ID를 넣어줘야한다.

     

    • 컬렉션을 저장하기 위한 별도의 테이블이 필요함

     

     

     

    값 타입 컬렉션 사용

    • 값 타입 저장 예제

    JpaMain Class

               //갑타입 저장 예제
                Member member =new Member();
                member.setUsername("member1");
                member.setHomeAddress(new Address("homeCity","street","zipcode"));
    
                member.getFavoriteFoods().add("치킨");
                member.getFavoriteFoods().add("족발");
                member.getFavoriteFoods().add("피자");
    
                member.getAddressHistory().add(new Address("old1","street","zipcode"));
                member.getAddressHistory().add(new Address("old2","street","zipcode"));
    
                em.persist(member);
                tx.commit();

    console

    Hibernate: 
        /* insert hello.jpa.Member
            */ insert 
            into
                Member
                (city, street, zipcode, USERNAME, endDate, startDate, MEMBER_ID) 
            values
                (?, ?, ?, ?, ?, ?, ?)
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.addressHistory */ insert 
            into
                ADDRESS
                (MEMBER_ID, city, street, zipcode) 
            values
                (?, ?, ?, ?)
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.addressHistory */ insert 
            into
                ADDRESS
                (MEMBER_ID, city, street, zipcode) 
            values
                (?, ?, ?, ?)
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.favoriteFoods */ insert 
            into
                FAVORITE_FOODS
                (MEMBER_ID, FOOD_NAME) 
            values
                (?, ?)
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.favoriteFoods */ insert 
            into
                FAVORITE_FOODS
                (MEMBER_ID, FOOD_NAME) 
            values
                (?, ?)
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.favoriteFoods */ insert 
            into
                FAVORITE_FOODS
                (MEMBER_ID, FOOD_NAME) 
            values
                (?, ?)

    첫번째 Member insert

    - >member.setHomeAddress(new Address("homeCity","street","zipcode"));

     

    두번째 AddressHistory 2번 insert

    - >member.getAddressHistory().add(new Address("old1","street","zipcode"));
        member.getAddressHistory().add(new Address("old2","street","zipcode"));

     

    세번째 favoriteFoods 3번 insert

    ->em.persist(member); 한번

    여기서 흥미로운것은 값타입 컬렉션을 따로 persist하지 않았고, member만 persist하니깐 값타입 컬렉션들은 자동으로 같이 저장됨. 

    이게 되는게 값타입 컬렉션도 본인 스스로도 라이프사이클이 없고 모든 라이프사이클에 대한것을 member에 소속된 것즉 라이프사이클이 member에 의존된 것이다.

    member의 값을 바꾸거나 하면 의존된 값타입들이 바뀌는 것이다.

    값타입 컬렉션은 영속성 전이의 고아객체 제거 기능을 필수로 가진다.

     

     

    • 값 타입 조회 예제

    JpaMain Class

               //갑타입 저장 예제
                Member member =new Member();
                member.setUsername("member1");
                member.setHomeAddress(new Address("homeCity","street","zipcode"));
    
                member.getFavoriteFoods().add("치킨");
                member.getFavoriteFoods().add("족발");
                member.getFavoriteFoods().add("피자");
    
                member.getAddressHistory().add(new Address("old1","street","zipcode"));
                member.getAddressHistory().add(new Address("old2","street","zipcode"));
    
                em.persist(member);
    
                //조회 예제
                em.flush();
                em.clear();
    
                System.out.println(" =========================START=========================");
                Member findMember = em.find(Member.class, member.getId());
    
    
                tx.commit();

    console

    Hibernate: 
        call next value for hibernate_sequence
    Hibernate: 
        /* insert hello.jpa.Member
            */ insert 
            into
                Member
                (city, street, zipcode, USERNAME, endDate, startDate, MEMBER_ID) 
            values
                (?, ?, ?, ?, ?, ?, ?)
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.addressHistory */ insert 
            into
                ADDRESS
                (MEMBER_ID, city, street, zipcode) 
            values
                (?, ?, ?, ?)
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.addressHistory */ insert 
            into
                ADDRESS
                (MEMBER_ID, city, street, zipcode) 
            values
                (?, ?, ?, ?)
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.favoriteFoods */ insert 
            into
                FAVORITE_FOODS
                (MEMBER_ID, FOOD_NAME) 
            values
                (?, ?)
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.favoriteFoods */ insert 
            into
                FAVORITE_FOODS
                (MEMBER_ID, FOOD_NAME) 
            values
                (?, ?)
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.favoriteFoods */ insert 
            into
                FAVORITE_FOODS
                (MEMBER_ID, FOOD_NAME) 
            values
                (?, ?)
     =========================START=========================
    Hibernate: 
        select
            member0_.MEMBER_ID as MEMBER_I1_2_0_,
            member0_.city as city2_2_0_,
            member0_.street as street3_2_0_,
            member0_.zipcode as zipcode4_2_0_,
            member0_.USERNAME as USERNAME5_2_0_,
            member0_.endDate as endDate6_2_0_,
            member0_.startDate as startDat7_2_0_ 
        from
            Member member0_ 
        where
            member0_.MEMBER_ID=?

    member를 조회한거라서 member만가지고옴 여기서 알수 있는게,여기 컬렉션들은 다 지연로딩이라는 것을 알 수 있다.

    얘는 같이 조회된다. Member에 소속된 값타입이기 때문에 homeAddress값은 같이 불러와 진다. 

     

     

     

    • 값 타입 컬렉션도 지연 로딩 전략 사용

    -지연로딩이라는 것을 알 수 있는 또 하나

    JpaMain Class

               //갑타입 저장 예제
                Member member =new Member();
                member.setUsername("member1");
                member.setHomeAddress(new Address("homeCity","street","zipcode"));
    
                member.getFavoriteFoods().add("치킨");
                member.getFavoriteFoods().add("족발");
                member.getFavoriteFoods().add("피자");
    
                member.getAddressHistory().add(new Address("old1","street","zipcode"));
                member.getAddressHistory().add(new Address("old2","street","zipcode"));
    
                em.persist(member);
    
                //조회 예제
                em.flush();
                em.clear();
    
                System.out.println(" =========================START=========================");
                Member findMember = em.find(Member.class, member.getId());
    
                List<Address> addressHistory = findMember.getAddressHistory();
                for (Address address : addressHistory) {
                    System.out.println("address.getCity() = " + address.getCity());
                }
                
                Set<String> favoriteFoods = findMember.getFavoriteFoods();
                for (String favoriteFood : favoriteFoods) {
                    System.out.println("favoriteFood = " + favoriteFood);
                }
                
                tx.commit();

    console

    Hibernate: 
        select
            addresshis0_.MEMBER_ID as MEMBER_I1_0_0_,
            addresshis0_.city as city2_0_0_,
            addresshis0_.street as street3_0_0_,
            addresshis0_.zipcode as zipcode4_0_0_ 
        from
            ADDRESS addresshis0_ 
        where
            addresshis0_.MEMBER_ID=?
    address.getCity() = old1
    address.getCity() = old2
    Hibernate: 
        select
            favoritefo0_.MEMBER_ID as MEMBER_I1_1_0_,
            favoritefo0_.FOOD_NAME as FOOD_NAM2_1_0_ 
        from
            FAVORITE_FOODS favoritefo0_ 
        where
            favoritefo0_.MEMBER_ID=?
    favoriteFood = 족발
    favoriteFood = 치킨
    favoriteFood = 피자

    • 값 타입 수정 예제

    JpaMain Class

    //수정 예제
    em.flush();
    em.clear();
    
    System.out.println(" =========================START=========================");
    Member findMember = em.find(Member.class, member.getId());
    
    //homeCity -> newCity
    
    //findMember.getHomeAddress().setCity("newCity");
    //이렇게 하면 되지 않나? -> x 이전에도 말했듯이 값타입이라는것은 이뮤터블 해야한다.
    // why? 잘못하면 사이드 이팩트가 생기기 때문에!! 결론  setter를 쓰면안된다.
    Address oldAddr = findMember.getHomeAddress();
    findMember.setHomeAddress(new Address("newCity",oldAddr.getStreet(),oldAddr.getZipcode()));
    //아에 새로 넣어야한다.
    
    
     tx.commit();

    console (entity update)

    Hibernate: 
        select
            member0_.MEMBER_ID as MEMBER_I1_2_0_,
            member0_.city as city2_2_0_,
            member0_.street as street3_2_0_,
            member0_.zipcode as zipcode4_2_0_,
            member0_.USERNAME as USERNAME5_2_0_,
            member0_.endDate as endDate6_2_0_,
            member0_.startDate as startDat7_2_0_ 
        from
            Member member0_ 
        where
            member0_.MEMBER_ID=?
    Hibernate: 
        /* update
            hello.jpa.Member */ update
                Member 
            set
                city=?,
                street=?,
                zipcode=?,
                USERNAME=?,
                endDate=?,
                startDate=? 
            where
                MEMBER_ID=?

    select 나간 다음에 update가 나가는 것을 확인 할 수 있다.

     

    JpaMain Class (값타입 컬렉션 update)

    //수정 예제
    em.flush();
     em.clear();
    
     System.out.println(" =========================START=========================");
     Member findMember = em.find(Member.class, member.getId());
    
    //homeCity -> newCity
    
    //findMember.getHomeAddress().setCity("newCity");
    //이렇게 하면 되지 않나? -> x 이전에도 말했듯이 값타입이라는것은 이뮤터블 해야한다.
    // why? 잘못하면 사이드 이팩트가 생기기 때문에!! 결론  setter를 쓰면안된다.
    Address oldAddr = findMember.getHomeAddress();
     findMember.setHomeAddress(new Address("newCity",oldAddr.getStreet(),oldAddr.getZipcode()));
    //아에 새로 넣어야한다.
    
    //치킨 -> 한식
    findMember.getFavoriteFoods().remove("치킨"); //치킨 지우고
    findMember.getFavoriteFoods().add("한식"); //새로 add
    //이것도 값타입이기때문에 통째로 갈아껴야한다 update를 할 수 없다.
    
    tx.commit();

    console (값타입 컬렉션 update)

    Hibernate: 
        /* delete collection row hello.jpa.Member.favoriteFoods */ delete 
            from
                FAVORITE_FOODS 
            where
                MEMBER_ID=? 
                and FOOD_NAME=?
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.favoriteFoods */ insert 
            into
                FAVORITE_FOODS
                (MEMBER_ID, FOOD_NAME) 
            values
                (?, ?)

    delete 한 후 insert 이것도 값타입이기때문에 통째로 갈아껴야한다 update를 할 수 없다.

    이것도 재밋는것은 컬렉션값만 변경해도 실제 DB쿼리가 날라가면서 뭐가 변경되었는지 알고JPA 바꿔줌

    마치 영속성 전이와 같다.

     

     

     

    JpaMain Class

                //old1 -> new1
                findMember.getAddressHistory().remove(new Address("old1","street","zipcode"));
                //기본적인 컬렌셕은 대부분 이런 대상을 찾을 때 equals를 사용한다.
                //그래서 아에 똑같은 것을 넣어야한다. 여기서 중요한게 이래서 equals와 hashCode를 제대로 구현해야한다.
                // 제대로 구현하지 않으면 안지워진다.!
                findMember.getAddressHistory().add(new Address("newCity1","street","zipcode"));
    
                tx.commit();

    console.

    Hibernate: 
        /* delete collection hello.jpa.Member.addressHistory */ delete 
            from
                ADDRESS 
            where
                MEMBER_ID=?
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.addressHistory */ insert 
            into
                ADDRESS
                (MEMBER_ID, city, street, zipcode) 
            values
                (?, ?, ?, ?)
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.addressHistory */ insert 
            into
                ADDRESS
                (MEMBER_ID, city, street, zipcode) 
            values
                (?, ?, ?, ?)

    분명 remove해서 하나만 지우고 하나만 add했는데 update를 왜 2번하지? 하는 의문이 들 수 있다.

    결론적으로는 원하는데로 한번만 바뀜.

    - 참고: 값 타입 컬렉션은 영속성 전에(Cascade) + 고아 객체 제 거 기능을 필수로 가진다고 볼 수 있다.

    - 이유는 값 타입 컬렉션의 제약사항을 보면 알 수있다.

     

     

     

    값 타입 컬렉션의 제약사항

    • 값 타입은 엔티티와 다르게 식별자 개념이 없다.

     

    • 값은 변경하면 추적이 어렵다.

     

    값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.

     -> ADDRESS 값을 변경하면 DB에서 쿼리를 날릴때 ADDRESS DB의 모든 값을 다지운다. 그다음 현재의 컬렉션의 최종 남은 데이터를 INSERT를 한다.

     -> city, street, zipcode 같은 경우 뭔가 값이 중간에 변경되면 추적이 거의 안된다, 어떤 ID가 있는게 아니기 때문이다. 중간에 값이 변경하면 db에서 중간에 얘를 찾아서 지우고 하기 힘들다.

     

    •    값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 함: null 입력X, 중복 저장X

     

     

    결론 : 값 타입 컬렉션 대안

    • 실무에서는 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려

       

    실제 운영에서 자주쓰는 방법

     

    AddressEntity 추가

    package hello.jpa;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    @Entity
    @Table(name = "ADDRESS") // 추가
    public class AddressEntity {
        @Id
        @GeneratedValue
        private Long id;
    
        private Address address; //값 타입
    
        public AddressEntity() {
        }
    
        public AddressEntity(Long id, Address address) {
            this.id = id;
            this.address = address;
        }
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public Address getAddress() {
            return address;
        }
    
        public void setAddress(Address address) {
            this.address = address;
        }
    }
    

    Member Class

        //값타입 매핑
    //    @ElementCollection //값 타입 컬렉션
    //    @CollectionTable(name = "ADDRESS" //테이블명 지정
    //            , joinColumns = @JoinColumn(name = "MEMBER_ID"))// @JoinColumn = ""외래키로 잡게된다.
    //    private List<Address> addressHistory =  new ArrayList<>();
    
        //엔티티 매핑
        @OneToMany(cascade = CascadeType.ALL,orphanRemoval = true)
        // 양방향으로 ManyToOne으로 해도 되지만 얘는 특별한 경우이기 때문에 caseCade로 정리
        @JoinColumn(name= "MEMBER_ID")
        private List<AddressEntity> addressHistory = new ArrayList<>();
        //이렇게하면 값타입보다 쿼리 최적화에 훨씬 유용하다
    

    JpaMain Class

                Member member =new Member();
                member.setUsername("member1");
                member.setHomeAddress(new Address("homeCity","street","zipcode"));
    
                member.getFavoriteFoods().add("치킨");
                member.getFavoriteFoods().add("족발");
                member.getFavoriteFoods().add("피자");
    
                member.getAddressHistory().add(new AddressEntity("old1","street","zipcode"));
                member.getAddressHistory().add(new AddressEntity("old2","street","zipcode"));
    
                em.persist(member);

    console

    Hibernate: 
        call next value for hibernate_sequence
    Hibernate: 
        call next value for hibernate_sequence
    Hibernate: 
        call next value for hibernate_sequence
    Hibernate: 
        /* insert hello.jpa.Member
            */ insert 
            into
                Member
                (city, street, zipcode, USERNAME, endDate, startDate, MEMBER_ID) 
            values
                (?, ?, ?, ?, ?, ?, ?)
    Hibernate: 
        /* insert hello.jpa.AddressEntity
            */ insert 
            into
                ADDRESS
                (city, street, zipcode, id) 
            values
                (?, ?, ?, ?)
    Hibernate: 
        /* insert hello.jpa.AddressEntity
            */ insert 
            into
                ADDRESS
                (city, street, zipcode, id) 
            values
                (?, ?, ?, ?)
    Hibernate: 
        /* create one-to-many row hello.jpa.Member.addressHistory */ update
            ADDRESS 
        set
            MEMBER_ID=? 
        where
            id=?
    Hibernate: 
        /* create one-to-many row hello.jpa.Member.addressHistory */ update
            ADDRESS 
        set
            MEMBER_ID=? 
        where
            id=?
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.favoriteFoods */ insert 
            into
                FAVORITE_FOODS
                (MEMBER_ID, FOOD_NAME) 
            values
                (?, ?)
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.favoriteFoods */ insert 
            into
                FAVORITE_FOODS
                (MEMBER_ID, FOOD_NAME) 
            values
                (?, ?)
    Hibernate: 
        /* insert collection
            row hello.jpa.Member.favoriteFoods */ insert 
            into
                FAVORITE_FOODS
                (MEMBER_ID, FOOD_NAME) 
            values
                (?, ?)

     

    일대다로 쿼리가 잘나가는 것을 볼 수 있다.

    <일대다 update쿼리부분만 자른것>

    Hibernate: 
        /* create one-to-many row hello.jpa.Member.addressHistory */ update
            ADDRESS 
        set
            MEMBER_ID=? 
        where
            id=?
    Hibernate: 
        /* create one-to-many row hello.jpa.Member.addressHistory */ update
            ADDRESS 
        set
            MEMBER_ID=? 
        where
            id=?

    여기선 update 쿼리가 나가는것은 어쩔 수 없다. 일대다 단방향 매핑이기 때문에다. 다른테이블에 외래키가 있기 때문에.

     

    그럼 값타입 컬렉션은 언제 쓰는가? -> 진짜 단순할때 selectbox에 치킨 피자 를 멅티로 select할수 있을 때

    그랄때 쓰는것 추적할 필요도 없고 값이 바껴도 업데이트 할 필요가 없을 때. 

    주소도 값이 잘변하지않지만 주소일 경우 주소이력을 관리해야할 수도 있다 이런것은 다 엔티티로 사용한다.

     

    • 일대다 관계를 위한 엔티티를 만들고, 여기에서 값 타입을 사용

     

    • 영속성 전이(Cascade) + 고아 객체 제거를 사용해서 값 타입 컬 렉션 처럼 사용

     

    • EX) AddressEntity

     

     

    정리

        • 엔티티 타입의 특징

        • 식별자O

        • 생명 주기 관리

        • 공유

    • 값 타입의 특징

        • 식별자X

        • 생명 주기를 엔티티에 의존

        • 공유하지 않는 것이 안전(복사해서 사용)

        • 불변 객체로 만드는 것이 안전

     

     

     

    값 타입은 정말 값 타입이라 판단될 때만 사용(생각보다 실무에서 값타입이 잘나오지 않는다.)

     

    엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안됨

     

    식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면 그것 은 값 타입이 아닌 엔티티

     

     

    'JPA' 카테고리의 다른 글

    #JPA-18 JPQL(Java Persistence Query Language)  (0) 2021.02.23
    #JPA-17 JPA가 지원하는 다양한 쿼리 방법  (0) 2021.02.23
    #JPA-15 속성 전이: CASCADE  (0) 2021.02.19
    #JPA-14 즉시 로딩과 지연 로딩  (0) 2021.02.19
    #JPA-13 프록시  (0) 2021.02.19

    댓글

Designed by Tistory.