JPA 동작 원리
어떤 기술을 잘 활용하려면, 내부 동작원리를 잘 이해하는게 중요하다고 생각한다. 때문에 JPA 를 본격적으로 사용하기 전, JPA 내부 동작원리에 대해서 알아보자. JPA 의 내부 동작원리에 대해서 알려면 영속성 컨텍스트에 대해서 알아야한다.
영속성 컨텍스트 ?
JPA 를 공부하다보면, 영속성 컨텍스트라는 단어를 쉽게 볼 수 있다. 영속성 컨텍스트란 엔티티를 영구 저장하는 환경 이라는 뜻이다.
말로는 쉽게 이해되지 않지만 논리적인 개념으로, 엔티티를 저장하고 관리하는 하나의 공간이라고 생각하면 편하다.
이러한 영속성 컨텍스트는 엔티티 매니저를 통해 접근할 수 있다.
엔티티 ?
엔티티란, 데이터베이스를 공부하는 대부분의 사람들이 알듯이 하나의 데이터 집합이다.
우리는 객체 위주로 설계하기 위해 하나의 클래스라고 생각하면 편하다.
자바에서 엔티티는 다음과 같은 생명주기를 가진다.
- 비영속
- 영속
- 준영속
- 삭제
1. 비영속
- 객체를 new 와 같이 단순 생성할 경우, 그 객체는 비영속 상태에 해당된다.
- 영속성 컨텍스트와 전혀 관계가 없는 상태이며, JPA의 이점을 활용할 수 없다.
2. 영속
- 엔티티 매니저를 생성해, em.persist(객체) 함수를 통해 객체를 영속성 컨텍스트에서 관리하는 상태이다.
- 영속하고 관리하는 과정은 밑에서 자세히 설명하겠다.
3. 준영속
- 영속성 컨텍스트에서 관리하고 있다가, detach 와 같은 함수들로 분리한 상태이다.
4. 삭제
- 객체를 더이상 쓰지 않아, 삭제한 상태이다.
엔티티 영속하기
// 단순히 객체를 생성했을 때 - 비영속
Member member = new member();
member.setId("member1");
member.setName("회원1");
앞서 설명했듯이, 단순히 객체를 생성하기만 하면 그림과 같이 영속성 컨텍스트에 관리되지 않는다.
// 객체를 생성해 엔티티 매니저를 통해 영속성 컨텍스트에 접근 - 영속
Member member = new member();
member.setId("member1");
member.setName("회원1");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(member);
엔티티 매니저 em 을 생성하고, 생성한 객체를 em.persiset 함수를 통해 영속성 컨텍스트에 접근해 관리한다.
엔티티 관리하기
Member member = new member();
member.setId("member1");
member.setName("회원1");
// 영속화
em.persist(member);
Member findMember1 = em.find(Member.class, "member1");
Member findMember2 = em.find(Member.class, "member2");
객체 맴버를 생성하고, persist 함수를 통해 영속화 시키면, 영속 컨텍스트에 1차 캐시에 해당 객체의 기본키와, 엔티티 값을 저장한다.
1차 캐시는 트랜잭션이 단위로 관리되며, 해당 트랜잭션이 끝나면 초기화 된다. 하나의 트랜잭션 안에서 같은 엔티티를 두번 요구하는 로직이 있다면, 데이터베이스에 select 쿼리를 날리지 않고, 캐시 안에서 바로 가져온다. 이러한 특성 때문에 JPA 는 엔티티의 동일성을 보장해준다.
위 코드에는 기본키가 member2 에 해당하는 값이 캐시에 없기 때문에, select 쿼리를 날려 데이터베이스에서 member2 의 값에 해당하는 엔티티를 가져와 캐시에 저장한 후 그 값을 반환한다.
영속 컨텍스트 안에는 1차 캐시 이외에도 쓰기 지연 SQL 저장소가 존재한다. JPA 는 persist 로 새로운 엔티티를 영속 컨텍스트에 추가할 때 쿼리를 데이터베이스에 날리지 않는다. SQL 저장소에 저장한 후, transcation.commit() 혹은 flush() 명령어가 실행될 때 데이터베이스에 쿼리를 날린다. 이는 옵션으로 바꿀 수 있다. 이는 후에 지연로딩 즉시로딩에 대해 포스팅 할 때 자세히 설명하겠다.
추가로 JPA 는 엔티티를 수정할 때 정말 기가막히게 편리한 기능을 제공해준다.
예를 들어 멤버 객체의 정보를 수정해야 한다고 가정해보자.
EntityManager em = emf.createEntityManager(); // 엔티티 매니저 생성
EntityTransaction tx = em.getTransaction(); // 엔티티 트랜잭션 생성 (작업의 단위라고 생각하면 편하다)
tx.begin(); // 트랜잭션 시작
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setName("hi");
//-> 수정한 memberA 의 데이터를 반영하는 코드가 있어야 하지 않을까 ? ex) em.update(member)
// SQL 쿼리 날리기
tx.commit();
memberA.setName("hi"); 코드를 통해 우리는 데이터베이스에 저장된 memberA 의 이름을 바꾸었다.
JPA 에서는 이렇게 수정한 코드를 따로 반영하는 코드를 짤 필요가 없다. 왜일까 ?
find 함수를 통해 데이터베이스에서 객체를 가져와 1차 캐시 안에다 저장한다.
setName 을 통해 1차 캐시 안에 있는 초기 데이터와 바뀐 데이터를 비교해 쿼리를 만들어 SQL 저장소에 저장한다.
commit() 이 발생하면 만든 쿼리를 데이터베이스에 날려 수정한 정보를 적용한다.
이는 정말 처음 봤을 때 거의 혁신이였다. 너무나 편리한 것 같다.
'개인 공부 > Spring' 카테고리의 다른 글
[Spring] JDBC 에서 Transaction 관리하는 법 (0) | 2022.11.08 |
---|---|
[Spring] #9 JPA 엔티티 매핑 (0) | 2022.04.18 |
[Spring] #7 JPA의 필요성 (0) | 2022.04.15 |
[Spring] #6 스프링 빈 등록 (0) | 2022.01.19 |
[Spring] #5 회원 서비스 구현 (0) | 2022.01.19 |