From b5bc84ae72c6474c35ce0c440c5849332f6693e6 Mon Sep 17 00:00:00 2001 From: blaxsior Date: Thu, 8 Aug 2024 11:20:34 +0900 Subject: [PATCH 01/16] =?UTF-8?q?[chore]=20=EB=A1=9C=EC=BB=AC=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=97=90=EC=84=9C=20=EC=9D=BC=EA=B4=80=EB=90=9C=20db?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9D=84=20=EC=9C=84=ED=95=9C=20docker=20?= =?UTF-8?q?compose=20=ED=8C=8C=EC=9D=BC=20=EC=9E=91=EC=84=B1(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infra/dev/db/docker-compose.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 infra/dev/db/docker-compose.yml diff --git a/infra/dev/db/docker-compose.yml b/infra/dev/db/docker-compose.yml new file mode 100644 index 00000000..9c089542 --- /dev/null +++ b/infra/dev/db/docker-compose.yml @@ -0,0 +1,11 @@ +services: + mysql: + image: mysql + ports: + - 3306:3306 + environment: + MYSQL_ROOT_PASSWORD: test + redis: + image: redis + ports: + - 6379:6379 \ No newline at end of file From 6788bb68580eeba3ec36ed373ae6c4f720559059 Mon Sep 17 00:00:00 2001 From: blaxsior Date: Thu, 8 Aug 2024 11:22:54 +0900 Subject: [PATCH 02/16] =?UTF-8?q?[feat]=20path=20variable=20/=20query=20pa?= =?UTF-8?q?rameter=20=ED=83=80=EC=9E=85=20=EB=B6=88=EC=9D=BC=EC=B9=98=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EB=A9=94=EC=8B=9C=EC=A7=95=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spring MVC에서는 뷰를 생성할 때 message를 자동으로 주입해줬지만, api 서버로 사용할 때는 예외에 자동으로 주입해주지 않음. 따라서 messagesource를 주입받아 직접 변환. 좀 더 일관된 방식이 있다면 변경 예정 --- .../orange/common/GlobalExceptionHandler.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/hyundai/softeer/orange/common/GlobalExceptionHandler.java b/src/main/java/hyundai/softeer/orange/common/GlobalExceptionHandler.java index b5aa736d..406085ab 100644 --- a/src/main/java/hyundai/softeer/orange/common/GlobalExceptionHandler.java +++ b/src/main/java/hyundai/softeer/orange/common/GlobalExceptionHandler.java @@ -8,9 +8,11 @@ import hyundai.softeer.orange.event.url.exception.UrlException; import hyundai.softeer.orange.eventuser.exception.EventUserException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; @@ -18,10 +20,13 @@ import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import java.util.HashMap; +import java.util.Locale; import java.util.Map; @RestControllerAdvice public class GlobalExceptionHandler { + @Autowired + private MessageSource messageSource; @ExceptionHandler(MethodArgumentNotValidException.class) // 요청의 유효성 검사 실패 시 @ResponseStatus(HttpStatus.BAD_REQUEST) // 400 Bad Request로 응답 반환 @@ -39,10 +44,15 @@ public ResponseEntity> handleInValidRequestException(MethodA // TODO: messages.properties에 예외 메시지 커스터마이징할 수 있게 방법 찾아보기 @ExceptionHandler(MethodArgumentTypeMismatchException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) - public ResponseEntity> handleInValidRequestException(MethodArgumentTypeMismatchException e) { - Map errors = new HashMap<>(); - errors.put(e.getName(), e.getLocalizedMessage()); - return ResponseEntity.badRequest().body(errors); + public Map handleInValidRequestException(MethodArgumentTypeMismatchException e) { + String code = e.getErrorCode(); + String fieldName = e.getName(); + Locale locale = LocaleContextHolder.getLocale(); // 현재 스레드의 로케일 정보를 가져온다. + String errorMessage = messageSource.getMessage(code, null, locale); // 국제화 된 메시지를 가져온다. + + Map error = new HashMap<>(); + error.put(fieldName, errorMessage); + return error; } @ExceptionHandler({CommentException.class, AdminException.class, EventUserException.class, FcfsEventException.class, UrlException.class, InternalServerException.class}) From 6811ca03a5cb99be0b1ec3540dd103f139883ee0 Mon Sep 17 00:00:00 2001 From: blaxsior Date: Thu, 8 Aug 2024 17:40:05 +0900 Subject: [PATCH 03/16] =?UTF-8?q?[feat]=20=EC=B6=94=EC=B2=A8=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EB=8B=B9=EC=B2=A8=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?bulk=20insert=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JPA로 추첨 이벤트 당첨 정보를 저장하기 위해서는 연관 객체인 EventUser를 객체로 가져와 연관 관계를 지정해야 하고, insertAll을 수행해도 단일 insert 여러번으로 처리된다. 이런 불필요한 작업을 제거하기 위해 JDBC Template 기반으로 bulk insert 기능을 구현한다. --- .../DrawEventWinningInfoBulkInsertDto.java | 18 +++++ .../CustomDrawEventWinningInfoRepository.java | 9 +++ ...tomDrawEventWinningInfoRepositoryImpl.java | 40 +++++++++++ .../DrawEventWinningInfoRepository.java | 3 +- ...rawEventWinningInfoRepositoryImplTest.java | 70 +++++++++++++++++++ 5 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 src/main/java/hyundai/softeer/orange/event/draw/dto/DrawEventWinningInfoBulkInsertDto.java create mode 100644 src/main/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepository.java create mode 100644 src/main/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepositoryImpl.java create mode 100644 src/test/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepositoryImplTest.java diff --git a/src/main/java/hyundai/softeer/orange/event/draw/dto/DrawEventWinningInfoBulkInsertDto.java b/src/main/java/hyundai/softeer/orange/event/draw/dto/DrawEventWinningInfoBulkInsertDto.java new file mode 100644 index 00000000..1e7333b4 --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/draw/dto/DrawEventWinningInfoBulkInsertDto.java @@ -0,0 +1,18 @@ +package hyundai.softeer.orange.event.draw.dto; + +import lombok.Getter; + +@Getter +public class DrawEventWinningInfoBulkInsertDto { + private long eventUserId; + private long ranking; + private long drawEventId; + + public static DrawEventWinningInfoBulkInsertDto of(long eventUserId, long ranking, long drawEventId) { + var dto = new DrawEventWinningInfoBulkInsertDto(); + dto.eventUserId = eventUserId; + dto.ranking = ranking; + dto.drawEventId = drawEventId; + return dto; + } +} diff --git a/src/main/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepository.java b/src/main/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepository.java new file mode 100644 index 00000000..e0967667 --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepository.java @@ -0,0 +1,9 @@ +package hyundai.softeer.orange.event.draw.repository; + +import hyundai.softeer.orange.event.draw.dto.DrawEventWinningInfoBulkInsertDto; + +import java.util.List; + +public interface CustomDrawEventWinningInfoRepository { + void insertMany(List targets); +} diff --git a/src/main/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepositoryImpl.java b/src/main/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepositoryImpl.java new file mode 100644 index 00000000..a8460b2e --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepositoryImpl.java @@ -0,0 +1,40 @@ +package hyundai.softeer.orange.event.draw.repository; + +import hyundai.softeer.orange.event.draw.dto.DrawEventWinningInfoBulkInsertDto; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class CustomDrawEventWinningInfoRepositoryImpl implements CustomDrawEventWinningInfoRepository { + private final JdbcTemplate jdbcTemplate; + + @Override + public void insertMany(List targets) { + String sql = "insert into draw_event_winning_info (event_user_id, ranking, draw_event_id) VALUES (?, ?, ?)"; + + jdbcTemplate.batchUpdate( + sql, + new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + var target = targets.get(i); + ps.setLong(1, target.getEventUserId()); + ps.setLong(2, target.getRanking()); + ps.setLong(3, target.getDrawEventId()); + } + + @Override + public int getBatchSize() { + return targets.size(); + } + } + ); + } +} diff --git a/src/main/java/hyundai/softeer/orange/event/draw/repository/DrawEventWinningInfoRepository.java b/src/main/java/hyundai/softeer/orange/event/draw/repository/DrawEventWinningInfoRepository.java index d56779ef..fe9ac841 100644 --- a/src/main/java/hyundai/softeer/orange/event/draw/repository/DrawEventWinningInfoRepository.java +++ b/src/main/java/hyundai/softeer/orange/event/draw/repository/DrawEventWinningInfoRepository.java @@ -4,6 +4,5 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -@Repository -public interface DrawEventWinningInfoRepository extends JpaRepository { +public interface DrawEventWinningInfoRepository extends JpaRepository, CustomDrawEventWinningInfoRepository { } diff --git a/src/test/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepositoryImplTest.java b/src/test/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepositoryImplTest.java new file mode 100644 index 00000000..4d746a87 --- /dev/null +++ b/src/test/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepositoryImplTest.java @@ -0,0 +1,70 @@ +package hyundai.softeer.orange.event.draw.repository; + +import hyundai.softeer.orange.event.common.entity.EventFrame; +import hyundai.softeer.orange.event.common.repository.EventFrameRepository; +import hyundai.softeer.orange.event.draw.dto.DrawEventWinningInfoBulkInsertDto; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.TestPropertySource; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + + +@DataJpaTest(showSql = false) +@TestPropertySource(locations = "classpath:application-test.yml") +class CustomDrawEventWinningInfoRepositoryImplTest { + @Autowired // DrawEventWinningInfoRepository로 접근 가능해야 함 + private DrawEventWinningInfoRepository repo; + + @Autowired + JdbcTemplate jdbcTemplate; + + @BeforeEach + void setUp() { + jdbcTemplate.execute("INSERT INTO event_frame(name) VALUES ('test')"); + jdbcTemplate.execute("INSERT INTO event_metadata(event_type,event_frame_id,event_id) values (1, 1, 'HD_240808_001')"); + jdbcTemplate.execute("INSERT INTO draw_event values ()"); + jdbcTemplate.execute("INSERT INTO event_metadata(event_type,event_frame_id,event_id) values (1, 1, 'HD_240808_002')"); + jdbcTemplate.execute("INSERT INTO draw_event values ()"); + jdbcTemplate.execute("INSERT INTO event_user(score, event_frame_id, user_id) values ( 0, 1, 'user1')"); + jdbcTemplate.execute("INSERT INTO event_user(score, event_frame_id, user_id) values ( 0, 1, 'user2')"); + jdbcTemplate.execute("INSERT INTO event_user(score, event_frame_id, user_id) values ( 0, 1, 'user3')"); + jdbcTemplate.execute("INSERT INTO event_user(score, event_frame_id, user_id) values ( 0, 1, 'user4')"); + jdbcTemplate.execute("INSERT INTO event_user(score, event_frame_id, user_id) values ( 0, 1, 'user5')"); + jdbcTemplate.execute("INSERT INTO event_user(score, event_frame_id, user_id) values ( 0, 1, 'user6')"); + } + + + @DisplayName("제대로 데이터를 삽입하는지 확인") + @Test + void testBulkInsertWork() { + // Executing prepared SQL statement [insert into draw_event_winning_info (event_user_id, ranking, draw_event_id) VALUES (?, ?, ?)] + + List targets = List.of( + DrawEventWinningInfoBulkInsertDto.of(1, 1, 1), + DrawEventWinningInfoBulkInsertDto.of(2, 1, 1), + DrawEventWinningInfoBulkInsertDto.of(3, 2, 1), + DrawEventWinningInfoBulkInsertDto.of(4, 2, 1), + DrawEventWinningInfoBulkInsertDto.of(5, 2, 1), + DrawEventWinningInfoBulkInsertDto.of(6, 2, 1), + DrawEventWinningInfoBulkInsertDto.of(1, 1, 2), + DrawEventWinningInfoBulkInsertDto.of(2, 1, 2), + DrawEventWinningInfoBulkInsertDto.of(3, 2, 2), + DrawEventWinningInfoBulkInsertDto.of(4, 2, 2), + DrawEventWinningInfoBulkInsertDto.of(5, 2, 2), + DrawEventWinningInfoBulkInsertDto.of(6, 2, 2) + ); + repo.insertMany(targets); + var events = repo.findAll(); + assertThat(events).hasSize(12); + } +} \ No newline at end of file From e9955a432e575c1fee9c0a0bd97219e1df7203a8 Mon Sep 17 00:00:00 2001 From: blaxsior Date: Thu, 8 Aug 2024 18:13:32 +0900 Subject: [PATCH 04/16] =?UTF-8?q?[feat]=20=EC=B6=94=EC=B2=A8=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EB=88=84=EC=A0=81=ED=95=A9=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=EC=B6=94=EC=B2=A8=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../picker/AccSumBasedWinnerPicker.java | 124 ++++++++++++++++++ .../draw/component/picker/PickTarget.java | 3 + .../draw/component/picker/WinnerPicker.java | 10 ++ .../picker/AccSumBasedWinnerPickerTest.java | 113 ++++++++++++++++ 4 files changed, 250 insertions(+) create mode 100644 src/main/java/hyundai/softeer/orange/event/draw/component/picker/AccSumBasedWinnerPicker.java create mode 100644 src/main/java/hyundai/softeer/orange/event/draw/component/picker/PickTarget.java create mode 100644 src/main/java/hyundai/softeer/orange/event/draw/component/picker/WinnerPicker.java create mode 100644 src/test/java/hyundai/softeer/orange/event/draw/component/picker/AccSumBasedWinnerPickerTest.java diff --git a/src/main/java/hyundai/softeer/orange/event/draw/component/picker/AccSumBasedWinnerPicker.java b/src/main/java/hyundai/softeer/orange/event/draw/component/picker/AccSumBasedWinnerPicker.java new file mode 100644 index 00000000..29c48737 --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/draw/component/picker/AccSumBasedWinnerPicker.java @@ -0,0 +1,124 @@ +package hyundai.softeer.orange.event.draw.component.picker; + +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import java.util.*; + +@Primary +@Component +public class AccSumBasedWinnerPicker implements WinnerPicker { + + @Override + public List pick(List items, long count) { + long maxPickCount = Math.min(items.size(), count); + // TODO: 둘 중 하나를 선택하는 최적 조건 분석하기. + if (count - maxPickCount <= 10 || // 두 값 차이가 작을 때 + ((double) count / maxPickCount) <= 1.1 ) { + return pickMany(items, maxPickCount); + } else { + return pickManyUsingSet(items, maxPickCount); + } + } + + protected List pickMany(List targets, long count) { + List pickedTargets = new ArrayList<>(); + // 추첨에 참여하는 객체들이 존재하는 set + Set targetSet = new HashSet<>(targets); + + for(int i = 0; i < count; i++) { + RandomItem[] items = getAccumulatedItems(targetSet); + long bound = items[items.length - 1].score; + long targetScore = new Random().nextLong(1, bound + 1); + int pickedIdx = binarySearch(items, targetScore); + PickTarget pickedTarget = items[pickedIdx].target; + + targetSet.remove(pickedTarget); + pickedTargets.add(pickedTarget); + } + + return pickedTargets; + } + + /** + * set 자료구조를 이용하여 가중합을 1번만 계산하는 타겟 선택 방식 + * @param targets 선택 대상이 되는 배열 + * @param count + * @return + */ + protected List pickManyUsingSet(List targets, long count) { + // 가중합 배열 + RandomItem[] items = getAccumulatedItems(targets); + List pickedTargets = new ArrayList<>(); + long bound = items[items.length - 1].score; + // 이미 선택된 대상이 존재하는 공간 + Set pickedIdxSet = new HashSet<>(); + + for(int i = 0; i < count; i++) { + int pickedIdx; + do { + long targetScore = new Random().nextLong(1, bound + 1); + pickedIdx = binarySearch(items, targetScore); + }while(pickedIdxSet.contains(pickedIdx)); + // 방문했다고 마킹 + pickedIdxSet.add(pickedIdx); + pickedTargets.add(targets.get(pickedIdx)); + } + + return pickedTargets; + } + + + + /** + * 누적합 배열을 반환한다 + * @param targets 추첨 대상들 + * @return 누적합 형태로 표현된 RandomItem 배열 + */ + protected RandomItem[] getAccumulatedItems(Collection targets) { + RandomItem[] items = new RandomItem[targets.size()]; + long score = 0; + + int idx = 0; + for (PickTarget target : targets) { + score += target.score(); + items[idx++] = new RandomItem(target, score); + } + + return items; + } + + /** + * 이진 탐색을 이용하여 누적합 배열에서 대상 인덱스를 얻는다. + * @param items 누적합 배열 + * @param target 대상 점수 + * @return 선택된 인덱스 값 + */ + protected int binarySearch(RandomItem[] items, long target) { + int low = 0; + int high = items.length - 1; + + while(low <= high) { + // >>>은 비트열 오른쪽으로 이동, 빈 공간은 0으로 채움 + // 2로 나누는 로직을 >>>으로 처리 + int mid = (low + high) >>> 1; + + if(items[mid].score > target) { + high = mid - 1; + } else if(items[mid].score < target) { + low = mid + 1; + } else { + break; + } + } + + return low; + } + + /** + * 가중합을 기록해두는 객체 + * @param target 추첨 대상 + * @param score 누적합 점수 + */ + protected record RandomItem(PickTarget target, long score) {} +} diff --git a/src/main/java/hyundai/softeer/orange/event/draw/component/picker/PickTarget.java b/src/main/java/hyundai/softeer/orange/event/draw/component/picker/PickTarget.java new file mode 100644 index 00000000..d0ac08a1 --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/draw/component/picker/PickTarget.java @@ -0,0 +1,3 @@ +package hyundai.softeer.orange.event.draw.component.picker; + +public record PickTarget(Long key, long score) {} \ No newline at end of file diff --git a/src/main/java/hyundai/softeer/orange/event/draw/component/picker/WinnerPicker.java b/src/main/java/hyundai/softeer/orange/event/draw/component/picker/WinnerPicker.java new file mode 100644 index 00000000..cc784695 --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/draw/component/picker/WinnerPicker.java @@ -0,0 +1,10 @@ +package hyundai.softeer.orange.event.draw.component.picker; + +import java.util.List; + +/** + * "추첨" 작업을 진행하는 서비스. 여러 추첨 방식이 존재할 수 있으므로, 추첨 과정은 별도 인터페이스로 분리. + */ +public interface WinnerPicker { + List pick(List items,long count); +} diff --git a/src/test/java/hyundai/softeer/orange/event/draw/component/picker/AccSumBasedWinnerPickerTest.java b/src/test/java/hyundai/softeer/orange/event/draw/component/picker/AccSumBasedWinnerPickerTest.java new file mode 100644 index 00000000..e3348df2 --- /dev/null +++ b/src/test/java/hyundai/softeer/orange/event/draw/component/picker/AccSumBasedWinnerPickerTest.java @@ -0,0 +1,113 @@ +package hyundai.softeer.orange.event.draw.component.picker; + +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +@Slf4j +class AccSumBasedWinnerPickerTest { + + @DisplayName("정상적으로 N개의 서로 다른 아이템을 뽑는지 검사") + @Test + public void test_pickMany() { + AccSumBasedWinnerPicker picker = new AccSumBasedWinnerPicker(); + List pickTargets = new ArrayList<>(); + + Random random = new Random(); + for (int i = 0; i < 10000; i++) { + pickTargets.add(new PickTarget((long)i, random.nextInt(1, 200))); + } + + int pickCount = 1000; + long startTime = System.currentTimeMillis(); + // pickMany와 pickMany를 바꿔가면서 테스트 필요. + var picked = picker.pickMany(pickTargets, pickCount); + long endTime = System.currentTimeMillis(); + log.info("time lapse: {}", (endTime - startTime)); + + var distinctPicked = picked.stream().map(PickTarget::key).collect(Collectors.toSet()); + assertThat(distinctPicked).hasSize(1000); + } + + @DisplayName("정상적으로 N개의 서로 다른 아이템을 뽑는지 검사") + @Test + public void test_pickManyUsingSet() { + AccSumBasedWinnerPicker picker = new AccSumBasedWinnerPicker(); + List pickTargets = new ArrayList<>(); + + Random random = new Random(); + for (int i = 0; i < 10000; i++) { + pickTargets.add(new PickTarget((long)i, random.nextInt(1, 200))); + } + + int pickCount = 1000; + long startTime = System.currentTimeMillis(); + // pickMany와 pickMany를 바꿔가면서 테스트 필요. + var picked = picker.pickManyUsingSet(pickTargets, pickCount); + long endTime = System.currentTimeMillis(); + log.info("time lapse: {}", (endTime - startTime)); + + var distinctPicked = picked.stream().map(PickTarget::key).collect(Collectors.toSet()); + assertThat(distinctPicked).hasSize(1000); + } + + @DisplayName("binary search가 제대로 동작하는지 테스트") + @Test + public void test_binarySearch() { + AccSumBasedWinnerPicker picker = new AccSumBasedWinnerPicker(); + AccSumBasedWinnerPicker.RandomItem[] items = new AccSumBasedWinnerPicker.RandomItem[5]; + + var target = new PickTarget(0L, 0L); + + items[0] = new AccSumBasedWinnerPicker.RandomItem(target, 5); + items[1] = new AccSumBasedWinnerPicker.RandomItem(target, 10); + items[2] = new AccSumBasedWinnerPicker.RandomItem(target, 16); + items[3] = new AccSumBasedWinnerPicker.RandomItem(target, 21); + items[4] = new AccSumBasedWinnerPicker.RandomItem(target, 33); + + // 1 ~ 5는 item 0에, 6 ~ 10은 item 1에 존재 + int idx = picker.binarySearch(items, 1); + assertThat(idx).isEqualTo(0); + int idx2 = picker.binarySearch(items, 5); + assertThat(idx2).isEqualTo(0); + int idx3 = picker.binarySearch(items, 6); + assertThat(idx3).isEqualTo(1); + int idx4 = picker.binarySearch(items, 10); + assertThat(idx4).isEqualTo(1); + int idx5 = picker.binarySearch(items, 11); + assertThat(idx5).isEqualTo(2); + int idx6 = picker.binarySearch(items, 33); + assertThat(idx6).isEqualTo(4); + } + + @DisplayName("정상적으로 누적합을 만드는지 검사") + @Test + public void test_getAccumulatedItems() { + + List pickTargets = new ArrayList<>(); + + for (int i = 1; i < 5; i++) { + pickTargets.add(new PickTarget((long)i, i)); + } + AccSumBasedWinnerPicker picker = new AccSumBasedWinnerPicker(); + + var randItems = picker.getAccumulatedItems(pickTargets); + + assertThat(randItems).hasSize(4); + assertThat(randItems[0].score()).isEqualTo(1); + assertThat(randItems[1].score()).isEqualTo(3); + assertThat(randItems[2].score()).isEqualTo(6); + assertThat(randItems[3].score()).isEqualTo(10); + } +} \ No newline at end of file From 1e6bf5ccfd95b2d1e746ab9be03953325cf1d9d3 Mon Sep 17 00:00:00 2001 From: blaxsior Date: Thu, 8 Aug 2024 19:14:27 +0900 Subject: [PATCH 05/16] =?UTF-8?q?[feat]=20native=20query=20count=EA=B0=80?= =?UTF-8?q?=20=EC=A0=9C=EB=8C=80=EB=A1=9C=20=EB=8F=99=EC=9E=91=ED=95=98?= =?UTF-8?q?=EB=8A=94=EC=A7=80=20=ED=99=95=EC=9D=B8(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/dto/WriteCommentCountDto.java | 6 +++ .../comment/repository/CommentRepository.java | 10 ++++ .../resources/sql/CommentRepositoryTest.sql | 26 ++++++++++ .../repository/CommentRepositoryTest.java | 52 +++++++++++++++++++ .../picker/AccSumBasedWinnerPickerTest.java | 4 -- 5 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 src/main/java/hyundai/softeer/orange/comment/dto/WriteCommentCountDto.java create mode 100644 src/main/resources/sql/CommentRepositoryTest.sql create mode 100644 src/test/java/hyundai/softeer/orange/comment/repository/CommentRepositoryTest.java diff --git a/src/main/java/hyundai/softeer/orange/comment/dto/WriteCommentCountDto.java b/src/main/java/hyundai/softeer/orange/comment/dto/WriteCommentCountDto.java new file mode 100644 index 00000000..f430d710 --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/comment/dto/WriteCommentCountDto.java @@ -0,0 +1,6 @@ +package hyundai.softeer.orange.comment.dto; + +public interface WriteCommentCountDto { + Long getEventUserId(); + Long getCount(); +} diff --git a/src/main/java/hyundai/softeer/orange/comment/repository/CommentRepository.java b/src/main/java/hyundai/softeer/orange/comment/repository/CommentRepository.java index f2da290f..37bc5125 100644 --- a/src/main/java/hyundai/softeer/orange/comment/repository/CommentRepository.java +++ b/src/main/java/hyundai/softeer/orange/comment/repository/CommentRepository.java @@ -1,5 +1,6 @@ package hyundai.softeer.orange.comment.repository; +import hyundai.softeer.orange.comment.dto.WriteCommentCountDto; import hyundai.softeer.orange.comment.entity.Comment; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -26,4 +27,13 @@ public interface CommentRepository extends JpaRepository { countProjection = "c.id", // 어떤 값으로 count 셀건지 지정. 지정 안하면 count(c.*)가 되어 문제 발생. nativeQuery = true) Page findAllByEventId(@Param("eventId") String eventId, Pageable pageable); + + // 이름 매핑 필요. 필드 이름과 직접 매핑. + @Query(value = "SELECT c.event_user_id as eventUserId, COUNT(c.event_user_id) as count " + + "FROM comment c " + + "JOIN event_frame ef ON c.event_frame_id = ef.id " + + "JOIN event_metadata e ON ef.id = e.event_frame_id " + + "WHERE e.id = :eventRawId " + + "GROUP BY c.event_user_id " , nativeQuery = true) + List countPerEventUserByEventId(@Param("eventRawId") Long eventRawId); } diff --git a/src/main/resources/sql/CommentRepositoryTest.sql b/src/main/resources/sql/CommentRepositoryTest.sql new file mode 100644 index 00000000..e1e03438 --- /dev/null +++ b/src/main/resources/sql/CommentRepositoryTest.sql @@ -0,0 +1,26 @@ +INSERT INTO event_frame(name) VALUES ('test1'); +INSERT INTO event_frame(name) VALUES ('test2'); + +INSERT INTO event_metadata(event_type, event_frame_id, event_id) VALUES (1, 1, 'HD_240808_001'); +INSERT INTO event_metadata(event_type, event_frame_id, event_id) VALUES (0, 2, 'HD_240808_002'); + +INSERT INTO event_user(score, event_frame_id, user_id) VALUES (0, 1, 'user1'); +INSERT INTO event_user(score, event_frame_id, user_id) VALUES (0, 1, 'user2'); +INSERT INTO event_user(score, event_frame_id, user_id) VALUES (0, 1, 'user3'); + +-- 3 comments for user1 +INSERT INTO comment(event_frame_id, event_user_id) VALUES (1, 1); +INSERT INTO comment(event_frame_id, event_user_id) VALUES (1, 1); +INSERT INTO comment(event_frame_id, event_user_id) VALUES (1, 1); + +-- 6 comments for user2 +INSERT INTO comment(event_frame_id, event_user_id) VALUES (1, 2); +INSERT INTO comment(event_frame_id, event_user_id) VALUES (1, 2); +INSERT INTO comment(event_frame_id, event_user_id) VALUES (1, 2); +INSERT INTO comment(event_frame_id, event_user_id) VALUES (1, 2); +INSERT INTO comment(event_frame_id, event_user_id) VALUES (1, 2); +INSERT INTO comment(event_frame_id, event_user_id) VALUES (1, 2); + +-- 2 comments for user3 +INSERT INTO comment(event_frame_id, event_user_id) VALUES (1, 3); +INSERT INTO comment(event_frame_id, event_user_id) VALUES (1, 3); diff --git a/src/test/java/hyundai/softeer/orange/comment/repository/CommentRepositoryTest.java b/src/test/java/hyundai/softeer/orange/comment/repository/CommentRepositoryTest.java new file mode 100644 index 00000000..c37ed566 --- /dev/null +++ b/src/test/java/hyundai/softeer/orange/comment/repository/CommentRepositoryTest.java @@ -0,0 +1,52 @@ +package hyundai.softeer.orange.comment.repository; + +import hyundai.softeer.orange.comment.dto.WriteCommentCountDto; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.jdbc.Sql; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +// 매 테스트마다 초기화하는 코드 찾아봐야 할듯? +@Sql(value = "classpath:sql/CommentRepositoryTest.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +@DataJpaTest(showSql = false) +@TestPropertySource(locations = "classpath:application-test.yml") +class CommentRepositoryTest { + @Autowired + JdbcTemplate jdbcTemplate; + + @Autowired + CommentRepository commentRepository; + + + @DisplayName("존재하는 대상 이벤트에 대해 작성된 댓글이 있다면 유저 별로 개수를 구해 반환") + @Test + void getCountOfCommentPerUserIfCommentExist() { + List counts = commentRepository.countPerEventUserByEventId(1L); + + assertThat(counts).hasSize(3); + assertThat(counts.get(0).getCount()).isEqualTo(3); + assertThat(counts.get(1).getCount()).isEqualTo(6); + assertThat(counts.get(2).getCount()).isEqualTo(2); + } + + @DisplayName("존재하지 않는 대상 이벤트는 빈 배열 반환") + @Test + void getCountOfCommentPerUserIfEventNotExist() { + List counts = commentRepository.countPerEventUserByEventId(3L); + assertThat(counts).hasSize(0); + } + + @DisplayName("존재해도 댓글 없으면 빈 배열 반환") + @Test + void getCountOfCommentPerUserIfEventExistButNoComment() { + List counts = commentRepository.countPerEventUserByEventId(2L); + assertThat(counts).hasSize(0); + } +} \ No newline at end of file diff --git a/src/test/java/hyundai/softeer/orange/event/draw/component/picker/AccSumBasedWinnerPickerTest.java b/src/test/java/hyundai/softeer/orange/event/draw/component/picker/AccSumBasedWinnerPickerTest.java index e3348df2..7a005846 100644 --- a/src/test/java/hyundai/softeer/orange/event/draw/component/picker/AccSumBasedWinnerPickerTest.java +++ b/src/test/java/hyundai/softeer/orange/event/draw/component/picker/AccSumBasedWinnerPickerTest.java @@ -1,19 +1,15 @@ package hyundai.softeer.orange.event.draw.component.picker; import lombok.extern.slf4j.Slf4j; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Random; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; @Slf4j class AccSumBasedWinnerPickerTest { From 377554b5f88e5fff3aa86ecbfff5c6bb53700ce6 Mon Sep 17 00:00:00 2001 From: blaxsior Date: Fri, 9 Aug 2024 10:22:56 +0900 Subject: [PATCH 06/16] =?UTF-8?q?[feat]=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=20=EC=A0=95=EB=B3=B4=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20native=20query=20count=EA=B0=80=20=EC=A0=9C?= =?UTF-8?q?=EB=8C=80=EB=A1=9C=20=EB=8F=99=EC=9E=91=ED=95=98=EB=8A=94?= =?UTF-8?q?=EC=A7=80=20=ED=99=95=EC=9D=B8(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CommentRepositoryTest.java | 4 -- .../EventParticipationInfoRepositoryTest.java | 52 +++++++++++++++++++ .../resources/sql/CommentRepositoryTest.sql | 0 .../EventParticipationInfoRepositoryTest.sql | 28 ++++++++++ 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 src/test/java/hyundai/softeer/orange/event/draw/repository/EventParticipationInfoRepositoryTest.java rename src/{main => test}/resources/sql/CommentRepositoryTest.sql (100%) create mode 100644 src/test/resources/sql/EventParticipationInfoRepositoryTest.sql diff --git a/src/test/java/hyundai/softeer/orange/comment/repository/CommentRepositoryTest.java b/src/test/java/hyundai/softeer/orange/comment/repository/CommentRepositoryTest.java index c37ed566..f719c267 100644 --- a/src/test/java/hyundai/softeer/orange/comment/repository/CommentRepositoryTest.java +++ b/src/test/java/hyundai/softeer/orange/comment/repository/CommentRepositoryTest.java @@ -5,7 +5,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.jdbc.Sql; @@ -18,9 +17,6 @@ @DataJpaTest(showSql = false) @TestPropertySource(locations = "classpath:application-test.yml") class CommentRepositoryTest { - @Autowired - JdbcTemplate jdbcTemplate; - @Autowired CommentRepository commentRepository; diff --git a/src/test/java/hyundai/softeer/orange/event/draw/repository/EventParticipationInfoRepositoryTest.java b/src/test/java/hyundai/softeer/orange/event/draw/repository/EventParticipationInfoRepositoryTest.java new file mode 100644 index 00000000..9bb4b028 --- /dev/null +++ b/src/test/java/hyundai/softeer/orange/event/draw/repository/EventParticipationInfoRepositoryTest.java @@ -0,0 +1,52 @@ +package hyundai.softeer.orange.event.draw.repository; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.jdbc.Sql; + +import static org.assertj.core.api.Assertions.assertThat; + +@Sql(value = "classpath:sql/EventParticipationInfoRepositoryTest.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +@DataJpaTest(showSql = false) +@TestPropertySource(locations = "classpath:application-test.yml") +class EventParticipationInfoRepositoryTest { + @Autowired + EventParticipationInfoRepository epiRepository; + + @DisplayName("존재하는 draw 이벤트는 참여자 정보 반환") + @Test + void getParticipationCountPerUserIfDrawEventExist() { + var participationCounts = epiRepository.countPerEventUserByEventId(1L); + participationCounts.sort((a,b) -> (int) (a.getEventUserId() - b.getEventUserId())); + assertThat(participationCounts).hasSize(3); + assertThat(participationCounts.get(0).getCount()).isEqualTo(3); + assertThat(participationCounts.get(1).getCount()).isEqualTo(6); + assertThat(participationCounts.get(2).getCount()).isEqualTo(2); + } + + @DisplayName("존재하지 않는 draw 이벤트는 빈 배열 반환") + @Test + void getEmptyArrIfDrawEventNotExist() { + var participationCounts = epiRepository.countPerEventUserByEventId(10L); + assertThat(participationCounts).isEmpty(); + } + + @DisplayName("draw 이벤트 존재해도 유저 없으면 안함") + @Test + void getEmptyArrIfDrawEventExistButUserNotExist() { + var participationCounts = epiRepository.countPerEventUserByEventId(2L); + assertThat(participationCounts).isEmpty(); + } + + + @DisplayName("draw 이벤트 존재해도 유저 없으면 안함") + @Test + void getEmptyArrIfDrawEventAndUserExistButNoParticipation() { + var participationCounts = epiRepository.countPerEventUserByEventId(2L); + assertThat(participationCounts).isEmpty(); + } +} \ No newline at end of file diff --git a/src/main/resources/sql/CommentRepositoryTest.sql b/src/test/resources/sql/CommentRepositoryTest.sql similarity index 100% rename from src/main/resources/sql/CommentRepositoryTest.sql rename to src/test/resources/sql/CommentRepositoryTest.sql diff --git a/src/test/resources/sql/EventParticipationInfoRepositoryTest.sql b/src/test/resources/sql/EventParticipationInfoRepositoryTest.sql new file mode 100644 index 00000000..6ac6126a --- /dev/null +++ b/src/test/resources/sql/EventParticipationInfoRepositoryTest.sql @@ -0,0 +1,28 @@ +INSERT INTO event_frame(name) VALUES ('test1'); +INSERT INTO event_frame(name) VALUES ('test2'); +INSERT INTO event_frame(name) VALUES ('test3'); + +INSERT INTO event_metadata(event_type, event_frame_id, event_id) VALUES (1, 1, 'HD_240808_001'); +INSERT INTO event_metadata(event_type, event_frame_id, event_id) VALUES (1, 2, 'HD_240808_002'); +INSERT INTO event_metadata(event_type, event_frame_id, event_id) VALUES (1, 3, 'HD_240808_003'); + +INSERT INTO draw_event(event_metadata_id) VALUES(1); + +INSERT INTO event_user(score, event_frame_id, user_id) VALUES (0, 1, 'user1'); +INSERT INTO event_user(score, event_frame_id, user_id) VALUES (0, 1, 'user2'); +INSERT INTO event_user(score, event_frame_id, user_id) VALUES (0, 1, 'user3'); +INSERT INTO event_user(score, event_frame_id, user_id) VALUES (0, 3, 'user4'); + +INSERT INTO event_partication_info(draw_event_id, event_user_id) VALUES (1,1); +INSERT INTO event_partication_info(draw_event_id, event_user_id) VALUES (1,1); +INSERT INTO event_partication_info(draw_event_id, event_user_id) VALUES (1,1); + +INSERT INTO event_partication_info(draw_event_id, event_user_id) VALUES (1,2); +INSERT INTO event_partication_info(draw_event_id, event_user_id) VALUES (1,2); +INSERT INTO event_partication_info(draw_event_id, event_user_id) VALUES (1,2); +INSERT INTO event_partication_info(draw_event_id, event_user_id) VALUES (1,2); +INSERT INTO event_partication_info(draw_event_id, event_user_id) VALUES (1,2); +INSERT INTO event_partication_info(draw_event_id, event_user_id) VALUES (1,2); + +INSERT INTO event_partication_info(draw_event_id, event_user_id) VALUES (1,3); +INSERT INTO event_partication_info(draw_event_id, event_user_id) VALUES (1,3); From 341fd654e5bf7ec4850348e2acf0dde6c1416705 Mon Sep 17 00:00:00 2001 From: blaxsior Date: Fri, 9 Aug 2024 10:30:32 +0900 Subject: [PATCH 07/16] =?UTF-8?q?[feat]=20=EC=A0=95=EC=B1=85=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=A0=90=EC=88=98=EB=A5=BC=20=EC=B1=84?= =?UTF-8?q?=EC=A0=90=ED=95=98=EB=8A=94=20=EC=97=AD=ED=95=A0=EC=9D=98=20Act?= =?UTF-8?q?ionHandler=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../score/actionHandler/ActionHandler.java | 12 ++++++++++ .../ParticipateEventActionHandler.java | 22 +++++++++++++++++++ .../WriteCommentActionHandler.java | 22 +++++++++++++++++++ .../event/draw/enums/DrawEventAction.java | 9 ++++++-- 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 src/main/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/ActionHandler.java create mode 100644 src/main/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/ParticipateEventActionHandler.java create mode 100644 src/main/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/WriteCommentActionHandler.java diff --git a/src/main/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/ActionHandler.java b/src/main/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/ActionHandler.java new file mode 100644 index 00000000..10dcc03c --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/ActionHandler.java @@ -0,0 +1,12 @@ +package hyundai.softeer.orange.event.draw.component.score.actionHandler; + +import java.util.Map; + +/** + * 채점 정책에 대한 동작을 처리한다. + *

component 이름은 action 이름 + ActionHandler 형식을 띄어야 한다.

+ *

액션 = {@link hyundai.softeer.orange.event.draw.enums.DrawEventAction}

+ */ +public interface ActionHandler { + void handle(Map scoreMap, long eventRawId, long score); +} diff --git a/src/main/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/ParticipateEventActionHandler.java b/src/main/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/ParticipateEventActionHandler.java new file mode 100644 index 00000000..00deb304 --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/ParticipateEventActionHandler.java @@ -0,0 +1,22 @@ +package hyundai.softeer.orange.event.draw.component.score.actionHandler; + +import hyundai.softeer.orange.event.draw.repository.EventParticipationInfoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@RequiredArgsConstructor +@Component("ParticipateEvent_ActionHandler") +public class ParticipateEventActionHandler implements ActionHandler { + private final EventParticipationInfoRepository repo; + + @Override + public void handle(Map scoreMap, long eventRawId, long score) { + var participateCounts = repo.countPerEventUserByEventId(eventRawId); + for (var p : participateCounts) { + long beforeScore = scoreMap.getOrDefault(p.getEventUserId(),0L); + scoreMap.put(p.getEventUserId(), beforeScore + p.getCount() * score); + } + } +} diff --git a/src/main/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/WriteCommentActionHandler.java b/src/main/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/WriteCommentActionHandler.java new file mode 100644 index 00000000..b0e38939 --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/WriteCommentActionHandler.java @@ -0,0 +1,22 @@ +package hyundai.softeer.orange.event.draw.component.score.actionHandler; + +import hyundai.softeer.orange.comment.repository.CommentRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@RequiredArgsConstructor +@Component("WriteComment_ActionHandler") +public class WriteCommentActionHandler implements ActionHandler { + private final CommentRepository repo; + + @Override + public void handle(Map scoreMap, long eventRawId, long score) { + var commentCounts = repo.countPerEventUserByEventId(eventRawId); + for (var c : commentCounts) { + long beforeScore = scoreMap.getOrDefault(c.getEventUserId(), 0L); + scoreMap.put(c.getEventUserId(), beforeScore + c.getCount() * score); + } + } +} diff --git a/src/main/java/hyundai/softeer/orange/event/draw/enums/DrawEventAction.java b/src/main/java/hyundai/softeer/orange/event/draw/enums/DrawEventAction.java index d17cada9..6404c2dd 100644 --- a/src/main/java/hyundai/softeer/orange/event/draw/enums/DrawEventAction.java +++ b/src/main/java/hyundai/softeer/orange/event/draw/enums/DrawEventAction.java @@ -1,6 +1,11 @@ package hyundai.softeer.orange.event.draw.enums; +/** + * 점수 계산에 반영될 수 있는 작업 목록. 대응되는 handler이 등록되어야 한다. + * + *

ActionHandler: {@link hyundai.softeer.orange.event.draw.component.score.actionHandler.ActionHandler}

+ */ public enum DrawEventAction { - WRITE_COMMENT, - PARTICIPATE_EVENT + WriteComment, + ParticipateEvent } From b619c00bf228d936b38b58c70d12cc3de8bcb9b3 Mon Sep 17 00:00:00 2001 From: blaxsior Date: Fri, 9 Aug 2024 11:12:29 +0900 Subject: [PATCH 08/16] =?UTF-8?q?[fix,=20chore]=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=97=90=EC=84=9C=20drawevent=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EC=99=B8=EB=9E=98=ED=82=A4=EA=B0=80=20=EC=A7=80?= =?UTF-8?q?=EC=A0=95=EB=90=98=EC=A7=80=20=EC=95=8A=EC=95=84=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=ED=95=98=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0=20/=20sql=EB=AC=B8=EC=9D=84=20=EB=B3=84=EB=8F=84?= =?UTF-8?q?=EC=9D=98=20sql=20=ED=8C=8C=EC=9D=BC=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...rawEventWinningInfoRepositoryImplTest.java | 24 ++------------ ...DrawEventWinningInfoRepositoryImplTest.sql | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+), 22 deletions(-) create mode 100644 src/test/resources/sql/CustomDrawEventWinningInfoRepositoryImplTest.sql diff --git a/src/test/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepositoryImplTest.java b/src/test/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepositoryImplTest.java index 4d746a87..be6d8e79 100644 --- a/src/test/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepositoryImplTest.java +++ b/src/test/java/hyundai/softeer/orange/event/draw/repository/CustomDrawEventWinningInfoRepositoryImplTest.java @@ -8,42 +8,22 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.jdbc.Sql; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +@Sql(value="classpath:sql/CustomDrawEventWinningInfoRepositoryImplTest.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) @DataJpaTest(showSql = false) @TestPropertySource(locations = "classpath:application-test.yml") class CustomDrawEventWinningInfoRepositoryImplTest { @Autowired // DrawEventWinningInfoRepository로 접근 가능해야 함 private DrawEventWinningInfoRepository repo; - @Autowired - JdbcTemplate jdbcTemplate; - - @BeforeEach - void setUp() { - jdbcTemplate.execute("INSERT INTO event_frame(name) VALUES ('test')"); - jdbcTemplate.execute("INSERT INTO event_metadata(event_type,event_frame_id,event_id) values (1, 1, 'HD_240808_001')"); - jdbcTemplate.execute("INSERT INTO draw_event values ()"); - jdbcTemplate.execute("INSERT INTO event_metadata(event_type,event_frame_id,event_id) values (1, 1, 'HD_240808_002')"); - jdbcTemplate.execute("INSERT INTO draw_event values ()"); - jdbcTemplate.execute("INSERT INTO event_user(score, event_frame_id, user_id) values ( 0, 1, 'user1')"); - jdbcTemplate.execute("INSERT INTO event_user(score, event_frame_id, user_id) values ( 0, 1, 'user2')"); - jdbcTemplate.execute("INSERT INTO event_user(score, event_frame_id, user_id) values ( 0, 1, 'user3')"); - jdbcTemplate.execute("INSERT INTO event_user(score, event_frame_id, user_id) values ( 0, 1, 'user4')"); - jdbcTemplate.execute("INSERT INTO event_user(score, event_frame_id, user_id) values ( 0, 1, 'user5')"); - jdbcTemplate.execute("INSERT INTO event_user(score, event_frame_id, user_id) values ( 0, 1, 'user6')"); - } - - @DisplayName("제대로 데이터를 삽입하는지 확인") @Test void testBulkInsertWork() { diff --git a/src/test/resources/sql/CustomDrawEventWinningInfoRepositoryImplTest.sql b/src/test/resources/sql/CustomDrawEventWinningInfoRepositoryImplTest.sql new file mode 100644 index 00000000..b29f2b9f --- /dev/null +++ b/src/test/resources/sql/CustomDrawEventWinningInfoRepositoryImplTest.sql @@ -0,0 +1,31 @@ +INSERT INTO event_frame(name) VALUES ('test'); + +INSERT INTO event_metadata(event_type, event_frame_id, event_id) +VALUES (1, 1, 'HD_240808_001'); + +INSERT INTO draw_event(event_metadata_id) +VALUES (1); + +INSERT INTO event_metadata(event_type, event_frame_id, event_id) +VALUES (1, 1, 'HD_240808_002'); + +INSERT INTO draw_event(event_metadata_id) +VALUES (2); + +INSERT INTO event_user(score, event_frame_id, user_id) +VALUES (0, 1, 'user1'); + +INSERT INTO event_user(score, event_frame_id, user_id) +VALUES (0, 1, 'user2'); + +INSERT INTO event_user(score, event_frame_id, user_id) +VALUES (0, 1, 'user3'); + +INSERT INTO event_user(score, event_frame_id, user_id) +VALUES (0, 1, 'user4'); + +INSERT INTO event_user(score, event_frame_id, user_id) +VALUES (0, 1, 'user5'); + +INSERT INTO event_user(score, event_frame_id, user_id) +VALUES (0, 1, 'user6'); \ No newline at end of file From ecbc5627c99c2028148836c109885802fc9701fd Mon Sep 17 00:00:00 2001 From: blaxsior Date: Fri, 9 Aug 2024 11:13:11 +0900 Subject: [PATCH 09/16] =?UTF-8?q?[test]=20Action=20Handler=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=B2=B4=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98=ED=96=89(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ParticipateEventActionHandlerTest.java | 46 +++++++++++++++++++ .../WriteCommentActionHandlerTest.java | 46 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/test/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/ParticipateEventActionHandlerTest.java create mode 100644 src/test/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/WriteCommentActionHandlerTest.java diff --git a/src/test/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/ParticipateEventActionHandlerTest.java b/src/test/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/ParticipateEventActionHandlerTest.java new file mode 100644 index 00000000..ce291622 --- /dev/null +++ b/src/test/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/ParticipateEventActionHandlerTest.java @@ -0,0 +1,46 @@ +package hyundai.softeer.orange.event.draw.component.score.actionHandler; + +import hyundai.softeer.orange.event.draw.dto.EventParticipateCountDto; +import hyundai.softeer.orange.event.draw.repository.EventParticipationInfoRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class ParticipateEventActionHandlerTest { + @DisplayName("사용자에 대한 점수 * 참여 수 설정") + @Test + void checkUserScoreXCountSet() { + EventParticipateCountDto dto1 = mock(EventParticipateCountDto.class); + when(dto1.getEventUserId()).thenReturn(1L); + when(dto1.getCount()).thenReturn(10L); + EventParticipateCountDto dto2 = mock(EventParticipateCountDto.class); + when(dto2.getEventUserId()).thenReturn(2L); + when(dto2.getCount()).thenReturn(3L); + EventParticipateCountDto dto3 = mock(EventParticipateCountDto.class); + when(dto3.getEventUserId()).thenReturn(3L); + when(dto3.getCount()).thenReturn(5L); + + var mockRepo = mock(EventParticipationInfoRepository.class); + when(mockRepo.countPerEventUserByEventId(anyLong())) + .thenReturn(List.of(dto1, dto2, dto3)); + + ParticipateEventActionHandler handler = new ParticipateEventActionHandler(mockRepo); + Map scoreMap = new HashMap<>(); + + long score = 3L; + handler.handle(scoreMap, anyLong(), score); + + assertThat(scoreMap).hasSize(3); + assertThat(scoreMap.get(1L)).isEqualTo(score * 10L); + assertThat(scoreMap.get(2L)).isEqualTo(score * 3L); + assertThat(scoreMap.get(3L)).isEqualTo(score * 5L); + } +} \ No newline at end of file diff --git a/src/test/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/WriteCommentActionHandlerTest.java b/src/test/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/WriteCommentActionHandlerTest.java new file mode 100644 index 00000000..c6d9f56a --- /dev/null +++ b/src/test/java/hyundai/softeer/orange/event/draw/component/score/actionHandler/WriteCommentActionHandlerTest.java @@ -0,0 +1,46 @@ +package hyundai.softeer.orange.event.draw.component.score.actionHandler; + +import hyundai.softeer.orange.comment.dto.WriteCommentCountDto; +import hyundai.softeer.orange.comment.repository.CommentRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class WriteCommentActionHandlerTest { + @DisplayName("사용자가 작성한 댓글 수 * 참여 수 설정") + @Test + void checkUserScoreXCountSet() { + WriteCommentCountDto dto1 = mock(WriteCommentCountDto.class); + when(dto1.getEventUserId()).thenReturn(1L); + when(dto1.getCount()).thenReturn(10L); + WriteCommentCountDto dto2 = mock(WriteCommentCountDto.class); + when(dto2.getEventUserId()).thenReturn(2L); + when(dto2.getCount()).thenReturn(3L); + WriteCommentCountDto dto3 = mock(WriteCommentCountDto.class); + when(dto3.getEventUserId()).thenReturn(3L); + when(dto3.getCount()).thenReturn(5L); + + var mockRepo = mock(CommentRepository.class); + when(mockRepo.countPerEventUserByEventId(anyLong())) + .thenReturn(List.of(dto1, dto2, dto3)); + + WriteCommentActionHandler handler = new WriteCommentActionHandler(mockRepo); + Map scoreMap = new HashMap<>(); + + long score = 3L; + handler.handle(scoreMap, anyLong(), score); + + assertThat(scoreMap).hasSize(3); + assertThat(scoreMap.get(1L)).isEqualTo(score * 10L); + assertThat(scoreMap.get(2L)).isEqualTo(score * 3L); + assertThat(scoreMap.get(3L)).isEqualTo(score * 5L); + } +} \ No newline at end of file From 686994e5dd3b41f448ca70df9a9bad84e8743499 Mon Sep 17 00:00:00 2001 From: blaxsior Date: Fri, 9 Aug 2024 11:46:09 +0900 Subject: [PATCH 10/16] =?UTF-8?q?[feat]=20=EC=A0=95=EC=B1=85=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=EC=A0=90=EC=88=98=20=EC=B1=84=EC=A0=90=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EA=B5=AC=ED=98=84(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../draw/component/score/ScoreCalculator.java | 29 +++++++ .../component/score/ScoreCalculatorTest.java | 84 +++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 src/main/java/hyundai/softeer/orange/event/draw/component/score/ScoreCalculator.java create mode 100644 src/test/java/hyundai/softeer/orange/event/draw/component/score/ScoreCalculatorTest.java diff --git a/src/main/java/hyundai/softeer/orange/event/draw/component/score/ScoreCalculator.java b/src/main/java/hyundai/softeer/orange/event/draw/component/score/ScoreCalculator.java new file mode 100644 index 00000000..c259950e --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/draw/component/score/ScoreCalculator.java @@ -0,0 +1,29 @@ +package hyundai.softeer.orange.event.draw.component.score; + +import hyundai.softeer.orange.event.draw.component.score.actionHandler.ActionHandler; +import hyundai.softeer.orange.event.draw.entity.DrawEventScorePolicy; +import hyundai.softeer.orange.event.draw.enums.DrawEventAction; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 특정 추첨 이벤트에 대한 점수를 계산하는 클래스. 저장은 안한다. + */ +@RequiredArgsConstructor +@Component +public class ScoreCalculator { + private final Map handlerMap; + + public Map calculate(long eventId, List policies) { + Map scoreMap = new HashMap<>(); + for (var policy : policies) { + ActionHandler handler = handlerMap.get(policy.getAction()); + handler.handle(scoreMap, eventId, policy.getScore()); + } + return scoreMap; + } +} diff --git a/src/test/java/hyundai/softeer/orange/event/draw/component/score/ScoreCalculatorTest.java b/src/test/java/hyundai/softeer/orange/event/draw/component/score/ScoreCalculatorTest.java new file mode 100644 index 00000000..1426c1a7 --- /dev/null +++ b/src/test/java/hyundai/softeer/orange/event/draw/component/score/ScoreCalculatorTest.java @@ -0,0 +1,84 @@ +package hyundai.softeer.orange.event.draw.component.score; + +import hyundai.softeer.orange.comment.dto.WriteCommentCountDto; +import hyundai.softeer.orange.comment.repository.CommentRepository; +import hyundai.softeer.orange.event.draw.component.score.actionHandler.ActionHandler; +import hyundai.softeer.orange.event.draw.component.score.actionHandler.ParticipateEventActionHandler; +import hyundai.softeer.orange.event.draw.component.score.actionHandler.WriteCommentActionHandler; +import hyundai.softeer.orange.event.draw.dto.EventParticipateCountDto; +import hyundai.softeer.orange.event.draw.entity.DrawEventScorePolicy; +import hyundai.softeer.orange.event.draw.enums.DrawEventAction; +import hyundai.softeer.orange.event.draw.repository.EventParticipationInfoRepository; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class ScoreCalculatorTest { + @DisplayName("유저의 점수 기록을 계산하여 반환. 제대로 계산하는지 검사") + @Test + void calculateScore() { + // 유저 참여 정보 mocking + EventParticipateCountDto dto1 = mock(EventParticipateCountDto.class); + when(dto1.getEventUserId()).thenReturn(1L); + when(dto1.getCount()).thenReturn(3L); + EventParticipateCountDto dto2 = mock(EventParticipateCountDto.class); + when(dto2.getEventUserId()).thenReturn(2L); + when(dto2.getCount()).thenReturn(4L); + EventParticipateCountDto dto3 = mock(EventParticipateCountDto.class); + when(dto3.getEventUserId()).thenReturn(3L); + when(dto3.getCount()).thenReturn(5L); + + var mockRepo = mock(EventParticipationInfoRepository.class); + when(mockRepo.countPerEventUserByEventId(anyLong())) + .thenReturn(List.of(dto1, dto2, dto3)); + + var epiHandler = new ParticipateEventActionHandler(mockRepo); + + WriteCommentCountDto dto4 = mock(WriteCommentCountDto.class); + when(dto4.getEventUserId()).thenReturn(2L); + when(dto4.getCount()).thenReturn(10L); + WriteCommentCountDto dto5 = mock(WriteCommentCountDto.class); + when(dto5.getEventUserId()).thenReturn(3L); + when(dto5.getCount()).thenReturn(6L); + WriteCommentCountDto dto6 = mock(WriteCommentCountDto.class); + when(dto6.getEventUserId()).thenReturn(4L); + when(dto6.getCount()).thenReturn(1L); + + var mockRepo2 = mock(CommentRepository.class); + when(mockRepo2.countPerEventUserByEventId(anyLong())) + .thenReturn(List.of(dto4, dto5, dto6)); + + var comHandler = new WriteCommentActionHandler(mockRepo2); + + + Map handlerMap = new HashMap<>(); + handlerMap.put(DrawEventAction.ParticipateEvent, epiHandler); + handlerMap.put(DrawEventAction.WriteComment, comHandler); + + ScoreCalculator scoreCalculator = new ScoreCalculator(handlerMap); + List policies = List.of( + DrawEventScorePolicy.of(DrawEventAction.ParticipateEvent, 5, null), + DrawEventScorePolicy.of(DrawEventAction.WriteComment, 3, null) + ); + + var resultMap = scoreCalculator.calculate(anyLong(), policies); + assertThat(resultMap).isNotNull(); + + assertThat(resultMap.size()).isEqualTo(4); + assertThat(resultMap.get(1L)).isEqualTo(15L); // 3 * 5 = 15 + assertThat(resultMap.get(2L)).isEqualTo(50L); // 4 * 5 + 10 * 3 = 50 + assertThat(resultMap.get(3L)).isEqualTo(43L); // 5 * 5 + 6 * 3 = 43 + assertThat(resultMap.get(4L)).isEqualTo(3L); // 1 * 3 = 3 + } +} \ No newline at end of file From dacefdca02be5aadbf7b7e998aa05c3dd4176e2a Mon Sep 17 00:00:00 2001 From: blaxsior Date: Fri, 9 Aug 2024 11:56:55 +0900 Subject: [PATCH 11/16] =?UTF-8?q?[chore]=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=20repository=20=EB=88=84=EB=9D=BD=EB=90=9C?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../orange/event/draw/dto/EventParticipateCountDto.java | 6 ++++++ .../repository/EventParticipationInfoRepository.java | 9 +++++++++ 2 files changed, 15 insertions(+) create mode 100644 src/main/java/hyundai/softeer/orange/event/draw/dto/EventParticipateCountDto.java diff --git a/src/main/java/hyundai/softeer/orange/event/draw/dto/EventParticipateCountDto.java b/src/main/java/hyundai/softeer/orange/event/draw/dto/EventParticipateCountDto.java new file mode 100644 index 00000000..ad16f5e2 --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/draw/dto/EventParticipateCountDto.java @@ -0,0 +1,6 @@ +package hyundai.softeer.orange.event.draw.dto; + +public interface EventParticipateCountDto { + long getEventUserId(); + long getCount(); +} diff --git a/src/main/java/hyundai/softeer/orange/event/draw/repository/EventParticipationInfoRepository.java b/src/main/java/hyundai/softeer/orange/event/draw/repository/EventParticipationInfoRepository.java index 19043aec..f74c5daa 100644 --- a/src/main/java/hyundai/softeer/orange/event/draw/repository/EventParticipationInfoRepository.java +++ b/src/main/java/hyundai/softeer/orange/event/draw/repository/EventParticipationInfoRepository.java @@ -1,9 +1,18 @@ package hyundai.softeer.orange.event.draw.repository; +import hyundai.softeer.orange.event.draw.dto.EventParticipateCountDto; import hyundai.softeer.orange.event.draw.entity.EventParticipationInfo; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface EventParticipationInfoRepository extends JpaRepository { + @Query(value = "SELECT event_user_id as eventUserId, COUNT(event_user_id) as count " + + "FROM event_partication_info " + + "WHERE draw_event_id = :eventRawId " + + "GROUP BY event_user_id", nativeQuery = true) + List countPerEventUserByEventId(Long eventRawId); } From 3e2d7a7864a18f44cd4e20c8096d800ba477fb69 Mon Sep 17 00:00:00 2001 From: blaxsior Date: Fri, 9 Aug 2024 11:57:11 +0900 Subject: [PATCH 12/16] =?UTF-8?q?[fix]=20Admin=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=EC=9D=98=20primary=20key=20=EC=A0=84=EB=9E=B5?= =?UTF-8?q?=EC=9D=B4=20auto=20increment=EA=B0=80=20=EC=95=84=EB=8B=88?= =?UTF-8?q?=EB=8D=98=20=EB=B6=80=EB=B6=84=20=EC=88=98=EC=A0=95(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/hyundai/softeer/orange/admin/entity/Admin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/hyundai/softeer/orange/admin/entity/Admin.java b/src/main/java/hyundai/softeer/orange/admin/entity/Admin.java index d97c3f69..8f3e53d5 100644 --- a/src/main/java/hyundai/softeer/orange/admin/entity/Admin.java +++ b/src/main/java/hyundai/softeer/orange/admin/entity/Admin.java @@ -14,7 +14,7 @@ @NoArgsConstructor public class Admin { @Id - @GeneratedValue(strategy = GenerationType.AUTO) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; /** From 4007e44a83024f121b719c05e7ebd2529efb47c8 Mon Sep 17 00:00:00 2001 From: blaxsior Date: Fri, 9 Aug 2024 11:57:19 +0900 Subject: [PATCH 13/16] =?UTF-8?q?[chore]=20=EC=BD=94=EB=93=9C=20=EB=A0=88?= =?UTF-8?q?=EB=B2=A8=EC=97=90=EC=84=9C=20ActionHandler=EC=9D=84=20DrawEven?= =?UTF-8?q?tAction=20enum=20=ED=83=80=EC=9E=85=20=EA=B8=B0=EB=B0=98?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=A0=91=EA=B7=BC=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../orange/event/draw/DrawEventConfig.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/main/java/hyundai/softeer/orange/event/draw/DrawEventConfig.java diff --git a/src/main/java/hyundai/softeer/orange/event/draw/DrawEventConfig.java b/src/main/java/hyundai/softeer/orange/event/draw/DrawEventConfig.java new file mode 100644 index 00000000..9730affa --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/draw/DrawEventConfig.java @@ -0,0 +1,36 @@ +package hyundai.softeer.orange.event.draw; + +import hyundai.softeer.orange.event.draw.component.score.actionHandler.ActionHandler; +import hyundai.softeer.orange.event.draw.enums.DrawEventAction; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Configuration +public class DrawEventConfig { + private static final String NamePattern = "(.+?)_ActionHandler"; + + // actionHandler을 Action 타입으로 가져올 수 있도록 설정 + @Bean(name = "actionHandlerMap") + public Map actionHandlerMap(Map handlers) { + Map actionHandlerMap = new HashMap<>(); + Pattern pattern = Pattern.compile(NamePattern); + + for(var entry: handlers.entrySet()) { + Matcher matcher = pattern.matcher(entry.getKey()); + if (!matcher.find()) throw new RuntimeException("no matched action"); + + String key = matcher.group(1); + ActionHandler actionHandler = entry.getValue(); + + actionHandlerMap.put(DrawEventAction.valueOf(key), actionHandler); + } + // 런타임에 변경 못하게 제한 + return Collections.unmodifiableMap(actionHandlerMap); + } +} From 58a17f637a22ccead0aaf75753fbd57222734e73 Mon Sep 17 00:00:00 2001 From: blaxsior Date: Fri, 9 Aug 2024 11:59:21 +0900 Subject: [PATCH 14/16] =?UTF-8?q?[feat]=20=EC=B6=94=EC=B2=A8=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=A0=90=EC=88=98=20=EC=B1=84=EC=A0=90=20?= =?UTF-8?q?+=20=EC=B6=94=EC=B2=A8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 차후 2개의 과정으로 분리할 수도 있음 --- .../draw/exception/DrawEventException.java | 10 +++ .../draw/repository/DrawEventRepository.java | 8 +- .../event/draw/service/DrawEventService.java | 90 +++++++++++++++++++ 3 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 src/main/java/hyundai/softeer/orange/event/draw/exception/DrawEventException.java create mode 100644 src/main/java/hyundai/softeer/orange/event/draw/service/DrawEventService.java diff --git a/src/main/java/hyundai/softeer/orange/event/draw/exception/DrawEventException.java b/src/main/java/hyundai/softeer/orange/event/draw/exception/DrawEventException.java new file mode 100644 index 00000000..ab9f8389 --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/draw/exception/DrawEventException.java @@ -0,0 +1,10 @@ +package hyundai.softeer.orange.event.draw.exception; + +import hyundai.softeer.orange.common.BaseException; +import hyundai.softeer.orange.common.ErrorCode; + +public class DrawEventException extends BaseException { + public DrawEventException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/hyundai/softeer/orange/event/draw/repository/DrawEventRepository.java b/src/main/java/hyundai/softeer/orange/event/draw/repository/DrawEventRepository.java index 6ba0d852..e97a507b 100644 --- a/src/main/java/hyundai/softeer/orange/event/draw/repository/DrawEventRepository.java +++ b/src/main/java/hyundai/softeer/orange/event/draw/repository/DrawEventRepository.java @@ -1,11 +1,15 @@ package hyundai.softeer.orange.event.draw.repository; -import hyundai.softeer.orange.event.common.entity.EventMetadata; import hyundai.softeer.orange.event.draw.entity.DrawEvent; +import io.lettuce.core.dynamic.annotation.Param; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository -public interface DrawEventRepository extends JpaRepository { } +public interface DrawEventRepository extends JpaRepository { + @Query(value = "SELECT d FROM DrawEvent d WHERE d.eventMetadata.eventId = :eventId") + Optional findByEventId(@Param("eventId") String eventId); +} diff --git a/src/main/java/hyundai/softeer/orange/event/draw/service/DrawEventService.java b/src/main/java/hyundai/softeer/orange/event/draw/service/DrawEventService.java new file mode 100644 index 00000000..a8bcd922 --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/event/draw/service/DrawEventService.java @@ -0,0 +1,90 @@ +package hyundai.softeer.orange.event.draw.service; + +import hyundai.softeer.orange.common.ErrorCode; +import hyundai.softeer.orange.event.draw.component.picker.PickTarget; +import hyundai.softeer.orange.event.draw.component.score.ScoreCalculator; +import hyundai.softeer.orange.event.draw.dto.DrawEventWinningInfoBulkInsertDto; +import hyundai.softeer.orange.event.draw.entity.DrawEvent; +import hyundai.softeer.orange.event.draw.entity.DrawEventMetadata; +import hyundai.softeer.orange.event.draw.entity.DrawEventScorePolicy; +import hyundai.softeer.orange.event.draw.exception.DrawEventException; +import hyundai.softeer.orange.event.draw.repository.DrawEventRepository; +import hyundai.softeer.orange.event.draw.component.picker.WinnerPicker; +import hyundai.softeer.orange.event.draw.repository.DrawEventWinningInfoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * 추첨 이벤트를 다루는 서비스 + */ +@RequiredArgsConstructor +@Service +public class DrawEventService { + private final DrawEventRepository deRepository; + private final DrawEventWinningInfoRepository deWinningInfoRepository; + private final WinnerPicker picker; + private final ScoreCalculator calculator; + + /** + * eventId에 대한 추첨을 진행하는 메서드 + * @param eventId 이벤트의 id 값 + */ + @Transactional + @Async + public void draw(String eventId) { + // 채점 & 추첨 과정 분리하는 것도 좋을것 같다. + DrawEvent drawEvent = deRepository.findByEventId(eventId) + .orElseThrow(() -> new DrawEventException(ErrorCode.EVENT_NOT_FOUND)); + + // draw event의 primary key id 값 + long drawEventRawId = drawEvent.getId(); + + // 점수 계산. 추후 추첨 과정과 분리될 수도 있음. + List policies = drawEvent.getPolicyList(); + var userScoreMap = calculator.calculate(drawEventRawId, policies); + + // 추첨 타겟 리스트 생성 + List targets = userScoreMap.entrySet().stream() + .map(it -> new PickTarget(it.getKey(), it.getValue())).toList(); + + // 몇 등이 몇명이나 있는지 적혀 있는 정보. 등급끼리 정렬해서 1 ~ n 등 순서로 정렬 + List metadataList = drawEvent.getMetadataList(); + metadataList.sort(Comparator.comparing(DrawEventMetadata::getGrade)); + + // 총 당첨 인원 설정 + long pickCount = drawEvent.getMetadataList().stream().mapToLong(DrawEventMetadata::getCount).sum(); + + // 당첨된 인원 구하기 + var pickedTargets = picker.pick(targets, pickCount); + + // 인원 등록을 위한 작업 + List insertTargets = new ArrayList<>(); + int mdIdx = -1; + long remain = 0; + long grade = -1; + DrawEventMetadata metadata = null; + + for(var target : pickedTargets) { + if(remain <= 0) { + mdIdx++; + metadata = metadataList.get(mdIdx); + grade = metadata.getGrade(); + remain = metadata.getCount(); + } + + insertTargets.add(DrawEventWinningInfoBulkInsertDto.of( + target.key(), + grade, + drawEventRawId + )); + } + + deWinningInfoRepository.insertMany(insertTargets); + } +} From 9e8bfa5825c43e60dc4f55dd5d907bed58d9f9bc Mon Sep 17 00:00:00 2001 From: blaxsior Date: Fri, 9 Aug 2024 16:10:45 +0900 Subject: [PATCH 15/16] =?UTF-8?q?[test]=20DrawEventService=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20=EC=88=98=EC=A0=95(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/draw/service/DrawEventService.java | 16 ++- .../draw/service/DrawEventServiceTest.java | 106 ++++++++++++++++++ 2 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 src/test/java/hyundai/softeer/orange/event/draw/service/DrawEventServiceTest.java diff --git a/src/main/java/hyundai/softeer/orange/event/draw/service/DrawEventService.java b/src/main/java/hyundai/softeer/orange/event/draw/service/DrawEventService.java index a8bcd922..f099d4e3 100644 --- a/src/main/java/hyundai/softeer/orange/event/draw/service/DrawEventService.java +++ b/src/main/java/hyundai/softeer/orange/event/draw/service/DrawEventService.java @@ -33,14 +33,14 @@ public class DrawEventService { /** * eventId에 대한 추첨을 진행하는 메서드 - * @param eventId 이벤트의 id 값 + * @param drawEventId draw event의 id 값. */ @Transactional @Async - public void draw(String eventId) { + public void draw(Long drawEventId) { // 채점 & 추첨 과정 분리하는 것도 좋을것 같다. - DrawEvent drawEvent = deRepository.findByEventId(eventId) - .orElseThrow(() -> new DrawEventException(ErrorCode.EVENT_NOT_FOUND)); + DrawEvent drawEvent = deRepository.findById(drawEventId) + .orElseThrow(() -> new DrawEventException(ErrorCode.DRAW_EVENT_NOT_FOUND)); // draw event의 primary key id 값 long drawEventRawId = drawEvent.getId(); @@ -54,15 +54,20 @@ public void draw(String eventId) { .map(it -> new PickTarget(it.getKey(), it.getValue())).toList(); // 몇 등이 몇명이나 있는지 적혀 있는 정보. 등급끼리 정렬해서 1 ~ n 등 순서로 정렬 + // 확률 높은 사람이 손해보면 안됨 List metadataList = drawEvent.getMetadataList(); metadataList.sort(Comparator.comparing(DrawEventMetadata::getGrade)); // 총 당첨 인원 설정 - long pickCount = drawEvent.getMetadataList().stream().mapToLong(DrawEventMetadata::getCount).sum(); + long pickCount = metadataList.stream() + .mapToLong(DrawEventMetadata::getCount).sum(); // 당첨된 인원 구하기 var pickedTargets = picker.pick(targets, pickCount); + // 이하 영역은 여러 작업을 동시에 수행할 수도 있음 + // ex) 인원 등록 / 상품 문자 발송 ... + // 인원 등록을 위한 작업 List insertTargets = new ArrayList<>(); int mdIdx = -1; @@ -83,6 +88,7 @@ public void draw(String eventId) { grade, drawEventRawId )); + remain--; } deWinningInfoRepository.insertMany(insertTargets); diff --git a/src/test/java/hyundai/softeer/orange/event/draw/service/DrawEventServiceTest.java b/src/test/java/hyundai/softeer/orange/event/draw/service/DrawEventServiceTest.java new file mode 100644 index 00000000..aae210e9 --- /dev/null +++ b/src/test/java/hyundai/softeer/orange/event/draw/service/DrawEventServiceTest.java @@ -0,0 +1,106 @@ +package hyundai.softeer.orange.event.draw.service; + +import hyundai.softeer.orange.event.draw.component.picker.PickTarget; +import hyundai.softeer.orange.event.draw.component.picker.WinnerPicker; +import hyundai.softeer.orange.event.draw.component.score.ScoreCalculator; +import hyundai.softeer.orange.event.draw.dto.DrawEventWinningInfoBulkInsertDto; +import hyundai.softeer.orange.event.draw.entity.DrawEvent; +import hyundai.softeer.orange.event.draw.entity.DrawEventMetadata; +import hyundai.softeer.orange.event.draw.repository.DrawEventRepository; +import hyundai.softeer.orange.event.draw.repository.DrawEventWinningInfoRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.*; + +class DrawEventServiceTest { + + private static final Logger log = LoggerFactory.getLogger(DrawEventServiceTest.class); + + @DisplayName("대응되는 이벤트가 존재하지 않으면 예외 반환") + @Test + void throwExceptionIfDrawEventNotFound() { + var deRepository = mock(DrawEventRepository.class); + when(deRepository.findById(anyLong())).thenReturn(Optional.empty()); + + var deService = new DrawEventService(deRepository, null, null, null); + + assertThatThrownBy(() -> { + deService.draw(0L); + }); + } + + @DisplayName("대응되는 이벤트가 존재하면 작업 수행") + @Test + void drawEvent() { + // draw event 처리 + var drawEvent = mock(DrawEvent.class); + + // 정확한 인원 수를 추첨하는지 + when(drawEvent.getMetadataList()).thenReturn(new ArrayList<>(List.of( + // 합 = 5 + DrawEventMetadata.of(3L, 2L, null, null), + DrawEventMetadata.of(1L, 1L, null, null), + DrawEventMetadata.of(2L, 2L, null, null) + ))); + when(drawEvent.getPolicyList()).thenReturn(List.of()); + + var deRepository = mock(DrawEventRepository.class); + when(deRepository.findById(anyLong())).thenReturn(Optional.of(drawEvent)); + + // repository 모킹. saveMany 호출하는지 검사 필요 + var deWinningInfoRepository = mock(DrawEventWinningInfoRepository.class); + + // 점수 채점기 모킹 + var calculator = mock(ScoreCalculator.class); + when(calculator.calculate(anyLong(), anyList())).thenReturn(Map.of(1L, 1L, 2L, 10L, 3L,5L)); + + // 추첨기 모킹 + var picker = mock(WinnerPicker.class); + when(picker.pick(anyList(), anyLong())).thenReturn(new ArrayList<>( + List.of( + new PickTarget(2L, 1L), // 1 + new PickTarget(1L, 2L), // 2 + new PickTarget(3L, 3L), // 2 + new PickTarget(5L, 1L), // 3 + new PickTarget(4L, 2L) // 3 + ))); + + var deService = new DrawEventService(deRepository, deWinningInfoRepository, picker, calculator); + + deService.draw(0L); + + ArgumentCaptor> ac = ArgumentCaptor.forClass(List.class); + + verify(picker, times(1)).pick(anyList(), eq(5L)); + verify(deWinningInfoRepository, times(1)).insertMany(ac.capture()); + + var list = ac.getValue(); + assertThat(list).hasSize(5); + assertThat(list.get(0).getEventUserId()).isEqualTo(2L); + assertThat(list.get(1).getEventUserId()).isEqualTo(1L); + assertThat(list.get(2).getEventUserId()).isEqualTo(3L); + assertThat(list.get(3).getEventUserId()).isEqualTo(5L); + assertThat(list.get(4).getEventUserId()).isEqualTo(4L); + for(var v: list) { + log.info("ranking {}", v.getRanking()); + } + assertThat(list.get(0).getRanking()).isEqualTo(1L); + assertThat(list.get(1).getRanking()).isEqualTo(2L); + assertThat(list.get(2).getRanking()).isEqualTo(2L); + assertThat(list.get(3).getRanking()).isEqualTo(3L); + assertThat(list.get(4).getRanking()).isEqualTo(3L); + } +} \ No newline at end of file From 26c2ae921c1161507a99dacea1637b7385abaeff Mon Sep 17 00:00:00 2001 From: blaxsior Date: Fri, 9 Aug 2024 16:11:52 +0900 Subject: [PATCH 16/16] =?UTF-8?q?[chore]=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EC=A0=90=EC=88=98=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../softeer/orange/common/ErrorCode.java | 1 + .../eventuser/dto/EventUserScoreDto.java | 3 ++ .../repository/CustomEventUserRepository.java | 10 ++++++ .../CustomEventUserRepositoryImpl.java | 36 +++++++++++++++++++ .../repository/EventUserRepository.java | 11 ++++-- 5 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 src/main/java/hyundai/softeer/orange/eventuser/dto/EventUserScoreDto.java create mode 100644 src/main/java/hyundai/softeer/orange/eventuser/repository/CustomEventUserRepository.java create mode 100644 src/main/java/hyundai/softeer/orange/eventuser/repository/CustomEventUserRepositoryImpl.java diff --git a/src/main/java/hyundai/softeer/orange/common/ErrorCode.java b/src/main/java/hyundai/softeer/orange/common/ErrorCode.java index cc9967aa..1673c945 100644 --- a/src/main/java/hyundai/softeer/orange/common/ErrorCode.java +++ b/src/main/java/hyundai/softeer/orange/common/ErrorCode.java @@ -31,6 +31,7 @@ public enum ErrorCode { USER_NOT_FOUND(HttpStatus.NOT_FOUND, false, "사용자를 찾을 수 없습니다."), SHORT_URL_NOT_FOUND(HttpStatus.NOT_FOUND, false, "단축 URL을 찾을 수 없습니다."), FCFS_EVENT_NOT_FOUND(HttpStatus.NOT_FOUND, false, "선착순 이벤트를 찾을 수 없습니다."), + DRAW_EVENT_NOT_FOUND(HttpStatus.NOT_FOUND, false, "추첨 이벤트를 찾을 수 없습니다."), EVENT_FRAME_NOT_FOUND(HttpStatus.NOT_FOUND, false, "이벤트 프레임을 찾을 수 없습니다."), EVENT_USER_NOT_FOUND(HttpStatus.NOT_FOUND, false, "이벤트 사용자를 찾을 수 없습니다."), COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, false, "기대평을 찾을 수 없습니다."), diff --git a/src/main/java/hyundai/softeer/orange/eventuser/dto/EventUserScoreDto.java b/src/main/java/hyundai/softeer/orange/eventuser/dto/EventUserScoreDto.java new file mode 100644 index 00000000..a6f17255 --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/eventuser/dto/EventUserScoreDto.java @@ -0,0 +1,3 @@ +package hyundai.softeer.orange.eventuser.dto; + +public record EventUserScoreDto(Long userId, Long score) {} diff --git a/src/main/java/hyundai/softeer/orange/eventuser/repository/CustomEventUserRepository.java b/src/main/java/hyundai/softeer/orange/eventuser/repository/CustomEventUserRepository.java new file mode 100644 index 00000000..fbb4eae7 --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/eventuser/repository/CustomEventUserRepository.java @@ -0,0 +1,10 @@ +package hyundai.softeer.orange.eventuser.repository; + +import hyundai.softeer.orange.eventuser.dto.EventUserScoreDto; + +import java.util.Collection; +import java.util.List; + +public interface CustomEventUserRepository { + void updateScoreMany(List dto); +} diff --git a/src/main/java/hyundai/softeer/orange/eventuser/repository/CustomEventUserRepositoryImpl.java b/src/main/java/hyundai/softeer/orange/eventuser/repository/CustomEventUserRepositoryImpl.java new file mode 100644 index 00000000..6172335c --- /dev/null +++ b/src/main/java/hyundai/softeer/orange/eventuser/repository/CustomEventUserRepositoryImpl.java @@ -0,0 +1,36 @@ +package hyundai.softeer.orange.eventuser.repository; + +import hyundai.softeer.orange.eventuser.dto.EventUserScoreDto; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; + +@RequiredArgsConstructor +@Repository +public class CustomEventUserRepositoryImpl implements CustomEventUserRepository { + private final JdbcTemplate jdbcTemplate; + + @Override + public void updateScoreMany(List userScores) { + String sql = "UPDATE event_user SET score = ? WHERE id = ?"; + jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { + + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + EventUserScoreDto userScore = userScores.get(i); + ps.setLong(1, userScore.score()); + ps.setLong(2, userScore.userId()); + } + + @Override + public int getBatchSize() { + return userScores.size(); + } + }); + } +} diff --git a/src/main/java/hyundai/softeer/orange/eventuser/repository/EventUserRepository.java b/src/main/java/hyundai/softeer/orange/eventuser/repository/EventUserRepository.java index 4311ab6a..01bd4038 100644 --- a/src/main/java/hyundai/softeer/orange/eventuser/repository/EventUserRepository.java +++ b/src/main/java/hyundai/softeer/orange/eventuser/repository/EventUserRepository.java @@ -1,5 +1,6 @@ package hyundai.softeer.orange.eventuser.repository; +import hyundai.softeer.orange.eventuser.dto.EventUserScoreDto; import hyundai.softeer.orange.eventuser.entity.EventUser; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -10,14 +11,20 @@ import java.util.Optional; @Repository -public interface EventUserRepository extends JpaRepository { +public interface EventUserRepository extends JpaRepository, CustomEventUserRepository { Optional findByUserNameAndPhoneNumber(String userName, String phoneNumber); Optional findByUserId(String userId); - @Query("select eu from EventUser eu where eu.userId in :userIds") + @Query("SELECT eu FROM EventUser eu WHERE eu.userId IN :userIds") List findAllByUserId(@Param("userIds") List userIds); boolean existsByPhoneNumber(String phoneNumber); + + @Query(value = "SELECT u.id as userId, u.score as score FROM event_user u " + + "JOIN event_frame ef ON ef.id = u.event_frame_id " + + "JOIN event_metadata e ON e.event_frame_id = ef.id " + + "WHERE e.id = :rawEventId", nativeQuery = true) + List findAllUserScoreByDrawEventId(@Param("rawEventId") long rawEventId); }