JAVA/JPA

[JPA]open-session-in-view 를 알아보자

gracelove91 2020. 7. 1. 00:40

Open-In-View 또는 Open-Session-In-View 또는 Open-EntityManager-In-View 란?

  • 관례상 OSIV 라고 한다.
  • true일 경우 영속성 컨텍스트가 트랜잭션 범위를 넘어선 레이어까지 살아있다.
    • Api라면 클라이언트에게 응답될 때까지, View라면 View가 렌더링될 때까지 영속성컨텍스트가 살아있다.
  • false일 경우 트랜잭션을 종료할 때 영속성 컨텍스트 또한 닫힌다.
  • 기본값은 true 이다

코드

간단히 코드로 false와 true일 때 어떤 차이가 있는지 살펴보자.

  • Member.class
/**
 * @author : Eunmo Hong
 * @since : 2020/06/30
 */

@ToString(of = "name")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;

    @Builder
    public Member(String name, Team team) {
        this.name = name;
        this.team = team;
    }


}
  • Team.class
/**
 * @author : Eunmo Hong
 * @since : 2020/06/30
 */

@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class Team {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "team_id")
    private Long id;
    private String teamName;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

    public Team(String teamName) {
        this.teamName = teamName;
    }
}

Member와 Team은 다 대 일 관계이며, 서로 양방향 연관관계를 맺었다.
Lazy 로딩이기 때문에 Member를 찾게된다면 Member 안의 Team은 프록시객체가 된다.
Team의 프로퍼티를 건드려야 비로소 Team 객체가 로딩된다.

  • Inin.class
/**
 * @author : Eunmo Hong
 * @since : 2020/07/01
 */

@RequiredArgsConstructor
@Component
public class Init implements ApplicationRunner {
    private final MemberRepository memberRepository;
    private final TeamRepository teamRepository;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Team teamA = teamRepository.save(new Team("teamA"));
        memberRepository.save(Member.builder()
                .name("member1")
                .team(teamA)
                .build());
    }
}

어플리케이션이 구동되면 실행되는 코드다.
이름이 "teamA" 인 Team 객체를 save 한다.
이름이 "member1" 이며 앞서 save한 Team에 소속된 Member를 Save 한다.
그렇다면 DB 상황은 다음과 같다.

  • MemberController.class
/**
 * @author : Eunmo Hong
 * @since : 2020/06/30
 */

@RequiredArgsConstructor
@RestController
public class MemberController {

    private final MemberService memberService;

    @GetMapping("/members/{name}")
    public MemberDto findMember(@PathVariable String name) {
        Member member = memberService.findByName(name);
        return MemberDto.builder()
                .name(member.getName())
                .teamName(member.getTeam().getTeamName())
                .build();
    }
}

간단한 컨트롤러다.
Query String 으로 들어온 String name 으로 해당하는 Member를 찾고,
MemberDto에 Member의 이름과, Member가 속한 Team의 이름을 Set 해주고 JSON으로 반환한다.
화면엔 이렇게 뜬다.

Controller의 return ...teamName(member.getTeam().getTeamName()) 코드에서
Proxy 객체인 Team의 프로퍼티인 TeamName 을 건드렸기 때문에 Lazy loading 이 됐다. 따라서 결과도 잘 나온다.

하지만 OSIVfalse 로 설정한다면?

  • application.yml
    spring:
    datasource:
      url: jdbc:h2:tcp://localhost/~/jpa-sample
      username: sa
      password:
    jpa:
      open-in-view: false

이와 같이 org.hibernate.LazyInitializationException: could not initialize proxy [kr.gracelove.osivdemo.domain.Team#1] - no Session 이라는 익셉션과 함께 500 에러가 뜨는 걸 볼 수 있다.

이는 영속성컨텍스트가 Transaction 범위 바깥인 Controller에서 Lazy loading 을 시도했기 때문이다.
영속성 컨텍스트가 닫혔다면 Lazy loading 또한 할 수 없다!

정리

  • OSIV가 true
    • 사용자에게 응답 또는 view가 렌더링 될 때까지 영속성컨텍스트를 유지한다.
  • OSIV가 false
    • Transaction이 끝나면 영속성컨텍스트 또한 닫힌다.

너무나도 당연하게 OSIV를 true로 두는 게 좋아보이지만! 단점이 존재한다.
영속성 컨텍스트를 유지한다는 건, DB Connection 또한 계속 가지고 있다는 뜻이다.
실시간 트래픽이 중요한 어플리케이션에서는 DB Connection이 모자를 수 있다. 성능이 중요하다면 OSIV는 false로 설정하자.

전체 코드는 다음 링크에서 볼 수 있다.

https://github.com/gracelove91/jpa-practice/tree/master/osiv-demo

레퍼런스

실전! 스프링 부트와 JPA활용 2편 - 김영한님
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-API%EA%B0%9C%EB%B0%9C-%EC%84%B1%EB%8A%A5%EC%B5%9C%EC%A0%81%ED%99%94/dashboard