Entity Manager Factory
** connection pool ?
사용자의 요청에 따라 Connection 을 생성하다 보면 많은 수의 연결이 발생했을 때 서버에 과부하가 걸리게 된다 . 이러한 상황을 방지하기 위해 미리 일정수의 Connection 을 만들어 pool 에 담아 뒀다가 사용자의 요청이 발생하면 연결을 해주고 연결 종료 시 pool 에 다시 반 환하여 보관하는 것이다 .
Entity Manager
Entity
// Entity Manager Factory
EntityManagerFactory emf = Persistence.createEntityManagerFactory("UserPlatformDev");
// Entity Manager
EntityManager em = emf.createEntityManager();
출처 : 자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)
영속성 컨텍스트
em.persist(member);
엔티티에는 4가지 상태가 존재
엔티티 객체를 생성, 순수한 객체상태이며 아직 저장하지 않음
Member member = new Member();
member.setId("UserPlatformDev Member");
member.setUsername("jongil");
영속성 컨텍스트가 관리하는 엔티티. JPQL을 사용해서 조회한 엔티티도 영속상태
em.persist(member);
// 분리 명령어
em.detach();
em.close();
엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제
em.remove(member);
출처 : 자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)
@Id
private Long sn;
flush
라 칭함영속성 컨텍스트는 내부에 캐시를 가지고 있다. 이를 1차 캐시라 한다.
쉽게 말하면 내부에 Map이 하나 있고 @Id
가 key값이라 생각하면 된다.
// 비영속 상태
Member member = new Member();
member.setId("UserPlatformDev Member");
member.setUsername("jongil");
// 영속 (DB에 저장되지는 않음)
em.persist(member);
Member member = em.find(Member.class, "jongil");
em.find를 호출하면 먼저 1차캐시에서 엔티티를 찾고 없으면 데이터베이스에서 조회한다.
Member member = em.find(Member.class, "jjj");
1차캐시에 없는 데이터일 경우 DB를 조회해서 엔티티를 생성하고 1차캐시에 저장 후 영속상태의 엔티티를 반환한다.
Member a = em.find(Memeber.class, "jongil");
Member b = em.find(Memeber.class, "jongil");
// a == b 는 참 일까?
당연히 참이다. 1차캐시에 있는 같은 엔티티 인스턴스를 반환하므로 동일성을 보장한다.
엔티티 매니저를 사용하여 엔티티를 영속성 컨텍스트에 등록해보자
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 트랜잭션 시작
transaction.begin();
// INSERT SQL을 DB에 보내지않음
em.persist(memberA);
em.persist(memberB);
// 커밋하는 순간 보냄
transaction.commit();
엔티티 매니저는 트랜잭션 커밋 직전까지 DB에 엔티티를 저장하는 것이 아닌 내부 쿼리 저장소에 INSERT SQL을 모아둔다. 커밋할 때 한번에 DB에 보내는데 이것이 바로 쓰기지연 !
이를 잘 활용하면 모아둔 등록쿼리를 DB에 한번에 전달해서 성능을 최적화 할 수 있다
SQL 수정쿼리의 문제점
변경 감지
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
Member member = em.find(Member.class, "jongil");
member.setUsername("jjj");
member.setAge(29);
// em.update(member); // 왜 없을까 ?
transaction.commit();
JPA는 변경사항을 명령어 없이 자동으로 감지하여 반영을하는데 이를 변경감지라고 한다.
변경감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용 !
변경감지 동작 과정
** 스냅샷 : 최초 상태를 복사해 저장해두는 것
DynamicInsert, DynamicUpdate
Update를 할 때 한개의 컬럼을 업데이트하면 한가지만 Set 하는 쿼리가 날아갈까?
아니다. JPA의 기본 전략은 모든 필드를 업데이트 한다.
전송량이 증가할텐데 왜?
- 모든 필드를 수정하면 수정쿼리가 항상 동일, 재사용성 증가
- DB에 동일한 쿼리를 보내면 DB는 이전에 한번 파싱된 쿼리를 재사용 할 수 있음
컬럼이 대략 30개 이상이 되면 기본 방법인 정적 수정쿼리보다 동적 수정쿼리가 필요하다. 이 때 사용하는 것이 바로 @DynamicUpdate
이다!
필요한 부분 (부분 null 인 entity 등)만 insert 가능한 @DynamicInsert
도 있다!
Member member = em.find(Member.class, "jongil");
em.remove(member);
em.remove();
엔티티 삭제 또한 바로 삭제되는 것이 아닌 삭제쿼리를 쓰기 지연 SQL에 등록 후 트랜잭션 커밋하여 플러시를 호출하면 삭제 ! 삭제된 엔티티는 재사용하지 않는것이 좋다
플러시는 영속성 컨텍스트의 변경 내용을 DB에 반영한다 !
동작과정
영속성 컨텍스트 플러시 방법
em.flush() 를 호출
트랜잭션 커밋 시 플러시 자동 호출
JPQL 쿼리 실행 시 플러시가 자동 호출
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
// DB 미반영 상태
query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();
FlushModeType.AUTO
: 커밋이나 쿼리를 실행할 때 플러시 (default)FlushModeType.COMMIT
: 커밋할 때만 플러시⚠️ flush는 영속성컨텍스트에 보관된 Entity를 지우는 것이 아닌 변경 내용을 DB에 동기화하는것 !
준영속?
영속상태였다가 더는 영속성 컨텍스트가 관리하지 않는 상태
준영속 상태로 만드는 방법
public void detach(Object entity);
// 비영속 상태
Member member = new Member();
member.setId("jongil");
member.setUsername("종일");
// 영속 상태
em.persist(member);
// 영속성 컨텍스트에서 분리(준영속 상태)
em.detach(member);
transaction.commit(); // 커밋
위와같이 중간에 detach
를 하게되면 영속성 컨텍스트로부터 분리(detach)된 상태이기 때문에 commit을 하더라도 반영이 되지 않는다.
detach는 부분 분리이지만 clear는 영속성 컨텍스트를 초기화하는것 !
휴지통 비우기 : clear
/ 휴지통에 파일 삭제 : detach
라 생각하면 쉬울것같다 !
영속 상태의 엔티티가 모두 준 영속 상태가 된다.
영속성 컨텍스트를 종료한다 생각하면 된다.
개발자가 직접 준영속 상태로 만드는 일은 드물다 ! 잘 알아두기만 하자
준영속 상태의 엔티티를 다시 영속상태로 변경하는 방법이다. 새로운 영속 상태의 엔티티를 반환.