JPA에서의 영속성 관리에 대해 알아봅니다.
EntityMangerFactory
데이터베이스를 하나만 사용하는 애플리케이션은 일반적으로 EntityMangerFactory를 하나만 생성합니다.
EntityMangerFactory 생성시
// 공장의 개념으로 생성비용이 많이든다.
EntityManagerFatory emf = Persistence.createEntityManagerFactory("db");
EntityMangerFactory는 EntityManager를 생산해주는 클래스입니다.
EntityManager
엔티티 매니저는 엔티티를 저장, 수정, 삭제, 조회 등 메모리상의 가상 데이터베이스입니다. 엔티티와 관련된 모든 일을 합니다. 보통 트랜잭션에 맞춘 라이프사이클을 가지고 있습니다.
EntityManager 생성시
EntityManagerFatory emf = Persistence.createEntityManagerFactory("db");
// 생성시 비용이 거의들지않습니다.
EntityManager em = emf.createEntityManager();
em.persist(item); // item을 영속화
위의 설명에서는 엔티티를 영구 저장하는 환경이라고 말했지만, 프로그램이 종료 전에 엔티티 매니저를 초기화 시키거나 종료시키는 코드를 만나면 영속성 컨텍스트는 소멸합니다.
이후에, commit이 일어나면 영속성 컨텍스트에서 쿼리를 만들어서 실제 데이터베이스에 반영합니다.
비영속
엔티티 객체가 존재하지만 영속성 컨텍스트에는 저장하지 않은 상태입니다.
Member member = new Member();
//em.persist(member) // 영속화 x
영속
엔티티 객체를 영속성 컨텍스트에 저장한 상태입니다.
Member member = new Member();
em.persist(member);
준영속
영속상태의 엔티티 객체를 영속성 컨텍스트에서 분리된 상태입니다.
Member member = new Member();
// 영속성 컨텍스트에의해 관리
em.persist(member);
// 준영속 상태 (분리)
em.detach(member);
삭제
삭제된 상태입니다.
Member member = new Member();
em.remove(member);
영속성 컨텍스트의 식별자 값
영속성 컨텍스트 데이터베이스 저장
플러시와 커밋의 차이는?
flush는 트랜잭션과 함께 엔티티를 즉시 데이터베이스에 반영하지만, 변경 사항을 커밋하지 않을 수 있습니다.
하지만 commit을 하는 순간 영속성 컨텍스트의 엔티티를 반영하며 롤백할 수 없습니다.
영속성 컨텍스트를 사용하면 장점
Member member = new Member();
member.setId("1);
member.setName("dog");
em.persist(member);
영속성 컨텍스트는 내부에 캐시를 가지고 있습니다, 이를 1차 캐시라고 부릅니다.
영속 상태의 엔티티를 저장하면 이곳에 저장합니다.
1차 캐시의 내부는 Map의 형태로 이루어져 있습니다. 키는 @id로 매핑만 식별자로 사용하고 인스턴스의 값은 엔티티를 사용합니다.
1차 캐시에서 조회
Member member = new Member();
member.setId("1);
member.setName("dog");
em.persist(member);
Member member = em.find(Member.class, "dog");
em.find()를 호출하는 순간 먼저 1차 캐시에서 엔티티를 찾은 뒤 데이터가 존재하지 않으면 데이터베이스에서 조회합니다.
데이터베이스에서 조회
Member member = new Member();
member.setId("1);
member.setName("dog");
em.persist(member);
Member member = em.find(Member.class, "cat"); // 강아지말고 고양이
1차 캐시가 만들어진 이유는?
데이터베이스를 커넥션 하는 과정은 서버에 부하가 많이 듭니다.
엔티티를 다시 사용할 일이 있으면 데이터베이스에 접근하지 않고 빠르게 1차 캐시에 있는 엔티티를 사용하여 성능상 이점을 얻을 수 있습니다.
하지만, 한편으로는 엔티티 매니저는 트랜잭션을 기준으로 생성과 삭제를 하기 때문에 데이터베이스도 트랜잭션을 단위로 캐싱 작업을 진행하기 때문에 성능상 큰 이점을 가질 수 없습니다.
Member member1 = em.find(Member.class, "member1");
Member member2 = em.find(Member.class, "member1");
System.out.println(member1 == member 2);
EntityManger em = emf.createEntityManager();
EntityTransaction tr = em.getTransaction();
em.persist(member1);
em.persist(member2); // 현재까지 SQL문을 DB에 보내지않습니다.
tr.commit(); //커밋하는 순간 SQL문을 DB에 INSERT를 합니다.
트랜잭션이 지원하는 쓰기 지연을 사용하는 이유
데이터베이스와 커넥션 하는 것은 많은 자원을 소모합니다.
쿼리를 보낼 때 마다 데이터베이스와 잦은 커넥션을 한다면 애플리케이션의 성능을 낮추기 때문에 쿼리를 저장소에 모아놓고 한 번에 전송하는 쓰기 지연 방식을 사용합니다. 자바의 버퍼의 방식과 유사합니다.
Member member = em.find(Member.class, "member1"); // 조회
member.setName("spring");
member.setEmail("0000@naver.com");
// em.update(member);
transaction.commit();
왜 update가 필요없는가?
변경 감지
단, 변경감지는 엔티티 컨텍스트에 관리되는 영속성의 엔티티에만 적용됩니다.
업데이트 기본 전략
JPA의 업데이트 기본전략은 엔티티의 모든 필드를 업데이트 합니다.
Member member1 = em.find(Member.class, "member1);
em.remove(member1); // 삭제
트랜잭션이 커밋이 될 때 DELETE 쿼리가 나갑니다.
영속성 컨텍스트를 플러시하는 방법
JPQL 사용시
em.persist(member1);
em.persist(member2);
// JPQL실행 (flush를 호출한다.)
query = em.createQuery("select m from Member m", Member.class);
플러시 옵션 모드
플러시 정리
플러시는 영속성 컨텍스트의 내용을 비우지 않습니다. 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스와 동기화하는 것입니다.
플러시를 사용할 수 있는 이유는 트랜잭션이라는 작업 단위 때문입니다. 트랜잭션 커밋 직전에만 변경 내용을 데이터베이스와 동기화를 시키면됩니다.
영속성 컨텍스트가 관리하는 엔티티가 영속성 컨텍스트에서 분리된 상태입니다.
Member member = em.find(Member.class, "member1");
member.setName("spring");
em.detach(member); // 준영속
transaction.commit();
clear()
영속성 컨텍스트 자체를 초기화 시킵니다.
영속성 컨텍스트에 존재하는 모든 엔티티를 준영속 상태로 만듭니다.
close()
영속성 컨텍스트를 종료시킵니다.
영속성 컨텍스트에 존재하는 모든 엔티티를 준영속 상태로 만듭니다.