엔티티 매핑 소개

1. 객체와 테이블 매핑: @Entity, @Table
2. 필드와 컬럼 매핑: @Column
3. 기본 키 매핑: @Id
4. 연관관계 매핑: @ManyToOne, @JoinColumn


✅객체와 테이블 매핑

📢@Entity

  • @Entity가 붙은 클래스는 JPA가 관리하고 이를 엔티티라 한다.
  • JPA를 사용해서 테이블과 매핑할 클래스 👉 @Entity 필수
  • 📌주의점
    • 기본 생성자는 필수로 존재해야 한다 (파라미터가 없는 public 또는 protected 생성자)
    • final 클래스, enum, interface, inner 클래스는 @Entity로 매핑할 수 없다
    • 데이터베이스에 저장할 필드에 final 사용하면 안된다

✍ @Entity name 속성

JPA에서 사용할 엔티티 이름을 지정한다.

@Entity(name = "Member") // JPA가 관리하게 된다.
public class Member {}
  • 속성: name
    • 기본값으로 클래스 이름을 그대로 사용한다.(예: Member)
    • 같은 클래스 이름이 없으면 가급적 기본값을 사용한다.

📢@Table

@Table은 엔티티와 매핑할 테이블 지정한다.

@Entity// JPA가 관리하게 된다.
@Table(name = "MBR") // MBR이라는 테이블과 매핑한다.
public class Member {}

MBR 테이블을 사용하는 쿼리가 나간다.

✍ @Table 속성

✅ 데이터베이스 스키마 자동 생성

JPA는 어플리케이션 로딩 시점에 데이터베이스 Table을 생성하는 기능을 지원한다.

  • 애플리케이션 실행(로딩) 시점에 DDL을 통해 테이블을 자동 생성한다
  • 테이블 중심 👉 객체 중심
  • 데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성한다.
  • 이렇게 생성된 DDL은 꼭 개발 장비에서만 사용해야 한다.
  • 생성된 DDL은 운영서버에 적용할 때 바로 사용하지 말고 적절히 다듬은 후 사용할 것을 권장한다.

📢hibernate.hbm2ddl.auto (데이터베이스 스키마 자동생성 속성)

persistence.xml 파일에서 속성을 설정할 수 있다.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    <persistence-unit name="hello">
        <properties>
            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.user" value="sa"/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>

            <!-- 옵션 -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="create" /> // 이 부분
            <!--            <property name="hibernate.jdbc.batch_size" value="10"/>-->
        </properties>
    </persistence-unit>
</persistence>
spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:tcp://localhost/~/test
    username: sa
    password: 
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.H2Dialect
        ddl-auto: create
        show_sql: true
        format_sql: true
        use_sql_comments: true
    show-sql: true
    generate-ddl: true
    open-in-view: false
  • <property name="hibernate.hbm2ddl.auto" value="create" />
@Entity// JPA가 관리하게 된다.
public class Member {

    @Id
    private Long id;
    private String name;

    public Member() {
    }

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

  • drop table Member if exists : Member라는 테이블이 존재하면 drop한다
  • create table Member .. : Member라는 테이블을 생성한다.
    👉 어플리케이션 로딩 시점에 @Entity 가 매핑된 클래스에 맞는 테이블을 생성한다.
  • <property name="hibernate.hbm2ddl.auto" value="create-drop" />

create와 같지만 어플리케이션 종료 시점에 테이블을 drop한다.
👉 테스트케이스 실행시킬 때 사용된다.

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

Member 테이블은 처음에 id, name 만 존재한다.

👉 이후에 age 속성을 추가한다.

drop table Member 하지 않고 alter table Member로 추가된 속성을 반영한다.

만약 age속성을 다시 지운다면 어떻게 될까 ❓
👉 아무 일도 일어나지 않는다. (속성 추가만 반영된다)

  • <property name="hibernate.hbm2ddl.auto" value="validate" />
@Entity// JPA가 관리하게 된다.
public class Member {

    @Id
    private Long id;
    private String name;
    private int age2; // Member 테이블에 없는 속성

    public Member() {
    }

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

private int age2; 에 매핑된 속성이 없다고 알려준다.
👉 엔티티와 테이블이 정상 매핑되었는지 확인할 때 사용한다.

📢데이터베이스 방언 별로 달라지는 DDL

  • ✍<propertyname="hibernate.dialect"value="org.hibernate.dialect.H2Dialect"/>

H2의 기본 문자 자료형은 varchar이다.

  • ✍<propertyname="hibernate.dialect"value="org.hibernate.dialect.Oracle12cDialect"/>

오라클의 기본 문자 자료형은 varchar2이다.

📢데이터베이스 스키마 자동 생성시 주의점

  • 📌운영 장비에는 절대 create, create-drop, update 사용하면 안된다. (큰일난다)
  • 개발 초기 단계는 create 또는 update 를 사용한다.
  • 테스트 서버는 update 또는 validate 를 사용한다.
    • 여러 개발자가 함께 테스트한다.
    • create를 사용하면 데이터가 날아가기 때문에 사용을 권장하지 않는다.
  • 스테이징과 운영 서버는 validate 또는 none를 사용한다.

👉 그냥 로컬 PC에서만 사용해라.

📢DDL 생성 기능

  • ✍제약조건 추가

회원 이름 속성이 필수인 경우 nullable = false,
길이를 10자 미만으로 설정할 경우 length = 10
👉 @Column(nullable = false, length = 10)

@Entity// JPA가 관리하게 된다.
public class Member {

    @Id
    private Long id;

    @Column(unique = true, length = 10)
    private String name;

...
}

  • length = 10 제약조건으로
    👉 name varchar(10)
  • unique = true 제약조건으로
    👉 add constraint UK_ektea7vp6e3low620iewuxhlq unique (name)
  • ✍유니크 제약조건 추가

    👉 @Table(uniqueConstraints = {@UniqueConstraint( name = "NAME_AGE_UNIQUE", columnNames = {"NAME", "AGE"} )})

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


✅필드와 컬럼 매핑

@Entity
public class Member {

    @Id
    private Long id;

    @Column(name = "name")
    private String username; // 객체 이름은 username, DB 컬럼 이름은 name

    private Integer age;

    @Enumerated(EnumType.STRING)
    private RoleType roleType;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createdDate;

    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;

    @Lob // varchar보다 큰 사이즈의 데이터 타입
    private String description;

    public Member() {
    }
}

📢매핑 어노테이션

속성값에 @Transient 어노테이션을 설정하면 DB에 생성되지 않는다. (메모리에서만 사용하는 변수가 됨)

✍@Column (제일 중요)

unique 제약조건 : @Column보다 @Table에서 사용하는 것을 권장

✍@Enumerated

자바 enum 타입을 매핑할 때 사용하는 어노테이션이다.

기본 값이 EnumType.ORDINAL 이지만 절대 사용하면 안된다.
👉 EnumType.STRING을 사용할 것 !

  • enum의 순서가 변경되면 꼬이게 된다.
  • 📌@Enumerated(EnumType.STRING) 필수

✍@Temporal

날짜 타입 (java.util.Date, java.util.Calendar) 을 매핑할 때 사용하는 어노테이션이다.

  • Java8 이후의 LocalDate (연도, 월), LocalDateTime (연도, 월, 일)을 사용할 때는 생략 가능 (최신 하이버네이트에서 지원)

✍@Lob

데이터베이스 BLOB, CLOB 타입과 매핑하는 어노테이션이다.

  • @Lob에는 지정할 수 있는 속성이 없다.
  • 매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑
    • CLOB: String, char[], java.sql.CLOB
    • BLOB: byte[], java.sql. BLOB

✍@Transient

필드 매핑을 사용하지 않는 변수에 사용하는 어노테이션이다.

  • 데이터베이스에 저장, 조회가 안된다.
  • 주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용한다.

✅기본 키 매핑

사용할 수 있는 어노테이션은 @Id, @GeneratedValue 이다.

📢기본 키 매핑 방법

@Id 
@GeneratedValue(strategy = GenerationType.IDENTITY) 
private Long id;
  • 직접 할당 : @Id만 사용
    👉 개발자가 테이블의 pk를 설정할 경우 @Id를 사용한다.
  • 자동 생성 : @GeneratedValue
    👉 데이터베이스가 pk 값을 자동으로 할당할 경우 @GeneratedValue를 사용한다.
    • @GeneratedValue(strategy = GenerationType.IDENTITY)
      • 데이터베이스에 위임
      • MYSQL
    • @GeneratedValue(strategy = GenerationType.SEQUENCE)
      • 데이터베이스 시퀀스 오브젝트 사용
      • ORACLE
      • @SequenceGenerator 필요
    • @GeneratedValue(strategy = GenerationType.TABLE)
      • 키 생성용 테이블 사용, 모든 DB에서 사용
      • @TableGenerator 필요
    • @GeneratedValue(strategy = GenerationType.AUTO)
      • 방언에 따라 IDENTITY, SEQUENCE, TABLE 중 하나로 자동 지정
      • 기본

✍ IDENTITY 전략

  • 기본 키 생성을 데이터베이스에 위임
  • 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용
    • 예: MySQL의 AUTO_ INCREMENT
  • JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
  • AUTO_ INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있음
  • IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회

H2 방언의 경우 (H2Dialect)

MySQL 방언의 경우 (MySQL5Dialect)

  • 📌 IDENTITY 전략 특징
@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // id에 값을 넣으면 안되는 전략
    private Long id;

    @Column(name = "name", nullable = false)
    private String username; // 객체 이름은 username, DB 컬럼 이름은 name

    public Member() {
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}


  • pk값이 IDENTITY 전략을 따르면 값을 넣어줄 수 없다.
    • 👉 pk값이 null로 데이터베이스에 날라가면 (null, ?) insert 쿼리를 실행할 때 pk값이 설정된다
    • 👉 데이터베이스에 insert되기 전까지 pk값을 모른다.
  • 영속성 컨택스트에 관리되기 위해서 무조건 pk값이 있어야 한다. (1차 캐시는 pk가 key, 데이터가 value인 테이블)
    • 📌하지만 IDENTITY 전략을 따르게 되면 데이터베이스에 insert되기 전까지 pk값을 알지 못한다.
    • IDENTITY 전략을 따를 경우에만 em.persist() 시점에 INSERT 쿼리를 실행한뒤 데이터베이스에서 pk값을 조회한다.
      • JPA는 보통 tx.commit() 시점에서 INSERT 쿼리를 실행한다.

IDENTITY 전략에서 쓰기 지연 SQL 저장소를 통해 INSERT 쿼리를 날리는 것은 불가능하다.

✍ SEQUENCE 전략

  • 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트 (예: 오라클 시퀀스)
  • 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용
@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE) // id에 값을 넣으면 안되는 전략
    private Long id;

    @Column(name = "name", nullable = false)
    private String username; // 객체 이름은 username, DB 컬럼 이름은 name

    public Member() {
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}




  • create sequence hibernate_sequence start with 1 increment by 1 : 데이터베이스에서 값을 생성하는 시퀀스 객체를 만든다
  • call next value for hibernate_sequence : 시퀀스 객체를 통해 값을 가져온 뒤, 가져온 값으로 id를 세팅한다.
  • 📌@SequenceGenerator

테이블 마다 시퀀스를 따로 관리하기 위해서 SequenceGenerator를 매핑한다.

@Entity
@SequenceGenerator(
        name = "MEMBER_SEQ_GENERATOR",
        sequenceName = "MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
        initialValue = 1, allocationSize = 1)
public class Member {

    @Id // 기본 키 매핑 어노테이션
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
            generator = "MEMBER_SEQ_GENERATOR")
    private Long id;

    @Column(name = "name", nullable = false)
    private String username; // 객체 이름은 username, DB 컬럼 이름은 name

    public Member() {
    }
}
  • sequenceName = "MEMBER_SEQ" : MEMBER_SEQ 라는 시퀀스가 생긴다
  • generator = "MEMBER_SEQ_GENERATOR" : 원하는 SequenceGenerator를 MEMBER_SEQ_GENERATOR로 매핑한다.




  • create sequence MEMBER_SEQ start with 1 increment by 1
    • 시퀀스 MEMBER_SEQ를 1부터 시작하고 1씩 증가시킨다.
  • 영속성 컨택스트에 넣기 위해 pk값을 반드시 알아야 한다.
    • SEQUENCE 전략에서는 시퀀스(MEMBER_SEQ)에서 pk값을 가져와야 한다.
    • 그래서 call next value for MEMBER_SEQ가 실행된다.
    • 즉, 데이터베이스의 MEMBER_SEQ 시퀀스에서 pk값을 얻어와서 member 객체의 id 속성에 넣어준 뒤 영속성 컨택스트에 저장한다.
      • 데이터베이스에 INSERT 쿼리가 실행되지 않고 tx.commit() 시점에서 실행된다.

SEQUENCE전략에서 쓰기 지연 SQL 저장소를 통해 INSERT 쿼리를 날리는 것이 가능하다.

  • call next value for MEMBER_SEQ 를 계속 실행하는 것 보다
    INSERT 쿼리 한번 실행한 뒤 pk값을 얻는게 더 성능상 유리한 방법 아닐까 ❓

SequenceGenerator의 allocationSize 속성을 통해 성능 최적화가 가능하다.

@Entity
@SequenceGenerator(
        name = "MEMBER_SEQ_GENERATOR",
        sequenceName = "MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
        initialValue = 1, allocationSize = 50)
public class Member {

    @Id // 기본 키 매핑 어노테이션
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
            generator = "MEMBER_SEQ_GENERATOR")
    private Long id;

    @Column(name = "name", nullable = false)
    private String username; // 객체 이름은 username, DB 컬럼 이름은 name

    public Member() {
    }
}



  • allocationSize = 50 으로 미리 데이터베이스의 시퀀스 값을 50개를 증가시켜둔 다음 (50)
    메모리에서 1씩 증가시킨다.
    메모리의 값이 50이 되면 그때서야 call next value for MEMBER_SEQ 을 호출하여
    데이터베이스의 시퀀스 값 50개를 증가시킨다 (100)
    👉 데이터베이스에서 call next value for MEMBER_SEQ가 실행될 때마다 시퀀스 값이 50개씩 증가한다.


  • 처음에는 DB의 MEMBER_SEQ 값 = 1 이다.
    • 어플리케이션 메모리에서 시퀀스 값을 50개씩 사용해야하는데 call next value for MEMBER_SEQ을 실행하니 DB의 시퀀스 값이 1이다
    • 그래서 한번 더 호출하여 DB의 시퀀스 값을 51까지 늘려준다.
  • em.persist(member1); : DB의 MEMBER_SEQ 값 = 51 , 어플리케이션 메모리에서 시퀀스 값은 1이다
  • em.persist(member2); : DB의 MEMBER_SEQ 값 = 51 , 어플리케이션 메모리에서 시퀀스 값은 2이다
  • em.persist(member3); : DB의 MEMBER_SEQ 값 = 51 , 어플리케이션 메모리에서 시퀀스 값은 3이다

✍ TABLE 전략

  • 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략
    • 장점: 모든 데이터베이스에 적용 가능
    • 단점: 성능
  • 📌@TableGenerator
@Entity
@TableGenerator(
        name = "MEMBER_SEQ_GENERATOR",
        table = "MY_SEQUENCE", //매핑할 데이터베이스 시퀀스 이름
        pkColumnValue = "MEMBER_SEQ", allocationSize = 1)
public class Member {

    @Id // 기본 키 매핑 어노테이션
    @GeneratedValue(strategy = GenerationType.TABLE,
            generator = "MEMBER_SEQ_GENERATOR")
    private Long id;

    @Column(name = "name", nullable = false)
    private String username; // 객체 이름은 username, DB 컬럼 이름은 name

    public Member() {
    }
}



  • @TableGenerator의 table = "MY_SEQUENCE"으로 MY_SEQUENCE 라는 테이블이 만들어진다.
    • pkColumnValue = "MEMBER_SEQ", allocationSize = 1 으로 MY_SEQUENCE 테이블에 값이 채워진다.

  • Member 테이블의 Id=1 값이 MY_SEQUENCE 테이블로부터 설정된 것
  • 📌@TableGenerator - 속성

allocationSize 속성을 설정하면 SQUENCE 전략과 동일한 방식으로 사용된다.

✍권장하는 식별자 전략

  • 기본 키 제약 조건: null이면 안되고 (NOT NULL), 유일해야 하고 (UNIQUE), 값이 변하면 안된다.
    • 미래까지 이 조건을 만족하는 자연키는 찾기 어렵다. 다시말해, 값이 변하지 않는 자연키를 찾기 어렵다
    • 예를 들어 주민등록번호도 기본 키로 적절하기 않다.
    • 👉 대신 대리키(대체키)를 사용하자.
      • 자연키 : 비즈니스적으로 의미있는 키
      • 대체키 : 비즈니스적으로 의미 없는 키
  • 권장: Long 형 + 대체키 + 키 생성전략 사용

0개의 댓글