Backend

[JPA] 값 타입(기본값, @Embedded, 값타입 컬렉션)

연_우리 2022. 2. 22. 00:44
반응형

목차

     

     

     

     

    기본값 타입

    단순히 값으로만 사용하는 기본 타입이나 객체이다.

    식별자가 없고 값만 있기때문에 변경 추적 불가능하다.

     

     

    임베디드타입

    엔티티이지만, 기본값처럼 단순히 값으로만 사용하는 객체이다.

     = 변경 추적 불가능하다.

     

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

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

    기본 생성자를 필수로 만들어주어야한다.

    @Entity
    public class Member{
        @Id @GeneratedValue
        @Column(name="member_id")
        private Long id;		//값 타입
        private String name;	//값 타입
        
        private String city;
        private String street;
        private String zipcode;
    }

    city, street, zipcode를 Address라는 엔티티로 묶어내자

     

    @Entity
    public class Member{
        @Id @GeneratedValue
        @Column(name="member_id")
        private Long id;		//값 타입
        private String name;	//값 타입
        
        @Embedded
        private Address address;	//임베디드 타입
    }
    
    @Embeddable
    public class Address{
        private String city;
        private String street;
        private String zipcode;
    }

     

     

    임베디드 타입으로 묶어내도 테이블 매핑은 똑같다.

     

    임베디드타입 VS @MappedSuperclass

    @MappedSuperclass는 "상속"이기 때문에 다중상속이 불가능하다.

    @Embedded는 "값"이기 때문에 같은 엔티티를 다중으로 사용할 수 있다.

     

    @Entity
    public class Member{
        @Id @GeneratedValue
        @Column(name="member_id")
        private Long id;		//값 타입
        private String name;	//값 타입
        
        @Embedded
        private Address homeaddress;	//임베디드 타입
        
        @Embedded
        private Address workaddress;	//임베디드 타입
    }
    
    @Embeddable
    public class Address{
        private String city;
        private String street;
        private String zipcode;
    }

    이때는 homeAddress와 workAddress의 컬럼명이 겹치기때문에 

    @AttributeOverrides, @AttributeOverride를 사용해서 컬럼명을 재정의 해야한다.

     

     

    @AttributeOverride

    @Entity
    public class Member{
        @Id @GeneratedValue
        @Column(name="member_id")
        private Long id;		//값 타입
        private String name;	//값 타입
        
        @Embedded
        private Address homeaddress;	//임베디드 타입
        
        @Embedded
        @AttributeOverrides({
        	@AttributeOverride(name="city", column=@Column(name="work_city")),
            @AttributeOverride(name="street", column=@Column(name="work_street")),
            @AttributeOverride(name="zipcode", column=@Column(name="work_zipcode"))
        })
        private Address workaddress;	//임베디드 타입
    }
    
    @Embeddable
    public class Address{
        private String city;
        private String street;
        private String zipcode;
    }

     

     

     

    값타입과 불변객체

    임베디드 타입값은 여러 엔티티에서 공유하면 위험하다.

    Address address = new Address("city", "street", "1000");
    
    Member member1 = new Member();
    member1.setName("member1");
    member1.setAddress(address);	//⭐⭐⭐
    em.persist(member1);
    
    Member member2 = new Member();
    member2.setName("member2");
    member2.setAddress(address);	//⭐⭐⭐
    em.persist(member2);
    
    member1.getAddress().setCity("newCity");
    
    System.out.println(member1.getAddress().getCity());	//newCity
    System.out.println(member2.getAddress().getCity());	//newCity 	❗❗❗❗❗

     

     

     

    반드시 값을 복사해서 사용해야한다!!!!!!!

    기본타입은 값을 복사하지만, 객체타입은 참조를 전달한다는 것을 잊지말자

    Address address = new Address("city", "street", "1000");
    
    Member member1 = new Member();
    member1.setName("member1");
    member1.setAddress(address);	//⭐⭐⭐
    em.persist(member1);
    
    Address copyAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode());
    
    Member member2 = new Member();
    member2.setName("member2");
    member2.setAddress(copyAddress);	//⭐⭐⭐
    em.persist(member2);
    
    member1.getAddress().setCity("newCity");
    
    System.out.println(member1.getAddress().getCity());	//newCity
    System.out.println(member2.getAddress().getCity());	//City 	❗❗❗❗❗

     

    하지만.. 누군가 member2.setAddress(address); 를... 실수로 입력하면 어떡하지?!

    객체타입을 아예 수정할 수 없게 만들어버리면 된다.

     

     

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

    생성자로만 값을 설정하고, setter를 만들지 않으면 된다.

    값을 변경하고 싶다면, 아예 copy메서드를 만들자

    @Entity
    public class Member{
        @Id @GeneratedValue
        @Column(name="member_id")
        private Long id;		//값 타입
        private String name;	//값 타입
        
        @Embedded
        private Address homeaddress;	//임베디드 타입
        
        ...getter만 생성...	//⭐⭐⭐
        
        public void copyAddress(){
        	~~~
        }
    }
    
    @Embeddable
    public class Address{
        private String city;
        private String street;
        private String zipcode;
    }

     

     

     

    값타입 컬렉션

    회원이 좋아하는 음식을 받으려면 어떻게하면될까?

    Member에 List<String> foods 이런식으로 추가하면된다.

    테이블에서는 컬렉션을 저장할 수 없기때문에 member_id와 food(varchar)를 가진 테이블을 하나 생성해야한다.

    @Entity
    public class Member{
        @Id @GeneratedValue
        @Column(name="member_id")
        private Long id;		//값 타입
        private String name;	//값 타입
        
        @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")
        )
        @Column(name = "food_name")
        private List<Address> addressList = new ArrayList<>();	//컬렉션 타입
       
    }

     

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

    값타입 컬렉션도 지연로딩 전략 사용 가능하다.

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

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

    ⚠ 값타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본키로 구성해야한다. 그래야 null과 중복저장을 막을 수 있다.

     

     

    => 되도록 값타입 컬렉션 대신 일대다 관계로 풀어내자

         차라리 엔티티로 한번 랩핑하자.

     

    @Entity
    public class Member{
        @Id @GeneratedValue
        @Column(name="member_id")
        private Long id;		//값 타입
        private String name;	//값 타입
        
        @ElementCollection
        @CollectionTable(name ="favorite_food",
        	joinColumns = @JoinColumn(name="member_id")
        )
        @Column(name = "food_name")
        private Set<String> favoriteFoods = new HashSet<>();	//컬렉션 타입
        
        @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
        @JoinColumn(name = "member_id")
        private List<AddressEntity> addressList = new ArrayList<>();	//컬렉션 타입
    }
    
    @Entity
    @Table(name = "address")
    public class AddressEntity{
        @Id @GeneratedValue
        @Column(name="member_id")
        private Long id;		//값 타입
        
        private Address address;
    }

     

     

     

     

     

    값타입은 값타입이라 판단될 때만 사용하자.

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

    식별자가 필요하고, 지속해서 값을 추적하고 변경해야한다면 엔티티이다.

     

     

     

    반응형
    • 네이버 블러그 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 구글 플러스 공유하기
    • 카카오톡 공유하기