값 타입
참고
값 타입
- 테이블의 컬럼이 되는 값을 말한다.
- int, Integer, String 등 기본 타입, 래퍼 클래스 등을 사용한다.
- 생명 주기를 엔티티에 의존한다.
- 엔티티가 생성되면 역시 생성
- 엔티티가 삭제되면 같이 삭제
- 값을 공유하지 않는 것이 안전하다.
- 자바의 기본 타입은 값을 복사하기 때문에 안전하다.
- 자바의 래퍼 타입은 값의 변경을 허용하지 않기 때문에 안전하다. (불변 객체)
1
2
3
4
5
6
7
8
9
10
11
@Entity
class Book {
// ...
private String title;
private int bookNumber;
}
- 이런 애들을 말한다.
임베디드 값 타입
- 값 타입을 새로 정의해서 사용하는 방법
- 클래스로 값 타입을 정의해서 재사용성과 응집성을 늘린다.
- 임베디드 값 타입에 메서드를 정의하여 사용할 수 있다.
- 디폴트 생성자를 필수적으로 가져야 한다.
1
2
3
4
5
6
7
8
9
@Entity
class Book {
// ...
@Embedded // 값 타입을 사용하는 곳에 붙이는 어노테이션
private RentalPeriod rentalPeriod;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@NoArgsConstructor // 필수적인 기본 생성자
@Embeddable // 값 타입을 정의하는 곳에 붙이는 어노테이션
class RentalPeriod {
@Column(name = "startDate")
private LocalDate rentalStartDate;
@Column(name = "endDate")
private LocalDate rentalEndDate;
public boolean isRented() { // 메서드 정의 가능
// ...
}
}
- 이런 식으로 어노테이션으로 정의해줄 수 있다.
- @Embedded
- 값 타입을 사용하는 곳에 붙인다.
- @Embeddable
- 값 타입을 정의하는 곳에 붙인다.
- @Embedded
- DB 테이블에는 임베디드 타입의 속성들이 컬럼으로 들어간 것을 볼 수 있다.
- @Column 어노테이션도 제대로 동작하는 것을 볼 수 있다.
- 임베디드 값 타입의 필드로 기본 값 타입은 위에서 봤듯이 당연히 들어갈 수 있다.
- 그리고 또 특이하게도 엔티티 타입도 들어갈 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Entity
class Librarian {
// ...
}
@Entity
class Book {
// ...
@Embedded
private RentalPeriod rentalPeriod;
}
@Embeddable
class RentalPeriod {
// ...
@ManyToOne
private Librarian librarian; // 임베디드 타입의 필드로 엔티티 타입
}
- 위의 예제에서 Librarian 엔티티를 추가했다.
- 이제부터 책의 대여 기간의 시작과 대여 기간의 끝 + 대여해준 사서의 정보를 담을 수 있다.
임베디드 값 타입의 필드로 엔티티 타입이 들어온 경우의 DB 테이블
- 일반 연관 관계 매핑처럼 외래키를 참조하는 형태로 테이블에 찍힌다.
@AttributeOverride
- 임베디트 값 타입을 여러개 사용하고 싶은 경우가 있을 수도 있다.
- 예를 들어 주소 정보를 나타내는 임베디드 값이 있다고 할 때,
- 집 주소, 학교 주소, 회사 주소 등 여러 곳의 주소를 사용하는데 임베디드 값 타입을 재사용하고 싶다.
- 그럴 때 사용하는 것이 @AttributeOverride이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Embeddable
class Address {
private String 시;
private String 구;
private String 동;
}
@Entity
class Student {
// ...
@Embedded
private Address homeAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "시", column = @Column("학교_시")),
@AttributeOverride(name = "구", column = @Column("학교_구")),
@AttributeOverride(name = "동", column = @Column("학교_동"))
})
private Address schoolAddress;
}
@AttributeOverride를 적용한 DB 테이블
- 원래라면 같은 이름의 컬럼을 여러개 가질 수 없기 때문에 에러가 날 것이다.
- 하지만 여기선 컬럼의 이름을 다르게 정의해줬기 때문에 같은 임베디드 타입을 재사용할 수 있다.
값 타입과 불변 객체
- 하나의 값 타입 인스턴스를 여러 엔티티가 공유해서 사용하는 것은 위험하다.
- 값 타입은 말 그대로 값으로써만 사용해야 한다.
- 부작용의 우려가 있다.
1
2
3
4
5
6
7
8
9
book1.setRentalPeriod(rentalPeriod1); // book1은 2022-11-20부터 2022-11-26까지 대여 기간을 갖는다.
book2.setRentalPeriod(rentalPeriod1); // book2도 book1과 대여 기간이 같아서 그냥 같은 rentalPeriod1 인스턴스를 사용하기로 했다.
// ...
book2.getRentalPeriod().extendRentalDays(3); // book2는 대여 기간을 3일 연장했다.
- 이 경우 book1과 book2가 같은 RentalPeriod 인스턴스를 사용하기 때문에
- book1의 대여 기간도 3일 연장된다.
- book2가 대여 기간을 연장하고 커밋하면 실제로 book1에 대한 update 쿼리도 날아간다.
같은 인스턴스를 사용함으로써 날아간 두 번의 업데이트 쿼리
1
2
3
4
5
6
7
8
9
10
11
12
13
RentalPeriod rentalPeriod1 = new RentalPeriod(LocalDate.of(2022, 11, 20), LocalDate.of(2022, 11, 26));
book1.setRentalPeriod(rentalPeriod1); // book1은 2022-11-20부터 2022-11-26까지 대여 기간을 갖는다.
RentalPeriod rentalPeriod2 = new RentalPeriod(rentalPeriod1.getStartDate(), rentalPeriod1.getEndDate());
book2.setRentalPeriod(rentalPeriod2); // book2도 book1과 대여 기간이 같아서 값을 복사해서 사용했다.
// ...
book2.getRentalPeriod().extendRentalDays(3); // book2는 대여 기간을 3일 연장했다.
- 이렇게 값을 복사해서 사용해야 부작용을 방지할 수 있다.
자바의 참조 타입을 사용하는 경우에는 이런 부작용이 쉽게 일어나기 때문에 방지해야 한다.
- 값 타입을 불변 객체로 만들어줌으로써 피할 수 있다.
- 생성자로 생성만 하고 수정자(setter)를 만들지 않는다.
값 타입 컬렉션
- 값 타입을 컬렉션에 담아 사용하는 것
- @ElementCollection, @CollectionTable 어노테이션을 사용한다.
- DB 테이블은 컬렉션이 구현되어있지 않기 때문에 별도의 테이블을 만들어 관리한다.
- 엔티티의 id를 fk이자 pk로 잡고,
- 값 타입의 모든 컬럼을 pk로 잡아 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class BaseballPlayer {
// ...
@ElementCollection
@CollectionTable(
name = "batting_stats",
joinColumns = @JoinColumn(name = "batter_id")
)
private List<Stat> statList = new ArrayList<>();
@ElementCollection
@CollectionTable(
joinColumns = @JoinColumn(name = "player_id")
)
@Column(name = "player_comments")
private List<String> comments = new ArrayList<>();
}
- 임베디드 값 타입의 컬렉션인 statList와
일반 값 타입의 컬렉션인 comments의 테이블이 각각 만들어진다.
- statList의 테이블은 이름을 batting_stat으로 정해준걸 확인할 수 있고
comments의 테이블은 이름을 정해주지 않아서 BASEBALL_PLAYER_COMMENTS로 만들어진걸 볼 수 있다.
- @JoinColumn을 속성으로 넘겨줘 각각 batter_id와 player_id로 fk의 컬럼명을 지어줬고
- 일반 값 타입의 컬렉션인 comments는 @Column 어노테이션을 줄 수 있어 comments의 컬럼명이 player_comments인걸 볼 수 있다.
This post is licensed under CC BY 4.0 by the author.