오늘이군
JPA 영속성 컨텍스트 본문
※ 추천 글
https://www.youtube.com/watch?v=xqEVS8LzxZM
http://yellowh.tistory.com/119
1. 난해하다. 하지만 알아야 하는 이유
가끔 하다보면 저장이 안되는 경우가 발생할 수 있다. 제대로 이해하지 못하면..
2. 영속성 컨텍스트(persistence context) - 엔티티를 저장하는 논리적인 저장공간
가. 비영속
Member member = new Member();
member.setId(id);
member.setUsername("kic1");
나. 영속
// 객체를 저장한 상태(영속) - DB에 저장된 상태는 아니다.
em.persist(member);
다. 준영속 : 영속상태에서 분리
em.detach(member); // member 엔티티만 준영속상태로 전환
em.clear(); // 영속성 컨텍스트 초기화 (쓸일은 없다고 보면 됨)
em.close(); // 영속성 컨텍스트 종료
@Trasactional 이 끝나는 순간 지연로딩을 못 함, 식별자 값은 존재 함
em.merge(member); // 다시 영속상태로 변경
라. 삭제 : 객체를 삭제한 상태
em.remove(member);
3. 영속성 컨텍스트의 이점
가. 1차 캐시
em.persist(member);
// 1차 캐시에서 조회
Member a = em.find(Member.class, "member1");
나. 동일성(identity) 보장
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
print(a == b);
// 결과 : true - Collection 에서 I/O 하는 것과 동일, ORM 이 객체지향스럽게 지원해 줌
다. 트랜잭션을 지원하는 쓰기지연 (transactional write-behind)
// 객체를 저장한 상태(영속) - DB에 저장된 상태는 아니다.
em.persist(memberA);
em.persist(memberB);
// 커밋하는 순간 데이터베이스에 INSERT SQL 을 보낸다. 두개가 한꺼번에 똭~~
// 즉, BATCH SQL 이 가능 함
transaction.commit();
라. 변경 감지 (Dirty checking)
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "member1");
// 영속 엔티티 데이터 수정
memberA.setUsername("tistory");
memberA.setAge(27);
// ----- update 가 없다 -----
transaction.commit();
1) 영속 컨텍스트에 있는 것이 변경 될 때 스냅샷을 떠 놓는다.
2) 엔티티가 변경이 되면 원본과 스냅샷을 비교하여 쓰기지연 SQL 저장소로 SQL 을 만들어 저장한다.
3) commit 이 일어날 때 SQL 을 실행시킨다.
마. 지연 로딩 (Lazy Loading)
엔티티를 조회 할 때 연관된 엔티티를 실제 사용할 때 조회한다.
cf. 즉시로딩 : 엔티티를 조회 할 때 연관된 엔티티도 함께 조회한다.
4. 플러시 : 영속성 컨텍스트의 변경내용을 데이터베이스에 반영
가. 영속성 컨텍스트를 플러시 하는 방법
1) em.flush()
2) 트랜잭션 커밋
3) JPQL 쿼리 실행 : JPQL 에서 영속상태인 객체를 사용 할 수 있으므로 JPQL 쿼리 실행 전에 flush() 됨
FlushModeType.COMMIT 을 하면 flush 안 될 수 있지만 보통(AUTO) 커밋이나 쿼리 실행시 플러시 하게 사용한다.
나. 플러시가 발생하면
1) 변경 감지 (Dirty checking)
2) 수정된 엔티티 쓰기지연 SQL 저장소에 등록
3) 쓰기지연 SQL 을 데이터베이스에 전송
※ 영속성 컨텍스트를 비우진 않는다
※ 트랜잭션이라는 작업 단위가 중요 > 커밋 직전에만 동기화 하면 됨
5. J2EE, 스프링 컨테이너에서 영속성 컨텍스트 동작 원리
가. 트랜잭션이 시작되면 영속성 컨텍스트가 만들어지고, 트랜잭션이 끝나면 영속성 컨텍스트를 삭제한다. (생존 범위가 같음)
나. 같은 트랜잭션 안에서는 항상 같은 영속성 컨텍스트에 접근
class HelloService {
@Transactional
public Member logic() {
repository1.hello();
Member member = repository2.findMember();
return member;
}
}
@Repository
Class Repository1 {
@PersistenceContext
EntityManager em;
}
@Repository
Class Repository2 {
@PersistenceContext
EntityManager em;
}
Repository1 과 Repository2 는 같은 영속성 컨텍스트를 바라본다. (cf. 트랜잭션이 다르면 다른 영속성 컨텍스트를 사용 함)
6. 트랜잭션 범위의 영속성 컨텍스트 약점
가. 지연로딩이 안 됨
class HelloController {
public void hello() {
// 반환된 member 엔티티는 준영속 상태이다. >> member.getTeam() 와 같은 지연 로딩 못함
Member member = helloService.logic();
}
}
나. 해결방안
1) 즉시로딩
해결방안이지만 쓰면 안되는 것
사용하지 않는 엔티티가 로딩 되며, N+1의 문제가 발생한다.
2) 페치 조인
repository.findOrderWithMember() 와 같이 order 와 member 같이 조인
프리젠테이션 계층과 데이터접근 계층간 의존관계가 증가하므로 적절히....
3) 강제로 초기화
권장하지 않음
프록시를 초기화 하는 역할을 서비스계층이 담당
4) Facade 계층 추가
사람이 할 짓이 아님... 다 위임.. 맥이 포카리 스웨트를 먹어버림 ㅋㅋ
결론) 스프링 OSIV(Open Session In View) 를 쓰면 된다.
주의) 중간에 비즈니스로직을 호출하면 안 된다.
class MemberController {
public String viewMember(Long id) {
Member member = memberService.getMember(id);
member.setName("XXX");
memberService.biz(); // 비즈니스로직 : 끝날 때 commit 이 되어 값이 변경 된다.
}
}
비즈니스 로직 다 수행 한 뒤 그 결과를 조회하는 순서로 진행 할 것
다. DB 의 데이터는 롤백되지만 객체는 롤백되지 않는다.
1) 영속성 컨텍스트의 범위를 트랜잭션의 범위보다 넓게 설정하면 트랜잭션 롤백시 영속성 컨텍스트 초기화
가) OSIV 미사용 : 트랜잭션 종료 시 영속성 컨텍스트 종료 됨. 이슈 없음
나) OSIV 사용 : 롤백 시 영속성 컨텍스트 초기화
스프링 프레임워크의 해결책...
우리는 가져다 쓰면 된다~~~~
'삶.. > 프로그래밍' 카테고리의 다른 글
Business Process Model and Notation (BPMN) (0) | 2017.06.08 |
---|---|
Use Case Diagram (0) | 2017.06.08 |
JPA 개요 (0) | 2017.05.23 |
스프링 삼각형 - PSA (0) | 2017.05.16 |
스프링 삼각형 - AOP (0) | 2017.05.16 |