포인트 테이블에는 endDt 라는 만료일이 지정되어 있다.
스케줄러를 사용하여 만료일이 지나면 포인트를 만료 시키는 기능을 구현하려고 한다.
우선 point_history 테이블에는 사용자별 만료되어야 할 총 포인트가 들어가야하며 point_history_detail에는 accumulateId와 userId를 가지고 amount의 합이 0보다 큰 리스트가 쌓여야 한다.
나는 사용자별 만료 총 포인트를 구하기 위하여 서브쿼리를 사용하였다.
ExpiredPoints
@Entity
@Subselect(
"select " +
" a.user_id " +
" , sum(a.amount) as expired_point " +
" from point_history_detail a " +
" group by a.point_accumulate_id " +
" , a.end_dt " +
"having sum(a.amount) > 0 " +
" and a.end_dt < current_timestamp "
)
@Immutable
@Synchronize("PointHistoryDetail")
@Getter
public class ExpiredPoints {
@Id
private Long userId;
private Long expiredPoint;
private Long amount;
private LocalDateTime endDt;
private Long pointAccumulateId;
}
PointHistoryDetailRepoSupport
public List<GetTotalExpiredPointDto> getTotalExpiredPoint() {
List<GetTotalExpiredPointDto> pointList = jpaQueryFactory
.select(Projections.bean(
GetTotalExpiredPointDto.class,
EXPIRED_POINTS.userId,
EXPIRED_POINTS.expiredPoint.sum().as("totalExpiredPoint")
))
.from(EXPIRED_POINTS)
.groupBy(EXPIRED_POINTS.userId)
.fetch();
return pointList;
}
public List<GetExpiredPointDto> getExpiredPoint(Long userId) {
List<GetExpiredPointDto> pointList = jpaQueryFactory
.select(Projections.bean(
GetExpiredPointDto.class,
POINT_HISTORY_DETAIL.pointAccumulateId,
POINT_HISTORY_DETAIL.userId,
POINT_HISTORY_DETAIL.endDt,
POINT_HISTORY_DETAIL.amount.sum().as("totalExpiredPoint")
))
.from(POINT_HISTORY_DETAIL)
.where(POINT_HISTORY_DETAIL.userId.eq(userId))
.groupBy(
POINT_HISTORY_DETAIL.pointAccumulateId,
POINT_HISTORY_DETAIL.endDt
)
.having(POINT_HISTORY_DETAIL.amount.sum().gt(0))
.having(POINT_HISTORY_DETAIL.endDt.lt(LocalDateTime.now()))
.fetch();
return pointList;
}
accumulateId와 endDt를 기준으로 amount의 합이 0보다 큰 리스트를 추출한다.
PointHistoryDetailService
public List<GetExpiredPointDto> getExpiredPointList(Long userId) {
return pointHistoryDetailRepoSupport.getExpiredPoint(userId);
}
public List<GetTotalExpiredPointDto> getTotalExpiredPointList() {
return pointHistoryDetailRepoSupport.getTotalExpiredPoint();
}
public void deadlinePoint() {
List<GetTotalExpiredPointDto> totalExpiredPoint = getTotalExpiredPointList();
totalExpiredPoint.forEach(
p -> {
PointHistory insertPointHistory = PointHistory.init(p);
pointHistoryRepo.save(insertPointHistory);
List<GetExpiredPointDto> expiredPoint = getExpiredPointList(p.getUserId());
expiredPoint.forEach(e -> {
PointHistoryDetail insertPointHistoryDetail = PointHistoryDetail.init(e,
insertPointHistory.getPointHistoryId());
pointHistoryDetailRepo.save(insertPointHistoryDetail);
});
}
);
}
두 개의 리스트를 추출한 후 PointHistoryDetail에는 historyId와 userId별로 데이터가 쌓여야하기 때문에 forEach문을 두 번 실행한다.
사용자별 만료 되어야 할 총 포인트 리스트.forEach(
History 테이블 insert;
적립 아이디별 만료되어야 할 포인트 리스트 조회 (파라미터: userId);
적립 아이디별 만료되어야 할 포인트 리스트.forEach(
History Detail 테이블 insert;
)
)
PointHistory
public static PointHistory init(GetTotalExpiredPointDto totalExpiredPoint) {
return PointHistory.builder().userId(totalExpiredPoint.getUserId())
.amount(totalExpiredPoint.getTotalExpiredPoint() * -1)
.pointHistoryType(null)
.pointStat(PointStat.EXPIRATION)
.endDt(null)
.remarks("만료소멸")
.build();
}
PointHistoryDetail
public static PointHistoryDetail init(GetExpiredPointDto expiredPoint, Long historyId) {
return PointHistoryDetail.builder().userId(expiredPoint.getUserId())
.amount(expiredPoint.getTotalExpiredPoint() * -1)
.pointAccumulateId(expiredPoint.getPointAccumulateId())
.endDt(expiredPoint.getEndDt())
.pointHistoryId(historyId)
.build();
}
여기까지 구현하였으면 이제 매일 밤 12시에 돌아가도록 설정한다.
PointHistorySchedulerService
@Slf4j
@Component
@RequiredArgsConstructor
public class PointHistorySchedulerService {
private final PointHistoryDetailService pointHistoryDetailService;
@Scheduled(cron = "0 0 24 * * *")
public void deadlineTicket() {
try {
pointHistoryDetailService.deadlinePoint();
} catch (RuntimeException exception) {
}
}
}
구현을 마치고 테스트 코드를 짜서 제대로 값이 들어가는지 확인했다.
PointHistoryDetailServiceTest
@SpringBootTest
@Transactional
@ExtendWith(SpringExtension.class)
public class PointHistoryDetailServiceTest {
@Autowired
private PointHistoryDetailService pointHistoryDetailService;
@Test
void getTotalExpiredPoints() {
List<GetTotalExpiredPointDto> list = pointHistoryDetailService.getTotalExpiredPointList();
list.stream().forEach(System.out::println);
list.stream().forEach(
p -> {
List<GetExpiredPointDto> pointList = pointHistoryDetailService.getExpiredPointList(p.getUserId());
pointList.stream().forEach(System.out::println);
}
);
}
@Test
@Rollback(value = false)
void deadlinePoints() {
pointHistoryDetailService.deadlinePoint();
}
}
point_history (DB table)
point_history_detail (DB table)
각각 가장 마지막 줄을 보면 오늘 날짜에 맞춰서 데이터가 잘 들어간 것을 확인해 볼 수가 있다.
다른 기능 구현보다 포인트 기능 구현하는 것이 가장 헷갈렸던 것 같다.
스케줄러도 처음 사용해 봤고 아직 공부해야 할 게 많다고 느낀다.
'개발' 카테고리의 다른 글
[에러] swagger로 테스트 시 LocalDateTime typeMisMatch (0) | 2024.06.26 |
---|---|
[개발] 내 포인트 내역 조회 (0) | 2024.06.04 |
[개발] 포인트 차감 (0) | 2024.05.30 |
[개발] Maven 의존성 추가 (0) | 2024.05.28 |
[개발] 총 포인트 조회 (0) | 2024.04.17 |