diff --git a/src/main/java/com/fc/mini3server/repository/WorkRepository.java b/src/main/java/com/fc/mini3server/repository/WorkRepository.java index 21cfffa..980e517 100644 --- a/src/main/java/com/fc/mini3server/repository/WorkRepository.java +++ b/src/main/java/com/fc/mini3server/repository/WorkRepository.java @@ -16,4 +16,5 @@ public interface WorkRepository extends JpaRepository, WorkRepositor Page findByUserAndStartTimeBetween(User user, LocalDateTime startOfweek, LocalDateTime endOfweek, Pageable pageable); Page findByUserOrderByStartTimeDesc(User user, Pageable pageable); Optional findTopByUserIdOrderByStartTimeDesc(Long userId); + List findByUserId(Long userId); } diff --git a/src/main/java/com/fc/mini3server/service/AdminService.java b/src/main/java/com/fc/mini3server/service/AdminService.java index 0e9291a..96b978d 100644 --- a/src/main/java/com/fc/mini3server/service/AdminService.java +++ b/src/main/java/com/fc/mini3server/service/AdminService.java @@ -38,7 +38,7 @@ public class AdminService { private static final LocalDate startOfMonth = today.withDayOfMonth(1); private static final LocalDate endOfMonth = today.withDayOfMonth(today.lengthOfMonth()); private static final LocalDate startOfLastMonth = today.minusMonths(1).withDayOfMonth(1); - private static final LocalDate endOfLastMonth = today.minusMonths(1).withDayOfMonth(today.lengthOfMonth()); + private static final LocalDate endOfLastMonth = today.withDayOfMonth(1).minusMonths(1).withDayOfMonth(today.minusMonths(1).lengthOfMonth()); private final UserService userService; private final UserRepository userRepository; private final ScheduleRepository scheduleRepository; diff --git a/src/main/java/com/fc/mini3server/service/ScheduleService.java b/src/main/java/com/fc/mini3server/service/ScheduleService.java index cebceb4..bdf3cac 100644 --- a/src/main/java/com/fc/mini3server/service/ScheduleService.java +++ b/src/main/java/com/fc/mini3server/service/ScheduleService.java @@ -2,7 +2,6 @@ import com.fc.mini3server._core.handler.Message; import com.fc.mini3server._core.handler.exception.Exception400; -import com.fc.mini3server._core.handler.exception.Exception500; import com.fc.mini3server.domain.*; import com.fc.mini3server._core.handler.exception.Exception404; import com.fc.mini3server.domain.CategoryEnum; @@ -232,36 +231,35 @@ public void startWorkUsingRedis(Long userId) { String lockKey = "lock:" + userId; String lockVal = UUID.randomUUID().toString(); + Boolean acquired = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, 10, TimeUnit.SECONDS); + log.info("lockKey : " + lockKey + ", " + "lockVal : " + lockVal); + log.info("acquired : " + acquired); + + if (! Boolean.TRUE.equals(acquired)) { + log.info("락 획득 실패, 다른 스레드가 우선순위를 가짐"); + return; + } + try { - Boolean acquired = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, 10, TimeUnit.SECONDS); - log.info("lockKey : " + lockKey + ", " + "lockVal : " + lockVal); - if (Boolean.TRUE.equals(acquired)) { - try { - User user = userRepository.findById(userId).orElseThrow(() -> new Exception400("유저를 찾지 못했습니다. 올바른 유저가 요청했는지 확인하십시오.")); - - Optional latestWork = workRepository.findTopByUserIdOrderByStartTimeDesc(userId); - if (latestWork.isPresent()) { - Work lastWork = latestWork.get(); - if (lastWork.getStartTime().toLocalDate().isEqual(LocalDate.now())) { - throw new Exception400("이미 출근했습니다."); - } - } - - Work work = new Work(); - work.setUser(user); - work.setStartTime(LocalDateTime.now()); - workRepository.save(work); - log.info("INSERT to DB safely"); - } finally { - String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; - redisTemplate.execute(new DefaultRedisScript(script, Long.class), Collections.singletonList(lockKey), lockVal); + User user = userRepository.findById(userId).orElseThrow(() -> new Exception400("유저를 찾지 못했습니다. 올바른 유저가 요청했는지 확인하십시오.")); + + Optional latestWork = workRepository.findTopByUserIdOrderByStartTimeDesc(userId); + if (latestWork.isPresent()) { + Work lastWork = latestWork.get(); + if (lastWork.getStartTime().toLocalDate().isEqual(LocalDate.now())) { + throw new Exception400("이미 출근했습니다."); } - } else { - throw new Exception500("REDIS LOCK 획득 실패"); } - } catch (Exception e) { - e.printStackTrace(); - throw new Exception500("UNKNOWN ERROR"); + + Work work = new Work(); + work.setUser(user); + work.setStartTime(LocalDateTime.now()); + workRepository.save(work); + log.info("INSERT to DB safely"); + + } finally { + String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; + redisTemplate.execute(new DefaultRedisScript(script, Long.class), Collections.singletonList(lockKey), lockVal); } } diff --git a/src/test/java/com/fc/mini3server/service/ScheduleServiceTest.java b/src/test/java/com/fc/mini3server/service/ScheduleServiceTest.java new file mode 100644 index 0000000..6f82f88 --- /dev/null +++ b/src/test/java/com/fc/mini3server/service/ScheduleServiceTest.java @@ -0,0 +1,71 @@ +package com.fc.mini3server.service; + +import com.fc.mini3server.domain.Work; +import com.fc.mini3server.repository.WorkRepository; +import com.querydsl.jpa.impl.JPAQueryFactory; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.EntityManager; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.assertj.core.api.Assertions.assertThat; + + +@SpringBootTest +@Transactional +@DisplayName("스케줄 서비스 테스트") +public class ScheduleServiceTest { + + @Autowired + EntityManager em; + @Autowired + JPAQueryFactory queryFactory; + @Autowired + ScheduleService scheduleService; + @Autowired + AdminService adminService; + @Autowired + WorkRepository workRepository; + + @Test + @DisplayName("레디스 분산 락을 구현 후 동시성 이슈 테스트") + public void testStartWorkUsingRedisWithLock() throws InterruptedException { + Long userId = 142L; + int threadCnt = 10; + ExecutorService service = Executors.newFixedThreadPool(threadCnt); + CountDownLatch latch = new CountDownLatch(threadCnt); + + for (int i=0; i { + try { + scheduleService.startWorkUsingRedis(userId); + } finally { + latch.countDown(); + } + }); + } + System.out.println("ON EXECUTE DONE"); + + latch.await(); + service.shutdown(); + + List works = workRepository.findByUserId(userId); + if (works == null) { + System.out.println("NULL"); + + } + int expectedCnt = 1; + + assertThat(works.size()).isEqualTo(expectedCnt); + } + + + +}