HoonK212 GitHub_Blog

[JPA] JPA 기초 2

JPA 기초 2

엔티티 삭제

Member memberA = em.find(Member.class, "memberA");
em.remove(memberA);
  • em.remove()에 삭제 대상 엔티티를 넘겨주면 엔티티를 삭제한다. 즉시 삭제하는 것이 아니라, 엔티티 등록과 비슷하게 삭제 쿼리를 쓰기 지연 SQL 저장소에 등록한 후, 트랜잭션을 커밋해서 플러시를 호출하면 실제 데이터베이스에 삭제 쿼리를 전달한다.
  • memberA는 영속성 컨텍스트에서 제거된다.

영속석 컨텍스트를 플러시 하는 3가지 방법

  • 플러시란?
    • 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 것
      1. 변경 감지가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해서 수정된 엔티티를 찾는다.
      2. 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록한다.
      3. 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다.(등록, 수정, 삭제 쿼리)
    • 영속성 컨텍스트를 비우지는 않는다.
    • 트랜잭션이라는 작업 단위가 중요함 → 커밋 직전에만 동기화하면 됨
  1. em.flush() - 직접 호출

    엔티티 매니저의 flush() 메서드를 직접 호출해서 영속성 컨텍스트를 강제로 플러시한다.

  2. 트랜잭션 커밋 - 플러시 자동 호출

    데이터베이스에 변경 내용을 SQL로 전달하지 않고 트랜잭션만 커밋하면 어떤 데이터도 데이터베이스에 반영되지 않는다. 따라서 트랜잭션을 커밋하기 전에 꼭 플러시를 호출해서 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영해야 한다. JPA는 이런 문제를 예방하기 위해 트랜잭션을 커밋할 때 플러시를 자동으로 호출한다.

  3. JPQL 쿼리 실행 - 플러시 자동 호출

    JPQL이나 Criteria 같은 객체지향 쿼리를 호출할 때도 플러시가 실행된다.


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

  • JPA는 데이터베이스 스키마를 자동으로 생성하는 기능을 지원한다. (애플리케이션 실행 시점)
  • 데이터베이스에 맞는 적절한 DDL을 생성
  • 생성된 DDL은 개발 장비에서만 사용하길 권장하며 운영서버에서는 사용하지 않거나, 적절히 다듬은 후에 사용해야 한다.
    • 운영 장비에는 create, create-drop, update 사용 금지
    • 개발 초기 단계는 create 또는 update
    • 테스트 서버는 update 또는 validate
    • 스테이징과 운영 서버는 validate 또는 none
  • hibernate.hbm2ddl.auto 속성
  • 2022-11-26-JPA-01.png

엔티티 매핑

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

객체와 테이블 매핑

  • @Entity
    • JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 어노테이션을 필수로 붙여야 한다.
    • @Entity가 붙은 클래스는 JPA가 관리하고, 엔티티라 부른다.
    • @Entity 적용 시 주의사항
      • 기본 생성자가 필수(파라미터가 없는 public 또는 protected 생성자)
      • final 클래스, enum, interface, inner 클래스에는 사용 불가
      • 저장할 필드에 final 사용 불가
    • @Entity 속성
    • 2022-11-26-JPA-02.png
  • @Table
    • 엔티티와 매핑할 테이블을 지정
    • 생략하면 매핑한 엔티티 이름을 테이블 이름으로 사용
    • @Table 속성
    • 2022-11-26-JPA-03.png

기본 키 매핑

  • 직접 할당과 자동 생성

    • 직접 할당 : 기본 키를 애플리케이션에서 직접 할당, @Id만 사용
    • 자동 생성 : 대리 키 사용 방식

      • IDENTITY 전략

        • 기본 키 생성을 DB에 위임하는 전략, DB에 값을 저장하고 나서야 기본 키 값을 구할 수 있을 때 사용한다.
        • 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용
          // IDENTITY 매핑 코드
          @Entity
          public class Member{
        
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          private Long id;
          ...
          }
        
          private static void logic(EntityManager em) {
          Member member = new Member();
          em.persist(member);
          System.out.println("member.id = " + member.getId());
          }
          // 출력: member.id = 1
        
        • 주의사항

          엔티티가 영속 상태가 되려면 식별자가 반드시 필요한데 IDENTITY 식별자 생성 전략은 엔티티를 데이터베이스에 저장해야 식별자를 구할 수 있으므로 em.persist()를 호출하는 즉시 INSERT SQL이 데이터베이스에 전달된다. (이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않음)

      • SEQUENCE 전략

        • 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트이다. SEQUENCE 전략은 이 시퀀스를 사용해서 기본 키를 생성한다.
        • 시퀀스를 지원하는 Oracle, PostgreSQL, DB2, H2 데이터베이스에 사용
          // 시퀀스 DDL
          CREATE TABLE MEMBER (
              ID BIGINT NOT NULL PRIMARY KEY,
          DATA VARCHAR(255)
          )
        
          // 시퀀스 생성
          CREATE SEQUENCE MEMBER_SEQ START WITH 1 INCREMENT BY 1;
        
          // 시퀀스 매핑 코드
          @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;
          ...
          }
        
          // 시퀀스 매핑 코드
          @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;
          ...
          }
        
        • IDENTITY 전략 - 먼저 엔티티를 데이터베이스에 저장한 후에 식별자를 조회해서 엔티티의 식별자에 할당한다.
        • SEQUENCE 전략 - em.persist()를 호출할 때 먼저 데이터베이스 시퀀스를 사용해서 식별자를 조회하고 해당 식별자를 엔티티에 할당하여 영속성 컨텍스트에 저장한다. 이후 트랜잭션을 커밋해서 플러시가 일어나면 엔티티를 데이터베이스에 저장한다.
        • @SequenceGenerator 속성
        • 2022-11-26-JPA-04.png

          • 주의사항

            allocationSize의 기본값 : 50

      • TABLE 전략

        • 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략
        • 모든 DB에 적용이 가능하지만 성능이 떨어진다.
          // TABLE 전략 키 생성 DDL
          create table MY_SEQUENCE(
              sequence_name varchar(255) not null ,
          next_val bigint,
          primary key ( sequence_name )
          )
        
        • sequence_name 컬럼을 시퀀스 이름으로 사용하고 next_val 컬럼을 시퀀스 값으로 사용
          // TABLE 전략 매핑 코드
          @Entity
          @TableGenerator(
          name = "BOARD_SEQ_GENERATOR",
          table = "MY_SEQUENCES",
          pkColumnValue = "BOARD_SEQ", allocationSize = 1)
          public class Member {
        
          @Id
          @GeneratedValue(strategy = GenerationType.TABLE,
                          generator = "BOARD_SEQ_GENERATOR")
          private Long id;
          ...
          }
        
          // TABLE 전략 매핑 코드
          @Entity
          @TableGenerator(
          name = "BOARD_SEQ_GENERATOR",
          table = "MY_SEQUENCES",
          pkColumnValue = "BOARD_SEQ", allocationSize = 1)
          public class Member {
        
          @Id
          @GeneratedValue(strategy = GenerationType.TABLE,
                          generator = "BOARD_SEQ_GENERATOR")
          private Long id;
          ...
          }
        
        • @TableGenerator 속성
        • 2022-11-26-JPA-05.png

          • 주의사항

            allocationSize의 기본값 : 50


권장하는 식별자 전략

  • DB의 기본 키 제약 : NOT NULL, UNIQUE
    • 미래까지 위 조건을 만족하는 자연키를 찾기는 어렵다. 대리키(대체키)를 사용하는 것이 낫다.
  • Long형 + 대체키 + 키 생성전략 사용
    • AUTO_INCREMENT나 SEQUENCE 오브젝트 등을 사용

allocationSize의 기본값이 50인 이유

  • DB에 여러번 접근하는 것을 방지하기 때문에 성능적으로 우위
  • 동작 방식
    • allocation 값이 50이면 시퀀스를 한 번에 50을 증가시킨 다음에 1 ~ 50까지는 메모리에 시퀀스 값을 할당
    • 51이 되면 시퀀스 값을 100으로 증가시킨 다음 51 ~ 100까지 메모리에서 식별자를 할당
  • 위 방법은 시퀀스 값을 선점하므로 여러 JVM이 동시에 동작해도 기본 키 값이 충돌하지 않는 장점이 있다. 반면에 데이터베이스에 직접 접근해서 데이터를 등록할 때 시퀀스 값이 한 번에 많이 증가한다는 점을 염두해두어야 한다. 이런 상황이 부담스럽고 INSERT 성능이 중요하지 않으면 allocationSize의 값을 1로 설정하면 됨

필드와 컬럼 매핑

  • @Column
    • 객체 필드를 테이블 컬럼에 매핑
    • 속성 중에 name, nullabe이 주로 사용되고 나머지는 잘 사용되지 않는 편
    • insert, updatable 속성은 데이터베이스에 저장되어 있는 정보를 읽기만 하고 실수로 변경하는 것을 방지하고 싶을 때 사용
    • @Column을 생략할 경우
      • 객체 타입일 때는 nullable 속성이 기본값으로 적용 (true)
      • 자바 기본 타입일 때는 JPA가 not null 제약조건을 추가
      • nullable 속성의 값을 따로 지정하여 관리하는 것이 안전
    • @Column 속성
    • 2022-11-26-JPA-06.png
  • @Enumerated
    • enum 타입을 매핑할 때 사용
    • @Enumerated 속성
    • 2022-11-26-JPA-07.png
    • 주의사항
      • 기본값인 ORDINAL은 enum의 순서가 바뀌면 안되므로 EnumType.STRING을 권장
  • @Lob
    • BLOB, CLOB 타입과 매핑
    • 지정할 수 있는 속성이 없으며, 매핑하는 필드 타입이 문자면 CLOB로 매핑하고 나머지는 BLOB로 매핑
      • CLOB: String, char[], java.sql.CLOB
      • BLOB: byte[], java.sql.BLOB
  • @Transient
    • 이 필드는 매핑하지 않는다.
    • DB에 저장하지도 조회하지도 않는다.
    • 객체에 임시로 어떤 값을 보관하고 싶을 때 사용
  • @Access
    • JPA가 엔티티 데이터에 접근하는 방식을 지정
    • 필드 접근
      • AccessType.FIELD는 필드에 직접 접근
      • 필드 접근 권한이 private이어도 접근할 수 있다.
    • 프로퍼티 접근
      • AccessType.PROPERTY로 지정
      • 접근자(getter)를 사용

“[JPA] JPA 기초” 시리즈는 인프런 김영한 강사님의 강좌를 기반으로 쓰여졌습니다.

(참고 : https://www.inflearn.com/users/@yh)