From b68c12b2c3c5ec8f221c07d37ec30388267a7798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=84=B8=EB=A6=BC?= <117977227+KwonSeRim2@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:23:35 +0900 Subject: [PATCH 1/9] =?UTF-8?q?FEAT:=20=EB=B0=B0=EC=B9=98=EC=97=90=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=A0=20XML=EC=9E=91=EC=84=B1(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../batch/job/IsaTaxSavingJobConfig.java | 49 ++++++++++ .../processor/IsaTaxSavingProcessor.java | 46 +++++++++ .../batch/reader/IsaTaxSavingReader.java | 33 +++++++ .../batch/scheduler/BatchScheduler.java | 45 +++++++++ .../batch/writer/IsaTaxSavingWriter.java | 26 +++++ .../domain/dto/UserProductQuarterData.java | 12 +++ .../domain/mapper/IsaTaxSavingMapper.java | 21 ++++ .../domain/vo/IsaTaxSavingHistoryVo.java | 17 ++++ .../planitbatch/global/util/DateUtils.java | 21 ++++ .../global/util/IsaTaxCalculator.java | 95 +++++++++++++++++++ .../resources/mapper/IsaTaxSavingMapper.xml | 69 ++++++++++++++ 11 files changed, 434 insertions(+) create mode 100644 src/main/java/woojooin/planitbatch/batch/job/IsaTaxSavingJobConfig.java create mode 100644 src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java create mode 100644 src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java create mode 100644 src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java create mode 100644 src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java create mode 100644 src/main/java/woojooin/planitbatch/domain/dto/UserProductQuarterData.java create mode 100644 src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java create mode 100644 src/main/java/woojooin/planitbatch/domain/vo/IsaTaxSavingHistoryVo.java create mode 100644 src/main/java/woojooin/planitbatch/global/util/DateUtils.java create mode 100644 src/main/java/woojooin/planitbatch/global/util/IsaTaxCalculator.java create mode 100644 src/main/resources/mapper/IsaTaxSavingMapper.xml diff --git a/src/main/java/woojooin/planitbatch/batch/job/IsaTaxSavingJobConfig.java b/src/main/java/woojooin/planitbatch/batch/job/IsaTaxSavingJobConfig.java new file mode 100644 index 0000000..79c2046 --- /dev/null +++ b/src/main/java/woojooin/planitbatch/batch/job/IsaTaxSavingJobConfig.java @@ -0,0 +1,49 @@ +package woojooin.planitbatch.batch.job; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.batch.core.step.tasklet.TaskletStep; +import org.springframework.batch.item.ItemReader; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import lombok.Data; +import lombok.RequiredArgsConstructor; +import woojooin.planitbatch.batch.processor.IsaTaxSavingProcessor; +import woojooin.planitbatch.batch.reader.IsaTaxSavingReader; +import woojooin.planitbatch.batch.writer.IsaTaxSavingWriter; +import woojooin.planitbatch.domain.dto.UserProductQuarterData; +import woojooin.planitbatch.domain.vo.IsaTaxSavingHistoryVo; + +@Configuration +@RequiredArgsConstructor +@EnableBatchProcessing +public class IsaTaxSavingJobConfig { + + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + private final IsaTaxSavingReader reader; + private final IsaTaxSavingProcessor processor; + private final IsaTaxSavingWriter writer; + + @Bean + public Job isaTaxSavingJob(@Qualifier("isaTaxReader") ItemReader reader) { + Step isaTaxSavingStep = stepBuilderFactory.get("isaTaxSavingStep") + .chunk(100) + .reader(reader) + .processor(processor) + .writer(writer) + .build(); + + return jobBuilderFactory.get("isaTaxSavingJob") + .start(isaTaxSavingStep) + .build(); + } + + + +} \ No newline at end of file diff --git a/src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java b/src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java new file mode 100644 index 0000000..c6f6bf5 --- /dev/null +++ b/src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java @@ -0,0 +1,46 @@ +package woojooin.planitbatch.batch.processor; + +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.item.ItemProcessor; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import woojooin.planitbatch.domain.dto.UserProductQuarterData; +import woojooin.planitbatch.domain.mapper.IsaTaxSavingMapper; +import woojooin.planitbatch.domain.vo.IsaTaxSavingHistoryVo; +import woojooin.planitbatch.global.util.DateUtils; +import woojooin.planitbatch.global.util.IsaTaxCalculator; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Component +@RequiredArgsConstructor +@EnableBatchProcessing +public class IsaTaxSavingProcessor implements ItemProcessor { + + private final IsaTaxSavingMapper mapper; + + @Override + public IsaTaxSavingHistoryVo process(UserProductQuarterData item) throws Exception { + BigDecimal totalProfit = item.getTotalProfit(); + Long memberId = item.getMemberId(); + + IsaTaxCalculator.IsaTaxResult result = IsaTaxCalculator.calculateTaxSaving(totalProfit, "general"); + + BigDecimal lastAccumulated = mapper.selectLatestAccumulatedSaving(memberId); + if (lastAccumulated == null) lastAccumulated = BigDecimal.ZERO; + + BigDecimal currentSaving = result.getTotalTaxSaved(); + BigDecimal newAccumulated = lastAccumulated.add(currentSaving); + + IsaTaxSavingHistoryVo history = new IsaTaxSavingHistoryVo(); + history.setMemberId(memberId); + history.setQuarter(DateUtils.getCurrentQuarter()); + history.setSavingAmount(currentSaving); + history.setAccumulatedSaving(newAccumulated); + history.setCreatedAt(LocalDateTime.now()); + + return history; + } +} diff --git a/src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java b/src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java new file mode 100644 index 0000000..69f25a6 --- /dev/null +++ b/src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java @@ -0,0 +1,33 @@ +package woojooin.planitbatch.batch.reader; + +import java.util.HashMap; + +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.batch.MyBatisPagingItemReader; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import woojooin.planitbatch.domain.dto.UserProductQuarterData; + +@Configuration +@RequiredArgsConstructor +public class IsaTaxSavingReader { + + private final SqlSessionFactory sqlSessionFactory; + + @Bean(name = "isaTaxReader") + @StepScope + public MyBatisPagingItemReader isaTaxReader() { + MyBatisPagingItemReader reader = new MyBatisPagingItemReader<>(); + reader.setSqlSessionFactory(sqlSessionFactory); + reader.setQueryId("woojooin.planitbatch.domain.mapper.IsaTaxSavingMapper.selectIsaProductProfitByMember"); + reader.setPageSize(100); + reader.setParameterValues(new HashMap<>()); // 파라미터 필요 없을 경우 빈 map + return reader; + } +} \ No newline at end of file diff --git a/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java b/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java new file mode 100644 index 0000000..7cb0aaf --- /dev/null +++ b/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java @@ -0,0 +1,45 @@ +package woojooin.planitbatch.batch.scheduler; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.*; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +@EnableBatchProcessing +@EnableScheduling +@Slf4j +public class BatchScheduler { + + private final JobLauncher jobLauncher; + private final JobExplorer jobExplorer; + private final Job isaTaxSavingJob; + + @Scheduled(cron = "0 * * * * ?") // 매 분 실행 (테스트용) + public void runIsaTaxSavingJob() throws Exception { + log.info("🔄 배치 job 실행 시도"); + + String jobName = isaTaxSavingJob.getName(); + + // 1. 최근 실행된 JobInstance 가져오기 + for (JobInstance instance : jobExplorer.getJobInstances(jobName, 0, 10)) { + for (JobExecution execution : jobExplorer.getJobExecutions(instance)) { + if (execution.isRunning()) { + log.warn("⚠️ Job '{}' 이(가) 아직 실행 중이므로, 새 실행을 건너뜁니다.", jobName); + return; + } + } + } + + // 2. 실행 + jobLauncher.run(isaTaxSavingJob, new JobParametersBuilder() + .addLong("time", System.currentTimeMillis()) // 항상 다른 파라미터로 실행 + .toJobParameters()); + } +} diff --git a/src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java b/src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java new file mode 100644 index 0000000..50efa19 --- /dev/null +++ b/src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java @@ -0,0 +1,26 @@ +package woojooin.planitbatch.batch.writer; + +import java.util.List; + +import org.springframework.batch.item.ItemWriter; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import woojooin.planitbatch.domain.mapper.IsaTaxSavingMapper; +import woojooin.planitbatch.domain.vo.IsaTaxSavingHistoryVo; + +@Component +@RequiredArgsConstructor +public class IsaTaxSavingWriter implements ItemWriter { + + private final IsaTaxSavingMapper mapper; + + @Override + public void write(List items) throws Exception { + for (IsaTaxSavingHistoryVo history : items) { + mapper.insertTaxSavingHistory(history); + } + } +} \ No newline at end of file diff --git a/src/main/java/woojooin/planitbatch/domain/dto/UserProductQuarterData.java b/src/main/java/woojooin/planitbatch/domain/dto/UserProductQuarterData.java new file mode 100644 index 0000000..64d0dfc --- /dev/null +++ b/src/main/java/woojooin/planitbatch/domain/dto/UserProductQuarterData.java @@ -0,0 +1,12 @@ +package woojooin.planitbatch.domain.dto; + + +import java.math.BigDecimal; +import lombok.Data; + +@Data +public class UserProductQuarterData { + private Long memberId; + private String quarter; + private BigDecimal totalProfit; +} diff --git a/src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java b/src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java new file mode 100644 index 0000000..284902a --- /dev/null +++ b/src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java @@ -0,0 +1,21 @@ +package woojooin.planitbatch.domain.mapper; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import woojooin.planitbatch.domain.dto.UserProductQuarterData; +import woojooin.planitbatch.domain.vo.IsaTaxSavingHistoryVo; + +@Mapper +public interface IsaTaxSavingMapper { + List selectIsaProductProfitByMember(); + + void insertTaxSavingHistory(IsaTaxSavingHistoryVo history); + + BigDecimal selectLatestAccumulatedSaving(@Param("memberId") Long memberId); +} diff --git a/src/main/java/woojooin/planitbatch/domain/vo/IsaTaxSavingHistoryVo.java b/src/main/java/woojooin/planitbatch/domain/vo/IsaTaxSavingHistoryVo.java new file mode 100644 index 0000000..ea5baf2 --- /dev/null +++ b/src/main/java/woojooin/planitbatch/domain/vo/IsaTaxSavingHistoryVo.java @@ -0,0 +1,17 @@ +package woojooin.planitbatch.domain.vo; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import lombok.Builder; +import lombok.Data; + +@Data +public class IsaTaxSavingHistoryVo { + private Long memberId; + private String quarter; + private BigDecimal savingAmount; + private BigDecimal accumulatedSaving; + private LocalDateTime createdAt; +} + diff --git a/src/main/java/woojooin/planitbatch/global/util/DateUtils.java b/src/main/java/woojooin/planitbatch/global/util/DateUtils.java new file mode 100644 index 0000000..c113a5b --- /dev/null +++ b/src/main/java/woojooin/planitbatch/global/util/DateUtils.java @@ -0,0 +1,21 @@ +package woojooin.planitbatch.global.util; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; + +public class DateUtils { + + //분기 계산 + public static String getCurrentQuarter() { + LocalDate now = LocalDate.now(); + int quarter = (now.getMonthValue() - 1) / 3 + 1; + return now.getYear() + "-Q" + quarter; + } + + public static String getQuarter(LocalDate date) { + int quarter = (date.getMonthValue() - 1) / 3 + 1; + return date.getYear() + "-Q" + quarter; + } + +} diff --git a/src/main/java/woojooin/planitbatch/global/util/IsaTaxCalculator.java b/src/main/java/woojooin/planitbatch/global/util/IsaTaxCalculator.java new file mode 100644 index 0000000..0cbe89b --- /dev/null +++ b/src/main/java/woojooin/planitbatch/global/util/IsaTaxCalculator.java @@ -0,0 +1,95 @@ +package woojooin.planitbatch.global.util; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +public class IsaTaxCalculator { + + /** + * ISA 계좌의 절세 효과를 계산한다. + * + * @param totalProfit ISA 계좌 전체 수익 + * @param userType "general" (일반형) 또는 "preferential" (서민형/청년형) + * @return IsaTaxResult (절세 이익 + 누적 절세) + */ + public static IsaTaxResult calculateTaxSaving(BigDecimal totalProfit, String userType) { + BigDecimal isaLimit = userType.equalsIgnoreCase("preferential") + ? new BigDecimal("4000000") + : new BigDecimal("2000000"); + + BigDecimal generalTaxRate = new BigDecimal("0.154"); // 일반 계좌 세율: 15.4% + BigDecimal isaOverLimitTaxRate = new BigDecimal("0.099"); // ISA 초과분 분리과세: 9.9% + + // 일반 계좌 기준 세금 + BigDecimal taxIfGeneralAccount = totalProfit.multiply(generalTaxRate); + + BigDecimal taxFreeAmount; // ISA 한도 내 금액 + BigDecimal taxableExcessAmount = BigDecimal.ZERO; + BigDecimal actualIsaTax = BigDecimal.ZERO; + + if (totalProfit.compareTo(isaLimit) <= 0) { + taxFreeAmount = totalProfit; + } else { + taxFreeAmount = isaLimit; + taxableExcessAmount = totalProfit.subtract(isaLimit); + actualIsaTax = taxableExcessAmount.multiply(isaOverLimitTaxRate); + } + + // 누적 절세 금액 = 일반 계좌 세금 - ISA 세금 + BigDecimal totalTaxSaved = taxIfGeneralAccount.subtract(actualIsaTax); + + return new IsaTaxResult( + taxFreeAmount.setScale(2, RoundingMode.DOWN), + taxableExcessAmount.setScale(2, RoundingMode.DOWN), + actualIsaTax.setScale(2, RoundingMode.DOWN), + totalTaxSaved.setScale(2, RoundingMode.DOWN) + ); + } + + public static class IsaTaxResult { + private BigDecimal taxFreeAmount; // 한도 내 비과세 금액 + private BigDecimal taxableExcessAmount; // 한도 초과 금액 + private BigDecimal actualIsaTax; // ISA에서 실제 낸 세금 (9.9%) + private BigDecimal totalTaxSaved; // 일반 계좌 대비 절세 금액 + + public IsaTaxResult(BigDecimal taxFreeAmount, BigDecimal taxableExcessAmount, + BigDecimal actualIsaTax, BigDecimal totalTaxSaved) { + this.taxFreeAmount = taxFreeAmount; + this.taxableExcessAmount = taxableExcessAmount; + this.actualIsaTax = actualIsaTax; + this.totalTaxSaved = totalTaxSaved; + } + + public BigDecimal getTaxFreeAmount() { + return taxFreeAmount; + } + + public BigDecimal getTaxableExcessAmount() { + return taxableExcessAmount; + } + + public BigDecimal getActualIsaTax() { + return actualIsaTax; + } + + public BigDecimal getTotalTaxSaved() { + return totalTaxSaved; + } + + // ISA 세금을 뺀 실제 수익 + public BigDecimal getActualProfitAmount(){ + BigDecimal actualAmount = taxableExcessAmount.subtract(actualIsaTax); + return taxFreeAmount.add(actualAmount); + } + + @Override + public String toString() { + return "IsaTaxResult{" + + "taxFreeAmount=" + taxFreeAmount + + ", taxableExcessAmount=" + taxableExcessAmount + + ", actualIsaTax=" + actualIsaTax + + ", totalTaxSaved=" + totalTaxSaved + + '}'; + } + } +} diff --git a/src/main/resources/mapper/IsaTaxSavingMapper.xml b/src/main/resources/mapper/IsaTaxSavingMapper.xml new file mode 100644 index 0000000..8656b3a --- /dev/null +++ b/src/main/resources/mapper/IsaTaxSavingMapper.xml @@ -0,0 +1,69 @@ + + + + + + + + + INSERT INTO isa_tax_saving_history ( + member_id, + quarter, + saving_amount, + accumulated_saving, + created_at + ) + VALUES + + (#{item.memberId}, #{item.quarter}, #{item.savingAmount}, #{item.accumulatedSaving}, #{item.createdAt}) + + + + + + + INSERT INTO isa_tax_saving_history (member_id, + quarter, + saving_amount, + accumulated_saving, + created_at) + VALUES (#{memberId}, + #{quarter}, + #{savingAmount}, + #{accumulatedSaving}, + NOW()) + + + + + + + + From 32106b97b1a89e48290948ea72ff0eedc8da30e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=84=B8=EB=A6=BC?= <117977227+KwonSeRim2@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:28:28 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20isa=EB=B0=B0=EC=B9=98=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=20mapper(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java b/src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java index 284902a..34dddb7 100644 --- a/src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java +++ b/src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java @@ -11,10 +11,11 @@ import woojooin.planitbatch.domain.dto.UserProductQuarterData; import woojooin.planitbatch.domain.vo.IsaTaxSavingHistoryVo; -@Mapper public interface IsaTaxSavingMapper { List selectIsaProductProfitByMember(); + void insertTaxSavingHistoryBatch(@Param("list") List histories); + void insertTaxSavingHistory(IsaTaxSavingHistoryVo history); BigDecimal selectLatestAccumulatedSaving(@Param("memberId") Long memberId); From bd723787588ece3498ab5ffe7cf78e3e7616cd4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=84=B8=EB=A6=BC?= <117977227+KwonSeRim2@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:31:20 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=EB=B0=B0=EC=B9=98=EC=9E=91?= =?UTF-8?q?=EC=97=85reader,writer,processor,job=EC=BD=94=EB=93=9C(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../planitbatch/batch/job/BatchConfig.java | 11 +++++++---- .../batch/processor/IsaTaxSavingProcessor.java | 3 +-- .../batch/reader/IsaTaxSavingReader.java | 1 - .../batch/writer/IsaTaxSavingWriter.java | 15 ++++++++++++++- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/main/java/woojooin/planitbatch/batch/job/BatchConfig.java b/src/main/java/woojooin/planitbatch/batch/job/BatchConfig.java index f74ce8e..61dc638 100644 --- a/src/main/java/woojooin/planitbatch/batch/job/BatchConfig.java +++ b/src/main/java/woojooin/planitbatch/batch/job/BatchConfig.java @@ -10,13 +10,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import lombok.RequiredArgsConstructor; + @Configuration @EnableBatchProcessing +@RequiredArgsConstructor public class BatchConfig { - @Autowired - private JobBuilderFactory jobs; - @Autowired - private StepBuilderFactory steps; + + private final JobBuilderFactory jobs; + + private final StepBuilderFactory steps; @Bean public Step sampleStep() { diff --git a/src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java b/src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java index c6f6bf5..8c97b94 100644 --- a/src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java +++ b/src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java @@ -16,7 +16,6 @@ @Component @RequiredArgsConstructor -@EnableBatchProcessing public class IsaTaxSavingProcessor implements ItemProcessor { private final IsaTaxSavingMapper mapper; @@ -36,7 +35,7 @@ public IsaTaxSavingHistoryVo process(UserProductQuarterData item) throws Excepti IsaTaxSavingHistoryVo history = new IsaTaxSavingHistoryVo(); history.setMemberId(memberId); - history.setQuarter(DateUtils.getCurrentQuarter()); + history.setQuarter(item.getQuarter()); history.setSavingAmount(currentSaving); history.setAccumulatedSaving(newAccumulated); history.setCreatedAt(LocalDateTime.now()); diff --git a/src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java b/src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java index 69f25a6..aa84de7 100644 --- a/src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java +++ b/src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java @@ -27,7 +27,6 @@ public MyBatisPagingItemReader isaTaxReader() { reader.setSqlSessionFactory(sqlSessionFactory); reader.setQueryId("woojooin.planitbatch.domain.mapper.IsaTaxSavingMapper.selectIsaProductProfitByMember"); reader.setPageSize(100); - reader.setParameterValues(new HashMap<>()); // 파라미터 필요 없을 경우 빈 map return reader; } } \ No newline at end of file diff --git a/src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java b/src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java index 50efa19..a7ec365 100644 --- a/src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java +++ b/src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java @@ -20,7 +20,20 @@ public class IsaTaxSavingWriter implements ItemWriter { @Override public void write(List items) throws Exception { for (IsaTaxSavingHistoryVo history : items) { - mapper.insertTaxSavingHistory(history); + try { + mapper.insertTaxSavingHistory(history); + } catch (Exception e) { + // 개별 실패 로깅 + System.err.println("[Insert 실패] memberId: " + history.getMemberId() + ", quarter: " + history.getQuarter()); + e.printStackTrace(); + // 실패 아이템 별도 처리 가능 (예: 실패 리스트에 저장, 알림 등) + } } } + + //MyBatis 다중 Insert + // @Override + // public void write(List items) throws Exception { + // mapper.insertTaxSavingHistoryBatch((List) items); + // } } \ No newline at end of file From 428d7587ef2192c8efe227bd6ddc0f3dcab7067d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=84=B8=EB=A6=BC?= <117977227+KwonSeRim2@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:32:58 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=EB=B0=B0=EC=B9=98=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EC=8A=A4=EC=BC=80=EC=A4=84=EB=9F=AC=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 배치 작업을 예약하여 자동으로 실행하는 스케줄러 구현 --- .../planitbatch/batch/scheduler/BatchScheduler.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java b/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java index 7cb0aaf..feaee13 100644 --- a/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java +++ b/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java @@ -28,15 +28,24 @@ public void runIsaTaxSavingJob() throws Exception { String jobName = isaTaxSavingJob.getName(); // 1. 최근 실행된 JobInstance 가져오기 + long minIntervalMillis = 5 * 60 * 1000; // 5분 for (JobInstance instance : jobExplorer.getJobInstances(jobName, 0, 10)) { for (JobExecution execution : jobExplorer.getJobExecutions(instance)) { if (execution.isRunning()) { - log.warn("⚠️ Job '{}' 이(가) 아직 실행 중이므로, 새 실행을 건너뜁니다.", jobName); + log.warn("⚠️ Job '{}' si still running!!!!!!!!!, jump!!!!!!.", jobName); return; } + if (execution.getEndTime() != null) { + long elapsed = System.currentTimeMillis() - execution.getEndTime().getTime(); + if (elapsed < minIntervalMillis) { + log.warn("⚠️ Job '{}' recently ended!!!!!!!!!! run{}s after!!!!!!!.", jobName, minIntervalMillis/1000); + return; + } + } } } + // 2. 실행 jobLauncher.run(isaTaxSavingJob, new JobParametersBuilder() .addLong("time", System.currentTimeMillis()) // 항상 다른 파라미터로 실행 From df40b1d60a64507f9277abe0a3f93b3e78aeeeac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=84=B8=EB=A6=BC?= <117977227+KwonSeRim2@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:37:41 +0900 Subject: [PATCH 5/9] =?UTF-8?q?fix:=20read,=20write=20=EC=95=88=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # src/main/java/woojooin/planitbatch/global/config/DatabaseConfig.java --- .../batch/job/IsaTaxSavingJobConfig.java | 1 - .../processor/IsaTaxSavingProcessor.java | 38 +-- .../batch/reader/IsaTaxSavingReader.java | 4 + .../batch/scheduler/BatchScheduler.java | 57 ++--- .../batch/writer/IsaTaxSavingWriter.java | 36 +-- .../domain/mapper/IsaTaxSavingMapper.java | 5 +- .../domain/vo/IsaTaxSavingHistoryVo.java | 11 +- .../global/config/DatabaseConfig.java | 229 +++++++++--------- .../global/util/IsaTaxCalculator.java | 74 ++---- .../resources/mapper/IsaTaxSavingMapper.xml | 81 +++---- 10 files changed, 232 insertions(+), 304 deletions(-) diff --git a/src/main/java/woojooin/planitbatch/batch/job/IsaTaxSavingJobConfig.java b/src/main/java/woojooin/planitbatch/batch/job/IsaTaxSavingJobConfig.java index 79c2046..d7c804b 100644 --- a/src/main/java/woojooin/planitbatch/batch/job/IsaTaxSavingJobConfig.java +++ b/src/main/java/woojooin/planitbatch/batch/job/IsaTaxSavingJobConfig.java @@ -21,7 +21,6 @@ @Configuration @RequiredArgsConstructor -@EnableBatchProcessing public class IsaTaxSavingJobConfig { private final JobBuilderFactory jobBuilderFactory; diff --git a/src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java b/src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java index 8c97b94..b258ec8 100644 --- a/src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java +++ b/src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java @@ -1,45 +1,33 @@ package woojooin.planitbatch.batch.processor; -import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import java.math.BigDecimal; + import org.springframework.batch.item.ItemProcessor; import org.springframework.stereotype.Component; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import woojooin.planitbatch.domain.dto.UserProductQuarterData; -import woojooin.planitbatch.domain.mapper.IsaTaxSavingMapper; import woojooin.planitbatch.domain.vo.IsaTaxSavingHistoryVo; -import woojooin.planitbatch.global.util.DateUtils; import woojooin.planitbatch.global.util.IsaTaxCalculator; -import java.math.BigDecimal; -import java.time.LocalDateTime; - @Component @RequiredArgsConstructor +@Slf4j public class IsaTaxSavingProcessor implements ItemProcessor { - - private final IsaTaxSavingMapper mapper; - @Override public IsaTaxSavingHistoryVo process(UserProductQuarterData item) throws Exception { - BigDecimal totalProfit = item.getTotalProfit(); + log.info("processor!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + log.info("data!!!!!!!!!!!!!!!!!: {}", item); Long memberId = item.getMemberId(); + String quarter = item.getQuarter(); + // "general" 타입 고정, 필요시 변경 가능 + String userType = "general"; - IsaTaxCalculator.IsaTaxResult result = IsaTaxCalculator.calculateTaxSaving(totalProfit, "general"); - - BigDecimal lastAccumulated = mapper.selectLatestAccumulatedSaving(memberId); - if (lastAccumulated == null) lastAccumulated = BigDecimal.ZERO; - - BigDecimal currentSaving = result.getTotalTaxSaved(); - BigDecimal newAccumulated = lastAccumulated.add(currentSaving); - - IsaTaxSavingHistoryVo history = new IsaTaxSavingHistoryVo(); - history.setMemberId(memberId); - history.setQuarter(item.getQuarter()); - history.setSavingAmount(currentSaving); - history.setAccumulatedSaving(newAccumulated); - history.setCreatedAt(LocalDateTime.now()); + if (item.getTotalProfit() == null) { + item.setTotalProfit(BigDecimal.ZERO); + } - return history; + return IsaTaxCalculator.calculateIsaTaxSavingHistoryVo(memberId, quarter, item.getTotalProfit(), userType); } } diff --git a/src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java b/src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java index aa84de7..b09f54c 100644 --- a/src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java +++ b/src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java @@ -12,10 +12,12 @@ import org.springframework.stereotype.Component; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import woojooin.planitbatch.domain.dto.UserProductQuarterData; @Configuration @RequiredArgsConstructor +@Slf4j public class IsaTaxSavingReader { private final SqlSessionFactory sqlSessionFactory; @@ -23,6 +25,8 @@ public class IsaTaxSavingReader { @Bean(name = "isaTaxReader") @StepScope public MyBatisPagingItemReader isaTaxReader() { + log.info("reader!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + MyBatisPagingItemReader reader = new MyBatisPagingItemReader<>(); reader.setSqlSessionFactory(sqlSessionFactory); reader.setQueryId("woojooin.planitbatch.domain.mapper.IsaTaxSavingMapper.selectIsaProductProfitByMember"); diff --git a/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java b/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java index feaee13..8c9a8ef 100644 --- a/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java +++ b/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java @@ -1,54 +1,47 @@ package woojooin.planitbatch.batch.scheduler; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.batch.core.*; -import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j @Component @RequiredArgsConstructor -@EnableBatchProcessing -@EnableScheduling -@Slf4j public class BatchScheduler { private final JobLauncher jobLauncher; private final JobExplorer jobExplorer; private final Job isaTaxSavingJob; - @Scheduled(cron = "0 * * * * ?") // 매 분 실행 (테스트용) - public void runIsaTaxSavingJob() throws Exception { - log.info("🔄 배치 job 실행 시도"); - + @Scheduled(cron = "0 31 10 * * *") // 매일 2시 20분에 실행 + public void runIsaTaxSavingJob() { String jobName = isaTaxSavingJob.getName(); - // 1. 최근 실행된 JobInstance 가져오기 - long minIntervalMillis = 5 * 60 * 1000; // 5분 - for (JobInstance instance : jobExplorer.getJobInstances(jobName, 0, 10)) { - for (JobExecution execution : jobExplorer.getJobExecutions(instance)) { - if (execution.isRunning()) { - log.warn("⚠️ Job '{}' si still running!!!!!!!!!, jump!!!!!!.", jobName); - return; - } - if (execution.getEndTime() != null) { - long elapsed = System.currentTimeMillis() - execution.getEndTime().getTime(); - if (elapsed < minIntervalMillis) { - log.warn("⚠️ Job '{}' recently ended!!!!!!!!!! run{}s after!!!!!!!.", jobName, minIntervalMillis/1000); - return; - } - } + JobParameters jobParameters = new JobParametersBuilder() + .addLong("timestamp", System.currentTimeMillis()) // 매 실행마다 달라지는 값 + .toJobParameters(); + + try { + // 현재 실행 중인 동일 Job이 있는지 확인 (jobName 기준) + if (!jobExplorer.findRunningJobExecutions(jobName).isEmpty()) { + log.warn("[스케줄러] Job '{}'이(가) 이미 실행 중입니다. 실행을 건너뜁니다.", jobName); + return; } - } + log.info("[스케줄러] Job '{}' 실행을 시작합니다.", jobName); + JobExecution jobExecution = jobLauncher.run(isaTaxSavingJob, jobParameters); + log.info("[스케줄러] Job '{}' 실행 상태: {}", jobName, jobExecution.getStatus()); - // 2. 실행 - jobLauncher.run(isaTaxSavingJob, new JobParametersBuilder() - .addLong("time", System.currentTimeMillis()) // 항상 다른 파라미터로 실행 - .toJobParameters()); + } catch (Exception e) { + log.error("[스케줄러] Job '{}' 실행 중 예외가 발생했습니다.", jobName, e); + } } } diff --git a/src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java b/src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java index a7ec365..83ff29e 100644 --- a/src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java +++ b/src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java @@ -2,38 +2,44 @@ import java.util.List; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; import org.springframework.batch.item.ItemWriter; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import woojooin.planitbatch.domain.mapper.IsaTaxSavingMapper; import woojooin.planitbatch.domain.vo.IsaTaxSavingHistoryVo; @Component @RequiredArgsConstructor +@Slf4j public class IsaTaxSavingWriter implements ItemWriter { private final IsaTaxSavingMapper mapper; + @Autowired + private SqlSessionFactory sqlSessionFactory; + @Override public void write(List items) throws Exception { - for (IsaTaxSavingHistoryVo history : items) { - try { - mapper.insertTaxSavingHistory(history); - } catch (Exception e) { - // 개별 실패 로깅 - System.err.println("[Insert 실패] memberId: " + history.getMemberId() + ", quarter: " + history.getQuarter()); - e.printStackTrace(); - // 실패 아이템 별도 처리 가능 (예: 실패 리스트에 저장, 알림 등) + log.info("writer!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + log.info("data!!!!!!!!!!!!!!!!!: {}", items); + + try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false)) { + IsaTaxSavingMapper mapper = sqlSession.getMapper(IsaTaxSavingMapper.class); + for (IsaTaxSavingHistoryVo item : items) { + mapper.upsertIsaTaxSavingHistory(item); } + sqlSession.commit(); // 꼭 커밋해야 함! + } catch (Exception e) { + System.err.println("[Insert/Update 배치 실패] 아이템 개수: " + items.size()); + e.printStackTrace(); } } - //MyBatis 다중 Insert - // @Override - // public void write(List items) throws Exception { - // mapper.insertTaxSavingHistoryBatch((List) items); - // } -} \ No newline at end of file + +} diff --git a/src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java b/src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java index 34dddb7..b9eae0b 100644 --- a/src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java +++ b/src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java @@ -14,9 +14,8 @@ public interface IsaTaxSavingMapper { List selectIsaProductProfitByMember(); - void insertTaxSavingHistoryBatch(@Param("list") List histories); + int upsertIsaTaxSavingHistory(IsaTaxSavingHistoryVo vo); - void insertTaxSavingHistory(IsaTaxSavingHistoryVo history); + int upsertIsaTaxSavingHistoryBatch(@Param("items") List items); - BigDecimal selectLatestAccumulatedSaving(@Param("memberId") Long memberId); } diff --git a/src/main/java/woojooin/planitbatch/domain/vo/IsaTaxSavingHistoryVo.java b/src/main/java/woojooin/planitbatch/domain/vo/IsaTaxSavingHistoryVo.java index ea5baf2..b9df1cb 100644 --- a/src/main/java/woojooin/planitbatch/domain/vo/IsaTaxSavingHistoryVo.java +++ b/src/main/java/woojooin/planitbatch/domain/vo/IsaTaxSavingHistoryVo.java @@ -1,6 +1,7 @@ package woojooin.planitbatch.domain.vo; import java.math.BigDecimal; +import java.time.LocalDate; import java.time.LocalDateTime; import lombok.Builder; @@ -8,10 +9,12 @@ @Data public class IsaTaxSavingHistoryVo { + private Long memberId; - private String quarter; - private BigDecimal savingAmount; - private BigDecimal accumulatedSaving; - private LocalDateTime createdAt; + private String quarter; // 예: "2024-Q1" + private Long isaProfit; // ISA 수익 + private Long generalTax; // 일반계좌였다면 냈을 세금 = isaProfit * 0.154 + private Long taxSaved; // 절세 금액 = generalTax - 0 = generalTax + } diff --git a/src/main/java/woojooin/planitbatch/global/config/DatabaseConfig.java b/src/main/java/woojooin/planitbatch/global/config/DatabaseConfig.java index c818735..0089e7c 100644 --- a/src/main/java/woojooin/planitbatch/global/config/DatabaseConfig.java +++ b/src/main/java/woojooin/planitbatch/global/config/DatabaseConfig.java @@ -19,7 +19,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.PropertySource; -import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; @@ -30,122 +29,120 @@ @Configuration @EnableBatchProcessing @PropertySource("classpath:application.properties") -@MapperScan(basePackages = {"woojooin.planitbatch.domain.mapper", "woojooin.planitbatch.domain.product.mapper", - "woojooin.planitbatch.domain.rebalance.mapper"}) +@MapperScan("woojooin.planitbatch.domain.mapper") public class DatabaseConfig implements BatchConfigurer { - @Value("${jdbc.driver}") - private String driverClassName; - - @Value("${jdbc.url}") - private String url; - - @Value("${jdbc.username}") - private String username; - - @Value("${jdbc.password}") - private String password; - - @Value("${batch.jdbc.driver}") - private String batchDriverClassName; - - @Value("${batch.jdbc.url}") - private String batchUrl; - - @Value("${batch.jdbc.username}") - private String batchUsername; - - @Value("${batch.jdbc.password}") - private String batchPassword; - - @Bean - @Primary - public DataSource dataSource() { - HikariConfig config = new HikariConfig(); - config.setDriverClassName(driverClassName); - config.setJdbcUrl(url); - config.setUsername(username); - config.setPassword(password); - config.setMaximumPoolSize(10); - - config.setConnectionTimeout(20000); - config.setIdleTimeout(300000); - config.setMaxLifetime(1200000); - config.setLeakDetectionThreshold(15000); - - return new HikariDataSource(config); - } - - @Bean("batchDataSource") - public DataSource batchDataSource() { - HikariConfig config = new HikariConfig(); - config.setDriverClassName(batchDriverClassName); - config.setJdbcUrl(batchUrl); - config.setUsername(batchUsername); - config.setPassword(batchPassword); - config.setMaximumPoolSize(5); - - config.setConnectionTimeout(20000); - config.setIdleTimeout(300000); - config.setMaxLifetime(1200000); - config.setLeakDetectionThreshold(15000); - - return new HikariDataSource(config); - } - - @Bean - public SqlSessionFactory sqlSessionFactory() throws Exception { - SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); - - sessionFactory.setDataSource(dataSource()); - - sessionFactory.setConfigLocation(new ClassPathResource("mybatis-config.xml")); - sessionFactory.setMapperLocations( - new PathMatchingResourcePatternResolver() - .getResources("classpath:mapper/*.xml") - ); - - return sessionFactory.getObject(); - } - - @Bean - public SqlSessionTemplate sqlSessionTemplate() throws Exception { - return new SqlSessionTemplate(sqlSessionFactory()); - } - - @Bean("batchTransactionManager") - public PlatformTransactionManager batchTransactionManager() { - return new DataSourceTransactionManager(batchDataSource()); - } - - @Override - public JobRepository getJobRepository() throws Exception { - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); - factory.setDataSource(batchDataSource()); - factory.setTransactionManager(batchTransactionManager()); - factory.afterPropertiesSet(); - return factory.getObject(); - } - - @Override - public PlatformTransactionManager getTransactionManager() throws Exception { - return batchTransactionManager(); - } - - @Override - public JobLauncher getJobLauncher() throws Exception { - SimpleJobLauncher jobLauncher = new SimpleJobLauncher(); - jobLauncher.setJobRepository(getJobRepository()); - jobLauncher.afterPropertiesSet(); - return jobLauncher; - } - - @Override - public JobExplorer getJobExplorer() throws Exception { - JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean(); - jobExplorerFactoryBean.setDataSource(batchDataSource()); - jobExplorerFactoryBean.afterPropertiesSet(); - return jobExplorerFactoryBean.getObject(); - } + @Value("${jdbc.driver}") + private String driverClassName; + + @Value("${jdbc.url}") + private String url; + + @Value("${jdbc.username}") + private String username; + + @Value("${jdbc.password}") + private String password; + + @Value("${batch.jdbc.driver}") + private String batchDriverClassName; + + @Value("${batch.jdbc.url}") + private String batchUrl; + + @Value("${batch.jdbc.username}") + private String batchUsername; + + @Value("${batch.jdbc.password}") + private String batchPassword; + + @Bean + @Primary + public DataSource dataSource() { + HikariConfig config = new HikariConfig(); + config.setDriverClassName(driverClassName); + config.setJdbcUrl(url); + config.setUsername(username); + config.setPassword(password); + config.setMaximumPoolSize(10); + + config.setConnectionTimeout(20000); + config.setIdleTimeout(300000); + config.setMaxLifetime(1200000); + config.setLeakDetectionThreshold(15000); + + return new HikariDataSource(config); + } + + @Bean("batchDataSource") + public DataSource batchDataSource() { + HikariConfig config = new HikariConfig(); + config.setDriverClassName(batchDriverClassName); + config.setJdbcUrl(batchUrl); + config.setUsername(batchUsername); + config.setPassword(batchPassword); + config.setMaximumPoolSize(5); + + config.setConnectionTimeout(20000); + config.setIdleTimeout(300000); + config.setMaxLifetime(1200000); + config.setLeakDetectionThreshold(15000); + + return new HikariDataSource(config); + } + + @Bean + public SqlSessionFactory sqlSessionFactory() throws Exception { + SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); + + sessionFactory.setDataSource(dataSource()); + + sessionFactory.setMapperLocations( + new PathMatchingResourcePatternResolver() + .getResources("classpath:mapper/*.xml") + ); + + return sessionFactory.getObject(); + } + + @Bean + public SqlSessionTemplate sqlSessionTemplate() throws Exception { + return new SqlSessionTemplate(sqlSessionFactory()); + } + + @Bean("batchTransactionManager") + public PlatformTransactionManager batchTransactionManager() { + return new DataSourceTransactionManager(batchDataSource()); + } + + @Override + public JobRepository getJobRepository() throws Exception { + JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + factory.setDataSource(batchDataSource()); + factory.setTransactionManager(batchTransactionManager()); + factory.afterPropertiesSet(); + return factory.getObject(); + } + + @Override + public PlatformTransactionManager getTransactionManager() throws Exception { + return batchTransactionManager(); + } + + @Override + public JobLauncher getJobLauncher() throws Exception { + SimpleJobLauncher jobLauncher = new SimpleJobLauncher(); + jobLauncher.setJobRepository(getJobRepository()); + jobLauncher.afterPropertiesSet(); + return jobLauncher; + } + + @Override + public JobExplorer getJobExplorer() throws Exception { + JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean(); + jobExplorerFactoryBean.setDataSource(batchDataSource()); + jobExplorerFactoryBean.afterPropertiesSet(); + return jobExplorerFactoryBean.getObject(); + } } \ No newline at end of file diff --git a/src/main/java/woojooin/planitbatch/global/util/IsaTaxCalculator.java b/src/main/java/woojooin/planitbatch/global/util/IsaTaxCalculator.java index 0cbe89b..e022511 100644 --- a/src/main/java/woojooin/planitbatch/global/util/IsaTaxCalculator.java +++ b/src/main/java/woojooin/planitbatch/global/util/IsaTaxCalculator.java @@ -2,17 +2,20 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import woojooin.planitbatch.domain.vo.IsaTaxSavingHistoryVo; public class IsaTaxCalculator { /** - * ISA 계좌의 절세 효과를 계산한다. + * ISA 절세 효과를 계산해서 IsaTaxSavingHistoryVo에 맞는 값을 세팅해 반환한다. * + * @param memberId 회원 ID + * @param quarter 분기 (예: "2024-Q1") * @param totalProfit ISA 계좌 전체 수익 - * @param userType "general" (일반형) 또는 "preferential" (서민형/청년형) - * @return IsaTaxResult (절세 이익 + 누적 절세) + * @param userType "general" 또는 "preferential" + * @return IsaTaxSavingHistoryVo (isaProfit, generalTax, taxSaved 세팅 완료) */ - public static IsaTaxResult calculateTaxSaving(BigDecimal totalProfit, String userType) { + public static IsaTaxSavingHistoryVo calculateIsaTaxSavingHistoryVo(Long memberId, String quarter, BigDecimal totalProfit, String userType) { BigDecimal isaLimit = userType.equalsIgnoreCase("preferential") ? new BigDecimal("4000000") : new BigDecimal("2000000"); @@ -20,7 +23,7 @@ public static IsaTaxResult calculateTaxSaving(BigDecimal totalProfit, String use BigDecimal generalTaxRate = new BigDecimal("0.154"); // 일반 계좌 세율: 15.4% BigDecimal isaOverLimitTaxRate = new BigDecimal("0.099"); // ISA 초과분 분리과세: 9.9% - // 일반 계좌 기준 세금 + // 일반 계좌 기준 세금 (isaProfit * generalTaxRate) BigDecimal taxIfGeneralAccount = totalProfit.multiply(generalTaxRate); BigDecimal taxFreeAmount; // ISA 한도 내 금액 @@ -35,61 +38,16 @@ public static IsaTaxResult calculateTaxSaving(BigDecimal totalProfit, String use actualIsaTax = taxableExcessAmount.multiply(isaOverLimitTaxRate); } - // 누적 절세 금액 = 일반 계좌 세금 - ISA 세금 + // 절세 금액 = 일반 계좌 세금 - ISA 세금 BigDecimal totalTaxSaved = taxIfGeneralAccount.subtract(actualIsaTax); - return new IsaTaxResult( - taxFreeAmount.setScale(2, RoundingMode.DOWN), - taxableExcessAmount.setScale(2, RoundingMode.DOWN), - actualIsaTax.setScale(2, RoundingMode.DOWN), - totalTaxSaved.setScale(2, RoundingMode.DOWN) - ); - } - - public static class IsaTaxResult { - private BigDecimal taxFreeAmount; // 한도 내 비과세 금액 - private BigDecimal taxableExcessAmount; // 한도 초과 금액 - private BigDecimal actualIsaTax; // ISA에서 실제 낸 세금 (9.9%) - private BigDecimal totalTaxSaved; // 일반 계좌 대비 절세 금액 - - public IsaTaxResult(BigDecimal taxFreeAmount, BigDecimal taxableExcessAmount, - BigDecimal actualIsaTax, BigDecimal totalTaxSaved) { - this.taxFreeAmount = taxFreeAmount; - this.taxableExcessAmount = taxableExcessAmount; - this.actualIsaTax = actualIsaTax; - this.totalTaxSaved = totalTaxSaved; - } - - public BigDecimal getTaxFreeAmount() { - return taxFreeAmount; - } - - public BigDecimal getTaxableExcessAmount() { - return taxableExcessAmount; - } + IsaTaxSavingHistoryVo vo = new IsaTaxSavingHistoryVo(); + vo.setMemberId(memberId); + vo.setQuarter(quarter); + vo.setIsaProfit(taxFreeAmount.setScale(0, RoundingMode.DOWN).longValue()); // 소수점 버림 후 Long 변환 + vo.setGeneralTax(actualIsaTax.setScale(0, RoundingMode.DOWN).longValue()); + vo.setTaxSaved(totalTaxSaved.setScale(0, RoundingMode.DOWN).longValue()); - public BigDecimal getActualIsaTax() { - return actualIsaTax; - } - - public BigDecimal getTotalTaxSaved() { - return totalTaxSaved; - } - - // ISA 세금을 뺀 실제 수익 - public BigDecimal getActualProfitAmount(){ - BigDecimal actualAmount = taxableExcessAmount.subtract(actualIsaTax); - return taxFreeAmount.add(actualAmount); - } - - @Override - public String toString() { - return "IsaTaxResult{" + - "taxFreeAmount=" + taxFreeAmount + - ", taxableExcessAmount=" + taxableExcessAmount + - ", actualIsaTax=" + actualIsaTax + - ", totalTaxSaved=" + totalTaxSaved + - '}'; - } + return vo; } } diff --git a/src/main/resources/mapper/IsaTaxSavingMapper.xml b/src/main/resources/mapper/IsaTaxSavingMapper.xml index 8656b3a..249fcb1 100644 --- a/src/main/resources/mapper/IsaTaxSavingMapper.xml +++ b/src/main/resources/mapper/IsaTaxSavingMapper.xml @@ -4,65 +4,46 @@ - - INSERT INTO isa_tax_saving_history ( - member_id, - quarter, - saving_amount, - accumulated_saving, - created_at - ) - VALUES - - (#{item.memberId}, #{item.quarter}, #{item.savingAmount}, #{item.accumulatedSaving}, #{item.createdAt}) - + + INSERT INTO isa_tax_saving_history (member_id, quarter, isa_profit, general_tax, tax_saved) + VALUES (#{memberId}, #{quarter}, #{isaProfit}, #{generalTax}, #{taxSaved}) + ON DUPLICATE KEY UPDATE isa_profit = VALUES(isa_profit), + general_tax = VALUES(general_tax), + tax_saved = VALUES(tax_saved) - - - - INSERT INTO isa_tax_saving_history (member_id, - quarter, - saving_amount, - accumulated_saving, - created_at) - VALUES (#{memberId}, - #{quarter}, - #{savingAmount}, - #{accumulatedSaving}, - NOW()) + + INSERT INTO isa_tax_saving_history (member_id, quarter, isa_profit, general_tax, tax_saved) + VALUES + + (#{item.memberId}, #{item.quarter}, #{item.isaProfit}, #{item.generalTax}, #{item.taxSaved}) + + ON DUPLICATE KEY UPDATE + isa_profit = VALUES(isa_profit), + general_tax = VALUES(general_tax), + tax_saved = VALUES(tax_saved) - - From c1c70623b8e85a9992b1a58c657fdb65c68ee289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=84=B8=EB=A6=BC?= <117977227+KwonSeRim2@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:46:30 +0900 Subject: [PATCH 6/9] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../batch/job/IsaTaxSavingJobConfig.java | 3 --- .../batch/processor/IsaTaxSavingProcessor.java | 2 -- .../batch/reader/IsaTaxSavingReader.java | 6 ------ .../batch/writer/IsaTaxSavingWriter.java | 11 ++--------- .../domain/mapper/IsaTaxSavingMapper.java | 7 ------- src/main/resources/mapper/IsaTaxSavingMapper.xml | 13 ------------- 6 files changed, 2 insertions(+), 40 deletions(-) diff --git a/src/main/java/woojooin/planitbatch/batch/job/IsaTaxSavingJobConfig.java b/src/main/java/woojooin/planitbatch/batch/job/IsaTaxSavingJobConfig.java index d7c804b..605b503 100644 --- a/src/main/java/woojooin/planitbatch/batch/job/IsaTaxSavingJobConfig.java +++ b/src/main/java/woojooin/planitbatch/batch/job/IsaTaxSavingJobConfig.java @@ -2,16 +2,13 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; -import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; -import org.springframework.batch.core.step.tasklet.TaskletStep; import org.springframework.batch.item.ItemReader; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import lombok.Data; import lombok.RequiredArgsConstructor; import woojooin.planitbatch.batch.processor.IsaTaxSavingProcessor; import woojooin.planitbatch.batch.reader.IsaTaxSavingReader; diff --git a/src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java b/src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java index b258ec8..28ce045 100644 --- a/src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java +++ b/src/main/java/woojooin/planitbatch/batch/processor/IsaTaxSavingProcessor.java @@ -17,8 +17,6 @@ public class IsaTaxSavingProcessor implements ItemProcessor { @Override public IsaTaxSavingHistoryVo process(UserProductQuarterData item) throws Exception { - log.info("processor!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - log.info("data!!!!!!!!!!!!!!!!!: {}", item); Long memberId = item.getMemberId(); String quarter = item.getQuarter(); // "general" 타입 고정, 필요시 변경 가능 diff --git a/src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java b/src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java index b09f54c..5e3c7bd 100644 --- a/src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java +++ b/src/main/java/woojooin/planitbatch/batch/reader/IsaTaxSavingReader.java @@ -1,15 +1,10 @@ package woojooin.planitbatch.batch.reader; -import java.util.HashMap; - import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.batch.MyBatisPagingItemReader; -import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.stereotype.Component; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -25,7 +20,6 @@ public class IsaTaxSavingReader { @Bean(name = "isaTaxReader") @StepScope public MyBatisPagingItemReader isaTaxReader() { - log.info("reader!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); MyBatisPagingItemReader reader = new MyBatisPagingItemReader<>(); reader.setSqlSessionFactory(sqlSessionFactory); diff --git a/src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java b/src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java index 83ff29e..bb05abf 100644 --- a/src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java +++ b/src/main/java/woojooin/planitbatch/batch/writer/IsaTaxSavingWriter.java @@ -6,7 +6,6 @@ import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.springframework.batch.item.ItemWriter; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import lombok.RequiredArgsConstructor; @@ -19,24 +18,18 @@ @Slf4j public class IsaTaxSavingWriter implements ItemWriter { - private final IsaTaxSavingMapper mapper; - - @Autowired - private SqlSessionFactory sqlSessionFactory; + private final SqlSessionFactory sqlSessionFactory; @Override public void write(List items) throws Exception { - log.info("writer!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - log.info("data!!!!!!!!!!!!!!!!!: {}", items); try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false)) { IsaTaxSavingMapper mapper = sqlSession.getMapper(IsaTaxSavingMapper.class); for (IsaTaxSavingHistoryVo item : items) { mapper.upsertIsaTaxSavingHistory(item); } - sqlSession.commit(); // 꼭 커밋해야 함! + sqlSession.commit(); } catch (Exception e) { - System.err.println("[Insert/Update 배치 실패] 아이템 개수: " + items.size()); e.printStackTrace(); } } diff --git a/src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java b/src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java index b9eae0b..8e36541 100644 --- a/src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java +++ b/src/main/java/woojooin/planitbatch/domain/mapper/IsaTaxSavingMapper.java @@ -1,12 +1,7 @@ package woojooin.planitbatch.domain.mapper; -import java.math.BigDecimal; -import java.math.BigInteger; import java.util.List; -import java.util.Map; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; import woojooin.planitbatch.domain.dto.UserProductQuarterData; import woojooin.planitbatch.domain.vo.IsaTaxSavingHistoryVo; @@ -16,6 +11,4 @@ public interface IsaTaxSavingMapper { int upsertIsaTaxSavingHistory(IsaTaxSavingHistoryVo vo); - int upsertIsaTaxSavingHistoryBatch(@Param("items") List items); - } diff --git a/src/main/resources/mapper/IsaTaxSavingMapper.xml b/src/main/resources/mapper/IsaTaxSavingMapper.xml index 249fcb1..672a525 100644 --- a/src/main/resources/mapper/IsaTaxSavingMapper.xml +++ b/src/main/resources/mapper/IsaTaxSavingMapper.xml @@ -32,19 +32,6 @@ tax_saved = VALUES(tax_saved) - - INSERT INTO isa_tax_saving_history (member_id, quarter, isa_profit, general_tax, tax_saved) - VALUES - - (#{item.memberId}, #{item.quarter}, #{item.isaProfit}, #{item.generalTax}, #{item.taxSaved}) - - ON DUPLICATE KEY UPDATE - isa_profit = VALUES(isa_profit), - general_tax = VALUES(general_tax), - tax_saved = VALUES(tax_saved) - - - From 7af5aae84eb8f5c01dede40ba833327ea87773a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=84=B8=EB=A6=BC?= <117977227+KwonSeRim2@users.noreply.github.com> Date: Thu, 7 Aug 2025 11:24:18 +0900 Subject: [PATCH 7/9] =?UTF-8?q?fix:=20=EC=A0=88=EC=84=B8=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0,=20vo=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../woojooin/planitbatch/batch/scheduler/BatchScheduler.java | 2 +- .../woojooin/planitbatch/global/util/IsaTaxCalculator.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java b/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java index 8c9a8ef..7aa3eb4 100644 --- a/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java +++ b/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java @@ -21,7 +21,7 @@ public class BatchScheduler { private final JobExplorer jobExplorer; private final Job isaTaxSavingJob; - @Scheduled(cron = "0 31 10 * * *") // 매일 2시 20분에 실행 + @Scheduled(cron = "0 13 11 * * *") // 매일 2시 20분에 실행 public void runIsaTaxSavingJob() { String jobName = isaTaxSavingJob.getName(); diff --git a/src/main/java/woojooin/planitbatch/global/util/IsaTaxCalculator.java b/src/main/java/woojooin/planitbatch/global/util/IsaTaxCalculator.java index e022511..f8947d7 100644 --- a/src/main/java/woojooin/planitbatch/global/util/IsaTaxCalculator.java +++ b/src/main/java/woojooin/planitbatch/global/util/IsaTaxCalculator.java @@ -44,8 +44,8 @@ public static IsaTaxSavingHistoryVo calculateIsaTaxSavingHistoryVo(Long memberId IsaTaxSavingHistoryVo vo = new IsaTaxSavingHistoryVo(); vo.setMemberId(memberId); vo.setQuarter(quarter); - vo.setIsaProfit(taxFreeAmount.setScale(0, RoundingMode.DOWN).longValue()); // 소수점 버림 후 Long 변환 - vo.setGeneralTax(actualIsaTax.setScale(0, RoundingMode.DOWN).longValue()); + vo.setIsaProfit(totalProfit.setScale(0, RoundingMode.DOWN).longValue()); // 소수점 버림 후 Long 변환 + vo.setGeneralTax(taxIfGeneralAccount.setScale(0, RoundingMode.DOWN).longValue()); vo.setTaxSaved(totalTaxSaved.setScale(0, RoundingMode.DOWN).longValue()); return vo; From 84b272f29698a3d6819b3c8b7b704a4ae51213f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=84=B8=EB=A6=BC?= <117977227+KwonSeRim2@users.noreply.github.com> Date: Mon, 18 Aug 2025 17:39:26 +0900 Subject: [PATCH 8/9] =?UTF-8?q?fix:=20config=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/DatabaseConfig.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/woojooin/planitbatch/global/config/DatabaseConfig.java b/src/main/java/woojooin/planitbatch/global/config/DatabaseConfig.java index 0089e7c..9b2b7b2 100644 --- a/src/main/java/woojooin/planitbatch/global/config/DatabaseConfig.java +++ b/src/main/java/woojooin/planitbatch/global/config/DatabaseConfig.java @@ -19,6 +19,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.PropertySource; +import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; @@ -27,9 +28,12 @@ import com.zaxxer.hikari.HikariDataSource; @Configuration -@EnableBatchProcessing @PropertySource("classpath:application.properties") -@MapperScan("woojooin.planitbatch.domain.mapper") +@MapperScan(basePackages = { + "woojooin.planitbatch.domain.mapper", + "woojooin.planitbatch.domain.product.mapper", + "woojooin.planitbatch.domain.rebalance.mapper" +}) public class DatabaseConfig implements BatchConfigurer { @Value("${jdbc.driver}") @@ -94,14 +98,12 @@ public DataSource batchDataSource() { @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); - sessionFactory.setDataSource(dataSource()); - + sessionFactory.setConfigLocation(new ClassPathResource("mybatis-config.xml")); sessionFactory.setMapperLocations( new PathMatchingResourcePatternResolver() .getResources("classpath:mapper/*.xml") ); - return sessionFactory.getObject(); } @@ -120,6 +122,7 @@ public JobRepository getJobRepository() throws Exception { JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); factory.setDataSource(batchDataSource()); factory.setTransactionManager(batchTransactionManager()); + factory.setIsolationLevelForCreate("ISOLATION_READ_COMMITTED"); // 격리 수준 설정 factory.afterPropertiesSet(); return factory.getObject(); } @@ -144,5 +147,4 @@ public JobExplorer getJobExplorer() throws Exception { jobExplorerFactoryBean.afterPropertiesSet(); return jobExplorerFactoryBean.getObject(); } - -} \ No newline at end of file +} From 078ab293cc2c20a11b908b710113eed80e7ad6eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=84=B8=EB=A6=BC?= <117977227+KwonSeRim2@users.noreply.github.com> Date: Mon, 18 Aug 2025 17:42:35 +0900 Subject: [PATCH 9/9] =?UTF-8?q?refactor:=20=ED=8C=8C=EC=9D=BC=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{BatchScheduler.java => IsaTaxSavingJobBatchScheduler.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/woojooin/planitbatch/batch/scheduler/{BatchScheduler.java => IsaTaxSavingJobBatchScheduler.java} (97%) diff --git a/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java b/src/main/java/woojooin/planitbatch/batch/scheduler/IsaTaxSavingJobBatchScheduler.java similarity index 97% rename from src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java rename to src/main/java/woojooin/planitbatch/batch/scheduler/IsaTaxSavingJobBatchScheduler.java index 7aa3eb4..9de63e9 100644 --- a/src/main/java/woojooin/planitbatch/batch/scheduler/BatchScheduler.java +++ b/src/main/java/woojooin/planitbatch/batch/scheduler/IsaTaxSavingJobBatchScheduler.java @@ -15,7 +15,7 @@ @Slf4j @Component @RequiredArgsConstructor -public class BatchScheduler { +public class IsaTaxSavingJobBatchScheduler { private final JobLauncher jobLauncher; private final JobExplorer jobExplorer;