JPA 에서 가장 중요한 2가지는 뭘까 ?
객체
와 관계형 데이터베이스
매핑하기영속성 컨텍스트란, JPA 를 이해하는 데 가장 중요한 용어 중 하나로, 엔티티를 영구 저장하는 환경
이라는 뜻이다.
엔티티 매니저 팩토리
는 엔티티 매니저
를 생성한다. 그리고 엔티티 매니저 팩토리를 생성할 때, 커넥션 풀
도 생성한다. 엔티티 매니저는 트랜잭션을 시작할 때, DB 연결에 꼭 필요한 커넥션
을 획득한다.
이러한 팩토리는 비용이 많이 들기 때문에 하나의 어플리케이션 전체에서 공유하도록 설계되어 있다. 매니저는 비용이 적어서 많이 만들어도 무방하다.
즉, 팩토리는 하나인데 매니저가 여러 개이면 여러 스레드가 있다는 뜻이다. 팩토리
는 여러 스레드가 동시에 공유할 수 있고, 매니저
는 동시성 문제로 스레드 간 공유를 절대로 할 수 없다 !
스프링 프레임워크 같은 컨테이너 환경에서는 엔티티 매니저와 영속성 컨텍스트가 N:1
이다.
// 객체를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
// 객체를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername(“회원1”);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// 객체를 저장한 상태 (영속)
em.persist(member);
// 회원 엔티티를 영속성 컨텍스트에서 분리한 상태 (준영속)
em.detach(member);
// 객체를 삭제한 상태 (삭제)
em.remove(member);
영속성 컨텍스트 안의 엔티티들은 각 키가 있고, 그 식별자 값은 DB 기본 키와 매핑되어있다. 즉, JPA 에서 @Id
에 쓰이는 칼럼이 키라는 뜻이다.
만약, 엔티티 매니저를 통해 어떠한 특정 엔티티를 찾는다고 할 때, 1차 캐시
에서 식별자 값으로 엔티티를 찾기 때문에 존재한다면, DB 까지 갈 필요가 없다. 즉, 조회 성능이 좋아진다. 하지만 존재하지 않는다면, DB 에서 엔티티를 추출해 1차 캐시에 저장한 후, 영속 상태의 엔티티를 반환한다.
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
// 1차 캐시에 저장됨
em.persist(member);
// 1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
1차 캐시에서 같은 엔티티 인스턴스를 반환하기 때문에 동일성을 보장해준다.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); // 동일성 비교 true
엔티티 매니저는 트랜잭션을 커밋
하기 전까지는 DB 에 엔티티를 저장하지 않고, 내부 쓰기 지연 저장소
에 INSERT SQL 을 모아둔다. 그리고, 트랜잭션을 커밋
하는 순간, 모아둔 쿼리를 DB 에 보내게 된다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
// 여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
// 커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
말 그대로 변경하는 것을 감지해준다는 뜻이다. JPA 가 영속 컨텍스트에 엔티티를 보관할 때, 최초 상태를 복사해서 저장하게 되는데, 그것이 스냅샷
이라 한다.
플러시 시점에 엔티티
와 스냅샷
을 비교해, 변경된 엔티티를 찾고 UPDATE SQL 을 생성하게 되는 것이다. 굳이 Update(member) 메서드를 생성할 필요가 없다는 것이다 !!
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
// em.update(member) 이런 코드가 있어야 하지 않을까? -> NO !
transaction.commit(); // [트랜잭션] 커밋
실제 객체 대신 프록시 객체
를 로딩해두고, 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러온다.
영속성 컨텍스트의 변경 내용
을 DB 에 반영하는 것 (동기화)
플러시가 발생하면 ?
쓰기 지연 SQL 저장소
에 등록DB
에 전송플러시가 실행돼도 영속성 컨텍스트를 비우지 않음 !
커밋 직전에만 동기화하면 됨.
em.flush()
- 직접 호출
트랜잭션 커밋
- 플러시 자동 호출
JPQL 쿼리 실행
- 플러시 자동 호출
💡 커밋과 플러시
커밋
: 트랜잭션 작업이 성공적으로 끝났고 DB 가 일관된 상태가 있을때 트랜잭션 관리자에게 알리는것플러시
: 영속성 컨텍스트에 있는 엔티티 정보를 DB 에 동기화하는 것즉, 엔티티 매니저가 DB 의 일관된 상태를 말하기 전까진
쓰기 지연 SQL 저장소
에 쿼리를 모아두고 있다가, Flush 를 해서 저장소에 있던 SQL 을 전부 DB 에 적용을 시킨 후 동기화하면, 그제서야 Commit 을 한다.
em.detach(entity)
: 특정 엔티티만 준영속 상태로 전환
em.clear()
: 영속성 컨텍스트를 완전히 초기화
em.close()
: 영속성 컨텍스트 종료
🔗 참고
자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)