개념
- 엔티티를 영구 저장하는 환경 이라는 뜻이다.
- 엔티티 매니저를 통해 영속성 컨텍스트에 접근할 수 있다.(EntityManager.persist(entity);)
생명주기
- 비영속(new/transient) : 영속성 컨텍스트와는 상관없는 새로운 상태.
Member member = new Member();
member.setId("member1");
member.setUserName("회원1");
- 영속 : 영속성 컨텍스트에 관리되고 있는 상태, DB에는 저장되지 않는다.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(member); // 영속
- 준영속 : 영속성 컨텍스트에 저장되었다가 분리된 상태, 영속 상태에서 다시 비영속 상태로 전환시킨다.
em.detach(member); //member 엔티티만 준영속 상태로 전환
em.clear(); // 영속성 컨텍스트의 내부에있는 데이터를 전부 초기화
em.close(); // 영속성 컨텍스트를 종료
--준영속 추가내용--
em.find()로 DB의 데이터를 1차캐시에 영속시키거나, em.persist()로 영속컨텍스트에 저장시킨 데이터가 있다고 가정할때, 이 데이터가 더티체킹을 통해 UPDATE쿼리를 날리고싶지 않다면 em.detach()를 통해 영속상태를 해제해 JPA에서 관리하지 않게 해줄 수 있다.
- 삭제 : 삭제된 상태
em.remove(member);
1차 캐시
영속성 컨텍스트 내부에 1차캐시가 존재한다. 사실상 1차캐시를 영속성 컨텍스트라고 봐도 무방하다.
1차 캐시에서의 조회
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class,"member1");
1차캐시에 저장을하고 1차캐시에서 조회를 하게되니까 em.find() 메서드를 호출하여도 SELECT쿼리가 생성되지 않는다.
Member findMember2 = em.fin(Member.class,"Member2");
1차 캐시에 Member2가 없으므로 DB에서 조회 후 DB에 있으면 1차캐시에 저장을 한 후 Member2를 반환한다. 그 후 Member2를 다시 조회하면 DB에 접근하지 않고 1차캐시에서 조회를 하게 된다. 다만 entityManager는 보통 트랜잭션 단위로 만들기때문에 트랜잭션이 종료될때 1차캐시 내부의 조회된 데이터도 같이 사라져서 1차캐시를 통한 성능이득을 보긴 어렵다. 성능을 위한것이라기보단 객체지향적 설계란 것에 의의를 두는것이 좋다.(애플리케이션 전체에서 공유되는 캐시는 2차캐시라 한다.)
영속 엔티티의 동일성 보장
Member a = em.find(Member.class, "Member1");
Member b = em.find(Member.class, "Member1");
System.out.println(a==b); //true
자바 컬렉션에서 같은 레퍼런스이 객체를 꺼낼시 동일성을 보장해주듯이 영속 엔티티또한 1차캐시가 동일성을 보장해준다.(같은 트랜젝션에서만 가능)
쓰기 지연
transaction.begin(); // 트랜젝션 시작
em.persist(memberA);
em.persist(memberB);
//위 두개의 영속화과정에서 INSERT 쿼리를 DB에 보내지 않는다.
transaction.commit(); // 트랜잭션을 커밋하는 순간 INSERT 쿼리를 DB에 보낸다.
em.persist(memberA)를 통해 1차캐시에 memberA가 들어가고 동시에 INSERT SQL을 생성후 쓰기지연 SQL에 저장한다.
em.persist(memberB)도 마찬가지로 작동한다.
그런 후 transaction.commit(); 시점에 DB에 INSERT SQL이 flush되고, 실제 데이터가 DB에 commit된다. (1차캐시에 데이터는 그대로 남아 있음)
이렇게 sql문을 데이터 하나하나 영속화 할때마다 짜지않고, 마지막 commit때 한번에 몰아서 쿼리문을 날리게되며 성능 향상을 야기할 수 있다. 이때 memberA와 memberB는 jdbc의 batch에 저장되었다가 한번에 commit된다.
변경 감지(dirty checking)
transaction.begin(); //트랜잭션 시작
Member memberA = em.find(Member.class,"Member1"); // DB 정보를 1차 캐시로 저장 후 반환
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member)를 하지 않아도 자동으로 변경을 감지 후 UPDATE 쿼리문을 생성후 DB에 날림
transaction.commit();
1차캐시에는 값을 읽어온 최초시점의 값을 스냅샷에 저장을 하게 되는데 엔티티를 변경하게되면 commit 시점에 flush()를 호출하며 JPA가 Entity와 스냅샷을 비교하고 Entity가 변경이 되었다면 UPDATE쿼리를 쓰기지연 SQL에 저장을 하게 된다. 그후 DB에 UPDATE쿼리를 날리게 된다.
플러시(flush)
1차캐시의 변경 내용을 DB에 동기화 하는것을 의미한다.
트랜잭션이 커밋될때 자동으로 플러시되며, 플러시가 되면 수정된 엔티티를 쓰기지연 SQL 저장소에 등록하게 된다. 그 후 쓰기지연 SQL저장소의 쿼리를 DB에 전송한다(등록, 수정, 삭제 쿼리)
em.flush() - 직접 호출
트랜잭션 커밋 - 플러시 자동 호출
JPQL쿼리 실행- 플러시 자동 호출
위 세개의 방법으로 플러시를 하게되며, 플러시를 호출한다 해도 1차캐시에는 데이터가 그대로 남아있다.
'스프링' 카테고리의 다른 글
[스프링] 즉시로딩과 지연로딩 (0) | 2023.02.10 |
---|---|
[스프링] 프록시란 (0) | 2023.02.10 |
[스프링] 기본키 관련 매핑 Annotation (0) | 2023.02.09 |
[스프링] 필드와 컬럼 매핑 Annotation (0) | 2023.02.09 |
JPA(Java Persistance API)란 (0) | 2023.02.06 |