ArtZip 프로젝트 정리 - JPA

yshjft·2022년 8월 18일
0

페이징 방식 Page와 Slice

Slice

  • 인터페이스
  • 전체 페이지 수와 전체 데이터 개수를 조회하지 않는다(다음 Slice가 있는지만 판단)
  • Page 방식보다 성능상 우위
  • 무한 스크롤에서 유용(전체 데이터에 대한 정보 필요 없는 경우 유용)
Slice<Member> findSliceBy(Pageable pageable);PageRequest pageRequest = PageRequest.of(1, 2, Sort.by(Sort.Direction.ASC, "age"));

Slice<Member> page = memberRepository.findSliceBy(pageRequest);

Page

  • 인터페이스
  • Slice를 상속
  • Slice와 다르게 전체 페이지 수와 전체 데이터 개수를 조회 → Count 쿼리 추가 필요
  • Slice 방식보다 성능이 낮음
Page<Member> findPageBy(Pageable pageable);PageRequest pageRequest = PageRequest.of(1, 2, Sort.by(Sort.Direction.ASC, "age"));

Page<Member> page = memberRepository.findPageBy(pageRequest);

Querydsl with Slice example

  • Slice 동작 방식
    • N개의 데이터가 필요한 경우 N+1개의 데이터를 가져온다.
      • 결과 값의 개수 > N → 다음 페이지 존재함을 의미
    • 결과 값의 개수 > N 인 경우 추가적으로 가져온 +1의 데이터를 빼고 결과 리스트를 반환한다.
public Slice<NotificationDto> findNotificationByUsername(String username, Pageable pageable) {

        List<OrderSpecifier> ORDERS = getAllOrderSpecifiers(pageable);

        List<NotificationDto> results = query
                .select(new QNotificationDto(
                        notification.title,
                        notification.message,
                        notification.checked,
                        notification.notificationType,
                        notification.uuid,
                        notification.TeamId
                ))
                .from(notification)
                .where(notification.member.username.eq(username))
                .orderBy(ORDERS.stream().toArray(OrderSpecifier[]::new))
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize() + 1)
                .fetch();

        return RepositorySliceHelper.toSlice(results, pageable);
}

...

public class RepositorySliceHelper {
    public static <T> Slice<T> toSlice(List<T> contents, Pageable pageable) {
        boolean hasNext = isContentSizeGreaterThanPageSize(contents, pageable);
        return new SliceImpl<>(hasNext ? subListLastContent(contents, pageable) : contents, pageable, hasNext);
    }

    // 다음 페이지 있는지 확인
    private static <T> boolean isContentSizeGreaterThanPageSize(List<T> content, Pageable pageable) {
        return pageable.isPaged() && content.size() > pageable.getPageSize();
    }

    // 데이터 1개 빼고 반환
    private static <T> List<T> subListLastContent(List<T> content, Pageable pageable) {
        return content.subList(0, pageable.getPageSize());
    }
}

Spring Data JPA의 Page와 Slice
[SpringBoot] Spring Data JPA 에서 Page와 Slice
Querydsl - 빠른 시작, 간단 정리

임베디드 타입

  • 임베디드 타입은 복합 값 타입으로 불리며 새로운 값 타입을 직접 정의해서 사용하는 JPA의 방법을 의미한다.
  • 상세한 데이터를 그대로 갖고 있는 것은 객체지향적이지 않으며 응집력을 떨어뜨린다.
  • 임베디드 타입을 사용함으로서 더욱 더 객체지향적인 코드(재사용성 향상, 높은 응집도)를 만들 수 있다.

ID 생성 전략

@GeneratedValue(strategy = GenerationType.IDENTITY)

  • 기본키 생성을 데이터베이스에 위임
  • 데이터를 저장해야 기본키 값을 가질 수 있다.
    • JPA는 기본 키 값을 얻어오기 위해 데이터베이스를 추가로 조회하게 된다.
  • MySQL

@GeneratedValue(strategy = GenerationType.SEQUENCE)

  • 데이터베이스 시퀀스 = 유일한 순서 값을 순서대로 생성하는 데이터베이스 오브젝트
  • H2

@GeneratedValue(strategy = GenerationType.TABLE)

  • 기본키 생성을 위한 전용 테이블 존재
  • 모든 데이터베이스에서 사용 가능

@GeneratedValue(strategy = GenerationType.AUTO)

  • 생성전략 default 값
  • 위 3방법 중 방언에 따라 하나로 자동 지정

ID 생성 전략을 AUTO로 사용하고 dialect도 Mysql을 사용하는데  왜 auto_increment가 누락될까?

  • Spring Boot는 Hibernate의 id 생성 전략을 그대로 따라갈지 말지를 결정하는 useNewIdGeneratorMappings 설정이 있다.
    • 1.5에선 기본값이 false, 2.0부터는 true
  • Hibernate 5.0부터 MySQL의 AUTO는 IDENTITY가 아닌 TABLE을 기본 시퀀스 전략으로 선택된다.
  • 즉, 1.5에선 Hibernate 5를 쓰더라도 AUTO를 따라가지 않기 때문에 IDENTITY가 선택
  • 2.0에선 true이므로 Hibernate 5를 그대로 따라가기 때문에 TABLE이 선택

해결 방법

  • application.properties/yml의 use-new-id-generator-mappings을 false로 설정한다
  • @GeneratedValue의 전략을 GenerationType.IDENTITY로 지정한다

Spring Boot Data JPA 2.0 에서 id Auto_increment 문제 해결

Querydsl

  • JPQL 빌더 역할(자바로 JPQL을 작성)
  • JPQL은 SQL로 번역되어 DB를 우선적으로 조회한다.
    • 만약 DB에서 찾은 엔티티가 영속성 컨텍스트에 존재한다면 DB에서 찾은 결과를 버리고 영속성 컨텍스트에 있는 엔티티를 JPQL 반환값을 준다.
    • DB에서 찾은 엔티티와 영속성 컨텍스트에 존재하는 엔티티의 비교 기준은 필드 값들이 아니라 엔티티의 식별자값(PK)이다.
    • Querydsl도 JPQL과 동일하기에 데이터가 save 되어 영속성 컨텍스트에 남아있어도 select시 실제 DB에 쿼리를 날리는 것이다.
  • flush 발생 조건 중 하나는 JPQL이 실행될 때이다.
    • Querydsl 또한 이에 따라 Querydsl의 select 호출 직전 엔티티를 변경하면 update 쿼리가 발생한다(flush 된다).
  • (참고) flush 발생 조건
    • EntityManager.flush()를 통한 직접 호출
    • 트랜잭션 commit시 flush 자동 호출
    • JPQL 쿼리 실행 시 flush 자동 호출(단, JPQL과 관련 있는 엔티티만 플러시)

영속성 컨텍스트에 대해 질문드립니다. - 인프런 | 질문 & 답변
JPA - 영속성 컨텍스트와 JPQL
QueryDsl 1차 캐시관련 - 인프런 | 질문 & 답변
[Spring JPA] 성능 개선을 하기 위해 당신이 알아야 할 상식

DTO 조회

  • DTO 조회는 DB에서 값만 가지고 오는 거라 영속성 컨텍스트에서 관리가 되지 않는다.
  • DTO 조회의 경우 확실히 퀴리를 줄여줄 수 있다는 장점이 있다. 다만 변경에 취약해지는 단점이 있다.

DTO로 조회해도 영속성 컨텍스트에서 관리가 되나요? - 인프런 | 질문 & 답변

Projection 시 원하는 요소만 interface를 통해 가져오기 (Interface based Projections)

일단 참고만 해두자..

[JPA] 엔티티 일부 데이터만 조회하는 Projection
SPRING,JPA 원하는 값 내려주기 방법 모음 총 정리, Projection 방법 모음

profile
꾸준히 나아가자 🐢

0개의 댓글