JPA를 왜 써야 할까에 앞서 이전에 진행했던 프로젝트들의 형태를 먼저 짚고 넘어가면,

 

기본적으로 JPA를 사용하고 있지만 더불어 JOOQ도 같이 사용하고 있습니다. 

 

public interface AccountInteHisRepositoryCustom {
    List<ActivityMonthlyReportDto> findAccountIntegrationHistoryMonthlyActiveStateByCreateDate(LocalDateTime date);
}

 

custom repository inteface를 정의하고 

 

@Repository
public class AccountInteHisRepositoryImpl implements AccountInteHisRepositoryCustom {

    ....

}

 

위와 같은 구현체인 XXXImpl에서 DSLContext를 주입받아 쿼리를 작성합니다.

이렇게 작성한 custom repository는 아래와 같이 JpaRepository와 같이 interface로 상속받아 적용합니다.

 

public interface AccountInteHisRepository extends JpaRepository<AccountInteHis, Long>, AccountInteHisRepositoryCustom {
....
}

 

그런데 이렇게 만들어진 custom repository는 변경에 대해 JPA보다 취약합니다.

native 쿼리보다 jooq dsl이 type safe하고 DB엔진에 영향을 받지 않습니다. 하지만 결국 쿼리가 바뀌면 custom repository도 바뀌어야 합니다.

예를 들어 필요에 의해 join해야하는 table이 하나 더 추가되면 JPA는 reference를 추가하는 것만으로 해결되지만 Jooq는 구현된 DSL을 다 바꿔야 하는 것입니다.

this.dslContext.select()
      .from(acctInteHis)
      .innerJoin(T_DEVICE)
      .on(acctInteHis.IMEI.eq(T_DEVICE.DEVICE_UID))
      .leftJoin(T_DEVICE_EXT)
      .on(device.DEVICE_UID.eq(T_DEVICE_EXT.DEVICE_UID))
      .where(acctInteHis.SEQ.in(selectSubQuery))
      .fetch()
      .stream()
      .map(m -> {
        return ActivityMonthlyReportDto.builder()
          .imei(m.getValue(acctInteHis.IMEI))
          .userId(m.getValue(acctInteHis.USER_ID))
          .state(AccountStateType.valueOf(m.getValue(acctInteHis.STATE)))
          .reqDate(m.getValue(acctInteHis.REQ_DATE).toInstant())
          .opCode(m.getValue(deviceExtension.OPCO))
          .productCode(m.getValue(deviceExtension.PRODUCE_CD))
          .build();
      })
      .collect(Collectors.toList());

jooq DSL의 예시입니다. 분명 내가 원하는 복잡한 쿼리를 구현할 수 있죠. 하지만 (비즈니스 로직 추가로 인해) query가 변경되면 DSL도 격하게 바뀔게 눈에 보이지 않나요? 실제 프로젝트는 영향받는 쿼리가 한 두 군데가 아닐 거라 생각합니다.

그리고 ActivityMonthlyReportDto라는 return 값도 바뀌지 말라는 보장도 없고 로직에 따라 Dto가 엄청 많아질 수 있습니다. 옛날에는 이게 귀찮아서 superset Dto를 만들어 사용하기도 했는데 이게 상황에 따라 어떤 member변수는 null이 된다는 것이 문제입니다.

하지만 JPA는 entity의 모든 reference는 (DB가 비어있지 않는 한) 채워져 있다는 것은 보장합니다. (이건 JPA가 application이 구동될 때 entity와 DB 스키마를 체크하기 때문에 믿을 수 있죠.)

더보기

이외에 JPA의 persistenceContext에 대한 장점등이 있는데요... dirty check, write behind, lazy loading등의 이점들이죠. 또한 domain 중심으로 데이터를 객체로 다룰 수 있는 기반을 JPA가 제공합니다.

 

결론은 DSL이 복잡하고 어려운 쿼리를 명확하게 구현한다는 것에는 의심의 여지가 없어 보입니다. 위의 dsl 예시처럼 subquery(selectSubQuery)도 마음먹은 대로 구현이 가능하니까요.

하지만 JPA를 잘 알고(!) 적용할 수 있다면 상당한 생산력 향상과 확장성을 확보 한다는 것은 매우 큰 매리트입니다.

JPA를 제대로 공부해서 마음껏 개발해야 겠습니다. (무슨 캠페인 같네요.ㅋㅋ)

'Programming > JPA' 카테고리의 다른 글

N + 1 문제 해결 1  (0) 2020.06.11
N + 1 문제 원인  (0) 2020.06.05
JPA 기본 Annotation 정리  (7) 2019.07.04
jpa 복합키에서 auto increment  (0) 2019.06.27
JPA 기본 키 전략  (0) 2019.06.27

+ Recent posts