티스토리 뷰
🌱 값 타입
JPA 의 데이터 타입은 2가지 이다.
- Entity - @Entity 로 영속 대상 객체
- 식별자를 통해 지속적인 추적이 가능 (dirty check 가능)
- 엔티티의 속성 값을 변경하더라도 같은 엔티티
- 값 타입 - 자바 기본 타입 or 객체
- 식별자가 없고 숫자나 문자같은 속성만 있으므로 추적 불가
- 변경시에 side effect를 주의 해야한다.
❓side effect?
래퍼 클래스나 primitive 자료형을 사용했었더라면 신경을 쓸 일이 없었다.
하지만 클래스 객체들의 경우 setter같은 메서드들이 열려 있을 경우
참조로 복사가 일어나기 때문에 한쪽에서 변경을 했을경우 다른쪽에서 변경이 일어나는
부수 효과가 발생 할 수 있다. (래퍼 클래스는 setter 메서드 존재 하지 않아 불가능)
JPA 에서의 값 타입은 3가지로 나뉜다.
- 기본 값 타입
- 자바 기본 타입
- 래퍼 클래스
- String
- 임베디드 타입(복합 값 타입)
- 사용자 정의 클래스
- 컬렉션 값 타입
- 하나 이상의 값 타입을 저장할 때...
🚀 임베디드 타입
JPA 에서 새로운 값 타입을 직접 정의해서 사용하는 타입
임베디드 타입도 int, String 처럼 값 타입이다.
Member 엔티티에서 사용자 정의 값타입을 만들어 보자
🖥 EmbeddedType 사용 X
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
// 근무 기간
@Temporal(TemporalType.DATE) java.util.Date startDate;
@Temporal(TemporalType.DATE) java.util.Date endDate;
// 집 주소 표현
private String city;
private String street;
private String zipcode;
}
를 변경 해보자
🖥 EmbeddedType 사용
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded Period workPeriod; // 근무 기간
@Embedded Address homeAddress; // 집 주소
}
@Embeddable
public class Period {
@Temporal(TemporalType.DATE) java.util.Date startDate;
@Temporal(TemporalType.DATE) java.util.Date endDate;
// ..
public boolean isWork(Date date){
//.. 값 타입을 위한 메소드를 정의할 수 있다.
}
}
@Embeddable
public class Address {
@Column(name = "city") // 매핑할 컬럼 정의 가능
private String city;
private String street;
private String zipcode;
// ..
}
값 타입의 장점?
해당 값 타입만 사용하는 의미 있는 메소드를 만들어서 사용하기 좋음
비지니스 로직에서 유효성 검사 같은거 하기 좋다.
=> 공통로직으로 단순화가 좋아진다.
임베디드 타입을 포함한 모든 값 타입은 엔티티의 생명주기에 의존
@Embeddable, @Embedded
@Embeddable : 값 타입을 정의하는 곳에 표시
@Embedded : 값 타입을 사용하는 곳에 표시
임베디드 타입은 기본 생성자가 필수다
연관관계 + 테이블 매핑
값타입은 엔티티의 값일 뿐이여서 값이 속한 엔티티의 테이블에 매핑한다.
한 테이블에 같이 소속(?) 된다고 말할 수 있다.
임베디드 타입은 값 타입을 포함하거나 엔티티를 참조할 수 있다.
임베디드 안에서 fk를 가질 수 있다는 말
🖥 엔티티를 참조하는 임베디드 타입
@Entity
public class Member {
@Embedded
Address address; // 임베디드 타입 포함
@Embedded
PhoneNumber phoneNumber; // 임베디드 타입 포함
// ...
}
@Embeddable
public class Address {
String street;
String city;
String state;
@Embedded
Zipcode zipcode; // 임베디드 타입 포함
}
@Embeddable
public class Zipcode {
String zip;
String plusFour;
}
@Embeddable
public class PhoneNumber {
String areaCode;
String localNumber;
@ManyToOne
PhoneServiceProvider provider; // 엔티티 참조
...
}
@Entity
public class PhoneServiceProvider {
@Id String name;
...
}
속성 재정의 @AttributeOverride
테이블 매핑 정보를 재정의 한다.
🖥 @AttributeOverride
// 집 주소와 회사 주소를 갖도록 할 경우.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded Address homeAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name="city", column=@Column(name="COMPANY_CITY")),
@AttributeOverride(name="street", column=@Column(name="COMPANY_STREET")),
@AttributeOverride(name="zipcode", column=@Column(name="COMPANY_ZIPCODE"))
})
Address CompanyAddress;
}
🤔 주의 사항
프로젝트를 진행 하던 와중 임베디드 타입을 썼는데
예를 들어 (Address 가 사용자 정의 객체로 되어있다고 가정) Address의 속성이 전부
null 이라고 한다면 객체 정보가 Address address = null 형식으로 되어있다.
만약 Address 내부의 속성이 전부 출력되고 null 이 표기 되기를 원한다면 yml에 다음 속성을 추가하자
spring.jpa.properties.hibernate.create_empty_composites.enabled = true
❗ side Effect의 방지
값타입을 안전하게 다룰 수 있기 위한 노력이다.
위에서 말했던 side Effect 를 방지 해보자
값타입을 여러 엔티티에서 공유하고 setter 를 열어 두게 된다면
원하지 않은 Entity에서 dirty checking이 되어버리는 이상한 현상이 나오게 된다.
방법 2가지를 살펴보자
- 깊은 복사의 사용
- 참조값을 복사하는 것이 아닌 값을 복사하여 사용하자
- 불변 객체의 사용
- setter 를 열지 않고 사용해 불변 객체를 만들자
JAVA 관련 사항 (비교)
자바가 제공하는 객체 비교는 2가지
- 동일성 비교 (참조값 비교), == 연산자를 사용
- 동등성 비교 (값 비교), 인스턴스의 값을 비교, equals() 메서드 사용
값 타입의 동등성을 비교하기 위해서는 equals() 메소드를 재정의할 때 모든 필드 값을 비교하도록 재정의
hashCode() 또한 재정의 하여 동일성 비교 또한 같게 만들자.
값 타입 컬렉션
객체에서의 컬렉션은 항상 테이블이 생성된다.
값 컬렉션은 테이블 컬럼 구조가 다음과 같다
속성들 모두가 PK 값으로 걸린다.
값 타입을 가지고 있는 entity 의 pk 값을 fk 및 pk 로 추가로 적용한다.
🖥 값 타입 컬렉션 테이블 생성
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Embedded
private Address homeAddress;
@ElementCollection
@CollectionTable(name = "FAVORITE_FOODS",
joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME") // FAVORITE_FOODS 테이블에 값으로 사용되는 컬럼이 FOOD_NAME 하나 뿐이라서 @Column을 사용해 컬럼명을 지정할 수 있다.
private Set<String> favoriteFoods = new HashSet<String>();
@ElementCollection
@CollectionTable(name = "ADDRESS",
joinColumns = @JoinColumn(name = "MEMBER_ID"))
private Set<Address> addressHistory = new ArrayList<Address>();
//...
}
@Embeddable
public class Address {
@Column
private String city;
private String street;
private String zipcode;
//...
}
😱 값 타입 컬렉션은 영속성 전이 + 고아 객체 제거 기능을 필수로 가지게 된다.
값 타입 수정하기
- 임베디드 값 타입 수정
임베디드 값 타입은 Entity 테이블과 매핑했으므로 MEMBER 테이블만 UPDATE 한다 - 임베디드 값 타입 컬렉션 수정
값 타입은 불변해야 해서 컬렉션에서 기존 주소를 삭제하고 새로운 주소를 등록한다.
이때 값 타입은 equals(), hashCode()를 꼭 구현해야 한다.
값 타입 컬렉션의 수정
값타입은 추적이 불가능 하므로 값 타입 컬렉션 테이블을 update 할 수없다(dirty checking 시점에서)
JPA는 추적 불가능한 성격을 가진 값 타입 컬렉션을 수정하기 위해서 해당 엔티티에 속한 모든 값타입을
DB에서 삭제하고 다시 모든 값을 저장한다.
또한 위에서 말했듯이 모든 column들을 PK로 등록하기 때문에 칼럼에 null 을 입력할 수 없다. 중복 또한 허용하지
않는다.
=> 고로 값 타입 컬렉션을 사용할 일 이있다면 일대다 매핑을 하되 연관관계의 주인을 일쪽으로
가져 가게 하고 cascade + orphanRemove 특성을 가져가도록 하자.
'Spring > JPA' 카테고리의 다른 글
커넥션 풀 (0) | 2022.08.23 |
---|---|
JDBC 사용해보기 (0) | 2022.08.23 |
프록시와 연관관계 관리 (0) | 2022.08.23 |
고급 매핑 (0) | 2022.08.23 |
JPA - 연관관계 이해하기 (0) | 2022.08.03 |
- Total
- Today
- Yesterday