OSIV
OSIV(Open Session In View)는 영속성 컨텍스트를 뷰까지 열어둔다는 의미이다
영속성 컨텍스트가 살아있으면 엔티티는 영속 상태로 유지되고 뷰에서도 지연 로딩을 사용할 수 있다
과거 OSIV: 요청 당 트랜잭션
클라이언트의 요청이 들어오면 서블릿 필터나 스프링 인터셉터에서 트랜잭션을 사작하고 요청이 끝날 때 트랜잭션도 끝내는 방식을 요청 당 트랜잭션 방식의 OSIV라고 한다
영속성 컨텍스트가 처음부터 끝까지 살아있으므로 조회한 엔티티도 영속 상태를 유지한다
뷰에서도 지연 로딩이 가능하므로 엔티티를 미리 초기화할 필요가 없다
요청 당 트랜잭션 방식의 OSIV 문제점
프레젠테이션 계층에서 엔티티를 변경할 수 있어 뷰에 노출할 때만 변경하고 싶은 데이터가 데이터베이스에 까지 영향을 줄 수 있다
이를 해결하기 위해서는 프레젠테이션 계층에서 엔티티를 수정하지 못하도록 막아야 한다
프레젠테이션 계층에서 엔티티를 수정하지 못하도록 막는 방법
1. 엔티티를 읽기 전용 인터페이스로 제공
엔티티를 직접 노출하는 대신에 읽기 전용 메소드만 제공하는 인터페이스를 프레젠테이션 계층에 제공한다
interface MemberView {
public String getName();
}
@Entity
class Member implements MemberView {
...
}
class MemberService {
public MemberView getMember(id) {
return memberRepository.findById(id);
}
}
읽기 전용 인터페이스의 메소드만 사용할 수 있고 엔티티를 수정할 수 없다
2. 엔티티 래핑
읽기 전용 메소드만 가지고 있는 엔티티를 감싼 객체를 만들고 이를 프레젠테이션 계층에 반환하는 방법이다
class MemberWrapper {
private Member member;
public MemberWrapper(member) {
this.member = member;
}
public String getName() {
member.getName();
}
}
3. DTO 반환
프레젠테이션 계층에 엔티티 대신 단순히 데이터만 전달하는 객체인 DTO를 생성해서 반환하는 방법으로 가장 전통적인 방법이다
OSIV를 사용하는 장점을 살릴 수 없고 엔티티를 거의 복사한 듯한 DTO 클래스도 하나 더 만들어야 한다
class MemberDTO {
private String name;
//Getter, Setter
}
...
MemberDTO.memberDTO = new MemberDTO();
memberDTO.setName(member.getName());
return memberDTO;
Member 엔티티와 거의 비슷한 MemberDTO를 만들고 엔티티의 값을 여기 채워서 반환한다
세 가지 방법 모두 코드량이 상당히 증가한다는 단점이 있다
최근에는 거의 사용하지 않는 방법이다
스프링 OSIV: 비즈니스 계층 트랜잭션
스프링 프레임워크의 spring-orm.jar는 다양한 OSIV 클래스를 제공한다
OSIV를 서블릿 필터에서 적용할지 스프링 인터셉터에서 적용할지에 따라 원하는 클래스를 선택해 사용하면 된다
- 하이버네이트 OSIV 서블릿 필터
- 하이버네이트 OSIV 스프링 인터셉터
- JPA OEIV 서블릿 필터
- JPA OEIV 스프링 인터셉터
스프링 OSIV 분석
스프링 프레임워크에서 제공하는 OSIV는 프레젠테이션 계층에서 데이터를 변경할 수 있다는 문제를 어느 정도 해결한다
스프링 프레임워크의 OSIV는 OSIV를 사용하기는 하지만 트랜잭션은 비즈니스 계층에서만 사용한다는 뜻이다
동작 원리
- 클라이언트의 요청이 들어오면 서블릿 필터나 스프링 인터셉터에서 영속성 컨텍스트를 생성한다 이때 트랜잭션은 시작하지 않는다
- 서비스 계층에서 @Transactional로 트랜잭션을 시작할 때 1번에서 미리 생성해둔 영속성 컨텍스를 찾아와 트랜잭션을 시작한다
- 서비스 계층이 끝나면 트랜잭션을 커밋하고 영속성 컨테스트를 플러시한다 이때 영속성 컨텍스트는 종료하지 않는다
- 컨트롤러와 뷰까지 영속성 컨텍스트가 유지되므로 조회한 엔티티는 영속 상태를 유지한다
- 서블릿 필터나 스프링 인터셉터로 요청이 돌아오면 영속성 컨텍스트를 종료한다 이때 플러시를 호출하지 않고 바로 종료한다 (바뀐 내용 반영X)
트랜잭션 없이 읽기
영속성 컨텍스트를 통한 모든 변경은 트랜잭션 안에서 이루어져야 하고 그렇지 않으면 예외가 발생한다
엔티티를 변경하지 않고 단순히 조회만 할 때는 트랜잭션이 없어도 되는데 이를 트랜잭션 없이 읽기라고 한다
프록시를 초기화하는 지연 로딩도 조회 기능이므로 트랜잭션 없이 읽기가 가능하다
스프링이 제공하는 OSIV 기능을 사용하면 프리젠테이션 계층에서는 트랜잭션이 없으므로 엔티티를 수정할 수 없지만 트랜잭션 없이 읽기를 사용해서 지연 로딩은 가능하다
기존의 OSIV의 단점을 보완한다
스프링 OSIV 주의사항
프리젠테이션 계층에서 엔티티를 수정한 직후에 트랜잭션을 시작하는 서비스 계층을 호출하는 경우 문제가 된다
class MemberController {
public String viewMember(Long id) {
Member member = memberService.getMember(id);
member.setName("XXX");
memberService.biz();
return "view";
}
}
class MemberService {
@Transactional
public void biz() {
//비즈니스 로직 실행
}
}
- 컨트롤러에서 회원 엔티티를 조회하고 이름을 수정했다
- 트랜잭션이 있는 비즈니스 로직을 실행했다
- 트랜잭션 AOP가 동작하면서 영속성 컨텍스트에 트랜잭션을 시작하고, 비즈니스 로직을 실행한다
- 비즈니스 로직이 끝나면 AOP는 트랜잭션을 커밋하고 영속성 컨텍스트를 플러시한다 이때 변경 감지가 동작하면서 회원 엔티티의 이름 수정 또한 데이터베이스에 반영된다
비즈니스 로직을 모두 호출한 뒤 엔티티를 변경하면 문제를 해결할 수 있다
스프링 OSIV는 같은 영속성 컨텍스트를 여러 트랜잭션이 공유할 수 있으므로 이런 문제가 발생한다
OSIV 정리
OSIV를 사용하는 방법이 만능은 아니다
OSIV를 사용하면 화면을 출력할 때 엔티티를 유지하면서 객체 그래프를 마음껏 탐색할 수 있다
하지만 복잡한 화면을 구성할 때는 이 방법이 효과적이지 않을 수 있다
JPQL로 필요한 데이터만 조회해서 DTO로 반환하는 것이 더 나은 해결책일 수 있다
OSIV는 같은 JVM을 벗어난 원격 상황에서는 사용할 수 없다
원격지인 클라이언트에서 연관된 엔티티를 지연 로딩하는 것은 불가능하므로 클라이언트가 필요한 데이터를 모두 JSON으로 생성해서 반환해야 한다
이때 외부 API는 엔티티를 직접 노출하기보다는 엔티티를 변경해도 완충 역할을 할 수 있는 DTO로 변환해서 노출하는 것이 안전하다
외부 API는 한번 정의하면 수정하기 어렵기 때문이다
'스터디 > JPA' 카테고리의 다른 글
컬렉션과 부가 기능 - (1) (0) | 2025.03.27 |
---|---|
웹 애플리케이션과 영속성 관리 - (1) (0) | 2025.03.20 |
스프링 데이터 JPA - (2) (0) | 2025.02.20 |
스프링 데이터 JPA - (1) (0) | 2025.02.20 |
객체지향 쿼리 언어 - (7) (1) | 2025.02.19 |