• @Entity로 정의하는 객체
• 데이터가 변해도 식별자로 지속해서 추적 가능
ex) 회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식 가능
• int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
• 식별자가 없고 값만 있으므로 변경시 추적 불가
• 예) 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체
• 자바 기본 타입(int, double)
• 래퍼 클래스(Integer, Long)
• String
• ex) 좌표 값을 묶어서 position이라는 타입으로 커스텀을 하는 데 이 것이 임베디드 타입이다.
• 재사용이 가능하다.
• 높은 응집도
• Period.isWork()처럼 해당 값 타입만 사용하는 의미 있는 메소
드를 만들 수 있음
• 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티
티에 생명주기를 의존함
예제로 살펴보자!!!
period 오타.. 무튼 기간에 넣을 두 가지 데이터와, 주소에 넣을 두 가지 데이터를 선정 후 각각의 .class를 만든다.
물론 Member.class에서 묶어준 친구들은
//기간 period
@Embedded
private Period period;
//주소
@Embedded
private Address address;
이런식으로 바꿔준다.
사용하는 곳엔 @Embedded
, 선언한 곳엔 @Embeddable
DB에는 반영이 되지 않는다!
자 이제 DB에 값이 제대로 들어가는지 테스트를 해보자!
먼저 생성자를 만들어 줘야 하는데, 빈 생성자와 값을 집어넣을 생성자 두 개를 만들어줘야 한다.
Lombok 사용하면 @Noargs 랑 @Allargs ?? 두 개 사용하면 될듯??
값을 넣어주면 된다!
값은 당연히 잘 들어가있다.
만약 한 엔티티에서 같은 값 타입을 사용한다면??
Address타입을 두 개의 컬럼이 쓰고 있는데, 그럴때는 @AttributeOverrides
, @AttributeOverride
를 사용하면 된다.
member의 city만 바꾸려고 했는데 결과는 member1과 member2 둘 다
newcity로 바뀐다.
해결 방법은 값(인스턴스)을 복사해서 사용하면 된다.
그런데 실수로 잘못 넣는다면 ?? 그것을 컴파일러 단에서 막을 수 있나??
없다. 그래서 불변객체로 설계해야 한다. 생성자 주입?? 이 가장 나은듯?>>
(필요 시 찾아보자)
• 자바 컬렉션에 기본값이나, 임베디드 타입을 넣을 수 있는 것이다.
FAVORITE_FOOD, ADDRESS 에서 모든 속성이 PK 인데, 그 이유는
해당 테이블은 값 타입인데, 하나를 식별자로 두는 개념을 도입하면 엔티티가 될 수도 있기 때문이다.
즉, 테이블에 값들만 저장하고 이들을 묶어서 pk로 사용한다.
@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
사용하여 구현한다.
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity", "street", "10000"));
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");
member.getAddressHistory().add(new Address("old1", "street1", "10001"));
member.getAddressHistory().add(new Address("old2", "street2", "10002"));
em.persist(member);
tx.commit();
- Member에 소속된 값 타입들의 라이프 사이클은 Member에 의존한다.
- 값 타입은 별도로 persist를 할 필요가 없이 Member에서 값을 변경만 하면 자동으로 처리해준다.
또한 컬렉션 값 타입들은 디폴트 값이 지연로딩이다. 더 자세하게는 @ElementCollection
의 fetch 기본값이 LAZY이다.
따라서 Member에서 .find()를 해줬다고 addressHistory와 favoriteFoods에 대한 쿼리는 나오지 않는다.
컬렉션 값 타입들은 불변객체여야 한다.
따라서 수정을 할 때에는
Address address = findMember.getHomeAddress();
findMember.setHomeAddress(
new Address("newCity", address.getStreet(), address.getZipCode())
);
setCity() 해서 넣는 건 잘못된 방법이다.
이런식으로 새로 생성해서 넣어줘야 한다.
자 그럼 Set 과 List 수정법을 알아보자
Member findMember = em.find(Member.class, member.getId());
// 치킨 -> 한식
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");
Member findMember = em.find(Member.class, member.getId());
// old1 -> newCity1
findMember.getAddressHistory().remove(new Address("old1", "street1", "10001"));
findMember.getAddressHistory().add(new Address("newCity1", "street1", "10001"));
값 타입 컬렉션을 사용하지 말자!!
이유
값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
또한 중간에 값이 변경되었을 때 값을 추적하기가 어렵다.
@OrderColumn(name = "address_history_order")
이 어노테이션으로 해결할 수 있지만 그냥 쓰지말자 쓰지마쓰지마쓰지마
그래도 해야한다. 엔티티 타입 말고 값 타입으로 해야한다 하면
영속성 전이(Cascade) + 고아 객체 제거를 사용해서 값 타입 컬
렉션 처럼 사용하자.
@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> addressHistory = new ArrayList<>();
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class AddressEntity {
@Id @GeneratedValue
private Long id;
private Address address; // 값 타입
}
아 그리고 객체끼리 비교할 때 equals()를 선언을 해줘야 한다.
그냥하면 false. 이윤ㄴ????? 더 찾아보자..
//단축 키 있음.
@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());
}
Address를 가지고 있는 엔티티를 만들어서 연관관계를 맺어준 것.
1대다 다대1 로 묶어도 된다.
끝!!!!!
다음은 객체지향 쿼리 언어1, 2 를 정리 할 예정이다!
내일 다 듣고 연관관계쪽을 한 번 더 들어야겠다.
파이팅~