JAVA/JPA 학습기록

[JPA] 영속성 컨테이너, 그리고 회원 CRUD

kth990303 2021. 4. 8. 20:12
반응형

오랜만에 개발 공부를 하는 겸,

스프링 공부를 할까, JPA 공부를 할까, nodejs 공부를 할까 고민하던 중,

너무 오랫동안 방치해두었던 jpa 공부를 진행해보기로 했다.

 


인프런 김영한님의 JPA 기본편 강의를 듣고 정리한 포스팅입니다.

틀린 부분은 댓글로 피드백 부탁드립니다 :)



PersistenceContext (영속성 컨텍스트)?

 

Entity를 영구 저장해주는 환경이라 한다.

이 영속성 컨텍스트 덕분에 db를 객체지향에 좀 더 맞게 다룰 수 있게 된다고 하는데,

사실 아직까지는 정확한 느낌은 오지 않는다.

 

개인적으로 이 블로그가 정리를 잘해놨다고 생각해 

앞으로 내가 보려고 주소를 남겨두려 한다.

velog.io/@neptunes032/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%9E%80

 

JPA 영속성 컨텍스트란?

영속성 컨텐스트란 엔티티를 영구 저장하는 환경이라는 뜻이다. 엔티티 매니저를 통해 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.em.persist

velog.io

내용 및 커리큘럼이 상당히 비슷한 걸 보니 김영한 선생님 강의 들으시는 분인 듯 하다.

 

이걸 왜 알아야 하냐? 우리가 CRUD 쿼리를 작성할 때 내부구조를 대략적으로 알면 도움이 되기 때문이다.


회원관리 CRUD 쿼리를 만들자

 

뭔가 너무 대충 영속성 컨텍스트를 설명하고 패스한 느낌이긴 한데... 어쩔 수 없다. 아직은 간단한 jpql 정도만 배우는 단계이기 때문에 괜히 이러쿵저러쿵 써놓는 것보다, 확실히 알게된 후에 작성하는 것이 낫다고 본다.

 

현재 내 h2데이터베이스는 아래와 같다.

참고로 데이터베이스 이름이 Member1이다.

현재 위와 같은 상태이다. 


Create (회원 등록)

EntityManager em=emf.createEntityManager();

EntityTransaction tx = em.getTransaction();
tx.begin();

EntityManager em=emf.createEntityManager(); 로 엔티티매니저를 생성해두었고,

그 아래 두줄의 코드로 트랜잭션을 시작시켰다.

 

회원등록할 준비가 완료된 셈이다.

이제 try-catch-finally문 코드를 아래와 같이 작성해주자.

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em=emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            Member1 member = new Member1(4L, "kth990303");
            em.persist(member);
            // 아직 쿼리가 전송되지 않음
            tx.commit();
            // 쿼리문 전송
        } catch(Exception e){
            tx.rollback();
        } finally{
            em.close();
        }
        emf.close();
    }
}

 

tx.commit() 이후로 쿼리가 전송됨.
회원이 db에 잘 등록됨.

회원이 db에 잘 등록됨을 알 수 있다.

em.persist(member); 만으로 db에 등록할 수 있다니... 혹시 JDBC만으로 하다가 이걸로 넘어왔을 경우,

신세계를 맛본 느낌이 들 것이다. 내가 그렇기 때문에...

 

그런데, em.persist(member); 이후 쿼리문이 전송되는 것이 아닌, tx.commit(); 이후 쿼리문이 전송되는 이유가 뭘까?

영속성 컨텍스트

위 사진을 보면 이해가 더 잘될 것이다.

영속성 컨텍스트 내에 @Id (primary key), Entity를 저장하는 공간이 있었던 것이다.

그리고 sql은 '쓰기 지연 SQL 저장소'에 임시로 저장시켜 놓는 것이다.

그리고 트랜잭션에서 커밋해주면, 한꺼번에 쿼리가 전송되는 셈이다.

 

즉, persist 등 각종 쿼리를 영속성 컨텍스트에 저장해놓았다가, 트랜잭션에서 커밋할 때 db로 쿼리문이 전송됨을 확인할 수 있다.


Read

 try{
            Member1 member1 = em.find(Member1.class, 4L);
            System.out.println("=========================");
            Member1 member2 = em.find(Member1.class, 4L);
            System.out.println("=========================");

            System.out.println("result = " + (member1==member2));
            tx.commit();
        } catch(Exception e){
            tx.rollback();
        } finally{
            em.close();
}

줄바꿈 왜이러지... 아무튼 위와 같이 코드를 작성했다.

 

em.find(Member1.class, 4L); 와 같이 Member1 객체의 id가 4L (id가 primary key이므로 id 정보를 적어주면 된다.)인 회원을 찾아준다.

이런 쿼리를 2개를 작성하면 과연 쿼리를 2번 날릴까?

결과를 보자.

같은 회원을 찾을 때엔 db에 쿼리문을 날리지 않는다.

두 번째 회원을 찾을 때엔 쿼리문을 날리지 않는것을 알 수 있다!

이유는 바로 영속성컨텍스트에 있는데, 

위 사진을 다시 한 번 보자.

영속성 컨텍스트

잘 보면 '1차캐시' 가 보이는 것을 확인할 수 있다.

find함수로 영속성 컨텍스트의 1차 캐시에 정보를 저장시켜놓았기 때문에,

두 번째로 부를 땐 1차캐시의 멤버 정보를 불러주는 것이다.

마치 dp (다이나믹 프로그래밍) 같구만...

 

참고로 1차캐시는 트랜잭션이 끝나면 자연스럽게 삭제된다고 생각하면 된다고 한다.


Update

사실상 업데이트가 핵심이다!

em.update라는 함수가 있을까? 아니다!

아래 코드를 보자.

 

    try{
            Member1 member1 = em.find(Member1.class, 4L);
            member1.setName("2sms22rjt");

            // 다시 persist 해줄 필요가 없음!
            // em.persist(member1);
            tx.commit();
        } catch(Exception e){
            tx.rollback();
        } finally{
            em.close();
        }

아까 id가 4L이고 이름이 kth990303인 회원을 등록했던 것이 기억나는가?

 

이름이 kth990303인 멤버가 알고보니 이름이 2sms22rjt이었다고 해서

이름을 수정하는 코드를 작성한 것이다.

member1.setName으로 이름 정보를 수정해주고, 

 

다시 persist나 update쿼리를 날릴 필요가 있을까?

아니다! 위에도 보이듯이, em.persist 코드를 주석처리 한 것을 알 수 있다.

 

결과는 어떨까?

이름이 2sms22rjt으로 잘 바뀌었다.

em.persist를 해주지 않고 Setter Method만으로도 이름이 잘 바뀜을 확인할 수 있다!

이것 역시 영속성 컨텍스트 덕분인데,

아래 사진을 보자.

사실 영속성 컨텍스트엔,

변경 전 회원정보를 담는 스냅샷, 최종 회원정보가 담긴 Entity가 있다.

 

Entity와 스냅샷의 정보가 서로 다르면, 

SQL 저장소에 update 쿼리가 저장되고,

tx.commit()을 할 때 쿼리가 전송되는 것이다~

 

(21.04.11. 추가)

참고로 tx.commit()을 하면 1차 캐시가 지워져버리는 것이 아니냐고 걱정할 수 있다.

그렇지 않다!

영속성 컨텍스트를 비우는 것이 아니고, 영속성 컨텍스트의 변경내용을 db에 동기화시킨다고 생각하면 된다.

 

참고로 트랜잭션 커밋할 때에만 db를 동기화하는 것은 아니고,

JPQL로 createQuery를 날릴 때에도 플러시가 일어나 db가 동기화가 일어난다.

그 이유는 아래와 같다.

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

//중간에 JPQL 실행
//따로 트랜잭션 커밋을 안했으므로 과연 members에는 아무것도 저장이 안될까?
query = em.createQuery("select m from Member1 m", Member1.class);
List<Member> members= query.getResultList();

만약 JPQL을 날릴 때 db에 동기화가 일어나지 않는다면

트랜잭션 생성 및 커밋이 굉장히 빈번하게 일어나야 할 것이다.

따라서 스프링에서는 JPQL을 날릴 때에도 db 동기화를 시켜준다.


Delete

delete는 굉장히 쉽다.

    try{
            Member1 member1 = em.find(Member1.class, 4L);
            em.remove(member1);
            
            tx.commit();
        } catch(Exception e){
            tx.rollback();
        } finally{
            em.close();
        }

em.remove 해주면 된다.

4번 아이디의 2sms22rjt 이름 회원 정보가 삭제되었다.

잘 삭제됨을 알 수 있다.


뭔가 뒤로 갈수록 급하게 포스팅을 마무리하는 느낌이 들 것이다.

정답이다...ㅋㅋㅋㅋㅋㅋ 실제로 급하게 내용 요약하고 마무리 지으려 하였기 때문이다.

왜냐면 노트북 배터리가 없다.

 

 

그래서 급하게 여기까지 포스팅한다.

반응형