diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/controller/PerfumeAdminController.java b/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/controller/PerfumeAdminController.java index 1f71ebf..08d6750 100644 --- a/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/controller/PerfumeAdminController.java +++ b/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/controller/PerfumeAdminController.java @@ -98,7 +98,7 @@ public ResponseEntity>> getDeleteRequests() { @Operation(summary = "등록 요청 향수 상세 조회", description = "관리자가 등록 요청된 향수의 세부정보를 조회합니다.(수정 요청조회에도 사용)") - @GetMapping("/register/{perfumeId}") + @GetMapping("/register/{requestId}") public ResponseEntity> getRegisterRequestDetail( @RequestParam String requestType, @PathVariable Long requestId @@ -109,7 +109,7 @@ public ResponseEntity> getRegisterRequestDetail( @Operation(summary = "삭제 요청 상세 조회", description = "관리자가 삭제 요청된 향수의 세부정보를 조회합니다.(수정 요청조회에도 사용)") - @GetMapping("/update/{perfumeId}") + @GetMapping("/update/{requestId}") public ResponseEntity> getUpdateRequestDetail( @RequestParam String requestType, @PathVariable Long requestId diff --git a/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/queryDSLRepository/PerfumeRepositoryCustom.java b/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/queryDSLRepository/PerfumeRepositoryCustom.java new file mode 100644 index 0000000..46ecbc5 --- /dev/null +++ b/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/queryDSLRepository/PerfumeRepositoryCustom.java @@ -0,0 +1,10 @@ +package com.perfumepedia.perfumepedia.domain.perfume.queryDSLRepository; + +import com.perfumepedia.perfumepedia.domain.perfume.entity.Perfume; + +import java.util.List; + +public interface PerfumeRepositoryCustom { + List searchPerfumesByKeyword(String keyword); + +} diff --git a/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/queryDSLRepository/PerfumeRepositoryCustomImpl.java b/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/queryDSLRepository/PerfumeRepositoryCustomImpl.java new file mode 100644 index 0000000..6485a74 --- /dev/null +++ b/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/queryDSLRepository/PerfumeRepositoryCustomImpl.java @@ -0,0 +1,35 @@ +package com.perfumepedia.perfumepedia.domain.perfume.queryDSLRepository; + +import com.perfumepedia.perfumepedia.domain.perfume.entity.Perfume; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +import static com.perfumepedia.perfumepedia.domain.brand.entity.QBrand.brand; +import static com.perfumepedia.perfumepedia.domain.note.entity.QNote.note; +import static com.perfumepedia.perfumepedia.domain.perfume.entity.QPerfume.perfume; +import static com.perfumepedia.perfumepedia.domain.perfumeNote.entity.QPerfumeNote.perfumeNote; + +@RequiredArgsConstructor +public class PerfumeRepositoryCustomImpl implements PerfumeRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List searchPerfumesByKeyword(String keyword) { + return queryFactory + .selectDistinct(perfume) // 중복된 Perfume 데이터 제거 + .from(perfume) + .leftJoin(perfume.brand, brand) + .leftJoin(perfumeNote).on(perfumeNote.perfume.eq(perfume)) + .leftJoin(perfumeNote.note, note) + .where( + brand.name.containsIgnoreCase(keyword) + .or(perfume.name.containsIgnoreCase(keyword)) + .or(note.name.containsIgnoreCase(keyword)) + ) + .fetch(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/repository/PerfumeRepository.java b/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/repository/PerfumeRepository.java index 45f3ed0..bbb1187 100644 --- a/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/repository/PerfumeRepository.java +++ b/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/repository/PerfumeRepository.java @@ -1,6 +1,7 @@ package com.perfumepedia.perfumepedia.domain.perfume.repository; import com.perfumepedia.perfumepedia.domain.perfume.entity.Perfume; +import com.perfumepedia.perfumepedia.domain.perfume.queryDSLRepository.PerfumeRepositoryCustom; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -8,7 +9,7 @@ import java.util.Optional; @Repository -public interface PerfumeRepository extends JpaRepository { +public interface PerfumeRepository extends JpaRepository, PerfumeRepositoryCustom { List findByNameContaining(String keyword); List findByBrand_NameContaining(String keyword); diff --git a/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/service/PerfumeService.java b/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/service/PerfumeService.java index 994548d..41d59c5 100644 --- a/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/service/PerfumeService.java +++ b/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/service/PerfumeService.java @@ -7,6 +7,7 @@ import com.perfumepedia.perfumepedia.domain.perfume.dto.PerfumeUpdateReq; import com.perfumepedia.perfumepedia.domain.perfume.entity.Perfume; import com.perfumepedia.perfumepedia.domain.perfume.entity.RequestPerfume; +import com.perfumepedia.perfumepedia.domain.perfume.queryDSLRepository.PerfumeRepositoryCustom; import com.perfumepedia.perfumepedia.domain.perfume.repository.PerfumeRepository; import com.perfumepedia.perfumepedia.domain.perfume.repository.RequestPerfumeRepository; import com.perfumepedia.perfumepedia.domain.perfumeNote.dto.PerfumeDetailResponse; @@ -21,11 +22,13 @@ import com.perfumepedia.perfumepedia.global.enums.NoneResponse; import com.perfumepedia.perfumepedia.global.handler.AppException; import com.perfumepedia.perfumepedia.global.response.SuccessResponse; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; +import java.util.stream.Collectors; import static com.perfumepedia.perfumepedia.global.enums.ErrorCode.*; import static com.perfumepedia.perfumepedia.global.enums.SuccessCode.*; @@ -38,26 +41,24 @@ public class PerfumeService { private NoteRepository noteRepository; private BrandRepository brandRepository; private RequestPerfumeNoteRepository requestPerfumeNoteRepository; - private RequestPerfumeRepository requestPerfumeRepository; private RequestRepository requestRepository; - @Autowired public PerfumeService(PerfumeRepository perfumeRepository, PerfumeNoteRepository perfumeNoteRepository, - NoteRepository noteRepository, BrandRepository brandRepository, RequestPerfumeNoteRepository requestPerfumeNoteRepository, - RequestPerfumeRepository requestPerfumeRepository, RequestRepository requestRepository) { - this.perfumeNoteRepository = perfumeNoteRepository; + NoteRepository noteRepository, BrandRepository brandRepository, + RequestPerfumeNoteRepository requestPerfumeNoteRepository, + RequestRepository requestRepository) { this.perfumeRepository = perfumeRepository; + this.perfumeNoteRepository = perfumeNoteRepository; this.noteRepository = noteRepository; this.brandRepository = brandRepository; this.requestPerfumeNoteRepository = requestPerfumeNoteRepository; - this.requestPerfumeRepository = requestPerfumeRepository; this.requestRepository = requestRepository; + } /** * 키워드별 향수 검색 - * * @param keyword 검색어 * @return 검색된 향수 리스트 */ @@ -93,6 +94,32 @@ public SuccessResponse> searchPerfumes(String keyword) { return new SuccessResponse<>(SEARCH_COMPLETED, searchResult); } +// +// /** +// * 키워드별 향수 검색 (QueryDSL 사용) +// */ +// public SuccessResponse> searchPerfumes(String keyword) { +// // 키워드가 null이거나 빈 문자열인 경우 예외 처리 +// if (keyword == null || keyword.trim().isEmpty()) { +// throw new AppException(INVALID_SEARCH_KEYWORD); +// } +// +// // QueryDSL로 향수 검색 +// List perfumes = perfumeRepository.searchPerfumesByKeyword(keyword.trim()); +// +// // 검색 결과가 없는 경우 예외 처리 +// if (perfumes.isEmpty()) { +// throw new AppException(PERFUME_NOT_FOUND); +// } +// +// // Perfume -> DTO 변환 +// List searchResult = perfumes.stream() +// .map(PerfumeUpdateReq::toDto) +// .collect(Collectors.toList()); +// +// return new SuccessResponse<>(SEARCH_COMPLETED, searchResult); +// } + /** * 향수 세부 정보 조회 @@ -274,7 +301,6 @@ public SuccessResponse acceptUpdateRequest(Request request) { } - /** * 삭제 요청 처리 */ diff --git a/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/service/RequestPerfumeService.java b/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/service/RequestPerfumeService.java index 0ef5f6b..fddac8a 100644 --- a/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/service/RequestPerfumeService.java +++ b/src/main/java/com/perfumepedia/perfumepedia/domain/perfume/service/RequestPerfumeService.java @@ -33,6 +33,7 @@ import java.util.List; +import static com.perfumepedia.perfumepedia.global.enums.ErrorCode.NOTES_FORMAT_INVALID; import static com.perfumepedia.perfumepedia.global.enums.ErrorCode.PERFUME_NOT_FOUND; import static com.perfumepedia.perfumepedia.global.enums.SuccessCode.*; @@ -335,11 +336,21 @@ private void savePerfumeNote(Perfume perfume, Note note, NoteType type) { } // 타입별로 노트 저장 - private void saveNotes(RequestPerfumeDetailReq dto, Perfume Perfume) { - saveNotesAndPerfumeNote(dto.getTopNote(), Perfume, NoteType.TOP); - saveNotesAndPerfumeNote(dto.getMiddleNote(), Perfume, NoteType.MIDDLE); - saveNotesAndPerfumeNote(dto.getBaseNote(), Perfume, NoteType.BASE); - saveNotesAndPerfumeNote(dto.getSingleNote(), Perfume, NoteType.SINGLE); + private void saveNotes(RequestPerfumeDetailReq dto, Perfume perfume) { + // 싱글 노트가 비어 있으면 탑, 미들, 베이스 노트 저장 + if (dto.getSingleNote() == null || dto.getSingleNote().isEmpty()) { + saveNotesAndPerfumeNote(dto.getTopNote(), perfume, NoteType.TOP); + saveNotesAndPerfumeNote(dto.getMiddleNote(), perfume, NoteType.MIDDLE); + saveNotesAndPerfumeNote(dto.getBaseNote(), perfume, NoteType.BASE); + } + // 탑, 미들, 베이스 노트가 비어 있으면 싱글 노트를 저장 + else if ((dto.getTopNote() == null || dto.getTopNote().isEmpty()) + && (dto.getMiddleNote() == null || dto.getMiddleNote().isEmpty()) + && (dto.getBaseNote() == null || dto.getBaseNote().isEmpty())) { + saveNotesAndPerfumeNote(dto.getSingleNote(), perfume, NoteType.SINGLE); + } else { + throw new AppException(NOTES_FORMAT_INVALID); + } } diff --git a/src/main/java/com/perfumepedia/perfumepedia/domain/perfumeNote/dto/RequestPerfumeDetailReq.java b/src/main/java/com/perfumepedia/perfumepedia/domain/perfumeNote/dto/RequestPerfumeDetailReq.java index 562e30d..a18fe63 100644 --- a/src/main/java/com/perfumepedia/perfumepedia/domain/perfumeNote/dto/RequestPerfumeDetailReq.java +++ b/src/main/java/com/perfumepedia/perfumepedia/domain/perfumeNote/dto/RequestPerfumeDetailReq.java @@ -9,7 +9,7 @@ public class RequestPerfumeDetailReq { private String name; // 향수 이름 - private String brandName; // 브랜드 이름 + private String brandName; // 브랜드 이름 private String topNote; // 탑 노트 private String middleNote; // 미들 노트 private String baseNote; // 베이스 노트 diff --git a/src/main/java/com/perfumepedia/perfumepedia/global/enums/ErrorCode.java b/src/main/java/com/perfumepedia/perfumepedia/global/enums/ErrorCode.java index 3af5882..b0c0e14 100644 --- a/src/main/java/com/perfumepedia/perfumepedia/global/enums/ErrorCode.java +++ b/src/main/java/com/perfumepedia/perfumepedia/global/enums/ErrorCode.java @@ -19,6 +19,9 @@ public enum ErrorCode implements ResponseCode { PERFUME_NOT_FOUND(HttpStatus.NOT_FOUND, "일치하는 향수가 업서용."), NOTE_NOT_FOUND(HttpStatus.NOT_FOUND, "일치하는 노트가 없습니다."), REQUEST_NOT_FOUND(HttpStatus.NOT_FOUND, "요청이 존재하지 않습니다."), + NOTES_FORMAT_INVALID(HttpStatus.BAD_REQUEST, "노트의 형식이 잘못되었습니다. 싱글 노트와 탑/미들/베이스 노트 중 하나만 제공되어야 합니다."), + INVALID_SEARCH_KEYWORD(HttpStatus.BAD_REQUEST, "유효하지 않은 검색어입니다."), + // DTO 관련 INVALID_REQUEST_TYPE(HttpStatus.BAD_REQUEST, "요청 타입이 잘못되었습니다."), diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f1a6195..ef1a275 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,11 +2,21 @@ spring: application: name: perfumepedia-backend datasource: +# url: jdbc:mysql://localhost:3306/perfumePedia?serverTimezone=UTC&characterEncoding=UTF-8 url: jdbc:mysql://localhost:3306/PerfumeProject?serverTimezone=UTC&characterEncoding=UTF-8 username: perfume password: password driver-class-name: com.mysql.cj.jdbc.Driver + jpa: + hibernate: + ddl-auto: update # 스키마 자동 업데이트 설정 + show-sql: true # SQL 쿼리 출력 + properties: + hibernate: + format_sql: true + database-platform: org.hibernate.dialect.MySQL8Dialect + springdoc: api-docs: @@ -26,3 +36,4 @@ logging: org: springframework: security: "trace" +