diff --git a/be-submodule b/be-submodule index b1676977..ea2f150a 160000 --- a/be-submodule +++ b/be-submodule @@ -1 +1 @@ -Subproject commit b16769770a38ca7a680f3e2bf1700003aa0dd411 +Subproject commit ea2f150a7b59ba2edf722c16c675ee90113128f4 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 +} 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..730b8e38 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/SeedDataJobConfig.java @@ -0,0 +1,51 @@ +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; +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, CommentInsertJobConfig.class, + FeedImageInsertJobConfig.class, FeedLikeInsertJobConfig.class, BookmarkInsertJobConfig.class}) +@Configuration +@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; + public static final int LIKE_COUNT = 500_000; + public static final int BOOKMARK_COUNT = 500_000; + + 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() { + return new JobBuilder("seedDataJob", jobRepository) + .start(studentInsertStep) + .next(feedInsertStep) + .next(commentInsertStep) + .next(feedImageInsertStep) + .next(feedLikeInsertStep) + .next(bookmarkInsertStep) + .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..6892735a --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/FeedInsertJobConfig.java @@ -0,0 +1,51 @@ +package com.team.buddyya.job.feed; + +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 FeedInsertJobConfig { + + private final JobRepository jobRepository; + private final PlatformTransactionManager platformTransactionManager; + private final DataSource dataSource; + 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 + 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())"; + 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..5baf3be7 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/FeedItemReader.java @@ -0,0 +1,60 @@ +package com.team.buddyya.job.feed; + +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 FeedItemReader implements ItemReader { + + private final JdbcTemplate jdbcTemplate; + private final int totalCount; + private final AtomicInteger counter = new AtomicInteger(0); + + private Long minStudentId; + private Long maxStudentId; + private Long minUniversityId; + private Long maxUniversityId; + + public FeedItemReader(JdbcTemplate jdbcTemplate, int totalCount) { + this.jdbcTemplate = jdbcTemplate; + this.totalCount = totalCount; + } + + @Override + 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); + } + + if (counter.get() >= totalCount) { + return null; + } + + 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) + .content("피드 내용입니다. " + currentCount) + .profileVisible(ThreadLocalRandom.current().nextBoolean()) + .studentId(randomStudentId) + .categoryId(randomCategoryId) + .universityId(randomUniversityId) + .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..ef5e3e4a --- /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 profileVisible; + private Long studentId; + private Long categoryId; + private Long universityId; +} 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..837efb9c --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/bookmark/BookmarkInsertJobConfig.java @@ -0,0 +1,53 @@ +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<>()) + .assertUpdates(false) + .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/comment/CommentInsertJobConfig.java b/src/main/java/com/team/buddyya/job/feed/comment/CommentInsertJobConfig.java new file mode 100644 index 00000000..9e5c56ce --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/comment/CommentInsertJobConfig.java @@ -0,0 +1,52 @@ +package com.team.buddyya.job.feed.comment; + +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.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 CommentInsertJobConfig { + + private final JobRepository jobRepository; + private final PlatformTransactionManager platformTransactionManager; + private final DataSource dataSource; + 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 + 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(); + } +} 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..9b26a946 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/comment/CommentItemReader.java @@ -0,0 +1,45 @@ +package com.team.buddyya.job.feed.comment; + +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 JdbcTemplate jdbcTemplate; + private final int totalCount; + private final AtomicInteger counter = new AtomicInteger(0); + + private Long minStudentId; + private Long maxStudentId; + private Long minFeedId; + private Long maxFeedId; + + public CommentItemReader(JdbcTemplate jdbcTemplate, int totalCount) { + this.jdbcTemplate = jdbcTemplate; + this.totalCount = totalCount; + } + + @Override + public CommentJobDTO read() throws Exception { + if (minFeedId == null) { + 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) + .content("Comment content " + currentCount) + .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; +} 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..49311f8d --- /dev/null +++ b/src/main/java/com/team/buddyya/job/feed/like/FeedLikeInsertJobConfig.java @@ -0,0 +1,53 @@ +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<>()) + .assertUpdates(false) + .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/StudentInsertJobConfig.java b/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java new file mode 100644 index 00000000..05de4d64 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/student/StudentInsertJobConfig.java @@ -0,0 +1,52 @@ +package com.team.buddyya.job.student; + +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.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 StudentInsertJobConfig { + + private final JobRepository jobRepository; + private final PlatformTransactionManager platformTransactionManager; + private final DataSource dataSource; + private static final int CHUNK_SIZE = 1000; + + @Bean + public Step studentInsertStep(ItemReader studentItemReader, + JdbcBatchItemWriter studentItemWriter) { + return new StepBuilder("studentInsertStep", jobRepository) + .chunk(CHUNK_SIZE, platformTransactionManager) + .reader(studentItemReader) + .writer(studentItemWriter) + .build(); + } + + @Bean + 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())"; + 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..5437b9e1 --- /dev/null +++ b/src/main/java/com/team/buddyya/job/student/StudentItemReader.java @@ -0,0 +1,55 @@ +package com.team.buddyya.job.student; + +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 StudentItemReader implements ItemReader { + + private final JdbcTemplate jdbcTemplate; + private final int totalCount; + private final AtomicInteger counter = new AtomicInteger(0); + + private Long minUniversityId; + private Long maxUniversityId; + + public StudentItemReader(JdbcTemplate jdbcTemplate, int totalCount) { + this.jdbcTemplate = jdbcTemplate; + this.totalCount = totalCount; + } + + @Override + public StudentJobDTO read() { + if (minUniversityId == null) { + 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); + } + 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"; + 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) + .country("ko") + .isCertificated(false) + .isKorean(true) + .isDeleted(false) + .universityId(randomUniversityId) + .role("STUDENT") + .gender(gender) + .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; +}