[JPA] JPA Study 3

Simple·2022년 3월 19일
0

JPA

목록 보기
3/6
  • @Valid 했을 때 에러나면 어떤 식으로 결과값 보여줄지 → 나중에 적용해보자

  • 엔티티를 손대서 API 스펙이 달라지면 문제다. (@NotEmpty를 name에 할당했는데 name→username 으로 바꾸면 api스펙이 달라진다.) → 해결: DTO를 만든다.

    • 또 다른 문제, DTO를 만들면 어느 값이 넘어오는지 정확하게 알 수 있다. DTO없이 Member객체 자체를 넘기면 어떤 값이 넘어올지 모른다.
    • 실무에서 엔티티를 절대로 외부에 노출하지 않는다.
  • command와 query를 분리해서 설계하면 유지보수성이 증가한다

    • ex) 회원 이름을 update한다 할 때 member객체를 return할 수도 있지만 이 때 member객체는 준영속 상태다. 그러므로 command + query가 합쳐진 것이기 때문에 이를 분리해준다고 보면 될 것같다.
  • 주로 “조회”에서 성능 문제난다.

  • 페이징할 때 유저가 주문한 상품들을 볼 때 주문한 상품이 2개면 쿼리 2개가 나갈텐데 이걸 어떻게 해결할 것인가?

  • 자바 가변인자 function(String... s) 하면 function(s1,s2,s3) 이런식으로 String값 여러개 할당 가능(내가 궁금해서 찾아본 것)

  • 주문조회 v2에서의 문제점은 N+1로 쿼리가 나간다.

    • 주문은 2건인데, 주문의 회원(N)에, 배송(주소,N) 이렇게 하면, 1번 쿼리 날리고 회원, 배송은 주문 건수만큼 조회
      즉, 위 사례는 쿼리 5번이나 날라감(만약 주문이 몇 만 건이면,,,?)
  • (복습)fetch join, N+1번으로 쿼리나가는 것을 join fetch를 통해 DB에서 한방쿼리로 가져온 이후 영속성 컨텍스트에 이것들이 저장되서, 쿼리를 안날리고 여기서 가져오면 된다.

    • N+1은 fetch join으로 해결한다. 90%문제는 이것으로 해결된다.
  • 주문조회 v4에서

return em.createQuery(
                "select new jpabook.jpashop.repository.OrderSimpleQueryDto(o.id, m.name, o.orderDate, o.status, d.address) " +
                        "from Order o" +
                        " join o.member m" +
                        " join o.delivery d", OrderSimpleQueryDto.class)
                        .getResultList();

    }
  • DTO랑 select o from Order o ~한다해서 o랑 OrderSimpleQueryDto가 매핑되지 않는다.
    Order객체를 안넣고 일일이 o.id, m.name
    하는 이유는 이 방식으로 o 넣으면, 식별자로 인식하지 않기 때문
    - new 명령어를 사용해서 JPQL의 결과를 DTO로 즉시 변환
    - 주문조회 v4코드만 봐도 재사용성 없을 것같다. 유지보수 최악(성능만 v3보다 좋을뿐;, select 가져올 때 적게 가져와서 최적화된다.)
  • 컬렉션 조회 v1에서 forEach구문에서 getter만 사용이 되었는데 어떻게 변수에 값이 할당이 되었는가? 궁금증이 생김
    • 프록시 객체개념 지연로딩을 통해 프록시 객체(엔티티프록시 객체, e.g. Team)를 불러오고, getName()과 같이 메소드 사용할 때 실제 객체를 DB에서 가져온다.
  • 엔티티는 절대 노출하면 안되고, Value Object(Address같은)는 가능하다.
  • JSON으로 반환 할 때 쿼리를 통해서 얻은 값을 그대로 반환하지 말고 클래스로 묶어서 반환해라!
  • fetch join 으로 order와 orderItem가 연결되어 있는데 order는 2건이고, orderItems은 4개 이므로 주문이 4건으로 해서 나옴(주문은 2건만 나와야하는데,,) → 데이터 뻥튀기 →이 때 쓰는게 “distinct”, 컬럼의 다른 값이 중복되지 않음에도 불구하고, 객체의 아이디가 같으면 중복제거한다. 데이터베이스 row를 줄여준다. 단점: 페이징 불가능
  • hibernate.default_batch_fetch_size == @Batchsize
    • 이 옵션을 사용하면 컬렉션이나, 프록시 객체를 한꺼번에 설정한 size 만큼 IN 쿼리로 조회한다
  • 엔티티 조회
    엔티티를 조회해서 그대로 반환: V1
    엔티티 조회 후 DTO로 변환: V2
    페치 조인으로 쿼리 수 최적화: V3
    컬렉션 페이징과 한계 돌파: V3.1
  • 컬렉션은 페치 조인시 페이징이 불가능
  • ToOne 관계는 페치 조인으로 쿼리 수 최적화 컬렉션은 페치 조인 대신에 지연 로딩을 유지하고, hibernate.default_batch_fetch_size , @BatchSize 로 최적화
  • DTO 직접 조회
    JPA에서 DTO를 직접 조회: V4
    컬렉션 조회 최적화 - 일대다 관계인 컬렉션은 IN 절을 활용해서 메모리에 미리 조회해서 최적화: V5
    플랫 데이터 최적화 - JOIN 결과를 그대로 조회 후 애플리케이션에서 원하는 모양으로 직접 변환: V6

→ 개인적으로 V3.1이 나랑 잘맞는것 같고, 하지만 엔티티 조회에서 문제가 된다면, DTO조회에서는 코드가 가독성이 너무 떨어짐,,, 그리고 데이터가 많을수록 페이징 처리가 중요하고, 성능이 중요해지므로 나는 JdbcTemplate 사용할 줄 아니까 이거 사용하는게 더 나을 듯

느낀점: 개발할 때 성능 최적화 vs 코드 복잡도를 고려하여 개발해야겠다.

  • 나중에 사용자 많을 때 Redis캐시를 사용할 텐데 이때 엔티티를 캐싱하면 안되고, DTO를 캐싱해야한다.

  • OSIV 전략은 트랜잭션 시작처럼 최초 데이터베이스 커넥션 시작 시점부터 API 응답이 끝날 때 까지 영속성 컨텍스트와 데이터베이스 커넥션을 유지 → 그래서 지연 로딩이 가능했던것이다.

    • But 너무 오랜시간동안 데이터베이스 커넥션 리소스를 물고 있다. 이는 장애로 이어질 수 있음
    • 실시간 트래픽이 중요한 서비스에서는 별로다.
  • OSIV를 끄면?

    • 모든 지연로딩을 트랜잭션 안에서 처리해야 한다.
    • 비즈니스 로직을 Service로 옮겨서 @Transactional처리 해줘야 한다.
profile
몰입하는 개발자

0개의 댓글