-
영속성 관리 - 내부 동작 방식김영한(인프런 강의)/자바 ORM 표준 JPA 프로그래밍 2020. 9. 29. 12:07반응형
JPA 에서 중요한 것 2가지
1. 객체와 관계형 데이터베이스 매핑하기 (Object Relational Mapping)
2. 영속성 컨텍스트
위 2가지인데 이번 글에서는 영속성 컨텍스트를 볼 것이다.
영속성 컨텍스트를 보기 전 JPA동작 과정을 알아야한다.
특히 엔티티 매니저 팩토리와 엔티티 매니저를 확인해 보자.
영속성 컨텍스트란?
1. JPA를 이해하는데 가장 중요한 용어
2. "엔티티를 영구 저장하는 환경"이라는 뜻
3. EntityManager.persist(entity);
- DB에저장한다는 뜻이 아니다.
- 엔티티를 영속성 컨텍스트에 저장한다는 뜻
엔티티매니저와 영속성 컨텍스트의 매핑 관계
1. J2SE 환경 - 엔티티 매니저와 영속성 컨텍스트가 1:1
- 엔티티 매니저와 영속성 컨텍스트가 1 : 1
2. J2EE, 스프링 프레임워크 같은 컨테이너 환경
- 엔티티 매니저와 영속성 컨텍스트가 N:1
엔티티의 생명주기
1. 비영속 (new/transient)
- 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
- 객체를 생성한 상태
//객체를 생성한 상태(비영속) Member member = new Member(); member.setId("member1"); member.setUsername("회원1");
2. 영속 (managed)
- 영속성 컨텍스트에 관리되는 상태
- 1차 캐쉬에 저장된 상태(JPA가 관리할 수 있는 상태)
- 영속상태가 된다고해도 DB에 저장되는게 아니라 commit을 해야지 DB에 저장이된다.
public class JpaStudyApplication { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); //트랜잭션 시작 tx.begin(); try { //비영속 Member member = new Member(); member.setId(3L); member.setName("비영속"); //영속 em.persist(member); tx.commit(); }catch (Exception e) { tx.rollback(); }finally { em.close(); } emf.close(); } }
3. 준영속 (detached)
- 영속성 컨텍스트에 저장되었다가 분리된 상태
- 아래코드를 보게 된다면 엔티티매니저에 저장된 member를 지움
public class JpaStudyApplication { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); //트랜잭션 시작 tx.begin(); try { //비영속 Member member = new Member(); member.setId(3L); member.setName("비영속"); //영속 em.persist(member); //영속한 컨텍스트를 지운상태 em.detach(member); tx.commit(); }catch (Exception e) { tx.rollback(); }finally { em.close(); } emf.close(); } }
4. 삭제 (removed)
- DB에서 객체를 삭제한 상태
public class JpaStudyApplication { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); //트랜잭션 시작 tx.begin(); try { //비영속 Member member = new Member(); member.setId(3L); member.setName("비영속"); //영속 em.persist(member); // 준영속 (detached) - 영속한 컨텍스트를 지운상태 em.detach(member); //삭제 em.remove(member); tx.commit(); }catch (Exception e) { tx.rollback(); }finally { em.close(); } emf.close(); } }
영속성 컨텍스트의 이점
● 에플리케이션과 DB사이에 중간계층이 존재해서 생기는 이점
1. 1차 캐시
1) 엔티티 조회, 1차 캐시
- 엔티티를 영속한 상태
//엔티티를 생성한 상태(비영속) Member member = new Member(); member.setId("member1"); member.setUsername("회원1"); //엔티티를 영속 em.persist(member);
2) 1차캐시에서 조회
- 1차캐시에 데이터가 존재할 시
- 1차캐시에 저장된 데이터를 조회하여 보여준다.( DB접속 X)
- findMember, findMember2와같이 같은 조건으로 데이터를 가져올 때 캐시에 저장된것을 사용한다.
Member member = new Member(); member.setId(1L); member.setName("jinSeok3"); //1차 캐시에 저장됨 em.persist(member); //1차 캐시에서 조회 Member findMember = em.find(Member.class, 1); Member findMember2 = em.find(Member.class, 1); System.out.println("findMember.getId() : " + findMember.getId()); System.out.println("findMember.getName() : " + findMember.getName()); tx.commit();
3) 데이터베이스에서 조회
- 1차 캐시에 존재하지 않을 시 DB에서 조회
- 아래와같이 findMember, findMember2와같이 같은 조건으로 데이터를 가져올 때 한번만 가져온다. (같은 조건으로 2번이상 조회할 시 1차캐시에서 가져온다.)
Member member = new Member(); member.setId("member1"); member.setUsername("회원1"); //1차 캐시에 저장됨 em.persist(member); //1차 캐시에서 조회 Member findMember = em.find(Member.class, "member1"); Member findMember2 = em.find(Member.class, "member1"); System.out.println("findMember.getId() : " + findMember.getId()); System.out.println("findMember.getName() : " + findMember.getName()); //1차 캐시에 존재하지 않기 때문에 DB에서 조회한다. Member findMember2 = em.find(Member.class, "member2"); tx.commit();
2. 영속 엔티티의 동일성 보장
- 1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공
- 같은 트랜잭션 안에서만 가능
//영속 Member a = em.find(Member.class, "member1"); Member b = em.find(Member.class, "member1"); System.out.println(a == b); //동일성 비교 true
3. 엔티티 등록 시 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
EntityManager em = emf.createEntityManager(); EntityTransaction transaction = em.getTransaction(); //엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다. transaction.begin(); // [트랜잭션] 시작 em.persist(memberA); em.persist(memberB); //여기까지 INSERT SQL을 데이터베이스에 보내지 않는다. //커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다. transaction.commit(); // [트랜잭션] 커밋
- em.persist(memberA); 에서 발생되는일
- em.persist(memberB) 에서 발생되는 일
- transaction.commit() 에서 발생되는 일
- 위의 코드를 실행하게 된다면 아래와 같이 따로 실행이 되는 것을 볼 수 있다. 이는 방지하기 위해서는 JPA옵션중 하나를 추가 해주면 사이즈 크기만큼 모아서 DB에 한번의 네트워크로 보내게 된다.
=> 네트워크를 여러번 타지 않고 한번에 하기 때문에 성능 향상
<property name="hibernate.jdbc.batch_size" value="10"/>
4. 변경 감지(Dirty Checking)
● 엔티티에 데이터가 변경이 된다면 JPA가 감지하여 수정 및 삭제를 하게 된다(commit시에)
- 수정
// 영속 엔티티 조회 Member memberA = em.find(Member.class, "memberA"); // 영속 엔티티 데이터 수정 memberA.setName("hi"); memberA.setId(123); //em.update(member) 이런 코드가 있어야 하지 않을까? transaction.commit(); // [트랜잭션] 커밋
- 삭제
//삭제 대상 엔티티 조회 Member memberA = em.find(Member.class, “memberA"); em.remove(memberA); //엔티티 삭제
5. 지연 로딩(Lazy Loading)
- 객체가 실제 사용할 때 로딩
플러쉬
1. 플러쉬란 무엇인가?
1) 영속성 컨텍스트를 비우지 않음
2) 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화(반영)
3) 트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화 하면 됨
2. 플러쉬 발생 이유
1) 변경 감지
2) 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
3) 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)
3. 영속성 컨텍스트를 플러시하는 방법
○ em.flush() - 직접 호출
○ 트랜잭션 커밋 - 플러시 자동 호출
○ JPQL 쿼리 실행 - 플러시 자동 호출
- 아래 코드는 플레쉬가 자동으로 호출될 때 예제
- 웬만하면 기본값을 사용하자
em.persist(memberA); em.persist(memberB); em.persist(memberC); //중간에 JPQL 실행 query = em.createQuery("select m from Member m", Member.class); List<Member> members= query.getResultList();
○ 플러쉬 옵션
em.setFlushMode(FlushModeType.COMMIT)
FlushModeType.AUTO 커밋이나 쿼리를 실행할 때 플러시 (기본값)
FlushModeType.COMMIT 커밋할 때만 플러시준영속
1. 준영속이란?
1) 영속 -> 준영속
2) 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
3) 영속성 컨텍스트가 제공하는 기능을 사용 못함
2. 준영속 상태로 만드는 방법
1) em.detach(entity)
- 특정 엔티티만 준영속 상태로 전환
3) em.clear()
- 영속성 컨텍스트를 완전히 초기화
3) em.close()
- 영속성 컨텍스트를 종료
참고
JPA는 내부적으로 리플랙션을 사용하기 때문에 동적으로 객체를 생성해야 된다.
그렇기 때문에 기본생성자를 생성해서 사용해야 한다.
@Entity public class Member { @Id private Long id; private String name; public Member() { } public Member(Long id, String name) { this.id = id; this.name = name; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
반응형'김영한(인프런 강의) > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
엔티티 매핑 - 필드와 컬럼 매핑 (0) 2020.10.02 엔티티 매핑 - 데이터베이스 스키마 자동 생성 (0) 2020.09.29 엔티티 매핑 - 객체와 테이블 매핑 (0) 2020.09.29 JPA 세팅 및 기본 실습 (0) 2020.09.28 JPA은무엇인가? (0) 2020.09.28