ํ์ : PM(1) / Design(1) / Frontend(2) / Backend(3)
๊ธฐ๊ฐ : 2024.03 ~ 2025.03
๋งํฌ : https://github.com/M-ung/MoodBuddy_Server
์๋น์ค ๋ด์ฉ : ์ฌ์ฉ์๊ฐ ์์ฑํ ์ผ๊ธฐ๋ฅผ ๋ฐํ์ผ๋ก ๊ฐ์ ๋ถ์ํ๋ ์น ์๋น์ค
์ํต : GitHub, Slack, Notion, Discord
์ฐ๋ฆฌ ์๋น์ค 'MoodBuddy" ์๋ ํน๋ณํ ๊ธฐ๋ฅ์ด ์๋ค. ๋ฐ๋ก "์ฟผ๋ํฐ์์ด(QuddyTI)" ์ด๋ค.
์ฟผ๋ํฐ์์ด(QuddyTI)๋?
๋จ์ํ MBTI๋ฅผ ์๊ฐํ๋ฉด ๋๋ค.
์ฌ์ฉ์๊ฐ ํ ๋ฌ ๋์ ์์ฑํ ์ผ๊ธฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก "์ผ๊ธฐ ์์ฑ ํ์, ์ผ๊ธฐ ๋ถ์์ ํตํด ๋์จ ๊ฐ์ , ์ฃผ์ " ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ผ๊ธฐ ์ฑ๊ฒฉ ์ ํ์ ํ๋ณํด์ฃผ๋ ์๋น์ค์ด๋ค.
์ด๋ ํ ๋ฌ ๊ธฐ๋ฐ์ผ๋ก ๊ฒฐ๊ณผ๊ฐ ๋์์ผ ํ๊ธฐ ๋๋ฌธ์ ๋งค๋ฌ 00์์ ์ฟผ๋ํฐ์์ด(QuddyTI) ์์ฑ(ํ์ฌ ๋ฌ) ๋ฐ ์์ (์ง๋ ๋ฌ)์ด ๋ฐ์ํ๋ค.
์ฒ์ 1์ฐจ ๋ฐฐํฌ ๋๋ ๋จ์ํ ์ค์ผ์ค๋ฌ๋ฅผ ์ฌ์ฉํด์ ์๋์ ๊ฐ์ด ๊ตฌํํ๋ค.
@Component
@RequiredArgsConstructor
public class QuddyTIScheduler {
private final QuddyTIFacade quddyTIFacade;
@Scheduled(cron = "0 0 0 1 * ?")
@Transactional
public void aggregateAndSaveDiaryData() {
quddyTIFacade.createAndUpadteQuddyTI();
}
}
์ค์ผ์ค๋ฌ๋ฅผ ๋๋ ค ๋งค๋ฌ 00์์ ๋์ํ๋๋ก ๊ตฌํํ๋ค.
ํ์ง๋ง ์๋์ ๊ฐ์ ๋ฌธ์ ์ ๋ค์ด ๋ฐ์ํ๋ค.
1. ํ ๋ฒ์ ๋ชจ๋ ์ฌ์ฉ์์ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ ค๋ค ๋ณด๋ ๋ฐ์ดํฐ๊ฐ ๋ง์์ง์๋ก ์ฟผ๋ฆฌ ๋ถํ ์ฆ๊ฐ
2. ํธ๋์ญ์
์ด ๊ธธ์ด์ง๊ณ , ์ ์ฒด ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์๊ฐ์ด ๋์ด๋๋ฉด์ ์๋ฒ ๋ถํ ๋ฐ์
3. ์ฌ์ฉ์๊ฐ ๋ง์์ง์๋ก ๋ฐ์ดํฐ ์ฆ๊ฐ๋ก ์ธํด ํ๋์ ์ค์ผ์ค๋ฌ์์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด ์ ์ ๋ถ๋ด์ค๋ฌ์์ง
๊ทธ๋์ ์ ๋ฌธ์ ๋ค์ ํด๊ฒฐํ๊ธฐ ์ํด "Spring Batch" ๋ฅผ ์ฐ๋ฆฌ ์๋น์ค์ ๋์ ํ๊ธฐ๋ก ํ๋ค.
Spring Batch๋?
Spring Batch๋ ๋๋์ ๋ฐ์ดํฐ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋๋ก ์ค๊ณ๋ ๋ฐฐ์น ์ฒ๋ฆฌ ํ๋ ์์ํฌ๋ค.
์ค์ผ์ค๋ฌ๊ฐ ๋จ์ ๋ฐ๋ณต ์คํ์ ํ๋ ๊ฒ๊ณผ ๋ฌ๋ฆฌ, ๋๋ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ๋๋์ด ์ฒ๋ฆฌํ๊ณ , ์ฌ์๋ ๋ฐ ์คํจ ๋ณต๊ตฌ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
Spring Batch์ ํต์ฌ ๊ฐ๋
1. Job: ํ๋์ ๋ฐฐ์น ์์
๋จ์
2. Step: Job์ ๊ตฌ์ฑํ๋ ๊ฐ๋ณ ๋จ๊ณ
3. ItemReader: ๋ฐ์ดํฐ๋ฅผ ์ฝ๋ ์ญํ
4. ItemProcessor: ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ๋ ์ญํ
5. ItemWriter: ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ์ญํ
์ "Spring Batch" ๋ฅผ ์ ์ฉํ๊ฒ ๋๋ฉด ์๋์ ๊ฐ์ ๊ธฐ๋ ํจ๊ณผ๋ฅผ ์ป์ ์ ์๋ค.
1. ์ฑ๋ฅ ๊ฐ์ : ์์ ๋จ์๋ก ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ์ฌ DB ๋ถํ ๊ฐ์
2. ์์ ์ฑ ํฅ์: ์คํจํ ์์
์ ์๋์ผ๋ก ์ฌ์๋ ๊ฐ๋ฅ
๋จผ์ ๊ธฐ์กด ์ค์ผ์ค๋ฌ๋ Spring Batch์ ํธ๋ฆฌ๊ฑฐ ์ญํ ์ ํ ์ ์๊ฒ ๋ณ๊ฒฝํด ์ฃผ์๋ค.
๐ QuddyTIBatchScheduler
@Component
@RequiredArgsConstructor
public class QuddyTIBatchScheduler {
private final JobLauncher jobLauncher;
private final Job quddyTIJob;
@Scheduled(cron = "0 0 0 1 * ?")
public void runBatchJob() throws JobExecutionException {
JobParameters jobParameters = new JobParametersBuilder()
.addLong("time", System.currentTimeMillis())
.toJobParameters();
jobLauncher.run(quddyTIJob, jobParameters);
}
}
๋งค์ 00์์ ์ ์ค์ผ์ค๋ฌ๋ฅผ ๋์์ํจ ํ Spring Batch๊ฐ ๋์ํ๊ฒ ๋ง๋ค์ด์คฌ๋ค.
๊ทธ๋ฆฌ๊ณ Spring Batch ์๋๋ฆฌ์ค๋ ์๋์ ๊ฐ๊ฒ ์ค๊ณํด ์ฃผ์๋ค.
๊ทธ๋ฆฌ๊ณ ์ ๊ณผ์ ์ ์ฒ์์๋ ๋จ์ํ Spring Data JPA๋ฅผ ํ์ฉํด์ ์์ฑ, ์์ , ์กฐํ๋ฅผ ํด๊ฒฐํ๋ ค๊ณ ํ๋ค.
ํ์ง๋ง Spring Data JPA๋ฅผ ํ์ฉํ ๋ฐฉ์์๋ ์๋์ ๊ฐ์ ๋ฌธ์ ์ ๋ค์ด ์์๋ค.
1. ๋๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์ ์ฑ๋ฅ ์ ํ
2. JPA์ ๊ธฐ๋ณธ์ ์ธ Bulk Update/Insert์ ํ๊ณ
๊ทธ๋์ Spring Data JPA ๋ณด๋ค JDBC ๋ฐฉ์์ผ๋ก ์์ฑ, ์์ , ์กฐํ๋ฅผ ๊ตฌํํ์๋ค.
์ฝ๋๋ ์๋์ ๊ฐ๋ค.
๐ QuddyTIBatchConfig.java
@Configuration
@EnableBatchProcessing
@RequiredArgsConstructor
public class QuddyTIBatchConfig {
private final QuddyTIBatchJDBCRepository quddyTIBatchJDBCRepository;
private final DiaryCountService diaryCountService;
private final JobRepository jobRepository;
private final DataSource dataSource;
private final PlatformTransactionManager transactionManager;
@Bean
public Job quddyTIJob(Step updateUserStatusStep) {
return new JobBuilder("quddyTIJob", jobRepository)
.start(updateUserStatusStep)
.build();
}
@Bean
public Step quddyTIStep(CompositeItemWriter<QuddyTI> compositeWriter) {
return new StepBuilder("quddyTIStep", jobRepository)
.<Long, QuddyTI>chunk(100, transactionManager)
.reader(userIdReader())
.processor(compositeProcessor())
.writer(compositeWriter)
.build();
}
@Bean
public JdbcCursorItemReader<Long> userIdReader() {
return new JdbcCursorItemReaderBuilder<Long>()
.dataSource(dataSource)
.name("userIdReader")
.sql("SELECT user_id FROM user WHERE deleted = 0 AND user_role = 'ROLE_USER'")
.rowMapper((rs, rowNum) -> rs.getLong("user_id"))
.build();
}
@Bean
public CompositeItemProcessor<Long, QuddyTI> compositeProcessor() {
CompositeItemProcessor<Long, QuddyTI> processor = new CompositeItemProcessor<>();
processor.setDelegates(Arrays.asList(quddyTICreator(), quddyTIUpdater()));
return processor;
}
@Bean
public ItemProcessor<Long, QuddyTI> quddyTICreator() {
return userId -> QuddyTI.of(userId, DateUtil.formatYear(LocalDate.now()), DateUtil.formatMonth(LocalDate.now()));
}
@Bean
public ItemProcessor<Long, QuddyTI> quddyTIUpdater() {
return userId -> {
LocalDate[] dates = DateUtil.getLastMonthDates();
Map<DiaryEmotion, Long> emotionCounts = diaryCountService.getEmotionCountsByDate(dates);
Map<DiarySubject, Long> subjectCounts = diaryCountService.getSubjectCountsByDate(dates);
QuddyTI quddyTI = quddyTIBatchJDBCRepository.findQuddyTIByUserIdAndDate(userId, DateUtil.formatYear(dates[0]), DateUtil.formatMonth(dates[1]));
quddyTI.update(emotionCounts, subjectCounts);
return quddyTI;
};
}
@Bean
public CompositeItemWriter<QuddyTI> compositeWriter() {
List<ItemWriter<? super QuddyTI>> writers = List.of(saveQuddyTI(), updateQuddyTI());
CompositeItemWriter<QuddyTI> writer = new CompositeItemWriter<>();
writer.setDelegates(writers);
return writer;
}
@Bean
public JdbcBatchItemWriter<QuddyTI> saveQuddyTI() {
return new JdbcBatchItemWriterBuilder<QuddyTI>()
.dataSource(dataSource)
.sql("INSERT INTO quddy_ti (user_id, quddy_ti_year, quddy_ti_month, diary_frequency, daily_count, growth_count, " +
"emotion_count, travel_count, happiness_count, anger_count, disgust_count, fear_count, neutral_count, " +
"sadness_count, surprise_count, quddy_ti_type, mood_buddy_status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.beanMapped()
.itemPreparedStatementSetter(new ItemPreparedStatementSetter<QuddyTI>() {
@Override
public void setValues(QuddyTI item, PreparedStatement ps) throws SQLException {
ps.setLong(1, item.getUserId());
ps.setString(2, item.getQuddyTIYear());
ps.setString(3, item.getQuddyTIMonth());
ps.setInt(4, item.getDiaryFrequency());
ps.setInt(5, item.getDailyCount());
ps.setInt(6, item.getGrowthCount());
ps.setInt(7, item.getEmotionCount());
ps.setInt(8, item.getTravelCount());
ps.setInt(9, item.getHappinessCount());
ps.setInt(10, item.getAngerCount());
ps.setInt(11, item.getDisgustCount());
ps.setInt(12, item.getFearCount());
ps.setInt(13, item.getNeutralCount());
ps.setInt(14, item.getSadnessCount());
ps.setInt(15, item.getSurpriseCount());
ps.setString(16, item.getQuddyTIType());
ps.setString(17, item.getMoodBuddyStatus().name());
}
})
.build();
}
@Bean
public JdbcBatchItemWriter<QuddyTI> updateQuddyTI() {
return new JdbcBatchItemWriterBuilder<QuddyTI>()
.dataSource(dataSource)
.sql("UPDATE quddy_ti SET diary_frequency = ?, daily_count = ?, growth_count = ?, emotion_count = ?, " +
"travel_count = ?, happiness_count = ?, anger_count = ?, disgust_count = ?, fear_count = ?, neutral_count = ?, " +
"sadness_count = ?, surprise_count = ?, quddy_ti_type = ? WHERE id = ? AND user_id = ? AND quddy_ti_year = ? AND quddy_ti_month = ?")
.beanMapped()
.itemPreparedStatementSetter(new ItemPreparedStatementSetter<QuddyTI>() {
@Override
public void setValues(QuddyTI item, PreparedStatement ps) throws SQLException {
ps.setInt(1, item.getDiaryFrequency());
ps.setInt(2, item.getDailyCount());
ps.setInt(3, item.getGrowthCount());
ps.setInt(4, item.getEmotionCount());
ps.setInt(5, item.getTravelCount());
ps.setInt(6, item.getHappinessCount());
ps.setInt(7, item.getAngerCount());
ps.setInt(8, item.getDisgustCount());
ps.setInt(9, item.getFearCount());
ps.setInt(10, item.getNeutralCount());
ps.setInt(11, item.getSadnessCount());
ps.setInt(12, item.getSurpriseCount());
ps.setString(13, item.getQuddyTIType());
ps.setLong(14, item.getId());
ps.setLong(15, item.getUserId());
ps.setString(16, item.getQuddyTIYear());
ps.setString(17, item.getQuddyTIMonth());
}
})
.build();
}
}
ํ์ง๋ง ์์ฒ๋ผ ๊ตฌํํ์ ๋, ์๋ฌ๋ ๋ง์ด ๋ฐ์ํ๊ณ ํ๋์ Reader์ ๋ ๊ฐ์ Processor, Writer๋ฅผ ๊ด๋ฆฌํ๊ธฐ์ ์ด๋ ค์ ๋ค. ๋ ํ ๋ฒ์ ๋ง์ DB ์ ๊ทผ์ ํ๊ธฐ์๋ ๋ถํ๊ฐ ํด ๊ฒ ๊ฐ์๋ค.
๊ทธ๋์ ์ด๋ฅผ ๋ ๊ฐ๋ก ๋๋๊ธฐ๋ก ํ๋ค. ์๋๋ฆฌ์ค๋ ์๋์ ๊ฐ๊ฒ ์ค๊ณํด ์ฃผ์๋ค.
์ค์ผ์ค๋ฌ๋ ๋ ๊ฐ๋ก ๋๋ด๋๋ผ๋ ๊ฐ์ ์๊ฐ์ ์คํํ๋ฉด ์์ ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๊ธฐ ๋๋ฌธ์ ์์ฑ๊ณผ ์์ ์ค์ผ์ค๋ฌ๋ฅผ ๊ฐ๊ฐ ๋ค๋ฅธ ๋งค๋ฌ ๋ง์ผ๊ณผ 1์ผ๋ก ๋ถ๋ฆฌํด์ ์คํํ๋๋ก ์ค๊ณํ๋ค.
๐ ์ฟผ๋ํฐ์์ด ์์ฑ
1. ๋งค๋ฌ ๋ง ์ผ 23์ 58๋ถ์ ์ค์ผ์ค๋ฌ๋ฅผ ํตํด ํธ๋ฆฌ๊ฑฐ๋ฅผ ๋ฐ์ํ๋ค.
2. Job -> Step ์์ผ๋ก ์คํํ๋ค.
3. Reader์์ ํ๋ ์ค์ธ ์ฌ์ฉ์ ์ ์ฒด๋ฅผ ์กฐํํด ์จ๋ค.
4. Processor์์ ์ฌ์ฉ์๋ค userId๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฟผ๋ํฐ์์ด ์์ฑ์ ์ค๋นํ๋ค.
5. ๋ง์ง๋ง์ผ๋ก Writer์์ ์ฟผ๋ํฐ์์ด ์์ฑ์ ํ๋ค.
๐ ์ฟผ๋ํฐ์์ด ์์
1. ๋งค๋ฌ 1์ผ 00์์ ์ค์ผ์ค๋ฌ๋ฅผ ํตํด ํธ๋ฆฌ๊ฑฐ๋ฅผ ๋ฐ์ํ๋ค.
2. Job -> Step ์์ผ๋ก ์คํํ๋ค.
3. Reader์์ ์ง๋๋ฌ ์ฟผ๋ํฐ์์ด ์ ์ฒด๋ฅผ ์กฐํํด ์จ๋ค.
4. Processor์์ ์ฟผ๋ํฐ์์ด quddy_ti_id๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฟผ๋ํฐ์์ด ์์ ์ ์ค๋นํ๋ค.
5. ๋ง์ง๋ง์ผ๋ก Writer์์ ์ฟผ๋ํฐ์์ด ์์ ์ ํ๋ค.
์๋๋ฆฌ์ค์ ๋ง๊ฒ ๊ตฌํํ ์ฝ๋๋ ์๋์ ๊ฐ๋ค.
๐ QuddyTICreateBatchScheduler.java
@Component
@RequiredArgsConstructor
public class QuddyTICreateBatchScheduler {
private final JobLauncher jobLauncher;
private final Job quddyTICreateJob;
@Scheduled(cron = "0 58 23 L * ?")
public void runBatchJob() throws JobExecutionException {
JobParameters jobParameters = new JobParametersBuilder()
.addLong("time", System.currentTimeMillis())
.toJobParameters();
jobLauncher.run(quddyTICreateJob, jobParameters);
}
}
๐ QuddyTICreateBatchConfig.java
@Configuration
@RequiredArgsConstructor
@EnableTransactionManagement
public class QuddyTICreateBatchConfig {
private final JobRepository jobRepository;
private final DataSource dataSource;
private final QuddyTIBatchJDBCRepository quddyTIBatchJDBCRepository;
private final PlatformTransactionManager transactionManager;
@Bean
public Job quddyTICreateJob(Step quddyTICreateStep) {
return new JobBuilder("quddyTICreateJob", jobRepository)
.start(quddyTICreateStep)
.build();
}
@Bean
public Step quddyTICreateStep() {
return new StepBuilder("quddyTICreateStep", jobRepository)
.<Long, QuddyTI>chunk(100, transactionManager)
.reader(userIdReader())
.processor(createQuddyTIProcessor())
.writer(saveQuddyTIWriter())
.build();
}
@Bean
public JdbcCursorItemReader<Long> userIdReader() {
return new JdbcCursorItemReaderBuilder<Long>()
.dataSource(dataSource)
.name("userIdReader")
.sql("SELECT id FROM user WHERE deleted = 0")
.rowMapper((rs, rowNum) -> rs.getLong("id"))
.fetchSize(100)
.saveState(false)
.build();
}
@Bean
public ItemProcessor<Long, QuddyTI> createQuddyTIProcessor() {
return userId -> QuddyTI.of(
userId,
DateUtil.formatYear(LocalDate.now()),
DateUtil.formatMonth(LocalDate.now())
);
}
@Bean
public ItemWriter<QuddyTI> saveQuddyTIWriter() {
return items -> quddyTIBatchJDBCRepository.bulkSave(items.getItems());
}
}
๐ QuddyTIUpdateBatchScheduler.java
@Component
@RequiredArgsConstructor
public class QuddyTIUpdateBatchScheduler {
private final JobLauncher jobLauncher;
private final Job quddyTIUpdateJob;
@Scheduled(cron = "0 0 0 1 * ?")
public void runBatchJob() throws JobExecutionException {
JobParameters jobParameters = new JobParametersBuilder()
.addLong("time", System.currentTimeMillis())
.toJobParameters();
jobLauncher.run(quddyTIUpdateJob, jobParameters);
}
}
๐ QuddyTIUpdateBatchConfig.java
@Configuration
@RequiredArgsConstructor
@EnableTransactionManagement
public class QuddyTIUpdateBatchConfig {
private final DataSource dataSource;
private final JobRepository jobRepository;
private final DiaryCountService diaryCountService;
private final QuddyTIBatchJDBCRepository quddyTIBatchJDBCRepository;
private final PlatformTransactionManager transactionManager;
@Bean
public Job quddyTIUpdateJob(Step quddyTIUpdateStep) {
return new JobBuilder("quddyTIUpdateJob", jobRepository)
.start(quddyTIUpdateStep)
.build();
}
@Bean
public Step quddyTIUpdateStep() {
return new StepBuilder("quddyTIUpdateStep", jobRepository)
.<QuddyTI, QuddyTI>chunk(100, transactionManager)
.reader(quddyTIReader())
.processor(findCountProcessor())
.writer(updateQuddyTIWriter())
.build();
}
@Bean
public JdbcCursorItemReader<QuddyTI> quddyTIReader() {
LocalDate[] dates = DateUtil.getLastMonthDates();
return new JdbcCursorItemReaderBuilder<QuddyTI>()
.dataSource(dataSource)
.name("quddyTIReader")
.sql(
"""
SELECT id, user_id, quddy_ti_year, quddy_ti_month, mood_buddy_status
FROM quddy_ti
WHERE quddy_ti_year = ? AND quddy_ti_month = ? AND mood_buddy_status = ?
"""
)
.queryArguments(
DateUtil.formatYear(dates[0]),
DateUtil.formatMonth(dates[1]),
MoodBuddyStatus.DIS_ACTIVE.name()
)
.rowMapper(this::mapQuddyTI)
.fetchSize(50)
.saveState(false)
.build();
}
private QuddyTI mapQuddyTI(ResultSet rs, int rowNum) throws SQLException {
return QuddyTI.builder()
.id(rs.getLong("id"))
.userId(rs.getLong("user_id"))
.quddyTIYear(rs.getString("quddy_ti_year"))
.quddyTIMonth(rs.getString("quddy_ti_month"))
.moodBuddyStatus(MoodBuddyStatus.valueOf(rs.getString("mood_buddy_status")))
.build();
}
@Bean
public ItemProcessor<QuddyTI, QuddyTI> findCountProcessor() {
return quddyTI -> {
LocalDate[] dates = DateUtil.getLastMonthDates();
Map<DiaryEmotion, Long> emotionCounts = diaryCountService.getEmotionCountsByDate(quddyTI.getUserId(), dates);
Map<DiarySubject, Long> subjectCounts = diaryCountService.getSubjectCountsByDate(quddyTI.getUserId(), dates);
quddyTI.update(emotionCounts, subjectCounts);
return quddyTI;
};
}
@Bean
public ItemWriter<QuddyTI> updateQuddyTIWriter() {
return items -> quddyTIBatchJDBCRepository.bulkUpdate(items.getItems());
}
}
์ ์์ ์ผ๋ก ๋์๊ฐ๋์ง ํ์ธํ๊ธฐ ์ํด ๋๋ฏธ ๋ฐ์ดํฐ๋ฅผ ๋ฃ๊ณ ์คํํ ๊ฒฐ๊ณผ, ์ ์์ ์ผ๋ก ๋์๊ฐ๋ ๊ฑธ ํ์ธํ ์ ์๋ค.
๋ฌธ์ ํด๊ฒฐ๋ก ์๋์ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ป์๋ค.
1. ์ฌ์ฉ์ 10,000๋ช
๊ธฐ์ค, ์ฟผ๋ํฐ์์ด ์์ฑ ํ๊ท ์ฝ 919ms, ์์ ํ๊ท ์ฝ 14,528ms๋ก ์ฑ๋ฅ ๊ฐ์ .
2. ์ค์ผ์ค๋ฌ โ Spring Batch ํธ๋ฆฌ๊ฑฐ ๋ฐฉ์์ผ๋ก ์ ํํ์ฌ ์ ์ง๋ณด์์ฑ์ ํฅ์.
3. JPA ๋์ JDBC ์ฌ์ฉ์ผ๋ก ๋ํฐ ์ฒดํน ์ค๋ฒํค๋๋ฅผ ์ ๊ฑฐํ๊ณ batch ์ฑ๋ฅ ์ต์ ํ.
4. ์ฟผ๋ํฐ์์ด ์์ฑ๊ณผ ์์ ์์
์ ๋ถ๋ฆฌํ์ฌ DB ๋ถํ๋ฅผ ๋ถ์ฐ
์ด๋ฒ Spring Batch ๋์ ์ ํตํด ์ค์ผ์ค๋ฌ ๊ธฐ๋ฐ์ ๋๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๋ฅผ ๊ฐ์ ํ๋ ๊ฒฝํ์ ํ ์ ์์๋ค.
์ฒ์ ์ ์ฉํ ์ค์ผ์ค๋ฌ ๋ฐฉ์์์๋ ๋๋ ๋ฐ์ดํฐ ์กฐํ ๋ฐ ์ ๋ฐ์ดํธ๋ก ์ธํด ํธ๋์ญ์ ์ด ๊ธธ์ด์ง๊ณ ์ฑ๋ฅ ์ ํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง๋ง, Spring Batch๋ฅผ ํ์ฉํ์ฌ ์์ ๋จ์๋ก ๋๋์ด ์ฒ๋ฆฌํ ์ ์์์ผ๋ฉฐ ์ฑ๋ฅ์ ๊ฐ์ ํ๊ณ ์์ ์ฑ์ ํ๋ณดํ ์ ์์๋ค.
์ ๊ฒฝํ๋ค์ ํตํด ๋๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๋ ๋จ์ํ ์ค์ผ์ค๋ฌ๋ก ์ ์ฉํด์ผ๊ฒ ๋ค๋ ์๊ฐ๋ณด๋จ Spring Batch์ ๊ฐ์ ํ๋ ์์ํฌ๋ฅผ ์ ์ฉํ๋ ๊ฒ์ด ํ์์ ์ด๋ผ๋ ์๊ฐ์ ํ๋ค.
๋ Spring Data JPA์ ํธ๋ฆฌ์ฑ์ผ๋ก ์ธํด ์ด์ ์์กดํ๊ธฐ๋ณด๋จ ์๊ณ ์ฐ๋ ๊ฒ์ด ์ค์ํ๋ค๋ ๊ฒ์ ๊นจ๋ฌ์๋ค. ๋ถํธํ๋๋ผ๋ ์ฑ๋ฅ์ ์ผ๋ก ๋จ์ด์ง๋ฉด JDBC์ ๊ฐ์ ๊ธฐ์ ์ ์ ์ฉํ ์ ์๋ ์ค๊ณ ๋ฅ๋ ฅ์ ๊ฐ์ถ ๊ฐ๋ฐ์๊ฐ ๋์ด์ผ๊ฒ ๋ค๊ณ ์๊ฐํ๋ค.