Skip to content

15장 고급 주제와 성능 최적화 #14

@LOG-INFO

Description

@LOG-INFO

끄적끄적

  • 이 책을 읽지 않고 직접 부딪혔다면 시간이 많이 들었을 법한 깊은 내용이 많이 나와서 좋았다

  • 예외 처리
    • JPA 예외는 크게 두가지로 나뉜다
      • 트랜잭션 롤백을 표시하는 예외
        • 무조건 롤백됨 (심지어 강제로 커밋하려고 해도)
        • EntityExixstException, EntityNotFoundExcetpion, RollbackException, ...
      • 트랜잭션 롤백을 표시하지 않는 예외
        • 개발자가 롤백할지 커밋할지 선택
        • NoResultException, NonUniqueResultException, QueryTimeoutException, ...
    • Data-Access 계층의 Exception에 의존하지 않기 위해, 추상화된 Spring 예외로 변경된다
      • 다른 것들은 이해가 되는데, JPA 어쩌고 Exception들은 여전히 의존성 갖는거 아닌가..? (e.g. JpaSystemException)

      • PersistentExceptionTranslationPostProcessor를 Bean으로 등록해야 AOP를 적용해서 변환해준다
  • 트랜잭션 롤백
    • 롤백하면 DB의 데이터는 롤백되지만, 영속성 컨텍스트의 객체는 수정 상태로 남아있음
    • 기본적으론 트랜랙션 AOP 종료 시점에 트랜잭션을 롤백하면서 영속성 컨텍스트도 같이 종료되기 때문에 문제 발생하지 않음
    • 하지만 OSIV처럼 영속성 컨텍스트의 범위를 트랜잭션 범위보다 넓어서 여러 트랜잭션이 하나의 영속성 컨텍스트를 사용하면 문제 발생
      • Spring Framework에서는 이런 경우 트랜잭션 롤백 시 영속성 컨텍스트를 초기화해서 문제를 예방함
        • JpaTransactionManager::doRollback()
  • Entity 비교
    • 1차 캐시는 영속성 컨텍스트와 생명주기를 같이 함
    • 같은 영속성 컨텍스트에서 Entity를 조회하면 항상 같은 Entity를 반환함
      • 동등성 비교(equals) 수준이 아니라 정말 주소값이 같은(==) 인스턴스 반환
    • 다른 영속성 컨텍스트에서 Entity를 비교하면 동일성 비교(==)는 실패함
      • 이 때문에 비즈니스 키를 이용한 동등성 비교(equals)를 권장함
  • 프록시 심화
    • 같은 영속성 컨텍스트 안에서는 아래를 통해 동일성 비교를 보장한다
      • Proxy로 먼저 조회된 Entity가 있으면, 다음 조회에서도 Proxy를 반환함
      • 원본 Entity로 먼저 조회된 Entity가 있으면, 다음 조회에서도 원본 Entity 반환 (Proxy 반환할 필요 없음)
    • Proxy 동등성 비교(equals) 시 아래 같은 경우 문제 발생 가능
      • if (this.getClass() != obj.getClass()) return false;
        • Proxy는 원본 Class를 상속받아서 만들어지므로 실패함
        • instanceof로 변경해야 함
      • if(name != null ? !name.equals(member.name) : member.name != null) return false;
        • equals의 경우 보통 필드에 직접 접근하는데, Proxy 객체의 멤버 변수는 null이므로 실패함
        • getter를 사용해야 원본 객체의 값을 프록싱함
    • 다형성을 프록시 조회와 함께 사용하면 instanceof도 실패하거나, 캐스팅 문제 발생 가능
      • Item 타입의 프록시는 실제 원본 클래스가 Book이더라도 Item 타입의 프록시로 생성됨 (당연)
      • 여러 해결 방법이 있는데, 별도 인터페이스 제공하는게 그나마 나을 듯
        • 비지터 패턴 사용방법이 확실해보이긴 하지만, 이해하기 어렵고 유지보수가 힘들어져 현실성이 떨어질 듯
  • N+1 문제
    • 즉시로딩으로 하면 상위 객체 호출 시 즉시 문제 발생
    • 지연로딩으로 하면 상위 객체 호출 시 문제 발생하지 않지만, 하위 객체 foreach 시 뒤늦게 문제 발생
    • 여튼 둘 다 문제 발생. 해결방법은 보통 Fetch Join
    • Hibernate BatchSize를 통해 어느정도 해소는 할 수 있지만 N+1 문제(N/batch size)+1 문제정도로 줄여줄 뿐
    • 추천하는 방법: 지연로딩만 사용하고, 최적화 필요한 곳에서 JPQL + Fetch Join 사용
      • 참고
        • @**ToOne 매핑은 기본적으로 즉시로딩
        • @**ToMany 매핑은 기본적으로 지연로딩
  • 읽기 전용 쿼리의 성능 최적화
    • 메모리 최적화
      • 영속성 컨텍스트는 변경 감지를 위해 Snapshot을 조회해서 메모리를 소모함
      • 스칼라 타입으로 조회하거나 read-only query hint를 사용하면, Snapshot 생성하지 않아 메모리 최적화 가능
    • 속도 최적화
      • read-only transaction을 사용하면, flush 호출을 막아서 속도 최적화 가능
  • 배치 처리
    • 성능이 많이 중요하면 그냥 JDBC 쓰자..!
    • JPA를 쓰면 flush 호출(속도), 1차 캐시(메모리) 등 성능을 저하시키는 요인이 많아 신경써야 하는게 많다.
  • Query Hint
    • 이것도 그냥 native query or JDBC 쓰는게 나아보임
  • 트랜잭션을 지원하는 쓰기 지연과 성능 최적화
    • INSERT SQL 모아서 한 번에 DB로 전송
    • JDBC만 사용하면 코드가 지저분해짐
    • JPA를 쓰면 쉽게 적용 가능 (쓰기 지연 & 변경 감지 덕분에 성능과 개발 편의성 둘 다 잡을 수 있음)
      • DB 테이블 Row에 Lock이 걸리는 시간을 최소화
        • flush 전까지는 DB의 데이터를 실제로 변경하지 않으므로

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions