-
SpringBoot에서 QueryDSL 설정 및 사용Language/Java 2024. 8. 16. 16:57
1. 환경 설정
1.1. Gradle 설정
- build.gradle
... // 프로젝트에서 사용하는 전역 변수를 설정 ext { set('queryDslVersion', "5.0.0") } ... dependencies { ... // QueryDSL implementation "com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta" annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" } // ##### QueryDSL 추가 시작 def querydslDir = "$buildDir/generated/querydsl" // java source set 에 querydsl QClass 위치 추가 sourceSets { main.java.srcDirs += [ querydslDir ] } // querydsl QClass 파일 생성 위치를 지정 tasks.withType(JavaCompile).configureEach { options.getGeneratedSourceOutputDirectory().set(file(querydslDir)) } // gradle clean 시에 QClass 디렉토리 삭제 clean.doLast { file(querydslDir).deleteDir() } // ##### QueryDSL 추가 종료 ...
1.2. QClass 생성 및 확인
clean, compileJava 순서대로 실행
QClass 생성되었는지 확인한다.
1.3. Config 설정
- QueryDslConfig
package net.neoflat.station.config; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class QueryDslConfig { @PersistenceContext private EntityManager entityManager; @Bean public JPAQueryFactory jpaQueryFactory() { return new JPAQueryFactory(entityManager); } }
2. 사용방법
2.1. Controller Layer
... @Operation(summary = "목록 조회", description = "목록 데이터를 조회한다.") @GetMapping("/contents") public ResponseEntity<ResponseDto> getList(@ModelAttribute @Valid ContentRequestDto.ContentListRequest listRequestDto) { listRequestDto.setExposureType(Channel.GOBANG.getBit()); // 키워드 등록 if (listRequestDto.getKeyword() != null && !listRequestDto.getKeyword().isEmpty()) { keywordPopularityService.insertKeyword(listRequestDto.getKeyword()); } // 요청한 픽스 데이터만 if (listRequestDto.getPicksType() != null && !listRequestDto.getPicksType().isEmpty() && listRequestDto.getPicksNames() != null && !listRequestDto.getPicksNames().isEmpty()) { List<Long> picksNos = commonPicksService.findByExposureAndTypeAndName(Channel.GOBANG.getBit(), listRequestDto.getPicksType(), listRequestDto.getPicksNames()); listRequestDto.setPicksNos(picksNos); } Page<BaseDto> contentPage = contentService.getList(Channel.GOBANG, listRequestDto); ResponseDto res = ResponseListDto.builder() .status(HttpStatus.OK.value()) .message("성공적으로 조회했습니다.") .items(contentPage.getContent()) .totalElements(contentPage.getTotalElements()) .hasMore(contentPage.getContent().size() == listRequestDto.getPageSize()) .build(); return new ResponseEntity<>(res, HttpStatus.OK); } ...
2.2. Service Layer
... public Page<BaseDto> getList(Channel channel, ContentRequestDto.ContentListRequest listRequestDto) { // Repository Command DTO 변환 ContentCommandRequestDto.ListRequestDto listRequestCommandDto = ContentRequestDto.convertToListRequestCommandDTO(listRequestDto); Pageable pageable = QueryDslUtil.getPageable(listRequestDto.getPageNo(), listRequestDto.getPageSize(), listRequestDto.getSort()); if (channel == Channel.GOBANG || channel == Channel.UCEO) { if (listRequestDto.getDisplayTypes() == null || !listRequestDto.getDisplayTypes().isEmpty()) { List<String> displayTypes = new ArrayList<>(); displayTypes.add("01"); listRequestDto.setDisplayTypes(displayTypes); } } List<ContentCommandResponseDto.ListDto> commandList = communityRepository.findList(channel, pageable, listRequestCommandDto); long totalCount = communityRepository.countList(channel, listRequestCommandDto); // Response DTO 변환 List<ContentResponseDto.ListDto> list = commandList.stream().map(ContentResponseDto.ListDto::new).toList(); // 2. 이미지 리사이징 설정 List<BaseDto> updatedList = list.stream() .peek(listDto -> { ContentResponseDto.ListThumbnailDto thumbnail = new ContentResponseDto.ListThumbnailDto(); thumbnail.setTitleImage1(NfUtils.getResizedImageUri("community", listDto.getTitleImage(), String.valueOf(listDto.getNo()), 312 * 2, 208 * 2, null)); thumbnail.setTitleImage2(NfUtils.getResizedImageUri("community", listDto.getTitleImage(), String.valueOf(listDto.getNo()), 48 * 2, 48 * 2, null)); thumbnail.setTitleImage3(NfUtils.getResizedImageUri("community", listDto.getTitleImage(), String.valueOf(listDto.getNo()), 56 * 2, 56 * 2, null)); thumbnail.setWeeklyTitleImage1(NfUtils.getResizedImageUri("community", listDto.getTitleImage(), String.valueOf(listDto.getNo()), -1, 164 * 2, null)); thumbnail.setWeeklyTitleImage2(NfUtils.getResizedImageUri("community", listDto.getTitleImage(), String.valueOf(listDto.getNo()), 328 * 2, 164 * 2, null)); thumbnail.setSeriesTitleImage2(NfUtils.getResizedImageUri("community", listDto.getTitleImage(), String.valueOf(listDto.getNo()), -1, 128 * 2, null)); thumbnail.setSeriesTitleImage2(NfUtils.getResizedImageUri("community", listDto.getTitleImage(), String.valueOf(listDto.getNo()), -1, 180 * 2, null)); thumbnail.setUserImage(NfUtils.getResizedImageUri("user", listDto.getUserImage(), String.valueOf(listDto.getRegUserNo()), 18 * 2, 18 * 2, null)); thumbnail.setTargetDirectImageUrl(NfUtils.getResizedImageUri("rentalHouse", listDto.getTargetDirectImageUrl(), String.valueOf(listDto.getTargetId()), 16 * 2, 19 * 2, null)); listDto.setThumbnail(thumbnail); }) .collect(Collectors.toList()); return new PageImpl<>(updatedList, pageable, totalCount); } ...
2.3. Repository Layer
@RequiredArgsConstructor @Repository public class CommunityRepositoryImpl implements CommunityRepositoryCustom { private final JPAQueryFactory queryFactory; @Override public List<ContentCommandResponseDto.ListDto> findList(Channel channel, Pageable pageable, ContentCommandRequestDto.ListRequestDto listRequestDto) { NumberPath<Long> commentCountAlias = Expressions.numberPath(Long.class, "commentCount"); NumberPath<Long> likeCountAlias = Expressions.numberPath(Long.class, "likeCount"); // 기본 필드 추가 List<Expression<?>> fields = new ArrayList<>(List.of( community.no, community.categoryCode, community.exposureType, ... ExpressionUtils.as(this.getCommentCountSubQuery(), "commentCount"), ExpressionUtils.as(this.getLikeCountSubQuery(), "likeCount"), ExpressionUtils.as(this.getPicksNameSubQuery(), "picksName"), this.getRegUserNameSubQuery().as("regUserName"), this.getModUserNameSubQuery().as("modUserName"), category.name.as("categoryName"), category.typeCode.as("categoryTypeCode"), ExpressionUtils.as(this.getCategoryTypeNameSubQuery(), "categoryTypeName") )); if (channel == Channel.GOBANG) { // 고방 전용 fields.add(user.nickname.as("userNickname")); fields.add(ExpressionUtils.as(this.getUserImageSubQuery(), "userImage")); ... fields.add(rentalHouse.no.as("targetId")); ... fields.add(image.fileName.as("targetDirectImageUrl")); fields.add(this.getTargetStatusSubQuery().as("targetStatus")); fields.add(ExpressionUtils.as(this.getZzimCountSubQuery(), "zzimCount")); } // 기본 쿼리 작성 JPAQuery<ContentCommandResponseDto.ListDto> query = queryFactory .select(Projections.fields(ContentCommandResponseDto.ListDto.class, fields.toArray(new Expression<?>[0]))) .from(community) .leftJoin(content1).on(content1.no.eq(community.contentId)) .leftJoin(user).on(user.no.eq(community.regUserNo)) .leftJoin(category).on(category.code.eq(community.categoryCode)) .leftJoin(comment).on(comment.contentId.eq(community.contentId) .and(comment.target.eq("RENTAL_HOUSE"))) .leftJoin(rentalHouse).on(rentalHouse.no.eq(comment.targetId)) .leftJoin(rentalHouseArea).on(rentalHouseArea.rentalHouseId.eq(rentalHouse.no) .and(rentalHouseArea.imageUrl.isNotNull())) .leftJoin(image).on(image.no.eq(rentalHouse.imageId) .and(image.seq.eq(1))) .where(this.getFindListWhereBuilder(listRequestDto)) .offset(pageable.getOffset()) .limit(pageable.getPageSize()); // Sort 값이 있는 경우 적용, 없는 경우 기본 정렬 적용 if (pageable.getSort().isSorted()) { List<OrderSpecifier<?>> orderSpecifiers = new ArrayList<>(); Set<String> appliedSorts = new HashSet<>(); // 이미 적용된 정렬 필드를 추적하는 Set for (Sort.Order order : pageable.getSort()) { if (appliedSorts.contains(order.getProperty())) { continue; // 이미 적용된 정렬 필드는 생략 } if (order.getProperty().equals("commentCount")) { if (order.isAscending()) { orderSpecifiers.add(commentCountAlias.asc()); } else { orderSpecifiers.add(commentCountAlias.desc()); } } else if (order.getProperty().equals("likeCount")) { if (order.isAscending()) { orderSpecifiers.add(likeCountAlias.asc()); } else { orderSpecifiers.add(likeCountAlias.desc()); } } else { orderSpecifiers.addAll(applySorting(pageable, community, appliedSorts)); // 중복 체크를 위해 appliedSorts 전달 } // 적용된 정렬 필드 추가 appliedSorts.add(order.getProperty()); } query.orderBy(orderSpecifiers.toArray(new OrderSpecifier[0])); } else { query.orderBy(community.no.desc()); } return query.fetch(); } @Override public long countList(Channel channel, ContentCommandRequestDto.ListRequestDto listRequestDto) { Long count = queryFactory .select(community.count()) .from(community) .leftJoin(content1).on(content1.no.eq(community.contentId)) .leftJoin(user).on(user.no.eq(community.regUserNo)) .leftJoin(category).on(category.code.eq(community.categoryCode)) .leftJoin(comment).on(comment.contentId.eq(community.contentId) .and(comment.target.eq("RENTAL_HOUSE"))) .leftJoin(rentalHouse).on(rentalHouse.no.eq(comment.targetId)) .leftJoin(rentalHouseArea).on(rentalHouseArea.rentalHouseId.eq(rentalHouse.no) .and(rentalHouseArea.imageUrl.isNotNull())) .leftJoin(image).on(image.no.eq(rentalHouse.imageId) .and(image.seq.eq(1))) .where(this.getFindListWhereBuilder(listRequestDto)) .fetchOne(); return count != null ? count : 0L; } @Override public Optional<ContentCommandResponseDto.DetailDto> findByNo(Channel channel, Long no) { // 기본 필드 추가 List<Expression<?>> fields = new ArrayList<>(List.of( community.no, community.categoryCode, community.exposureType, ... ExpressionUtils.as(this.getPicksNameSubQuery(), "picksName"), this.getRegUserNameSubQuery().as("regUserName"), this.getModUserNameSubQuery().as("modUserName"), category.name.as("categoryName"), category.typeCode.as("categoryTypeCode"), ExpressionUtils.as(this.getCategoryTypeNameSubQuery(), "categoryTypeName") )); if (channel == Channel.GOBANG) { // 고방 전용 fields.add(user.nickname.as("userNickname")); fields.add(ExpressionUtils.as(this.getUserImageSubQuery(), "userImage")); fields.add(rentalHouse.no.as("targetId")); fields.add(ExpressionUtils.as(this.getZzimCountSubQuery(), "zzimCount")); ... } // 기본 쿼리 작성 JPAQuery<ContentCommandResponseDto.DetailDto> query = queryFactory .select(Projections.fields(ContentCommandResponseDto.DetailDto.class, fields.toArray(new Expression<?>[0]))) .from(community) .leftJoin(user).on(user.no.eq(community.regUserNo)) .leftJoin(category).on(category.code.eq(community.categoryCode)) .leftJoin(comment).on(comment.contentId.eq(community.contentId) .and(comment.target.eq("RENTAL_HOUSE"))) .leftJoin(rentalHouse).on(rentalHouse.no.eq(comment.targetId)) .leftJoin(rentalHouseArea).on(rentalHouseArea.rentalHouseId.eq(rentalHouse.no) .and(rentalHouseArea.imageUrl.isNotNull())) .leftJoin(image).on(image.no.eq(rentalHouse.imageId) .and(image.seq.eq(1))) .where(community.no.eq(no)); ContentCommandResponseDto.DetailDto result = query.fetchOne(); // 결과를 Optional로 감싸서 반환 return Optional.ofNullable(result); } private BooleanBuilder getFindListWhereBuilder(ContentCommandRequestDto.ListRequestDto listRequestDto) { // 동적 WHERE 절 구성 BooleanBuilder whereBuilder = new BooleanBuilder(); // 기본 조건 추가 whereBuilder.and(community.deleteYn.eq("N")); if (listRequestDto.getDisplayTypes() != null) { whereBuilder.and(community.displayType.in(listRequestDto.getDisplayTypes())); } else { // whereBuilder.and(community.displayType.eq("01")); } if (listRequestDto.getTypeCodes() != null) { whereBuilder.and(category.typeCode.in(listRequestDto.getTypeCodes())); } if (listRequestDto.getCategoryCodes() != null) { whereBuilder.and(community.categoryCode.in(listRequestDto.getCategoryCodes())); } if (listRequestDto.getNo() != null) { whereBuilder.and(community.no.eq(listRequestDto.getNo())); } if (listRequestDto.getExposureType() != null) { whereBuilder.and(QueryDslUtil.matchExposureType(community.exposureType, listRequestDto.getExposureType())); } if (listRequestDto.getTitle() != null && !listRequestDto.getTitle().isEmpty()) { whereBuilder.and(community.title.contains(listRequestDto.getTitle())); } if (listRequestDto.getTags() != null && !listRequestDto.getTags().isEmpty()) { whereBuilder.and(community.tags.contains(listRequestDto.getTags())); } if (listRequestDto.getAssortCodes() != null && !listRequestDto.getAssortCodes().isEmpty()) { whereBuilder.and(picks.assortCode.in(listRequestDto.getAssortCodes())); } if (listRequestDto.getTagName() != null && !listRequestDto.getTagName().isEmpty()) { // 서브쿼리 작성 JPQLQuery<Long> subQuery = JPAExpressions .select(tagItem.communityId) .from(tag) .leftJoin(tagItem).on(tagItem.tagNo.eq(tag.no)) .where(tag.name.eq(listRequestDto.getTagName())); whereBuilder.and(community.no.in(subQuery)); } if (listRequestDto.getTagGroupId() != null && !listRequestDto.getTagGroupId().isEmpty()) { // 서브쿼리 작성 JPQLQuery<Long> subQuery = JPAExpressions .select(tagItem.communityId) .from(tag) .leftJoin(tagItem).on(tagItem.tagNo.eq(tag.no)) .leftJoin(tagGroupRel).on(tagGroupRel.tagNo.eq(tag.no)) .where(tagGroupRel.tagGroupId.eq(listRequestDto.getTagGroupId())); whereBuilder.and(community.no.in(subQuery)); } if (listRequestDto.getPicksType() != null && !listRequestDto.getPicksType().isEmpty()) { whereBuilder.and(community.picksType.eq(listRequestDto.getPicksType())); } if (listRequestDto.getPicksNo() != null && listRequestDto.getPicksNo() > 0L) { whereBuilder.and(community.picksNo.eq(listRequestDto.getPicksNo())); } if (listRequestDto.getPicksNos() != null) { whereBuilder.and(community.picksNo.in(listRequestDto.getPicksNos())); } if (listRequestDto.getKeyword() != null && !listRequestDto.getKeyword().isEmpty()) { whereBuilder.and( community.title.contains(listRequestDto.getKeyword()) .or(community.tags.contains(listRequestDto.getKeyword())) .or(content1.contentText.contains(listRequestDto.getKeyword())) ); } if (listRequestDto.getContentText() != null && !listRequestDto.getContentText().isEmpty()) { whereBuilder.and(content1.contentText.contains(listRequestDto.getContentText())); } if (listRequestDto.getRecentDays() != null && listRequestDto.getRecentDays() > 0) { // DATEDIFF(NOW(), A.REG_DT) <= #{RECENT_DAYS} NumberTemplate<Integer> dateDiff = Expressions.numberTemplate(Integer.class, "DATEDIFF(NOW(), {0})", community.regDt); whereBuilder.and(dateDiff.loe(listRequestDto.getRecentDays())); } if (listRequestDto.getRegUserNo() != null && listRequestDto.getRegUserNo() > 0L) { whereBuilder.and(community.regUserNo.eq(listRequestDto.getRegUserNo())); } if (listRequestDto.getLimitType() != null && !listRequestDto.getLimitType().isEmpty() && listRequestDto.getLimitType().equals("CREATED_RECENT_14_DAYS")) { // 현재 시간 LocalDateTime now = LocalDateTime.now(); // 14일 전 시간 LocalDateTime fourteenDaysAgo = now.minusDays(14); // 날짜 포맷 지정 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); // 날짜 포맷을 사용하여 regDt를 비교할 값으로 변환 DateTemplate<String> regDtFormatted = Expressions.dateTemplate(String.class, "DATE_FORMAT({0}, '%Y%m%d')", community.regDt); // BETWEEN 조건 적용 BooleanExpression dateBetween = regDtFormatted.between( Expressions.constant(fourteenDaysAgo.format(formatter)), Expressions.constant(now.format(formatter)) ); whereBuilder.and(dateBetween); } return whereBuilder; } private JPQLQuery<Long> getCommentCountSubQuery() { return JPAExpressions .select(comment.count()) .from(comment) .where(comment.target.eq("COMMUNITY") .and(comment.targetId.eq(community.no)) .and(comment.blindNotiClusterId.isNull()) .and(comment.deleteYn.eq("N"))); } private JPQLQuery<Long> getLikeCountSubQuery() { return JPAExpressions .select(opinion.count()) .from(opinion) .where(opinion.targetCd.eq(OpnionTarget.CONTENT.code) .and(opinion.targetId.eq(community.contentId))); } private JPQLQuery<String> getPicksNameSubQuery() { return JPAExpressions .select(picks.picksName) .from(picks) .where(picks.no.eq(community.picksNo)); } private JPQLQuery<String> getUserImageSubQuery() { return JPAExpressions .select(image.fileName) .from(image) .where(image.no.eq(user.userImageId)); } private JPQLQuery<String> getCategoryTypeNameSubQuery() { return JPAExpressions .select(commCode.name) .from(commCode) .where(commCode.code.eq(category.typeCode)); } private SimpleExpression<String> getRegUserNameSubQuery() { return new CaseBuilder() .when(community.regUserType.eq("10")) .then( JPAExpressions .select(user.nickname) .from(user) .where(user.no.eq(community.regUserNo)) ) .when(community.regUserType.eq("20")) .then( JPAExpressions .select(member.name) .from(member) .where(member.no.eq(community.regUserNo)) ) .otherwise(Expressions.asString("")); } private SimpleExpression<String> getModUserNameSubQuery() { return new CaseBuilder() .when(community.modUserType.eq("10")) .then( JPAExpressions .select(user.nickname) .from(user) .where(user.no.eq(community.modUserNo)) ) .when(community.modUserType.eq("20")) .then( JPAExpressions .select(member.name) .from(member) .where(member.no.eq(community.modUserNo)) ) .otherwise(Expressions.asString("")); } private SimpleExpression<String> getTargetStatusSubQuery() { String currentDateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm")); return new CaseBuilder() .when( JPAExpressions.select(rentalHouseSchedule.endDt.max()) // 명시적 타입 변환 .from(rentalHouseSchedule) .where(rentalHouseSchedule.rentalHouseId.eq(rentalHouse.no)) .lt(currentDateTime) ) .then(Expressions.constant("EXPIRE")) .when( Expressions.asString(currentDateTime) .between( JPAExpressions.select(rentalHouseSchedule.beginDt.min()) // 명시적 타입 변환 .from(rentalHouseSchedule) .where(rentalHouseSchedule.rentalHouseId.eq(rentalHouse.no)), JPAExpressions.select(rentalHouseSchedule.endDt.max()) // 명시적 타입 변환 .from(rentalHouseSchedule) .where(rentalHouseSchedule.rentalHouseId.eq(rentalHouse.no)) ) ) .then(Expressions.constant("RECRUIT")) .when( JPAExpressions.select(rentalHouseSchedule.beginDt.min()) // 명시적 타입 변환 .from(rentalHouseSchedule) .where(rentalHouseSchedule.rentalHouseId.eq(rentalHouse.no)) .gt(currentDateTime) ) .then(Expressions.constant("WAIT")) .otherwise(Expressions.asString("")); } @Override public Optional<List<ContentCommandResponseDto.ListDto>> getRecent3ListByPicksNo(Long picksNo) { JPAQuery<ContentCommandResponseDto.ListDto> query = queryFactory .select(Projections.fields( ContentCommandResponseDto.ListDto.class, community.no, community.categoryCode, community.exposureType, community.picksType, community.picksNo, community.title, community.contentId, community.titleImage, community.viewCount, community.tags, community.blindNotiClusterId, community.displayType, community.regUserType, community.modUserType, community.regUserNo, community.modUserNo, community.regDt, community.modDt )) .from(community) .where(community.picksNo.eq(picksNo) .and(community.displayType.eq("01")) ) .orderBy(community.no.desc()) .limit(3); List<ContentCommandResponseDto.ListDto> result = query.fetch(); // 결과를 Optional로 감싸서 반환 return Optional.ofNullable(result); } private BooleanBuilder getFindNearListWhereBuilder(ContentCommandRequestDto.NearListRequestDto listRequestDto) { // 동적 WHERE 절 구성 BooleanBuilder whereBuilder = new BooleanBuilder(); if (listRequestDto.getExposureType() != null) { whereBuilder.and(QueryDslUtil.matchExposureType(community.exposureType, listRequestDto.getExposureType())); } return whereBuilder; } @Override public Optional<List<ContentCommandResponseDto.ListDto>> findNearList(ContentCommandRequestDto.NearListRequestDto listRequestDto) { JPAQuery<ContentCommandResponseDto.ListDto> query = queryFactory .select(Projections.fields( ContentCommandResponseDto.ListDto.class, community.no, community.categoryCode, community.exposureType, community.picksType, community.picksNo, community.title, community.contentId, community.titleImage, community.viewCount, community.tags, community.blindNotiClusterId, community.displayType, community.regUserType, community.modUserType, community.regUserNo, community.modUserNo, community.regDt, community.modDt )) .from(community) .where(this.getFindNearListWhereBuilder(listRequestDto)) .orderBy(community.regDt.asc()) .limit(3); List<ContentCommandResponseDto.ListDto> result = query.fetch(); // 결과를 Optional로 감싸서 반환 return Optional.ofNullable(result); } private JPQLQuery<Long> getZzimCountSubQuery() { return JPAExpressions .select(opinion.count()) .from(opinion) .where(opinion.targetCd.eq(OpnionTarget.COMMUNITY.code) .and(opinion.targetId.eq(community.no))); } private List<OrderSpecifier<?>> applySorting(Pageable pageable, QCommunity community, Set<String> appliedSorts) { List<OrderSpecifier<?>> orderSpecifiers = new ArrayList<>(); for (Sort.Order sortOrder : pageable.getSort()) { if (appliedSorts.contains(sortOrder.getProperty())) { continue; // 이미 적용된 정렬 필드는 생략 } PathBuilder pathBuilder = new PathBuilder<>(community.getType(), community.getMetadata()); OrderSpecifier<?> orderSpecifier = new OrderSpecifier<>( sortOrder.isAscending() ? Order.ASC : Order.DESC, pathBuilder.get(sortOrder.getProperty()) ); orderSpecifiers.add(orderSpecifier); // 중복 방지를 위해 추가된 필드 추적 appliedSorts.add(sortOrder.getProperty()); } return orderSpecifiers; // 정렬 조건 리스트를 반환 } }
2.4. 조회용 DTO
참고로 Swagger를 사용한다면 Inner Class형태로 DTO를 사용한다면 ContentRequestDto.ContentListRequest 형태로 다른 DTO의 Inner Class 명과 겹치면 안된다.
예를 들어 ContentRequestDto.ListRequest, Test1TRequestDto.ListRequest, Test2TRequestDto.ListRequest 이렇게 사용한다면 ListRequest 겹쳐서 Swagger가 잘못 인식한다. (Swagger 버그인것 같은데 찾는데 오래 걸렸다 ㅠ)package net.neoflat.gobang.dto; import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import net.neoflat.common.dto.BaseDto; import net.neoflat.common.dto.RequestListDto; import net.neoflat.common.dto.content.ContentCommandRequestDto; import net.neoflat.common.dto.file.FileItemDto; public class ContentRequestDto { @Getter @Setter @AllArgsConstructor @NoArgsConstructor @ToString @Schema(description = "조회 DTO") public static class ContentListRequest extends RequestListDto { @Schema(description = "페이지 번호", defaultValue = "1") private int pageNo; @Schema(description = "페이지 크기", defaultValue = "10") private int pageSize; @Schema(description = "정렬 방식", defaultValue = "regDt,DESC") private String sort; private Long no; private Integer exposureType; private String tags; private String title; private String tagName; private String tagGroupId; private List<String> typeCodes; private List<Long> picksNos; ... } public static ContentCommandRequestDto.ListRequestDto convertToListRequestCommandDTO(ContentListRequest listRequestDto) { return ContentCommandRequestDto.ListRequestDto.builder() .pageNo(listRequestDto.getPageNo()) .pageSize(listRequestDto.getPageSize()) .sort(listRequestDto.getSort()) ... .keyword(listRequestDto.getKeyword()) .recentDays(listRequestDto.getRecentDays()) .build(); } @Getter @Setter @AllArgsConstructor @NoArgsConstructor @ToString @Schema(description = "픽스관리 조회 DTO") public static class NearListRequestDto extends RequestListDto { @Schema(description = "정렬 방식", defaultValue = "regDt,DESC") private String sort; } @Getter @Setter @AllArgsConstructor @NoArgsConstructor @ToString public static class ContentSave extends BaseDto { private Long no; private Long picksNo; private String categoryCode; ... private List<String> tagItems; private List<FileItemDto> fileItems; } }
3. 예시
member.username.eq("member1") // username = 'member1' member.username.ne("member1") //username != 'member1' member.username.contains("member1") // username like '%member1%' member.username.isNotNull() // username is not null member.age.in(10, 20) // age in (10,20) member.age.notIn(10, 20) // age not in (10, 20) member.age.between(10,30) // between 10, 30 member.age.goe(30) // age >= 30 member.age.gt(30) // age > 30 member.age.loe(30) // age <= 30 member.age.lt(30) // age < 30 member.username.like("member%") // like 검색 member.username.contains("member") // like ‘%member%’ 검색 member.username.startsWith("member") // like ‘member%’ 검색
이전에 있던 분이 myBatis로 작업할건 JPA & QueryDSL로 변환중인데.. 너무 빡세다..
JPA를 사용하려면 설계할때부터 고려해야할 부분이 많은것 같다.
안그럼 쿼리짤때 헬이다.
하지만 다 바꾸고 말리라!
'Language > Java' 카테고리의 다른 글
springboot 3.x + JPA + QueryDSL에서 p6spy 적용 (1) 2024.09.05 비트마스크 적용 (QueryDSL & Hibernate 6.x) (0) 2024.08.28 SpringBoot에서 엑셀파일 암호화 후 내려받기 (0) 2024.06.18 Java Enum 적용 (0) 2024.01.15 문자열 날짜 포멧 변경 (0) 2023.04.18