JPA주의 사항 및 성능 최적화

  • bulk 연산을 할때는 영속성 컨텍스트의 값을 무시하고 바로 DB에 질의문을 던진다. 때문에 bulk연산 이후 이전에 영속성 컨텍스트에서 관리하는 데이터는 변경되지 않은 상태이다.
    • 변경된 내용을 조회하고자 할경우 EntityManager에서 bulk연산 전에 flush/clear를 수행해줘서 영속성 컨텍스트 내용이 DB에 반영될 수 있도록 하고 영속성 컨텍스트를 비워준 후 다시 조회한다.
    • in spring jpa : @Modifying(clearAutomatically = true)
    • JDBC Template, myBatis 등 직접 쿼리하는 방식과 혼용할 경우 flush, clear를 적절하게 사용해서 영속성 컨텍스트 내 무결성이 깨지지 않도록 한다.
  • 연관관계 설정시 mappedBy는 FK가 없는 Entity쪽에 작성한다.

  • Entity를 response, request객체로 사용하지 말아라

    • 데이터 전송 스펙과 entity는 다를 수 있어 api명세가 실제 화면단과의 커뮤니케이션에 괴리감이 생길 수 있다.

    • entity가 오염된다.

      • entity에 프리젠테이션 계층을 위한 로직이 추가된다.

      • 엔티티의 모든값이 노출된다

      • 응답스펙을 맞추기위한 로직이 추가된다.(양방향 참조시 @JsonIgnore등)

      • 엔티티가 변경되면 API스펙이 변한다.(커뮤니케이션 문제 발생)

      • 추가로 컬렉션을 직접 반환하면 향후 API스펙을 변경하기 어렵다.

        • array를 반환하는 json에 추가 attribute를 추가 해야 하는 경우 등.(아래와 같이 반환하자)

          {
              "count":4, 
              "data":[
                  datas...
              ]
          }
          
    • 별도로 데이터 전송용DTO를 만들어 활용하자

  • update에는 변경감지를 이용하자
    • 데이터를 불러와서 영속성 컨텍스트에 올려놓은 후 데이터를 변경하여 자동으로 update가 되도록 하자
  • XToOne관계의 fetch전략은 LAZY로 설정하자
    • EAGER가 필요한곳만 따로 설정하도록 한다.
    • 필요시 JPQL의 fetch join을 활용하자
      • 일반 join과의 차이점
        • 일반 join : from절에 걸린 대상만 영속성 컨텍스트에 올려주고 나머지는 proxy만을 담는다. 때문에 LAZY로 설정된 entity의 컬럼은 select절에서 다루지 않는다.
        • fetch join : 쿼리상에 있는 모든정보를 실제 조회한다. 실제 select문에서도 컬럼명 지정시 join에 걸린 컬럼들도 함께 지정되는것을 확인할 수 있다.
      • 조회하는 컬럼수가 많을 경우 select new [DTO클래스] from 처럼 new 키워드로 가져오고 싶은 컬럼만 따로 지정해서 가져온다.
  • 1:관계에서 join에 의해 행의 개수가 많아질 경우
    • jpql 의 distinct 키워드 사용
      • 실제 쿼리가 distinct로 나가긴 하지만 조회된 결과가 중복되면 jpa에서 한번더 distinct를 수행해 준다.
      • 이때는 페이징 쿼리가 적용되지 않는다.
        • 페이징 쿼리는 메모리에서 처리된다.(데이터가 많을 경우 치명적)
    • 배치 사이즈 조절
      • XToOne관계 까지만 fetch join으로 조회한다.(쿼리로 페이징 처리 가능)
      • 이 후 LAZY로딩 되는 객체를 조회하면 where-in절이 적용된다.
      • spring.jpa.properties.hibernate.default_batch_fetch_size값을 조절한다.
        • 이 경우 LAZY로딩으로 조회되는 XToMany관계의 entity는 where ~ in 쿼리로 조회된다.(설정 된 값을 기준으로 in 쿼리 내의 조건 개수가 설정된다. DBMS한계로 인해 100~ 1000개 권장.)
        • 항목별로 적용하고 싶을 경우 해당 변수에 @BatchSize(n) 을 설정한다.
    • 별도로 N관계에 있는 entity만 조회해도된다.
      • for문 돌면서.. distinct, where-in 이럴 필요 없이.. 전통적으로!
    • 그래도 N관계의 쿼리가 1에 있는 개수만큼 돌기 때문에 완전히 해결된것은 아니다.
      • N관계에 있는 쿼리는 1에서 조회된 key값 만큼 where-in절에 걸어서 돌려준다.
      • 그럼 2번만 돈다.(1번째 : 본쿼리 ,2번째 : N관계의 where-in쿼리)
      • Map을 사용해서 N관계의 쿼리를 본쿼리 결과에 매핑시켜준다.
  • OSIV(open session in view)
    • spring.jpa.open-in-view=true 로 설정되며 이는 기본값이다.
      • false일 경우 Transaction안에서만 영속성 컨텍스트가 유지된다.
    • Service단에서 데이터를 조회하고 Transaction이 끝나더라도 영속성 컨텍스트는 계속 유지가 된다.
    • 이때문에 DB 커넥션이 반환이 되지 않는다.
      • 이유는 controller단에서 lazy객체를 호출할 수 도 있기 때문인데, view가 렌더링되고 response가 다 보내지면 커넥션이 반환되고 영속성 커넥션도 사라지게 된다.
    • 실시간 처리가 중요한 서비스에서는 장애로 이어질 수 있다.
    • OSIV를 끌 경우
      • LAZY로딩을 Transaction안에서 모두 끝내야 한다.(@Service단)
  • 힌트

    • @QueryHints를 이용해 JPA에서 제공하는 힌트 사용
      • org.hibernate.readOnly등..