목차
기본값 타입
단순히 값으로만 사용하는 기본 타입이나 객체이다.
식별자가 없고 값만 있기때문에 변경 추적 불가능하다.
임베디드타입
엔티티이지만, 기본값처럼 단순히 값으로만 사용하는 객체이다.
= 변경 추적 불가능하다.
@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;
}
값타입은 값타입이라 판단될 때만 사용하자.
엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안된다.
식별자가 필요하고, 지속해서 값을 추적하고 변경해야한다면 엔티티이다.
'Backend' 카테고리의 다른 글
자바, Spring Boot로 크롤링하기 - Jsoup 이용 (정적페이지) (0) | 2022.03.07 |
---|---|
[Spring Security] OAuth 카카오 로그인하기 (0) | 2022.02.23 |
[JPA] 프록시와 즉시로딩, 지연로딩 / 영속성 전이와 고아객체 (0) | 2022.02.21 |
[JPA] 상속관계 매핑, 공통 속성 매핑 (0) | 2022.02.21 |
[JPA] 다양한 연관관계 매핑(다대일, 일대일, 다대다) (0) | 2022.02.21 |