JPA는 영속성(Persistence)을 관리함
영속성 ?
: 일반적으로 영속성 컨텍스트(Persistence Context)란 Entity를 영구히 저장하는 환경을 의미함
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
// EntityManagerFactory를 통해 EntityManager 객체 생성
EntityManager em = emf.createEntityManager();
→ em, 즉 EntityManager로 Entity를 CRUD 처리하며,
이렇게 처리된 Entity는 DB에 곧바로 반영되는 것이 아니고, 영속성 컨텍스트에 보관, 관리됨
Book book = new Book();
// 필드 맵핑 setXxx() 생략..
em.persist(book); // 영속성 컨텍스트에 book Entity 저장 (DB가 아님)
영속성 컨텍스트는 EntityManager 생성 시 만들어지기 때문에 EntityManager를 통해 영속성 컨텍스트에 접근, 관리가 가능함.
em.persist(book);
를 통해 현재 영속성 context에 book이라는 entity가 들어가 있다.
그럼 언제 실제로 DB에 반영될까?
비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태
: Book book = new Book()
까지만 만들었을 때
영속(managed): 영속성 컨텍스트에 저장된 상태
: em.persist(book)
준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
삭제(removed): 삭제된 상태
: em.remove()
persist()
시 Managed라는 영속상태에 들어가고,
flush()
는 영속 상태에 있는 entity를 DB에 보낼 때
사용한다.
1) 비영속 상태(new/transient) : 순수하게 엔티티 객체를 생성만 한 상태
아직 EntityManager를 통해 영속성 컨텍스트에 저장하지 않았음
Book book = new Book();
book.setBookName("어른왕자");
2) 영속 상태(managed) : EntityManager를 통해 Entity를 영속성 컨텍스트에 저장
→ 영속성 컨텍스트가 관리하는 Entity를 영속 상태(Managed)라고 함
em.persist()
3) 준영속 상태(detached) : 영속성 컨텍스트가 기존에 관리히던 Entity를 더 이상 관리하지 않을 경우 준영속 상태가 됨
// Book entity를 영속성 컨텍스트에서 분리, 영속 -> 준영속 상태
em.detach(book);
// 혹은
em.close() // 영속성 컨텍스트 닫기
// 혹은
em.clear() // 영속성 컨텍스트 초기화
4) 삭제 상태(removed) : Entity를 영속성 컨텍스트와 DB에서 삭제
em.remove(book);
1) 영속성 컨텍스트는
Entity를 식별자(PK, 기본키, @Id로 Table의 기본 키와 맵핑한 값)
로 구분한다.
-> 영속 상태는 반드시 식별자가 있어야한다.
2) 영속성 컨텍스트에 저장된 Entity는
Transaction이 Commit되는 순간 실제 DB에 반영
되는데, 이를 flush
라고 함
영속성 컨텍스트 내부에는 캐시가 있는데, 이를 1차 캐시
라고 함
영속 상태의 Entity는 모두 이곳에 저장됨 → Map 객체 상태로 저장된다.
Key : @Id
Value : Entity Instance
// 비영속
Book book = new Book();
book.setBookName("어른 왕자");
// 영속
em.persist(book);
→ @Id는 기본 키인 식별자 값이기 때문에 영속성 컨텍스트에서 데이터를 저장,
조회
하는 기준은 기본키(Id)
로 이루어짐
Entity 조회
Book book = em.find(Book.class, 1L);
→ em.find()가 호출되면 1차 캐시에서 Entity를 찾고, 찾고자 하는 Entity가 1차 캐시에 없을 경우 DB에서 조회하게 됨
즉, 영속성 컨텍스트를 먼저 보고 Entity를 조회한 뒤, 없는 경우 DB에서 조회한다.
이를 통해 성능이 좋아지고, 조회 시간을 감소할 수 있다.
id가 1L인 Entity가 1차 캐시에 존재하므로, DB까지 접근하지 않는다.
EntityManager는 DB를 조회해서 Entity를 생성, 1차 캐시에 저장한 후 영속 상태의 Entity를 반환함
식별자(Id)가 같은 Entity 인스턴스를 조회해서 비교
Book book = em.find(Book.class, 1L);
Book book2 = em.find(Book.class, 1L);
System.out.println(a == b); // ?
→ 결과는 true
,
반복해서 find()를 호출해도 영속성 컨텍스트는 같은 Entity 인스턴스를 반환함.(Entity의 동일성 보장)
JDBC의 경우에는 서로 다른 인스턴스로 생성
됨.
EntityManger는 트랜잭션을 commit하기 전 까지 DB에 Entity를 저장하지 않고, 내부 쿼리 저장소에 INSERT SQL을 모아둠.
트랜잭션이 commit될 때 모아둔 쿼리를 DB에 보냄(쓰기 지연, Transactional write-behind)
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
// EntityManager는 데이터 변경 시 트랜잭션을 시작해야 함
tx.begin();
em.persist(book1);
em.persist(book2);
// 영속성 컨텍스트에만 집어넣은 상태, DB에 들어가지 않은 상태
// 아직 Insert SQL을 수행하지 않음
// Commit되는 순간 DB에 Insert SQL을 보냄
tx.commit();
em.persist()
를 통해 영속성 컨텍스트 내의 1차 캐시에 Entity를 저장
하고, INSERT SQL 코드를 생성
한다.
실제로 DB에 반영되는 순간은, tx.commit()
이 수행되었을 때이다.
DB에 접근할 때는 디스크를 돌리기 때문에 시간이 걸리는데, SQL문을 영속 컨텍스트에 모아놨다가 한번에 commit()하면 성능이 좋아진다.
트랜잭션이 commit되면 EntityManager는 영속성 컨텍스트를 flush() 함
→ flush()는 영속성 컨텍스트의 변경 내용을 DB에 동기화하는 작업
즉, 등록, 수정, 삭제한 Entity를 DB에 반영.
→ 쓰기 지연 SQL저장소에 모인 쿼리를 DB에 보냄.
이렇게 영속성 컨텍스트의 변경 내역을 DB에 동기화한 후, 실제 DB의 트랜잭션을 commit()함.
→ SQL문을 DB에 한 번에 전달하기 때문에 성능 최적화가 가능
DB의 데이터를 수정하는 SQL문
UPDATE BOOK
SET
BOOK_NAME=?,
AUTHOR=?
WHERE
id=?
책의 발행 날짜 필드 추가 및 변경 기능 요구
→ 발행 날짜를 수정하는 별도의 SQL문 작성
UPDATE BOOK
SET
PUB_DATE
WHERE
id=?
→ 비즈니스 로직을 위해 SQL문을 일일이 확인해야 하는 번거로움, 복잡성 증가(비즈니스 로직이 SQL에 의존하게 됨)
→ em.update()같은 메서드는 없음, Entity의 변경 사항을 DB에 자동으로 반영하는 기능을 변경 감지라고 함.
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
// 영속 상태의 Entity 조회
Book book = em.find(Book.class, 1L);
// 영속 상태의 Entity 데이터 수정
book.setBookName("어른 왕자");
tx.commit();
JPA는 Entity를 영속성 컨텍스트에 저장할 때,
최초 상태를 복사해서 저장해둠(스냅샷)
flush() 시점에 스냅샷과 Entity를 비교해서 변경된 Entity를 찾음
Entity를 삭제하려면 먼저 삭제 대상의 Entity를 조회해야 함
// 삭제할 Entity 조회
Book book = em.find(Book.class, 1L);
em.remove(book); // 엔티티 삭제
→ DELETE SQL역시 쓰기 지연 SQL 저장소에 등록하고, 트랜잭션이 commit되어 flush()가 호출되면 실제 DB에 Query를 전달함.