ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JPA (8) - 값 타입과 값 타입 컬렉션
    카테고리 없음 2024. 1. 11. 17:53

    - JPA에서는 Entity타입과, 값타입 존재

    - > @Entity로 정의하는 객체, 데이터가 변해도 식별자로 지속해서 추적 가능 (회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식가능)

    -> 기본 값 타입 : int,Integer,String

     

    1. 기본값 타입

     

    > 기본 값 타입 : 자바 기본 타입, 래퍼 클래스, String, 임베디드 타입, 컬렉션 타입 

     

    1.1 기본 값 타입 

     

    > 생명주기 엔티티 의존, 값타입은 공유해선 안된다 (회원 변경시 다른 회원 이름도 함께 변경되거나 이런거 조심)

    > 자바 기본 타입은 어차피 공유 발가능함 (래퍼나 String등 특수 클래스도 객체라서 공유가능할 것 같지만 안됨)

    > List같은거 운용할 때 조심하자

     

    1.2 임베디드 타입 (복합 값 타입)

     

    > 새로운 값 타입을 직접 정의 

    > JPA는 임베디드 타입이라함 

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

    @Entity
    public class Member {
    
    	//..
        
        @Embedded
        private Period period
        
        @Embedded
        private Address address
    
    }
    
    @Embeddable
    @Getter @Setter
    public class Period{
    
    	private LocalDateTime startDate;
        prrivate LocalDateTime endDate;
    }
    
    @Embeddable
    @Getter @Setter
    public class Address{
    	
        private String city;
        private String street;
        private String zipcode;
    }

     

    > 기본 생성자는 필수이다.

     

    장점: 재사용성, 높은 응집도, 메서드 만들어서 사용도 가능, 임베디드 타입을 포함한 모든 값 타입은 엔티티에 생명주기 의존 

     

    * DB입장에선 임베디드건 뭐건 똑같음 그냥 풀어서 저장함 

      객체와 테이블을 아주 세밀하게 매핑하는 것 뿐이다! 잘 설계된 ORM은 매핑한 테이블 수보다 클래스의 수가 더 많다.

     

    1.3 임베디드 타입과 연관관계

     

    출처: jpa기본편(김영한) - 인프런

     

    - 임베디드 타입안에 엔티티 집어넣을 수 있고, 임베디드 타입에 값들에 @Column속성 사용 가능

     

    * @AttributeOverride 

      한 엔티티에서 같은 임베디드 타입을 두번 사용한다고 생각해보자, 임베디드 자체는 변수명으로 구분 가능하지만, 임베디드 안에 값 타입들은 컬럼에 저장할 때 애매해진다. 이럴때 이 어노테이션으로 같은 임베디드 타입이더라도 컬럼을 다르게 저장할 수 있다.

     @AttributeOverrides({@AttributeOverride(name = "city",column = @Column(name = "CITY"))})

     

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

     

    2. 값 타입과 불변 객체 

     

    > 임베디드 타입 같은 값 타입을 여러 엔티티에서 공유시 매우 위험 (side effect 발생)

    > 값 타입의 실제 인스턴스인 값을 공유하는 것은 위험하다 (인스턴스 복사해서 사용하자)

     

    출처: jpa기본편(김영한) - 인프런

    2.1 객체 타입의 한계 

     

     > 항상 값을 복사해서 사용하면, 공유 참조로 부작용을 피할 수 있다.

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

     > 객체 타입을 참조 값을 직접 대입하는 것은 막을 방법이 없다! 공유 참조 피할 수 없음 복사해서 써야함

     

     

    2.2 불변 객체 

     

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

     > 값 타입은 불변 객체로 설계해야한다. 

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

     > 생성자로만 수정하고 수정자를 안만들면 그만이다!!! (Integer,String은 자바가 제공하는 대표적인 불변 객체)

     

    3. 값 타입의 비교 

     

    > 인스턴스가 달라도 값이 같으면 같다고 봐야함 

    > 동일성 비교 : 인스턴스 참조값 비교 == 사용

       동등성 비교 : 인스턴스의 값을 비교 equals()사용 -> 값타입은 a.equals(b)를 사용해서 동등성 비교 해야한다!! 

                              -> 임베디드 타입은 equals()메소드를 적절하게 재정의 합시다 

     

     

    4. 값 타입 컬렉션 

     

    출처: jpa기본편(김영한) - 인프런

     

     > 임베디드 타입 List, 혹은 기본 값 타입 List

     >  값 타입 하나 이상 저장시 사용 

     > @ElementCollection 혹은 @CollectionTable 사용 -> 사용 엔티티 기준으로 OneToMany 관계

     

    @Entity
    public class Member{
    
    
    	@ElementCollection
        @CollectionTable(name="FAVORITE_FOOD", joinColomns = @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<>(); //임베디드 타입 
    
    }

     

    - 각각 Set과 List를 사용하여 값 타입 컬렉션을 Member Entity에 생성했다. 

    - @CollectionTable를 사용하여 테이블명과 외래키 지정해주었다 (OneToMany라서 외래키 many쪽에 생성)

     

    4.1 값 타입 저장 

     

    	 Member member = new Member();
    	member.setName("member1");
                member.setHomeAddress(new Address("city1","street","1"));
    
                member.getFavoriteFoods().add("치킨");
    
                member.getAddressHistory().add(new Address("old1","street","1"));
                member.getAddressHistory().add(new Address("old2","street","1"));
    
                em.persist(member);

     

    4.2 값 타입 조회

     

    Member findMember = em.find(Member.class, member.getId());
    //지연로딩전략 사용 
    
    List<Addres> addressHistory = findMember.getAddressHistory();
    for(Address address : addressHistory){
    	System.out.println("address = "+address.getCity());
    }

     

    4.3 값 타입 수정 

     

    //String 컬렉션 - 일반적인 수정 (불변객체라 상관없다)
    findMember.getFavoriteFoods().remove("칰");
    findMember.getFavoriteFoods().remove("한식");
    
    //임베디드 타입 
    Address oldAdd =findMember.getHomeAddress();
    findMember.setHomeAddress(new Address("new",oldAdd.getStreet(),oldAdd.getZipcode()));
    
    //컬렉션을 수정하기 위해선 위와같이 새로운 값을 넣으면 됨 
    
    //임베디드 컬렉션 
    findMember.getAddressHistory().remove(new Address("old_1","street","123"));
    findMember.getAddressHistory().add(new Address("newCity1","street","123");
    
    //remove를 위해서 새로운 객체 만들어서 삭제하고 새로 넣었다.

     

    *remove는 내부적으로 equals()오 hashCode() 메서드 사용함 이거 잘 오버라이딩 해두자 

    * 값타입 컬렉션은 영속성 전이와 고아객체 기능을 필수로 가지고 있음

     

    4.4 값 타입 컬렉션 제약사항

     

    > 다른 엔티티와 다르게 식별자 개념이 없음

    > 값은 변경하면 추적하기 어렵다.

    > 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터 삭제하고, 값 타입 컬렉션에 있는 현재 값 모두 다시 저장함  (@OrderColumn()을 사용하여 컬렉션 값 타입으로 생성된 테이블에 식별자와 기본키를 넣어주는 방식 사용 가능 -> INSERT대신 UPDATE가 전송됨 다만, 원하는 대로 작동하지 않을 때가 있다.!)

    >값 타입 컬렉션을 매핑하는 테이블은 모든 칼럼을 묶어서 기본키를 구성해야한다. (null X, 중복 저장 X)

      (JPA 기본으로 만들어주는 값타입 테이블은 기본키가 없는 상태이다. 실제 DB에 반영할 때 모든 값들을 묶어서 기본키 구성해야함)

     

    4.5 값 타입 컬렉션 대안

     

    > 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려하자 

    > 일대다 관계를 위한 엔티티 만들고 여기에서 값 타입 사용하자 

    > 여기에 영속성 전이와 고아 객체 사용하면 값타입 컬렉션 처럼 사용 가능하다.

     

    //기존의 임베디드 타입을 감싸는 Entity생성 
    @Entity
    @Table(name="ADDRESS")
    public class AddressEntity{
    
    	@Id @GeneratedValue
        private Long id;
        
        private Address address;
        
        public AddressEntity(){
        }
    	
        public AddressEntity(Address address){
        	this.address = address;
        }
        
        public AddressEntity(String city, String street,String zipcode){
        	this.address = new Address(city,street,zipcode);
        }
    }
    @OneToMany(cascade = ALL, orphanRemoval=true)
    @JoinColumn(name = "MEMBER_ID")
    private List<AddressEntity> addressHistory = new ArrayList<>();
    
    //일대다 단방향 관계 (양방향이었다면, 주인은 일 쪽에둠) 
    //생명주기가 모두 member에 위임

     

     

    *** 값 타입은 정말 값 타입이라 판단될 때 사용하기

    -> 식별자가 필요하고, 지속해서 추적하고, 변경이 발생한다면 -> 값타입이 아닌 엔티티!!

Designed by Tistory.