JPA 상속관계 매핑 전략

web/JPA|2018. 10. 31. 00:58

객체 지향으로 데이터베이스 중심 매핑을 변경하기 위해서 가장 애매한게 상속이다. 이런 상속관계속에서 테이블로 구현할 3가지 방법을 선택할 있다.


1) 각각의 테이블로 변환 : 각각을 모두 테이블로 만들고 조회할 조인을 사용.

2) 통합 테이블로 변환 : 테이블을 하나만 사용해서 통합 

3) 서브타입 테이블로 변환 : 서브 타입마다 하나의 테이블을 만드는 방식.



순서대로 하나씩 정리해보자.




각각의 테이블로 변환 (조인전략)

- 부모와 각각의 자식 엔티티를 모두 각자의 테이블로 만들고 부모의 기본키와 자식의 외래키를 사용하여 조인하여 사용한다.

-  자식 엔티티의 타입을 구별하기 위한 DTYPE 컬럼을 구분컬럼으로 추가하여 사용한다. (없어도 무관)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Culture {
  
  @Id @GeneratedValue
  @Column(name = "CULTURE_ID")
  private Long id;
  
  private String name;
  private int price;
  
}
 
cs

-> 부모 추상 클래스 Culture 선언된 애노테이션에 대한 설명은 다음과 같다.


1
2
3
4
5
6
7
8
9
10
11
12
@Inheritance(strategy = InheritanceType.JOINED) : 조인전략 사용을 의미
@DiscriminatorColumn(name = "DTYPE") : 부모 클래스에 구분 컬럼으로 써 자식 테이블을 구분할 때 사용할 키로 사용된다. 기본값은 DTYPE이며 바꿔서 사용가능 (해당 기능을 사용하지 않아도 무관하다.)
 
 
@Entity
@DiscriminatorValue("M")
public class Movie {
 
  private String artiest;
  private String genre;
 
}
cs


기본적으로 사용하는 자식클래스 형식이다. 기본적으로 부모 테이블의 ID 컬럼명을 승계받아 사용 하지만 만약 변경하고 싶은 경우 클래스 위에 @PrimaryKeyJoinColumn(name = "MOVIE_ID")처럼 정의해서 사용할 있다.


조인전략의 장점은 저장공간을 효율적으로 관리하거나 테이블의 정규성이 지켜진다는 점이 있지만 조회할 조인이 많아지고 조회 쿼리시 귀찮아지며 수정이 발생하면 부모와 자식 테이블 두번을 해주어야한다.

 


 통합 테이블로 변환 (단일 테이블 전략)

부모의 속성과 자식의 속성을 하나의 테이블로 사용하는 것이다. 그리고 구분 컬럼 DTYPE 추가하여 해당 테이블이 어떤 자식 엔티티를 기반으로 만들어진 테이블인지 구분한다. (DTYPE 어떤자식인지 구분하기 위해 사용한다. 만약 속성이 같은 자식인 경우 구분이 안되기 때문이다.) 해당 전략의 단점으로는 자식 엔티티에 추가된 필드는 필수가 아니기 때문에 null 입력 되어도 된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Culture {
 
  @Id @GeneratedValue
  @Column(name = "CULTURE_ID")
  private Long id;
 
  private String name;
  private int price;
 
}
 
cs

-> Inheritance(strategy = InheritanceType.SINGLE_TABLE) 조인 타입을 단일 테이블 전략으로 지정한다. 


1
2
3
4
5
6
7
@Entity
@DiscriminatorValue("M")
public class Movie extends Culture {
 
  private String genre;
 
}
cs


 하나의 테이블로 지정되기 때문에 조인전략과 다르게 자식클래스를 구분할 있는 DTYPE 무조건 추가되어야 한다. 방식은 하나로 묶여있어 쿼리가 단순하다는 장점이 있지만 자식 엔티티의 필드의 null 무조건 허용해줘야 한다는 것과 자식마다 성격이 모두 달라도 테이블에 필요없는 필드까지 추가되어 있어야 하는 문제점이 있다.



- 서브타입 테이블로 변환 (구현 클래스마다 테이블 전략)

 자식 엔티티 마다 별도의 모든 테이블을 만들어 주는 방식이다. 부모와 자식을 키로 묶어서 사용하는 조인 방식과 다르게 자식 마다 부모의 속성과 자식의 속성을 모두 포함한 엔티티를 개별적으로 모두 만드는 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Culture {
 
  @Id @GeneratedValue
  @Column(name = "CULTURE_ID")
  private Long id;
 
  private String name;
  private int price;
 
}
cs


- 단일테이블 전략과 다르게 모든 자식 테이블이 별도로 만들어지기 때문에 필요없는 필드가 추가되지 않아서 not null조건을 넣어줄 있다. 하지만 모두 만들어야하고 자식테이블끼리 합칠 성능의 문제가 발생할 있다. 가장 비효율적인 방법으로 일반적으로 조인이나 단일 테이블 전략중에서 사용한다.


- 참고 JAVA ORM 표준 JPA 프로그래밍


댓글()

JPA 관계 유형별 엔티티 설정 방법

web/JPA|2018. 10. 31. 00:14

JPA에서 관계 유형별로 엔티티를 설정하는 방법을 정리해보자.



1. 다대일 (단방향)

-> 다쪽에 @ManyToOne 으로 설정


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
28
@Entity
@Table(name = "student")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
 
  @Id
  @Column(name = "STUDENT_ID")
  private String id;
 
  @Column(name = "name")
  private String name;
 
  @ManyToOne
  @JoinColumn(name = "CLASSES_ID")
  private Classes classes;
 
  public void setClasses(Classes classes) {
    // 먼저 지워준다.
    classes.getStudents().remove(this);
 
    // 그리고 반을 바꾸고 학생추가
    this.classes = classes;
    classes.getStudents().add(this);
  }
 
}
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Table(name = "classes")
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Classes {
 
  @Id
  @Column(name = "CLASSES_ID")
  private String id;
 
  @Column(name = "name")
  private String name;
}
 
cs


2. 다대일 양방향

-> 다쪽에 @ManyToOne, 일쪽에 @OneToMany 지정해서 사용

-> 다쪽인 Student에서 외래키를 가지고 있으므로 직접 조작할 있고 반대쪽은 조회만 가능

-> 이전시간에 정리한거 처럼 setClasses(), addStudent()등에 메서드에 빈곳이 없도록 해야한다.

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Entity
@Table(name = "student")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
 
  @Id
  @Column(name = "STUDENT_ID")
  private String id;
 
  @Column(name = "name")
  private String name;
 
  @ManyToOne
  @JoinColumn(name = "CLASSES_ID")
  private Classes classes;
 
  public void setClasses(Classes classes) {
    // 먼저 지워준다.
    classes.getStudents().remove(this);
 
    // 그리고 반을 바꾸고 학생추가
    this.classes = classes;
    classes.getStudents().add(this);
  }
 
}
 
@Table(name = "classes")
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Classes {
 
  @Id
  @Column(name = "CLASSES_ID")
  private String id;
 
  @Column(name = "name")
  private String name;
 
  @OneToMany(mappedBy = "classes")
  private List<Student> students;
 
}
cs


3. 일대다 (단방향)

-> 일대다에서 외래키가 다쪽에 존재

-> 다대일 단방향에서 일쪽에 @JoinColumn(name = "CLASSES_ID") 추가된 형태

-> 일대다보다 있으면 다대일을 사용하자.

1
2
3
4
@JoinColumn(name = "CLASSES_ID")
@OneToMany(mappedBy = "classes")
private List<Student> students;
 
cs


4. 일대다 (양방향)

-> 일대다 매핑은 존재하지 않고 다대일 양방향으로 사용해야한다.



5. 일대일

-> 양쪽이 서로 하나의 관계만 가지는 .

-> 양쪽 테이블 모두 외래키를 가질 있다.

-> @OneToOne으로 매핑하고 주인이 엔티티에 @JoinColumn 사용하고, 나머지 엔티티에는  mappedBy 옵션을 사용한다.

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Entity
@Table(name = "student")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
 
  @Id
  @Column(name = "STUDENT_ID")
  private String id;
 
  @Column(name = "name")
  private String name;
 
  @ManyToOne
  @JoinColumn(name = "CLASSES_ID")
  private Classes classes;
 
  @OneToOne
  @JoinColumn(name = "LOCKER_ID")
  private Locker locker;
 
  public void setClasses(Classes classes) {
    // 먼저 지워준다.
    classes.getStudents().remove(this);
 
    // 그리고 반을 바꾸고 학생추가
    this.classes = classes;
    classes.getStudents().add(this);
  }
 
}
 
@Table(name = "classes")
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Locker {
 
  @Id
  @Column(name = "LOCKER_ID")
  private String id;
 
  @Column(name = "location")
  private String location;
 
  @OneToOne(mappedBy = "locker")
  private Student student;
}
cs


6. 다대다

-> 데이터베이스에서 사용하지 않는 다대다를 사용하지 말고 중간에 중점을 두어 다대일로 구별하여 처리한다.

-> 예를 들어 학생과 책의 다대다 관계를 지칭한다 했을 중간에서 학생과 책의 관계를 담당하는 엔티티 StudentBook 있다고 가정해보자.

-> Student에는 관계 테이블에서 사용될 키가 보함된   @OneToMany(mappedBy = "student") 설정한다.

-> 관계 엔티티 에는 키를 관리하는 별도의 객체를 만들어서 @IdClass(StudentBookId.class) 설정해준다.


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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@Entity
@Table(name = "student")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
 
  @Id
  @Column(name = "STUDENT_ID")
  private String id;
 
  @Column(name = "name")
  private String name;
 
  // 다대일
  @ManyToOne
  @JoinColumn(name = "CLASSES_ID")
  private Classes classes;
 
  // 일대일
  @OneToOne
  @JoinColumn(name = "LOCKER_ID")
  private Locker locker;
 
  // 다대다(중간에 관계테이블 있는경우)
  @OneToMany(mappedBy = "student")
  private List<Book> books;
 
  public void setClasses(Classes classes) {
    // 먼저 지워준다.
    classes.getStudents().remove(this);
 
    // 그리고 반을 바꾸고 학생추가
    this.classes = classes;
    classes.getStudents().add(this);
  }
 
}
 
 
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
 
  @Id
  @Column(name = "PRODUCT_ID")
  private String id;
 
  private String name;
 
}
 
@Entity
@IdClass(StudentBookId.class)
public class StudentBook {
 
  @Id
  @ManyToOne
  @JoinColumn(name = "STUDENT_ID")
  private Student student;
 
  @Id
  @ManyToOne
  @JoinColumn(name = "BOOK_ID")
  private Book book;
 
}
 
@EqualsAndHashCode
public class StudentBookId implements Serializable {
 
  private String student;
  private String book;
 
}
 
cs


댓글()

데이터 모델링 기초 설명

명칭 설명

엔티티 -> 테이블

속성 -> 컬럼

인스턴스 -> 행




관계도

1 대 N


1 대 1


M:N

=> 모델링에서 M N은 아직 완성되지 않은 모델로 간주하여 1:N으로 전환시키는 작업을 진행하여야 한다.




참여도 표시

||  필수

선택



=> 사원은 부서를 필수로 포함해야하지만부서는 사원이 선택이다.



키 표시

PK ◆ID 또는 ID(PK)와 같이 기재 (주 식별자)

FK ID(FK)로 기재

 



Identifying Non-Identifying

외래 식별자가 관계에 있는 다른 엔티티의 주 식별자의 일부일 경우 Identifying이라 하고 별도일 경우Non-Identifying 이라고 한다.





=> Identifying





=> Non-Identifying




요구사항을 통해 엔티티 정의 만들기


엔티티 정의


-> 먼저 명사를 찾는다.


-> 엔티티의 정의는 `업무의 관심대상이 되는 정보를 갖고 있거나그에 대한 정보를 관리 할 필요가 있는 유형무형의 개체를 의미한다.

  = 엔티티 정의를 생각하며 정의한다.


-> 중복된 명사나 유사의미를 통합한다.

  = ex) 고객과 회원은 통합하여 생각한다.


-> 개념이 명확하지 않거나의미가 불분명한것을 찾는다.

-> 데이터의 관리가 필요한지 여부를 판단한다.

-> 추출한 명사가 데이터 관리의 주가 되는 엔티티인지 다른 엔티티의 속성의 성질을 가지는지 판단한다.

-> 그 업무에 맞는 핵심 엔티티를 찾고 나머지 엔티티를 구별하는 방식으로 하면 편하다.

-> 엔티티 내부에서 여러 곳에서 많이 사용되는 속성은 별도의 엔티티로 관리한다.

-> 업무별로 정의하고중복된 것을 줄이자.

댓글()

엔티티 관계와 속성 추출 방법

새로운 속성으로 주 식별자 선정의 장 단점

대출일, 회원번호, 도서관리번호를 주식별자로 가져야 하는 경우, 너무 많은 속성이 주 식별자가 되어 구별하기 어려울 때, 대출 번호라는 새로운 주 식별자를 도입할 수 있다.

-> 하지만 새로운 주 식별자를 도입한경우, 다음과 같은 문제가 발생할 수 있다.

-> 대출번호가 키가 되는경우 대출일, 회원번호, 도서관리번호가 중복된 데이터가 있을 수 있다.

-> 새로운 개념의 속성을 만들어 주식별자로 지정할 경우 단점과 장점이 있으니 잘 사용해야 한다.



필요없는 속성을 주 식별자로 지정하는 경우.

필요없는 속성이 주 식별자가 되는 경우, 문제가 발생할 수 있다.

제품번호와 제품가격이 주 식별자가 되었을 때, 제품번호별로 제품가격은 하나여야 하는데 서로 다른 데이터가 존재할 수 있는 의미적 중복문제를 발생시킬 수 있다.

제품번호 가격

1111111 11

1111111 22


 

관계의 정의 방법

엔티티는 명사를 도출 하였다면 관계정의는 요청서에서 동사를 보고 판단하라.

부모 관계를 확인하라.

=> A 엔티티가 만들어지기 위해서 B엔티티가 필요하다면, B엔티티는 A엔티티의 부모의

관계를 가지고 있다.

=> 두 엔티티 중 정보를 먼저 생성하여 가지고 있는 쪽이 부모이고, 가져다 쓰는 쪽이

자식이다.



외래식별자 정의

=> 부모 엔티티의 속성이 자식 엔티티에도 공통적으로 존재한다면 자식 엔티티에 있는 공통

속성이 외래 식별자가 된다.


관계/외래식별자에 대한 규칙

=> 두 엔티티가 관련이 있다는 의미는 두 엔티티가 공유하는 속성이 있다는

의미.

=> 공통 속성의 값이 먼저 생성되는 쪽이 부모 엔티티고 , 가져다 쓰는 쪽이

자식 엔티티

=> 부모엔티티에 있는 공통 속성은 주 식별자가 되고, 자식 엔티티의 공통

속성은 외래 식별자가 된다.

=> 부모 엔티티와 자식 엔티티의 카디낼러티는 1:N

댓글()