일전에 JPA를 알아보면서 QueryDSL이 복잡한 쿼리를 구성하기에 좋은 라이브러리 란 것을 보았다.
다음 링크들을 참조해서 본 프로젝트에 적용 해보았다.
참고링크
-maven 프로젝트 querydsl 적용
https://engkimbs.tistory.com/828
-Query Dsl이용한 페이징 API 만들기
http://bitly.kr/SYuvO5n48
-이동욱님 , Spring Boot Data Jpa 프로젝트에 Querydsl 적용하기
https://jojoldu.tistory.com/372
-Spring Data JPA와 QueryDSL 이해, 실무 경험 공유
(개인적으로 많은 도움이 된 글입니다.)
https://ict-nroo.tistory.com/117
1. Querydsl 의존성 추가
pom.xml 에 디펜던시와 플러그인을 추가하자
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
참조링크에서 다음과 같이 이야기해줍니다.
위 maven 설정에서 부분은 Qdomain 이라고 부르는 자바 코드를 생성하는 플러그인입니다. QueryDsl을 통해 쿼리를 생성할 때 이 Qdomain 객체를 사용합니다. 생성된 QDomain 객체를 보면 실제 도메인 객체의 모든 필드에 대해 사용가능한 모든 operation을 호출하는 메서드들이 정의되어 있습니다.
그렇다. 그리고 이 Qdomain을 사용하기 위해서 maven에서 한가지 더 필요하다.
complie.
엔티티가 수정될 때도 complie을 해주어야 Qdomain이 수정사항을 반영하여 컴파일 된다.
intellij에서 컴파일은 이렇게 하면 된다.
후에 target/generated-sources/java 를 따라 들어가보면 패키지에 Qdomain이 생성되있는 것을 볼 수 있다.
2. 페이징 객체 만들기
참조링크를 통해 페이징 객체를 만들어 주기
페이징 API 만들기 中
http://bitly.kr/XGannBRj7
위의 Pageable의 개선할 점이 있습니다. 우선 size에 대한 limit이 없습니다. 위의 API에서 size값을 200000을 넘기면 실제 데이터베이스 쿼리문이 200000의 조회할 수 있습니다. 그 밖에 page가 0 부터 시작하는 것들도 개선하는 것이 필요해 보입니다.
아래와 같은 PageRequest를 정의 한다.
public final class PageRequest {
private int page;
private int size;
private Sort.Direction direction;
public void setPage(int page) {
this.page = page <= 0 ? 1 : page;
}
public void setSize(int size) {
int DEFAULT_SIZE = 10;
int MAX_SIZE = 50;
this.size = size > MAX_SIZE ? DEFAULT_SIZE : size;
}
public void setDirection(Sort.Direction direction) {
this.direction = direction;
}
// getter
public org.springframework.data.domain.PageRequest of() {
return org.springframework.data.domain.PageRequest.of(page -1, size, direction, "createdAt");
}
3. QuerydslConfiguration 등록하기
간단하다! Querydsl을 사용할 수 있는 EntityManager를 Bean으로 등록한다.
@Configuration
public class QuerydslConfiguration {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory(){return new JPAQueryFactory(entityManager);}
}
4. 구현하기
우선 queryDSL을 사용할 인터페이스와 구현 클래스를 만들어 준다. 이동욱님의 설명에 따르면 xxxRespositoryCustom - xxxRepositoryImp 로 짝을 이룬다고 한다.
4.1 인터페이스 연결
public interface DriverRepositoryCustom {
Page<Driver> searchAll(DriverSearchDto search, Pageable pageable);
}
public interface DriverRepository extends JpaRepository<Driver,Long>, DriverRepositoryCustom {
}
기존의 JPA에서 사용하는 repository와 함께 CutomRepository를 사용할 수 있도록 extends에 추가
4.2 CutomRepositoryImpl 구현하기
import static com.project.domain.Driver.QDriver.driver;
@RequiredArgsConstructor
public class DriverRepositoryImpl implements DriverRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public Page<Driver> searchAll(DriverSearchDto search, Pageable pageable){
QueryResults<Driver> result = queryFactory
.selectFrom(driver)
.where(eqCity(search.getCityCode()),
eqStatus(search.getStatus())
)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetchResults();
return new PageImpl<>(result.getResults(),pageable,result.getTotal());
}
private BooleanExpression eqCity(CityCode cityCode) {
if (ObjectUtils.isEmpty(cityCode)) {
return null;
}
return driver.city.code.eq(cityCode);
}
private BooleanExpression eqStatus(DriverStatus status) {
if (ObjectUtils.isEmpty(status)) {
return null;
}
return driver.status.eq(status);
}
}
@Getter
@ToString
public class DriverSearchDto {
private CityCode cityCode;
private DriverStatus status;
@Builder
public DriverSearchDto(CityCode cityCode, DriverStatus status) {
this.cityCode = cityCode;
this.status = status;
}
}
Qdomain으로 쓰일 객체를 import static 으로 불러오기 (일일히 패키지를 입력하지 않도록)
selectFrom에 Qdomain으로 로드 할 엔티티를 지정 한 후 where 조건문을 입력한다.
Querydsl의 offset과 limit / Pageable의 offset과 pagesize를 사용하여 페이징 구현.
위와 같이 BooleanExpression을 사용한 메소드는 가독성 좋은 where 절을 사용할 수 있다.
(dto는 당연히 검색할 조건을 필드로 정의해주면 되겠다..)
4.3 컨트롤러와 서비스 구현
@RequiredArgsConstructor
@RestController
public class DriverController {
Logger logger = LoggerFactory.getLogger(this.getClass());
private final DriverService driverService;
@GetMapping("/api/v1/drivers/search")
public ResponseEntity<?> searchAll(DriverSearchDto search, PageRequest pageRequest) throws Exception {
ResponseEntity<?> result;
try {
result = new ResponseEntity<>(driverService.searchAll(search,pageRequest.of()),HttpStatus.OK) ;
}
catch(Exception e) {
logger.debug(e.getMessage());
result = new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST) ;
}
return result;
}
}
@RequiredArgsConstructor
@Service
public class DriverService {
private final DriverRepository driverRepository;
@Transactional(readOnly = true)
public Page<DriverResponseDto> searchAll(DriverSearchDto search,Pageable pageable){
return driverRepository.searchAll(search,pageable)
.map(DriverResponseDto::new);
}
}
컨트롤러에서 매핑된 PageRequest객체는 of메소드를 통해 pageable로 전달할 수 있도록 한다.
Page<Entity>를 Page<Dto>로 변환은 간단하게 .map()을 활용할 수 있다.
4.4 테스트
@RunWith(SpringRunner.class)
@SpringBootTest
public class DriverServiceTest {
@Autowired
DriverService driverService;
@Test
public void searchAll() {
DriverSearchDto dto = DriverSearchDto
.builder()
.status(DriverStatus.STANDBY)
.cityCode(CityCode.MNL)
.build();
PageRequest pageRequest = new PageRequest();
pageRequest.setDirection(Sort.Direction.ASC);
pageRequest.setSize(10);
pageRequest.setPage(0);
Page<DriverResponseDto> result = driverService.searchAll(dto,pageRequest.of());
result.forEach(
x -> System.out.println("element : " + x)
);
}
}
PageRequest 객체는 따로 생성자로 초기화하지 않고 setter를 사용한다.
컨트롤러에서 json을 dto로 파싱하는 부분이 좀 걸린다. converter를 사용했던 걸로 기억하는데 시도 해보아야할 부분
역시 쉽지만 어렵다.
'Study > JPA' 카테고리의 다른 글
[JPA] Transactional(readOnley=true) 가 성능 향상이 되는 이유 (0) | 2022.05.31 |
---|---|
[JPA] @AttributeOverride - 매핑 정보 재정의 (0) | 2022.05.25 |
Spring Data JPA - QueryDSL 쿼리 작성하기 (0) | 2022.05.13 |
[JPA] AuditEntity관련 내용 정리 (0) | 2022.04.19 |
[JPA] @MappedSuperclass (0) | 2022.04.19 |
댓글