From dff607e8a07ab7b812c40566198dcc2265066637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=92=E1=85=A9=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=A5=E1=86=A8?= Date: Mon, 5 May 2025 20:37:15 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EB=B6=84=EB=A6=AC=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/controller/ChatController.java | 11 ++++++----- .../member/controller/MemberController.java | 15 ++++++++------- .../schedule/controller/ScheduleController.java | 9 +++++---- .../travel/controller/TravelController.java | 14 +++++++------- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/jett/domain/chat/controller/ChatController.java b/src/main/java/com/jett/domain/chat/controller/ChatController.java index 8b2b8ba..55865e1 100644 --- a/src/main/java/com/jett/domain/chat/controller/ChatController.java +++ b/src/main/java/com/jett/domain/chat/controller/ChatController.java @@ -3,6 +3,7 @@ import com.jett.domain.chat.dto.ChatMessageDto; import com.jett.domain.chat.dto.ChatRoomDto; import com.jett.domain.chat.dto.ChatRoomInfoDto; +import com.jett.domain.chat.service.ChatService; import com.jett.domain.chat.service.ChatServiceImpl; import com.jett.global.common.CustomApiResponse; import com.jett.global.config.security.CustomUserDetails; @@ -21,35 +22,35 @@ @RestController @AllArgsConstructor public class ChatController { - private final ChatServiceImpl chatServiceImpl; + private final ChatService chatService; private final SimpMessagingTemplate template; @Operation(summary = "채팅방 생성", description = "채팅방 생성하기") @PostMapping("/chat/createRoom") public ResponseEntity> createChatRoom(@AuthenticationPrincipal CustomUserDetails customUserDetails, @RequestBody ChatRoomDto chatRoomDto) { Long userId = customUserDetails.getId(); - long roomId = chatServiceImpl.createRoom(userId, chatRoomDto); + long roomId = chatService.createRoom(userId, chatRoomDto); return ResponseEntity.status(HttpStatus.OK).body(CustomApiResponse.onSuccess(roomId)); } @Operation(summary = "메시지 전송", description = "메시지 전송하기") @MessageMapping("/sendMessage") public void sendMessage(@Payload ChatMessageDto chatMessageDto) { - chatServiceImpl.saveMessage(chatMessageDto); + chatService.saveMessage(chatMessageDto); template.convertAndSend("/sub/chat/room/" + chatMessageDto.getRoomId(), chatMessageDto.getMessage()); } @Operation(summary = "채팅방 불러오기", description = "채팅방 불러오기") @GetMapping("/chat/info/{chatroomId}") public ResponseEntity> getChatroom(@PathVariable("chatroomId") Long chatroomId) { - ChatRoomInfoDto chatRoomInfoDto = chatServiceImpl.getChatroom(chatroomId); + ChatRoomInfoDto chatRoomInfoDto = chatService.getChatroom(chatroomId); return ResponseEntity.status(HttpStatus.OK).body(CustomApiResponse.onSuccess(chatRoomInfoDto)); } @Operation(summary = "채팅 내용 불러오기", description = "특정 채팅방에서 채팅 내용 불러오기") @GetMapping("/chat/{chatroomId}/getMessages") public ResponseEntity>> getMessage(@PathVariable("chatroomId") Long chatroomId) { - List messages = chatServiceImpl.getMessages(chatroomId); + List messages = chatService.getMessages(chatroomId); return ResponseEntity.status(HttpStatus.OK).body(CustomApiResponse.onSuccess(messages)); } diff --git a/src/main/java/com/jett/domain/member/controller/MemberController.java b/src/main/java/com/jett/domain/member/controller/MemberController.java index 09176a1..42b377a 100644 --- a/src/main/java/com/jett/domain/member/controller/MemberController.java +++ b/src/main/java/com/jett/domain/member/controller/MemberController.java @@ -1,5 +1,6 @@ package com.jett.domain.member.controller; +import com.jett.domain.member.service.MemberService; import com.jett.global.config.security.CustomUserDetails; import com.jett.global.common.CustomApiResponse; import com.jett.domain.member.service.MemberServiceImpl; @@ -20,33 +21,33 @@ @RestController @AllArgsConstructor public class MemberController { - private final MemberServiceImpl memberServiceImpl; + private final MemberService memberService; @Operation(summary = "회원 가입", description = "새로운 회원을 등록합니다.") @PostMapping("/signUp") public ResponseEntity> signUp(@RequestBody MemberInfoRequestDto memberInfoRequestDto) { - Long memberId = memberServiceImpl.signUp(memberInfoRequestDto); + Long memberId = memberService.signUp(memberInfoRequestDto); return ResponseEntity.status(HttpStatus.CREATED).body(CustomApiResponse.onSuccess(memberId)); } @Operation(summary = "로그인", description = "회원 로그인을 처리합니다.") @PostMapping("/login") public ResponseEntity> login(@RequestBody LoginRequestDto loginRequestDto) { - TokenResponseDto tokenResponseDto = memberServiceImpl.login(loginRequestDto); - memberServiceImpl.updateLastLoginDate(loginRequestDto.getEmail()); + TokenResponseDto tokenResponseDto = memberService.login(loginRequestDto); + memberService.updateLastLoginDate(loginRequestDto.getEmail()); return ResponseEntity.status(HttpStatus.OK).body(CustomApiResponse.onSuccess(tokenResponseDto)); } @Operation(summary = "카카오 로그인", description = "카카오 로그인을 처리합니다.") @GetMapping("/kakao") public RedirectView kakaoConnect() { - String url = memberServiceImpl.kakaoConnect(); + String url = memberService.kakaoConnect(); return new RedirectView(url); } @GetMapping("/kakao/callback") public ResponseEntity> kakaoLogin(@RequestParam("code") String code) { - TokenResponseDto tokenResponseDto = memberServiceImpl.getKakaoToken(code); + TokenResponseDto tokenResponseDto = memberService.getKakaoToken(code); return ResponseEntity.status(HttpStatus.OK).body(CustomApiResponse.onSuccess(tokenResponseDto)); } @@ -54,7 +55,7 @@ public ResponseEntity> kakaoLogin(@RequestPa @GetMapping("/getInfo") public ResponseEntity> getMember(@AuthenticationPrincipal CustomUserDetails customUserDetails) { Long userId=customUserDetails.getId(); - MemberDto memberDto = memberServiceImpl.getMember(userId); + MemberDto memberDto = memberService.getMember(userId); return ResponseEntity.status(HttpStatus.OK).body(CustomApiResponse.onSuccess(memberDto)); } diff --git a/src/main/java/com/jett/domain/schedule/controller/ScheduleController.java b/src/main/java/com/jett/domain/schedule/controller/ScheduleController.java index c0736ef..aa6203f 100644 --- a/src/main/java/com/jett/domain/schedule/controller/ScheduleController.java +++ b/src/main/java/com/jett/domain/schedule/controller/ScheduleController.java @@ -1,5 +1,6 @@ package com.jett.domain.schedule.controller; +import com.jett.domain.schedule.service.ScheduleService; import com.jett.global.common.CustomApiResponse; import com.jett.global.config.security.CustomUserDetails; import com.jett.domain.schedule.dto.ScheduleRequest; @@ -21,12 +22,12 @@ @AllArgsConstructor public class ScheduleController { - private final ScheduleServiceImpl scheduleServiceImpl; + private final ScheduleService scheduleService; @Operation(summary = "여행 일정 추가", description = "여행 일정 추가") @PostMapping("/{travelId}/add") public ResponseEntity>> addSchedule(@AuthenticationPrincipal CustomUserDetails customUserDetails, @PathVariable Long travelId , @RequestBody List scheduleRequests) { Long userId=customUserDetails.getId(); - List scheduleId= scheduleServiceImpl.addSchedule(userId,travelId, scheduleRequests); + List scheduleId= scheduleService.addSchedule(userId,travelId, scheduleRequests); return ResponseEntity.status(HttpStatus.CREATED).body(CustomApiResponse.onSuccess(scheduleId)); } @@ -34,7 +35,7 @@ public ResponseEntity>> addSchedule(@Authentication @Operation(summary = "일정 조회", description = "선택 여행 일정 조회") @GetMapping("/lists/{travelId}") public ResponseEntity>> getSchedule(@PathVariable Long travelId) { - List scheduleResponses = scheduleServiceImpl.getAllSchedule(travelId); + List scheduleResponses = scheduleService.getAllSchedule(travelId); return ResponseEntity.status(HttpStatus.OK).body(CustomApiResponse.onSuccess(scheduleResponses)); } @@ -42,7 +43,7 @@ public ResponseEntity>> getSchedule(@Pa @DeleteMapping("/{travelId}/{scheduleId}") public ResponseEntity> deleteSchedule(@AuthenticationPrincipal CustomUserDetails customUserDetails,@PathVariable Long travelId,@PathVariable Long scheduleId) { Long userId=customUserDetails.getId(); - scheduleServiceImpl.deleteSchedule(userId,travelId,scheduleId); + scheduleService.deleteSchedule(userId,travelId,scheduleId); return ResponseEntity.status(HttpStatus.NO_CONTENT).body(CustomApiResponse.onSuccess("일정 삭제됌")); } diff --git a/src/main/java/com/jett/domain/travel/controller/TravelController.java b/src/main/java/com/jett/domain/travel/controller/TravelController.java index 886604c..11d0a0a 100644 --- a/src/main/java/com/jett/domain/travel/controller/TravelController.java +++ b/src/main/java/com/jett/domain/travel/controller/TravelController.java @@ -4,11 +4,11 @@ import com.jett.domain.travel.dto.request.TravelRequest; import com.jett.domain.travel.dto.response.PopularPlaceResponse; import com.jett.domain.travel.dto.response.TravelResponse; +import com.jett.domain.travel.service.TravelService; import com.jett.global.common.CustomApiResponse; import com.jett.global.config.security.CustomUserDetails; import com.jett.domain.travel.kakao.KakaoMapService; import com.jett.domain.travel.opendata.TourApiService; -import com.jett.domain.travel.service.TravelServiceImpl; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; @@ -33,7 +33,7 @@ public class TravelController { private final KakaoMapService kakaoMapService; - private final TravelServiceImpl travelServiceImpl; + private final TravelService travelService; private final TourApiService tourApiService; @Operation(summary = "장소 검색", description = "키워드를 통해 장소 검색하기") @@ -50,7 +50,7 @@ public ResponseEntity> searchKeyword( public ResponseEntity> addTravel(@RequestBody TravelRequest travelRequest, @AuthenticationPrincipal CustomUserDetails customUserDetails) { Long userId = customUserDetails.getId(); - Long travelId = travelServiceImpl.addTravel(userId, travelRequest); + Long travelId = travelService.addTravel(userId, travelRequest); return ResponseEntity.status(HttpStatus.OK).body(CustomApiResponse.onSuccess(travelId)); } @@ -58,7 +58,7 @@ public ResponseEntity> addTravel(@RequestBody TravelRequ @PostMapping("/invite/{travelId}") public ResponseEntity> inviteTravel( @RequestBody TravelInviteRequest travelInviteRequest, @PathVariable Long travelId) { - travelServiceImpl.inviteTravel(travelInviteRequest, travelId); + travelService.inviteTravel(travelInviteRequest, travelId); return ResponseEntity.status(HttpStatus.OK).body(CustomApiResponse.onSuccess("친구 초대됌")); } @@ -67,7 +67,7 @@ public ResponseEntity> inviteTravel( public ResponseEntity>> checkTravelSchedule( @AuthenticationPrincipal CustomUserDetails customUserDetails) { Long userId = customUserDetails.getId(); - List checkTravelResult = travelServiceImpl.getAllTravel(userId); + List checkTravelResult = travelService.getAllTravel(userId); return ResponseEntity.status(HttpStatus.OK) .body(CustomApiResponse.onSuccess(checkTravelResult)); } @@ -77,7 +77,7 @@ public ResponseEntity>> checkTravelSchedu public ResponseEntity> checkOnlyTravelSchedule( @AuthenticationPrincipal CustomUserDetails customUserDetails, @PathVariable Long travelId) { Long userId = customUserDetails.getId(); - TravelResponse checkTravelResult = travelServiceImpl.checkOnlyTravelSchedule(userId,travelId); + TravelResponse checkTravelResult = travelService.checkOnlyTravelSchedule(userId,travelId); return ResponseEntity.status(HttpStatus.OK) .body(CustomApiResponse.onSuccess(checkTravelResult)); } @@ -87,7 +87,7 @@ public ResponseEntity> checkOnlyTravelSchedule public ResponseEntity> deleteTravel( @AuthenticationPrincipal CustomUserDetails customUserDetails, @PathVariable Long travelId) { Long userId = customUserDetails.getId(); - travelServiceImpl.deleteTravel(userId, travelId); + travelService.deleteTravel(userId, travelId); return ResponseEntity.status(HttpStatus.NO_CONTENT).body(CustomApiResponse.onSuccess("여행 삭제됌")); } From 1d3c2156a9e0d3e5726f4679aac7443353399d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=92=E1=85=A9=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=A5=E1=86=A8?= Date: Wed, 7 May 2025 16:30:35 +0900 Subject: [PATCH 2/3] =?UTF-8?q?redis=EB=A5=BC=20=ED=86=B5=ED=95=9C=20?= =?UTF-8?q?=EC=9D=B8=EA=B8=B0=EC=97=AC=ED=96=89=EC=A7=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 1 + docker-compose.yml | 2 +- .../invitation/component/RedisService.java | 10 ++++ .../travel/controller/TravelController.java | 55 ++++++++++++++++++- .../com/jett/domain/travel/entity/Travel.java | 4 +- .../travel/opendata/entity/PopularPlace.java | 30 ++++++++++ .../repository/PopularPlaceRepository.java | 10 ++++ .../{ => service}/TourApiService.java | 44 ++++++++++++++- .../travel/repository/TravelRepository.java | 8 +++ .../travel/service/TravelServiceImpl.java | 35 ++++++++---- .../jett/global/config/redis/RedisConfig.java | 10 +++- 11 files changed, 189 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/jett/domain/travel/opendata/entity/PopularPlace.java create mode 100644 src/main/java/com/jett/domain/travel/opendata/repository/PopularPlaceRepository.java rename src/main/java/com/jett/domain/travel/opendata/{ => service}/TourApiService.java (70%) diff --git a/Dockerfile b/Dockerfile index fbc46bb..1e4e17c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ FROM bellsoft/liberica-openjdk-alpine:17 # FROM openjdk:11-jdk-alpine CMD ["./gradlew", "clean", "build"] + # or Maven # CMD ["./mvnw", "clean", "package"] diff --git a/docker-compose.yml b/docker-compose.yml index 03c8b03..7d0f2a0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,7 +29,7 @@ services: container_name: mysql_db image: mysql:latest ports: - - "3306:3306" + - "3307:3306" environment: MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD} diff --git a/src/main/java/com/jett/domain/invitation/component/RedisService.java b/src/main/java/com/jett/domain/invitation/component/RedisService.java index 703770e..538052d 100644 --- a/src/main/java/com/jett/domain/invitation/component/RedisService.java +++ b/src/main/java/com/jett/domain/invitation/component/RedisService.java @@ -1,6 +1,8 @@ package com.jett.domain.invitation.component; +import com.jett.domain.travel.dto.response.PopularPlaceResponse; import jakarta.transaction.Transactional; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.HashOperations; @@ -65,4 +67,12 @@ public static Duration toTomorrow() { final LocalDateTime tomorrow = now.plusDays(1); return Duration.between(now, tomorrow); } + @SuppressWarnings("unchecked") + public List getPopularPlaces(String key) { + return (List) redisTemplate.opsForValue().get(key); + } + + public void setPopularPlaces(String key, List data, Duration ttl) { + redisTemplate.opsForValue().set(key, data, ttl); + } } \ No newline at end of file diff --git a/src/main/java/com/jett/domain/travel/controller/TravelController.java b/src/main/java/com/jett/domain/travel/controller/TravelController.java index 11d0a0a..86869b8 100644 --- a/src/main/java/com/jett/domain/travel/controller/TravelController.java +++ b/src/main/java/com/jett/domain/travel/controller/TravelController.java @@ -1,5 +1,6 @@ package com.jett.domain.travel.controller; +import com.jett.domain.invitation.component.RedisService; import com.jett.domain.travel.dto.request.TravelInviteRequest; import com.jett.domain.travel.dto.request.TravelRequest; import com.jett.domain.travel.dto.response.PopularPlaceResponse; @@ -8,11 +9,15 @@ import com.jett.global.common.CustomApiResponse; import com.jett.global.config.security.CustomUserDetails; import com.jett.domain.travel.kakao.KakaoMapService; -import com.jett.domain.travel.opendata.TourApiService; +import com.jett.domain.travel.opendata.service.TourApiService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import java.time.Duration; +import java.time.Instant; import java.util.List; import lombok.AllArgsConstructor; + +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -29,12 +34,14 @@ @RequestMapping("/travel") @RestController @AllArgsConstructor +@Slf4j public class TravelController { private final KakaoMapService kakaoMapService; private final TravelService travelService; private final TourApiService tourApiService; + private final RedisService redisService; @Operation(summary = "장소 검색", description = "키워드를 통해 장소 검색하기") @GetMapping("/kakao/searchKeyword") @@ -91,9 +98,44 @@ public ResponseEntity> deleteTravel( return ResponseEntity.status(HttpStatus.NO_CONTENT).body(CustomApiResponse.onSuccess("여행 삭제됌")); } - - @Operation(summary = "지역별 인기여행지 조회", description = "지역을 입력받을 후 해당하는 위치 관광지 조회") + @Operation(summary = "지역별 인기여행지 조회", description = "지역을 MySQL 또는 Redis에서 조회") @GetMapping("/popularLists") + public ResponseEntity>> getPopularPlaceList( + @RequestParam String place) { + Instant startTime = Instant.now(); + String redisKey = "지역명"+ place; + List cachedData = redisService.getPopularPlaces(redisKey); + if (cachedData != null) { + log.info("캐시데이터 저장되어있었음"); + Instant endTime = Instant.now(); + long duration = java.time.Duration.between(startTime, endTime).toNanos(); + log.info("API 호출 시간 (캐시 데이터 조회): " + duration + "ns"); + return ResponseEntity.ok(CustomApiResponse.onSuccess(cachedData)); + }// 레디스에 저장되어있음 + log.info("레디스에 없었음"); + + // 없으면 DB + // DB 조회 시작 시간 기록 + Instant dbStartTime = Instant.now(); + List popularResults = tourApiService.getPopularPlaceList(place); + // DB 조회 끝나고 시간 측정 + Instant dbEndTime = Instant.now(); + long dbDuration = java.time.Duration.between(dbStartTime, dbEndTime).toNanos(); + log.info("API 호출 시간 (DB 조회): " + dbDuration + "ns"); + redisService.setPopularPlaces(redisKey, popularResults, Duration.ofMinutes(30)); + + // 끝 시간 기록 및 소요 시간 계산 + Instant endTime = Instant.now(); + long duration = java.time.Duration.between(startTime, endTime).toNanos(); + log.info("API 호출 시간 (DB 조회 및 캐시 저장): " + duration + "ns"); + return ResponseEntity.ok(CustomApiResponse.onSuccess(popularResults)); + } + + + + + @Operation(summary = "API 요청 테스트 DB테스트 및 저장용지역별 인기여행지 조회 사용X", description = "지역을 입력받을 후 해당하는 위치 관광지 조회") + @GetMapping("/popularLists/Test") public ResponseEntity>> getPopularPlace( @RequestParam String place) { try { @@ -104,5 +146,12 @@ public ResponseEntity>> getPopularP .body(CustomApiResponse.onFailure(e.getMessage(), null)); } } + @Operation(summary = "지역별 인기여행지 MYSQL 대량 저장 사용 X", description = "모든 지역 DB 저장 API") + @GetMapping("/AllLists") + public ResponseEntity> saveAllPopularPlace() { + tourApiService.saveAllPopularPlace(); + return ResponseEntity.status(HttpStatus.OK) + .body(CustomApiResponse.onSuccess("Save 완료")); + } } diff --git a/src/main/java/com/jett/domain/travel/entity/Travel.java b/src/main/java/com/jett/domain/travel/entity/Travel.java index 8c5569d..a64b2b4 100644 --- a/src/main/java/com/jett/domain/travel/entity/Travel.java +++ b/src/main/java/com/jett/domain/travel/entity/Travel.java @@ -6,6 +6,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.BatchSize; import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; @@ -46,6 +47,7 @@ public class Travel { private Boolean isDeleted = false; - @OneToMany(mappedBy = "travel") + @OneToMany(mappedBy = "travel", fetch = FetchType.LAZY) + @BatchSize(size = 10) private List travelMembers; } \ No newline at end of file diff --git a/src/main/java/com/jett/domain/travel/opendata/entity/PopularPlace.java b/src/main/java/com/jett/domain/travel/opendata/entity/PopularPlace.java new file mode 100644 index 0000000..a072c6e --- /dev/null +++ b/src/main/java/com/jett/domain/travel/opendata/entity/PopularPlace.java @@ -0,0 +1,30 @@ +package com.jett.domain.travel.opendata.entity; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "popular_place") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class PopularPlace { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String region; + + @Column(nullable = false) + private String title; + + @Column(nullable = false) + private String address; + + @Column(name = "image_url") + private String imageUrl; +} diff --git a/src/main/java/com/jett/domain/travel/opendata/repository/PopularPlaceRepository.java b/src/main/java/com/jett/domain/travel/opendata/repository/PopularPlaceRepository.java new file mode 100644 index 0000000..90f540e --- /dev/null +++ b/src/main/java/com/jett/domain/travel/opendata/repository/PopularPlaceRepository.java @@ -0,0 +1,10 @@ +package com.jett.domain.travel.opendata.repository; + +import com.jett.domain.travel.opendata.entity.PopularPlace; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PopularPlaceRepository extends JpaRepository { + + List findByRegion(String place); +} diff --git a/src/main/java/com/jett/domain/travel/opendata/TourApiService.java b/src/main/java/com/jett/domain/travel/opendata/service/TourApiService.java similarity index 70% rename from src/main/java/com/jett/domain/travel/opendata/TourApiService.java rename to src/main/java/com/jett/domain/travel/opendata/service/TourApiService.java index f82de22..2de1576 100644 --- a/src/main/java/com/jett/domain/travel/opendata/TourApiService.java +++ b/src/main/java/com/jett/domain/travel/opendata/service/TourApiService.java @@ -1,7 +1,10 @@ -package com.jett.domain.travel.opendata; +package com.jett.domain.travel.opendata.service; import com.jett.domain.travel.dto.response.PopularPlaceResponse; import com.fasterxml.jackson.databind.ObjectMapper; +import com.jett.domain.travel.opendata.entity.PopularPlace; +import com.jett.domain.travel.opendata.repository.PopularPlaceRepository; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -25,6 +28,7 @@ public class TourApiService { @Value("${api.serviceKey}") private String serviceKey; + private final PopularPlaceRepository popularPlaceRepository; private final ObjectMapper objectMapper = new ObjectMapper(); private static final List VALID_PLACES = Arrays.asList( "서울", "부산", "대구", "인천", "광주", "대전", "울산", "세종", @@ -36,6 +40,7 @@ public List getJsonResponse(String place) { if (!VALID_PLACES.contains(place)) { throw new IllegalArgumentException("해당 지역은 지원하지 않습니다: " + place); } + long startTime = System.currentTimeMillis(); try { String encodedPlace = URLEncoder.encode(place, StandardCharsets.UTF_8); String urlString = "http://apis.data.go.kr/B551011/KorService1/searchKeyword1" @@ -43,6 +48,7 @@ public List getJsonResponse(String place) { + "&MobileApp=MobileApp" + "&_type=json" + "&keyword=" + encodedPlace + + "&numOfRows=1000" + "&serviceKey=" + serviceKey; log.info("최종 API 요청 URL: {}", urlString); @@ -98,7 +104,43 @@ public List getJsonResponse(String place) { } catch (Exception e) { log.error("API 요청 중 오류 발생", e); } + // 시간 측정 종료 + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + + // 시간 출력 (단위: 밀리초) + log.info("API 요청 및 응답 처리 시간: {} ms", duration); return popularPlaces; } + public void saveAllPopularPlace() { + for (String place : VALID_PLACES) { + try { + List responses = getJsonResponse(place); + List entities = responses.stream() + .map(r -> PopularPlace.builder() + .region(place) + .title(r.getTitle()) + .address(r.getAddress()) + .imageUrl(r.getImageUrl()) + .build()) + .toList(); + popularPlaceRepository.saveAll(entities); + log.info("[{}] 저장 완료. {}개", place, entities.size()); + } catch (Exception e) { + log.error("[{}] 저장 중 오류 발생", place, e); + } + } + } + public List getPopularPlaceList(String place) { + List entities = popularPlaceRepository.findByRegion(place); + + return entities.stream() + .map(entity -> new PopularPlaceResponse( + entity.getTitle(), + entity.getAddress(), + entity.getImageUrl())) + .collect(Collectors.toList()); + } + } diff --git a/src/main/java/com/jett/domain/travel/repository/TravelRepository.java b/src/main/java/com/jett/domain/travel/repository/TravelRepository.java index 72a671a..5dca16f 100644 --- a/src/main/java/com/jett/domain/travel/repository/TravelRepository.java +++ b/src/main/java/com/jett/domain/travel/repository/TravelRepository.java @@ -1,13 +1,21 @@ package com.jett.domain.travel.repository; import com.jett.domain.travel.entity.Travel; +import io.lettuce.core.dynamic.annotation.Param; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; +import org.springframework.data.jpa.repository.Query; public interface TravelRepository extends JpaRepository { List findByTravelMembers_Member_Id(Long userId); + @Query("SELECT DISTINCT t FROM Travel t " + + "JOIN FETCH t.travelMembers tm " + + "JOIN FETCH tm.member m " + + "WHERE m.id = :userId") + List findAllByUserIdWithMembers(@Param("userId") Long userId); + } diff --git a/src/main/java/com/jett/domain/travel/service/TravelServiceImpl.java b/src/main/java/com/jett/domain/travel/service/TravelServiceImpl.java index c7d171f..99ba83e 100644 --- a/src/main/java/com/jett/domain/travel/service/TravelServiceImpl.java +++ b/src/main/java/com/jett/domain/travel/service/TravelServiceImpl.java @@ -26,22 +26,35 @@ public class TravelServiceImpl implements TravelService { private final MemberRepository memberRepository; private final TravelMemberRepository travelMemberRepository; + @Override @Transactional public List getAllTravel(Long userId) { + long startTime = System.nanoTime(); + List travels = travelRepository.findByTravelMembers_Member_Id(userId); - return travels.stream() - .map(travel -> TravelResponse.builder() - .travelId(travel.getTravelId()) - .travelName(travel.getTravelName()) - .startDate(travel.getStartDate()) - .endDate(travel.getEndDate()) - .participants(travel.getTravelMembers().stream() - .map(travelMember -> travelMember.getMember().getName()) // TravelMember를 통해 참여자 이름 추출 - .collect(Collectors.toList())) - .build()) - .collect(Collectors.toList()); + + //List travels = travelRepository.findAllByUserIdWithMembers(userId); + + List travelResponses = travels.stream() + .map(travel -> TravelResponse.builder() + .travelId(travel.getTravelId()) + .travelName(travel.getTravelName()) + .startDate(travel.getStartDate()) + .endDate(travel.getEndDate()) + .participants(travel.getTravelMembers().stream() + .map(travelMember -> travelMember.getMember().getName()) + .collect(Collectors.toList())) + .build()) + .collect(Collectors.toList()); + + long endTime = System.nanoTime(); + long duration = endTime - startTime; + System.out.println("getAllTravel 메서드 실행 시간: " + duration + " 나노초"); + + return travelResponses; } + @Override @Transactional public Long addTravel(Long userId, TravelRequest travelRequest) { diff --git a/src/main/java/com/jett/global/config/redis/RedisConfig.java b/src/main/java/com/jett/global/config/redis/RedisConfig.java index 20b3b58..8d7bb92 100644 --- a/src/main/java/com/jett/global/config/redis/RedisConfig.java +++ b/src/main/java/com/jett/global/config/redis/RedisConfig.java @@ -4,6 +4,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration @@ -14,11 +15,14 @@ public RedisTemplate redisTemplate(RedisConnectionFactory connec RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(connectionFactory); + // JSON 직렬화기 사용 + Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Object.class); + redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(serializer); // 값을 JSON으로 직렬화 redisTemplate.setHashKeySerializer(new StringRedisSerializer()); - redisTemplate.setHashValueSerializer(new StringRedisSerializer()); + redisTemplate.setHashValueSerializer(serializer); // 해시값도 JSON으로 직렬화 return redisTemplate; } -} +} \ No newline at end of file From 7914b40cd4acc11e713eec8affba4a94a8a0968e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=92=E1=85=A9=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=A5=E1=86=A8?= Date: Wed, 7 May 2025 18:32:05 +0900 Subject: [PATCH 3/3] =?UTF-8?q?redis=EB=A5=BC=20=ED=86=B5=ED=95=9C=20?= =?UTF-8?q?=EC=9D=B8=EA=B8=B0=EC=97=AC=ED=96=89=EC=A7=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 7 ++++++- .../java/com/jett/global/config/redis/RedisConfig.java | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 7d0f2a0..0ad1278 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,7 +46,12 @@ services: image: redis:latest ports: - "6379:6379" - command: ["redis-server", "--appendonly", "yes"] + command: [ + "redis-server", + "--maxmemory", "1mb", + "--maxmemory-policy", "allkeys-lfu", + "--appendonly", "yes" + ] networks: - mynetwork diff --git a/src/main/java/com/jett/global/config/redis/RedisConfig.java b/src/main/java/com/jett/global/config/redis/RedisConfig.java index 8d7bb92..a796ac7 100644 --- a/src/main/java/com/jett/global/config/redis/RedisConfig.java +++ b/src/main/java/com/jett/global/config/redis/RedisConfig.java @@ -15,13 +15,13 @@ public RedisTemplate redisTemplate(RedisConnectionFactory connec RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(connectionFactory); - // JSON 직렬화기 사용 + // 직렬화 수정 Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Object.class); redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(serializer); // 값을 JSON으로 직렬화 + redisTemplate.setValueSerializer(serializer); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); - redisTemplate.setHashValueSerializer(serializer); // 해시값도 JSON으로 직렬화 + redisTemplate.setHashValueSerializer(serializer); return redisTemplate; }