(JPA) could not initialize proxy - no Session (1)
JPA를 사용하다 보면 아래 오류를 빈번하게 마주치게 된다.
could not initialize proxy - no Session
해당 오류의 원인을 Open Session In View와 관련지어 정리해보고자 한다.
또한 영속성 컨텍스트의 시작과 종료 시 JPA 내부의 코드를 디버깅하여 정확히 살펴보고자 한다.
Example
예제는 Member와 Team 엔티티가 존재하고 Member가 Team으로 향하는 단방향 OneToMany 관계이다.
자세한 구조는 아래 접은 글을 참고하시길 바란다.
Diagram
Member
@Entity
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "member")
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long id;
@Column(name = "name")
private String name;
@OneToMany(cascade = CascadeType.PERSIST)
@JoinColumn(name = "member_id")
private Set<Team> teams;
}
Team
@Entity
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "team")
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "team_id")
private Long id;
@Column(name = "team_name")
private String name;
}
Controller
@GetMapping("team/{id}")
public Set<Team> getTeam(@PathVariable Long id) {
Set<Team> teams = teamService.getTeam(id);
return teams;
}
Service
public Set<Team> getTeam(Long id) {
Member member = memberRepository.findById(id)
.orElseThrow();
Set<Team> teams = member.getTeams();
return teams;
}
해당 스켈레톤 코드를 바탕으로 OSIV를 적용하였을 때와 적용하였을 때를 구분하여 살펴보자!
Open Session In View를 적용하지 않았을 때
Configuration
open-in-view: false
Request
### Team 조회
GET http://localhost:8080/osiv/team/1
Accept: application/json
해당 요청을 진행하면 아래 에러가 발생한다.
Error
2022-11-16 21:42:27.300 WARN 19985 --- [nio-8080-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: co
m.example.blog.osiv.entity.Member.teams,
could not initialize proxy - no Session; nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: co
m.example.blog.osiv.entity.Member.teams,
could not initialize proxy - no Session]
이유가 무엇일까?
이유는 Service 레이어에서 시작되었던 영속성 컨텍스트의 라이프 사이클이 Controller 레이어까지 이어지지 않았기 때문이다.
Open Session In View 가 설정되지 않으면 트랜잭션 시작 시점에 영속성 컨텍스트가 시작되어 트랜잭션이 종료되는 시점에 영속성 컨텍스트가 소멸되게 된다.
그렇기 때문에 Service 레이어에서 리턴 받은 값을 Controller에서 조회하는 과정에서 영속성 컨텍스트가 끊겼기 때문에 값을 가져올 수 없어 오류가 발생하는 것이다.
Open Session In View를 적용하였을 때
Configuration
open-in-view: true
Request
### Team 조회
GET http://localhost:8080/osiv/team/1
Accept: application/json
해당 요청을 진행하면 위와 달리 아래 에러가 발생하지 않는다.
이유가 무엇일까?
컨텍스트의 라이프 사이클이 요청 종료시점까지 소멸되지 않았기 때문이다.
Open Session In View 가 설정되면 컨트롤러 요청 시점에 영속성 컨텍스트가 시작되어 트랜잭션 종료시점에 영속성 컨텍스트가 소멸하지 않는다.
그렇기 때문에 Service 레이어에서 리턴 받은 값을 Controller에서 조회하는 과정에서 영속성 컨텍스트가 유지되기 때문에 값을 가져올 수 수 있어 위와 달리 에러가 발생하지 않는 것이다.
정리
OSIV 에 따라서 영속성 컨텍스트의 라이프 사이클이 달라진다.
OSIV 가 적용되면 요청시점 (DispatcherServlet 이후의 과정) 부터 요청 종료까지 영속성 컨텍스트의 라이프 사이클이 유지된다.
반면 OSIV 가 적용되지 않으면 영속성 컨텍스트의 라이프 사이클은 트랜잭션의 라이프 사이클과 함께한다.
그렇기 때문에 다음 레이어 까지 트랜잭션이 이어지지 않으면 영속성 컨텍스트가 종료되어
could not initialize proxy - no Session
에러를 마주하게 된다.
다음 장에서는 영속성 컨텍스트의 생성 시점과 종료시점을 내부 코드를 통해 살펴보도록하자!
https://jwdeveloper.tistory.com/311
Code Link
https://github.com/mike6321/Blog/commit/106556c6d5350180303cf1e0b23d3ce2393b5478
https://github.com/mike6321/Blog/commit/4ba38e67e583daec37dfe76685068fd70c3165f0
https://github.com/mike6321/Blog/commit/2397e82577e8034c005bb141442d3449c41f2118
https://github.com/mike6321/Blog/commit/4e60747a15d20be750060b4ccd1b2ea7f01650c7