인프런 커뮤니티 질문&답변

빠른 게님의 프로필 이미지

작성한 질문수

자바 ORM 표준 JPA 프로그래밍 - 기본편

@Autowired EntityManager 세션 관리에 대해 질문 드립니다.

작성

·

2.3K

0

안녕하세요 김영한 선생님. 저번에 아크를 JPA에서 매핑 하는 방법에 대해 질문 드렸던 학생입니다.

https://www.inflearn.com/questions/14583

저번 답변은 제가 가지고 있던 의문점을 많이 해소해 주셨습니다.

JPA를 재미있게 공부할 수 있게 해주셔서 다시 감사드립니다.^^

저번 질문에 이어서 질문을 달려다가 다른 주제인것 같아서 새로 질문 드립니다 :D

Spring boot 2.1.7 에서 선생님이 주셨던 코드를 기반으로 JPA를 공부 하던 중 생긴 궁금증을 혼자서 해결하지 못했습니다..

`EntityManager`를 스프링으로부터 주입 받아서 사용하는 경우와,

`EntityManagerFactory.createEntityManager()`로 생성하여 사용하는 경우에 트랜잭션(세션)의 범위(?)에 대한 질문입니다.

모든 FetchType은 LAZY로 설정 했습니다.

`entityManager`는 스프링으로부터 주입 받은 상태 입니다.

void selectPoint() {
String jpql = "SELECT p FROM Point p";
TypedQuery<Point> query = entityManager.createQuery(jpql, Point.class);
final List<Point> resultList = query.getResultList();

System.out.println(" ============================");

for (Point point : resultList) {
final Member member = point.getMember();
System.out.println("member.getMemberId() = " + member.getMemberId());
System.out.println("member.getName()= " + member.getName());
}

예제 코드의 엔티티는 `Point`, `Member` 2개고, 관계는 아래와 같습니다.

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

1. 위 코드를 기반으로 `selectPoint()`를 실행 시키면 `member.getName()` 시점에 select query를 할거라 기대 했지만, `member.getName()` 부분에서

Caused by: org.hibernate.LazyInitializationException: could not initialize proxy - no Session

에러가 발생합니다. 

-------------------------------------------------

void selectPoint() {
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
String jpql = "SELECT p FROM Point p";
TypedQuery<Point> query = entityManager.createQuery(jpql, Point.class);
final List<Point> resultList = query.getResultList();

System.out.println(" ============================");

for (Point point : resultList) {
final Member member = point.getMember();
System.out.println("member.getMemberId() = " + member.getMemberId());
System.out.println("member = " + member.getName());
}
transaction.commit();
}

2. 위 코드에서는 EntityTransaction 으로 직접 관리를 하고 실행 시켜 보면 `entityManager.getTransaction()`에서

Caused by: java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead

에러가 발생합니다.

-------------------------------------------------

3. 그래서 `@Transactional` 어노테이션으로 실행을 시켜 보면 `member.getName()`에서 1번과 같은 에러가 발생합니다.

@Transactional
void selectPoint() {
String jpql = "SELECT p FROM Point p";
TypedQuery<Point> query = entityManager.createQuery(jpql, Point.class);
final List<Point> resultList = query.getResultList();

System.out.println(" ============================");

for (Point point : resultList) {
final Member member = point.getMember();
System.out.println("member.getMemberId() = " + member.getMemberId());
System.out.println("member = " + member.getName());
}
}

-------------------------------------------------

4. 그래서 `EntityManagerFactory`를 주입 받아 아래와 같이 `EntityManager`와 `EntityTransaction`을 직접 관리 해보면

void selectPoint() {
final EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
String jpql = "SELECT p FROM Point p";
TypedQuery<Point> query = entityManager.createQuery(jpql, Point.class);
final List<Point> resultList = query.getResultList();

System.out.println(" ============================");

for (Point point : resultList) {
final Member member = point.getMember();
System.out.println("member.getMemberId() = " + member.getMemberId());
System.out.println("member = " + member.getName());
}
transaction.commit();
}

제가 기대했던 대로 `member.getName()`에서 select query가 실행 됩니다.

-------------------------------------------------

제가 스프링 쓰고 있으니까 2번의 결과는 OK.

4번도 OK.

궁금한 부분은 1, 3번인데

A. entityManager를 스프링으로 주입 받아 쓰는 1번 예제에서 왜 세션이 미리 종료 되는지 궁금합니다.

B. 1. 번 예제에서 트랜잭션의 범위를 제가 지정해주지 않았으니 쿼리 하나 실행하고 알아서 트랜잭션 종료했다고 치면 그나마 OK.

그럼 `@Transactional` 어노테이션으로 범위를 명확히(?) 지정해준 3번의 예제는 왜 `selectPoint()` 함수가 끝나지도 않았는데 트랜잭션이 종료되어 `member.getName()`을 가져오지 못하는 걸까요ㅠㅠ

매 번 질문을 드릴 때 마다, 이제 혼자 해결 해야지 하면서도 답변을 너무 잘해주시니, 마약처럼 쉽게 끊질 못하겠네요.ㅠ

답변 4

3

김영한님의 프로필 이미지
김영한
지식공유자

ㅎㅎㅎ 저도 지금까지 삽질을 많이 했는데, 보람이 있네요 ㅋㅋ

좋은 주말 되세요 :)

3

김영한님의 프로필 이미지
김영한
지식공유자

ㅎㅎ 안녕하세요.

제가 딱! 보니까!

1번은 트랜잭션 없이 읽기만 했기 때문에 영속성 컨텍스트가 없어서 그렇습니다.

3번은 트랜잭션 자체가 실행이 안된 것 같습니다^^;;;

3번에서 selectPoint()를 호출하는 전체 코드를 같이 보여주세요 :)

참고로 스프링은 @Transactional을 실행할 때 프록시 클래스를 만드는데, 이 프록시 클래스는 외부 클래스에서 호출하는 경우만 동작하고, 같은 클래스에서 해당 메서드를 호출하면 동작하지 않습니다. :) 혹시 이 경우면 한번 클래스를 분리해보세요.

2

빠른 게님의 프로필 이미지
빠른 게
질문자

아이고~~ JPA 도사님~ ㅠㅠ

도사님 말씀대로 `selectPoint()`는 같은 클래스에 있는 함수였습니다.

어떻게 이런 문제들을 한번에 딱! 보시고 알아맞추시는지 정말 신통하십니다.ㅠ

1번은 말씀하신대로 영속성 컨텍스트가 없어서 그런것 같습니다. 이 부분은 제가 공부를 소홀히 하여 파악하지 못한것 같습니다..ㅠ

사실 3번도 제가 공부를 소홀히 하지 않았다면 (2권의 모니터 받침대를 더 자세히 살폈으면 어렵지 않게 파악 했을 텐데..) 클래스를 분리하여 실행해보니 생각한대로 잘 돌아 가네요.

도사님 없었으면 JPA 공부 어찌 했을런지 생각만 해도 눈앞이 캄캄합니다.

이제 정말 질문 하지 않도록 공부 열심히 하겠습니다.

다시 한 번 도사님의 신기에 감탄을 하며 감사드립니다ㅠㅠ

부디 좋은 주말 보내시길 바랍니다.

1

2번은 무슨 경우인가요?