JPA

2023. 2. 23. 18:11JPA

◎ JPA(Java Persistance API)

-> 자바 진영에서 사용하는 ORM 기술의 *표준 사양

* 표준 사양은 자바의 인터페이스로 사양이 정의되어 있어 JPA라는 표준 사양을 구현한 구현체는 따로 있음을 의미한다.

 

Hibernate ORM

-> JPA에서 정의한 인터페이스를 구현한 구현체로 JPA에서 지원하는 기능 외에 hibernate자체적으로 사용할 수 있는 API도 지원한다.

 

◎ 데이터 액세스 계층과 JPA

-> jpa는 데이터 액세스 계층 상단에 위치한다.

-> 데이터 저장, 조회 등 데이터 작업은 JPA를 거쳐 JPA의 구현체인 Hibernate ORM을 통해 진행되고, Hibernate ORM은 내부적으로 JDBC API를 사용해서 데이터베이스에 접근한다.

 

◎ 영속성 컨텍스트

-> JPA의 P는 Persistance(영속, 지속성)이란 뜻이다. 어떤 데이터가 들어와서 오래 지속됨을 의미한다.

-> ORM은 객체와 데이터베이스 테이블의 매핑을 통해 엔티티 클래스 내부에 포함된 정보를 테이블에 저장하는 기술이다. jpa에서 테이블과 매핑되는 엔티티 객체 정보를 영속성 컨텍스트라는 곳에 보관하여 애플리케이션 내에서 지속적으로 관리한다.

-> 영속성 컨텍스트에는 1차 캐시, 쓰기 지연 SQL 저장소로 이루어져 있다.

-> 엔티티 정보를 영속성 컨텍스트에 저장하면 1차 캐시에 해당 엔티티가 저장된다. 

-> 예시 1

@Getter
@Setter
@NoArgsConstructor
@Entity // 엔티티 클래스로 지정
public class Member { // 엔티티 클래스
    @Id
    @GeneratedValue
    private Long memberId;
    private String email;

    public Member(String email) {
        this.email = email;
    }
}
@Configuration
public class JpaBasicConfig {
    private EntityManager em; // 영속성 컨텍스트는 EntityManager에 의해 관리된다.
    private EntityTransaction tx;


    @Bean
    public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) {
        this.em = emFactory.createEntityManager();
        this.tx = em.getTransaction();

        return args -> {
            example01();
        };
    }

    private void example01(){
        Member member = new Member("hong@email.com");
        em.persist(member); // 영속성 컨텍스트에 member 객체 정보가 저장된다.

        // find의 첫 번째 파라미터는 조회할 엔티티의 클래스 타입, 두 번째 파라미터는 조회할 엔티티 클래스의 식별자 값
        Member resultMember = em.find(Member.class, 1L);
        System.out.println("id : " + resultMember.getMemberId() + ", email : " + resultMember.getEmail());
    }
}

-> 결과

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 : hong@email.com

-> 결과를 보면 jpa가 내부적으로 테이블을 자동으로 생성하고 테이블의 기본키도 할당한다.

-> em.persist(member)를 호출할 경우 영속성 컨텍스트에 member객체가 저장되지만 실제 테이블에 회원 정보를 저장하는 것은 아니다.

 

-> 예시2

@Configuration
public class JpaBasicConfig {
    private EntityManager em; // 영속성 컨텍스트는 EntityManager에 의해 관리된다.
    private EntityTransaction tx; // JPA에서는 이 Transaction 객체를 기준으로 데이터베이스의 테이블에 데이터를 저장


    @Bean
    public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) {
        this.em = emFactory.createEntityManager();
        this.tx = em.getTransaction();

        return args -> {
            example02();
        };
    }

    private void example01(){
        Member member = new Member("hong@email.com");
        em.persist(member); // 영속성 컨텍스트에 member 객체 정보가 저장된다.

        // find의 첫 번째 파라미터는 조회할 엔티티의 클래스 타입, 두 번째 파라미터는 조회할 엔티티 클래스의 식별자 값
        Member resultMember = em.find(Member.class, 1L);
        System.out.println("id : " + resultMember.getMemberId() + ", email : " + resultMember.getEmail());
    }

    private void example02(){
        tx.begin(); // Transaction 시작
        Member member = new Member("hsh@email.com");

        em.persist(member); // member를 영속성 건텍스트에 저장

        tx.commit(); // commit()호출 시점에 영속성 컨텍스트에 저장된 member 객체를 데이터베이스의 테이블에 저장

        // 영속성 컨텍스트에 있는 member 객체를 1차 캐시에서 조회, 1차 캐시에 member 객체 있으면 별도로 테이블에 SELECT 쿼리 전송하지 않음
        Member resultMember = em.find(Member.class, 1L);
        System.out.println("id : " + resultMember.getMemberId() + ", email : " + resultMember.getEmail());

        Member resultMember2 = em.find(Member.class, 2L);
        System.out.println(resultMember2 == null); // 식별자가 2L인 member객체는 없기 때문에 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 : hsh@email.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

- member 객체가 영속성 컨텍스트에 저장되고, 이후 em.find(Member.class, 1L)을 호출하면 영속성 컨텍스트에 저장된 member객체를 1차 캐시에서 조회한다.

- 식별자 값이 2L인 member 객체를 조회하는데 해당 객체는 영속성 컨텍스트에 없기 때문에 영속성 컨텍스트에서 찾지 않고 select 쿼리를 테이블에 직접 전송한다.

 

※ em.persist() 호출 시 영속성 컨텍스트의 1차 캐시에 엔티티 클래스의 객체가 저장되고, 쓰기 지연 저장소에 INSERT 쿼리가 등록된다.

※ tx.commit()을 하면 쓰기 지연 SQL 저장소에 등록된 INSERT 쿼리가 실행되고, 실행된 INSERT 쿼리는 쓰기 지연 SQL 저장소에서 사라진다.

※ em.find() 호출 시 해당 객체가 1차 캐시에 있는지 찾아보고 없으면 테이블에 SELECT 쿼리를 전송하여 조회한다.

 

-> 예시3

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

        Member member1 = new Member("hong@yahoo.co.kr");
        Member member2 = new Member("hong@yaho.co.kr");

        em.persist(member1);
        em.persist(member2);

        tx.commit();
    }

예시3 코드의 영속성 컨텍스트(tx.commit()실행 전 상황)

- em.persist(member1), em.persist(member2)로 member1,member2 객체가 영속성 컨텍스트에 저장되고, 쓰기 지연 SQL 저장소에 저장된 INSERT 쿼리는 실행되지 않다가 tx.commit()이 호출되면 INSERT쿼리가 모두 실행되고 실행된 쿼리는 쓰기 지연 SQL 저장소에서 사라진다.

-> 결과

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 (?, ?)

 

-> 예시4(영속성 컨텍스트와 테이블에 엔티티 업데이트)

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

        em.persist(new Member("hong@yahoo.co.kr"));
        tx.commit(); // 쓰기 지연 SQL 저장소 내 INSERT 쿼리 실행, member 객체는 테이블에 저장됨

        tx.begin();
        Member member1 = em.find(Member.class, 1L); // 테이블에 저장된 member 객체를 영속성 컨텍스트 1차 캐시에서 조회
        member1.setEmail("hong@naver.com");// 이메일 변경
        tx.commit(); // 쓰기 지연 SQL 저장소 내 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=?

- 엔티티 값을 setter메서드로 변경 후 tx.commit()을 하면 변경된 엔티티와 이전에 상태의 엔티티의 스냅샷을 비교하여 변경된 값이 있으면 쓰기 지연 SQL 저장소에 UPDATE 쿼리를 등록하고 UPDATE 쿼리를 실행한다.

 

-> 예시5(영속성 컨텍스트와 테이블의 엔티티 삭제)

private void example05(){
        tx.begin();
        em.persist(new Member("hong@yahoo.co.kr")); // Member 클래스 객체를 영속성 컨텍스트 1차 캐시에 저장
        tx.commit();

        tx.begin();
        Member member = em.find(Member.class, 1L); // 위에서 영속성 컨텍스트에 저장된 Member 클래스 객체를 영속성 컨텍스트 1차 캐식에서 조회
        em.remove(member); // 영속성 컨텍스트 1차 캐시에 있는 엔티티 삭제 요청
        tx.commit(); // 영속성 컨텍스트 1차 캐시에 있는 엔티티 객체 지우고 쓰기 지연 SQL 저장소에 등록된 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=?

 

※ tx.commit()메서드가 호출되면 JPA 내부적으로 em.flush()메서드가 호출되어 영속성 컨텍스트의 변경사항을 데이터베이스의 테이블에 반영한다.

'JPA' 카테고리의 다른 글

flush  (0) 2023.05.07
더티 체킹  (0) 2023.05.07
식별자 전략  (0) 2023.02.28
JPA 프로젝트 생성하고 실행하기  (0) 2022.03.12
JPA  (0) 2022.02.25