엔티티의 생명주기
비영속(new/reansient)
- 영속성 컨텍스트와 전혀 관계가 없는 상태
영속(managed)
- 영속성 컨텍스트에 저장된 상태
준영속(detached)
- 영속성 컨텍스트에 저장되었다가 분리된 상태
삭제(removed)
- 삭제된 상태
영속
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);
비영속
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
준영속 , 삭제
//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
//객체를 삭제한 상태(삭제)
em.remove(member);
영속성 컨텍스트가 존재하는 이유는 무엇일까?
영속성 컨텍스트의 이점
- 1차 캐시
- 동일성(identity) 보장
- 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
- 변경 감지(Dirty Checking)
- 지연 로딩(Lazy Loading)
1차 캐시
em.persist(member);
- 1차 캐시에 저장
Member findMember = em.find(Member.class , "member");
- 1차 캐시 확인 있다면 DB 조회를 하지않고 캐시에서 데이터를 가져온다.
Member findMember2 = em.find(Member.class , "member2");
- 1차 캐시에 없다면 DB에 가서 조회한다. 그 후에 데이터를 1차캐시에 저장 후 반환
트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
em.persist(memberA);
1. insert SQL(memberA 데이터) 생성해서 쓰기 지연 SQL 저장소에 쿼리를 저장해놓는다.
2. 1차 캐시에 저장한다.
em.persist(memberB);
1. insert SQL(memberB 데이터) 생성해서 쓰기 지연 SQL 저장소에 쿼리를 저장해놓는다.
2. 1차 캐시에 저장한다.
transaction.commit();
1. flush() - 쓰기 지연 SQL 저장소의 내용을 insert SQL (memberA , memberB 데이터) DB에 전달
2. DB commit();
영속 엔티티의 동일성 보장
Member a = em.find(Member.class , "member1");
Member b = em.find(Member.class , "member1");
System.out.println(a == b);
/*
동일성 비교 true 왜냐 내부에 1차캐시가 존재하기 때문에
같은 래퍼런스 반환
*/
1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공
변경 감지(Dirty Checking)
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) 라는 코드가 있어야 하지 않을까?
trasaction.commit();
영속성 컨텍스트에 스냅샷을 떠두고 비교해서 바뀐부분을 update 쿼리로 만들어 DB에 전달한다.
왜 update 코드가 없어도 변경을 가능하게 만들어놨을까? 그건 자바의 사상때문이다.
public class Test2 {
private String name;
private int age;
public Test2(String name ,int age){
this.name = name;
this.age = age;
}
public static void main(String[] args) {
Test2 t1 = new Test2("a" , 1);
Test2 t2 = new Test2("b" , 2);
Test2 t3 = new Test2("c" , 3);
List<Test2> list = new ArrayList<Test2>();
list.add(t1);
list.add(t2);
list.add(t3);
for (Test2 test2 : list)
System.out.println(test2.name + "/ " + test2.age);
//값 변경
t2.age=123;
for (Test2 test2 : list)
System.out.println(test2.name + "/ " + test2.age);
}
}
t2.age 값을 변경하고 다시 list를 조회해보니 자동으로 변경되었다.
플러쉬
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영
- 변경 감지
- 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(등록 , 수정 , 삭제 쿼리)
영속성 컨텍스트를 플러시하는 방법
- em.flush() - 직접 호출
- 트랜잭션 Commit - 자동 호출
- JPQL 쿼리 실행 - 자동 호출
플러시는
영속성 컨텍스트를 비우지 않는다.
영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화가 목적이다.
트랜잭션이라는 작업 단위가 중요하다 - > 커밋 직전에만 동기화 하면 된다.
준영속 상태로 만드는 방법
- em.detach(entity) -> 특정 엔티티만 준영속 상태로 전환
- em.clear() -> 영속성 컨텍스트를 완전히 초기화
- em.close() -> 영속성 컨텍스트를 종료
Member를 조회할 때 Team도 함께 조회해야 할까?
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(Fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
..
}
LAZY로 걸면 Member를 조회하면 member만 조회가 된다.
어떤게 이렇식으로 진행되는가?
지연 로딩 LAZY을 사용해서 프록시로 조회
Member member = em.find(Member.class , 1L);
Team team = member.getTeam();
team.getName(); // 실제 team을 사용하는 시점에 초기화(DB 조회)
- 가급적이면 지연 로딩을 사용해라
- 즉시로딩을 적용하면 예상하지 못한 SQL이 발생한다.
- 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
지연로딩을 사용하려면 영속성 컨텍스트에서 관리되어야 한다.
그렇지 않다면 LazyInitiallizationException 의 예외가 발생한다.
'JPA' 카테고리의 다른 글
Spring Data JPA & QueryDSL (0) | 2021.01.07 |
---|---|
JPA 사용시 주의사항 정리 (0) | 2020.08.22 |
연관관계 매핑 (0) | 2020.05.25 |
JPA 매핑 어노테이션 (0) | 2020.05.24 |
이제는 Mybatis 에서 JPA로 넘어가자 (0) | 2020.04.17 |