EntityManager 생성 시점
•
트랜잭션 시작 시
•
JpaTransactionManager
◦
doBegin()
▪
createEntityManagerForTransaction() 으로 새로운 EM 생성 요청
◦
createEntityManagerForTransaction()
▪
obtainEntityManagerFactory() 로 EMF 획득(필드로 가지고 있음)
▪
EntityManagerFactoryInfo 의 createNativeEntityManager() 호출
•
AbstractEntityManagerFactoryBean(EntityManagerFactoryInfo 추상 구현체)
◦
createNativeEntityManager()
▪
SessionFactoryImpl 의 createEntityManager() 호출
•
SessionFactoryImpl
◦
createEntityManager()
▪
openSession() 호출
◦
openSession()
▪
new SessionImpl(sessionFactory, this);
•
SessionImpl
◦
생성자
▪
영속성 컨텍스트, 액션 큐 등 생성
EntityManager 라이프사이클
•
open-in-view 가 true 라면
◦
DispatcherServlet 의 doDispatch 메서드에서
◦
ha.handle() 한 뒤(실제 컨트롤러 부르는 메서드)
◦
processDispatchResult() 메서드를 호출하여 close 를 한다.
•
false 라면
◦
APTM 의 commit() 메서드 에서 processCommit() 호출
◦
processCommit() 에서 cleanupAfterCompletion() 호출
◦
cleanupAfterCompletion() 에서 JTM 의 doCleanupAfterCompletion() 호출
◦
JTM 의 doCleanupAfterCompletion() 에서 EntityManagerFactoryUtils 의 closeEntityManager() 메서드를 호출하여 close
•
그렇다면 이 둘의 차이는 어디서 발생하는가?
◦
일단 true 면 HandlerInterceptor 로 WebRequestHandlerInterceptorAdapter 가 추가로 등록된다.
◦
여기서 close 를 한다.
•
그렇다면 true 일 때는 왜 APTM 의 commit 메서드에서 processCommit() 을 호출하지 않는가?
◦
APTM 의 cleanupAfterCompletion 메서드에서 JTM 의 doCleanupAfterCompletion() 을 호출하기 위해서는 if 분기문의 status.isNewTransaction() 이 true 가 되어야 하는데 여기서 통과하지 못했다.
◦
즉 Service 계층의 Transactional 어노테이션으로 트랜잭션을 열기 전 이미 트랜잭션이 열려 있는 상태인 것.
•
그랬다. 이미 트랜잭션을 열어버린 것이다. 그럼 트랜잭션을 연 가장 유력한 용의자는 WebRequestHandlerInterceptorAdapter 인 것 같았다. interceptor 라서 preHandle 이 있다. 여길 디버그 찍어보았다.
◦
DispatcherServlet 에서 핸들러와 핸들러어댑터를 찾고 핸들러어댑터에 handle 을 호출하기 전 preHandle 로 인터셉터를 호출한다.
◦
WebRequestHandlerInterceptorAdapter 의 preHandle 에서 필드로 등록된 requestInterceptor 의 preHandle 을 요청한다.
◦
등록된 requestInterceptor 는 OpenEntityManagerInViewInterceptor 이고 이 객체의 preHandle 메서드에서 EMF 를 얻은 뒤 createEntityManager 로 EM 을 생성한다.
◦
이때 커넥션은 빌려가지 않음! 단순히 세션을 열과 em 을 생성할 뿐
OSIV 의 기능
•
true 일 때
◦
Lazy 일 때
▪
프록시로 있는 객체 조회 가능(쿼리 발생)
▪
프록시로 있는 객체 변경 불가(더티 체킹 불가능) 하지만 에러 X
•
false 일 때
◦
Lazy 일 때
▪
프록시로 있는 객체 조회 시 LazyInitializationException: could not initialize proxy [com.example.tosstemp.Team#1] - no Session] with root cause 에러 발생
▪
변경도 당연히 불가
결론
•
true 일 때는 트랜잭셔널 어노테이션 바깥에서도 em이 열려있다. 하지만 준영속상태이다. 그래서 조회는 가능하다. 하지만 트랜잭셔널 어노테이션 바깥이기때문에 더티체킹을 하지 않는다. 그래서 변경점에 대한 업데이트를 하지 않는다.
•
그 이유는 트랜잭셔널 어노테이션으로 세션을 연 것이 아니고 냅다 interceptor 에서 세션을 생성했기 때문이다.
•
false 일 때는 커넥션, 세션 모두 닫혀있다. 그래서 레이지 로딩 조회도 되지 않는다.
•
EM 생성 → 커넥션 획득 → 트랜잭션 생성 순
◦
setAutoCommit 하면 커넥션을 뒤에서 획득하는데 이 때는?
▪
ConnectionHandle 을 생성해서 커넥션을 실제로 얻는 것과는 다르다.
▪
ConnectionHandle 생성은 세션만으로 가능하다.
▪
그래서 트랜잭션 begin 호출 시 TransactionStatus 만 ACTIVE 로 바꾼다! 실제 커넥션을 획득하지 않고!