아래와 같은 관계도가 있는 테이블이 있다.
위에 그림 두개는 DB 관계도, 아래의 이미지는 자바단 부모 @Entity 쪽 @OneToMany 설정이다.
원래는 LAZY를 쓰고싶지만 회사 ..사정상 EAGER를 쓰고있다.
편의상 OP_REGULAR_ROUND => OP_ROUND
OP_USER_REGULAR_ROUND -> OP_ROUND_ORDER 로 바꿔서 설명하겠다.
1. JPA N+1 이슈
내가원하는 SQL문은 아래와 같다.
SELECT RR.ROUND, OR.*
FROM OP_ROUND OR
INNER JOIN OP_ROUND_ORDER RR ON OR.CODE = RR.CODE
WHERE OR.CODE = '1111'
ORDER BY RR.ROUND DESC;
조인을 하고 코드를 파라미터로 넘겨준다.
1) service와 같은 root 에 repository를 생성 - 나는 일단 impl 생성했다.
public interface RoundCustom {
/**
* 설명 ~~~~
* @return
*/
Round getDetail(String code);
}
import static saleson.model.QRound.round;
import static saleson.model.QRoundOrder.roundOrder;
@RequiredArgsConstructor
public class RoundCustomImpl implements RoundCustom{
private final JPAQueryFactory jpaQueryFactory;
@Override
public Round getDetail(String roundCode) {
}
}
querydsl에서 사용할 q클래스와 JPAQeuryFactory 를 선언해줬다.
그리고 리포지토리에 extends 추가해준다.
사실 커스텀안하고 별도 리포지토리를 생성하고 거기에 관리해도된다.
public interface RoundRepository extends JpaRepository<Round, Long>, QuerydslPredicateExecutor<Round>, RoundCustom {
Optional<Round> findByCode(String code);
}
2) QUERY DSL를 사용 할 메소드 생성
다시 RoundCustomImpl에 돌아와서..
public Round getDetail(String code) {
//builder 를 따로 뺀 이유는, 이후에 where 조건이 추가될수도 있기 때문입니다.
BooleanBuilder builder = new BooleanBuilder();
builder.and(round.code.eq(code));
}
querydsl 생성에 앞서 먼저 builder 를 만들어준다.
where 조건에 들어갈 부분을 넣으면 된다.
public Round getDetail(String code) {
//builder 를 따로 뺀 이유는, 이후에 where 조건이 추가될수도 있기 때문입니다.
BooleanBuilder builder = new BooleanBuilder();
builder.and(round.code.eq(code));
return jpaQueryFactory
.select(round)
.from(round)
.innerJoin(round.list,roundOrder) //oneToMany
.fetchJoin()
.where(builder)
.orderBy(roundOrder.round.desc())
.fetchOne();
}
이게 완성된 로직인데, innerjoin에 들어가는 파라미터는, 부모@Entity에 @OneToMany로 걸려있는 변수를, 뒤에는 조인할 q클래스명을 입력해줬다.
이렇게 돌리면
select regularrou0_.ID as id1_47_0_,
userregula1_.ID as id1_60_1_,
regularrou0_.CREATED as created2_47_0_,
regularrou0_.CREATED_BY as created_3_47_0_,
regularrou0_.UPDATED as updated4_47_0_,
regularrou0_.UPDATED_BY as updated_5_47_0_,
regularrou0_.BUYER_EMAIL as buyer_em6_47_0_,
regularrou0_.BUYER_MOBILE as buyer_mo7_47_0_,
regularrou0_.BUYER_NAME as buyer_na8_47_0_,
regularrou0_.CARD_NAME as card_nam9_47_0_,
regularrou0_.INDEX_ROUND as index_r10_47_0_,
regularrou0_.ITEM_NAME as item_na11_47_0_,
regularrou0_.ITEM_USER_CODE as item_us12_47_0_,
regularrou0_.RECEIVE_NAME as receive13_47_0_,
regularrou0_.REGULAR_ROUND_END_DATE as regular14_47_0_,
regularrou0_.REGULAR_ROUND_START_DATE as regular15_47_0_,
regularrou0_.STATUS as status16_47_0_,
regularrou0_.TOTAL_ROUND as total_r17_47_0_,
regularrou0_.USER_ID as user_id18_47_0_,
regularrou0_.USER_REGULAR_CARD_ID as user_re19_47_0_,
regularrou0_.USER_REGULAR_ROUND_CODE as user_re20_47_0_,
userregula1_.CREATED as created2_60_1_,
userregula1_.CREATED_BY as created_3_60_1_,
userregula1_.UPDATED as updated4_60_1_,
userregula1_.UPDATED_BY as updated_5_60_1_,
userregula1_.DISCOUNT_RATE as discount6_60_1_,
userregula1_.FAIL_COUNT as fail_cou7_60_1_,
userregula1_.ITEM_ID as item_id8_60_1_,
userregula1_.ITEM_NAME as item_nam9_60_1_,
userregula1_.ITEM_SEQUENCE as item_se10_60_1_,
userregula1_.ORDER_CODE as order_c11_60_1_,
userregula1_.ORDER_DATE as order_d12_60_1_,
userregula1_.ORDER_SEQUENCE as order_s13_60_1_,
userregula1_.ORDER_STATUS as order_s14_60_1_,
userregula1_.PAY_AMOUNT as pay_amo15_60_1_,
userregula1_.PAY_DATE as pay_dat16_60_1_,
userregula1_.PAYMENT_INTERVAL as payment17_60_1_,
userregula1_.QUANTITY as quantit18_60_1_,
userregula1_.ROUND as round19_60_1_,
userregula1_.SHIPPING_CANCEL_END_DATE as shippin20_60_1_,
userregula1_.SHIPPING_CANCEL_START_DATE as shippin21_60_1_,
userregula1_.SHIPPING_READY_END_DATE as shippin22_60_1_,
userregula1_.SHIPPING_READY_START_DATE as shippin23_60_1_,
userregula1_.USE_FLAG as use_fla24_60_1_,
userregula1_.USER_DELIVERY_ID as user_de25_60_1_,
userregula1_.USER_ID as user_id26_60_1_,
userregula1_.USER_REGULAR_CARD_ID as user_re27_60_1_,
userregula1_.USER_REGULAR_ROUND_CODE as user_re28_60_1_,
userregula1_.USER_REGULAR_ROUND_CODE as user_re28_60_0__,
userregula1_.ID as id1_60_0__
from OP_ROUND regularrou0_
inner join OP_ROUND_ORDER userregula1_
on regularrou0_.CODE = userregula1_.code
where regularrou0_.CODE = '1111'
order by userregula1_.ROUND desc
이런식으로 내가원하던 조인문이 나온다.
이때 fetchJoin을 안쓰면
select regularrou0_.ID as id1_47_,
regularrou0_.CREATED as created2_47_,
regularrou0_.CREATED_BY as created_3_47_,
regularrou0_.UPDATED as updated4_47_,
regularrou0_.UPDATED_BY as updated_5_47_,
regularrou0_.BUYER_EMAIL as buyer_em6_47_,
regularrou0_.BUYER_MOBILE as buyer_mo7_47_,
regularrou0_.BUYER_NAME as buyer_na8_47_,
regularrou0_.CARD_NAME as card_nam9_47_,
regularrou0_.INDEX_ROUND as index_r10_47_,
regularrou0_.ITEM_NAME as item_na11_47_,
regularrou0_.ITEM_USER_CODE as item_us12_47_,
regularrou0_.RECEIVE_NAME as receive13_47_,
regularrou0_.REGULAR_ROUND_END_DATE as regular14_47_,
regularrou0_.REGULAR_ROUND_START_DATE as regular15_47_,
regularrou0_.STATUS as status16_47_,
regularrou0_.TOTAL_ROUND as total_r17_47_,
regularrou0_.USER_ID as user_id18_47_,
regularrou0_.USER_REGULAR_CARD_ID as user_re19_47_,
regularrou0_.USER_REGULAR_ROUND_CODE as user_re20_47_
from OP_ROUND regularrou0_
inner join OP_ROUND_ORDER userregula1_
on (regularrou0_.CODE = userregula1_.CODE)
where regularrou0_.CODE = '1111'
order by userregula1_.ROUND desc;
select userregula0_.USER_REGULAR_ROUND_CODE as user_re28_60_0_,
userregula0_.ID as id1_60_0_,
userregula0_.ID as id1_60_1_,
userregula0_.CREATED as created2_60_1_,
userregula0_.CREATED_BY as created_3_60_1_,
userregula0_.UPDATED as updated4_60_1_,
userregula0_.UPDATED_BY as updated_5_60_1_,
userregula0_.DISCOUNT_RATE as discount6_60_1_,
userregula0_.FAIL_COUNT as fail_cou7_60_1_,
userregula0_.ITEM_ID as item_id8_60_1_,
userregula0_.ITEM_NAME as item_nam9_60_1_,
userregula0_.ITEM_SEQUENCE as item_se10_60_1_,
userregula0_.ORDER_CODE as order_c11_60_1_,
userregula0_.ORDER_DATE as order_d12_60_1_,
userregula0_.ORDER_SEQUENCE as order_s13_60_1_,
userregula0_.ORDER_STATUS as order_s14_60_1_,
userregula0_.PAY_AMOUNT as pay_amo15_60_1_,
userregula0_.PAY_DATE as pay_dat16_60_1_,
userregula0_.PAYMENT_INTERVAL as payment17_60_1_,
userregula0_.QUANTITY as quantit18_60_1_,
userregula0_.ROUND as round19_60_1_,
userregula0_.SHIPPING_CANCEL_END_DATE as shippin20_60_1_,
userregula0_.SHIPPING_CANCEL_START_DATE as shippin21_60_1_,
userregula0_.SHIPPING_READY_END_DATE as shippin22_60_1_,
userregula0_.SHIPPING_READY_START_DATE as shippin23_60_1_,
userregula0_.USE_FLAG as use_fla24_60_1_,
userregula0_.USER_DELIVERY_ID as user_de25_60_1_,
userregula0_.USER_ID as user_id26_60_1_,
userregula0_.USER_REGULAR_CARD_ID as user_re27_60_1_,
userregula0_.USER_REGULAR_ROUND_CODE as user_re28_60_1_
from OP_ROUND_ORDER userregula0_
where userregula0_.CODE = 'R20230216100309';
이런식으로 두번나온다.
하지만 fetchJoin이 만사형통은 아니다.. 페이징은 쓸수없기에....
참고로 해당 sql문은 콘솔에서 확인할수있는데
이런식으로 쓱- 보고 지나가기 쉽다. 하지만 꼭 작업할때 긁어서 실제로 sql문을 육안으로 확인해보는 습관을 길러야한다.
2. querydsl 결과를 특정 DTO에 리턴, 서브쿼리받는법
내가 받고싶은 sql문
select RR.USER_ID,
RR.USER_REGULAR_ROUND_CODE,
RR.ITEM_NAME,
RR.REGULAR_ROUND_START_DATE,
RR.TOTAL_ROUND,
RR.BUYER_NAME,
RR.CREATED,
min(OURR.PAY_DATE),
min(OURR.PAYMENT_INTERVAL),
OURR.SHIPPING_READY_START_DATE
from OP_ROUND RR
inner join OP_ROUND_ORDER OURR
on RR.CODE = OURR.CODE
where OURR.ORDER_CODE is null
and RR.CODE = (select CODE
from OP_ROUND_ORDER
where ORDER_CODE = '2222')
특정 결과만 dto에 리턴받고싶을때
@Override
public dtoA method2(String orderCode){
//서브쿼리에 넣을 where 절
BooleanBuilder subBuilder = new BooleanBuilder();
subBuilder.and(roundOrder.orderCode.eq(orderCode));
//메인쿼리에 넣을 where 절
BooleanBuilder builder = new BooleanBuilder();
builder.and(roundOrder.orderCode.isNull());
builder.and(round.code.eq(
JPAExpressions.select(roundOrder.code)
.from(roundOrder)
.where(subBuilder)
));
return jpaQueryFactory
.select(Projections.constructor(dtoA.class,
round.userId,
round.userRegularRoundCode,
round.itemName,
round.regularRoundStartDate,
round.totalRound,
round.buyerName,
round.created,
roundOrder.payDate.min(),
roundOrder.paymentInterval.min())) //그룹함수는 여기서
.from(round)
.join(round.list,roundOrder)
.where(builder)
.fetchOne();
}
서브쿼리는 JPAExpressions 를 사용해서 조인시킨다.
역시 이때도 where 조건이 많아지면 너무 산만하기때문에 상단에 따로 선언하고 사용하였다.
Projections.contructor 사용하면 dto로 받을수있다.
첫 파라미터에 리턴받고싶은 dto 클래스를 추가하고 뒤에는 전부 내가받고싶은 컬럼명 나열한다.
이때 해당 dto에 해당 컬럼을 받는 생성자가 선언되어있어야한다.
그룹함수는 select할때 바로 쓰면된다.