[내일배움캠프 Spring 4기] 72일차 TIL - Projection

서예진·2024년 3월 13일
0

오늘의 학습 키워드

Projection


Projection

  • 전체를 조회하면 당연히 필드가 많아질수록 느려진다. -> 일부 필드만 조회해서 성능을 최적화하고자할 때 Projection 기능 사용

Projection의 기능

  1. 원하는 필드만 지정해서 조회 가능
  2. 여러필드 합쳐서 재정의 필드(Alias) 조회 가능 (Nested 프로젝션)
  3. 조회 필드 그룹을 인터페이스 또는 클래스로 만들어놓고 재사용 가능

Porjection 필드 사용방법

1. get필드() 메소드로 정의

  • 정의한 필드만 조회하기 때문에 Closed 프로젝션 이라고 한다.
  • 쿼리를 줄이므로 최적화 할 수 있다.
  • 메소드 형태이기 때문에 Java 8의 메소드를 사용해서 연산을 할 수 있다.
public interface UserProfile {
  String getUsername();

  String getProfileImageUrl();
}

// select username, profileImageUrl from user; // closed projection

2. @Value 로 정의

  • 전체 필드를 조회할 수 밖에 없어서 Open 프로젝션 이라고 한다.
  • @Value(SpEL)을 사용해서 연산을 할 수 있다.
  • 스프링 빈 들의 메소드도 호출 가능하다.
    • 스프링 빈 메소드 호출 예시
      // workersHolder 는 bean 으로 등록한 contextHolder
      
      @Value("#{workersHolder.salaryByWorkers['George']}")
      private Integer georgeSalary;
  • 쿼리 최적화를 할 수 없다. SpEL을 엔티티 대상으로 사용하기 때문에.
public interface UserProfile {

	@Value('#{target.profileImageUrl != null}')
  Boolean hasProfileImage;
}

Projection 구현체 정의방법

1. 인터페이스 기반 Projection

  • Projection 을 Interface 처럼 사용하는 방법
public interface UserProfile {

  String getUsername();

  String getProfileImageUrl();

  @Value("#{target.profileImageUrl != null}")
  boolean hasProfileImage();

  default String getUserInfo() {
    return getUsername() + " " + (hasProfileImage() ? getProfileImageUrl() : "");
  }
}
// UserRepository.java

List<UserProfile> findByUsername(String username);
@Test
  void projectionTest() {
    // given
    var newUser = User.builder().username("user").profileImageUrl("http://").password("pass")
        .build();

    // when
    var savedUser = userRepository.save(newUser);

    // then interface projection
    var userProfiles = userRepository.findByUsername("user");
    System.out.println("interface projection : ");
    userProfiles.forEach(userProfile -> System.out.println(userProfile.hasProfileImage()));
    assert !userProfiles.isEmpty();
  }

2. 클래스 기반 Projection ⭐️

  • Projection 을 DTO 클래스 처럼 사용하는 방법
@Getter
@AllArgsConstructor
public class UserInfo {

  private String username;

  private String password;

  public String getUserInfo() {
    return username + " " + password;
  }
}
// UserRepository.java

List<UserInfo> findByPassword(String password);
@Test
  void projectionTest() {
    // given
    var newUser = User.builder().username("user").profileImageUrl("http://").password("pass")
        .build();

    // when
    var savedUser = userRepository.save(newUser);

    // then class projection
    var userInfos = userRepository.findByPassword("pass");
    System.out.println("class projection : ");
    userInfos.forEach(userInfo -> System.out.println(userInfo.getUserInfo()));
    assert !userInfos.isEmpty()
  }

3. 다이나믹 Projection

  • Projection 클래스를 동적으로 지정해서 사용하는 방법
// UserRepository.java

<T> List<T> findByProfileImageUrlStartingWith(String profileImageUrlStartWith, Class<T> type);
@Test
  void projectionTest() {
    // given
    var newUser = User.builder().username("user").profileImageUrl("http://").password("pass")
        .build();

    // when
    var savedUser = userRepository.save(newUser);

    // then dynamic projection
    var userProfiles2 = userRepository.findByProfileImageUrlStartingWith("http", UserProfile.class);
    System.out.println("dynamic projection1 : ");
    userProfiles2.forEach(userProfile -> System.out.println(userProfile.getProfileImageUrl()));
    assert !userProfiles2.isEmpty();

    // then dynamic projection
    var userInfos2 = userRepository.findByProfileImageUrlStartingWith("http", UserInfo.class);
    System.out.println("dynamic projection2 : ");
    userInfos2.forEach(userInfo -> System.out.println(userInfo.getProfileImageUrl()));
    assert !userProfiles2.isEmpty();
  }
profile
안녕하세요

0개의 댓글