Projection
프로젝션은 JPA로 조회 시 원하는 컬럼을 가져오는 방법입니다.
보통 Spring Data JPA에서 사용하는 방식과 QueryDSL에서 사용하는 방식으로 나뉩니다.
프로젝션은 데이터 최적화 및 간결한 데이터 구조를 제공해 성능 향상과 유지보수성에서 큰 장점이 있지만, 수정이 불가능한 읽기 전용 구조와 프로젝션 사용이 많아질수록 복잡해지는 쿼리 관리라는 단점이 있습니다.
Base : Store.java
@Entity
@Table(name = "store")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 20, unique = true, nullable = false)
private String name;
@Column(columnDefinition = "TEXT")
private String description;
Spring Data JPA
1. Interface 기반 Projections
인터페이스를 정의하여 특정 필드만 가져오는 방법입니다.
여기에는 Closed Projections
와 Open Projections
두 가지 방법이 있습니다.
1-1. Closed Projections
Closed Projections는 인터페이스를 통해 반환할 필드를 미리 정의합니다.
각 메서드는 엔티티의 필드 이름과 일치하며, 이 메서드를 통해 값을 가져옵니다.
Store 에서 id와 name만 가져오기 위해 interface를 만들고 repository에서 해당 interface로 담는 메서드를 만듭니다.
Projection
public interface StoreProjection {
Long getId();
String getName();
}
Repository
public interface StoreRepository extends JpaRepository<Store, Long> {
List<StoreProjection> findById(Long id);
}
1-2. Open Projections
Open Projections는 Spring의 SpEL(Expression Language)를 사용하여 데이터를 조합할 수 있습니다.#{}
를 사용하여 필드를 가공하거나 특정 표현식을 사용할 수 있습니다.
테이터 가공을 위한 추가 로직이 필요한 경우에 유용하지만 모든 column을 가져와 처리하기 때문에 성능에 영향을 줄 수 있습니다.
public interface StoreProjection {
Long getId();
String getName();
@Value("#{target.id + ' ' + target.name}")
String getIdAndName();
}
다른 사용방법
public interface StoreProjection {
Long getId();
String getName();
Default getIdAndName();
return getId() + " " + getName();
}
2. Class 기반 Projections
결과를 특정 클래스의 인스턴스로 반환하는 방법입니다.
DTO와 비슷한 구조로 사용되며 엔티티 필드의 일부 또는 계산된 필드를 포함할 수 있습니다.
-. Class 기반 Projection을 사용하려면 반환하려는 Class에 해당하는 생성자가 필요합니다.
-. 생성자는 Projection에서 선택한 필드를 매개변수로 받아야 합니다.
-. Repository에서 Query 결과를 해당 Class의 객체로 매핑할 수 있습니다.
-. 이 방식을 사용하면 interface 기반보다 더 명확한 객체를 반환할 수 있기 때문에 DATA 조작과 유지 및 보수에 유리할 수 있습니다.
public class StoreProjection {
private Long id;
private String name;
public StoreProjection(Long Id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return this.id;
}
public String getName() {
return this.name;
}
}
3. Dynamic Projections
메서드를 호출할 때 원하는 Projection 타입을 동적으로 지정하는 방식입니다.
이 방법을 사용하면 여러 Projection을 하나의 Repository 메서드로 처리할 수 있습니다.
예를 들어 id로 조회하는데 어느 부분에서는 id와 name만 필요하고 다른 부분에서는 name과 description만 필요하다면
Projection
public interface StoreIdAndNameProjection {
Long getId();
String getName();
}
public interface StoreNameAndDescriptionProjection {
String getName();
String getDescription();
}
Repository
public interface StoreRepository extends JpaRepository<Store, Long> {
<T> T findById(Long Id, Class<T> type);
}
QueryDSL
@AllArgsConstructor
public class StoreDto {
private Long id;
private String name;
private String description
}
1. Projections.fields()
필드 접근 방식은 DTO에 필드로 매핑하는 방식입니다.
DTO의 필드가 직접 매핑되어있어야 하고 필드 이름이 엔티티의 필드와 동일해야 합니다.
QStore store = Qstore;
List<StoreDto> storeList = queryFactory
.select(Projections.fields(StoreDto.class,
Store.id,
Store.name
))
.from(store)
.fetch();
2. Projections.bean()
Bean 접근 방식은 DTO의 Setter 메서드를 이용해 필드를 설정하는 방식입니다.
QStore store = Qstore;
List<StoreDto> storeList = queryFactory
.select(Projections.bean(StoreDto.class,
Store.id,
Store.name
))
.from(store)
.fetch();
3. Projection.constructor()
생성자 접근 방식은 DTO의 생성자를 이용해 필드를 설정합니다.
DTO에 적절한 생성자가 있어야 하며, 생성자 인자와 매핑할 필드의 순서가 일치해야 합니다.
생성자 접근 방식은 DTO의 모든 필드를 가져와야 합니다.
QStore store = Qstore;
List<StoreDto> storeList = queryFactory
.select(Projections.constructor(StoreDto.class,
Store.id,
Store.name,
Store.description
))
.from(store)
.fetch();
4. 별칭을 사용하여 필드 매핑
QueryDSL에서 프로젝션 필드 이름이 DTO와 일치하지 않는 경우 Expressions
와 as()
를 사용하여 별칭을 지정해 매핑할 수 있습니다.
as()
: 엔티티 필드를 DTO 필드명에 맞게 별칭으로 매핑Expressions
: 복잡한 표현식을 사용할 때 사용되며 아래 예제에서는 두 필드를 합치는 데 사용
예를 들어 DTO에서 name이 아니라 private String storeName;
과 private String fullName;
으로 되어있는 경우
QStore store = Qstore;
List<StoreDto> storeList = queryFactory
.select(Projections.constructor(StoreDto.class,
Store.id,
Store.name.as("storeName"),
Expressions.stringTemplate("function('concat',
{0},
{1},
Store.firstName,
Store.lastName) as ("fullName")
))
.from(store)
.fetch();
5. 동적 프로젝션
메서드 호출 시 원하는 프로젝션을 동적으로 지정할 수 있습니다.
public <T> List<T> findAllStores(Class<T> type) {
QStore store = Qstore;
return queryFactory
.select(Projections.bean(StoreDto.class,
Store.id,
Store.name
))
.from(store)
.fetch();
}
'내일배움캠프 > 내일배움캠프' 카테고리의 다른 글
[내일배움캠프] TIL : RawJPA 기본 (0) | 2024.11.14 |
---|---|
[내일배움캠프] TIL : Transaction (0) | 2024.11.13 |
[내일배움캠프] 심화 과제 진행해보기 Lv. 1 (0) | 2024.11.13 |
[내일배움캠프] H2 DB (0) | 2024.11.12 |
[내일배움캠프] 트러블 슈팅 TIL (0) | 2024.11.06 |
댓글