본문 바로가기
Develop/Java

[JPA] Entity 매핑

by 연로그 2022. 1. 10.
반응형

이전 글: https://yeonyeon.tistory.com/178

예제 코드https://github.com/yeon-06/inflearnSpring/tree/master/jpa-ex1

 


목차

1. @Entity란?
2. DB 스키마 자동 생성
3. 필드와 컬럼 매핑
4. 기본 키 매핑
5. 연관 관계 매핑
6. 상속 관계 매핑
7. 공통 매핑

 


1. @Entity란?

 

  • JPA가 관리하는 클래스
  • JPA를 사용해 테이블과 매핑할 클래스
  • public, protected 기본 생성자 필수
    👉 JPA의 구현체(ex: hibernate)가 지원하는 다양한 기능을 사용하기 위함
  • final, enum, interface, inner 클래스 사용 불가
    👉 @Entity로 매핑이 불가능
  • 값을 저장할 필드에 final 사용 불가

 

@Table 어노테이션을 이용해 매핑할 테이블 이름 지정, DB catalog 매핑, DB schema 매핑, DDL 생성 시 유니크 제약 조건 생성 등을 설정할 수 있다.
(DDL: CREATE, DROP, ALTER, TRUNCATE 같은 데이터 정의어)

 

🔻 예제 코드

더보기
@Entity
@Table(name = "USER_TABLE")
public class Users {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String name;

    public Users() {
    }

    // getter, setter, ...
}

 


2. DB 스키마 자동 생성

 

  • 애플리케이션 실행 시점에 DDL 자동으로 생성
  • 개발 환경에서만 사용하도록 유의
    (운영에서 애플리케이션 실행 이후 DDL로 인한 테이블 변경은 위험)
  • JPA의 실행 로직에 영향 X

 

🔻 DB 스키마 자동 생성 설정하기

더보기

/main/resources/META-INF/persistence.xml에 아래 속성 추가

<property name="hibernate.hbm2ddl.auto" value="create"/>

 

옵션 종류

  • create: 기존 테이블 삭제 후 생성
  • create-drop: create와 같으나 종료 시점에 테이블 삭제
  • update: 변경 사항만 반영
  • validate: Entity와 Table 정상 매핑 여부만 확인
  • none: 사용 X

 


3. 필드와 컬럼 매핑

 

  • @Column : 컬럼 매핑
  • @Temporal : 날짜 타입 매핑 (LocalDAte 사용 시 생략 가능)
  • @Enumerated : enum 타입 매핑
  • @Lob : BLOB, CLOB 매핑
  • @Transient : 매핑 무시

 


4. 기본 키 매핑

 

  • 직접 할당: @Id만 사용
  • 자동 생성: @GeneratedValue와 함께 사용

 

@GeneratedValue의 속성

  • IDENTITY: DB에 위임
  • SEQUENCE: DB 시퀀스 오브젝트 사용
  • TABLE: 키 생성용 테이블 생성해 DB 시퀀스 흉내
  • AUTO: 방언에 따라 자동 지정 (default)

 

TIP! - 기본 키에 대해
기본 키는 null이 아니고, 유일해야 하고, 변경되면 안된다.
권장 사항은 Long + 대체키 + 키 생성 전략을 사용하는 것이다.

예를 들어 전화 번호를 PK로 잡았다고 가정하자.
국가 보안 정책으로 인해 전화 번호를 DB에 더이상 저장하지 못하는 상황이 온다면?
전화번호를 PK로 잡은 테이블은 PK만 변경하면 되긴 한다.
하지만 해당 테이블과 조인시킨 테이블들에는 문제가 발생한다.

 

🔻 IDENTITY와 SEQUENCE

더보기

아래와 같은 Entity가 존재한다고 가정하자.

@Entity
public class Users {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String name;
    
    // 생성자, getter, setter, ...
}

위 로직을  실행하면 DDL이 아래와 같이 생성된다. (현재 방언 설정 H2)

 

GernerationType.IDENTITY

create table Users (
    id bigint generated by default as identity,
    name varchar(255),
    primary key (id)
)
  • 기본 키 생성을 DB가 담당
  • AUTO_INCREMENT는 DB에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있음
  • entity manager의 persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자 조회
  • 사용 DB: MySQL, PostgreSQL, SQL Server, DB2, ...

 

GenerationType.SEQUENCE

create table Users (
    id bigint not null,
    name varchar(255),
    primary key (id)
)
  • 유일한 값을 순서대로 생성하는 특별한 DB 오브젝트 (ex: Oracle Sequence)
  • 사용 DB: Oracle, PostgreSQL, DB2, H2, ...

 

GenerationType.TABLE은 SEQUENCE를 흉내낸 것으로 키 생성 전용 테이블을 따로 만든다.

모든 DB에 적용이 가능하지만 성능상 문제가 있을 수 있다.

 


5. 연관 관계 매핑

 

연관 관계가 필요한 이유?

객체지향 설계의 목표는 자율적인 객체들의 협력 공통체를 만드는 것이다.
- 객체지향의 사실과 오해

 

  • 테이블: 연관된 테이블을 찾기 위해 외래키 사용
  • 객체: 연관된 객체를 찾기 위해 참조 사용

👉 객체를 테이블에 맞춰 데이터 중심으로 모델링 시 협력 관계를 만들 수 없다.

 

연관 관계

되도록이면 단방향 연관 관계로 설계하는 것이 좋지만, 필요에 따라 양방향 연관 관계도 맺는다.
(+ 편의상 양방향 연관 관계라고 하지만 Java에서 보면 서로 다른 단방향 관계 2개를 이을 뿐이다.)

 

양방향 연관 관계는 주인과 주인이 아닌 쪽으로 나눌 수 있다.

연관 관계의 주인은 외래 키를 관리하고, 주인이 아닌 쪽은 read 기능만 할 수 있도록 한다.

주인이 아닌 쪽은 mappedBy 속성을 통해 나타낸다.

(보통 외래키가 있는 곳을 주인으로 설정한다.)

 

🔻 예제로 이해하기

더보기

Team과 Member는 일대다 관계이다.
MEMBER 테이블은 team_id라는 외래키를 갖고 있다.

(= Member 클래스의 team은 연관 관계의 주인)

 

Team

@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

    // getter, setter ...
}

 

Member

@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;

    private String username;
    
    // getter, setter, ...
}

 

Team에서는 @OneToMany(mappedBy = "연관 관계의 주인")를 통해 Member 목록을 가진다.
Member에서는 @JoinColumn을 이용해 Team을 가진다.

 

양방향 매핑 시 유의점
- 값을 연관 관계의 주인에게 입력해야 한다.
- 순수 객체 상태를 고려해 항상 양쪽에 값을 설정하자. (메소드를 생성해두면 좋다.)
  위 예제 같은 경우에는 Team을 직접 조회해 변경할 수도, Member의 team을 조회해 변경할 수도 있지만 Team의 이름을 변경하는 것이 권장 사항.
- toString(), lombok, JSON 등에서 무한 루프를 조심할 것.

 

다대일 (N:1)

  • @ManyToOne
  • 외래 키가 있는 쪽이 연관 관계의 주인
  • 양쪽을 서로 참조하도록 개발

 

일대다 (1:N)

  • @OneToMany
  • 객체의 구조상 반대편 테이블의 외래 키 관리
  • @JoinColumn의 사용 필수
    -> 미사용 시 테이블을 하나 추가해 조인 테이블 방식 사용
  • @JoinColumn(insertable=false, updatable=false) 옵션 권장
  • 되도록이면 다대일 양방향 매핑 이용
ex: Team 객체 내에는 사용자 목록을 저장하는 List<Member> members가 존재

 

일대일 (1:1)

  • @OneToOne
  • '주 테이블에 외래 키', '대상 테이블에 외래 키' 중 선택 가능
ex: Member와 Locker는 1:1 관계인 객체이다.

- 주 테이블에 외래 키: Member 내에서 Locker locker를 선언해 사용.
- 대상 테이블에 외래 키: Locker 내에서 Member member를 선언해 사용.

👨‍💻 DBA
: D한 사람당 Locker를 여러개 쓰도록 구조가 변할 수도 있잖아요?
DB 관점에서 1:1 관계가 1:N 관계로 변환되는 것을 고려하면 대상 테이블에 외래키 구조가 더 좋아요!

👩‍💻 Java 개발자
: Member만 조회해도 Locker 데이터가 존재하는지 확인 가능하고 JPA 매핑도 편리해요. 
주 테이블에 외래키 구조가 더 좋아요!

 

다대다 (N:M)

  • @ManyToMany
  • RDB는 정규화된 테이블 2개로 N:M 관계 표현 불가능
    (연결 테이블을 추가해 1:N, N:1 관계로 풀어냄)
  • 많은 한계가 존재해 거의 사용하지 않음
ex1: Member, Product, Member_Product가 존재한다고 가정하자.
Member_Product에는 매핑 정보만 존재하고 추가적인 데이터(Member가 Product를 몇 개 주문했고 주문 날짜가 언제인지 등)을 추가할 수 없다.

ex2: Member에서 Product와 조인해 SQL문을 작성할 경우 Member_Product를 거쳐야 하므로 복잡해지고 애매하다.

 

 


6. 상속 관계 매핑

 

관계형 DB는 상속 관계를 갖지 않는다.

슈퍼타입, 서브타입을 통해 서로 관계를 갖는다.

 

구조부터 살펴보자면 위 이미지와 같다.

좌측은 각 전략에 따른 DB를 Java 객체로 변환한 것으로 Album, Movie, Book은 Item을 오버라이딩 한다.

 

  1. 객체를 각 테이블로 변환 -> 조인 전략
  2. 모든 데이터를 하나의 통합 테이블로 변환 -> 단일 테이블 전략
  3. 서브타입 테이블로 변환 -> 구현 클래스 별 테이블 전략

 

조인 전략

  • @Inheritance(strategy = InheritanceType.JOINED)
  • 테이블의 정규화
  • 외래 키 참조 무결성 제약조건 활용 가능
  • 저장공간 효율화
  • 비즈니스 로직적으로 중요하면 join하여 사용
  • 조회 시 조인 많이 사용 (성능 저하 가능성)
  • 조회 쿼리의 복잡성
  • 데이터 저장 시 INSERT문 2번 호출
    (ex: ALBUM에 컬럼 추가하려면 ALBUM과 ITEM 테이블 둘 다 INSERT 해야함)

 

단일 테이블 전략

  • @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
  • 조인이 필요 없으므로 일반적으로 조회 성능 빠름
  • 조회 쿼리 단순
  • 자식 엔티티가 매핑한 컬럼이 존재하지 않으면 null 허용
  • 단일 테이블에 모든 것을 저장해 테이블이 커짐 (상황에 따라 조회 성능 오히려 떨어질수도)
    (다만 이 경우는 테이블이 임계점에 다를 때 발생하는 문제로 보통은 문제가 없어서 많이 사용)

 

구현 클래스별 테이블 전략

  • @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
  • DBA, ORM 모두가 비추천
  • 서브 타입을 명확하게 구분해 처리할 때 효과적
  • not null 제약조건 사용 가능
  • 여러 자식 테이블을 함께 조회할 때 UNION 필요 (성능 저하)
  • 자식 테이블을 통합해 쿼리하기 어려움

 


7. 공통 매핑

 

@MappedSuperclass

  • 공통 매핑 정보가 필요할 때 사용
    (ex: createdDate, updatedDate, ... )
  • 직접 생성해 사용할 일이 없으므로 abstract class으로 선언 추천
  • 상속관계에 의한 매핑 X
  • Entity나 Table과 매핑되는 것이 아닌 부모 클래스를 상속 받는 자식 클래스에 매핑 정보만 제공
    (= 조회, 검색 불가)
  • 참고로 @Entity 클래스는 @Entity 또는 @MappedSuperclass로 지정한 클래스만 상속 가능

 

🔻 예제 코드

더보기
@MappedSuperclass
public abstract class BaseEntity {
    private String createdBy;
    private String modifiedBy;
    private LocalDateTime createdDate;
    private LocalDateTime modifiedDate;
    // 생성자, getter, setter, ... 
}

 

위 BaseEntity 이용

@Entity
public class Item extends BaseEntity {
    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private int price;
    // ...
}

 


본 게시글은 김영한 님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편' 강의를 구매 후 정리하기 위한 포스팅입니다.

내용을 임의로 추가, 수정, 삭제한 부분이 많으며 정확한 이해를 위해서 강의를 구매하시는 것을 추천 드립니다.

 

https://inf.run/yR3B

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 본 강의는 자바 백엔

www.inflearn.com

반응형