0+ 스프링/0 + 스프링 ORM(JPA)

[JPA] 엔티티 매핑(@Entity, @Table)

힘들면힘을내는쿼카 2023. 2. 16. 12:18
728x90
반응형

[JPA] 엔티티 매핑(@Entity, @Table)

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의
이 글은 인프런에서 제공하는 자바 ORM 표준 JPA 프로그래밍 - 기본편 강의를 참고했고
강의 내용을 다시 복습하면서 정리하려는 목적으로 작성합니다.

 

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

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

www.inflearn.com

 

 

JPA에서 가장 중요한 일은 엔티티와 테이블을 정확하게 매핑하는 것 입니다.
따라서 매핑 애노테이션은 반드시 숙지해야 합니다.

(매핑(mapping)이란 하나의 값을 다른 값으로 대응시키는 것을 말합니다.)

 

 

 

매핑 애노테이션은 크게 4가지로 분류할 수 있습니다.

  • 객체와 테이블 매핑
    • @Entity, @Table
  • 필드와 컬럼 매핑
    • @Column
  • 기본 키 매핑
    • @Id
  • 연관관계 매핑
    • @ManyToOne, @JoinColumn

연관관계 매핑은 내용이 조금 많아서 나중에 따로 정리하고
해당 포스팅에는 연관관계 매핑을 제외한 3가지에 대해서 설명하도록 하겠습니다.

@Entity

@Entity가 붙은 클래스는 JPA가 관리합니다.
흔히 엔티티라고 부릅니다.

Team

@Entity(name = "Team") // JPA가 관리하는 객체
@Table(name = "TM") // TM 테이블에 매핑
public class Team {

    @Id
    private Long id;
    private String name;

    // 기본 생성자 필수
    public Team() {
    }

    public Team(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}

특징

  • 기본 생성자가 필수 입니다.(public, protected)
    • JPA를 사용하는 라이브러리들이 리플렉셔, 프록시 같은 기술을 사용하려면 필요하기 때문
  • final 클래스, enum, interface, inner 클래스 사용 불가
  • 저장할 필드에 final 사용 불가

속성

  • name
    • JPA에서 사용할 엔티티 이름을 지정
    • 기본값: 클래스 이름을 그대로 사용(E.g: User)
    • 같은 클래스 이름이 없으면 가급적 기본값 사용 권장

@Table

엔티티와 매핑할 테이블을 지정합니다.

속성

  • name
    • 매핑할 테이블 이름
    • 기본값: 엔티티 이름을 사용
  • catalog
    • DB catalog 매핑
  • schema
    • DB schema 매핑
  • uniqueConstraints(DDL)
    • DDL 생성시 유니크 제약 조건 생성

데이터 베이스 스키마 자동 생성 기능

JPA 에서는 애플리케이션 로딩 시점에 DB 테이블도 생성하는 기능도 제공합니다.

  • 테이블 중심 -> 객체 중심
  • DB 방언을 활용해서 DB에 맞는 적절한 DDL 생성

이렇게 생성된 DDL은 개발 장비에서만 사용하며,
운영서버에서는 사용하지 않거나, 적절히 다듬은 후에 사용하는 것을 권장 합니다.

hibernate.hbm2ddl.auto 속성

  • create
    • 기존 테이블 삭제 후 다시 생성
    • 운영 DB 사용 X
  • create-drop
    • create와 같으나, 종료시점에 테이블 drop
    • 운영 DB 사용 X
  • update
    • 변경분만 반영
    • 운영 DB 사용 X
    • 테스트 DB 권장
  • validate
    • 엔티티 테이블이 정상 매핑되었는지만 확인
    • 테스트 DB 권장
  • none
    • 사용하지 않음
    • 운영 DB 권장

DDL 생성 기능

DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않습니다.

유니크 제약조건 추가

@Entity
@Getter @Setter
// 유니크 제약 조건 추가
@Table(uniqueConstraints =
        {@UniqueConstraint(
                name = "NAME_AGE_UNIQUE", 
                columnNames = {"NAME", "AGE"}
        )})
public class Team {

    @Id
    private Long id;
    private String name;
    private Integer age;
    // 유니크 제약 조건 추가
    @Column(unique = true)
    private String nickName;
}

필드와 컬럼 매핑

모든 속성을 외우는 것보다는 어느정도 숙지하신 뒤 엔티티 설계시 필요할때마다 찾아보는것을 추천 드립니다.^^

@Column

컬럼 매핑 애노테이션

속성

  • name
    • 필드와 매핑할 테이블의 컬럼 이름
  • insertable, updatable
    • 등록, 변경 가능 여부
    • 기본값 TRUE
  • nullable
    • null 값의 허용 여부 설정
    • flase이면 DDL 생성시 not null 제약조건 추가
  • unique
    • @TableuniqueConstraints와 같지만 한 컬럼에 간단히 유니크 제약조건을 걸 때 사용
      • 하지만, 잘 사용하지 않음
        • 랜덤한 값으로 이름을 설정해서 개발자가 인식에 어려움이 있음(아래 코드 참고 [UK_syftr7gx86fwf7ox7bgvnnta7])
Hibernate: 

    alter table User 
       add constraint UK_syftr7gx86fwf7ox7bgvnnta7 unique (name)
  • columnDefinition
    • 데이터베이스 컬럼 정보를 직접 줄 수 있음
@Column(name = "name", 
        columnDefinition = "varchar(100) default 'EMPTY'")
private String username;
Hibernate: 

    create table User (
       id bigint not null,
        name varchar(100) default 'EMPTY',
        primary key (id)
    )
  • length
    • 문자 길이 제약조건, String 타입에만 사용
  • precision, scale
    • BigDecimal또는 BigInteger 타입에서 사용
    • precision은 소수점을 포함한 전체 자릿수
    • scale은 소수의 자릿수
    • double, float 타입에는 적용되지 않음
      • 아주 큰 숫자나 정밀한 소수를 다룰때만 사용
@Column(precision = 20, scale = 6)
private BigDecimal age;
Hibernate: 

    create table User (
       id bigint not null,
        age decimal(20,6),
        primary key (id)
    )

@Temporal

날짜 타입 매핑 애노테이션

속성

  • value
    • TemporalType.DATE
      • DBdate타입과 매핑
      • yyyy-MM-dd
      • 2023-02-14
    • TemporalType.TIME
      • DBtime타입과 매핑
      • hh:mm:ss
      • 11:11:11
    • TemporalType.TIMESTAMP
      • DBtimestamp타입과 매핑
      • yyyy-MM-dd hh:mm:ss
      • 2023-02-14 11:11:11

참고
LocalDate, LocalDateTime을 사용할 때는 @Temporal 생략 가능!

@Enumerated

enum 타입 매핑 애노테이션
ORDINAL 사용을 권장하지 않습니다.

속성

  • value
    • EnumType.ORDINAL
      • enum 순서를 데이터베이스에 저장
    • EnumType.STRING
      • enum 이름을 데이터베이스에 저장

ORDINAL 사용을 권장하지 않는 이유는 다음과 같습니다.
만약 처음 User 엔티티 설계시 USER, ADMIN이라는 enum 타입으로 RoleType을 설정했다고 합시다.

RoleType

public enum RoleType {
    USER, ADMIN
}

User

@Entity
@Getter @Setter
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
            generator = "USER_SEQ_GENERATOR")
    private Long id;
    private String name;
    @Enumerated(EnumType.ORDINAL)
    private RoleType roleType;
}

JpaMainV3

public class JpaMainV3 {
    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("pureJpa");
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            User user1 = new User();
            user1.setId(1L);
            user1.setName("노홍철");
            user1.setRoleType(RoleType.USER);

            User user2 = new User();
            user2.setId(2L);
            user2.setName("유재석");
            user2.setRoleType(RoleType.ADMIN);

            em.persist(user1);
            em.persist(user2);

            tx.commit(); // 커밋
        } catch (Exception e) {
            tx.rollback(); // 롤백
        } finally {
            em.close(); // DB Connection을 사용하여 작업함
        }
        emf.close(); // 애플리케이션이 종료되면 닫아야함
    }
}

결과


ADMIN1 USER0으로 저장된것을 확인 할 수 있습니다.
그런데, RoleTypeGUEST라는 권한이 추가되면….?

public enum RoleType {
    GUEST, USER, ADMIN
}

이렇게 되면 유재석은 ADMIN이기 때문에 2, 노홍철은 USER이기 때문에 1로 변경되어야 합ㄴ디ㅏ.
그런데 이미 DB에 저장된 회원이 500만명이라고 해봅시다…. 다 수정하기가 어렵겠죠? 😿

따라서 EnumType.STRING을 사용하는 것을 권장 합니다.
enum 이름을 데이터베이스에 저장할수 있기 때문이죠!

@Enumerated(EnumType.STRING)
private RoleType roleType;

EnumType.STRING으로 변경한뒤 유재석과 노홍철을 저장하면 다음과 같은 결과를 얻습니다.

EnumType.STRING을 사용하는 것을 권장 합니다.

@Lob

BLOB, CLOB 매핑 애노테이션
(LOB은 TEXT, 그래픽, 이미지, 비디오, 사운드 등 구조화되지 않은 대형 데이터를 저장하는 목적으로 개발됨)
@Lob에는 지정할 수 있는 속성이 없음

  • 매핑하는 필드 타입이 문자이면 CLOB 매핑, 나머지는 BLOB 매핑
    • CLOB: String, char[], java.sql.CLOB
      • 문자 대형 객체
      • 문자 기반 데이터 보관용
    • BLOB: bytep[], java.sql.BLOB
      • 2진 대형 객체, 이미지, 동영상, MP3 등
      • 비 전통적인 데이터 보관용

@Transient

특정 필드를 컬럼에 매핑하지 않음(매핑 무시)
메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용합니다.
(@Transient에는 지정할 수 있는 속성이 없음!)

기본 키 매핑

기본키 매핑에 들어가기에 앞서 권장드리는 기본키(식별자) 전략을 말씀 드립니다.
기본키 제약조건은 null이면 안되고, 유일해야하며, 불변해야 합니다.
그런데, 먼 미래까지 이 조건을 만족하는 자연키는 찾기가 어렵습니다.(특히 불변이 어려워요)😥

그래서 대리키(대체키)를 사용하는것을 권장 드립니다.😊
(대리 키(alternate key)는 관계형 데이터베이스의 관계 모델 에서 관계의 ‘후보 키’ 중 기본 키로 선정되지 않은 키를 말한다.)
예를 들어 주민등록번호도 기본키로 적절하지 않습니다.
Longauto_increment(10억 넘어도 동작 가능), UUID같은 대체키 같은 것을 조합해서 사용하는 것을 권장합니다.

기본키를 매핑하는 방법은 2가지가 존재합니다.
직접 할당과 자동 할당 입니다.

직접 할당

  • @Id만 사용

자동 할당

  • @GeneratedValue 사용

@GeneratedValue

기본 키 생성을 DB에 위임 합니다.

속성
* IDENTITY
* 기본키 생성을 DB에 위임
* 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용
* MySQLAUTO_INCREMENT
* JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
* AUTO_INCREMENTDBINSERT SQL을 실행한 이후에 ID 값을 알 수 있음
* IDENTITY 전략은 em.persist() 시점 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회함
* 영속성 컨텍스트에 관리되려면 PK가 무조건 있어야 하기 때문에 commitSQL이 날아가지 않음

User

@Entity
@Getter @Setter
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @Enumerated(EnumType.STRING)
    private RoleType roleType;
}

JpaMainV3

public class JpaMainV3 {
    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("pureJpa");
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            User user1 = new User();
            user1.setName("노홍철");
            user1.setRoleType(RoleType.USER);

            System.out.println("persist 전");
            em.persist(user1);
            System.out.println("persist 후");

            tx.commit(); // 커밋
        } catch (Exception e) {
            tx.rollback(); // 롤백
        } finally {
            em.close(); // DB Connection을 사용하여 작업함
        }
        emf.close(); // 애플리케이션이 종료되면 닫아야함
    }
}

결과

persist 전
Hibernate: 
    /* insert com.study.purejpa.entity.User
        */ insert 
        into
            User
            (id, name, roleType) 
        values
            (default, ?, ?)
persist 후

정말로 commit 이전인 em.persist()SQL를 날린 것을 알수 있습니다.
따라서 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자(PK)를 조회합니다.
결국에 쓰기 지연 SQL 저장소를 이용하여 SQL을 날리는 것은 IDENTITY 전략에서는 불가능 합니다.

  • SEQUENCE
    • 유일한 값을 순서대로 생성하는 특별한 DB 오브젝트
    • DB 시퀀스 오브젝트 사용
    • 오라클, PostgreSQL, H2, DB2에서 사용
    • @SequenceGenerator 필요

@SequenceGenerator

 

@Entity
@SequenceGenerator(
        name = "USER_SEQ_GENERATOR", // 시퀀스 제너레이터 이름
        sequenceName = "MEMBER_SEQ", // 매핑할 DB 시퀀스 이름
        initialValue = 1, // 시작 값
        allocationSize = 1 // 시퀀스 한 번 호출에 증가하는 수
)
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
            generator = "USER_SEQ_GENERATOR")
    private Long id;
}

allocationSize가 50이면 애플리케이션에서 한번에 시퀀스 값을 50개를 갖고오는 것입니다.
쉽게 이야기 해서 50을 메모리상에 띄우고 하나씩 증가시키며 사용하다가 50이 넘어가게 되면 그때 다시 DB에서 100까지 갖고오는 것 입니다.
이러한 방법을 사용하면 시퀀스가 증가할 때마다 DB와 통신하지 않기 때문에 네트워크를 이용하지 않아도 됩니다.

  • TABLE
    • 생성 전용 테이블을 하나 만들어서 DB 시퀀스를 흉내내는 전략
    • 모든 DB에서 사용
      • @TableGenerator 필요
    • 성능 이슈가 있음

@TableGenerator

 

@Entity
@TableGenerator(
        name = "USER_SEQ_GENERATOR", // 식별자 생성기 이름
        table = "MY_SEQUENCES", // 키생성 테이블명
        pkColumnName = "USER_SEQ", // 키로 사용할 값 이름
        allocationSize = 1 // 시퀀스 한 번 호출에 증가하는 수
)
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
            generator = "USER_SEQ_GENERATOR")
    private Long id;
}

 

 

  • AUTO
    • 방언에 따라 자동 지정(기본값)
@Entity
@Getter @Setter
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
}

 

 

 

 

728x90
반응형