본문 바로가기

국비필기노트/Spring

Spring_JPA[2](영속성, 영속성 컨텍스트)

영속성이란?

 

영속성(persistency)이란 영속성이란 JPA를 이해하는데에 가장 중요한 용어로서 데이터를 영구적으로 저장하는 것을 의미하며 따라서 영속성을 갖지 않는 데이터는 단지 메모리에서만 존재하기 때문에 프로그램을 종료하면 모두 잃어버리게 된다.

 

 

영속성 컨텍스트란?

 

영속성 컨텍스트를 직역하자면 데이터를 영구적으로 저장하는 환경 정도로 해석될 수 있다. "Entity를 영구 저장하는 환경"이라는 뜻도 가지고 있는 영속성 컨텍스트는 EntityManager를 통해 DB에 데이터를 저장한다.

 

//설정파일 읽어옴
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();

//데이터베이스의 상태를 변화시키기 위해 수행하는 작업 단위
EntityTransaction tx = em.getTransaction();
tx.begin();

 

Entity는 JPA가 관리할 객체임을 알려주기위해 Member나 Team 등 DTO객체에 붙혀주는 어노테이션이다. 이를 코드로 표현하자면 EntityManager.persisty( Entity ); 가 되는데 이를 보고 우리는 Entity객체를 EntityManager를 통해 DB에 저장한다고 생각할 수 있다. 이 때 EntityManager는 처음부터 바로 데이터를 DB에 저장하는 것이 아니라 영속성 컨텍스트라는 곳에 저장을 먼저 시켜주는 것이다. 

 

 

영속성 컨텍스트 사용이유

 

 

1) 1차캐시

 

//엔티티를 생성
Member member = new Member( ) ;
member.setId("member1");
member.setName("name1);

//엔티티를 영속
em.persist(member);

//조회
Member findMember = em.find(Member.class,"member1");

 

엔티티를 영속한 em.persist(member)의 순간은 일반 DB가 아닌 1차캐시에 저장이 된 상태이다. 

 

우리가 DB를 가지고 올 때 통신에 대한 어떤 병목현상이 생길 수 있고 다양한 이유로 인해 시간이 늦어질 수 있다. 이 때 영속성이라는 공간을 주어 거기에 데이터를 저장하면 꼭 DB를 가지고 오지 않고도 데이터를 가지고 올 수 있다는 이점이 있으며 DB에는 최종적으로 Commit이 이루어지는 시점에 저장된다.

 

또한 select문을 여러번 할 때 이점을 가지는데

 

Member findMember2 = em.find(Member.class, "member2"); 

 

findMember2라는 메서드를 통해서 member2라는 값을 찾는다고 가정했을 때 먼저 1차캐시를 찾아보고 DB를 찾아보는 순서로 데이터를 가지고온다.

만약, member2가 1차캐시에 없어 DB에서 데이터를 가지고 온 상황이라면 JPA는 자동으로 member2값을 1차캐시에 저장시켜서 다음엔 DB까지 가지 않고 1차캐시에서 바로 찾을 수 있도록 해준다. 

 

2) 동일성(identity)보장

 

Member a = em.find(Member.class , "member1"); 
Member b = em.find(Member.class , "member1");

 

동일한 값을 출력할 a와 b가 있다. 원래라면 a와 b의 값을 가지고 오기위해선 a를 위한 DB 트렌젝션 1번, b를 위한 DB 트렌젝션 1번하여 총 2번 DB를 다녀와야하는데 JPA는 이미 가져온 값을 1차캐시에 저장을 하기 때문에 b를 위한 데이터는 DB가 아닌 1차캐시에서 가지고 올 수 있다. 

 

예를들어 모든 등급이 보는 공지사항과 같은 경우, 100명의 사용자가 공지사항이라는 버튼을 클릭하여 select문을 실행한다면 기존엔 100번 DB를 다녀와야했다면 JPA는 한번 가지고 온 값은 1차 캐시에 저장을 하기 때문에 DB는 최초 1회만 다녀오고 나머지 99번은 1차캐시에서 데이터를 가지고오기에 속도적인 측면에서 빠르게 진행할 수 있다는 것이다.

 

3) 트렌잭션을 지원하는 쓰기 지연(transactional write-behind)

 

EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();

//트랜젝션 시작
transaction.begin( ) ; 

em.persist(memberA);
em.persist(memberB);

//트랜잭션 커밋
transaction.commit( ) ;

 

DB에서 트랜잭션(Transaction)이란, 데이터베이스의 상태를 변화시키기 위해 수행하는 작업의 단위를 의미한다. 즉, Select, Insert, Delete, Update를 이용하여 데이터베이스에 접근하는 것을 의미하며 이 때의 작업단위는 하나의 명령어만 뜻하는 것이 아니라는 것이 포인트이다. 

 

예를들자면 게시판과 같이 게시물을 올리고 확인하는 복합적 과정이 필요한 사이트에서 select문과 insert문이 동시에 일어나는 하나의 작업단위를 트랜잭션이라고 부르는 것이다.

 

transaction.begin은 트랜잭션을 시작하는 의미로 이는 단순 "시작"의 트렌젝션의 의미를 가지고 있지 여기서 바로 데이터가 insert되는 것은 아니다.

 

em.persist에서 데이터를 1차 캐시에 저장시키고 commit을 할 때 마다 DB에 저장된다.

 

여기서 포인트는 begin( )과 commit( )을 개발자 편의에 맞게 커스텀이 가능하다는 것이다.

데이터 하나를 1차 캐시에 저장하고 commit할 수 도 있으며 여러가지를 쌓았다가 commit하여 한번에 DB에 저장할 수 도 있다.

 

4) 변경감지

 

//트렌젝션 시작
transaction.begin( ) ;

//엔티티조회
Member memberA = em.find(Member.class, "memberA");

//영속 엔티티 데이터 수정
memberA.setUsername("자바학생");
memberA.setAge(20);

//트렌젝션 커밋
transaction.commit( ) ;

 

어떤 데이터를 update해야한다고 생각했을 때 우리는 흔히 update문을 사용한다고 생각한다. 

그러나 JPA는 update구문을 set으로 대체해준다.

트렌젝션 시작과 끝 사이에서 update할 구문을 set으로 처리한다. 즉, 덮어쓰기하여 데이터가  update가 되는 것이다. 이 역시 DB에서 처리되지않고 entity에 대한 변경을 영속 컨테스트에서 가지고 있다가 commit이 되는 순간 DB로 넘어간다.

 

 

5) 지연로딩(플러시)

 

 

transaction.commit( )메서드를 호출할 떄 commit의 내부에서는 commit과 flush 두가지 영역으로 나누어진다.

 

flush는 스냅샷의 어떠한 변경을 감지한 후 수정이 된다면 수정된 엔티티 쓰기 지연 SQL저장소에 등록한다.

그렇게 등록, 수정, 삭제 쿼리등을 데이터베이스에 전송을 하는 역할 까지가 플러시(flush)의 역할이고 그 다음 스텝 데이터베이스에 저장하는 역할을 commit이 진행한다.

 

commit( ) 메서드를 호출하는 순간 플러시라는 메서드를 호출이 된다.

 

영속성 컨텍스트를 플러시하기 위해 밑 3가지 방법을 사용한다.

  • em.flush( )                //직접호출
  • 트랜젝션 커밋          //플러시 자동 호출
  • JPQL쿼리 실행        //플러시 자동 호출

 

즉, 플러시는 영속성 컨텍스트를 비워내는 작업이 아닌 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화 시키는 작업이다. 그럼으로 트랜잭션이라는 작업 단위가 중요하며 커밋 직전에만 동기화 하면 된다.