기본 값 타입
jpa의 데이터 타입 분류
- 엔티티 타입
- @Entity로 정의하는 객체
- 데이터가 변해도 식별자로 지속해서 추적 가능
- 예) 회원 엔티티의 키나 나이값을 변경해도 식별자로 인식 가능
- 값 타입
- int Integer String 처럼 단순히 값으로 사용하는 자바 기본타입이나 객체
- 식별자가 없고 값만 있으므로 변경시 추적 불가
- 예) 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체
값 타입 분류
- 기본 값타입
- 자바 기본타입 (int, double)
- 래퍼 클래스
- String
- 임베디드 타입(embedded type, 복합 값타입)
- 컬렉션 값타입 (collection value type)
기본 값 타입
- 예) String name, int age
- 생명주기를 엔티티에 의존
- 예) 회원을 삭제하면 이름, 나이 필드도 함께 삭제
- 값 타입은 공유하면 X
- 예) 회원 이름 변경시 다른 회원의 이름도 함께 변경되면 안됨
참고 : 자바의 기본 타입을 절대 공유 X
임베디드 타입 (복합 값 타입)
임베디드 타입
- 새로운 값 타입을 직접 정의 가능
- JPA는 임베디드 타입이라 함
- 주로 기본값 타입을 모아서 만들어서 복합 값 타입이라고도 함
- int, String 과 같은 값 타입
- 중복되는 값들을 하나의 타입으로 선언해줘 작성 코드를 줄여준다.
예를 들자면 다음과 같다.
package hellojpa;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Member {
@Id @GeneratedValue @Column(name = "member_id")
private Long id;
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
private LocalDateTime startDate;
private LocalDateTime endDate;
private String city;
private String strret;
private String zipcode;
}
다음과 같은 구조를 가지고 있는 엔티티가 있다.
해당 엔티티의 Date와 주소들은 공통되는 내용이라고 볼 수 있다.
이 코드를 다음과 같이 나눠준다.
package hellojpa;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Member {
@Id @GeneratedValue @Column(name = "member_id")
private Long id;
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
// 기간
@Embedded
private Period workperiod;
// 주소
@Embedded
private Address homeaddress;
}
package hellojpa;
import jakarta.persistence.Embeddable;
@Embeddable
public class Address {
// 주소
private String city;
private String strret;
private String zipcode;
}
package hellojpa;
import jakarta.persistence.Embeddable;
import java.time.LocalDateTime;
@Embeddable
public class Period {
// 기간
private LocalDateTime startDate;
private LocalDateTime endDate;
}
값을 사용하는 곳엔 @Embedded 어노테이션을 선언해주었고 값으로 사용될 객체는 @Embeddable 어노테이션을 선언해준 모습이다.
이렇게 선언해주면 처음 선언했던 엔티티와 동일한 테이블이 생성되게 된다.
그리고 다음과 같은 코드를 실행하면 각 컬럼에 값이 할당되는 모습을 볼 수 있다.
Member member = new Member();
member.setUsername("hello");
member.setHomeaddress(new Address("test1", "test2", "test3"));
member.setWorkperiod(new Period());
em.persist(member);
임베디드 타입과 테이블 매핑
- 임베디드 타입은 엔티티의 값일 뿐이다
- 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다.
- 객체와 테이블을 아주 세밀하게 매핑하는 것이 가능 (find-grained)
- 잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많음
- 값 타입 내부 다른 엔티티를 포함시킬 수도 있다
@AttributeOberride: 속성 재정의
- 한 엔티티에서 같은 값 타입을 사용하면 컬럼명이 중복된다. 이를 예방하기 위해 해당 어노테이션을 정의해준다.
@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;
이런식으로 각각 테이블 컬럼명과 매핑해주면 되는데 자주 사용되진 않는다고 한다.
값 타입과 불변객체
값 타입 공유 참조
- 임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험함
- 부작용으로 side effect발생함
Address address = new Address("test1", "test2", "test3");
Member member = new Member();
member.setUsername("member1");
member.setHomeaddress(address);
em.persist(member);
Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeaddress(address);
em.persist(member2);
member.getHomeaddress().setCity("newCity");
해당 코드를 실행해보면 member와 member2의 값이 모두 변경된다.
이를 해결하기 위해서는 다음과 같은 방법이 있다
Address address = new Address("test1", "test2", "test3");
Member member = new Member();
member.setUsername("member1");
member.setHomeaddress(address);
em.persist(member);
Address copyAddress = new Address(address.getCity(), address.getStrret(), address.getZipcode());
Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeaddress(copyAddress);
em.persist(member2);
member.getHomeaddress().setCity("newCity");
이렇게 새로운 인스턴스를 생성해서 넣어주면 개별적인 Address가 들어가게 되서 member의 city만 newCity로 변경되게 된다
객체 타입의 한계
- 항상 값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용을 피할 수 있다.
- 문제는 임베디드 타입처럼 직접 정의한 값 타입은 자바의 기본 타입이 아니라 객체 타입이다.
- 자바 기본타입에 값을 대입하면 값을 복사한다.
- 객체타입은 참조값을 직접 대입하는 것을 막을 방법이 없다
- 객체의 공유참조는 피할 수 없다.
불변 객체
- 객체타입을 수정할 수 없게 만들면 부작용을 원천 차단
- 생성 시점 이후 절대 값을 변경할 수 없는 객체
- 생성자로만 값을 설정하면 된다
- Integer,String은 자바가 제공하는 대표적인 불변객체다
@Embeddable
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Address {
// 주소
private String city;
private String strret;
private String zipcode;
}
이렇게 Getter와 생성자만 남겨준다
값 타입의 비교
- 값 타입 : 인스턴스가 달라도 그 안에 값이 같으면 같은것으로 봐야함.
- 동일성 비교 : 인스턴스의 참조값을 비교, == 사용
- 동등성 비교 : 인스턴스의 값을 비교, equals() 사용
- 동등성을 비교해야함
값 타입 컬렉션
값 타입 컬렉션의 선언은 다음과 같다
package hellojpa;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Member {
@Id @GeneratedValue @Column(name = "member_id")
private Long id;
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
// 기간
@Embedded
private Period workperiod;
// 주소
@Embedded
private Address homeAddress;
// 컬렉션을 저장할 수 있는 테이블을 생성하겠다
@ElementCollection
// 그 테이블의 이름읜 FAVORITE_FOOD이고 PK는 MEMBER_ID이다
// 그리고 String이 저장될 컬럼의 이름은 FOOD_NAME이다
@CollectionTable(name = "FAVORITE_FOOD", joinColumns =
@JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME")
private Set<String> favoriteFoodss = 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("member");
member.setHomeAddress(new Address("test1","test2","test3"));
member.getFavoriteFoodss().add("치킨");
member.getFavoriteFoodss().add("피자");
member.getFavoriteFoodss().add("햄버거");
member.getFavoriteFoodss().add("먹고싶다");
member.getAddressHistory().add(new Address("old1","test2","test3"));
member.getAddressHistory().add(new Address("old2","test2","test3"));
em.persist(member);
이런식으로 member만 insert해도 내가 선언해준 모든 테이블에 insert 쿼리가 같이 들어가게 된다.
값 타입 컬렉션 사용
- 값 타입 컬렉션도 지연로딩 전략을 사용한다. (모든 컬렉션은 기본적으로 LAZY가 걸려있다 똑똑하네)
- 값 타입 컬렉션은 영속성 전이 (Cascade) + 고아객체 제거기능을 필수로 가진다고 볼 수 있다.
- 값 타입의 업데이트에서 값타입은 불변 객체임으로 setter 메소드의 사용이 아닌 새로운 객체를 생성해서 다시 넣어줘야 한다.
Member member = new Member();
member.setUsername("member");
member.setHomeAddress(new Address("test1","test2","test3"));
member.getFavoriteFoodss().add("치킨");
member.getFavoriteFoodss().add("피자");
member.getFavoriteFoodss().add("햄버거");
member.getFavoriteFoodss().add("먹고싶다");
member.getAddressHistory().add(new Address("old1","test2","test3"));
member.getAddressHistory().add(new Address("old2","test2","test3"));
em.persist(member);
em.flush();
em.clear();
System.out.println("==================start====================");
Member findMember = em.find(Member.class, member.getId());
findMember.setHomeAddress(new Address("update1","update1","update1"));
스트링 같은 경우도 예외는 없다.
findMember.getFavoriteFoodss().remove("치킨");
findMember.getFavoriteFoodss().add("오므라이스");
이런식으로 수정하고자 하는 값을 지우고 새로운 값을 추가해주는 방법밖에 없다.
값타입을 가지고 있는 콜렉션은 이거보다 더하다
findMember.getAddressHistory().remove(new Address("old1","test2","test3"));
이런식으로 모든 값이 일치하는 address를 찾아서 지워야 한다
하지만 이런 방법으로 삭제를 해버리면 해당 테이블을 모두 드랍하고 다시 insert를 실행하기 때문에 비효율적인 쿼리가 나가게 된다.
값 타입 컬렉션의 제약사항
- 값 타입은 엔티티와 다르게 식별자 개념이 없다
- 값은 변경하면 추적이 어렵다.
- 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다
- 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 함: null입력 X, 중복 저장X
값 타입 컬렉션 대안
- 실무에서는 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려
- 일대다 관계를 위한 엔티티를 만들고, 여기에서 값 타입을 사용
- 영속성 전이 (Cascade) + 고아객체 제거를 사용해서 값 타입 컬렉션처럼 사용
'코딩딩 > Spring' 카테고리의 다른 글
JPQL (0) | 2023.11.18 |
---|---|
객체지향 쿼리 언어 (JPQL) (0) | 2023.11.13 |
프록시 (0) | 2023.11.08 |
고오급 매핑 (0) | 2023.11.07 |
다양한 연관관계 매핑 (0) | 2023.11.05 |