이 글은 김영한님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편' 강의 수강 후 정리한 글입니다.
고객 요청이 올때마다 Entity Manager Factory를 통해 EntityManager를 각각 생성해서 DB에 접근한다.
- 엔티티를 영구 저장하는 환경이다. - 엔티티를 저장하는 방식: EntityManger.persist(entity); -> DB에 저장하는게 아니라 엔티티를 영속성 컨텍스트에 저장하는 것이다. - 영속성 컨텍스트는 논리적인 개념으로, 눈에 보이지 않는다. - 엔티티 매니저를 통해 영속성 텍스트에 접근한다. (엔티티 매니저 안에 생성됨)
1. 비영속
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태이다.
Member member = new Member();
member.setId(100L);
member.setName("HelloJPA");
위와 같이, JPA와 관련 없이 그저 객체를 생성한 상태이다.
2. 영속
영속성 컨텍스트에 관리되는 상태이다.
em.persist(member);
멤버 객체를 생성하고, 엔티티 매니저에 persist를 통해 멤버 객체를 삽입하면 엔티티 매니저 안에 있는 영속성 컨텍스트에 멤버 객체가 들어간 상태를 영속 상태라고 한다.
이 상태에서 SQL이 실행되는 것이 아니며, 데이터가 DB에 저장되지 않는다. 해당 트랜잭션을 커밋하는 시점에 쿼리가 DB에 반영된다.
3. 준영속 , 삭제
em.detach(member);
회원 엔티티를 영속성 컨텍스트에서 분리하는 것을 준영속 상태라고 한다.
em.remove(member);
실행 시 객체를 상태한 상태가 된다.
- 1차 캐시를 저장할 수 있다.
Member member = new Member();
member.setId("member1);
member.setUsername("회원1");
// 1차 캐시에 저장된 상태
em.persist(member);
// 1차 캐시에서 조회
Member findMember = em.find(Member.class,"member2");
영속 상태가 되면 1차 캐시에 저장되며, 1차 캐시에서 바로 조회할 수 있다.
DB에서 조회하지 않고도 1차 캐시에서 조회가 가능하다. 1차 캐시에 없지만 DB에 있는 값을 조회하려고 하면, 1차 캐시에 저장하고 값이 반환된다.
조회 시 DB에서 가져오는게아니라, 1차캐시에서 조회하는 것이므로 select문이 실행되지는 않는다.
데이터베이스에서 조회하는 시나리오
1 ) find("member2") 가 DB에는 있지만 1차 캐시에 없을 경우
2 ) member2를 DB에서 조회
3 ) member2를 1차 캐시에 저장
4 ) 저장한 member2 값을 반환
해당 요청이 끝나면 1차 캐시가 사라지기 때문에 엄청난 이득이 있지는 않고, 한 트랜젝션 안에서만 효과가 있다. 비즈니스 로직이 복잡할 경우에도 유용할 수 있다.
- 영속 엔티티의 동일성을 보장할 수 있다.
Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L);
System.out.println( findMemeber1 == findMember2 );
자바 컬렉션에서는 주소가 같은데, JPA에서도 1차 캐시에서 가져오면 되기 때문에 == 비교를 True가 되도록 보장해준다.단, 같은 트랜젝션 안에서 비교해야 한다.
- 엔티티 등록 시 트렌젝션을 지원하는 쓰기 지연이 가능하다.
JPA는 트랜잭션을 커밋하는 순간에 sql을 데이터베이스로 보낸다.
엔티티 매니저는 데이터 변경시 트랜젝션을 시작해야 하며 em.persist 할 때는 insert sql을 실행하지 않는다.
위의 경우 memberA를 persist로 1차 캐시에 저장하면, 동시에 jpa가 insert 쿼리를 생성해서 쓰기 지연 SQL 저장소에 쿼리를 쌓아둔다.
동시에 memberB 저장 시에도 insert 쿼리가 쓰기 지연 SQL 저장소에 저장된다.
transaction.commit 하는 순간, 쓰기 지연 SQL 저장소에 있는 쿼리를 모두 flush가 되면서 데이터베이스로 전달된다.
hibernate의 경우, 버퍼링 기능이 존재한다.
hibernate.jdbc.batch_size value=10 으로 설정하면 설정한 만큼 모았다가 한번에 보내게 되므로 성능이 향상될 수 있다.
- 엔티티 수정 시 자동으로 변경을 감지한다. ( dirty checking )
Member membernew = em.find(Member.class, 150L);
membernew.setName("Z");
setter만 실행해도 자바 컬렉션처럼 em.update(member)와 같은 코드를 실행하지 않아도 변경된 내용이 자동으로 반영된다.
JPA에서는 트랜잭션을 커밋하는 시점에 엔티티와 스냅샷을 비교한다. 영속성 컨텍스트의 1차 캐시에 들어온 최초 시점의 데이터를 스냅샷으로 저장한다.
커밋하는 시점에 memberA의 엔티티 값과 스냅샷에서의 값을 비교해서 변경되어있다면, 내부적으로 flush()가 호출될 때 update 쿼리가 쓰기 지연 SQL 저장소에 생성해두고 데이터베이스에 반영하여 커밋하게 된다.
즉 데이터를 변경하고 싶으면 em.persist(membernew);
를 굳이 실행할 필요가 없는 것이다.
영속성 컨텍스트의 변경내용을 데이터베이스에 반영하는 것이다.
변경 감지가 발생하면 수정된 엔티티 쓰기 지연 SQL 저장소에 등록하고, 저장소의 쿼리를 데이터베이스에 보내게 된다.
1. em.flush()를 통해 직접 호출한다.
강제로 flush를 호출하고 커밋하면 이전의 쿼리가 반영된다. flush 호출 시 1차 캐시는 그대로 유지되며 쓰기 지연 SQL 저장소에 있는 쿼리들이 데이터베이스에 반영이 되는 것이다.
2. 트랜젝션 커밋 시에도 flush 자동 호출된다.
3. JPQL 쿼리 실행 시에 flush가 자동 호출된다.
em.persist(memberA);
em.persist(memberB);
query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();
이 시점에 JPQL 실행 시, sql문으로 바로 날라가는데, 위의 memberA와 memberB를 저장하는 쿼리 자체가 반영이 안되어있으므로 select할 데이터가 없는 상황이 된다. 이러한 경우 문제가 생기는걸 방지하기 위해, JPQL 쿼리를 실행하면 flush를 자동호출하도록 해서 이전의 쿼리도 같이 반영되도록 하는 것이다.
em.setFlushMode(FlushModeType.COMMIT)
커밋할 때만 플러시하는 옵션이지만, 기본적으로 FlushModeType.AUTO
모드가 기본값으로 설정되어있으며 이 모드를 보통 사용한다.
플러시는 영속성 컨텍스트를 비우지 않고, 변경 내용을 데이터베이스에 동기화한다. 트랜잭션이라는 작업 단위가 중요한데, 커밋 직전에만 동기화하면 된다.
영속 상태 엔티티가 영속성 컨텍스트에서 분리된 것이며, 준영속 상태에서는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없다.
em.detach(member);
특정 엔티티만 준영속 상태로 전환할 경우 사용한다. jpa에서 더이상 관리하지 않는 상태가 되므로 sql 쿼리가 날아가지 않게된다.
em.clear();
엔티티 메니저 안에 있는 영속성 컨텍스트를 통째로 삭제한다. 1차 캐시 내용까지 초기화된다.
em.close();