JPA의 기초이면서도 가장 중요한 개념인 영속성 컨텍스트에 관하여 정리해보자.
모든 예제는 JPA 구현체 중 가장 많이 사용되는 Hibernate를 기준으로 설명하였고,
예제코드는 인프런 강좌 자바 ORM 표준 JPA 프로그래밍 - 기본편(김영한) 로 부터 가져왔다.
설명을 워낙 잘해주셔서 꼭 추천하는 강의이고 나도 아직 수강중이다ㅎㅎ..
먼저 영속성 컨텍스트를 개념적으로 말해보자면
엔티티를 영구저장 하는 환경
하지만 역시 바로 와닿진 않는다.
일단 코드를 통해 살펴보자
먼저 Member라는 Entity가 있다고 가정하자
@Entity
public class Member {
@Id
private Long id;
private String name;
... 생략
간단하게 데이터를 하나 추가하는 예제를 작성해보겠다.
🎈 예제코드 1
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = new Member();
member.setId(1L);
member.setName("HelloJPA");
em.persist(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
emf.close();
}
코드를 간략히 설명하자면 가장 위 3줄은
EntityManagerFactory, EntityManager, EntityTransaction 객체를 각각 얻는 코드이다.
EntityManagerFactory는 단 하나만 생성되어 애플리케이션 전체에서 공유하는데,
그와는 달리 EntityManager는 쓰레드 별로 하나씩 생성되어 사용 후 버린다.
마지막으로, JPA의 모든 데이터 변경은 트랜잭션 안에서 실행되는데 트랜잭션 처리를 위해 EntityTransaction 객체를 얻는다.
이런 부분은 일단 이해가 되지 않더라도 넘어가자
앞으로 설명할 코드에 위 세줄은 항상 공통적으로 포함되어있다고 가정하고 설명하겠다.
그 후 핵심적인 코드는 정말 간단하다.
Member 객체를 생성한 뒤 Setter를 통해 적절한 값을 넣어준 뒤
em.persist(member);
를 하게 되면
🎉 로그 1
[LOG]
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
위와 같은 로그를 남기며 Member 테이블에 하나의 Row가 삽입되는 것을 확인할 수 있다.
이제 이 데이터를 조회하고 출력해보자!
🎈 예제코드 2
Member findMember = em.find(Member.class, 1L);
System.out.println("findMember.id : " + findMember.getId());
System.out.println("findMember.name : " + findMember.getName());
를 실행해보면
🎉 로그 2
[LOG]
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
findMember.id : 1
findMember.name : HelloJPA
잘 조회되는것을 확인할 수 있다. 예제를 통해 추측해보자. persist()는 데이터를 DB에 저장하는 메소드? find()는 데이터를 DB로 부터 찾아오는 메소드? 당연한 추측이고 어느정도는 맞는 표현이다. 하지만 이것은 정확한 설명이 아니다.
그렇다면, persist()와 find()의 정확한 설명은 어떻게 해야할까?
위의 예제코드 1을 약간만 변경하고 실행해보자
🎈 예제코드 3
Member member = new Member();
member.setId(2L);
member.setName("TestJPA");
System.out.println("=== BEFORE ===");
em.persist(member);
System.out.println("=== AFTER ===");
tx.commit();
새로운 Member 객체를 만들고 persist() 메소드를 호출한다.
어떤 결과가 예상되는가?
당연히 em.persist()를 호출하기 전후로 println 메소드를 호출하였으니 정상적인 흐름대로라면 콘솔에 BEFORE -> 쿼리문 -> AFTER가 차례대로 나올것이라고 예상할 수 있다.
하지만 실제 결과는
🎉 로그 3
=== BEFORE ===
=== AFTER ===
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
???? 이상하다. 왜 BEFORE와 AFTER가 모두 출력 되고 난 후 INSERT 쿼리문이 찍힐까?
바로 영속성 컨텍스트의 존재때문이다.
자세한 설명은 잠시 뒤로 미루고 다음 예제 역시 살펴보자
System.out.println("첫번째 호출");
Member findMember1 = em.find(Member.class, 2L);
System.out.println("두번째 호출");
Member findMember2 = em.find(Member.class, 2L);
System.out.println("세번째 호출");
Member findMember3 = em.find(Member.class, 2L);
방금 예제3에서 저장한 데이터인 Member(PK가 2인 데이터)를 동일하게 3번 find() 메소드를 통해 찾아오는 예제이다. 만약 예제 1,2를 보고 실행한 후 추측했던 것처럼 find() 메소드가 DB로부터 데이터를 조회해오는것이라면 이 코드는 실행시 SELECT문을 총 세번 호출할 것이다.
🎉 로그 4
첫번째 호출
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
두번째 호출
세번째 호출
???? 이 역시 뭔가 이상하다. 왜 최초에 한번은 SELECT문을 통해 데이터를 조회해오지만, 그 이후로는 쿼리문이 찍히지 않은걸까? 혹시 데이터를 제대로 못가져온건 아닐까?
System.out.println(findMember1); // 결과 : Member{id=2, name='TestJPA'}
System.out.println(findMember2); // 결과 : Member{id=2, name='TestJPA'}
System.out.println(findMember3); // 결과 : Member{id=2, name='TestJPA'}
findMember1,2,3 변수가 참조하는 객체에 값이 올바르게 들어있는것을 확인할 수 있다.
이것 역시, 영속성 컨텍스트가 만들어낸 일이다. 재미있다. 그리고 궁금하다. 왜 이런 결과가 나오는 걸까?
그럼 다음글에서 본격적으로 영속성 컨텍스트가 무엇이길래 위와 같은 결과를 만들어내는지,
또한 어떠한 영속성 컨텍스트가 존재함으로 어떤 장점이 있는지 등에 대해 알아보겠다.
2편에서 계속..!