JPA를 이해하는데 가장 중요한 용어
Entity를 영구 저장하는 환경
EntityManager.persist(entity);
영속성 컨텍스트는 논리적인 개념
EntityManager를 통해서 영속성 컨텍스트에 접근
- J2SE 환경
EntityManager 영속성 컨텍스트가 1:1
EntityManager -> PersistenceContext
- J2EE , 스프링 프레임워크 같은 컨테이너 환경
EntityManager와 영속성 컨텍스트가 N:1
EntityManager ->
EntityManager -> PersistenceContext
EntityManager ->
- 비영속 (new / transient)
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
- 영속 (managed)
영속성 컨텍스트에 관리되는 상태
- 준영속 (detached)
영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제 (removed)
삭제된 상태
=====================================================================
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
=====================================================================
영속 상태가 됐다고 해서 DB에 쿼리가 날라가지 않음
commit을 해야 쿼리가 날라감
=====================================================================
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);
tx.commit(); //여기서 DB에 쿼리를 날림
=====================================================================
1. 1차 캐시
2. 동일성(identity) 보장
3. 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
4. 변경 감지 (Dirty Checking)
5. 지연 로딩 (Lazy Loading)
※ 고객의 비지니스 요청이 끝나면 EntityManager가 지워지므로 그렇게 큰 이점이 없음
- 한번 조회한 데이터를 또 조회하면 1차 캐시에서 조회 함 (동일한 트랜잭션 안에서만)
- 없을 경우 DB에서 조회함
- DB에서 조회가 될 경우 다시 1차 캐시에 저장함
예시
=====================================================================
Member member = new Member();
member.setId(100L);
member.setName("member100");
em.persist(member);//1차 캐시에 저장
//DB에있는 데이터가 아닌 1차 캐시에 있는 데이터를 가져옴
System.out.println(em.find(Member.class,100L));
=====================================================================
위의 코드 출력
조회가 먼저 된 후 insert 쿼리가 실행됨
=====================================================================
Member{id=100, name='member100'}
Hibernate:
/* insert org.hello.jpa.Member
*/ insert
into
MEMBER
(name, id)
values
(?, ?)
=====================================================================
똑같은 조회를 두번 요청함
=====================================================================
Member findMember1 = em.find(Member.class,100L);
Member findMember2 = em.find(Member.class,100L);
System.out.println(findMember1);
System.out.println(findMember2);
=====================================================================
실행 결과 쿼리는 한번만 실행됨
=====================================================================
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
MEMBER member0_
where
member0_.id=?
Member{id=100, name='member100'}
Member{id=100, name='member100'}
=====================================================================
1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 어플리케이션 차원에서 제공
=====================================================================
Member findMember1 = em.find(Member.class,100L);
Member findMember2 = em.find(Member.class,100L);
System.out.println(findMember1==findMember2);
=====================================================================
결과
=====================================================================
true
=====================================================================
BufferWrite 이점을 얻을수 있음
=====================================================================
tx.begin(); //트랜잭션 시작
Member member = new Member();
member.setId(101L);
member.setName("member101");
//1차 캐시에 저장, SQL 쿼리를 실행하지 않고 쿼리만 생성함
//쓰기 지연 SQL 저장소에 저장
em.persist(member);
Member member2 = new Member();
member.setId(102L);
member.setName("member102");
//1차 캐시에 저장, SQL 쿼리를 실행하지 않고 쿼리만 생성함
//쓰기 지연 SQL 저장소에 저장
em.persist(member);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않음
tx.commit();//commit 하는 시점에 쓰기 지연 SQL 저장소에 있는 INSERT 쿼리가 실행됨
=====================================================================
persistence.xml 파일에서 아래의 옵션을 추가하면 쿼리를 모아서 날리는 사이즈를 정할수 있음
=====================================================================
<!--아래의 사이즈만큼 쿼리를 모았다가한번에 쿼리를 날림-->
<property name="hibernate.jdbc.batch_size" value="10"/>
=====================================================================
데이터 수정시에는 em.persist(findMember); 를 해주지 않아도 됨
1. flush()
2. Entity와 스냅샷 비교
3. UPDATE SQL 생성
=====================================================================
Member findMember = em.find(Member.class,100L);
findMember.setName("zzzzz");//데이터 수정
//JPA가 자동으로 업데이트를 쳐주므로 쿼리를 보내지않아도 됨
//em.persist(findMember);
tx.commit();//commit 하는 시점에 쓰기 지연 SQL 저장소에 있는 INSERT 쿼리가 실행됨
=====================================================================
삭제 또한 commit 시점에 실행됨
em.remove(findMember);
영속성 컨텍스트의 변경 내용을 데이터베이스에 반영
commit이 실행될때 flush가 실행됨
쓰기 지연 SQL 저장소에 있는 쿼리들이 DB에 반영이 되는 과정
영속성 컨텍스트를 비우지 않음
영속성 컨텍스트의 변경내용을 데이터베이스에 동기화
트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화 하면 됨
영속성 컨텍스트와 트랜잭션의 주기를 맞춰서 설계해야 문제가 없음
- 플러시 발생
변경 감지
수정된 엔티티 쓰기 지연 SQL 저장소에 등록
쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록,수정,삭제 쿼리)
- 플러시 하는 방법
직접 호출 : em.flush()
플러시 자동 호출 : 트랜잭션 커밋 , em.commit();
플러시 자동 호출 : JPQL 쿼리 실행
- JPQL 실행
=====================================================================
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//자동으로 플러시가 실행이 안되면 아래의 데이터에선 위에 추가한 memberA , memberB , memberC가 보이지 않음
//그렇기에 자동으로 실행 한 후 아래의 쿼리 실행
query = em.createQuery("select * from member",Member.class);
List<Member> members = query.getResultList();
=====================================================================
- 플러시 모드 옵션
em.setFlushMode(FlushModeType.AUTO)
기본값 , 커밋이나 쿼리를 실행할 때 플러시
em.setFlushMode(FlushModeType.COMMIT)
커밋할 때만 플러시
쓰기 지연 SQL 저장소에 등록되어 있는 쿼리와 SELECT 쿼리가 상관 없을때 써도되나,, 그냥 AUTO로 냅두는게 젤 좋음
member.getTeam(); 쿼리를 나중에 날리는 기능
실무에서 가장 중요하므로 별도로 정리