SpringBoot+Jpa

OSIV

lby132 2022. 10. 29. 01:41

OSIV란 Open Session In View 

 

jpa가 영속성 컨텍스트를 동작하기 위해서는 데이터베이스와 1:1로 쓰고 있어야한다. 그래서 jpa와 db는 아주 밀접한 관련이있는데 또 데이터베이스 트랜잭션이 시작할때 영속성 컨텍스트가 db connection을 가져온다. 그럼 커넥션을 언제 반납을 해야할까?

여기서 커넥션을 반납하는 시점을 바꾸는게 open-in-view 의 true와 false이다.

open-in-view 가 true인 상태이면(기본이 true이다.) 

@PostMapping("/api/v1/members")
public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) {
    final Long id = memberService.join(member);
    return new CreateMemberResponse(id);
}

join()을 실행하고

@Transactional
public Long join(Member member) {
    validateDuplicateMember(member);
    memberRepository.save(member);
    return member.getId();
}

@Transactional이 걸려있는곳이 호출되면 보통 setAutocommit=false로 트랜잭션이 시작된다.

아무튼 트랜잭션이 시작 되었고 영속성컨텍스트가 db에서 커넥션을 가져왔을것이다. 그런데 save()가 호출될거고 

public void save(Member member) {
    em.persist(member);
}

Repository까지갔다 왔는데도 영속성 컨텍스트는 커넥션을 반납하지 않았다.

spring.jpa.open-in-view: true 인 상태이다.

영속성 컨텍스트의 생존 범위가 요청이들어오고 나갈때까지 살아있다.

이유는 지연 로딩때문인데 지연로딩은 영속성 컨텍스트가 살아있어야 가능하기 때문이다. 영속성 컨텍스트는 기본적으로 데이베이스 커넥션을 유지한다. 그래서 끝까지 커넥션을 물고있는것. 이게 가장큰 장점이다. 그런데 반대로 단점이된다.

이유는 만약 요청이 많을때 대기시간이 길어진다면 그 시간만큼 커넥션을 반환하지 못하고 유지하고 있을것이다. 그럼 나중엔 커넥션이 모자랄 수도있다.

설정을 false로 바꾸면 커넥션은 트랜잭션 범위에서 끝이 난다. 즉 컨트롤러로 되돌아가지 않는다.

false로 설정하고 

@GetMapping("/api/v1/orders")
public List<Order> ordersV1() {
    final List<Order> all = orderRepository.findAllByString(new OrderSearch());
    for (Order order : all) {
        order.getMember().getName();
        order.getDelivery().getAddress();

        final List<OrderItem> orderItems = order.getOrderItems();
        orderItems.stream().forEach(o -> o.getItem().getName());
    }
    return all;
}

얘를 실행해보면 lazyinitializationexception이 난다.

앞에서 말했듯이 false이면 트랜잭션이 시작되면 컨트롤러로 나오기 전에 영속성컨텍스트는 종료가 되는데(트랜잭션 종료 커넥션반납)

order에서 getMember().getName()을 호출하기 때문이다. 원래대로 라면 Lazy로딩이 일어나면서 영속성 컨텍스트가 Member가 1차캐시에 있으면 getMember를 프록시로 가져오고 없으면 db에서라도 꺼내서 가져와야하는데 영속성 컨텍스트는 이미 끝나고 없으니까 lazyinitializationexception이 나왔고 could not initalize proxy라는 문구가 떳다 Member 객체가 no Session이다 라는 문구와 함께.

해결방법은 fetch join을 해주던지 for문 에 있는 부분을 트랜잭션안으로 넣는 방법이 있다. 강의에서는 이런 방식을 썻다.

service패키지 안에 query패키지를 만들어서 서비스로직과 쿼리부분을 분리했다. 그저 분리해서 하는걸 강조한것 다른 방식으로 패키지를 잡는게 좋다고 함.

 

어쨋든 컨트롤러.

 

@GetMapping("/api/v3/orders")
public List<jpabook.jpashop.service.query.OrderDto> ordersV3() {
    return orderQueryService.ordersV3();
}

query패키지에 있는 서비스

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderQueryService {

    private final OrderRepository orderRepository;

    public List<OrderDto> ordersV3() {
        return orderRepository.findAllWithItem().stream().map(OrderDto::new).collect(toList());
    }
}

 

findAllWithItem

public List<Order> findAllWithItem() {
    return em.createQuery(
            "select distinct o from Order o" +
                    " join fetch o.member m" +
                    " join fetch o.delivery d" +
                    " join fetch o.orderItems oi" +
                    " join fetch oi.item i", Order.class)
            .getResultList();
}

이렇게 실행하면 spring.jpa.open-in-view: false인 상태인데도 트랜잭션 안에 있기때문에 오류 없이 잘 동작한다.

'SpringBoot+Jpa' 카테고리의 다른 글

양방향 연관관계 무한루프  (0) 2022.11.28
casecade(영속성 전이)  (0) 2022.11.25
1대 N fetch join 위험성  (0) 2022.10.27
EntityManager의 @Autowired  (0) 2022.10.23
JPA사용시 @Transactional  (0) 2022.10.23