public class PrintNumberJob implements Runnable{
private int number;
@Override
public void run() {
while(number<101) {
System.out.printf("%d - %s[active count : %d]\n", ++number, Thread.currentThread().getName(), Thread.activeCount());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class PrintNumberJobGenerator implements Runnable{
private int jobCount;
@Override
public void run() {
while(jobCount++<10) { // 이게 true라면?
System.out.println((jobCount)+"번째 PrintNumberJob생성");
PrintNumberJob job = new PrintNumberJob();
Thread jobThread = new Thread(job); // 자원을 할당받는(=cpu 1개를 선점할 수 있는) 구조는 됐으나 아직 시작은 안됨
jobThread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class SimpleThreadTest {
public static void main(String[] args) {
PrintNumberJobGenerator generator = new PrintNumberJobGenerator();
// generator.run(); // run()은 스레드를 분리하지 않음
Thread generatorThread = new Thread(generator); // 자원을 할당받는(=cpu 1개를 선점할 수 있는) 구조는 됐으나 아직 시작은 안됨
generatorThread.start(); // 나 이제부터 cpu필요해! 대기순번으로 들어감
System.out.println("메인 쓰레드 end");
}
}
문제점
1. 자원이 한정된 상태에서 무한반복을 돌면? => 풀링으로 해결
2. job과 schedule이 분리되어있찌 않음 => 스케줄러로 해결
<task:annotation-driven scheduler="scheduler" executor="executor"/>
@Component
@Scheduled(initialDelay=0,fixedDelay=1000)
fixed rate | fixed delay |
---|---|
•"0 0 * * * *" = the top of every hour of every day.
•"*/10 * * * * *" = every ten seconds.
•"0 0 8-10 * * *" = 8, 9 and 10 o'clock of every day.
•"0 0 6,19 * * *" = 6:00 AM and 7:00 PM every day.
•"0 0/30 8-10 * * *" = 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day.
•"0 0 9-17 * * MON-FRI" = on the hour nine-to-five weekdays
•"0 0 0 25 12 ?" = every Christmas Day at midnight
public class SpringTaskTestView {
public static void main(String[] args) {
ConfigurableApplicationContext container =
new ClassPathXmlApplicationContext("kr/or/ddit/springtask/conf/task-context.xml");
container.registerShutdownHook();
// 실행하면 아무것도 주입받고 실행한적없지만 등록되어있는 task에 따라 작업이 진행됨
}
}
<!-- tx 모듈도 필요 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.29.RELEASE</version>
</dependency>
<!-- quartz와의 연동, 메일 수발신 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.29.RELEASE</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
public class PrintNumberQuartzJobBean extends QuartzJobBean {
private int number;
/*
* 작업정의
*/
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.printf("%d - %s[active count : %d]\n"
, ++number, Thread.currentThread().getName(), Thread.activeCount());
}
}
<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"
p:jobClass="kr.or.ddit.quartz.PrintNumberQuartzJobBean"
>
<property name="jobDataAsMap">
<map>
<entry key="today">
<bean class="java.util.Date" />
</entry>
</map>
</property>
</bean>
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"
p:jobDetail-ref="jobDetail"
p:startDelay="0"
p:repeatInterval="1000"
p:repeatCount="10"
/>
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<array>
<ref bean="simpleTrigger"/>
</array>
</property>
</bean>
<!-- PNJ를 spring bean으로 등록, 하나의 작업이 하나의 메서드 단위로 묶여야함-->
<bean id="printNumberJob" class="kr.or.ddit.quartz.PrintNumberJob" />
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"
p:targetObject-ref="printNumberJob"
p:targetMethod="printNumber"
/>
public class CustomJobListener extends JobListenerSupport{
@Override
public String getName() {
return this.getClass().getSimpleName();
}
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
System.err.println("작업 거절");
}
@Override
public void jobToBeExecuted(JobExecutionContext context) {
System.err.println("작업 실행");
}
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
System.err.println("작업 완료");
}
}
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="globalJobListeners">
<array>
<bean class="kr.or.ddit.quartz.CustomJobListener" />
</array>
</property>
<property name="triggers">
<array>
<ref bean="simpleTrigger"/>
</array>
</property>
</bean>
<!-- targetObject bean은 MemberDeleteJob.java에서 어노테이션으로 등록됨, coc에 의한 id를 ref로 넣어줌 -->
<bean id="methodInvokingJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"
p:targetObject-ref="memberDeleteJob"
p:targetMethod="deleteMember"
/>
<!-- 월요일 새벽 3시(0 0 3 ? * MON)라는 특정 조건을 위해 CronTrigger가 필요 -->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"
p:jobDetail-ref="methodInvokingJobDetail"
p:cronExpression="0/5 * * * * ?"
/>
<!-- jobDetail과 trigger가 놀 수 있는 context -->
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"
p:triggers-ref="cronTrigger"
/>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
<jdbc:embedded-database type="HSQL" id="realDataSource">
<jdbc:script location="classpath:schema-all.sql"/>
</jdbc:embedded-database>
<!-- 어플리케이션이 읽기 위해 id는 반드시 dataSource로 -->
<bean class="net.sf.log4jdbc.Log4jdbcProxyDataSource"
c:realDataSource-ref="realDataSource"
/>
<bean id="flatFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader"
p:resource="classpath:sample-data.csv"
p:name="personItemReader"
>
<property name="lineMapper"><!-- 한줄씩 처리 -->
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer"><!-- names로 토큰들이 식별자를 갖게됨 -->
<bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
p:delimiter=","
p:names="firstName,lastName"
/>
</property>
<property name="fieldSetMapper"><!-- Person에 매핑, line에서 토큰의 순서와 필드 정보를 매핑 -->
<bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"
p:targetType="com.example.batchprocessing.Person"
/>
</property>
</bean>
</property>
</bean>
<batch:job id="personJob">
<batch:step id="step1">
<batch:tasklet>
<!-- commit-interval: chunk사이즈 == writer 인자 list의 사이즈 -->
<batch:chunk
commit-interval="10"
reader="flatFileItemReader"
processor="personItemProcessor"
writer="jdbcBatchItemWriter"
/>
</batch:tasklet>
</batch:step>
</batch:job>
<jdbc:embedded-database type="HSQL" id="realDataSource">
<jdbc:script location="classpath:schema-all.sql"/>
<jdbc:script location="classpath:/org/springframework/batch/core/schema-drop-hsqldb.sql"/><!--이거새로추가-->
</jdbc:embedded-database>
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean"
p:dataSource-ref="dataSource"
/>
<!-- 작업 실행자 (p:taskExecutor로 thread pooling정책도 설정 가능)-->
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher"
p:jobRepository-ref="jobRepository"
/>
public class BatchContextTestView {
public static void main(String[] args) throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException {
ConfigurableApplicationContext container
= new ClassPathXmlApplicationContext("com/example/batchprocessing/conf/*-context.xml");
container.registerShutdownHook();
JobLauncher jobLauncher = container.getBean(JobLauncher.class);
Job personJob = container.getBean("personJob", Job.class);
JobParameters jobParameters = new JobParametersBuilder()
.addDate("today", new Date())
.toJobParameters(); // toJobParameter == build
jobLauncher.run(personJob, jobParameters);
}
}
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'step1': Cannot resolve reference to bean 'jobRepository' while setting bean property 'jobRepository'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobRepository' defined in file [D:\A_TeachingMaterial\8.gitRepository\lec-202007\SchedulerLab\target\classes\com\example\batchprocessing\conf\batch-context.xml]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: TransactionManager must not be null.
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"
/>
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean"
p:dataSource-ref="dataSource"
p:transactionManager-ref="transactionManager"
/>
Caused by: java.sql.SQLSyntaxErrorException: user lacks privilege or object not found: BATCH_JOB_INSTANCE in statement [SELECT JOB_INSTANCE_ID, JOB_NAME from BATCH_JOB_INSTANCE where JOB_NAME = ? and JOB_KEY = ?]
Caused by: org.hsqldb.HsqlException: user lacks privilege or object not found: BATCH_JOB_INSTANCE
<jdbc:embedded-database type="HSQL" id="realDataSource">
<jdbc:script location="classpath:schema-all.sql"/>
<jdbc:script location="classpath:/org/springframework/batch/core/schema-hsqldb.sql"/>
</jdbc:embedded-database>