[GDG]홍대 맛집 아카이빙 프로젝트 #15 - 투표기능 마무리도전하기

2025. 8. 9. 00:30·팀 프로젝트/[2025][GDG]홍대 맛집 아카이빙 프로젝트

어제 투표기능 몇 가지를 만들고

월말에 DB에 저장하는 기능을 만들다가 끝났다.

 

그래서 매달 첫째날에 현재 투표 중인 상점들의 리스트를 보여주는 STORE_FOR_VOTE 테이블에서 상위 3개의 요소를 뽑아서 홍슐랭에 선정된 DB테이블로 이동시키고 STORE_FOR_VOTE 테이블의 요소들을 모두 지우고 각 요소들에 isSelected와 WhenForVote 변수를 추가해 PAST_STORE_FOR_VOTE 테이블에 넣으면 된다.

 

이게 로직이었고,

 

STORE_FOR_VOTE 테이블에서 상위 3개의 요소를 뽑아서 까지 했고

 

홍슐랭에 선정된 DB테이블로 이동시키고 부분부터 하면 된다.

가게의 ID를 저장하는 것으로 했는데, 이게 또 DB의 정규화 / 비정규화 여부와 갈린단다.

근데 실수로 점포가 삭제된 후 그 점포의 ID로 새로운 가게가 등록되면 그 가게가 홍슐랭에 선정된 DB테이블로 이동하기 때문에.. 우선은 STORE DB테이블에서의 ID값은 최대한 유지시키고 점포삭제기능은 만들지 않기로한다. (물론 실수나 테스트용도로 점포를 등록할 수도 있어서 삭제기능까지는 만들생각)

 

    private Long id;

    private Long idInDb;
    private String WhenSelected;
    private Integer votedCount = 0;

대충 이정도의 요소가 들어감

idInDb가 STORE DB테이블에서의 ID값이고, id는 그냥 겉치레용(?)

암튼 이렇게 만들고 레포지토리도 만들었다. JPA 쓰니가 편하다.

 

그리고 짜면서 생각해봤는데.. 이 메서드를 매달 말에 실행하는게 나을 것 같다. 정확히는 투표를 매달 말 (30일 또는 31일) 18시까지만 받고 DB로 옮기는 작업은 21시나 22시 정도에 하는거지

 

public ResponseEntity<ResponseDTO> modifyInEnd(){
        List<StoreForVote> stores = storeForVoteRepository.findTop3ByOrderByVotedCountDesc();
        List<StoreForSelected> selectedList = new ArrayList<>();

        for (StoreForVote store : stores) {
            String storeName = store.getStoreName();
            String storeLocation = store.getStoreLocation();

            Store individualizedStore = storeRepository.findByStoreNameAndStoreLocation(storeName, storeLocation);

            long selectedStoreId = individualizedStore.getId();
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM");
            String whenForSelected = formatter.format(date);

            Integer votedCount = store.getVotedCount();

            StoreForSelected storeForSelected = StoreForSelected.builder()
                    .WhenSelected(whenForSelected)
                    .idInDb(selectedStoreId)
                    .votedCount(votedCount)
                    .build();

            selectedList.add(storeForSelected);
        }

        for (StoreForSelected store : selectedList) {
            storeForSelectedRepository.save(store);
        }

        ResponseDTO responseDTO = ResponseDTO.builder()
                .status(200)
                .message("성공")
                .build();

        return ResponseEntity.ok(responseDTO);

 코드를 마저 짰다.

혹시몰라서 에러가 터지면 저장을 중지해야하니까 선정된 가게들을 먼저 리스트에 담아둔 후에 리스트를 순회하며 저장시키는 방식을 택했고, 로직은 대충 STORE_FOR_VOTE에서 상위 3개의 객체를 가져와 상호명과 주소로 STORE 테이블에서 ID를 가져와 저장하고 이 날 날짜를 토대로 투표한 월, STORE_FOR_VOTE 테이블에서 득표수까지 가져와 하나의 객체로 만든 뒤에 리스트에 저장한다.

 

PostConstruct 어노테이션을 사용했기 때문에 그냥 실행만 시켜보고 DB를 조회하면 된다.

성공

지금 STORE_FOR_VOTE 테이블에 값이 3개 밖에 없는데다가 모두 득표수가 0이라서 모든 값이 들어갔다. 그래도 어쨌든 들어갔잖아요


STORE_FOR_VOTE 테이블의 요소들을 모두 지우고 각 요소들에 isSelected와 WhenForVote 변수를 추가해 PAST_STORE_FOR_VOTE 테이블에 넣으면 부분

STORE_FOR_VOTE 테이블을 지우는게 마지막으로 해야할 일이고, 어차피 홍슐랭에 선정된 가게들은 위에 나온 반복문을 타게되므로 반복문 처리과정에서 PAST_STORE_FOR_VOTE 테이블에 추가하고 DB에서 제거하는 과정부터하면 될 듯하다.

 

PAST_STORE_FOR_VOTE가 상호명과 주소 등의 정보를 받는 형식이길래 이것도 STORE 테이블에서의 ID만 받도록 수정했다.

 

    public ResponseEntity<ResponseDTO> modifyInEnd() {
        List<StoreForVote> stores = storeForVoteRepository.findTop3ByOrderByVotedCountDesc();
        List<StoreForSelected> selectedList = new ArrayList<>();
        List<PastStoreForVote> unSelectedList = new ArrayList<>();
        List<PastStoreForVote> selected = new ArrayList<>();

        for (StoreForVote store : stores) {
            String storeName = store.getStoreName();
            String storeLocation = store.getStoreLocation();

            Store individualizedStore = storeRepository.findByStoreNameAndStoreLocation(storeName, storeLocation);

            long selectedStoreId = individualizedStore.getId();
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM");
            String whenForSelected = formatter.format(date);

            Integer votedCount = store.getVotedCount();

            StoreForSelected storeForSelected = StoreForSelected.builder()
                    .WhenSelected(whenForSelected)
                    .idInDb(selectedStoreId)
                    .votedCount(votedCount)
                    .build();

            selectedList.add(storeForSelected);

            /* 여기서부터는 PAST_FOR_STORE 에 추가할 부분입니다.*/
            PastStoreForVote pastStoreForVote = PastStoreForVote.builder()
                    .isSelected(true)
                    .whenForVote(whenForSelected)
                    .storeId(selectedStoreId)
                    .build();

            selected.add(pastStoreForVote);
        }

        Iterable<StoreForVote> storeForVotes = storeForVoteRepository.findAll();

        for (StoreForVote storeForVote : storeForVotes) {
            String storeName = storeForVote.getStoreName();
            String storeLocation = storeForVote.getStoreLocation();

            Store individualizedStore = storeRepository.findByStoreNameAndStoreLocation(storeName, storeLocation);

            long selectedStoreId = individualizedStore.getId();
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM");
            String whenForSelected = formatter.format(date);

            Integer votedCount = storeForVote.getVotedCount();

            PastStoreForVote pastStoreForVote = PastStoreForVote.builder()
                    .whenForVote(whenForSelected)
                    .storeId(selectedStoreId)
                    .votedCount(votedCount)
                    .isSelected(false)
                    .build();

            unSelectedList.add(pastStoreForVote);
        }

        for (StoreForSelected store : selectedList) {
            storeForSelectedRepository.save(store);
        }

        for (PastStoreForVote pastStoreForVote : selected) {
            String storeName = storeRepository.findById(pastStoreForVote.getStoreId()).getStoreName();
            String storeLocation = storeRepository.findById(pastStoreForVote.getStoreId()).getStoreLocation();

            pastStoreRepository.save(pastStoreForVote);
            storeForVoteRepository.deleteByStoreNameAndStoreLocation(storeName, storeLocation);
        }

        for (PastStoreForVote pastStoreForVote : unSelectedList) {
            String storeName = storeRepository.findById(pastStoreForVote.getStoreId()).getStoreName();
            String storeLocation = storeRepository.findById(pastStoreForVote.getStoreId()).getStoreLocation();

            pastStoreRepository.save(pastStoreForVote);
            storeForVoteRepository.deleteByStoreNameAndStoreLocation(storeName, storeLocation);
        }

        ResponseDTO responseDTO = ResponseDTO.builder()
                .status(200)
                .message("성공")
                .build();

        return ResponseEntity.ok(responseDTO);

추가함

테스트를 해봐야하는데 잘 될지 모르겠다. 우선 뭔가 에러가 터져서 봤더니 PostConstruct 어노테이션을 사용할 때 delete, save, remove 등의 작업을 수행하면 트랜잭션을 활성화시키지 않는다고 한다. 사실 자바의 스레드나 트랜잭션에 대해서는 잘 모르기도하고 (나중에 공부할 예정) 해서 우선은 PostConstruct는 활용하지 않을 예정


@Scheduled 어노테이션 사용하기

https://dev-coco.tistory.com/176

 

[Spring Boot] @Scheduled을 이용해 일정 시간 마다 코드 실행하기

@Scheduled Spring Boot에서 @Scheduled 어노테이션을 사용하면 일정한 시간 간격으로, 혹은 특정 시간에 코드가 실행되도록 설정할 수 있다. 주기적으로 실행해야 하는 작업이 있을 때 적용해 쉽게 사용

dev-coco.tistory.com

를 활용하면 된다.

 

그래서 PostConstruct 어노테이션을 제거하고

@Scheduled(cron = "* 49 * * * *")

어노테이션을 추가했다. 왼쪽부터 초 분 시 일 월 요일 순. 추가로 더 설정이 몇 개 있는데 위의 링크를 참고하면 된다. 저거는 매시 49분마다 실행하겠다는 뜻.

 

추가로 Scheduled 어노테이션을 사용하면 메서드의 리턴타입이 void 여야한다. 그래서 ResponseEntity 제거

암튼 2분정도 여유시간을 두고 POSTMAN으로 회원가입 로그인 투표까지 다하고 기다리면 된다.

 

그랬더니 정확히 52분에 에러가 터졌다^^

그렇다! Scheduled 어노테이션도 트랜잭션을 지원하지 않는다. 대체 트랜잭션이 무엇인지.. 좀 더 배워야겠다. 헤드퍼스트에는 없다고해서 좀 찾아보니 스프링인액션에 관련내용이 있단다. 기초적인 내용이고 김영한 강사님의 강의에서 좀 더 확실하게 다룬다고한다. 9월쯤에 스프링인액션과 인프런 강의를 좀 들읍시다. 멀티스레드 내용을 좀 알려면 자바의 모든 것도 읽어야하고?

 

이 부분은 좀 몰라서 GPT에게 몇 번 물어봤더니 Transactional 어노테이션을 추가하라고 한다.

 

성공. 득표수 상위 3개의 가게들의 ID가 들어간다.

 

다만 약간의 문제가 있는데

이렇게 STORE_FOR_VOTE에 있는 객체들이 두 번씩 PAST_STORE_FOR_VOTE에 들어간다.

 

알고보니 플로우가

상위 3개 뽑고 → 그걸 STORE_FOR_SELECTED 테이블에 넣고 → PAST_STORE_FOR_VOTE 테이블에 넣고 → STORE_FOR_VOTE 테이블에서 제거하고 → 나머지 가게들도 같은 과정을 거쳐서 PAST_STORE_FOR_VOTE에 넣는 플로우로 진행되는데, 상위 3개의 가게들을 STORE_FOR_VOTE 테이블에서 제거하지 않고 나머지 가게들을 PAST_STORE_FOR_VOTE에 추가하는 과ㅣ정이어서 상위 3개의 가게들이 두 번씩 PAST_STORE_FOR_VOTE에 추가되었던 것이다.

 

사실 로직이 좀 복잡해서 설명이 잘 안되긴 했네

암튼 반복문 순서를 조금 바꿨다. 그리고 득표수를 좀 다양하게 해서 테스트해볼예정

 

???

넘어가긴 했는데 넘어가기만 했다. WHEN_FOR_VOTE 빼고 전부 엉망진창이다. 다시합시다.

 

알바 15분 전인데 열심히 합시다잉잉잉

코드 좀 수정함

ID값과 VOTED_COUNT값이 안오는 것 같아서 그 부분만 추가해줬다. 알고보니 VOTED_COUNT를 추가해주는 로직이 없어서 기본값인 0으로 들어갔고, IS_SELECTED가 FALSE인 경우에만 VOTED_COUNT를 추가해주는 로직이 있어서 넘어왔던거고

 

STORE_ID는 대체 왜 잘못 넘어왔는지 잘 모르겠다

잘 넘어간듯하다.

 

SELECTED에도 잘 넘어갔다 야호

알바 가기 전에 처리해서 다행이다. 이제 내일까지 테스트 좀 돌리면 될 듯

 

우선은 여기서 투표기능은 거진 마무리했다.

남은거는 테스트와 JWT토큰 관련 처리인데, 이거는 다룰 때 16편으로 다룰 예정이다.


내일 할 거

- 코어타임 19:30 / 광흥창

- JWT토큰 처리 (토큰이 없을 때, DB에 정보가 없을 때 등을 메서드 분리)

- 꾸준히 테스트하기

'팀 프로젝트 > [2025][GDG]홍대 맛집 아카이빙 프로젝트' 카테고리의 다른 글

[GDG]홍대 맛집 아카이빙 프로젝트 #17 - 테스트 디버깅  (3) 2025.08.10
[GDG]홍대 맛집 아카이빙 프로젝트 #16 - 테스트 돌리기  (2) 2025.08.09
[GDG]홍대 맛집 아카이빙 프로젝트 #14 - 기타 투표기능 개발  (4) 2025.08.08
[GDG]홍대 맛집 아카이빙 프로젝트 #13 - 투표기능  (6) 2025.08.06
[GDG] 홍대 맛집 아카이빙 프로젝트 #12 - 투표기능 DB연결  (2) 2025.08.06
'팀 프로젝트/[2025][GDG]홍대 맛집 아카이빙 프로젝트' 카테고리의 다른 글
  • [GDG]홍대 맛집 아카이빙 프로젝트 #17 - 테스트 디버깅
  • [GDG]홍대 맛집 아카이빙 프로젝트 #16 - 테스트 돌리기
  • [GDG]홍대 맛집 아카이빙 프로젝트 #14 - 기타 투표기능 개발
  • [GDG]홍대 맛집 아카이빙 프로젝트 #13 - 투표기능
윤마치
윤마치
개발을 합니다.
  • 윤마치
    윤마치
    윤마치
  • 전체
    오늘
    어제
    • 분류 전체보기 (211)
      • 신년사 (3)
        • 2025년 (2)
        • 2026년 (1)
      • CS (59)
        • JVM (12)
        • 백엔드 (20)
        • 언어구현 (1)
        • 객체지향 (1)
        • 논리회로 (5)
        • 컴퓨터구조 (9)
        • 데이터베이스 (1)
        • 컴퓨터 네트워크 (10)
      • 언어공부 (64)
        • Java | Kotlin (48)
        • JavaScript | TypeScript (9)
        • C | C++ (6)
      • 개인 프로젝트 (11)
        • [2025] Happy2SendingMails (3)
        • [2026] 골든리포트! (8)
        • [2026] 순수자바로 개발하기 (0)
        • 기타 이것저것 (0)
      • 팀 프로젝트 (29)
        • [2025][GDG]홍대 맛집 아카이빙 프로젝트 (29)
      • 알고리즘 (13)
        • 백준풀이기록 (11)
      • 놀이터 (0)
      • 에러 수정일지 (2)
      • 고찰 (24)
        • CEOS 23기 회고록 (2)
  • 블로그 메뉴

    • 링크

      • 컬러잇 개발블로그
    • 공지사항

    • 인기 글

    • 태그

      144
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.5
    윤마치
    [GDG]홍대 맛집 아카이빙 프로젝트 #15 - 투표기능 마무리도전하기
    상단으로

    티스토리툴바