Skip to content

Commit

Permalink
[#108] refactor: 포스트 조회수 통계 배치 작업 스프링 배치로 마이그레이션
Browse files Browse the repository at this point in the history
  • Loading branch information
shin-mallang committed Jan 21, 2024
1 parent 74b4fe7 commit ca019a9
Show file tree
Hide file tree
Showing 12 changed files with 257 additions and 218 deletions.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.mallang.statistics.batch.job;

import java.time.LocalDate;

public record PostViewHistoryDto(
Long postId,
Long blogId,
LocalDate date,
int viewCount
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.mallang.statistics.batch.job;

import com.mallang.post.domain.PostId;
import com.mallang.statistics.statistic.PostViewStatistic;
import com.mallang.statistics.statistic.PostViewStatisticRepository;
import com.mallang.statistics.statistic.source.PostViewHistoryRepository;
import jakarta.persistence.EntityManagerFactory;
import java.time.LocalDateTime;
import javax.sql.DataSource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobScope;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.database.JdbcCursorItemReader;
import org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder;
import org.springframework.batch.item.database.builder.JpaItemWriterBuilder;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
import org.springframework.jdbc.core.DataClassRowMapper;
import org.springframework.transaction.PlatformTransactionManager;

@Slf4j
@RequiredArgsConstructor
@Configuration
public class PostViewStatisticJobConfig {

private static final int CHUNK_SIZE = 500;

private final EntityManagerFactory entityManagerFactory;
private final DataSource dataSource;
private final JobRepository jobRepository;
private final PlatformTransactionManager txManager;
private final PostViewStatisticRepository postViewStatisticRepository;
private final PostViewHistoryRepository postViewHistoryRepository;
private final StartAndEndTimeValidator startAndEndTimeValidator;

@Bean
public Job postViewStatisticJob() {
return new JobBuilder("postViewStatisticJob", jobRepository)
.start(postViewStatisticStep())
.next(postViewHistoryDeleteStep(null, null))
.validator(startAndEndTimeValidator)
.build();
}

@Bean
public Step postViewStatisticStep() {
return new StepBuilder("postViewStatisticStep", jobRepository)
.<PostViewHistoryDto, PostViewStatistic>chunk(CHUNK_SIZE, txManager)
.reader(postViewHistoryDtoReader(null, null))
.processor(postViewHistoryToStatisticProcessor())
.writer(postViewStatisticWriter())
.build();
}

@Bean
@JobScope
public Step postViewHistoryDeleteStep(
@Value("#{jobParameters[startInclude]}") LocalDateTime startInclude,
@Value("#{jobParameters[endExclude]}") LocalDateTime endExclude
) {
return new StepBuilder("postViewHistoryDeleteStep", jobRepository)
.tasklet((contribution, chunkContext) -> {
postViewHistoryRepository.deleteWithCreatedDateBetweenIncludeStartAndExcludeEnd(
startInclude, endExclude
);
return RepeatStatus.FINISHED;
}, txManager)
.build();
}

@Bean
@StepScope
public JdbcCursorItemReader<PostViewHistoryDto> postViewHistoryDtoReader(
@Value("#{jobParameters[startInclude]}") LocalDateTime startInclude,
@Value("#{jobParameters[endExclude]}") LocalDateTime endExclude
) {
return new JdbcCursorItemReaderBuilder<PostViewHistoryDto>()
.fetchSize(CHUNK_SIZE)
.dataSource(dataSource)
.rowMapper(new DataClassRowMapper<>(PostViewHistoryDto.class))
.sql("""
SELECT pvh.post_id, pvh.blog_id, pvh.date, COUNT(*) as view_count
FROM post_view_history pvh
WHERE pvh.created_date >= ? AND pvh.created_date < ?
GROUP BY pvh.post_id, pvh.blog_id, pvh.date
""")
.name("postViewHistoryDtoReader")
.preparedStatementSetter(new ArgumentPreparedStatementSetter(new Object[]{startInclude, endExclude}))
.build();
}

@Bean
public ItemProcessor<PostViewHistoryDto, PostViewStatistic> postViewHistoryToStatisticProcessor() {
return historyDto -> {
PostId postId = new PostId(historyDto.postId(), historyDto.blogId());
PostViewStatistic postViewStatistic = postViewStatisticRepository
.findByPostIdAndStatisticDate(postId, historyDto.date())
.orElseGet(() -> postViewStatisticRepository.save(
new PostViewStatistic(historyDto.date(), postId)
));
postViewStatistic.addCount(historyDto.viewCount());
return postViewStatistic;
};
}

@Bean
public ItemWriter<PostViewStatistic> postViewStatisticWriter() {
return new JpaItemWriterBuilder<PostViewStatistic>()
.entityManagerFactory(entityManagerFactory)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.mallang.statistics.batch.job;

import static com.mallang.common.utils.LocalDateTimeUtils.onlyHours;

import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Slf4j
@RequiredArgsConstructor
@Component
public class PostViewStatisticJobScheduler {

public static final String EACH_HOUR_CRON = "0 0 * * * *";

private final JobLauncher jobLauncher;
private final Job postViewStatisticJob;

@Scheduled(cron = EACH_HOUR_CRON)
public void runTask() throws Exception {
LocalDateTime now = LocalDateTime.now();
log.info("포스트 조회수 통계 작업 실행 [실행시간: {}]", now);
LocalDateTime startInclude = onlyHours(now.minusHours(2));
LocalDateTime endExclude = onlyHours(now);
JobParameters jobParameters = new JobParametersBuilder()
.addLocalDateTime("startInclude", startInclude)
.addLocalDateTime("endExclude", endExclude)
.toJobParameters();
jobLauncher.run(postViewStatisticJob, jobParameters);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.mallang.post.domain.PostId;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.AccessLevel;
Expand All @@ -20,9 +21,12 @@ public class PostViewHistory extends CommonHistory {
@Column(nullable = false)
private PostId postId;

private LocalDate date;

public PostViewHistory(UUID uuid, PostId postId, LocalDateTime createdDate) {
super(createdDate);
this.uuid = uuid;
this.postId = postId;
this.date = createdDate.toLocalDate();
}
}
Loading

0 comments on commit ca019a9

Please sign in to comment.