Spring

[Spring] JPA 공부 #1

오잎 클로버 2022. 3. 25. 08:20
728x90

JPA에 대해 자세히 공부해보고자 JPA 공부를 해보고자합니다.

일단 JPA에 대해 공부하다보니 영속성 컨텍스트에 대해 알아야하기에

영속성 컨텍스트가 무엇이고, 또 어떤 역할을 수행해주는 지에 대해 간단하게 제 방식대로

요약을 해보고, 직접 테스트하는 것으로 #1을 마무리하도록 하겠습니다.

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

일단 영속성 컨텍스트에 대해 알아보기전에 영속이 무슨 뜻인지 알아보기위해 사전에 검색했습니다.

네이버 국어 사전

그리고 컨텍스트는 저장소로써 어떤 정보들을 저장하고 관리하는 용도라고 하니

이 둘을 그냥 이어붙인다면 "영원히 계속 유지되는 성질들을 저장하고 관리"라는 뜻이 되나,

뭔가 어색한 뜻이기에 저만의 풀이로 해석하자면, "지속적으로 정보들을 저장하고 관리하는 저장소" 정도로 해석할 수 있을 것 같습니다. 이뜻을 조금 더 부드럽고 와닿게 말하자면 "엔티티를 저장하고 관리"라고 뜻할 수 있을 것 같습니다.

엔티티(Entity)란?
Entity는 Table과 매핑한다.
예를 들어 Address 테이블에 4개의 row가 저장되어 있을 때, select * from Address와 같이 조회한다면
영속성 컨텍스트에 4개의 엔티티가 생성되고 관리된다.

JPA(영속성 컨텍스트)를 사용하는 이유

  1. 1차 캐시 / 엔티티 동일성 보장
  2. 쓰기 지연
  3. 로딩 지연
  4. 변경 감지

1차 캐시 / 엔티티 동일성 보장

영속성 컨텍스트는 내부에서 캐시를 갖고 있습니다.

id, instance의 맵 형태를 갖고 엔티티를 저장합니다.

트렌잭션 단위의 굉장히 짧은 메모리 공간입니다.

EntityManager#persist()로 Address가 영속성 컨텍스트에 영속되면, 1차 캐시는 이를 담습니다.

 

이후 조회시, 탐색하려는 Address를 DB에 접근하여 조회하지 않고, 1차 캐시를 먼저 훑어서 찾은 후, 

없다면 DB에 검색한 후, 1차 캐시에 저장합니다.

 

이런 1차 캐시를 거친 조회로 엔티티의 동일성이 보장이 가능한 것입니다.

트랜잭션을 지원하는 쓰기 지연

SQL 쿼리를 즉시 전송할 수도 나중으로 지연시킬 수도 있습니다.

쿼리를 쓰기 지연 SQL 버퍼에 쿼리를 담아두었다가, 영속성 컨텍스트의 명령에 따라 DB에 전송됩니다.

이후, transaction을 commit하거나 컨텍스트에 버퍼를 지우도록 명령(#flush)하면 그제서야 버퍼의 쿼리가 DB로

날아가게 됩니다.

 

예시)

@RunWith(SpringRunner.class)
@SpringBootTest
public class AddressTest {

    @PersistenceContext(type = PersistenceContextType.TRANSACTION)
    private EntityManager entityManager;

    @Transactional
    @Test
    public void flush() {
        Address address1 = Address.builder()
                .name("대구시")
                .build();
        entityManager.persist(address1);
        Address address2 = Address.builder()
                .name("서울특별시")
                .build();
        entityManager.persist(address2);
        
        Member member = Member.builder()
                .name("와이파이")
                .address(address1)
                .build();
        entityManager.persist(member);

        entityManager.flush();

        Member findMember = entityManager.find(Member.class, member.getId());
        Address findAddress = findMember.getAddress();

        System.out.println(findAddress.getName());
    }
    
}

 

1. Address 하나를 ADDRESS 테이블에 insert 요청

2. Member 하나를 MEMBER 테이블에 insert 요청 (Address 포함)

3. #flush 메소드 호출

4. Member 찾기

5. print Member의 Address

를 하였다고 가정해보자

그렇다면 당연히

insert 쿼리는 2번 발생하게 될 것이고, 순서대로 작동할 것입니다.

하지만 위 예시에서 3번을 맨 마지막으로 옮기게 된다면 오류가 발생하게 될 것입니다.

IDLE는 미리 잡아줄 수 없습니다. (DB 정보에 대해서는 IDLE에서 알 수가 없기때문)

3번을 없애더라도 문제 없이 작동합니다.

이유

더보기

JPA는 persist()가 호출되고 엔티티가 영속성 컨텍스트에 저장될 때 식별자가 필요함

이때, 엔티티 식별자가 null이라면 다음과 같은 과정을 겪음

  1. Entity의 식별자가 GeneratedValue인지 파악
  2. 만일 1번이 아니라면 식별자가 없으므로 영속성 컨텍스트에 저장 불가... 오류 발생
  3. 만일 1번이라면 하이버네이트 기준, 데이터를 저장과 동시에 생성된 식별자로 값을 얻어옴

엔티티 객체의 id가 GeneratedValue이기에 persist()가 호출됨과 동시에 insert 쿼리를 날려, 저장 및 값을 가져와서 영속성 컨텍스트에 저장을 하기에 3번을 없애더라도 작동함

 

 

JPA는 깊고 깊습니다. 계속 노력하겠습니다!

이상입니다.

'Spring' 카테고리의 다른 글

[Spring] Spring 3.0.0 AutoConfigure 사용  (0) 2023.03.13
스프링 4 경험하기  (0) 2022.04.30
[Spring] AOP 예제  (0) 2022.03.14
[Spring] DTO vs VO  (0) 2022.02.28
[Spring] Spring Transaction 세부 설정 설명  (0) 2022.02.21