
이전 프로젝트에서와 마찬가지로, CQRS 스타일로 조회를 분리해 구현하기로 했다. (DB는 동일)
대신 이번에는 동작 원리를 확실히 이해하고 넘어가려고 한다.
[쓰기] POST/PATCH/DELETE → UserService → UserRepository (JPA save/delete)
[읽기] GET → UserQueryService → UserQueryRepository (QueryDSL select)
@Entity User → 빌드 시 APT가(아파트 아님 주의: 어노테이션 프로세싱 툴) QUser 자동 생성
↓
JPAQueryFactory.selectFrom(QUser.user)
.where(QUser.user.name.contains("김")) // 반환 메서드가 null을 반환하면
//QueryDSL이 해당 where 조건을 자동으로 무시 → 동적 쿼리
.offset(0).limit(10)
.fetch()
JPAQueryFactory는 ueryDSL이 제공하는 쿼리 생성 빌더입니다.
역할: select, from, where 같은 SQL 문법을 자바 메서드 형태로 작성할 수 있게 해줍니다.
특징: EntityManager를 내부에서 사용하여 실제 DB에 쿼리를 날립니다.
핵심 메서드:
selectFrom(QUser.user): 전체 조회
where(...): 조건절 (null 입력 시 자동 무시)
orderBy(...): 정렬
fetch(): 리스트 반환 / fetchOne(): 단건 반환
QUser는 우리가 만든 @Entity User 클래스를 바탕으로 APT가 자동 생성한 클래스입니다.
역할: 엔티티의 필드들을 QueryDSL이 이해할 수 있는 메타데이터 형태로 제공합니다.
User 엔티티의 name 필드는 그냥 String일 뿐이지만,
QUser의 name은 StringPath라는 특수 타입입니다. 이 타입 안에는 .eq(), .contains(), .like() 같은 쿼리용 메서드들이 미리 정의되어 있습니다.
정적 인스턴스: 보통 QUser.user라는 기본 인스턴스를 사용하는데, 이는 SQL의 Alias(별칭) 역할을 합니다. (예: select u from User u에서 u)
자바 코드를 JPQL로 변환 (최종적으로 SQL로 변환됨)
완성된 쿼리를 DB에 날리고 결과를 가져옴
dependencies {
implementation "com.pagely:common:2.0.0"
// QueryDSL (core는 common에서 전파, jpa+apt만 추가)
implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta'
annotationProcessor 'com.querydsl:querydsl-apt:5.1.0:jakarta'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
./gradlew clean compileJava
경로에 Q엔티티 파일이 생겨야 한다.

APT (Annotation Processing Tool)?
APT는 자바 컴파일러의 일종의 '플러그인'입니다. 코드에 붙어있는 @Entity 같은 어노테이션을 찾아내서, 컴 파일 시점에 새로운 소스 코드(java 파일)를 자동으로 만들어주는 역할을 합니다.
1. 발견: 컴파일러가 소스 코드를 읽다가 @Entity 를 발견합니다.
2. 호출: "어! 여기 엔티티 있다! APT 일해라!" 하고 APT를 깨웁니다.
3. 생성: APT는 해당 엔티티 정보를 바탕으로 auser. java 같은 Q-Class를 생성합니다.
4. 완성: 개발자는 이 생성된 Q-Class를 사용해 Type-safe한 쿼리를 작성하게 됩니다.
5. 활용: 개발자가 user.nume이라고 오타를 내도 컴파일러가 바로 "그런 필드 없는데?"하고 잡아줍니다.
@Configuration
public class QueryDslConfig {
@PersistenceContext
private EntityManager em;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em);
}
}



내부 API 테스트는 FeignClient를 호출하는 어플리케이션을 추가로 만들어 테스트했다.

외부 API의 Page size 방어 로직은 Pagerequest의 size를 강제하는 방식으로 했는데, 내부 API의 경우 page size의 사용이 더 자유로워야한다고 생각했고, size를 지정하지 않았을 때 상한을 1000으로 지정했다.
나중에 알게 된 사실인데, 별도의 프로퍼티(spring.data.web.pageable.max-page-size)설정 없이 size를 크게 지정하면 spring 자체 상한값으로 2000을 가진다고 한다.
