Post

값 타입





참고



값 타입

  • 테이블의 컬럼이 되는 값을 말한다.
  • 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
      • 값 타입을 정의하는 곳에 붙인다.

img-description 임베디드 타입이 컬럼으로 들어간 DB 테이블

  • 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 엔티티를 추가했다.
  • 이제부터 책의 대여 기간의 시작과 대여 기간의 끝 + 대여해준 사서의 정보를 담을 수 있다.

img-description 임베디드 값 타입의 필드로 엔티티 타입이 들어온 경우의 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;

}


img-description @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 쿼리도 날아간다.

img-description 같은 인스턴스를 사용함으로써 날아간 두 번의 업데이트 쿼리


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<>();
}


img-description 값 타입 컬렉션이 저장되는 positions 테이블

  • 임베디드 값 타입의 컬렉션인 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.