From 9ac3f4eaf2a1e14adbdace16d5fe0c1ad9fbc2ef Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Wed, 3 Sep 2025 11:17:20 +0900 Subject: [PATCH 01/10] =?UTF-8?q?chore:=20spring=20batch=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=84=A4=EC=B9=98=20(#365)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 2215cf9e..db04ad14 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-batch' // 채팅 웹 소켓 implementation 'org.springframework.boot:spring-boot-starter-websocket' implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' @@ -41,7 +42,7 @@ dependencies { //Flyway implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-mysql' - + implementation 'com.fasterxml.jackson.core:jackson-databind' testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1' compileOnly 'org.projectlombok:lombok' @@ -69,4 +70,4 @@ tasks.register('copySecret', Copy) { from './be-submodule' // 서브 모듈 디렉토리 경로 include "*.properties" // 설정 파일 복사 into 'src/main/resources' // 붙여넣을 위치 -} \ No newline at end of file +} From 8c9b3cc844260802581b0ebb2b75026bda3856e0 Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Thu, 4 Sep 2025 13:14:24 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20=ED=95=99=EC=83=9D=20=EB=8C=80?= =?UTF-8?q?=EC=9A=A9=EB=9F=89=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=82=BD?= =?UTF-8?q?=EC=9E=85=20(#365)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../job/student/StudentInsertJobConfig.java | 70 +++++++++++++++++++ .../job/student/StudentItemReader.java | 50 +++++++++++++ .../buddyya/job/student/StudentJobDTO.java | 20 ++++++ 3 files changed, 140 insertions(+) create mode 100644 src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java create mode 100644 src/main/java/com/team/buddyya/job/student/StudentItemReader.java create mode 100644 src/main/java/com/team/buddyya/job/student/StudentJobDTO.java diff --git a/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java b/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java new file mode 100644 index 00000000..880503ba --- /dev/null +++ b/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java @@ -0,0 +1,70 @@ +package com.team.buddyya.job.student; + +import com.team.buddyya.student.repository.UniversityRepository; +import javax.sql.DataSource; +import lombok.RequiredArgsConstructor; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +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.ItemReader; +import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider; +import org.springframework.batch.item.database.JdbcBatchItemWriter; +import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.PlatformTransactionManager; + +@Configuration +@RequiredArgsConstructor +public class StudentInsertJobConfig { + + private static final int CHUNK_SIZE = 1000; + private final JobRepository jobRepository; + private final PlatformTransactionManager platformTransactionManager; + private final DataSource dataSource; + private final UniversityRepository universityRepository; + + @Bean + public Job studentInsertJob(Step studentInsertStep) { + return new JobBuilder("studentInsertJob", jobRepository) + .start(studentInsertStep) + .build(); + } + + @Bean + public Step studentInsertStep( + ItemReader studentItemReader, + JdbcBatchItemWriter studentItemWriter + ) { + return new StepBuilder("studentInsertStep", jobRepository) + .chunk(CHUNK_SIZE, platformTransactionManager) + .reader(studentItemReader) + .writer(studentItemWriter) + .build(); + } + + @Bean + @StepScope + public ItemReader studentItemReader( + @Value("#{jobParameters['count'] ?: 10000}") int totalCount + ) { + return new StudentItemReader(universityRepository, totalCount); + } + + @Bean + public JdbcBatchItemWriter studentItemWriter() { + String sql = + "INSERT INTO student (phone_number, name, country, certificated, korean, deleted, university_id, role, gender, character_profile_image, banned, created_date, updated_date) " + + + "VALUES (:phoneNumber, :name, :country, :isCertificated, :isKorean, :isDeleted, :universityId, :role, :gender, :characterProfileImage, :isBanned, NOW(), NOW())"; + return new JdbcBatchItemWriterBuilder() + .dataSource(dataSource) + .sql(sql) + .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) + .build(); + } +} diff --git a/src/main/java/com/team/buddyya/job/student/StudentItemReader.java b/src/main/java/com/team/buddyya/job/student/StudentItemReader.java new file mode 100644 index 00000000..0155c827 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/student/StudentItemReader.java @@ -0,0 +1,50 @@ +package com.team.buddyya.job.student; + +import com.team.buddyya.student.domain.University; +import com.team.buddyya.student.repository.UniversityRepository; +import java.util.List; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.item.ItemReader; + +@Slf4j +public class StudentItemReader implements ItemReader { + + private final UniversityRepository universityRepository; + private final int totalCount; + private final AtomicInteger counter = new AtomicInteger(0); + private final Random random = new Random(); + private List universities; + + public StudentItemReader(UniversityRepository universityRepository, int totalCount) { + this.universityRepository = universityRepository; + this.totalCount = totalCount; + } + + @Override + public StudentJobDTO read() { + if (this.universities == null) { + this.universities = universityRepository.findAll(); + } + int currentCount = counter.getAndIncrement(); + if (currentCount >= totalCount) { + return null; + } + int index = currentCount + 1; + University randomUniversity = universities.get(random.nextInt(universities.size())); + return StudentJobDTO.builder() + .phoneNumber("010" + String.format("%08d", index)) + .name("student" + index) + .country("ko") + .isCertificated(false) + .isKorean(true) + .isDeleted(false) + .universityId(randomUniversity.getId()) + .role("STUDENT") + .gender(index % 2 == 0 ? "MALE" : "FEMALE") + .characterProfileImage("default_image_url_" + (index % 8)) + .isBanned(false) + .build(); + } +} diff --git a/src/main/java/com/team/buddyya/job/student/StudentJobDTO.java b/src/main/java/com/team/buddyya/job/student/StudentJobDTO.java new file mode 100644 index 00000000..19322e51 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/student/StudentJobDTO.java @@ -0,0 +1,20 @@ +package com.team.buddyya.job.student; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class StudentJobDTO { + private final String phoneNumber; + private final String name; + private final String country; + private final Boolean isCertificated; + private final Boolean isKorean; + private final Boolean isDeleted; + private final Long universityId; + private final String role; + private final String gender; + private final String characterProfileImage; + private final Boolean isBanned; +} From 593ea2802ef9b9ef3f3594d763804fbb91f1f803 Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Thu, 4 Sep 2025 13:23:48 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20local=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=97=90=EC=84=9C=EB=A7=8C=20=EB=B0=B0?= =?UTF-8?q?=EC=B9=98=EA=B0=80=20=EB=8F=99=EC=9E=91=ED=95=9C=EB=8B=A4=20(#3?= =?UTF-8?q?65)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/team/buddyya/job/student/StudentInsertJobConfig.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java b/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java index 880503ba..1897ded8 100644 --- a/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java +++ b/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java @@ -16,8 +16,10 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.transaction.PlatformTransactionManager; +@Profile("local") @Configuration @RequiredArgsConstructor public class StudentInsertJobConfig { From 73108273c649dfe033f0b1b0bf9f15a64483e4ad Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Thu, 4 Sep 2025 14:18:29 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20=ED=94=BC=EB=93=9C=20=EB=8C=80?= =?UTF-8?q?=EC=9A=A9=EB=9F=89=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=82=BD?= =?UTF-8?q?=EC=9E=85=20Job=20(#365)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team/buddyya/job/SeedDataJobConfig.java | 32 +++++++++ .../buddyya/job/feed/FeedInsertJobConfig.java | 66 +++++++++++++++++++ .../team/buddyya/job/feed/FeedItemReader.java | 63 ++++++++++++++++++ .../com/team/buddyya/job/feed/FeedJobDTO.java | 15 +++++ .../job/student/StudentInsertJobConfig.java | 11 ---- 5 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/team/buddyya/job/SeedDataJobConfig.java create mode 100644 src/main/java/com/team/buddyya/job/feed/FeedInsertJobConfig.java create mode 100644 src/main/java/com/team/buddyya/job/feed/FeedItemReader.java create mode 100644 src/main/java/com/team/buddyya/job/feed/FeedJobDTO.java diff --git a/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java b/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java new file mode 100644 index 00000000..296be81e --- /dev/null +++ b/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java @@ -0,0 +1,32 @@ +package com.team.buddyya.job; + +import com.team.buddyya.job.feed.FeedInsertJobConfig; +import com.team.buddyya.job.student.StudentInsertJobConfig; +import lombok.RequiredArgsConstructor; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Profile; + +@Profile("local") +@Import({StudentInsertJobConfig.class, FeedInsertJobConfig.class}) +@Configuration +@RequiredArgsConstructor +public class SeedDataJobConfig { + + private final JobRepository jobRepository; + private final Step studentInsertStep; + private final Step feedInsertStep; + + @Bean + public Job seedDataJob() { + return new JobBuilder("seedDataJob", jobRepository) + .start(studentInsertStep) + .next(feedInsertStep) + .build(); + } +} diff --git a/src/main/java/com/team/buddyya/job/feed/FeedInsertJobConfig.java b/src/main/java/com/team/buddyya/job/feed/FeedInsertJobConfig.java new file mode 100644 index 00000000..557456ac --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/FeedInsertJobConfig.java @@ -0,0 +1,66 @@ +package com.team.buddyya.job.feed; + +import com.team.buddyya.feed.repository.CategoryRepository; +import com.team.buddyya.student.repository.StudentRepository; +import com.team.buddyya.student.repository.UniversityRepository; +import javax.sql.DataSource; +import lombok.RequiredArgsConstructor; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider; +import org.springframework.batch.item.database.JdbcBatchItemWriter; +import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.PlatformTransactionManager; + +@Configuration +@RequiredArgsConstructor +public class FeedInsertJobConfig { + + private final JobRepository jobRepository; + private final PlatformTransactionManager platformTransactionManager; + private final DataSource dataSource; + private final StudentRepository studentRepository; + private final CategoryRepository categoryRepository; + private final UniversityRepository universityRepository; + + private static final int CHUNK_SIZE = 1000; + + @Bean + public Step feedInsertStep( + ItemReader feedItemReader, + JdbcBatchItemWriter feedItemWriter + ) { + return new StepBuilder("feedInsertStep", jobRepository) + .chunk(CHUNK_SIZE, platformTransactionManager) + .reader(feedItemReader) + .writer(feedItemWriter) + .build(); + } + + @Bean + @StepScope + public ItemReader feedItemReader( + @Value("#{jobParameters['count'] ?: 100000}") int totalCount + ) { + return new FeedItemReader(studentRepository, categoryRepository, universityRepository, totalCount); + } + + @Bean + public JdbcBatchItemWriter feedItemWriter() { + String sql = + "INSERT INTO feed (title, content, is_profile_visible, student_id, category_id, university_id, like_count, comment_count, view_count, pinned, created_at, updated_at) " + + + "VALUES (:title, :content, :isProfileVisible, :studentId, :categoryId, :universityId, 0, 0, 0, false, NOW(), NOW())"; + return new JdbcBatchItemWriterBuilder() + .dataSource(dataSource) + .sql(sql) + .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) + .build(); + } +} diff --git a/src/main/java/com/team/buddyya/job/feed/FeedItemReader.java b/src/main/java/com/team/buddyya/job/feed/FeedItemReader.java new file mode 100644 index 00000000..2f0c4b55 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/FeedItemReader.java @@ -0,0 +1,63 @@ +package com.team.buddyya.job.feed; + +import com.team.buddyya.feed.domain.Category; +import com.team.buddyya.feed.repository.CategoryRepository; +import com.team.buddyya.student.domain.Student; +import com.team.buddyya.student.domain.University; +import com.team.buddyya.student.repository.StudentRepository; +import com.team.buddyya.student.repository.UniversityRepository; +import java.util.List; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.item.ItemReader; + +@Slf4j +public class FeedItemReader implements ItemReader { + + private final StudentRepository studentRepository; + private final CategoryRepository categoryRepository; + private final UniversityRepository universityRepository; + private final AtomicInteger counter = new AtomicInteger(0); + private final int totalCount; + private final Random random = new Random(); + + private List students; + private List categories; + private List universities; + + public FeedItemReader(StudentRepository studentRepository, CategoryRepository categoryRepository, + UniversityRepository universityRepository, int totalCount) { + this.studentRepository = studentRepository; + this.categoryRepository = categoryRepository; + this.universityRepository = universityRepository; + this.totalCount = totalCount; + } + + @Override + public FeedJobDTO read() { + if (students == null) { + students = studentRepository.findAll(); + categories = categoryRepository.findAll(); + universities = universityRepository.findAll(); + if (students.isEmpty() || categories.isEmpty() || universities.isEmpty()) { + throw new IllegalStateException("Prerequisite data (Student, Category, or University) is missing."); + } + } + int currentCount = counter.getAndIncrement(); + if (currentCount >= totalCount) { + return null; + } + Student randomStudent = students.get(random.nextInt(students.size())); + Category randomCategory = categories.get(random.nextInt(categories.size())); + University randomUniversity = universities.get(random.nextInt(universities.size())); + return FeedJobDTO.builder() + .title("피드 제목 " + (currentCount + 1)) + .content("피드 내용입니다. " + (currentCount + 1) + "번째 글입니다. 무작위 텍스트...") + .isProfileVisible(random.nextBoolean()) + .studentId(randomStudent.getId()) + .categoryId(randomCategory.getId()) + .universityId(randomUniversity.getId()) + .build(); + } +} diff --git a/src/main/java/com/team/buddyya/job/feed/FeedJobDTO.java b/src/main/java/com/team/buddyya/job/feed/FeedJobDTO.java new file mode 100644 index 00000000..28028324 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/FeedJobDTO.java @@ -0,0 +1,15 @@ +package com.team.buddyya.job.feed; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class FeedJobDTO { + private String title; + private String content; + private boolean isProfileVisible; + private Long studentId; + private Long categoryId; + private Long universityId; +} diff --git a/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java b/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java index 1897ded8..a12d55f5 100644 --- a/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java +++ b/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java @@ -3,10 +3,8 @@ import com.team.buddyya.student.repository.UniversityRepository; import javax.sql.DataSource; import lombok.RequiredArgsConstructor; -import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; 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.ItemReader; @@ -16,10 +14,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; import org.springframework.transaction.PlatformTransactionManager; -@Profile("local") @Configuration @RequiredArgsConstructor public class StudentInsertJobConfig { @@ -30,13 +26,6 @@ public class StudentInsertJobConfig { private final DataSource dataSource; private final UniversityRepository universityRepository; - @Bean - public Job studentInsertJob(Step studentInsertStep) { - return new JobBuilder("studentInsertJob", jobRepository) - .start(studentInsertStep) - .build(); - } - @Bean public Step studentInsertStep( ItemReader studentItemReader, From 566bc6e23c78be1086db1aec025ba4a3a782f6d0 Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Thu, 4 Sep 2025 14:42:40 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=EB=8C=93=EA=B8=80=20=EB=8C=80?= =?UTF-8?q?=EC=9A=A9=EB=9F=89=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=82=BD?= =?UTF-8?q?=EC=9E=85=20Job=20(#365)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team/buddyya/job/SeedDataJobConfig.java | 5 +- .../buddyya/job/feed/FeedInsertJobConfig.java | 4 +- .../team/buddyya/job/feed/FeedItemReader.java | 2 +- .../com/team/buddyya/job/feed/FeedJobDTO.java | 2 +- .../feed/comment/CommentInsertJobConfig.java | 50 +++++++++++++++++++ .../job/feed/comment/CommentItemReader.java | 47 +++++++++++++++++ .../job/feed/comment/CommentJobDTO.java | 13 +++++ 7 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/team/buddyya/job/feed/comment/CommentInsertJobConfig.java create mode 100644 src/main/java/com/team/buddyya/job/feed/comment/CommentItemReader.java create mode 100644 src/main/java/com/team/buddyya/job/feed/comment/CommentJobDTO.java diff --git a/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java b/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java index 296be81e..276d8186 100644 --- a/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java +++ b/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java @@ -1,6 +1,7 @@ package com.team.buddyya.job; import com.team.buddyya.job.feed.FeedInsertJobConfig; +import com.team.buddyya.job.feed.comment.CommentInsertJobConfig; import com.team.buddyya.job.student.StudentInsertJobConfig; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Job; @@ -13,7 +14,7 @@ import org.springframework.context.annotation.Profile; @Profile("local") -@Import({StudentInsertJobConfig.class, FeedInsertJobConfig.class}) +@Import({StudentInsertJobConfig.class, FeedInsertJobConfig.class, CommentInsertJobConfig.class}) @Configuration @RequiredArgsConstructor public class SeedDataJobConfig { @@ -21,12 +22,14 @@ public class SeedDataJobConfig { private final JobRepository jobRepository; private final Step studentInsertStep; private final Step feedInsertStep; + private final Step commentInsertStep; @Bean public Job seedDataJob() { return new JobBuilder("seedDataJob", jobRepository) .start(studentInsertStep) .next(feedInsertStep) + .next(commentInsertStep) .build(); } } diff --git a/src/main/java/com/team/buddyya/job/feed/FeedInsertJobConfig.java b/src/main/java/com/team/buddyya/job/feed/FeedInsertJobConfig.java index 557456ac..e7c10fd7 100644 --- a/src/main/java/com/team/buddyya/job/feed/FeedInsertJobConfig.java +++ b/src/main/java/com/team/buddyya/job/feed/FeedInsertJobConfig.java @@ -54,9 +54,9 @@ public ItemReader feedItemReader( @Bean public JdbcBatchItemWriter feedItemWriter() { String sql = - "INSERT INTO feed (title, content, is_profile_visible, student_id, category_id, university_id, like_count, comment_count, view_count, pinned, created_at, updated_at) " + "INSERT INTO feed (title, content, is_profile_visible, student_id, category_id, university_id, like_count, comment_count, view_count, pinned, created_date, updated_date) " + - "VALUES (:title, :content, :isProfileVisible, :studentId, :categoryId, :universityId, 0, 0, 0, false, NOW(), NOW())"; + "VALUES (:title, :content, :profileVisible, :studentId, :categoryId, :universityId, 0, 0, 0, false, NOW(), NOW())"; return new JdbcBatchItemWriterBuilder() .dataSource(dataSource) .sql(sql) diff --git a/src/main/java/com/team/buddyya/job/feed/FeedItemReader.java b/src/main/java/com/team/buddyya/job/feed/FeedItemReader.java index 2f0c4b55..a3f45ccf 100644 --- a/src/main/java/com/team/buddyya/job/feed/FeedItemReader.java +++ b/src/main/java/com/team/buddyya/job/feed/FeedItemReader.java @@ -54,7 +54,7 @@ public FeedJobDTO read() { return FeedJobDTO.builder() .title("피드 제목 " + (currentCount + 1)) .content("피드 내용입니다. " + (currentCount + 1) + "번째 글입니다. 무작위 텍스트...") - .isProfileVisible(random.nextBoolean()) + .profileVisible(random.nextBoolean()) .studentId(randomStudent.getId()) .categoryId(randomCategory.getId()) .universityId(randomUniversity.getId()) diff --git a/src/main/java/com/team/buddyya/job/feed/FeedJobDTO.java b/src/main/java/com/team/buddyya/job/feed/FeedJobDTO.java index 28028324..ef5e3e4a 100644 --- a/src/main/java/com/team/buddyya/job/feed/FeedJobDTO.java +++ b/src/main/java/com/team/buddyya/job/feed/FeedJobDTO.java @@ -8,7 +8,7 @@ public class FeedJobDTO { private String title; private String content; - private boolean isProfileVisible; + private boolean profileVisible; private Long studentId; private Long categoryId; private Long universityId; diff --git a/src/main/java/com/team/buddyya/job/feed/comment/CommentInsertJobConfig.java b/src/main/java/com/team/buddyya/job/feed/comment/CommentInsertJobConfig.java new file mode 100644 index 00000000..0abf4a44 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/comment/CommentInsertJobConfig.java @@ -0,0 +1,50 @@ +package com.team.buddyya.job.feed.comment; + +import com.team.buddyya.feed.repository.FeedRepository; +import com.team.buddyya.student.repository.StudentRepository; +import javax.sql.DataSource; +import lombok.RequiredArgsConstructor; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider; +import org.springframework.batch.item.database.JdbcBatchItemWriter; +import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.PlatformTransactionManager; + +@Configuration +@RequiredArgsConstructor +public class CommentInsertJobConfig { + + private final JobRepository jobRepository; + private final PlatformTransactionManager platformTransactionManager; + private final DataSource dataSource; + private final FeedRepository feedRepository; + private final StudentRepository studentRepository; + private static final int CHUNK_SIZE = 1000; + + @Bean + public Step commentInsertStep(ItemReader commentItemReader, + JdbcBatchItemWriter commentItemWriter) { + return new StepBuilder("commentInsertStep", jobRepository).chunk(CHUNK_SIZE, + platformTransactionManager).reader(commentItemReader).writer(commentItemWriter).build(); + } + + @Bean + @StepScope + public ItemReader commentItemReader(@Value("#{jobParameters['count'] ?: 100000}") int totalCount) { + return new CommentItemReader(feedRepository, studentRepository, totalCount); + } + + @Bean + public JdbcBatchItemWriter commentItemWriter() { + String sql = "INSERT INTO comment (student_id, feed_id, parent_id, content, like_count, deleted, created_date, updated_date) VALUES (:studentId, :feedId, :parentId, :content, 0, false, NOW(), NOW())"; + return new JdbcBatchItemWriterBuilder().dataSource(dataSource).sql(sql) + .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()).build(); + } +} diff --git a/src/main/java/com/team/buddyya/job/feed/comment/CommentItemReader.java b/src/main/java/com/team/buddyya/job/feed/comment/CommentItemReader.java new file mode 100644 index 00000000..b636b1e3 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/comment/CommentItemReader.java @@ -0,0 +1,47 @@ +package com.team.buddyya.job.feed.comment; + +import com.team.buddyya.feed.domain.Feed; +import com.team.buddyya.feed.repository.FeedRepository; +import com.team.buddyya.student.domain.Student; +import com.team.buddyya.student.repository.StudentRepository; +import java.util.List; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import org.springframework.batch.item.ItemReader; + +public class CommentItemReader implements ItemReader { + + private final FeedRepository feedRepository; + private final StudentRepository studentRepository; + private final int totalCount; + private final AtomicInteger counter = new AtomicInteger(0); + private final Random random = new Random(); + + private List feeds; + private List students; + + public CommentItemReader(FeedRepository feedRepository, StudentRepository studentRepository, int totalCount) { + this.feedRepository = feedRepository; + this.studentRepository = studentRepository; + this.totalCount = totalCount; + } + + @Override + public CommentJobDTO read() { + if (feeds == null) { + feeds = feedRepository.findAll(); + students = studentRepository.findAll(); + if (feeds.isEmpty() || students.isEmpty()) { + throw new IllegalStateException("Prerequisite data (Feed or Student) is missing."); + } + } + int currentCount = counter.getAndIncrement(); + if (currentCount >= totalCount) { + return null; + } + Feed randomFeed = feeds.get(random.nextInt(feeds.size())); + Student randomStudent = students.get(random.nextInt(students.size())); + return CommentJobDTO.builder().studentId(randomStudent.getId()).feedId(randomFeed.getId()) + .content("Comment content " + (currentCount + 1)).parentId(null).build(); + } +} diff --git a/src/main/java/com/team/buddyya/job/feed/comment/CommentJobDTO.java b/src/main/java/com/team/buddyya/job/feed/comment/CommentJobDTO.java new file mode 100644 index 00000000..08ef96cb --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/comment/CommentJobDTO.java @@ -0,0 +1,13 @@ +package com.team.buddyya.job.feed.comment; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class CommentJobDTO { + private Long studentId; + private Long feedId; + private String content; + private Long parentId; +} From 4f3765eaa9071ee6d8996ec6396f8bcf52edd586 Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Thu, 4 Sep 2025 15:57:14 +0900 Subject: [PATCH 06/10] =?UTF-8?q?refactor:=20=EB=A9=94=EB=AA=A8=EB=A6=AC?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EB=9F=89=20=EC=B5=9C=EC=A0=81=ED=99=94?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20findAll()=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team/buddyya/job/SeedDataJobConfig.java | 4 + .../buddyya/job/feed/FeedInsertJobConfig.java | 29 ++----- .../team/buddyya/job/feed/FeedItemReader.java | 79 +++++++++---------- .../feed/comment/CommentInsertJobConfig.java | 30 +++---- .../job/feed/comment/CommentItemReader.java | 56 ++++++------- .../job/student/StudentInsertJobConfig.java | 28 +++---- .../job/student/StudentItemReader.java | 43 +++++----- 7 files changed, 128 insertions(+), 141 deletions(-) diff --git a/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java b/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java index 276d8186..5584cb9e 100644 --- a/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java +++ b/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java @@ -19,6 +19,10 @@ @RequiredArgsConstructor public class SeedDataJobConfig { + public static final int STUDENT_COUNT = 100_000; + public static final int FEED_COUNT = 1_000_000; + public static final int COMMENT_COUNT = 100_000; + private final JobRepository jobRepository; private final Step studentInsertStep; private final Step feedInsertStep; diff --git a/src/main/java/com/team/buddyya/job/feed/FeedInsertJobConfig.java b/src/main/java/com/team/buddyya/job/feed/FeedInsertJobConfig.java index e7c10fd7..6892735a 100644 --- a/src/main/java/com/team/buddyya/job/feed/FeedInsertJobConfig.java +++ b/src/main/java/com/team/buddyya/job/feed/FeedInsertJobConfig.java @@ -1,21 +1,19 @@ package com.team.buddyya.job.feed; -import com.team.buddyya.feed.repository.CategoryRepository; -import com.team.buddyya.student.repository.StudentRepository; -import com.team.buddyya.student.repository.UniversityRepository; +import static com.team.buddyya.job.SeedDataJobConfig.FEED_COUNT; + import javax.sql.DataSource; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Step; -import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider; import org.springframework.batch.item.database.JdbcBatchItemWriter; import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.PlatformTransactionManager; @Configuration @@ -25,17 +23,10 @@ public class FeedInsertJobConfig { private final JobRepository jobRepository; private final PlatformTransactionManager platformTransactionManager; private final DataSource dataSource; - private final StudentRepository studentRepository; - private final CategoryRepository categoryRepository; - private final UniversityRepository universityRepository; - private static final int CHUNK_SIZE = 1000; @Bean - public Step feedInsertStep( - ItemReader feedItemReader, - JdbcBatchItemWriter feedItemWriter - ) { + public Step feedInsertStep(ItemReader feedItemReader, JdbcBatchItemWriter feedItemWriter) { return new StepBuilder("feedInsertStep", jobRepository) .chunk(CHUNK_SIZE, platformTransactionManager) .reader(feedItemReader) @@ -44,19 +35,13 @@ public Step feedInsertStep( } @Bean - @StepScope - public ItemReader feedItemReader( - @Value("#{jobParameters['count'] ?: 100000}") int totalCount - ) { - return new FeedItemReader(studentRepository, categoryRepository, universityRepository, totalCount); + public ItemReader feedItemReader() { + return new FeedItemReader(new JdbcTemplate(dataSource), FEED_COUNT); } @Bean public JdbcBatchItemWriter feedItemWriter() { - String sql = - "INSERT INTO feed (title, content, is_profile_visible, student_id, category_id, university_id, like_count, comment_count, view_count, pinned, created_date, updated_date) " - + - "VALUES (:title, :content, :profileVisible, :studentId, :categoryId, :universityId, 0, 0, 0, false, NOW(), NOW())"; + String sql = "INSERT INTO feed (title, content, is_profile_visible, student_id, category_id, university_id, like_count, comment_count, view_count, pinned, created_date, updated_date) VALUES (:title, :content, :profileVisible, :studentId, :categoryId, :universityId, 0, 0, 0, false, NOW(), NOW())"; return new JdbcBatchItemWriterBuilder() .dataSource(dataSource) .sql(sql) diff --git a/src/main/java/com/team/buddyya/job/feed/FeedItemReader.java b/src/main/java/com/team/buddyya/job/feed/FeedItemReader.java index a3f45ccf..5baf3be7 100644 --- a/src/main/java/com/team/buddyya/job/feed/FeedItemReader.java +++ b/src/main/java/com/team/buddyya/job/feed/FeedItemReader.java @@ -1,63 +1,60 @@ package com.team.buddyya.job.feed; -import com.team.buddyya.feed.domain.Category; -import com.team.buddyya.feed.repository.CategoryRepository; -import com.team.buddyya.student.domain.Student; -import com.team.buddyya.student.domain.University; -import com.team.buddyya.student.repository.StudentRepository; -import com.team.buddyya.student.repository.UniversityRepository; -import java.util.List; -import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; -import lombok.extern.slf4j.Slf4j; import org.springframework.batch.item.ItemReader; +import org.springframework.jdbc.core.JdbcTemplate; -@Slf4j public class FeedItemReader implements ItemReader { - private final StudentRepository studentRepository; - private final CategoryRepository categoryRepository; - private final UniversityRepository universityRepository; - private final AtomicInteger counter = new AtomicInteger(0); + private final JdbcTemplate jdbcTemplate; private final int totalCount; - private final Random random = new Random(); + private final AtomicInteger counter = new AtomicInteger(0); - private List students; - private List categories; - private List universities; + private Long minStudentId; + private Long maxStudentId; + private Long minUniversityId; + private Long maxUniversityId; - public FeedItemReader(StudentRepository studentRepository, CategoryRepository categoryRepository, - UniversityRepository universityRepository, int totalCount) { - this.studentRepository = studentRepository; - this.categoryRepository = categoryRepository; - this.universityRepository = universityRepository; + public FeedItemReader(JdbcTemplate jdbcTemplate, int totalCount) { + this.jdbcTemplate = jdbcTemplate; this.totalCount = totalCount; } @Override - public FeedJobDTO read() { - if (students == null) { - students = studentRepository.findAll(); - categories = categoryRepository.findAll(); - universities = universityRepository.findAll(); - if (students.isEmpty() || categories.isEmpty() || universities.isEmpty()) { - throw new IllegalStateException("Prerequisite data (Student, Category, or University) is missing."); + public FeedJobDTO read() throws Exception { + if (minStudentId == null) { + // Student, University 테이블의 데이터 존재 여부 및 ID 범위 초기화 + if (jdbcTemplate.queryForObject("SELECT COUNT(1) FROM student", Long.class) == 0) { + throw new IllegalStateException("Prerequisite data (Student) is missing."); + } + this.minStudentId = jdbcTemplate.queryForObject("SELECT MIN(id) FROM student", Long.class); + this.maxStudentId = jdbcTemplate.queryForObject("SELECT MAX(id) FROM student", Long.class); + if (jdbcTemplate.queryForObject("SELECT COUNT(1) FROM university", Long.class) == 0) { + throw new IllegalStateException("Prerequisite data (University) is missing."); } + this.minUniversityId = jdbcTemplate.queryForObject("SELECT MIN(id) FROM university", Long.class); + this.maxUniversityId = jdbcTemplate.queryForObject("SELECT MAX(id) FROM university", Long.class); } - int currentCount = counter.getAndIncrement(); - if (currentCount >= totalCount) { + + if (counter.get() >= totalCount) { return null; } - Student randomStudent = students.get(random.nextInt(students.size())); - Category randomCategory = categories.get(random.nextInt(categories.size())); - University randomUniversity = universities.get(random.nextInt(universities.size())); + + int currentCount = counter.incrementAndGet(); + long randomStudentId = ThreadLocalRandom.current().nextLong(minStudentId, maxStudentId + 1); + long randomUniversityId = ThreadLocalRandom.current().nextLong(minUniversityId, maxUniversityId + 1); + // Category는 데이터가 적다고 가정하고 SQL로 랜덤 조회, 많아진다면 동일하게 MIN/MAX 방식으로 변경 + Long randomCategoryId = jdbcTemplate.queryForObject("SELECT id FROM category ORDER BY RAND() LIMIT 1", + Long.class); + return FeedJobDTO.builder() - .title("피드 제목 " + (currentCount + 1)) - .content("피드 내용입니다. " + (currentCount + 1) + "번째 글입니다. 무작위 텍스트...") - .profileVisible(random.nextBoolean()) - .studentId(randomStudent.getId()) - .categoryId(randomCategory.getId()) - .universityId(randomUniversity.getId()) + .title("피드 제목 " + currentCount) + .content("피드 내용입니다. " + currentCount) + .profileVisible(ThreadLocalRandom.current().nextBoolean()) + .studentId(randomStudentId) + .categoryId(randomCategoryId) + .universityId(randomUniversityId) .build(); } } diff --git a/src/main/java/com/team/buddyya/job/feed/comment/CommentInsertJobConfig.java b/src/main/java/com/team/buddyya/job/feed/comment/CommentInsertJobConfig.java index 0abf4a44..afa56c1c 100644 --- a/src/main/java/com/team/buddyya/job/feed/comment/CommentInsertJobConfig.java +++ b/src/main/java/com/team/buddyya/job/feed/comment/CommentInsertJobConfig.java @@ -1,20 +1,19 @@ package com.team.buddyya.job.feed.comment; -import com.team.buddyya.feed.repository.FeedRepository; -import com.team.buddyya.student.repository.StudentRepository; +import static com.team.buddyya.job.SeedDataJobConfig.COMMENT_COUNT; + import javax.sql.DataSource; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Step; -import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider; import org.springframework.batch.item.database.JdbcBatchItemWriter; import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.PlatformTransactionManager; @Configuration @@ -24,27 +23,30 @@ public class CommentInsertJobConfig { private final JobRepository jobRepository; private final PlatformTransactionManager platformTransactionManager; private final DataSource dataSource; - private final FeedRepository feedRepository; - private final StudentRepository studentRepository; private static final int CHUNK_SIZE = 1000; @Bean public Step commentInsertStep(ItemReader commentItemReader, JdbcBatchItemWriter commentItemWriter) { - return new StepBuilder("commentInsertStep", jobRepository).chunk(CHUNK_SIZE, - platformTransactionManager).reader(commentItemReader).writer(commentItemWriter).build(); + return new StepBuilder("commentInsertStep", jobRepository) + .chunk(CHUNK_SIZE, platformTransactionManager) + .reader(commentItemReader) + .writer(commentItemWriter) + .build(); } @Bean - @StepScope - public ItemReader commentItemReader(@Value("#{jobParameters['count'] ?: 100000}") int totalCount) { - return new CommentItemReader(feedRepository, studentRepository, totalCount); + public ItemReader commentItemReader() { + return new CommentItemReader(new JdbcTemplate(dataSource), COMMENT_COUNT); } @Bean public JdbcBatchItemWriter commentItemWriter() { - String sql = "INSERT INTO comment (student_id, feed_id, parent_id, content, like_count, deleted, created_date, updated_date) VALUES (:studentId, :feedId, :parentId, :content, 0, false, NOW(), NOW())"; - return new JdbcBatchItemWriterBuilder().dataSource(dataSource).sql(sql) - .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()).build(); + String sql = "INSERT INTO comment (student_id, feed_id, parent_id, content, like_count, deleted, created_at, updated_at) VALUES (:studentId, :feedId, :parentId, :content, 0, false, NOW(), NOW())"; + return new JdbcBatchItemWriterBuilder() + .dataSource(dataSource) + .sql(sql) + .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) + .build(); } } diff --git a/src/main/java/com/team/buddyya/job/feed/comment/CommentItemReader.java b/src/main/java/com/team/buddyya/job/feed/comment/CommentItemReader.java index b636b1e3..cd7fd60f 100644 --- a/src/main/java/com/team/buddyya/job/feed/comment/CommentItemReader.java +++ b/src/main/java/com/team/buddyya/job/feed/comment/CommentItemReader.java @@ -1,47 +1,49 @@ package com.team.buddyya.job.feed.comment; -import com.team.buddyya.feed.domain.Feed; -import com.team.buddyya.feed.repository.FeedRepository; -import com.team.buddyya.student.domain.Student; -import com.team.buddyya.student.repository.StudentRepository; -import java.util.List; -import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; import org.springframework.batch.item.ItemReader; +import org.springframework.jdbc.core.JdbcTemplate; public class CommentItemReader implements ItemReader { - private final FeedRepository feedRepository; - private final StudentRepository studentRepository; + private final JdbcTemplate jdbcTemplate; private final int totalCount; private final AtomicInteger counter = new AtomicInteger(0); - private final Random random = new Random(); - private List feeds; - private List students; + private Long minStudentId; + private Long maxStudentId; + private Long minFeedId; + private Long maxFeedId; - public CommentItemReader(FeedRepository feedRepository, StudentRepository studentRepository, int totalCount) { - this.feedRepository = feedRepository; - this.studentRepository = studentRepository; + public CommentItemReader(JdbcTemplate jdbcTemplate, int totalCount) { + this.jdbcTemplate = jdbcTemplate; this.totalCount = totalCount; } @Override - public CommentJobDTO read() { - if (feeds == null) { - feeds = feedRepository.findAll(); - students = studentRepository.findAll(); - if (feeds.isEmpty() || students.isEmpty()) { - throw new IllegalStateException("Prerequisite data (Feed or Student) is missing."); - } + public CommentJobDTO read() throws Exception { + if (minFeedId == null) { + // Student, Feed 테이블의 ID 범위 초기화 + this.minStudentId = jdbcTemplate.queryForObject("SELECT MIN(id) FROM student", Long.class); + this.maxStudentId = jdbcTemplate.queryForObject("SELECT MAX(id) FROM student", Long.class); + this.minFeedId = jdbcTemplate.queryForObject("SELECT MIN(id) FROM feed", Long.class); + this.maxFeedId = jdbcTemplate.queryForObject("SELECT MAX(id) FROM feed", Long.class); } - int currentCount = counter.getAndIncrement(); - if (currentCount >= totalCount) { + + if (counter.get() >= totalCount) { return null; } - Feed randomFeed = feeds.get(random.nextInt(feeds.size())); - Student randomStudent = students.get(random.nextInt(students.size())); - return CommentJobDTO.builder().studentId(randomStudent.getId()).feedId(randomFeed.getId()) - .content("Comment content " + (currentCount + 1)).parentId(null).build(); + + int currentCount = counter.incrementAndGet(); + long randomStudentId = ThreadLocalRandom.current().nextLong(minStudentId, maxStudentId + 1); + long randomFeedId = ThreadLocalRandom.current().nextLong(minFeedId, maxFeedId + 1); + + return CommentJobDTO.builder() + .studentId(randomStudentId) + .feedId(randomFeedId) + .content("Comment content " + currentCount) + .parentId(null) + .build(); } } diff --git a/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java b/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java index a12d55f5..df4e086e 100644 --- a/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java +++ b/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java @@ -1,36 +1,34 @@ package com.team.buddyya.job.student; -import com.team.buddyya.student.repository.UniversityRepository; +import static com.team.buddyya.job.SeedDataJobConfig.STUDENT_COUNT; + import javax.sql.DataSource; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Step; -import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider; import org.springframework.batch.item.database.JdbcBatchItemWriter; import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.PlatformTransactionManager; @Configuration @RequiredArgsConstructor public class StudentInsertJobConfig { - private static final int CHUNK_SIZE = 1000; private final JobRepository jobRepository; private final PlatformTransactionManager platformTransactionManager; private final DataSource dataSource; - private final UniversityRepository universityRepository; + private static final int CHUNK_SIZE = 1000; + // private final UniversityRepository universityRepository; // 의존성 제거 @Bean - public Step studentInsertStep( - ItemReader studentItemReader, - JdbcBatchItemWriter studentItemWriter - ) { + public Step studentInsertStep(ItemReader studentItemReader, + JdbcBatchItemWriter studentItemWriter) { return new StepBuilder("studentInsertStep", jobRepository) .chunk(CHUNK_SIZE, platformTransactionManager) .reader(studentItemReader) @@ -39,19 +37,13 @@ public Step studentInsertStep( } @Bean - @StepScope - public ItemReader studentItemReader( - @Value("#{jobParameters['count'] ?: 10000}") int totalCount - ) { - return new StudentItemReader(universityRepository, totalCount); + public ItemReader studentItemReader() { + return new StudentItemReader(new JdbcTemplate(dataSource), STUDENT_COUNT); } @Bean public JdbcBatchItemWriter studentItemWriter() { - String sql = - "INSERT INTO student (phone_number, name, country, certificated, korean, deleted, university_id, role, gender, character_profile_image, banned, created_date, updated_date) " - + - "VALUES (:phoneNumber, :name, :country, :isCertificated, :isKorean, :isDeleted, :universityId, :role, :gender, :characterProfileImage, :isBanned, NOW(), NOW())"; + String sql = "INSERT INTO student (phone_number, name, country, certificated, korean, deleted, university_id, role, gender, character_profile_image, banned, created_date, updated_date) VALUES (:phoneNumber, :name, :country, :isCertificated, :isKorean, :isDeleted, :universityId, :role, :gender, :characterProfileImage, :isBanned, NOW(), NOW())"; return new JdbcBatchItemWriterBuilder() .dataSource(dataSource) .sql(sql) diff --git a/src/main/java/com/team/buddyya/job/student/StudentItemReader.java b/src/main/java/com/team/buddyya/job/student/StudentItemReader.java index 0155c827..e60b29ae 100644 --- a/src/main/java/com/team/buddyya/job/student/StudentItemReader.java +++ b/src/main/java/com/team/buddyya/job/student/StudentItemReader.java @@ -1,38 +1,43 @@ package com.team.buddyya.job.student; -import com.team.buddyya.student.domain.University; -import com.team.buddyya.student.repository.UniversityRepository; -import java.util.List; -import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; -import lombok.extern.slf4j.Slf4j; import org.springframework.batch.item.ItemReader; +import org.springframework.jdbc.core.JdbcTemplate; -@Slf4j public class StudentItemReader implements ItemReader { - private final UniversityRepository universityRepository; + private final JdbcTemplate jdbcTemplate; private final int totalCount; private final AtomicInteger counter = new AtomicInteger(0); - private final Random random = new Random(); - private List universities; - public StudentItemReader(UniversityRepository universityRepository, int totalCount) { - this.universityRepository = universityRepository; + private Long minUniversityId; + private Long maxUniversityId; + + public StudentItemReader(JdbcTemplate jdbcTemplate, int totalCount) { + this.jdbcTemplate = jdbcTemplate; this.totalCount = totalCount; } @Override public StudentJobDTO read() { - if (this.universities == null) { - this.universities = universityRepository.findAll(); + if (minUniversityId == null) { + // University 테이블의 데이터 존재 여부 및 ID 범위 초기화 + if (jdbcTemplate.queryForObject("SELECT COUNT(1) FROM university", Long.class) == 0) { + throw new IllegalStateException("Prerequisite data (University) is missing."); + } + this.minUniversityId = jdbcTemplate.queryForObject("SELECT MIN(id) FROM university", Long.class); + this.maxUniversityId = jdbcTemplate.queryForObject("SELECT MAX(id) FROM university", Long.class); } - int currentCount = counter.getAndIncrement(); - if (currentCount >= totalCount) { + + if (counter.get() >= totalCount) { return null; } - int index = currentCount + 1; - University randomUniversity = universities.get(random.nextInt(universities.size())); + + int index = counter.incrementAndGet(); + long randomUniversityId = ThreadLocalRandom.current().nextLong(minUniversityId, maxUniversityId + 1); + String gender = (index % 2 == 0) ? "MALE" : "FEMALE"; + return StudentJobDTO.builder() .phoneNumber("010" + String.format("%08d", index)) .name("student" + index) @@ -40,9 +45,9 @@ public StudentJobDTO read() { .isCertificated(false) .isKorean(true) .isDeleted(false) - .universityId(randomUniversity.getId()) + .universityId(randomUniversityId) .role("STUDENT") - .gender(index % 2 == 0 ? "MALE" : "FEMALE") + .gender(gender) .characterProfileImage("default_image_url_" + (index % 8)) .isBanned(false) .build(); From 60cbfa937026684a25acc774a69fd08c8fbcd509 Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Thu, 4 Sep 2025 16:12:31 +0900 Subject: [PATCH 07/10] =?UTF-8?q?fix:=20created=5Fat=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20created=5Fdate=EB=A1=9C=20=EC=88=98=EC=A0=95=20(#36?= =?UTF-8?q?5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team/buddyya/job/feed/comment/CommentInsertJobConfig.java | 2 +- .../com/team/buddyya/job/feed/comment/CommentItemReader.java | 4 ---- .../com/team/buddyya/job/student/StudentInsertJobConfig.java | 1 - .../java/com/team/buddyya/job/student/StudentItemReader.java | 3 --- 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/java/com/team/buddyya/job/feed/comment/CommentInsertJobConfig.java b/src/main/java/com/team/buddyya/job/feed/comment/CommentInsertJobConfig.java index afa56c1c..9e5c56ce 100644 --- a/src/main/java/com/team/buddyya/job/feed/comment/CommentInsertJobConfig.java +++ b/src/main/java/com/team/buddyya/job/feed/comment/CommentInsertJobConfig.java @@ -42,7 +42,7 @@ public ItemReader commentItemReader() { @Bean public JdbcBatchItemWriter commentItemWriter() { - String sql = "INSERT INTO comment (student_id, feed_id, parent_id, content, like_count, deleted, created_at, updated_at) VALUES (:studentId, :feedId, :parentId, :content, 0, false, NOW(), NOW())"; + String sql = "INSERT INTO comment (student_id, feed_id, parent_id, content, like_count, deleted, created_date, updated_date) VALUES (:studentId, :feedId, :parentId, :content, 0, false, NOW(), NOW())"; return new JdbcBatchItemWriterBuilder() .dataSource(dataSource) .sql(sql) diff --git a/src/main/java/com/team/buddyya/job/feed/comment/CommentItemReader.java b/src/main/java/com/team/buddyya/job/feed/comment/CommentItemReader.java index cd7fd60f..9b26a946 100644 --- a/src/main/java/com/team/buddyya/job/feed/comment/CommentItemReader.java +++ b/src/main/java/com/team/buddyya/job/feed/comment/CommentItemReader.java @@ -24,21 +24,17 @@ public CommentItemReader(JdbcTemplate jdbcTemplate, int totalCount) { @Override public CommentJobDTO read() throws Exception { if (minFeedId == null) { - // Student, Feed 테이블의 ID 범위 초기화 this.minStudentId = jdbcTemplate.queryForObject("SELECT MIN(id) FROM student", Long.class); this.maxStudentId = jdbcTemplate.queryForObject("SELECT MAX(id) FROM student", Long.class); this.minFeedId = jdbcTemplate.queryForObject("SELECT MIN(id) FROM feed", Long.class); this.maxFeedId = jdbcTemplate.queryForObject("SELECT MAX(id) FROM feed", Long.class); } - if (counter.get() >= totalCount) { return null; } - int currentCount = counter.incrementAndGet(); long randomStudentId = ThreadLocalRandom.current().nextLong(minStudentId, maxStudentId + 1); long randomFeedId = ThreadLocalRandom.current().nextLong(minFeedId, maxFeedId + 1); - return CommentJobDTO.builder() .studentId(randomStudentId) .feedId(randomFeedId) diff --git a/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java b/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java index df4e086e..05de4d64 100644 --- a/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java +++ b/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java @@ -24,7 +24,6 @@ public class StudentInsertJobConfig { private final PlatformTransactionManager platformTransactionManager; private final DataSource dataSource; private static final int CHUNK_SIZE = 1000; - // private final UniversityRepository universityRepository; // 의존성 제거 @Bean public Step studentInsertStep(ItemReader studentItemReader, diff --git a/src/main/java/com/team/buddyya/job/student/StudentItemReader.java b/src/main/java/com/team/buddyya/job/student/StudentItemReader.java index e60b29ae..329b2978 100644 --- a/src/main/java/com/team/buddyya/job/student/StudentItemReader.java +++ b/src/main/java/com/team/buddyya/job/student/StudentItemReader.java @@ -29,15 +29,12 @@ public StudentJobDTO read() { this.minUniversityId = jdbcTemplate.queryForObject("SELECT MIN(id) FROM university", Long.class); this.maxUniversityId = jdbcTemplate.queryForObject("SELECT MAX(id) FROM university", Long.class); } - if (counter.get() >= totalCount) { return null; } - int index = counter.incrementAndGet(); long randomUniversityId = ThreadLocalRandom.current().nextLong(minUniversityId, maxUniversityId + 1); String gender = (index % 2 == 0) ? "MALE" : "FEMALE"; - return StudentJobDTO.builder() .phoneNumber("010" + String.format("%08d", index)) .name("student" + index) From ab7f74aea795f4fa7ca6b07b51f062ab59d061bc Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Fri, 5 Sep 2025 16:48:34 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20feed=EB=A5=BC=20=EC=B0=B8?= =?UTF-8?q?=EC=A1=B0=ED=95=98=EB=8A=94=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=82=BD=EC=9E=85=20Job=20(#365)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team/buddyya/job/SeedDataJobConfig.java | 14 ++++- .../bookmark/BookmarkInsertJobConfig.java | 52 ++++++++++++++++++ .../job/feed/bookmark/BookmarkItemReader.java | 43 +++++++++++++++ .../job/feed/bookmark/BookmarkJobDTO.java | 11 ++++ .../feed/image/FeedImageInsertJobConfig.java | 52 ++++++++++++++++++ .../job/feed/image/FeedImageItemReader.java | 53 +++++++++++++++++++ .../job/feed/image/FeedImageJobDTO.java | 11 ++++ .../feed/like/FeedLikeInsertJobConfig.java | 52 ++++++++++++++++++ .../job/feed/like/FeedLikeItemReader.java | 43 +++++++++++++++ .../buddyya/job/feed/like/FeedLikeJobDTO.java | 11 ++++ .../job/student/StudentItemReader.java | 5 +- 11 files changed, 345 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkInsertJobConfig.java create mode 100644 src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkItemReader.java create mode 100644 src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkJobDTO.java create mode 100644 src/main/java/com/team/buddyya/job/feed/image/FeedImageInsertJobConfig.java create mode 100644 src/main/java/com/team/buddyya/job/feed/image/FeedImageItemReader.java create mode 100644 src/main/java/com/team/buddyya/job/feed/image/FeedImageJobDTO.java create mode 100644 src/main/java/com/team/buddyya/job/feed/like/FeedLikeInsertJobConfig.java create mode 100644 src/main/java/com/team/buddyya/job/feed/like/FeedLikeItemReader.java create mode 100644 src/main/java/com/team/buddyya/job/feed/like/FeedLikeJobDTO.java diff --git a/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java b/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java index 5584cb9e..f1d555db 100644 --- a/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java +++ b/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java @@ -1,7 +1,10 @@ package com.team.buddyya.job; import com.team.buddyya.job.feed.FeedInsertJobConfig; +import com.team.buddyya.job.feed.bookmark.BookmarkInsertJobConfig; import com.team.buddyya.job.feed.comment.CommentInsertJobConfig; +import com.team.buddyya.job.feed.image.FeedImageInsertJobConfig; +import com.team.buddyya.job.feed.like.FeedLikeInsertJobConfig; import com.team.buddyya.job.student.StudentInsertJobConfig; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Job; @@ -14,7 +17,8 @@ import org.springframework.context.annotation.Profile; @Profile("local") -@Import({StudentInsertJobConfig.class, FeedInsertJobConfig.class, CommentInsertJobConfig.class}) +@Import({StudentInsertJobConfig.class, FeedInsertJobConfig.class, CommentInsertJobConfig.class, + FeedImageInsertJobConfig.class, FeedLikeInsertJobConfig.class, BookmarkInsertJobConfig.class}) @Configuration @RequiredArgsConstructor public class SeedDataJobConfig { @@ -22,11 +26,16 @@ public class SeedDataJobConfig { public static final int STUDENT_COUNT = 100_000; public static final int FEED_COUNT = 1_000_000; public static final int COMMENT_COUNT = 100_000; + public static final int LIKE_COUNT = FEED_COUNT * 5; + public static final int BOOKMARK_COUNT = FEED_COUNT * 2; private final JobRepository jobRepository; private final Step studentInsertStep; private final Step feedInsertStep; private final Step commentInsertStep; + private final Step feedImageInsertStep; + private final Step feedLikeInsertStep; + private final Step bookmarkInsertStep; @Bean public Job seedDataJob() { @@ -34,6 +43,9 @@ public Job seedDataJob() { .start(studentInsertStep) .next(feedInsertStep) .next(commentInsertStep) + .next(feedImageInsertStep) + .next(feedLikeInsertStep) + .next(bookmarkInsertStep) .build(); } } diff --git a/src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkInsertJobConfig.java b/src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkInsertJobConfig.java new file mode 100644 index 00000000..d13e1e75 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkInsertJobConfig.java @@ -0,0 +1,52 @@ +package com.team.buddyya.job.feed.bookmark; + +import static com.team.buddyya.job.SeedDataJobConfig.BOOKMARK_COUNT; + +import javax.sql.DataSource; +import lombok.RequiredArgsConstructor; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider; +import org.springframework.batch.item.database.JdbcBatchItemWriter; +import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.PlatformTransactionManager; + +@Configuration +@RequiredArgsConstructor +public class BookmarkInsertJobConfig { + + private final JobRepository jobRepository; + private final PlatformTransactionManager platformTransactionManager; + private final DataSource dataSource; + private static final int CHUNK_SIZE = 1000; + + @Bean + public Step bookmarkInsertStep(ItemReader bookmarkItemReader, + JdbcBatchItemWriter bookmarkItemWriter) { + return new StepBuilder("bookmarkInsertStep", jobRepository) + .chunk(CHUNK_SIZE, platformTransactionManager) + .reader(bookmarkItemReader) + .writer(bookmarkItemWriter) + .build(); + } + + @Bean + public ItemReader bookmarkItemReader() { + return new BookmarkItemReader(new JdbcTemplate(dataSource), BOOKMARK_COUNT); + } + + @Bean + public JdbcBatchItemWriter bookmarkItemWriter() { + String sql = "INSERT IGNORE INTO bookmark (feed_id, student_id, created_date) VALUES (:feedId, :studentId, NOW())"; + return new JdbcBatchItemWriterBuilder() + .dataSource(dataSource) + .sql(sql) + .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) + .build(); + } +} diff --git a/src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkItemReader.java b/src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkItemReader.java new file mode 100644 index 00000000..07a74c67 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkItemReader.java @@ -0,0 +1,43 @@ +package com.team.buddyya.job.feed.bookmark; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicInteger; +import org.springframework.batch.item.ItemReader; +import org.springframework.jdbc.core.JdbcTemplate; + +public class BookmarkItemReader implements ItemReader { + + private final JdbcTemplate jdbcTemplate; + private final int totalCount; + private final AtomicInteger counter = new AtomicInteger(0); + private final ThreadLocalRandom random = ThreadLocalRandom.current(); + + private Long minFeedId; + private Long maxFeedId; + private Long minStudentId; + private Long maxStudentId; + + public BookmarkItemReader(JdbcTemplate jdbcTemplate, int totalCount) { + this.jdbcTemplate = jdbcTemplate; + this.totalCount = totalCount; + } + + @Override + public BookmarkJobDTO read() { + if (minFeedId == null) { + this.minFeedId = jdbcTemplate.queryForObject("SELECT MIN(id) FROM feed", Long.class); + this.maxFeedId = jdbcTemplate.queryForObject("SELECT MAX(id) FROM feed", Long.class); + this.minStudentId = jdbcTemplate.queryForObject("SELECT MIN(id) FROM student", Long.class); + this.maxStudentId = jdbcTemplate.queryForObject("SELECT MAX(id) FROM student", Long.class); + } + if (counter.getAndIncrement() >= totalCount) { + return null; + } + long randomFeedId = random.nextLong(minFeedId, maxFeedId + 1); + long randomStudentId = random.nextLong(minStudentId, maxStudentId + 1); + return BookmarkJobDTO.builder() + .feedId(randomFeedId) + .studentId(randomStudentId) + .build(); + } +} diff --git a/src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkJobDTO.java b/src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkJobDTO.java new file mode 100644 index 00000000..dae9c66f --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkJobDTO.java @@ -0,0 +1,11 @@ +package com.team.buddyya.job.feed.bookmark; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class BookmarkJobDTO { + private Long feedId; + private Long studentId; +} diff --git a/src/main/java/com/team/buddyya/job/feed/image/FeedImageInsertJobConfig.java b/src/main/java/com/team/buddyya/job/feed/image/FeedImageInsertJobConfig.java new file mode 100644 index 00000000..eab33b60 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/image/FeedImageInsertJobConfig.java @@ -0,0 +1,52 @@ +package com.team.buddyya.job.feed.image; + +import static com.team.buddyya.job.SeedDataJobConfig.FEED_COUNT; + +import javax.sql.DataSource; +import lombok.RequiredArgsConstructor; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider; +import org.springframework.batch.item.database.JdbcBatchItemWriter; +import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.PlatformTransactionManager; + +@Configuration +@RequiredArgsConstructor +public class FeedImageInsertJobConfig { + + private final JobRepository jobRepository; + private final PlatformTransactionManager platformTransactionManager; + private final DataSource dataSource; + private static final int CHUNK_SIZE = 1000; + + @Bean + public Step feedImageInsertStep(ItemReader feedImageItemReader, + JdbcBatchItemWriter feedImageItemWriter) { + return new StepBuilder("feedImageInsertStep", jobRepository) + .chunk(CHUNK_SIZE, platformTransactionManager) + .reader(feedImageItemReader) + .writer(feedImageItemWriter) + .build(); + } + + @Bean + public ItemReader feedImageItemReader() { + return new FeedImageItemReader(new JdbcTemplate(dataSource), FEED_COUNT); + } + + @Bean + public JdbcBatchItemWriter feedImageItemWriter() { + String sql = "INSERT INTO feed_image (feed_id, url, created_date) VALUES (:feedId, :url, NOW())"; + return new JdbcBatchItemWriterBuilder() + .dataSource(dataSource) + .sql(sql) + .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) + .build(); + } +} diff --git a/src/main/java/com/team/buddyya/job/feed/image/FeedImageItemReader.java b/src/main/java/com/team/buddyya/job/feed/image/FeedImageItemReader.java new file mode 100644 index 00000000..22846591 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/image/FeedImageItemReader.java @@ -0,0 +1,53 @@ +package com.team.buddyya.job.feed.image; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicInteger; +import org.springframework.batch.item.ItemReader; +import org.springframework.jdbc.core.JdbcTemplate; + +public class FeedImageItemReader implements ItemReader { + + private final JdbcTemplate jdbcTemplate; + private final int totalFeeds; + private final AtomicInteger feedCounter = new AtomicInteger(0); + private final ThreadLocalRandom random = ThreadLocalRandom.current(); + private final List imageBuffer = new ArrayList<>(); + + private Long minFeedId; + private Long maxFeedId; + + public FeedImageItemReader(JdbcTemplate jdbcTemplate, int totalFeeds) { + this.jdbcTemplate = jdbcTemplate; + this.totalFeeds = totalFeeds; + } + + @Override + public FeedImageJobDTO read() { + if (minFeedId == null) { + this.minFeedId = jdbcTemplate.queryForObject("SELECT MIN(id) FROM feed", Long.class); + this.maxFeedId = jdbcTemplate.queryForObject("SELECT MAX(id) FROM feed", Long.class); + } + if (!imageBuffer.isEmpty()) { + return imageBuffer.remove(0); + } + while (feedCounter.get() < totalFeeds) { + feedCounter.incrementAndGet(); + if (random.nextInt(10) < 3) { + long randomFeedId = random.nextLong(minFeedId, maxFeedId + 1); + int imageCount = random.nextInt(1, 3); + + for (int i = 0; i < imageCount; i++) { + int imageIndex = random.nextInt(1, 9); + String imageUrl = String.format( + "https://buddyya.s3.ap-northeast-2.amazonaws.com/default-profile-image/image__%d.png", + imageIndex); + imageBuffer.add(FeedImageJobDTO.builder().feedId(randomFeedId).url(imageUrl).build()); + } + return imageBuffer.remove(0); + } + } + return null; + } +} diff --git a/src/main/java/com/team/buddyya/job/feed/image/FeedImageJobDTO.java b/src/main/java/com/team/buddyya/job/feed/image/FeedImageJobDTO.java new file mode 100644 index 00000000..1cb41b2e --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/image/FeedImageJobDTO.java @@ -0,0 +1,11 @@ +package com.team.buddyya.job.feed.image; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class FeedImageJobDTO { + private Long feedId; + private String url; +} diff --git a/src/main/java/com/team/buddyya/job/feed/like/FeedLikeInsertJobConfig.java b/src/main/java/com/team/buddyya/job/feed/like/FeedLikeInsertJobConfig.java new file mode 100644 index 00000000..f9a4178d --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/like/FeedLikeInsertJobConfig.java @@ -0,0 +1,52 @@ +package com.team.buddyya.job.feed.like; + +import static com.team.buddyya.job.SeedDataJobConfig.LIKE_COUNT; + +import javax.sql.DataSource; +import lombok.RequiredArgsConstructor; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider; +import org.springframework.batch.item.database.JdbcBatchItemWriter; +import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.PlatformTransactionManager; + +@Configuration +@RequiredArgsConstructor +public class FeedLikeInsertJobConfig { + + private final JobRepository jobRepository; + private final PlatformTransactionManager platformTransactionManager; + private final DataSource dataSource; + private static final int CHUNK_SIZE = 1000; + + @Bean + public Step feedLikeInsertStep(ItemReader feedLikeItemReader, + JdbcBatchItemWriter feedLikeItemWriter) { + return new StepBuilder("feedLikeInsertStep", jobRepository) + .chunk(CHUNK_SIZE, platformTransactionManager) + .reader(feedLikeItemReader) + .writer(feedLikeItemWriter) + .build(); + } + + @Bean + public ItemReader feedLikeItemReader() { + return new FeedLikeItemReader(new JdbcTemplate(dataSource), LIKE_COUNT); + } + + @Bean + public JdbcBatchItemWriter feedLikeItemWriter() { + String sql = "INSERT IGNORE INTO feed_like (feed_id, student_id, created_date) VALUES (:feedId, :studentId, NOW())"; + return new JdbcBatchItemWriterBuilder() + .dataSource(dataSource) + .sql(sql) + .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) + .build(); + } +} diff --git a/src/main/java/com/team/buddyya/job/feed/like/FeedLikeItemReader.java b/src/main/java/com/team/buddyya/job/feed/like/FeedLikeItemReader.java new file mode 100644 index 00000000..a2bc74b0 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/like/FeedLikeItemReader.java @@ -0,0 +1,43 @@ +package com.team.buddyya.job.feed.like; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicInteger; +import org.springframework.batch.item.ItemReader; +import org.springframework.jdbc.core.JdbcTemplate; + +public class FeedLikeItemReader implements ItemReader { + + private final JdbcTemplate jdbcTemplate; + private final int totalCount; + private final AtomicInteger counter = new AtomicInteger(0); + private final ThreadLocalRandom random = ThreadLocalRandom.current(); + + private Long minFeedId; + private Long maxFeedId; + private Long minStudentId; + private Long maxStudentId; + + public FeedLikeItemReader(JdbcTemplate jdbcTemplate, int totalCount) { + this.jdbcTemplate = jdbcTemplate; + this.totalCount = totalCount; + } + + @Override + public FeedLikeJobDTO read() { + if (minFeedId == null) { + this.minFeedId = jdbcTemplate.queryForObject("SELECT MIN(id) FROM feed", Long.class); + this.maxFeedId = jdbcTemplate.queryForObject("SELECT MAX(id) FROM feed", Long.class); + this.minStudentId = jdbcTemplate.queryForObject("SELECT MIN(id) FROM student", Long.class); + this.maxStudentId = jdbcTemplate.queryForObject("SELECT MAX(id) FROM student", Long.class); + } + if (counter.getAndIncrement() >= totalCount) { + return null; + } + long randomFeedId = random.nextLong(minFeedId, maxFeedId + 1); + long randomStudentId = random.nextLong(minStudentId, maxStudentId + 1); + return FeedLikeJobDTO.builder() + .feedId(randomFeedId) + .studentId(randomStudentId) + .build(); + } +} diff --git a/src/main/java/com/team/buddyya/job/feed/like/FeedLikeJobDTO.java b/src/main/java/com/team/buddyya/job/feed/like/FeedLikeJobDTO.java new file mode 100644 index 00000000..4984d35d --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/like/FeedLikeJobDTO.java @@ -0,0 +1,11 @@ +package com.team.buddyya.job.feed.like; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class FeedLikeJobDTO { + private Long feedId; + private Long studentId; +} diff --git a/src/main/java/com/team/buddyya/job/student/StudentItemReader.java b/src/main/java/com/team/buddyya/job/student/StudentItemReader.java index 329b2978..5437b9e1 100644 --- a/src/main/java/com/team/buddyya/job/student/StudentItemReader.java +++ b/src/main/java/com/team/buddyya/job/student/StudentItemReader.java @@ -22,7 +22,6 @@ public StudentItemReader(JdbcTemplate jdbcTemplate, int totalCount) { @Override public StudentJobDTO read() { if (minUniversityId == null) { - // University 테이블의 데이터 존재 여부 및 ID 범위 초기화 if (jdbcTemplate.queryForObject("SELECT COUNT(1) FROM university", Long.class) == 0) { throw new IllegalStateException("Prerequisite data (University) is missing."); } @@ -35,6 +34,10 @@ public StudentJobDTO read() { int index = counter.incrementAndGet(); long randomUniversityId = ThreadLocalRandom.current().nextLong(minUniversityId, maxUniversityId + 1); String gender = (index % 2 == 0) ? "MALE" : "FEMALE"; + int profileImageIndex = ThreadLocalRandom.current().nextInt(1, 9); + String profileImageUrl = String.format( + "https://buddyya.s3.ap-northeast-2.amazonaws.com/default-profile-image/image__%d.png", + profileImageIndex); return StudentJobDTO.builder() .phoneNumber("010" + String.format("%08d", index)) .name("student" + index) From ff0a32080ec92fc75ebc24bf9c9b6826614689af Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Sat, 6 Sep 2025 14:23:53 +0900 Subject: [PATCH 09/10] =?UTF-8?q?feat:=20=EC=A2=8B=EC=95=84=EC=9A=94,=20?= =?UTF-8?q?=EB=B6=81=EB=A7=88=ED=81=AC=20Unique=20=EC=A0=9C=EC=95=BD?= =?UTF-8?q?=EC=9D=B4=20=EA=B1=B8=EB=A6=AC=EB=A9=B4=20=EB=8B=A4=EC=9D=8C?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=84=98=EC=96=B4=EA=B0=80=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20(#365)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- be-submodule | 2 +- src/main/java/com/team/buddyya/job/SeedDataJobConfig.java | 4 ++-- .../buddyya/job/feed/bookmark/BookmarkInsertJobConfig.java | 1 + .../team/buddyya/job/feed/like/FeedLikeInsertJobConfig.java | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/be-submodule b/be-submodule index b1676977..9f05be11 160000 --- a/be-submodule +++ b/be-submodule @@ -1 +1 @@ -Subproject commit b16769770a38ca7a680f3e2bf1700003aa0dd411 +Subproject commit 9f05be11bb927fae0d05cdb498c938fe1859abcd diff --git a/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java b/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java index f1d555db..730b8e38 100644 --- a/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java +++ b/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java @@ -26,8 +26,8 @@ public class SeedDataJobConfig { public static final int STUDENT_COUNT = 100_000; public static final int FEED_COUNT = 1_000_000; public static final int COMMENT_COUNT = 100_000; - public static final int LIKE_COUNT = FEED_COUNT * 5; - public static final int BOOKMARK_COUNT = FEED_COUNT * 2; + public static final int LIKE_COUNT = 500_000; + public static final int BOOKMARK_COUNT = 500_000; private final JobRepository jobRepository; private final Step studentInsertStep; diff --git a/src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkInsertJobConfig.java b/src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkInsertJobConfig.java index d13e1e75..837efb9c 100644 --- a/src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkInsertJobConfig.java +++ b/src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkInsertJobConfig.java @@ -47,6 +47,7 @@ public JdbcBatchItemWriter bookmarkItemWriter() { .dataSource(dataSource) .sql(sql) .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) + .assertUpdates(false) .build(); } } diff --git a/src/main/java/com/team/buddyya/job/feed/like/FeedLikeInsertJobConfig.java b/src/main/java/com/team/buddyya/job/feed/like/FeedLikeInsertJobConfig.java index f9a4178d..49311f8d 100644 --- a/src/main/java/com/team/buddyya/job/feed/like/FeedLikeInsertJobConfig.java +++ b/src/main/java/com/team/buddyya/job/feed/like/FeedLikeInsertJobConfig.java @@ -47,6 +47,7 @@ public JdbcBatchItemWriter feedLikeItemWriter() { .dataSource(dataSource) .sql(sql) .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) + .assertUpdates(false) .build(); } } From 053fc41e691c9ff6b24443708a73e0c056a51632 Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Sat, 6 Sep 2025 14:24:47 +0900 Subject: [PATCH 10/10] =?UTF-8?q?chore:=20submodule=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- be-submodule | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/be-submodule b/be-submodule index 9f05be11..ea2f150a 160000 --- a/be-submodule +++ b/be-submodule @@ -1 +1 @@ -Subproject commit 9f05be11bb927fae0d05cdb498c938fe1859abcd +Subproject commit ea2f150a7b59ba2edf722c16c675ee90113128f4