작성
·
448
0
제가 제대로 이해하고 있는지 제대로 잡고 넘어가려고 합니다.
질문1
먼저 단순한 제목 검색과 페이지처리와 정렬을 구현했을 때
fetchCount 대신에 fetchOne을 사용하기 위해서 별도로 count 쿼리를 생성해서 페이지 처리를 해줍니다.
페이지와 정렬을 처리할 때 동적 정렬을 처리하기 위해서 OrderSpecifier
을 사용해서 동적 정렬 쿼리를 하기 위해서 다음과 같이 설정
http://localhost:9090/v2/members?page=1&sort=memberId,desc
Pageable로 페이지랑 정렬을 받아서 동적으로 뽑아와서 메소드를 만들고 orderBy에 넣었습니다. 이런식으로 처리하는게 맞나요?
질문2
@Repository
@Log4j2
public abstract class Querydsl4RepositorySupport {
// 이 클래스가 다루는 도메인(엔터티)의 클래스
private final Class domainClass;
// 도메인 엔터티에 대한 Querydsl 쿼리를 생성하고 실행
private Querydsl querydsl;
// 데이터베이스와의 상호 작용을 담당하는 JPA의 핵심 객체
private EntityManager entityManager;
// queryFactory를 통해 Querydsl 쿼리를 생성하고 실행합니다.
private JPAQueryFactory queryFactory;
public Querydsl4RepositorySupport(Class<?> domainClass) {
Assert.notNull(domainClass, "Domain class must not be null!");
this.domainClass = domainClass;
}
// Pageable안에 있는 Sort를 사용할 수 있도록 설정한 부분
@Autowired
public void setEntityManager(EntityManager entityManager) {
Assert.notNull(entityManager, "EntityManager must not be null!");
// JpaEntityInformation을 얻기 위해 JpaEntityInformationSupport를 사용합니다.
// 이 정보는 JPA 엔터티에 대한 메타데이터 및 정보를 제공합니다.
JpaEntityInformation entityInformation =
JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager);
// 이는 Querydsl에서 엔터티의 경로를 생성하는 데 사용됩니다.
SimpleEntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE;
// entityInformation을 기반으로 엔티티의 경로를 생성합니다.
EntityPath path = resolver.createPath(entityInformation.getJavaType());
this.entityManager = entityManager;
// querydsl 객체를 생성합니다.
// 이 객체는 Querydsl의 핵심 기능을 사용할 수 있도록 도와줍니다.
// 엔터티의 메타모델 정보를 이용하여 Querydsl의 PathBuilder를 생성하고, 이를 이용하여 Querydsl 객체를 초기화합니다.
this.querydsl = new Querydsl(entityManager, new
PathBuilder<>(path.getType(), path.getMetadata()));
this.queryFactory = new JPAQueryFactory(entityManager);
}
// 해당 클래스의 빈(Bean)이 초기화될 때 자동으로 실행되는 메서드
@PostConstruct
public void validate() {
Assert.notNull(entityManager, "EntityManager must not be null!");
Assert.notNull(querydsl, "Querydsl must not be null!");
Assert.notNull(queryFactory, "QueryFactory must not be null!");
}
// 이 팩토리는 JPA 쿼리를 생성하는 데 사용됩니다.
protected JPAQueryFactory getQueryFactory() {
return queryFactory;
}
// 이 객체는 Querydsl의 핵심 기능을 사용하는 데 도움이 됩니다.
protected Querydsl getQuerydsl() {
return querydsl;
}
// EntityManager는 JPA 엔터티를 관리하고 JPA 쿼리를 실행하는 데 사용됩니다.
protected EntityManager getEntityManager() {
return entityManager;
}
// Querydsl을 사용하여 쿼리의 SELECT 절을 생성하는 메서드입니다.
// expr은 선택할 엔터티나 엔터티의 속성에 대한 표현식입니다.
protected <T> JPAQuery<T> select(Expression<T> expr) {
return getQueryFactory().select(expr);
}
// Querydsl을 사용하여 쿼리의 FROM 절을 생성하는 메서드입니다.
// from은 엔터티에 대한 경로 표현식입니다.
protected <T> JPAQuery<T> selectFrom(EntityPath<T> from) {
return getQueryFactory().selectFrom(from);
}
// 이 메서드는 주어진 contentQuery를 사용하여 Querydsl을 통해 JPA 쿼리를 생성하고 실행하고,
// 그 결과를 Spring Data의 Page 객체로 변환하는 기능을 제공
protected <T> Page<T> applyPagination(Pageable pageable,
Function<JPAQueryFactory, JPAQuery> contentQuery) {
// 1. contentQuery를 사용하여 JPAQuery 객체를 생성
JPAQuery jpaQuery = contentQuery.apply(getQueryFactory());
// 2. Querydsl을 사용하여 페이징 및 정렬된 결과를 가져옴
List<T> content = getQuerydsl().applyPagination(pageable,
jpaQuery).fetch();
// 3. contentQuery를 다시 사용하여 countQuery를 생성
JPAQuery<Long> countQuery = contentQuery.apply(getQueryFactory());
// 4. countQuery를 실행하고 총 레코드 수를 얻음
long total = countQuery.fetchOne();
// 5. content와 pageable 정보를 사용하여 Spring Data의 Page 객체를 생성하고 반환
return PageableExecutionUtils.getPage(content, pageable,
() -> total);
}
// 이 메서드는 contentQuery와 함께 countQuery를 인자로 받아서 사용합니다.
// contentQuery를 사용하여 페이징된 결과를 가져오고, countQuery를 사용하여 전체 레코드 수를 얻습니다.
protected <T> Page<T> applyPagination(Pageable pageable,
Function<JPAQueryFactory, JPAQuery> contentQuery, Function<JPAQueryFactory,
JPAQuery> countQuery) {
JPAQuery jpaContentQuery = contentQuery.apply(getQueryFactory());
List<T> content = getQuerydsl().applyPagination(pageable,
jpaContentQuery).fetch();
JPAQuery<Long> countResult = countQuery.apply(getQueryFactory());
log.info("countResult : " + countResult );
Long total = countResult.fetchOne();
return PageableExecutionUtils.getPage(content, pageable,
() -> total);
}
}
fetchCount() → fetchOne()으로 변경
// count처리 까지 한것
public Page<Member> applyPagination2(MemberSearchCondition condition, Pageable pageable) {
return applyPagination(pageable, contentQuery ->
contentQuery.selectFrom(member)
.leftJoin(member.team, team)
.where(userNameEq(condition.getUserName()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe())
), countQuery -> countQuery
.select(member.count())
.from(member)
.where(userNameEq(condition.getUserName()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe()))
);
}
private BooleanExpression userNameEq(String userName) {
return hasText(userName) ? member.userName.eq(userName) : null;
}
private BooleanExpression teamNameEq(String teamName) {
return hasText(teamName) ? team.name.eq(teamName) : null;
}
private BooleanExpression ageGoe(Integer ageGoe) {
return ageGoe != null ? member.age.goe(ageGoe) : null;
}
private BooleanExpression ageLoe(Integer ageLoe) {
return ageLoe != null ? member.age.loe(ageLoe) : null;
}
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberTestRepository memberTestRepository;
public Page<MemberTeamDTO> search(MemberSearchCondition condition, Pageable pageable) {
Sort sort = pageable.getSort();
PageRequest pageRequest = PageRequest.of(
(int) pageable.getOffset(),
pageable.getPageSize(),
sort
);
Page<Member> resultPage = memberTestRepository.applyPagination2(condition, pageRequest);
return resultPage.map(member -> MemberTeamDTO.builder()
.memberId(member.getId())
.age(member.getAge())
.userName(member.getUserName())
.teamId(member.getTeam().getId())
.teamName(member.getTeam().getName())
.build());
}
}
Sort를 처리할 때 orderBy할 필요 없이 PageRequest.of로 보내주면 된다고 글을 봐서
동적으로 처리할 수 있도록
Sort sort = pageable.getSort();
PageRequest pageRequest = PageRequest.of(
(int) pageable.getOffset(),
pageable.getPageSize(),
sort
);
이렇게 처리했습니다. Post맨으로 돌려본 결과 제개 원하는 대로 페이징, 정렬이 되었는데 이런식으로 하는게 맞나 확인하고 싶어서 질문드립니다.
질문3
질문1 OrderSpecifier
로 동적인 쿼리 처리하고 있는데 Querydsl4RepositorySupport이거를 사용한 이유가 유지보수와 가독성을 더 높여주는 방법이라 이해했는데 맞나요?
답변 2
0
안녕하세요, 인프런 AI 인턴입니다.
Sort 관련하여 Querydsl을 활용한 페이징 및 정렬 처리 방법에 대한 질문으로 보입니다. 관련하여 강사님께서 제공하신 답변 중 일부가 여러분의 질문에 도움이 될 것 같습니다.
강사님의 답변을 요약하자면, PageRequest
객체를 생성할 때 Sort.by("age")
와 같이 정렬 조건을 포함해서 던지면 해당 조건이 함께 적용된다고 합니다. 별도로 orderBy
에 적용하지 않아도 된다는 내용이 있습니다.
이 답변에서 강사님은 Pageable
의 Sort
속성을 검사하는 로직에 대해 언급하고 있으며, 이를 통해 잘못된 정렬 프로퍼티를 요청했을 때 예외를 처리하는 것에 대해 설명하고 있습니다.
Sort
를 변환하여 적용하는 방법에 대한 세부사항은 다음 링크에서 확인하실 수 있습니다.여기서는 OrderSpecifier
를 사용하여 동적 정렬을 적용하는 방법에 대해 논의되고 있습니다. 강사님께서는 직접 OrderSpecifier
를 구성하여 적용하는 방법을 제안하고 있습니다.
강의 내용을 확인하시고 각 링크를 참고하시어 정렬 처리 로직을 개선하는데 도움이 되길 바랍니다.