본문 바로가기
cs

JPA와, JPA에서 영속성에 대한 궁금증..

by 이쟝 2023. 5. 24.

Jpa에 프로젝트에서 공부해보다 영속성이라는 개념이 나왔는데, (비교하는 조건에서) 영속성이란 도대체 무엇인가 해서 정리해보았다...!

엔티티를 데이터베이스에 영속화 시키고 연결짓고 사용하는 환경에서 서로 다른 두 엔티티가 같은 조건이 무엇인가를 비교해야 한다.

개요

JPA의 영속화에 대해 이해하기 위해서는 가장 먼저 영속성 컨텍스트에 대해 이해해야 한다.
자바는 OOP 개념을 가지고 데이터를 객체처럼 관리 하고 DB는 관계형으로 데이터를 관리한다. 그래서 이 간극을 매우기 위해 ORM이라는 개념이 등장했다.
ORM : 객체가 테이블이 되도록 매핑 시켜주는 것 (SQL query가 아닌 직관적인 코드(메서드)로서 데이터 조작 가능) ex) SELECT * FROM user => user.findAll( )라는 메서드 호출로 데이터 조회 가능

JPA(Java Persistence API)

  • JPA는 ORM을 위한 자바 EE 표준이며 Spring-Data-JPA는 JPA를 쉽게 사용하기 위해 스프링에서 제공하고 있는 프레임워크
  • 추상화 정도 Spring-Data-JPA -> Hibernate -> JPA
    • Hibernate를 쓰는 것과 Spring Data JPA를 쓰는 것 사이에는 큰 차이가 없지만, 구현체 교체와 저장소 교체의 용이성이라는 이유로 Spring Data JPA를 사용하는 것이 더 좋다.
  • JPA는 스레드가 하나 생성될 때마다(매 요청마다) EntityManagerFactory에서 EntityManager를 생성한다.
    • EntityManager는 내부적으로 DB 커넥션 풀을 사용해 DB에 붙는다.

JPA 동작 과정

  • JPA는 애플리케이션과 JDBC 사이에서 동작한다. 
  • JPA를 사용하면, JPA 내부에서 JDBC API를 사용해 SQL을 호출해 DB와 통신한다. => 개발자가 직접 JDBC API를 사용하지 않는다.

JPA를 사용하는 이유

  1. sql 중심적인 개발에서 객체 중심적인 개발 가능
  2. 생산성의 증가(간단한 메서드로 CRUD 가능)
  3. 쉬운 유지보수
    • 기존에는 필드 변경 시 모든 SQL을 수정해야 했지만, JPA를 사용하면 필드만 추가하면 된다. 
  4. Object와 RDB 간의 패러다임 불일치 해결

 

JPA는 영속성 컨텍스트를 갖고 있다!!

영속성 컨텍스트(Persistence Context)

영속성 : 데이터 -> 영구적으로 저장해주는 것

컨텍스트(context) : 모든 정보를 갖고 있는 것

영속성 컨테스트 : 엔티티 클래스에서 만들어지는 엔티티를 영구 저장하고 관리하는 환경

영속성 컨텍스트는 Server와 Database 사이에 위치

  • 영속성 컨텍스트에서 엔티티를 관리하고 필요에 따라 DB의 데이터를 저장, 조회, 수정, 삭제 할 수 있는데 이런 작업을 담당하는 객체를 엔티티 매니저(Entity Manager)라고 한다.

영속성 컨텍스트의 2가지 영역

1차 캐시 저장소

  • 영속성 컨텍스트가 관리하는 엔티티 정보를 보관한다. 이 상태를 '영속 상태'라고 한다.
  • 아직 DB에 저장된 상태가 아닌 단순히 영속성 컨텍스트에서 관리(managed)하는 상태

쿼리문 저장소(SQL 저장소)

  • 필요한 쿼리문(SQL)을 보관하는 저장소
  • 최대한 여러 쿼리문을 모아두고, DB에 접근하는 횟수를 최소화해 성능상 이점을 얻는다.
    • 저장해둔 쿼리문으로 DB에게 접근하는 행위는 엔티티 매니저의 'flush( )'로 진행한다.
 

영속성 컨텍스트와 관련한 엔티티의 생명주기 

1. 비영속(new/transition) 상태 : 영속성 컨텍스트와 전혀 관계 없는 새로운 상태(객체 생성 상태)

2. 영속(managed) 상태 : 영속성 컨텍스트에 관리되는 상태

3. 준영속(detached) 상태 : 영속성 컨텍스트에 저장되었다가 분리된 상태

4. 삭제(removed) 상태 : 객체를 삭제한 상태

엔티티 생명주기

1. 비영속(new, transition) 상태

엔티티가 영속성 컨텍스트와 전혀 관련이 없는 상태(객체 생성 상태) 
Member member = new Member();
member.setId("soso");
member.setUsername("소소");

2. 영속(managed) 상태 

엔티티가 영속성 컨텍스트에서 관리되고 있는 상태로 아직 DB에 저장된 상태가 아님

 

  • 엔티티 매니저의 persist( )를 사용하면 비영속 상태의 엔티티를 영속상태로 만들 수 있다. 
  • flush( )가 실행되기 전에는 실제 DB에서 접근하지 않는다.
// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("soso");
member.setUsername("소소");

EntityManager em = emf.createEntityManager();

// 엔티티 매니저에서 트랜잭션을 실행할 수 있도록 begin() 메서드를 시작
em.getTransaction().begin();

// 객체를 저장한 상태(영속)
em.persist(member);

 

  • 데이터베이스에 변경 사항을 반영할 때 commit을 날리는데 commit을 날릴 때 정상적으로 데이터베이스에 변경 사항이 반영되면 커넥션 종료

  • 여러 개의 엔티티를 persist( )하게 되도, 해당하는 INSERT 쿼리문은 계속 SQL 저장소에 보관하게 된다.

flush로 쿼리문을 DB에 반영한다.

  • 모아둔 쿼리문은 flush( )를 실행할 때 DB에 반영하게 된다. 
  • 플러시를 하더라도 1차 캐시 저장소에서 관리중인 엔티티들이 사라지는 것은 아니다.
    • 플러시는 영속성 컨텍스트와 DB를 동기화(Synchronize) 한다.

  • 생성한 엔티티를 입력할 때 외에도 엔티티 매니저가 DB에서 조회해온 데이터도 '영속 상태'인 엔티티가 된다.
    • 조회해온 데이터는 1차 캐시 저장소에 먼저 저장되고, 저장된 엔티티 정보를 반환한다.
    • 조회를 하기 위해서 엔티티 매니저의 find( )를 사용한다.

3. 준영속(detached) 상태

영속성 컨텍스트에서 관리되던 엔티티가 영속성 컨텍스트에서 관리되지 않는 상태

 

em.detach(member);
엔티티를 준영속 상태로 만드는 방법 3가지

1. 엔티티 매니저의 detach( ) 사용 (특정 엔티티 준영속 상태로 만들 수 있다.)
2. 영속성 컨텍스트 전체를 초기화 시키는 clear( ) 사용 (이 때 쿼리문 저장소의 보관해둔 쿼리들 모두 초기화)
3. 영속성 컨텍스트를 닫아버리는 close( ) 사용 (영속성 컨텍스트 자체가 사라지는 거라 관리되던 엔티티들은 모두 준영속상태가 되어버림)

=> 준영속 상태의 엔티티는 엔티티 매니저의 merge( )를 사용하면 다시 영속성 컨텍스트에서 관리되는 '영속 상태'로 변환 할 수 있음

4. 삭제(removed) 상태

엔티티를 영속성 컨텍스트에서 관리하지 않게 되고, 해당 엔티티를 DB에서 삭제하는 DELETE 쿼리문을 보관하는 상태 (객체 삭제한 상태)

 

  • persist( )와 마찬가지로 flush( )가 호출되기 전까지는 실제 DB에게 접근되지 않는다.
em.remove(member);


영속성 컨텍스트의 특징

1. 데이터 캐싱 (1차 캐시)

  • 영속성 컨텍스트는 1차 캐시 역할을 해주기 때문에 persist 한 후 find를 하게 되면 DB에서 값을 조회하는 것이 아닌 1차 캐시에서 값을 조회하게 됨
 Member member = new Member();
 member.setId("soso");
 member.setUsername("소소");
 
 em.persist(member);
 
 Member findMember = em.find(Member.class, "soso");

  • 영속성 컨텍스트는 대부분 트랜잭션 단위로 만들고, DB 트랜잭션이 끝나면 영속성 컨텍스트도 종료시킨다.

2. 변경 감지(dirty checking)

  • 영속성 컨텍스트에서 관리되는 엔터티는 값이 변경 되었을 경우 이를 자동으로 인지해 데이터베이스에 영속화를 진행한다.
  • 영속화를 진행할 때 JPA가 update 쿼리를 생성해 쿼리 저장소에 추가하고, 이것은 즉시 반영은 되지 않고 트랜잭션이 끝나는 시점에 실제 영속화 과정이 완료된다.
  • 준영속 상태인 엔티티가 변경된다고 해도 변경 감지는 발생하지 않는다.
  • 1차 캐시에 저장할 때 동시에 스냅샷 필드도 저장하는데 
    • 그리고 commit( ) 또는 flush( )가 일어날 때 엔티티와 스냅샷을 비교해서, 변경사항이 있으면 UPDATE SQL을 자도응로 만들어서 DB에 저장한다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // 트랜잭션 시작
​
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
​
// 영속 엔티티 수정
memberA.setUsername("jh");
memberA.setAge(27);
​
//em.update(member) 또는 em.persist(member)로 다시 저장해야 하지 않을까?
​
transaction.commit(); // 트랜잭션 커밋

3. 트랜잭션 쓰기 지연

  • 영속성 컨텍스트는 트랜잭션 범위 안에서만 동작이 되고, 모든 엔터티의 생성/수정/삭제에 해당하는 쿼리를 쌓아 두었다가 트랜잭션 끝나는 시점(commit)에 일괄 / 순차적으로 반영한다.
  • 영속성 컨텍스트에서 관리되는 엔티티는 트랜잭션 단위로 관리되고, 트랜잭션이 끝나는 순간 (commit / rollback) 캐싱된 데이터는 초기화된다. 
  • 트랜잭션을 커밋하게 되면, flush( )와 commit( ) 두 가지 일을 하게 된다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();

//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작

em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.

//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋

4. 플러시 (flush)

  • 영속성 컨텍스트의 변경내용을 DB에 반영한다.(영속성 컨텍스트의 변경내용을 DB에 동기화)
  • 플러시 발생은 변경 감지, 수정된 엔티티 쓰기 지연, SQL 저장소의 쿼리를 데이터베이스에 전송
  • 플러시를 해도 1차 캐시는 지워지지 않고 유지된다.
영속성 컨텍스트를 플러시하는 방법

1. em.flush( ) : 직접 호출
// 영속
Member member = new Member(200L, "A");
em.persist(member);
​
em.flush();
​
System.out.println("플러시 직접 호출하면 쿼리가 커밋 전 플러시 호출 시점에 나감");
​
transaction.commit();​

2. 트랜잭션 커밋 : 플러시 자동 호출
3. JPQL 쿼리 실행 : 플러시 자동 호출
- JPQL로 SELECT 쿼리를 날리려고 하면 저장되어 있는 값이 없어서 문제가 생길 수 있다.
--> JPA는 이런 상황을 방지하고자 JPQL 실행 전에 무조건 flush()로 DB와의 싱크를 맞춘 다음에 JPQL 쿼리를 날리도록 설정 되어 있다.
em.persist(memberA);
em.persist(memberA);
em.persist(memberA);
​
// 중간에 JPQL 실행
query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();​

 

플러시 발생 후 생기는 일

1. 변경을 감지(Dirty Checking)
2. 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록
3. 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송(등록, 수정, 삭제 SQL)
4. 플러시가 발생한다고 커밋일 이루어지는게 아닌, 플러시 다음에 커밋 일어남 

JPA 저장과 영속화 과정에 대해서

JPA의 영속성 컨텍스트와 엔티티 생명주기

[JPA] 영속성 컨텍스트란 ? 그리고 영속성 관리

JPA(Java Persistence API)의 개념

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