Database에 Sales에 대한 Table이 있다고 가정할 때
만들고자 하는 DTO는 다음과 같습니다.
(필드는 DB의 애트리뷰트의 자료형과 맞추어서 만들었습니다.)
@Getter
@NoArgs
Constructor
@AllArgsConstructor
public
class
SalesCountDTO
{
private
long
id
;
private
String
name
;
private
Long
sales
;
Java Stream 활용
.
.
.
@Transactional
public
List
<
SalesCountDTO
>
getSalesByStream
(
)
{
return
saleRepo
.
findAll
(
)
.
parallelStream
(
)
.
map
(
sale
->
new
SalesCountDTO
(
sale
.
getId
(
)
,
sale
.
getName
(
)
,
sale
.
getSales
(
)
)
)
.
collect
(
Collectors
.
toMap
(
sum
->
sum
.
getId
(
)
,
Function
.
identify
(
)
,
(
sum1
,
sum2
)
->
new
SaleSum
(
sum1
.
getId
(
)
,
sum1
.
getSales
.
add
(
sum2
.
getSales
(
)
)
.
values
(
)
.
stream
(
)
.
sorted
(
Comparator
.
comparing
(
SalesCountDTO
:
:
getsales
)
)
.
collect
(
Collectors
.
toList
(
)
)
collect
를 이용하면 다양한
groupBy
를 사용할 수 있습니다.
Collectors.toMap
을 이용하여
Sale.getId()
를 기준으로
SaleSum
을 생성하는 방식으로 사용하였습니다.
Stream
을 사용하게 되면 어플리케이션 단에서 구현된 내용을 쉽게 수정하거나 파악할 수 있는 장점이 있지만 전체 Data를 어플리케이션에서
group by
를 하기 때문에
데이터가 많아지면 느려지고 메모리 사용이 많아지기에 적절한 방식이 아닙니다.
JPQL 활용
JPA
에
JPQL
과
Projection
을 이용해서
DTO
에
Query
결과값을 담는다.
@Repository
public
interface
SalesRepository
extends
JpaRepository
<
Sales
,
Long
>
{
@Query
(
"SELECT "
+
"new study.jpa.sales.dto.SalesCountDTO(s.Id, s.name, SUM(s.sale)) "
+
"FROM Sale s "
+
"GROUP BY s.ID"
List
<
SalesCountDTO
>
findSalesCountDtoJPQL
(
)
;
JPQL
을 이용하면
Query
에 대한 타입 안정성과
Query
안정성을 바로 확인 할수 있지만
Projection
을 사용할 때 new 를 통해 생성자를 쿼리 내에서 호출해 줘야하는 단점이 있습니다.
Native Query 활용
Native Query
에 대해
Entity
가 아닌 Retury 값을 반환받기 위해서는
Interface based Projection
을 활용해야 합니다.
Interface
선언
public interface SalesCountInterface{
Long getId();
String getName();
Long getSales();
Interface
에 맞게 Native Query
를 작성
@Repository
public interface SalesRepository extends JpaRepository<Sales, Long>{
@Query(
"SELECT "
+ "s.Id AS id "
+ ", s.Name As name "
+ ", SUM(Sales) AS sales "
+ "FROM Sale s "
+ "GROUP BY s.ID"
, nativeQuery = true
List<SalesCountDTO> findSalesCountDtoNativeQuery();
JPQL
과 유사하지만 nativeQuery=true
를 통해 순수 Query
로 작성되어 있으며, SELECT된 각 칼럼을 Interface
명에 맞춰 Alias
를 설정해야 됩니다.
Native Query
를 활용하게 되면 쿼리 자체를 Database에서 빠르게 검증 가능하지만, 반환된 Interface
를 별도의 DTO로 변환해야 할 필요성이 생깁니다.
QueryDSL
QueryDSL
을 활용한 Group By
쿼리는 JPAQuery
를 직접 활용해야 합니다.
@Service
@Slf4j
@RequiredArgsConstructor
public class SalesServiceImpl implements SalesService {
@PersistenceContext
private EntityManager entityManager;
@Transactional
public List<SalesCountDTO> getSalesDTOByQueryDSL(){
QSale qSale = QSale.sale;
JPAQueryFactory qf = new JPAQueryFactory(entityManager);
JPAQuery<SalesCountDTO> query = qf.from(qSale)
.groupBy(qSale.id)
.select(
Projections.bean(
SalesCountDTO,
qSale.id,
qSale.name,
qSale.sales.sum().as("sales")
return query.fetch();
QueryDSL
에서 Group By
를 사용하기 위해서는 EntityManager
를 통해 JPAQuery
를 직접 생성해서 사용해야 하는 단점이 있지만, QueryDSL
의 장점인 다양한 조건의 쿼리를 생성할 수 있다는 장점이 있습니다. (Optional
한 쿼리 조건을 각각 적용 가능)
230건의 데이터를 각 100회씩 호출했을 때 응답 시간은
Java Stream이 가장 속도가 느리며 나머지는 실제 쿼리가 비슷하기 때문에 평균 응답 시간도 같게 나왔습니다.
다만 JPQL
과 QueryDSL
은 초기 쿼리 파싱 때문에 Max 속도가 다른 것을 볼 수 있습니다.