Reference. 자바 ORM 표준 JPA 프로그래밍
책 목차 및 이전 글
더보기
들어가기 전 JPA 특징, Q&A
1. JPA 소개
3. 영속성 관리
4. 엔티티 매핑
4.1 - 4.3 @Entity, @Table, 다양한 매핑
4.4 - 4.5 데이터베이스 스키마 자동 생성, DDL 생성 기능
5. 연관관계 매핑 기초
6. 다양한 연관관계 매핑
7. 고급매핑
8. 프록시와 연관관계 관리
9. 값 타입
9.3~5 값 타입과 불변 객체, 값 비교, 값 타입 컬렉션
10. 객체지향 쿼리 언어
11. 웹 애플리케이션 제작
11.1 프로젝트 환경설정
11.2 도메인 모델과 테이블 설계
11.3 애플리케이션 구현
12. 스프링 데이터 JPA
12.1~3 스프링 데이터 JPA 소개, 공통 인터페이스 기능
12.5~10 명세, 사용자 정의 리포지토리, Web 확장...
13. 웹 애플리케이션과 영속성 관리
13.3 OSIV
- OSIV(
Open Session In View
)는 영속성 컨텍스트를 뷰까지 열어둔다는 의미- OSIV는 하이버네이트에서 사용하는 용어
JPA는 OEIV(
Open EntityManager In View
) 관례상 OSIV라 부름
- OSIV는 하이버네이트에서 사용하는 용어
JPA는 OEIV(
13.3.1 과거 OSIV: 요청 당 트랜잭션
- OSIV의 핵심은 뷰에서도 지연 로딩이 가능하다록 하는 것
- 가장 단순한 구현 방법은 클라이언트의 요청이 들어오자마자 서블릿 필터나 스프링 인터셉터에서 트랜잭션을 시작하고 요청이 끝날떄 트랜잭션도 종료하는 방법
- 요청 당 트랜잭션(Transaction per Request) 방식의 OSIV
- 트랜잭션을 요청에 시작과 종료에 동작하므로 영속성 컨텍스트도 동일한 범위에서 유지
- 뷰에서도 지연 로딩을 할 수 있으므로 미리 초기화할 필요가 없어짐
요청 당 트랜잭션 방식의 OSIV 문제점
- 컨트롤러나 뷰 같은 프리젠테이션 계층이 엔티티를 변경할 수 있다는 점
class MemberController { public String viewmember(Long id) { Member member = memberService.getMember(id); member.setName("XXX"); //보안상 XXX로 변경 model.addAttribute("member", member); ... } }
- 뷰에 노출할 떄만 이름을 변경하고, 실제 DB에는 반영하고 싶지 않은 경우 문제 발생
- OSIV의 경우 뷰를 렌더링 후 커밋하기 때문에 영속성 컨텍스트가 그때 플러시 실행
- DB에 고객의 이름이 XXX로 변경되는 심각한 문제 발생
- 뷰에 노출할 떄만 이름을 변경하고, 실제 DB에는 반영하고 싶지 않은 경우 문제 발생
문제점 해결을 위해 프리젠테이션 계층에서 엔티티 수정을 막는 방법
- 엔티티를 읽기 전용 인터페이스로 제공
- 읽기 전용 메소드만 제공하는 인터페이스를 프리젠테이션 계층에 제공하는 방법
interface MemberView { public String getName(); } @Entity class Member implements MemberView { ... } class MemberService { public MemberView getMember(id) { return memberRepository.findById(id); } }
- 엔티티 레핑
- 엔티티의 읽기 전용 메소드만 가지고 있는 엔티티를 감싼 객체를 만들고 반환하는 방법
class MemberWrapper { private Member member; public MemberWrapper(Member member) { this.member = member; } //읽기 전용 메소드만 제공 public String getName() { member.getName(); } }
- DTO만 반환
- 가장 전통적인 방법이며 엔티티 대신에 단순히 데이터만 전달하는 객체인 DTO를 반환
- 이 방법은 OSIV를 사용하는 장점을 사용할 수 없고 DTO 클래스를 하나 더 만드는 단점 발생
class MemberDTO { private String name; //Getter, Setter } ... MemberDTO memberDTO = new MemberDTO(); memberDTO.setName(member.getName()); return memberDTO;
- 위에 방식들은 모두 코드량이 상당히 증가하는 단점
- 요청 당 트랜잭션 방식의 OSIV는 이런 문제점들 떄문에 최근에는 거의 사용하지 않음
- 문제점을 보완해서 비즈니스 계층에서만 트랜잭션을 유지하는 방식의 OSIV를 사용
- 스프링 프레임워크가 제공하는 OSIV가 이런 방식
13.3.2 스프링 OSIV: 비즈니스 계층 트랜잭션
스프링 프레임워크가 제공하는 OSIV 라이브러리
- 스프링 프레임워크의 spring-orm.jar는 다양한 OSIV 클래스를 제공
- 서블릿 필터에서 적용할지 스프링 인터셉터에서 적용할지에 따라 원하는 클래스를 선택 사용
- 하이버네이트 OSIV 서블릿 필터:
org.springframework.orm.hibernate4.support.OpenSessionInViewFilter
- 하이버네이트 OSIV 스프링 인터셉터:
org.springframework.orm.hibernate4.support.OpenSessionInViewInterceptor
- JPA OEIV 서블릿 필터:
org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
- JPA OEIV 스프링 인터셉터
org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor
- 하이버네이트 OSIV 서블릿 필터:
스프링 OSIV 분석
- 요청 당 트랜잭션 방식의 OSIV의 문제를 어느 정도 해결 (프리젠테이션 데이터 변경 가능)
- 스프링 프레임워크가 제공하는 OSIV는 "비즈니스 계층에서 트랜잭션을 사용하는 OSIV"
- 스프링 프레임워크가 제공하는 OSIV의 동작 원리
- 클라이언트의 요청이 들어오면 서블릿 필터나 스프링 인터셉터에 영속성 컨텍스트를 생성 이때 트랜잭션은 시작하지 않음
- 서비스 계층에서 @Transactional로 트랜잭션을 시작할 때 1번에 미리 생성해둔 영속성 컨텍스트를 찾아와 트랜잭션을 시작
- 서비스 계층이 끝나면 트랜잭션을 커밋하고 영속성 컨텍스트를 플러시 이때 트랜잭션은 끝내지만 영속성 컨텍스트는 종료하지 않음
- 컨트롤러와 뷰까지 영속성 컨텍스트가 유지되므로 조회한 엔티티 영속상태를 유지
- 서블릿 필터나, 스프링 인터셉터로 요청이 돌아오면 영속성 컨텍스트를 종료 이때 플러시를 호출하지 않고 바로 종료
트랜잭션 없이 읽기
- 영속성 컨텍스트를 통한 모든 변경은 트랜잭션 안에서 처리
- 트랜잭션 없이 엔티티변경 후 플러시하면
TransactionRequiredException
예외 발생
- 트랜잭션 없이 엔티티변경 후 플러시하면
- 엔티티를 변경하지 않고 단순 조회만 하는 경우 트랜잭션 없이 읽기가 가능 (지연 로딩도 조회)
* 스프링이 제공하는 비즈니스 계층 트랜잭션 OSIV의 특징
- 영속성 컨텍스트를 프리젠테이션 계층까지 유지
- 프리젠테이션 계층에는 트랜잭션이 없으므로 엔티티 수정 불가
- 기존 OSIV 단점을 보완(기존은 프리젠테이션에서 엔티티 수정 후 플러시)
- 프리젠테이션 계층에는 트랜잭션이 없지만 트랜잭션 없이 읽기를 사용해서 지연 로딩 가능
- 스프링이 제공하는 OSIV 적용 예제
class MemberController { public String viewMember(Long id) { Member member = memberService.getMember(id); member.setName("XXX"); //보안상 XXX로 변경 model.addAtrribute("member", member); } }
- 스프링의 OSIV의 경우 2가지 이유로 영속성 컨텍스트를 플러시 처리 안함
- 트랜잭션을 사용하는 서비스 계층이 끝날 떄 트랜잭션이 커밋되면서 이미 플러시 처리
스프링이 제공하는 OSIV 서블릿 필터나 인터셉터는 요청이 끝나면 플러시 호출하지 않고
em.close()
로 영속성 컨텍스트를 종료
- 프리젠테이션 계층에서
em.flush()
를 호출해서 강제 플러시해도 트랜잭션 범위 밖이므로 예외가 발생javax.persistence.TransactionRequiredException: no transaction is in progress
- 트랜잭션을 사용하는 서비스 계층이 끝날 떄 트랜잭션이 커밋되면서 이미 플러시 처리
스프링이 제공하는 OSIV 서블릿 필터나 인터셉터는 요청이 끝나면 플러시 호출하지 않고
- 스프링의 OSIV의 경우 2가지 이유로 영속성 컨텍스트를 플러시 처리 안함
스프링 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() { //... 비즈니스 로직 실행 } }
- 실행 순서
- 컨트롤러에서 회원 엔티티를 조회하고 이름을 수정
- biz() 메소드를 실행해서 트랜잭션이 있는 비즈니스 로직 실행
- 트랜잭션 AOP가 동작하면서 영속성 컨텍스트에 트랜잭션을 시작, 그리고 biz() 메소드 실행
- biz() 메소드가 끝나면 트랜잭션 AOP는 영속성 컨텍스트를 플러시, 이 때 변경감지가 동작하면서 회원 엔티티 수정사항을 데이터베이스에 반영
- 문제를 해결하기 위한 단순한 방법은 트랜잭션이 있는 비즈니스 로직 호출 후 엔티티 변경
memberService.biz(); //비즈니스 로직 먼저 실행 Member member = memberService.getMember(id); member.setName("XXX"); //마지막에 엔티티를 수정
- 스프링 OSIV는 같은 영속성 컨텍스트를 여러 트랜잭션이 공유할 수 있으므로 이런 문제 발생
- OSIV를 사용하지 않는 영속성 컨텍스트 전략은 트랜잭션 생명주기와 같으므로 문제 발생 X
13.3.3 OSIV 정리
스프링 OSIV의 특징
- 클라이언트의 요청이 들어올 때 영속성 컨텍스트를 생성해서 요청이 끝날 때까지 유지 따라서 한 번 조회한 엔티티는 요청이 끝날 때까지 영속 상태를 유지
- 엔티티 수정은 트랜잭션이 있는 계층에서만 동작, 트랜잭션이 없는 프리젠테이션 계층은 지연 로딩을 포함해서 조회만 가능
스프링 OSIV의 단점
- OSIV를 적용하면 같은 영속성 컨텍스트를 여러 트랜잭션이 공유할 수 있다는 점을 주의 특히 트랜잭션 롤백 시 주의(15.1.4절 참고)
- 프리젠테이션 계층에서 엔티티를 수정하고나서 비즈니스 로직을 수행하면 엔티티 수정이 발생
- 프리젠테이션 계층에서 지연 로딩에 의한 SQL이 실행, 성능 튜닝시에 확인 부분이 확장
OSIV vs FACADE vs DTO
- 어떤 방법을 사용하든 결국 주영속 상태가 되기 전에 프록시 초기화가 필요
- 엔티티와 거의 비슷한 DTO를 반환하는 방법도 있지만 OSIV를 사용하는 것과 비교해서 코드를 많이 작성해야하는 단점이 발생
OSIV를 사용하는 방법이 최선이 아님
- 복잡한 통계 화면과 같은 경우 JPQL을 작성해서 DTO로 조회하는 것이 효과적
- 수많은 테이블을 보여주어야 하는 관리자 화면도 객체 그래프로 표현하기 어려움
- 복잡한 경우 JPQL로 필요한 데이터만 조회해서 DTO 반환처리가 더 나은 해결책이 될 수 있음
OSIV는 같은 JVM을 벗어난 원격 상황에서 사용 불가
- JSON이나 XML을 생성할 때는 지연 로딩이 사용 가능하지만 원격지인 클라이언트에서 연관된 엔티티를 지연 로딩하는 것은 불가능
- 클라이언트가 필요한 데이터를 모두 JSON으로 생성해서 반환이 필요
- JSON으로 생성한 API는 수정하기 어려운 외부 API와 수정 가능한 내부 API로 구분
- 외부 API: 외부에 노출하며 한번 정의하면 변경 어려움, 서버-클라이언트 동시 수정 어려움
- 예: 타팀과 협업하기 위한 API, 타 기업과 협엽하는 API
- 내부 API: 외부에 노출없이 언제든 변경 가능, 서버-클라이언트 동시 수정 가능
- 예: 같은 프로젝트에 있는 화면을 구성하기 위한 AJAX 호출
- 외부 API: 외부에 노출하며 한번 정의하면 변경 어려움, 서버-클라이언트 동시 수정 어려움
- 엔티티는 자주 변경되므로 JSON 대상 객체로 사용하면 JSON API도 함께 변경 따라서 외부 API는 엔티티를 직접 노출하기보단 DTO로 변환해 노출하는 것이 안전
- 내부 API는 엔티티를 변경해도 클라이언트와 서버를 동시에 수정이 가능하므로 실용적인 관점에서 엔티티를 직접 노출하는 것도 괜찮은 방법
'개발서적 > 자바 ORM 표준 JPA' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 14.1 컬렉션 (0) | 2021.10.25 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 13.4 너무 엄격한 계층 (0) | 2021.09.21 |
[자바 ORM 표준 JPA 프로그래밍] 13.2 준영속 상태와 지연 로딩 (0) | 2021.09.21 |
[자바 ORM 표준 JPA 프로그래밍] 13.1 트랜잭션 범위의 영속성 컨텍스트 (0) | 2021.09.21 |
[자바 ORM 표준 JPA 프로그래밍] 12.5~10 명세, 사용자 정의 리포지토리, Web 확장... (0) | 2021.09.21 |