JPA

jungseo·2023년 6월 21일
0

Spring

목록 보기
8/23
post-thumbnail

JPA란

  • JPA(Java Persistence API)

    • 현재 Jakarta Persistence
  • Java ORM(Object Relational Mapping) 기술의 표준 사양

    표준 사양은 인터페이스로 정의되어 해당 사양을 구현한 구현체는 따로 있다.

  • Hibernate ORM

    • JPA 표준 사양을 구현한 구현체
    • JPA에서 지원하는 기능 이외에 자체 API도 제공

  • 데이터 조회, 저장 등의 작업은 JPA를 거쳐 JPA 구현체인 Hibernate ORM을 통해 이루어짐
  • Hibernate ORM은 JDBC API를 이용해 데이터베이스에 접근

1. 영속성 컨텍스트

  • Persistence Context
  • ORM은 객체와 데이터베이스 테이블의 매핑을 통해 엔티티 객체 안에 포함된 정보를 테이블에 저장하는 기술
  • JPA에서는 테이블과 매핑되는 엔티티 객체 정보를 영속성 컨텍스트에 보관하여 애플리케이션에서 오래 지속되게 함
  • 보관된 엔티티 정보는 테이블에 데이터를 저장, 수정, 조회, 삭제 하는데 사용
  • 1차 캐시에 엔티티 정보 저장
  • 쓰기 지연 SQL 저장소엔 1차 캐시에 저장된 엔티티에 대한 쿼리 저장

2. JPA API 사용

  • build.gradle에 Spring Data JPA 의존 라이브러리 추가
    • JPA API만 사용하기 위해선 별도의 의존 라이브러리를 추가해야 한다.
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  • application.yml 설정 추가
spring:
  h2:
    console:
      enabled: true
      path: /h2     
  datasource:
    url: jdbc:h2:mem:test
  jpa:
    hibernate:
      ddl-auto: create  # (1) 스키마 자동 생성
    show-sql: true      # (2) SQL 쿼리 출력
  • (1) 엔티티 클래스를 정의하고 애플리케이션 실행 시 해당 엔티티와 매핑되는 테이블을 자동 생성
    • Spring Date JDBC에선 schema.sql 파일에 테이블 생성 스키마를 직접 지정
  • (2) JPA API를 통해 실행되는 쿼리를 로그로 출력

1. 샘플 코드 실행을 위한 Config 클래스와 엔티티 클래스

  • Config 클래스

    @Configuration 애너테이션을 추가하면 Spring에서 Bean 검색 대상인 Configuration 클래스로 간주해서 @Bean 애너테이션이 추가된 메서드를 검색한 후, 해당 메서드에서 리턴하는 객체를 Spring Bean으로 추가

  • JPA의 영속성 컨텍스트는 EntityManager 클래스에의해 관리
  • (1-1) EntityManageFactory 객체를 Spring으로부터 DI 받을 수 있음
  • (1-2) EntityManagerFactory의 createEntityManager() 메서드로 EntityManager 객체 얻기 가능

  • Member 클래스
@Getter
@Setter
@NoArgsConstructor
@Entity // (1)
public class Member {
    @Id
    @GeneratedValue // (2)
    private Long memberId;

    private String email;

    public Member(String email) {
        this.email = email;
    }
}
  • @GeneratedValue : 테이블에서 기본키가 되는 식별자를 자동으로 설정

2. 영속성 컨텍스트에 엔티티 객체 저장

    private void example01() {
        Member member = new Member("hgd@gmail.com");

//        (1-3)
        em.persist(member);

//        (1-4)
        Member resultMember = em.find(Member.class, 1L);
        System.out.println("Id = " + resultMember.getMemberId() + ", email = " +
                resultMember.getEmail());
    }
  • (1-3) persis(member) 메서드를 호출하면 영속성 컨텍스트에 member 객체의 정보들이 저장
    • 1차 캐시에 member 객체가 저장되고 쓰기 지연 저장소엔 INSERT 쿼리가 등록
    • persis() 메서드 호출 시 영속성 컨텍스트에 객체를 저장하지만 실제 테이블에는 저장하지 않음
  • (1-4) 영속성 컨텍스트에 객체가 저장되었는지 find(Member.class, 1L) 메서드로 조회
    • (조회할 엔티티 클래스의 타입, 조회할 엔티티 클래스의 식별자 값)
  • 실행 로그
Hibernate: drop table if exists member CASCADE 
Hibernate: drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: create table member (member_id bigint not null, email varchar(255), primary key (member_id))
Hibernate: call next value for hibernate_sequence
Id = 1, email = hgd@gmail.com

3. 테이블과 영속성 컨텍스트에 엔티티 저장

    private void example02() {
//        (2-2)
        tx.begin();
        Member member = new Member("hgd@gmail.com");

//        (2-3)
        em.persist(member);

//        (2-4)
        tx.commit();

//        (2-5)
        Member resultMember1 = em.find(Member.class, 1L);
        System.out.println("Id = " + resultMember1.getMemberId() + ", email = " +
                resultMember1.getEmail());

//        (2-6)
        Member resultMember2 = em.find(Member.class, 2L);

//        (2-7)
        System.out.println(resultMember2 == null);
    }
  • (2-1) EntityManager를 통해 Transaction 객체를 얻음
    • JPA는 Transaction 객체를 기준으로 테이블에 데이터 저장
  • (2-2) tx.begin() 메서드 호출로 Transaction 시작
  • (2-3) 엔티티 객체를 영속성 컨텍스트에 저장
  • (2-4) 영속성 컨텍스트에 저장되어 있는 엔티티 객체를 쓰기 지연 저장소에 있는 쿼리를 실행하여 테이블에 저장
    • commit() 메서드 호출 시 쓰기 지연 저장소에 있던 쿼리는 실행 후 사라짐
  • (2-5) find() 메서드 호출 시 영속성 컨텍스트에 저장한 엔티티 객체를 1차 캐시에서 조회
  • (2-6) 영속성 컨텍스트에 식별자가 2L인 객체가 존재하지 않기 때문에 테이블에 SELECT 쿼리를 전송, (2-7)은 true
  • 실행 로그
Hibernate: drop table if exists member CASCADE 
Hibernate: drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: create table member (member_id bigint not null, email varchar(255), primary key (member_id))
Hibernate: call next value for hibernate_sequence
Hibernate: insert into member (email, member_id) values (?, ?)
Id = 1, email = hgd@gmail.com
Hibernate: select member0_.member_id as member_i1_0_0_, member0_.email as email2_0_0_ from member member0_ where member0_.member_id=?
true

4. 쓰기 지연을 통한 테이블과 영속성 컨텍스트에 엔티티 일괄 저장

    private void example03() {
        tx.begin();

        Member member1 = new Member("hgd1@gmail.com");
        Member member2 = new Member("hgd2@gmail.com");

        em.persist(member1); // (3-1)
        em.persist(member2); // (3-2)

        tx.commit();
    }
  • 실행 로그
Hibernate: drop table if exists member CASCADE 
Hibernate: drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: create table member (member_id bigint not null, email varchar(255), primary key (member_id))
Hibernate: call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
Hibernate: insert into member (email, member_id) values (?, ?)
Hibernate: insert into member (email, member_id) values (?, ?)

5. 영속성 컨텍스트와 테이블에 엔티티 업데이트

    private void example04() {
        tx.begin();
        em.persist(new Member("hgd@gmail.com")); // (4-1)
        tx.commit(); // (4-2)

        tx.begin();
        Member member1 = em.find(Member.class, 1L); // (4-3)
        member1.setEmail("hgd1@gmail.com"); // (4-4)
        tx.commit(); // (4-5)
    }
  • (4-3) 영속성 컨텍스트의 1차 캐시에 저장된 객체가 있기 때문에 테이블이 아닌 영속성 컨텍스트에서 조회

  • (4-4) setter() 메서드로 정보 변경

  • (4-5) commit() 실행시 쓰기 지연 저장소에 등록된 UPDATE 쿼리가 실행

    • setter 메서드로 값을 변경만 해도 tx.commit() 시점에 UPDATE 쿼리가 실행이 되는 이유
      • 영속성 컨텍스트에 엔티티가 저장될 경우에는 저장되는 시점의 상태를 그대로 가지고 있는 스냅샷을 생성
      • 이후 해당 엔티티의 값을 setter 메서드로 변경한 후, commit() 실행 하면 변경된 엔티티와 스냅샷을 비교한 후, 변경된 값이 있으면 쓰기 지연 저장소에 UPDATE 쿼리를 등록하고 UPDATE 쿼리를 실행
  • 실행 로그

Hibernate: drop table if exists member CASCADE 
Hibernate: drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: create table member (member_id bigint not null, email varchar(255), primary key (member_id))
Hibernate: call next value for hibernate_sequence
Hibernate: insert into member (email, member_id) values (?, ?)
Hibernate: update member set email=? where member_id=

6. 영속성 컨텍스트와 테이블의 엔티티 삭제

    private void example05() {
        tx.begin();
        em.persist(new Member("hgd@gmail.com")); // (5-1)
        tx.commit(); // (5-2)

        tx.begin();
        Member member = em.find(Member.class, 1L); // (5-3)
        em.remove(member); // (5-4)
        tx.commit(); // (5-5)
    }
  • (5-4) 영속성 컨텍스트의 1차 캐시에 있는 객체 제거 요청
  • (5-5) commit() 실행시 1차 캐시에 해당 객체를 제거하고 쓰기 지연 저장소에 등록된 DELETE 쿼리 실행
  • 실행 로그
Hibernate: drop table if exists member CASCADE 
Hibernate: drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: create table member (member_id bigint not null, email varchar(255), primary key (member_id))
Hibernate: call next value for hibernate_sequence
Hibernate: insert into member (email, member_id) values (?, ?)
Hibernate: delete from member where member_id=?

사용한 메서드 정리

  • em.persist()를 사용해서 엔티티 객체를 영속성 컨텍스트에 저장
  • em.remove()를 사용해서 엔티티 객체를 영속성 컨텍스트에서 제거
  • em.flush()를 사용해서 영속성 컨텍스트의 변경 사항을 테이블에 반영
  • tx.commit()을 호출하면 내부적으로 em.flush()가 호출

0개의 댓글