🧣 SpringBoot + Redis - 메인 화면 인기 주차장 띄우기

이하얀·2023년 12월 8일
0
post-thumbnail

Redis의 search_count를 정렬해 인기 순으로 보여주기🙌

  • 로직
    • UserService에서 Redis의 현재의 search_count 정렬하기
    • ParkingMainController 만들어서 GET으로 인기 주차장 출력(일단 순위 별로 전체 출력)

1. UserService에 카운트 정렬하기

📁 UserService.java

...
public Map<String, String> getAllSearchCounts(){
        Set<String> keys = stringRedisTemplate.keys(SEARCH_COUNT_KEY_PREFIX + "*");

        if(keys != null && !keys.isEmpty()){
            List<String> keyList = new ArrayList<>(keys);
            List<String> values = stringRedisTemplate.opsForValue().multiGet(keyList);

            Map<String, String> searchCounts = new HashMap<>();
            for(int i = 0; i < keyList.size(); i++){
                String key = keyList.get(i);
                String parkingId = key.substring(SEARCH_COUNT_KEY_PREFIX.length());
                String count = values.get(i);
                searchCounts.put(parkingId, count);
            }
            return searchCounts;
        }
        return Collections.emptyMap();
    }
...
  • getAllSearchCounts
  • Redis에 접근해서 현재의 key 값을 내림차순으로 정렬하여 인기순으로 정리하는 메서드입니다.
...
public int getSearchCount(Long parkingId) {
        String key = SEARCH_COUNT_KEY_PREFIX + parkingId;
        String count = stringRedisTemplate.opsForValue().get(key);
        return count != null ? Integer.parseInt(count) : 0;
    }
  • getSearchCount
  • controller에 전달할 정렬된 키 값 메서드입니다.


2. ParkingMainController를 통한 인기순 출력하기

  • 설계

    • Request :GET /api/v1/parking/main 호출
    • Response : Redis의 카운트 내림차순으로 정렬된 값 출력
  • 적용
    📁 ParkingMainController.java

    @Slf4j
    @Controller
    @RequestMapping(value = "/api/v1/parking")
    public class ParkingMainController {
    
        private final UserService userService;
    
        public ParkingMainController(UserService userService) {
            this.userService = userService;
        }
    
        @GetMapping("/main")
        public ResponseEntity<List<ParkingInfoDTO>> getParkingInfoForUI(Model model) {
            try {
                // Redis에서 주차장 정보 가져오기
                List<ParkingInfoDTO> parkingInfoList = userService.getParkingInfo();
    
                // 검색 횟수를 높은 순으로 정렬
                List<ParkingInfoDTO> sortedParkingInfoList = parkingInfoList.stream()
                        .sorted(Comparator.comparingInt(dto -> -userService.getSearchCount(dto.getParkingId())))
                        .collect(Collectors.toList());
    
                // 메인 페이지에 전달할 데이터 설정
                model.addAttribute("parkingInfoList", sortedParkingInfoList);
    
                return ResponseEntity.status(HttpStatus.OK).body(sortedParkingInfoList);
            } catch (Exception e) {
                log.error("주차장 정보 목록을 가져오는 중 오류 발생", e);
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
            }
        }
    }


3. 테스트

  • Postman

📤 Request

GET
localhost:8080/api/v1/parking/main

📥 Response



4. 결과 및 보완할 사항 정리하기

1️⃣ 결과

  • 테스트 기준
    기존의 위치 정보 조회(영등포) 이용 : 카운트를 가장 많이 진행한 DB를 사용했습니다.
    ✚ 스타벅스 뚝섬유원지역의 위도, 경도를 이용해 주변 주차장 조회를 1회 진행하여 카운트를 다르게 설정해주었습니다.

  • Redis 현황

    1. 336번 : 기존 주변 주차장 조회에 가장 많이 사용한 위도 경도 기반으로 하여 카운트가 가장 많습니다.
    2. 370번 : 스타벅스 뚝섬유원지역 카운트입니다.
    3. 1번 : 위의 2가지 모두 해당 없는 카운트입니다.

웹페이지 결과(포스트맨과 동일하나 더 많은 값을 보여줄 수 있음)

  • 현재 count 현황
    • 336, 380, 43, 293 : 19
    • 370, 27, 70, 549, 66, 247 : 11
    • 그 외 : 10(이전 카운트 테스트로 인해 증가됨..)
  • 실제 Redis의 카운트 값에 맞게 잘 정렬되어 있습니다.


2️⃣ 보완할 사항 정리

  • EC2 서버에 적용
  • 정렬한 카운트 값을 상위 n개까지만 보여줄 수 있도록 로직 수정


🚀 MySQL을 이용한 주변 주차장 조회 로직 변경!

부여받은 RDS의 MySQL 버전은 5.6.15인데, 현재 주변 주차장 조회 로직에 사용한 st_distance_sphere 라는 GIS Function은 8.0버전에서부터만 사용할 수 있기 때문에 문제가 발생했습니다.
-> 결국, 기존의 다른 방식이었던 RADIANS를 이용한 계산 로직으로 변경했습니다!

이전

public List<ParkingEntity> findNearbyParking(double userLon, double userLat, int maxDistance) {
        QParkingEntity parkingEntity = QParkingEntity.parkingEntity;

        return queryFactory
                .select(parkingEntity)
                .from(parkingEntity)
                .where(Expressions.numberTemplate(Double.class,
                                "FUNCTION('ST_Distance_Sphere', FUNCTION('POINT', {0}, {1}), FUNCTION('POINT', {2}, {3}))",
                                parkingEntity.lon, parkingEntity.lat, userLon, userLat)
                        .loe(maxDistance))
                .orderBy(Expressions.numberTemplate(Double.class,
                        "FUNCTION('ST_Distance_Sphere', FUNCTION('POINT', {0}, {1}), FUNCTION('POINT', {2}, {3}))",
                        parkingEntity.lon, parkingEntity.lat, userLon, userLat).asc())
                .fetch();
    }

이후

public List<ParkingEntity> findNearbyParking(double userLon, double userLat, int maxDistance) {
        QParkingEntity parkingEntity = QParkingEntity.parkingEntity;

       return queryFactory
            .select(parkingEntity)
            .from(parkingEntity)
            .where(
                    Expressions.numberTemplate(Double.class,
                                    "ROUND(6371 * 2 * ASIN(SQRT(POW(SIN((RADIANS({0}) - RADIANS({1})) / 2), 2) + " +
                                            "COS(RADIANS({1})) * COS(RADIANS({0})) * POW(SIN((RADIANS({2}) - RADIANS({3})) / 2), 2))), 2)",
                                    parkingEntity.lat, userLat, parkingEntity.lon, userLon)
                            .loe(maxDistance)
            )
            .orderBy(Expressions.numberTemplate(Double.class,
                    "ROUND(6371 * 2 * ASIN(SQRT(POW(SIN((RADIANS({0}) - RADIANS({1})) / 2), 2) + " +
                            "COS(RADIANS({1})) * COS(RADIANS({0})) * POW(SIN((RADIANS({2}) - RADIANS({3})) / 2), 2))), 2)",
                    parkingEntity.lat, userLat, parkingEntity.lon, userLon).asc())

            .fetch();
    }

공부 자료
📮 MySQL 좌표 데이터 가져오기 (+ Spatial Index 활용하기)
📮 [데이터베이스] 8. 인덱스 (2)

MySQL 관련 참고 자료
💬 12.1 Built-In Function and Operator Reference(MySQL 8.0 버전에 추가된 function에 st_distance_sphere 있음)
💬 12.16.13 Spatial Convenience Functions

profile
언젠가 내 코드로 세상에 기여할 수 있도록, BE 개발 기록 노트☘️

0개의 댓글