Colleciton
- 엔티티 필드 중 컬렉션 타입도 프록시의 일종은 컬렉션 래퍼(PersistentBag)로 교체된다.
- final 키워드도 의미가 없다. 컬렉션 래퍼인 프록시로 교체되어야 하기 때문에 사용 안 하는 것이 좋다. 그리고 JPA 철학이 엔터티를 그대로 영속화한다는 개념이기 때문에. final로 선언된 필드가 테이블의 컬럼이라고 생각하면 final 은 더욱 어울리지 않는다.
- JPA에서 지원하는 컬렉션은 Collection, Set, List, Map이 있다.
- Collection: 최상위 컬렉션. 하이버네이트는 중복 허용, 순서 보장 x
- Set: 중복 X, 순서 X
- List: 중복 O, 순서 O
- Map: Key, Value 형태. Map은 복잡한 매핑에 비해 활용도가 떨어지고 다른 컬렉션을 사용해도 충분하다.
고아 객체 Orphan
- JPA에서 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 Orphan 고아라고 부른다.
orphanRemoval
편의 기능: 부모에서 자식과의 연관관계를 끊으면 자식이 DB에서 까지 삭제하는 편의기능. 그리고 부모 엔티티가 삭제되면 자식 엔티티들도 다 같이 삭제된다. (현재 테스트해보니 컬렉션에서 삭제해도 자식 객체들이 삭제가 안 되는 버그가 있다. Cascade와 함께 써야 삭제가 되는 것 같다)
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY, orphanRemoval = true)
private final List<Student> students = new ArrayList<>();
@Test
void 부모_삭제로_고아_삭제2() {
// Given
teamRepository.deleteById(축구팀.getId());
// When
List<Long> 축구팀_학생들_아이디들 = 축구팀.getStudents().stream().map(Student::getId).collect(Collectors.toList());
List<Student> students = studentRepository.findAllById(축구팀_학생들_아이디들);
// Then
assertThat(students).hasSize(0);
}
PersistContext와 트랜잭션
- 트랜잭션에 따라 PersistContext 라이프사이클이 결정된다.
- 트랜잭션 A는 독립된 PersistContext A를 가진다.
- EntityManager A,B가 있더라도, 한 트랜잭션이면 PersistContext가 하나이다.
- EntityManager가 1개가 있고 쓰레드가 2개이고 각각 트랜잭션이 있다면, PersistContext 2개이다.
- 이렇게 만들어진 이유는 트랜잭션을 확실히 보장하기 위함이다. 만약 EntityManager에 따라 PersistContext가 된다면, 여러 EM을 사용하는 비즈니스 로직 같은 경우에 트랜잭션을 보장하기 힘들 것이다.
준영속성 상태
- 트랜잭션이 종료된 엔티티는 영속 상태에서 비영속 상태로 변환된다.
- 비영속 상태에서는 지연 로딩 Fetch Lazy가 작동을 하지 않는다. 영속 상태가 아니라서 PersistContext가 존재하지 않기 때문.
- 트랜잭션이 끝나도 페치만 가능하도록 readOnly만 가능하도록 스프링에서 제공하는 OSIV(Open Session In View) 기능을 제공한다.
- 다른 방법으로는 Controller와 Service Layer 사이에 View에 필요한 페치만 하도록 Facade Layer 를 추가한다. 단점은 Layer를 하나 더 개발 및 관리해야 하기 때문에 큰 규모가 아니면 효율이 떨어진다.
Facade Layer 역할과 특징
- 프리젠테이션 계층과 도메인 모델 계층 간의 논리적 의존성 분리
- 프리젠테이이션 계층에서 필요한 프록시 객체 초기화
- 리포지토리를 직접 호출해서 뷰가 요구하는 엔티티 조회
스프링 OSIV
- 기존 OSIV 는 요청 당 트랜잭션을 생성해서 요청이 끝날 때 트랜잭션도 끝나는 방식이다. 하지만 Controller에서 엔티티 수정이 가능하다는 위험성이 있다.
- 그래서 스프링 프레임워크가 제공하는 OSIV는 Service 레이어까지만 트랜잭션을 해두고, PersistContext는 소멸시키지 않고 남겨둬서 해당 엔티티들은 영속 상태로 유지한다. 이 PersistContext는 ReadOnly로. 엔티티 수정은 트랜잭션에서만 가능하다.
- 주의할 점은 트랜잭션 보다 PersistContext 범위가 넓게 되면, 여러 트랜잭션이 PersistContext를 사용할 수 있게 된다. 그러면 그 중 한 개의 트랜잭션이 롤백 시, PersistContext 가 같이 clear 되는지 확인해야 한다. 다행히 스프링에서는 한 개의 트랜잭션이라도 롤백하면 PersistContext를 clear 하도록 한다.
- 프레젠테이션 계층에서 엔티티를 수정되고 엔티티 그대로 비즈니스 계층으로 그대로 넘어가면 DB에 반영될 위험이 있다.
- 통계와 같은 복잡한 View 는 DTO와 JPQL을 쓰는 것이 좋다.
API 전략
- 외부 API: 외부에 노출. 한 번 정의하면 변경이 어렵다. 서버와 클라이언트를 동시에 수정하기 어렵다. 예시) 타 팀과 협업하기 위한 API, 타 기업과 협업하는 API.
- 내부 변경사항이 외부에 반영되지 않도록(의존성을 낮추도록) DTO를 사용하는 것이 좋다.
- 내부 API: 외부에서 노출하지 않는다. 언제든 변경할 수 있다. 서버와 클라이언트를 동시에 수정할 수 있다. 예시) 같은 프로젝트에 있는 화면을 구성하기 위한 AJAX 호출
- 엔티티와 클라이언트를 함꺼번에 같이 수정할 수 있기 때문에, 엔티티를 직접 노출해도 괜찮다고 생각.
'공부노트 > 스프링' 카테고리의 다른 글
AOP 정리2 (0) | 2022.10.23 |
---|---|
[JPA] N+1 문제 (0) | 2022.08.06 |
AOP / Spring AOP 요약 (0) | 2022.07.16 |
Spring AOP 적용 (0) | 2022.06.25 |
스프링 AOP (0) | 2022.06.19 |