Skip to content

Commit 0b646c2

Browse files
authored
Merge pull request #60 from ConconDev/feat/bootpay-authenticaation
feat: reviewCreate에 SpringBatch 작업 추가 + redis
2 parents 1b80b54 + abb5f8a commit 0b646c2

File tree

9 files changed

+247
-1
lines changed

9 files changed

+247
-1
lines changed

build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ repositories {
2424
}
2525

2626
dependencies {
27+
// 스프링 배치
28+
implementation 'org.springframework.boot:spring-boot-starter-batch'
29+
2730
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
2831
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
2932
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
@@ -36,7 +39,7 @@ dependencies {
3639
runtimeOnly 'com.mysql:mysql-connector-j'
3740
annotationProcessor 'org.projectlombok:lombok'
3841
testImplementation 'org.springframework.boot:spring-boot-starter-test'
39-
testImplementation 'org.springframework.security:spring-security-test'
42+
testImplementation 'org.springframework.security:spring-security-test®'
4043
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
4144

4245

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.sookmyung.concon.Review.configuration;
2+
3+
import com.sookmyung.concon.Review.configuration.chunk.ReviewItemProcessor;
4+
import com.sookmyung.concon.Review.configuration.chunk.ReviewItemReader;
5+
import com.sookmyung.concon.Review.configuration.chunk.ReviewItemWriter;
6+
import com.sookmyung.concon.Review.dto.ReviewRedisDto;
7+
import com.sookmyung.concon.Review.entity.Review;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.batch.core.Job;
11+
import org.springframework.batch.core.Step;
12+
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
13+
import org.springframework.batch.core.job.builder.JobBuilder;
14+
import org.springframework.batch.core.repository.JobRepository;
15+
import org.springframework.batch.core.step.builder.StepBuilder;
16+
import org.springframework.context.annotation.Bean;
17+
import org.springframework.context.annotation.Configuration;
18+
import org.springframework.transaction.PlatformTransactionManager;
19+
20+
@Configuration
21+
@EnableBatchProcessing
22+
@Slf4j
23+
@RequiredArgsConstructor
24+
public class ReviewBatchConfig {
25+
private final JobRepository jobRepository;
26+
private final PlatformTransactionManager transactionManager;
27+
28+
@Bean
29+
public Job reviewCreateJob(Step reviewCreateStep) {
30+
return new JobBuilder("reviewCreateJob", jobRepository)
31+
.start(reviewCreateStep)
32+
.build();
33+
}
34+
35+
@Bean
36+
public Step reviewCreateStep(ReviewItemReader reader, ReviewItemProcessor processor,
37+
ReviewItemWriter writer) {
38+
return new StepBuilder("reviewCreateStep", jobRepository)
39+
.<ReviewRedisDto, Review>chunk(10, transactionManager)
40+
.reader(reader)
41+
.processor(processor)
42+
.writer(writer)
43+
.build();
44+
}
45+
46+
47+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.sookmyung.concon.Review.configuration.chunk;
2+
3+
import com.sookmyung.concon.Coupon.Entity.Coupon;
4+
import com.sookmyung.concon.Coupon.service.CouponFacade;
5+
import com.sookmyung.concon.Review.dto.ReviewCreateRequestDto;
6+
import com.sookmyung.concon.Review.dto.ReviewRedisDto;
7+
import com.sookmyung.concon.Review.entity.Review;
8+
import com.sookmyung.concon.User.Entity.User;
9+
import com.sookmyung.concon.User.service.UserFacade;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.batch.item.ItemProcessor;
12+
import org.springframework.stereotype.Component;
13+
14+
@RequiredArgsConstructor
15+
@Component
16+
public class ReviewItemProcessor implements ItemProcessor<ReviewRedisDto, Review> {
17+
private final UserFacade userFacade;
18+
private final CouponFacade couponFacade;
19+
20+
@Override
21+
public Review process(ReviewRedisDto dto) throws Exception {
22+
23+
User user = userFacade.findUserByToken(dto.getToken());
24+
ReviewCreateRequestDto request = dto.getRequest();
25+
Coupon coupon = couponFacade.findByCouponId(request.getCouponId());
26+
return request.toEntity(user, coupon);
27+
}
28+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.sookmyung.concon.Review.configuration.chunk;
2+
3+
import com.sookmyung.concon.Review.dto.ReviewCreateRequestDto;
4+
import com.sookmyung.concon.Review.dto.ReviewRedisDto;
5+
import org.springframework.batch.item.ItemReader;
6+
import org.springframework.batch.item.NonTransientResourceException;
7+
import org.springframework.batch.item.ParseException;
8+
import org.springframework.batch.item.UnexpectedInputException;
9+
import org.springframework.stereotype.Component;
10+
11+
import java.util.List;
12+
13+
@Component
14+
public class ReviewItemReader implements ItemReader<ReviewRedisDto> {
15+
private List<ReviewRedisDto> reviewRedisDtoList;
16+
private int nextIndex;
17+
18+
public void setReviewRedisDtoList(List<ReviewRedisDto> reviewRedisDtoList) {
19+
this.reviewRedisDtoList = reviewRedisDtoList;
20+
this.nextIndex = 0;
21+
}
22+
23+
@Override
24+
public ReviewRedisDto read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
25+
if (nextIndex < reviewRedisDtoList.size()) {
26+
return reviewRedisDtoList.get(nextIndex++);
27+
}
28+
return null;
29+
}
30+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.sookmyung.concon.Review.configuration.chunk;
2+
3+
import com.sookmyung.concon.Review.entity.Review;
4+
import com.sookmyung.concon.Review.repository.ReviewRepository;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.batch.item.Chunk;
7+
import org.springframework.batch.item.ItemWriter;
8+
import org.springframework.stereotype.Component;
9+
10+
@Component
11+
@RequiredArgsConstructor
12+
public class ReviewItemWriter implements ItemWriter<Review> {
13+
private final ReviewRepository reviewRepository;
14+
@Override
15+
public void write(Chunk<? extends Review> reviews) throws Exception {
16+
reviewRepository.saveAll(reviews);
17+
}
18+
}

src/main/java/com/sookmyung/concon/Review/controller/ReviewController.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
package com.sookmyung.concon.Review.controller;
22

3+
import com.sookmyung.concon.Review.configuration.chunk.ReviewItemReader;
34
import com.sookmyung.concon.Review.dto.ReviewCreateRequestDto;
5+
import com.sookmyung.concon.Review.dto.ReviewRedisDto;
46
import com.sookmyung.concon.Review.dto.ReviewUpdateRequestDto;
57
import com.sookmyung.concon.Review.entity.Review;
8+
import com.sookmyung.concon.Review.repository.ReviewRedisRepository;
9+
import com.sookmyung.concon.Review.service.ReviewBatchService;
610
import com.sookmyung.concon.Review.service.ReviewService;
711
import com.sookmyung.concon.User.Jwt.JwtUtil;
812
import io.swagger.v3.oas.annotations.Operation;
913
import io.swagger.v3.oas.annotations.tags.Tag;
1014
import lombok.RequiredArgsConstructor;
15+
import org.springframework.batch.core.Job;
16+
import org.springframework.batch.core.launch.JobLauncher;
1117
import org.springframework.beans.factory.annotation.Autowired;
1218
import org.springframework.http.ResponseEntity;
1319
import org.springframework.security.core.Authentication;
@@ -21,7 +27,26 @@
2127
@RequestMapping("/api/reviews")
2228
@RequiredArgsConstructor
2329
public class ReviewController {
30+
private final JobLauncher jobLauncher;
31+
private final Job reviewCreateJob;
32+
private final ReviewItemReader itemReader;
33+
private final ReviewRedisRepository reviewRedisRepository;
2434
private final ReviewService reviewService;
35+
private final ReviewBatchService reviewBatchService;
36+
37+
@Operation(summary = "리뷰 10개 생성 시 한꺼번에 저장 batch")
38+
@PostMapping("/reviews/batch")
39+
public ResponseEntity<?> createReviewBatch(
40+
@RequestBody ReviewCreateRequestDto request,
41+
@RequestHeader("Authorization") String token) {
42+
43+
try {
44+
reviewBatchService.createReview(request, token);
45+
return ResponseEntity.ok().build();
46+
} catch (Exception e) {
47+
return ResponseEntity.badRequest().body(e.getMessage());
48+
}
49+
}
2550

2651
@Operation(summary = "후기 작성")
2752
@PostMapping
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.sookmyung.concon.Review.dto;
2+
3+
import lombok.Builder;
4+
import lombok.Getter;
5+
6+
@Builder
7+
@Getter
8+
public class ReviewRedisDto {
9+
private ReviewCreateRequestDto request;
10+
private String token;
11+
12+
public static ReviewRedisDto toDto(ReviewCreateRequestDto request, String token) {
13+
return ReviewRedisDto.builder()
14+
.request(request)
15+
.token(token)
16+
.build();
17+
}
18+
19+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.sookmyung.concon.Review.repository;
2+
3+
import com.sookmyung.concon.Review.dto.ReviewRedisDto;
4+
import org.springframework.data.redis.core.RedisTemplate;
5+
import org.springframework.stereotype.Repository;
6+
7+
import java.util.List;
8+
import java.util.UUID;
9+
10+
@Repository
11+
public class ReviewRedisRepository {
12+
private RedisTemplate<String, Object> redisTemplate;
13+
private static final String REVIEW_KEY = "reviews";
14+
15+
public void save(ReviewRedisDto dto) {
16+
String reviewId = UUID.randomUUID().toString();
17+
redisTemplate.opsForHash().put(REVIEW_KEY, reviewId, dto);
18+
}
19+
20+
public int size() {
21+
return redisTemplate.opsForHash().size(REVIEW_KEY).intValue();
22+
}
23+
24+
public List<ReviewRedisDto> getAndClearReviewsDtoList() {
25+
List<ReviewRedisDto> reviews = redisTemplate.opsForHash().values(REVIEW_KEY)
26+
.stream()
27+
.map(obj -> (ReviewRedisDto) obj)
28+
.toList();
29+
30+
redisTemplate.delete(REVIEW_KEY);
31+
32+
return reviews;
33+
}
34+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.sookmyung.concon.Review.service;
2+
3+
import com.sookmyung.concon.Review.configuration.chunk.ReviewItemReader;
4+
import com.sookmyung.concon.Review.dto.ReviewCreateRequestDto;
5+
import com.sookmyung.concon.Review.dto.ReviewRedisDto;
6+
import com.sookmyung.concon.Review.repository.ReviewRedisRepository;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.batch.core.Job;
9+
import org.springframework.batch.core.JobParameters;
10+
import org.springframework.batch.core.JobParametersBuilder;
11+
import org.springframework.batch.core.launch.JobLauncher;
12+
import org.springframework.stereotype.Service;
13+
14+
import java.util.List;
15+
16+
@Service
17+
@RequiredArgsConstructor
18+
public class ReviewBatchService {
19+
private final ReviewRedisRepository reviewRedisRepository;
20+
private final ReviewItemReader reviewItemReader;
21+
private final JobLauncher jobLauncher;
22+
private final Job reviewCreateJob;
23+
24+
public void createReview(ReviewCreateRequestDto request, String token) throws Exception{
25+
ReviewRedisDto dto = ReviewRedisDto.toDto(request, token);
26+
reviewRedisRepository.save(dto);
27+
28+
if (reviewRedisRepository.size() >= 10) {
29+
runBatchJob();
30+
}
31+
}
32+
private void runBatchJob() throws Exception {
33+
List<ReviewRedisDto> reviewRedisDtoList = reviewRedisRepository.getAndClearReviewsDtoList();
34+
reviewItemReader.setReviewRedisDtoList(reviewRedisDtoList);
35+
36+
JobParameters jobParameters = new JobParametersBuilder()
37+
.addLong("time", System.currentTimeMillis())
38+
.toJobParameters();
39+
40+
jobLauncher.run(reviewCreateJob, jobParameters);
41+
}
42+
}

0 commit comments

Comments
 (0)