Home 18. 기타 매핑 설정
Post
Cancel

18. 기타 매핑 설정

01. 상속 매핑

예) 시스템에 일반 이슈, 방문 예약, 민원의 세 가지 이슈 종류가 있다고 할 때 이 이슈 타입을 상속을 이용해서 설계할 수 있다.

JPA에서는 같은 클래스 계층을 테이블과 매핑하는 다음과 같은 방식을 지원한다.

1-1. 클래스 계층을 한 개 테이블로 매핑

상속 관계의 엔티티를 매핑하는 가장 쉬운 방법은 클래스 계층을 한 테이블에 매핑하는 것이다. 한 테이블에 계층의 전체 클래스를 매핑하려면 매핑 대상 클래스를 식별할 수 있어야 한다. 대상 클래스를 구분하기 위해 타입 식별 컬럼을 추가로 필요로 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Entity
@Table(name="issue")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) //상속 매핑 방식 설정, SINGLE_TABLE : 한 테이블로 상속 계층을 매핑한다.
@DiscriminatorColumn(name="issue_type") //타입 식별값을 저장할 컬럼 지정
@DiscriminatorValue("IS") //Issue 타입을 위한 식별 값을 지정
public class Issue {
  @Id
  @GeneratedValue(strategy=GenerationType.IDENTITY)
  private Long id;

  @Temporal(TemporalType.TIMESTAMP)
  @Column(name="issue_date")
  private Date issueDate;

  @Column(name="customer_name")
  private String customerName;

  @Column(name="customer_cp")
  private String customerCp;
  private String content;
  private boolean closed;
}
1
2
3
4
5
6
7
8
9
10
11
@Entity
@DiscriminatorValue("VR")
public class VisitReservation extends Issue {
  @Column(name="assignee_emp_id")
  private String assigneeEngineerId;

  @Temporal(TemporalType.TIMESTAMP)
  @Column(name="schedule_date")
  private Date scheduleDate;

}
1
2
3
4
5
6
@Entity
@DiscriminatorValue("AP")
public class Appeal extends Issue {
  private String response;

}

위 엔티티를 저장하는 save()를 사용하면 @DiscriminatorValue로 지정한 값을 issue_type 컬럼의 값으로 사용한다.

단일 객체를 조회할 때는 상속 계층에 있는 타입중 알맞은 타입을 지정해서 조회하면 된다. em.find(Appeal.class, id); 로 하면 where 조건절에 issue_type = ‘AP’가 자동으로 들어간다.

1-2. 계층의 클래스마다 테이블로 매핑

예) 첨부 파일 정보를 표현하는 모델이 있을 때 처음 요구사항은 고정된 폴더에 파일을 저장하는 방식이였다가 시간이 지나 폴더를 고정하지 않고 파일을 저장할 경로를 지정하는 방식이 추가되었다. 그리고 클라우드에 올린 파일 정보를 추가할 수 있는 방식도 추가되었다.

눈여겨볼 점은 하위 클래스에 해당하는 테이블은 상위 클래스에 해당하는 테이블의 주요키를 공유한다는 점이다.

1
2
3
4
5
6
7
8
9
10
11
12
@Entity
@Table(name="attach_file")
@Inheritance(strategy=InheritanceType.JOINED) //JOINED : 계층 클래스마다 테이블을 사용하면 각 테이블을 조인해서 필요한 데이터를 조회
public class AttachFile {
  @Id
  private String id;
  private String name;

  @Column(name="upload_date")
  @Temporal(TemporalType.TIMESTAMP)
  private Date uploadDate;
}
1
2
3
4
5
@Entity
@Table(name="local_file")
public class LocalFile extends AttachFile {
  private String path;
}
1
2
3
4
5
6
@Entity
@Table(name="cloud_file")
public class CloudFile extends AttachFile {
  private String provider;
  private String url;
}

계층마다 테이블을 매핑하기 때문에 엔티티를 저장하면, 엔티티 타입에 따라 매핑된 모든 테이블에 데이터를 나눠서 저장한다. 엔티티를 조회할 때에는 엔티티 타입에 따라 사용하는 쿼리가 달라진다. em.find(Attach.class, "F001");로 실행해 보면 attach_file 테이블 뿐만 아니라 local_file 테이블과 cloud_file 테이블을 외부 조인을 이용해서 함께 조회한다.

1-3. 객체 생성 가능한 클래스마다 테이블로 매핑

이 방법은 계층에서 추상이 아닌 콘크리트 클래스를 별도 테이블로 매핑하는 방식이다. 이 방식의 경우 매핑된 테이블이 상위 타입을 포함한 모든 속성을 포함한다.

위 그림에서 최상위의 Member 클래스가 추상 클래스일 때, Member 클래스를 제외한 나머지 클래스는 객체 생성이 가능하다. 나머지 객체 생성한 클래스들은 상속 계층에 속한 모든 속성을 정의하고 있다.

1
2
3
4
5
6
7
8
@Entity
//TABLE_PER_CLASS : 이 설정을 사용하면 클래스 계층에서 객체 생성 가능 클래스마다 테이블을 별도로 매핑한다.
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class Memberber {
  @Id
  private String id;
  private String name;
}

Member 클래스는 추상 클래스이므로 특정 테이블과 매핑되지 않는다.

1
2
3
4
5
6
@Entity
@Table(name="ent_member")
public class EntMember extends Member {
  @Column(name="comp_id")
  private String companyId;
}

이런 방식으로 매핑했을 때 TempMember를 하위 타입으로 갖는 PersonalMember 클래스를 이용해서 조회하면 이 두 테이블을 조회한 결과를 union 연산으로 합한 뒤에 검색 조건을 설정하는 것을 알 수 있다. TempMember 객체는 PersonalMember 타입에 속하기 때문에 두 타입에 매핑된 테이블을 모두 조회하는 것이다.

1-4. 상속 계층과 다형 쿼리

@Inheritance 애노테이션을 이용해서 상속 계층을 매핑하면 다형 쿼리를 사용할 수 있다. 다형 쿼리란 상위 타입을 사용해서 엔티티를 조회하는 기능이다.

상위 타입으로 조회하면 클래스 계층 매핑 방식에 따라 데이터 조회에 필요한 모든 테이블을 조회한다. 또 해당 타입에 필요한 모든 데이터를 조회하므로 상위 타입으로 조회한 객체를 실제 타입으로 변환할 수 있다.

1-5. 세 방식의 장단점

  • 한 테이블로 매핑 : 매핑이 간단하고, 한 테이블만 조회하므로 성능이 좋다는 장점이 있다. 단점으로는 하위 클래스에 매핑된 칼럼은 not null일 수 없고, 하위 클래스를 추가하면 테이블을 변경해야 한다.
  • 클래스마다 테이블로 매핑 : 테이블마다 필요한 데이터만 보관하므로 데이터가 정규화된다는 장점이 있고, 외부 조인을 사용하므로 계층도가 복잡해질수록 조회 성능이 떨어진다는 단점이 있다.
  • 객체 생성 가능 클래스마다 별도 테이블 매핑 : 최하위 타입으로 조회하면 조인이 발생하지 않는다는 장점이 있고, 식별자 중복 여부를 테이블 단위로 막을 수 없고, 상위 타입의 속성이 바뀌면 모든 테이블을 변경해야 한다는 단점이 있다.

02. AttributeConverter 를 이용한 속성 변환

AttributeConverter는 다음과 같은 경우 유용하게 사용할 수 있다.

  • JPA가 지원하지 않는 타입을 매핑할 때
  • 두 개 이상 속성을 갖는 밸류 타입을 한 개 컬럼에 매핑할 때

예) JPA 표준에 따르면 InetAddress 타입과 VARCHAR 사이의 매핑은 지원하지 않는다. 이때 사용할 수 있는 것이 AttributeConverter 이다.

AttributeConverter은 자바 타입과 DB 타입간의 변환을 처리 해주어 이를 사용하면 지원하지 않는 자바 타입을 매핑할 수 있다.

[메서드]

  • public Y convertToDatabaseColumn(X attribute) : 엔티티의 X 타입 속성을 Y 타입의 DB 데이터로 변환
  • public X convertToEntityAttribute(Y dbData) : Y 타입으로 읽은 DB 데이터를 엔티티의 X 타입 속성으로 변환
1
2
3
4
@Converter  //해당 클래스가 AttributeConverter를 구현한 클래스임을 지정
public class InetAddressConverter implements AttributeConverter<InetAddress, String> {
  //...
}
1
2
3
4
5
6
7
8
9
10
11
@Entity
@Table(name="auth_log")
public class AuthLog {
  //...

  //변환에 사용할 AttributeConverter 구현 클래스를 지정
  @Convert(converter=InetAddressConverter.class)
  private InetAddress ipAddress;

  //...
}

410~412p

03. @MappedSuperclass와 매핑 설정 공유

업무 시스템을 만들다 보면 데이터 생성 시점, 생성자, 접근 IP정보를 저장하기 위한 용도로 각 도메인의 이같은 속성을 공통으로 추가해야할 때가 있다. @MappedSuperclass를 이용해서 공통 설정을 위한 상위 클래스를 생성할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@MappedSuperclass
public class DomainModel {
  @Id
  private String id;

  @Column(name="crt_dtm")
  private Date CreationDate;

  @Column(name="crt_empid")
  private String creationEmpId;

  @Column(name="crt_ip")
  private String creationIp;

  //...
}

@MappedSuperclass로 설정한 클래스는 테이블과 매핑할 대상은 아니다. 매핑 대상은 하위 클래스 이다. 하위클래스에서 상위 클래스에 설정한 매핑 정보를 그대로 물려 받아 사용한다.

1
2
3
4
5
6
7
@Entity
public class Category extends DomainModel {
  private String name;

  //...

}

그리고 @MappedSuperclass로 설정한 클래스의 설정 값을 재정의하고 싶은 경우 @AttributeOverride를 클래스에 설정한다.

1
2
3
4
5
6
7
@Entity
//name 속성으로 재정의할 속성을 지정, column 속성으로 매핑할 컬럼 지정
@AttributeOverride(name="creationIp", column=@Column(name="creation_ip"))
public class Category extends DomianModel {
  //...
}

This post is licensed under CC BY 4.0 by the author.