관리 메뉴

제뉴어리의 모든것

[Section3] [Spring MVC] JPA - 1 [추후 정리] 본문

코드스테이츠/정리 블로깅

[Section3] [Spring MVC] JPA - 1 [추후 정리]

제뉴어리맨 2022. 9. 3. 14:49

본 내용은 필자가 공부하며 수집하고 이해한 내용이 적혀 있는 내용입니다.

실제와 틀린 내용이 존재 할 수도 있습니다. 감안하여 보시기 바랍니다.

 

JPA(Java Persistence API)란?

JPA(Java Persistence API)는 Java 진영에서 사용하는 ORM(Object-Relational Mapping) 기술의 표준 사양(또는 명세, Specification)입니다. java 진영에서 개발의 가드라인을 제시한 즉, 인터페이스.

쉽게말해, JPA는 JAVA에서 ORM 기술을 사용하기 위해서는 이런이런 메소드들을 구현하고 이런저런 방식으로 작동하게끔 설계를 해라라는 명세이고, 그것이 인터페이스인것이다.

인터페이스인 JPA의 사용

JPA는 인터페이스일 뿐 실제 구현된 내용이 아니다.

그러므로, JPA 스펙으로 개발된 실제 구현체가 필요한데, 구현체로는 Hibernate ORM, EclipseLink, DataNucleus 등이 있다.

 

JPA(Java Persistence API)에서 P(Persistence)의 의미

Persistence는 영속성, 지속성이라는 뜻을 가지고 있다 즉, 무언가를 금방 사라지지 않고 오래 지속되게 한다라는 것이 Persistence의 목적인 것이다.

이 의미는 엔티티가 영속성 컨텍스트 (Persistence Context) 라는 논리적 개념 공간내의, 1차 캐시에 한번 적재된 이후 지속적으로 관리되어, 영속성 컨텍스트가 종료될때까지 DB의 접근을 최소화여 엔티티를 참조할 수 있는 것을 말한다.

 

영속성이 있는 개념과 없는 개념의 차이

쉽게 말해 JPA 기술을 사용하지 않을때와 

JPA 기술을 사용할때로 생각해 보겠다.

JPA 기술을 사용하지 않을때는, 한 처리내 (트랜잭션)에서

특정 테이블의 한 Row 데이터(현재 DB상의 최신 데이터)를 여러번 얻어야할 경우,

그 순간마다 실제 DB에 Select 문을 날려서 Row 데이터를 얻어와야 할것이다.

그러나, 

JPA 기술을 사용하면,

한 처리(트랜잭션) 내에서 특정 Row 데이터 (최신 데이터) 를 얻어와야 할때,

최초 한번의 Select문만 날려주면 최초에 가져온 Row 데이터를 한 처리내에서는 계속 가지고 있으므로 

Select 문을 날릴 필요가 없는것을 말한다.

 

영속성 컨텍스트(Persistence Context) 란?

엔티티 객제 정보가 저장되는 눈에 보이지 않는 논리적인 공간을 말한다.
영속성 컨텍스트는 EntityManager로 접근할 수 있으며,

아래의 그림과 같은 대략적인 모습을 가지고 있다.

영속성 컨텍스트를 이해하려면 예를 들어 설명해야지 더 와닿는다.

그러므로 예를 들어 설명해 보겠다.

 

예를들어.

Member라는 타입의 객체 member를 DB의 Member 테이블에 저장하려고 한다.

그럼 우선 Member 객체를 생성하고 필드값들을 넣어 준뒤 , DB에 저장을 할것이다.

그런데 JPA는 DB에 바로 반영하는것이 아니다

EntityManager가 해당 객체를 persist() 하여 우선 "1차 캐시"에 저장을 한다.

그리고 해당 엔티티의 내용을 DB에 반영할 Create 쿼리문을 "쓰기 지연 SQL 저장소" 에 저장한다.

그리고 EntityManager가 commit() 메소드를 호출하면,

그제서야 쓰기 지연 SQL 저장소에 저장되어 있던 쿼리를 DB에 날려 실제로 DB에 반영이 된다.

그리곤 SQL 저장소에 있던 쿼리들은 삭제된다.

그리고 DB와 동기화된 Entity의 내용은 현재 트랜잭션이 종료되기 전까진 1차캐시에서 유지되어,

이후 해당 Entity에 해당되는 Row 데이터들은 실제로 DB에서 Select 하지 않아도 "1차 캐시"에 있는 Entity를 가지고 와서 참조가 가능해진다.

이때, "1차 캐시에"에 최초로 저장된 Entity의 상태를 SnapShot 이라고 한다.

그리고 현재 트랜잭션이 종료될때 영속성 컨텍스트도 종료되면서 "1차 캐시"에 있던 Entity 의 내용도 소멸된다.

Update와 같은 기능도 이러한 환경속에서 이루어진다.

 

 

EntityManager와 EntityManagerFacotry

EntityManager는 클라이언트에게 요청이 하나 들어올때 (스레드 생성) 마다

EntityManagerFactory에서 하나씩 생성해준다.

즉, 100개의 요청이 동시에 들어오면 100개의 EntityManager가 생성되는 것이다.

물론, 해당 요청의 처리가 종료되면 EntityManager 또한 소멸된다.

그리고 EntityManager는 내부적으로 DB 커넥션풀을 사용하여 DB에 접근한다.

참조 : https://ict-nroo.tistory.com/130

 

[JPA] 영속성 컨텍스트와 플러시 이해하기

영속성 컨텍스트 JPA를 공부할 때 가장 중요한게 객체와 관계형 데이터베이스를 매핑하는 것(Object Relational Mapping) 과 영속성 컨텍스트를 이해하는 것 이다. 두가지 개념은 꼭 알고 JPA를 활용하자.

ict-nroo.tistory.com

 

 

JPA 사용법

build.gradle에 아래의 의존성을 넣어주면 된다.

implementation 'org.springframework.boot:spring-boot-starter-data-jpa' //

 

그리고 Repository 에서는 아래와 같이 JpaRepository<엔티티명, 엔티티의 Id 타입> 를 상속 받도록 해주면 된다.

public interface MemberRepository extends JpaRepository<Member, Long> { // 수정된 부분
    Optional<Member> findByEmail(String email);
}

 

 

JPA에서 사용되는 애노테이션

  • @GeneratedValue
    @GeneratedValue 애너테이션은 식별자를 생성해주는 전략을 지정할 때 사용.
    멤버 변수에 @GeneratedValue 애너테이션을 추가하면 데이터베이스 테이블에서 기본키가 되는 식별자를 자동으로 설정해준다.

    전략에는 4가지가 있으며,
    @GeneratedValue(strategy = GenerationType.IDENTITY) 와 같이 적용한다.

    - IDENTITY
    기본키 생성을 데이터베이스에게 위임하는 방식으로 id값을 따로 할당하지 않아도 데이터베이스가 자동으로 AUTO_INCREMENT를 하여 기본키를 생성해준다.
    즉, Id에 해당하는 필드는 null로써 비워둔 상태로 DB에 쿼리를 날리는것이다.
    Insert문에 넣어줄 Values() 에 기본키가 null로 지정되는것과 같다.
    그럼 DB에서 알아서 Auto_Increment와 같은 기능으로 자동으로 Id값을 지정해주는것이다.

    - SEQUENCE
    기본키가 IDENTITY 전략과 마찬가지로 DB상에서 정해진 순서에 따른 값으로 지정되는것은 같으나 (Auto_Increment와 같이 순차적으로) Entity의 내용이 Insert문으로 바로 DB에 날려지는것이 아니라,
    Insert문을 날리기 전에 DB에 
 call next value for hibernate_sequence

위와 같은 쿼리를 Insert문 전에 날려서 다음 Id값은 무엇이냐? 라고 물어본다.
그렇게 Id값을 얻어 온 뒤에 해당 Id값을 PK에 해당하는 필드에 채워준 뒤, Insert문을 날린다.

 


- TABLE

-  AUTO

위에 두가지 전략에 대해선 추후 정리 하겠지만, 성능면에서 떨이지므로 많이 사용되는 방법은 아니다.

 

 

IDENTITY 전략과 SEQUECE 전략의 차이

  • 테스트용 전체 코드

    - 실행 코드
package com.codestates;

@Configuration
public class JpaBasicConfig {

    private EntityManager em; //JPA 메소드 사용이 가능한 객체
    private EntityTransaction tx;

    @Bean
    public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) { // E.M 생성이 가능한 E.M.F란 객체
        this.em = emFactory.createEntityManager(); // E.M 객체 하나 생성

        this.tx = em.getTransaction();

        return args -> {

            test();
            
        };
    }

    private void test() {
        tx.begin();
        Player player = new Player();
        player.setName("제뉴어리맨");

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

        tx.commit();
    }
}

  

    - 테스트에 사용 된 Player 클래스

@NoArgsConstructor
@Setter
@Getter
@Entity
public class Player {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) //GenerationType.SEQUENCE 테스트시에는 바뀔것.
    Long id;

    @Column(length = 10)
    String name;

}

 

 

  • IDENTITY 전략일때

위에 코드를 실행시키고 파란색 영역의 라인이 현재 멈춰져 있는 브레이크포인트이다.

그리고 아래는 현재까지의 진행상황을 보여주는 콘솔창이다.

즉, 우리가 배운 대로라면 commit()을 호출해야만, 실제로 "쓰기 지연 sql 저장소" 에 있던 쿼리가 DB에 날라간다고 했지만,

em.persist(player)만 하였는데 하이버네이트 (JPA 구현체) 가 실제로 쿼리를 날리고 있다.

왜냐하면, @GeneratedValue 애노테이션의 strategy 전략에 따라 달라지기 때문이다.

현재는 IDENTITY 방식인데, 해당 방식은 DB에게 Id 지정을 완전히 위임하는것이다.

그러므로, DB에 실제 데이터가 들어갈때 Id가 정해지는것이다.

그런데 트랜잭션이 종료되기 전까지 영속성 컨텍스트에 엔티티를 영속하여 가지고 있어야하는데, Id는 DB에만 있으므로,

persist 할때 Insert문을 날려서 DB에서 할당된 Id값을 받아오는것이다.   

위에 코드를 보면 tx.commit() 부분이 트랜잭션의 종료 시점이다.

그리고 그 전까지는 player라는 엔티티는 영속화되어 있어야하고, 

해당 엔티티 데이터는 DB에 따로 select 하지 않아도 "1차 캐시"에서 항상 얻을 수 있어야한다.

그런데 이 엔티티에 Id값이 할당되어 있지 않으면 안되기 때문이다. 

 

 

  • SEQUENCE 전략일때

아까와 또 같은 부분에서의 breakpoint 상태이다.

그러나 콘솔창의 결과는 다르다.

아래를 보자

IDENTITY 테스트 상황과 같은 지점에서 멈춘 상태인데,

쿼리의 내용이 다르다.

즉, DB에게 다음 시퀀스값(Id)이 무엇이냐 물어보기만 하고, DB에게 Create 쿼리는 날리지 않는다.

이렇게 받아온 다음 Id값을 영속성 컨텍스트에 저장되어 있는 Entity에 채워주는것이다.

그리고 아래와 같이 commit() 이 이루어져야지

아래와 같이 콘솔창에 insert문이 실제로 DB에 날라간것을 확인할 수 있다

 

  • IDENTITY 전략과 SEQUECE 전략의 차이의 결론
    IDENTITY는 persist시에 Insert 날림.
    SEQUENCE는 persist시에 Id값만 받아오고, commit시에 실제 Insert 날림.

참조 : https://velog.io/@gudnr1451/GeneratedValue-%EC%A0%95%EB%A6%AC

 

스냅샷

최초로 1차캐시에 올라간 Entity의 상태.

 

 

Entity의 Upsate 시점

EntityManager.commit() 할때 최초 생겨난 스냅샷 (데이터) 과 현재 entity의 데이터를 비교 하여 달라진게 있는 경우 update문 발생시킴

 

 

flush 란

EntityManager.commit()을 호출하면 내부적으로 호출되는 메소드.

"쓰기 지연 sql 저장소" 에 저장되어 있는 쿼리 모두를 DB에 날리는것을 말한다.

그로인해 "1차 캐시"에 있는 엔티티와 실제 DB의 데이터가 동기화됨.

DB로 날리고난 뒤 쓰기 지연 sql 저장소에 저장된 쿼리들은 모두 삭제된다.

 

참조 : https://gmlwjd9405.github.io/2019/08/07/what-is-flush.html 

 

[JPA] 플러시(Flush)란 - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

 

 

 

영속성 컨텍스트내에서의 Entity의 생명주기

참조 : https://hyeooona825.tistory.com/87