This commit is contained in:
mindol1004
2024-08-29 17:15:37 +09:00
parent ecbf7f8698
commit 58495710ec
16 changed files with 272 additions and 29 deletions

View File

@@ -7,7 +7,7 @@ import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Component;
import com.spring.infra.batch.AbstractBatchJob;
import com.spring.infra.batch.AbstractBatchTask;
import com.spring.infra.batch.BatchJobInfo;
import lombok.extern.slf4j.Slf4j;
@@ -19,7 +19,7 @@ import lombok.extern.slf4j.Slf4j;
jobName = "emailSendJob",
cronExpression = "*/5 * * * * ?"
)
public class EmailSendBatch extends AbstractBatchJob {
public class EmailSendBatch extends AbstractBatchTask {
@Override
protected List<Step> createSteps() {

View File

@@ -1,7 +1,5 @@
package com.spring.domain.post.batch;
import java.util.List;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
@@ -11,7 +9,7 @@ import org.springframework.transaction.PlatformTransactionManager;
import com.spring.domain.post.entity.Post;
import com.spring.domain.post.mapper.PostMapper;
import com.spring.infra.batch.AbstractBatchJob;
import com.spring.infra.batch.AbstractBatchTask;
import com.spring.infra.batch.BatchJobInfo;
import com.spring.infra.db.orm.jpa.SecondaryJpaConfig;
@@ -23,10 +21,10 @@ import lombok.extern.slf4j.Slf4j;
@BatchJobInfo(
group = "POST",
jobName = "postCreateJob",
cronExpression = "*/5 * * * * ?"
cronExpression = "0/2 * * * * ?"
)
@RequiredArgsConstructor
public class PostCreateBatch extends AbstractBatchJob {
public class PostCreateBatch extends AbstractBatchTask {
private final PostMapper postMapper;
@@ -40,11 +38,9 @@ public class PostCreateBatch extends AbstractBatchJob {
@Override
protected Tasklet createTasklet() {
log.info(">>>>> PostCreateBatch-createTasklet");
return ((contribution, chunkContext) -> {
postMapper.save(Post.builder().title("testTitle").content("testPost").build());
log.info(">>>>> PostCreateBatchTasklet333333333");
List<Post> list = postMapper.findAll();
list.forEach(item -> log.info(item.getContent()));
return RepeatStatus.FINISHED;
});
}

View File

@@ -0,0 +1,138 @@
package com.spring.domain.post.batch;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManagerFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.database.JpaPagingItemReader;
import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import com.spring.domain.post.entity.Post;
import com.spring.domain.post.entity.PostBackUp;
import com.spring.domain.post.repository.PostBackUpRepository;
import com.spring.domain.post.repository.PostRepository;
import com.spring.infra.db.orm.jpa.SecondaryJpaConfig;
import com.spring.infra.quartz.QuartzJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration
@RequiredArgsConstructor
public class PostCreateBatchChunk {
private final JobRepository jobRepository;
private final @Qualifier(SecondaryJpaConfig.TRANSACTION_MANAGER) PlatformTransactionManager transactionManager;
private final @Qualifier(SecondaryJpaConfig.ENTITY_MANAGER_FACTORY) EntityManagerFactory entityManagerFactory;
private final PostRepository postRepository;
private final PostBackUpRepository postBackUpRepository;
private List<Post> list = new ArrayList<>();
@QuartzJob(group = "POST", jobName = "testPostJob", cronExpression = "0/5 * * * * ?")
@Bean
Job testPostJob() {
return new JobBuilder("testPostJob")
.repository(jobRepository)
.incrementer(new RunIdIncrementer())
.start(readListStep())
.next(decider())
.from(decider()).on("PROCESS").to(processStep())
.from(decider()).on("TERMINATE").to(terminateStep())
.end()
.build();
}
@Bean
JobExecutionDecider decider() {
return (JobExecution jobExecution, StepExecution stepExecution) ->
!list.isEmpty() ? new FlowExecutionStatus("PROCESS") : new FlowExecutionStatus("TERMINATE");
}
private Step readListStep() {
log.info(">>>>> readListStep");
return new StepBuilder("readListStep")
.repository(jobRepository)
.transactionManager(transactionManager)
.tasklet(readListTasklet())
.build();
}
private Tasklet readListTasklet() {
log.info(">>>>> readListTasklet");
return (contribution, chunkContext) -> {
list = postRepository.findAll();
return RepeatStatus.FINISHED;
};
}
private Step processStep() {
log.info(">>>>> processStep");
return new StepBuilder("processStep")
.repository(jobRepository)
.transactionManager(transactionManager)
.<Post, PostBackUp>chunk(5)
.reader(testReader())
.processor(testProcessor())
.writer(testWriter())
.build();
}
private JpaPagingItemReader<Post> testReader() {
log.info(">>>>> JpaPagingItemReader");
return new JpaPagingItemReaderBuilder<Post>()
.name("testReader")
.entityManagerFactory(entityManagerFactory)
.pageSize(5)
.queryString("select p from Post p")
.build();
}
private ItemProcessor<Post, PostBackUp> testProcessor() {
return post ->
PostBackUp.builder().postId(post.getPostId()).content(post.getContent()).title(post.getTitle()).build();
}
private ItemWriter<PostBackUp> testWriter() {
log.info(">>>>> testWriter");
return postBackUpRepository::saveAll;
}
private Step terminateStep() {
log.info(">>>>> terminateStep");
return new StepBuilder("terminateStep")
.repository(jobRepository)
.transactionManager(transactionManager)
.tasklet(terminateTasklet())
.build();
}
private Tasklet terminateTasklet() {
log.info(">>>>> terminateTasklet");
return (contribution, chunkContext) -> {
log.error("List Read Error : List is null");
return RepeatStatus.FINISHED;
};
}
}

View File

@@ -12,12 +12,13 @@ import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
@Entity
@Table(name = "APP_POST")
@Getter
@Builder
@NoArgsConstructor
public class Post {
@Id
@@ -31,4 +32,11 @@ public class Post {
@Column(name = "CONTENT", nullable = false, length = 2000)
private String content;
@Builder
public Post(Long postId, String title, String content) {
this.postId = postId;
this.title = title;
this.content = content;
}
}

View File

@@ -0,0 +1,39 @@
package com.spring.domain.post.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.spring.infra.db.SecondaryDataSourceConfig;
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
@Entity
@Table(name = "APP_POST_BACKUP")
@Getter
@NoArgsConstructor
public class PostBackUp {
@Id
@Column(name = "POST_ID", nullable = false)
private Long postId;
@Column(name = "TITLE", nullable = false, length = 100)
private String title;
@Column(name = "CONTENT", nullable = false, length = 2000)
private String content;
@Builder
public PostBackUp(Long postId, String title, String content) {
this.postId = postId;
this.title = title;
this.content = content;
}
}

View File

@@ -0,0 +1,12 @@
package com.spring.domain.post.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.spring.domain.post.entity.PostBackUp;
import com.spring.infra.db.SecondaryDataSourceConfig;
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
@DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
public interface PostBackUpRepository extends JpaRepository<PostBackUp, Long> {
}

View File

@@ -0,0 +1,29 @@
package com.spring.domain.post.service;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.spring.domain.post.entity.Post;
import com.spring.domain.post.mapper.PostMapper;
import com.spring.infra.db.orm.jpa.SecondaryJpaConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class PostCreateService {
private final PostMapper postMapper;
@Transactional(SecondaryJpaConfig.TRANSACTION_MANAGER)
public void save() {
postMapper.save(Post.builder().title("testTitle").content("testPost").build());
List<Post> list = postMapper.findAll();
list.forEach(item -> log.info(item.getContent()));
}
}

View File

@@ -6,6 +6,7 @@ import java.util.List;
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.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
@@ -31,7 +32,7 @@ import org.springframework.transaction.PlatformTransactionManager;
* @version 1.0
*/
@Configuration
public abstract class AbstractBatchJob implements ApplicationContextAware {
public abstract class AbstractBatchTask implements ApplicationContextAware {
private final BatchJobInfo batchJobInfo;
private ApplicationContext applicationContext;
@@ -44,7 +45,7 @@ public abstract class AbstractBatchJob implements ApplicationContextAware {
* BatchJobInfo 어노테이션을 찾고, 없으면 예외를 발생시킵니다.
* </p>
*/
protected AbstractBatchJob() {
protected AbstractBatchTask() {
this.batchJobInfo = AnnotationUtils.findAnnotation(getClass(), BatchJobInfo.class);
if (this.batchJobInfo == null) {
throw new IllegalStateException("BatchJobInfo annotation is missing");
@@ -104,7 +105,10 @@ public abstract class AbstractBatchJob implements ApplicationContextAware {
if (steps.isEmpty()) {
throw new IllegalStateException("No steps defined for job: " + jobName());
}
var jobBuilder = new JobBuilder(jobName()).repository(jobRepository).start(steps.get(0));
var jobBuilder = new JobBuilder(jobName())
.incrementer(new RunIdIncrementer())
.repository(jobRepository)
.start(steps.get(0));
for (int i = 1; i < steps.size(); i++) {
jobBuilder = jobBuilder.next(steps.get(i));
}

View File

@@ -1,4 +1,4 @@
package com.spring.infra.db.orm.jpa.util;
package com.spring.infra.db.orm.jpa;
import javax.persistence.Entity;

View File

@@ -24,7 +24,6 @@ import org.springframework.transaction.PlatformTransactionManager;
import com.spring.infra.db.PrimaryDataSourceConfig;
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
import com.spring.infra.db.orm.jpa.util.EntityScanner;
import lombok.RequiredArgsConstructor;
@@ -52,8 +51,8 @@ import lombok.RequiredArgsConstructor;
public class PrimaryJpaConfig {
public static final String TRANSACTION_MANAGER = "primaryTransactionManager";
public static final String ENTITY_MANAGER_FACTORY = "primaryEntityManagerFactory";
private static final String BASE_PACKAGE = "com.spring.domain";
private static final String ENTITY_MANAGER_FACTORY = "primaryEntityManagerFactory";
private static final String PERSISTENCE_UNIT = "primaryPersistenceUnit";
private final JpaProperties jpaProperties;

View File

@@ -25,7 +25,6 @@ import org.springframework.transaction.PlatformTransactionManager;
import com.spring.infra.db.SecondaryDataSourceConfig;
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
import com.spring.infra.db.orm.jpa.util.EntityScanner;
import lombok.RequiredArgsConstructor;
@@ -53,8 +52,8 @@ import lombok.RequiredArgsConstructor;
public class SecondaryJpaConfig {
public static final String TRANSACTION_MANAGER = "secondaryTransactionManager";
public static final String ENTITY_MANAGER_FACTORY = "secondaryEntityManagerFactory";
private static final String BASE_PACKAGE = "com.spring.domain";
private static final String ENTITY_MANAGER_FACTORY = "secondaryEntityManagerFactory";
private static final String PERSISTENCE_UNIT = "secondaryPersistenceUnit";
private final JpaProperties jpaProperties;

View File

@@ -12,6 +12,7 @@ import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* Quartz 작업을 실행하는 Spring Batch Job 실행기 클래스입니다.
@@ -31,13 +32,16 @@ import lombok.RequiredArgsConstructor;
* @see JobLauncher
* @see JobRegistry
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class QuartzJobLauncher extends QuartzJobBean {
private static final String JOB_PARAMETERS_INSTANCE_KEY = "InstanceId";
private static final String JOB_PARAMETERS_TIMESTAMP_KEY = "timestamp";
private final JobLauncher jobLauncher;
private final JobRegistry jobRegistry;
private String jobName;
public void setJobName(String jobName) {
@@ -61,11 +65,13 @@ public class QuartzJobLauncher extends QuartzJobBean {
try {
Job job = jobRegistry.getJob(jobName);
JobParameters params = new JobParametersBuilder()
.addString("JobID", String.valueOf(System.currentTimeMillis()))
.addString(JOB_PARAMETERS_INSTANCE_KEY, context.getScheduler().getSchedulerInstanceId())
.addLong(JOB_PARAMETERS_TIMESTAMP_KEY, System.currentTimeMillis())
.toJobParameters();
jobLauncher.run(job, params);
} catch (Exception e) {
throw new JobExecutionException(e);
log.error("job execution exception! - {}", e.getCause());
throw new JobExecutionException();
}
}

View File

@@ -19,7 +19,7 @@ import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import com.spring.infra.batch.AbstractBatchJob;
import com.spring.infra.batch.AbstractBatchTask;
import lombok.RequiredArgsConstructor;
@@ -57,7 +57,7 @@ public class QuartzJobRegistrar implements ApplicationListener<ContextRefreshedE
@Override
public void onApplicationEvent(@NonNull ContextRefreshedEvent event) {
registerQuartzJobs();
registerAbstractJobs();
registerAbstractTasks();
}
/**
@@ -94,7 +94,7 @@ public class QuartzJobRegistrar implements ApplicationListener<ContextRefreshedE
return JobBuilder.newJob(QuartzJobLauncher.class)
.withIdentity(quartzJobAnnotation.jobName(), quartzJobAnnotation.group())
.setJobData(jobDataMap)
.storeDurably()
.storeDurably(true)
.build();
}
@@ -116,15 +116,15 @@ public class QuartzJobRegistrar implements ApplicationListener<ContextRefreshedE
/**
* AbstractBatchJob을 상속받은 모든 클래스를 찾아 Quartz 스케줄러에 등록합니다.
*/
public void registerAbstractJobs() {
Map<String, AbstractBatchJob> batchJobs = applicationContext.getBeansOfType(AbstractBatchJob.class);
for (AbstractBatchJob batchJob : batchJobs.values()) {
public void registerAbstractTasks() {
Map<String, AbstractBatchTask> batchJobs = applicationContext.getBeansOfType(AbstractBatchTask.class);
for (AbstractBatchTask batchJob : batchJobs.values()) {
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("jobName", batchJob.jobName());
JobDetail jobDetail = JobBuilder.newJob(QuartzJobLauncher.class)
.withIdentity(batchJob.jobName(), batchJob.group())
.setJobData(jobDataMap)
.storeDurably()
.storeDurably(true)
.build();
CronTrigger trigger = TriggerBuilder.newTrigger()
.forJob(jobDetail)

View File

@@ -0,0 +1 @@
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier

View File

@@ -143,5 +143,15 @@
"name": "spring.datasource.secondary.hikari.minimum-idle",
"type": "java.lang.String",
"description": "A description for 'spring.datasource.secondary.hikari.minimum-idle'"
},
{
"name": "spring.datasource.primary.hikari.idle-timeout",
"type": "java.lang.String",
"description": "A description for 'spring.datasource.primary.hikari.idle-timeout'"
},
{
"name": "spring.datasource.secondary.hikari.idle-timeout",
"type": "java.lang.String",
"description": "A description for 'spring.datasource.secondary.hikari.idle-timeout'"
}
]}

View File

@@ -14,6 +14,7 @@ spring:
pool-name: HikariPool-1
maximum-pool-size: 10
minimum-idle: 5
idle-timeout: 60000
secondary:
driver-class-name: org.h2.Driver
url: 'jdbc:h2:mem:mob'
@@ -23,6 +24,7 @@ spring:
pool-name: HikariPool-2
maximum-pool-size: 10
minimum-idle: 5
idle-timeout: 60000
# sql:
# init:
# mode: always