JPA

고급 주제와 성능 최적화 1

리승자이 2022. 8. 6. 00:08
728x90

스터디도 시작한지 3개월이 되었다. 시간 참빠른것 같다.

진도가 이제 15장을 둘로 쪼개고 16장 해도 9월에는 종료가 될것이다. 😂

예외처리와 엔티티 그리고 프록시에 대해 정리한다.

예외 처리

image{: text-center}

JPA는 그림과 같이 javax.persistence.PersistenceException의 자식 클래스이다.

그리고 이 예외 클래스는 RuntimeException의 자식이다.

JPA 예외는 모두 uncheck Exception이다.

JPA 표준예외

  • 트랜잭션 롤백을 표시하는 예외
  • 트랜잭션 롤백을 표시하지 않는 예외

서비스 계층에서 데이터 접근 계층의 구현 기술에 직접 의존하는 것은 좋지 않다.
SOLID원칙 생각해보자. 이것이 예외에서도 마찬가지가 된다.

서비스에서 JPA의 예외를 그대로 사용한다면 JPA에게 의존하는것이 된다.

그래서 Spring은 이런 문제를 해결하려고 예외를 추상화해서 제공하였다.

방법

JPA 예외를 스프링 프레임워크에서 제공하는 추상화된 예외로 변경하려면 PersistenceExceptionTranslationPostProcessor를 스프링 빈으로 등록해준다.

@Repository를 사용한 곳에 여기에 예외 변환 AOP를 적용해준다.

@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation {
    return new PersistenceExceptionTranslationPostProcessor();
}

트랜잭션 롤백 시 주의 사항

  • 트랜잭션을 롤백하여 DB의 데이터가 원래대로 복구되지만 영속성 컨텍스트까지 롤백이 된것은 아니다. 그래서 영속성 컨텍스트를 초기화 해준 후 사용해야 한다.
  • OSIV는 영속성 컨텍스트의 범위를 트랜잭션 범위보다 넓게 했기 때문에 자주 발생가능
    • 같은 엔티티에 여러 트랜잭션 발생 가능, 이렇게 사용하면 문제가 야기될 수 있다.
    • 넓게 설정해야한다면, 트랜잭션 롤백 시 영속성 컨텍스트를 초기화한다.

엔티티 비교

저장하는 em.persist()와 Spring data JPA의 findById()로 아이디값을 같은것을 비교하면 저장한 엔티티와 불러온 엔티티는 값만 같은 것이 아니라 인스턴스가 완전히 같다.
같은 트랜잭션 범위에 있으므로 같은 영속성 컨텍스트를 사용하기 때문이다.

  • 동일성
    • == 로 주소값을 따짐
  • 동등성
    • equals()로 값만 비교
  • DB동등성
    • @Id DB 식별자가 같다.

여지껏 내가 했던 테스트가 잘못될 수도 있었다. @Transactional 의 중요성 그리고 영속성 컨텍스트에 대해 계속 생각하며 Junit 테스트코드를 작성했어야 했다.

왜냐하면

@Test
@Transactional
void test() {
    Member member = new Member("홍길동");

    Member findMember = memberRepository.findById(1L).orElseThrow(new Member("테스트"));


    assertThat(member.getId()).isEqualTo(findMember.getId())
}

이렇게 짜주니까 이상적으로 동작했던것이었지 왜냐 ❓

엔티티를 영속화해야 DB 동등성 비교가 가능하다. 근데 만약 @Transactional이 각기 다르게 되어 동작을 할 때엔 이런 비교는 에러를 뱉게 된다.

프록시 심화

프록시는 원본 엔티티를 상속받아서 만들어지므로 엔티티를 사용하는 클라이언트는 엔티티가 프록시인지, 원본 엔티티인지 확인하면서 사용할 필요가 없다.

따라서 원본 엔티티를 사용하다가 지연 로딩으로 프록시로 변경되어도 다른 로직을 건들 필요가 없다.

이렇게하면 프록시를 먼저 조회했기 때문에 어차피 원본 엔티티를 상속받아 만들어진 것이기 때문이다.

영속성 컨텍스트와 프록시

작업 단위로 영속성 컨텍스트가 돌아가기 때문에 프록시로 조회하나 아니면 실제 엔티티를 조회하나

둘다 같은 객체를 반환해야 영속성 컨텍스트가 동일성을 보장하면서 운영할 수가 있다.

예를 들어

Member proxyMember = em.getReference(Member.class, "member");
Member findMember = em.find(Member.class, "member");

여기는 프록시 객체로 영속성 컨텍스트 동일성을 보장함

처음 프록시로 조회된 객체를 바로 반환시켜준다.

Member findMember = em.find(Member.class, "member");
Member proxyMember = em.getReference(Member.class, "member");

여기는 원본 엔티티 객체로 영속성 컨텍스트 동일성 보장.

이런것 까지 꼼꼼하게 생각 안하게 만들어준 JPA다.

클래스 타입비교

상속받아 만들어진 프록시 객체는 원본을 상속받은 객체이기 때문에 instanceof 로 타입을 비교해야 한다.

정리

전체적인 내용을 살펴보면 결국 영속성 컨텍스트가 얼마나 살아있는지에 대해 프록시과 원본 엔티티 동등성을 유지할 수 있는가가 주 내용이다.

작업단위를 잘 생각하고 영속성의 경계선을 파악하면 잘 사용할 수 있을것 같다. 지연로딩에 대해서 너무 간단하게 생각하지말고 부모자식으로 조회할 때의 프록시 객체를 생각하며 개발해야 한다.

728x90