(spring) (자바 ORM 표준 JPA 프로그래밍 - 기본편_04)

전성영·2022년 6월 8일
0

spring

목록 보기
15/31

JPA의 데이터 타입 분류

엔티티 타입

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

값 타입

• int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
• 식별자가 없고 값만 있으므로 변경시 추적 불가
• 예) 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체

값 타입 분류

기본값 타입

• 자바 기본 타입(int, double)
• 래퍼 클래스(Integer, Long)
• String

임베디드 타입(embedded type, 복합 값 타입)

• 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로 바뀐다.

해결 방법은 값(인스턴스)을 복사해서 사용하면 된다.

그런데 실수로 잘못 넣는다면 ?? 그것을 컴파일러 단에서 막을 수 있나??
없다. 그래서 불변객체로 설계해야 한다. 생성자 주입?? 이 가장 나은듯?>>
(필요 시 찾아보자)

컬렉션 값 타입(collection value type)

• 자바 컬렉션에 기본값이나, 임베디드 타입을 넣을 수 있는 것이다.

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 수정법을 알아보자

Set

Member findMember = em.find(Member.class, member.getId());

// 치킨 -> 한식 
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");

List

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 를 정리 할 예정이다!
내일 다 듣고 연관관계쪽을 한 번 더 들어야겠다.
파이팅~

profile
Slow and Steady

0개의 댓글