over 7 years ago
使用單一 Thread 跑完所有工作,想當然一個地方沒寫好可能就掛掉了,所以改用 Quartz 來調度工作
那為什麼不用之前 Spring Boot Schedule ,原本我以為沒有 Thread pool 的概念,後來才發現可以用 @Async 來實現異步執行,不過 Quartz 有他好用的地方還是可以導入來使用
啟動類別的寫法
package com.lottery.scheduled;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- 這次改用 Spring Data 整合的 JPA ,所以需要 @EnableTransactionManagement
Entity 實體
package com.lottery.scheduled.model.entity;
import java.io.Serializable;
import javax.persistence.*;
import lombok.Data;
@Data
@Entity
public class Biglotto implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private Integer id;
private String num1;
private String num2;
private String num3;
private String num4;
}
Repository 存取
package com.lottery.scheduled.model.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import com.lottery.scheduled.model.entity.*;
public interface BiglottoRepository extends JpaRepository<Biglotto, Integer> {
}
- 使用 JpaRepository 比 CrudRepository 方便一點提供更多基本操作
Service 層
package com.lottery.scheduled.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.lottery.scheduled.model.dao.BiglottoRepository;
import com.lottery.scheduled.model.entity.Biglotto;
@Service
@Transactional
public class BiglottoService {
@Autowired
private BiglottoRepository biglottoRepository;
public List<Biglotto> findAll() {
return biglottoRepository.findAll(new Sort(Sort.Direction.ASC, "id"));
}
}
接下來是真正執行的排程 Job
package com.lottery.scheduled.job;
import java.util.List;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
import com.lottery.scheduled.service.BiglottoService;
@Component
public class Job1 extends QuartzJobBean{
@Autowired
private BiglottoService biglottoService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println("Job1 Start:"+Thread.currentThread().getId());
List list = biglottoService.findAll();
try {
Thread.sleep(1000 * 30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Job1 End");
}
}
- 但是 Job 內其實無法直接使用 @Autowired 註解來注入,所以要提供下面這支 Autowired 來輔助
輔助 Job 內的自動注入
package com.lottery.scheduled;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
public class AutoWiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware{
private transient AutowireCapableBeanFactory beanFactory;
public void setApplicationContext(final ApplicationContext context){
beanFactory = context.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception{
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
才是真正開始寫排程的設定
package com.lottery.scheduled.schedule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import com.lottery.scheduled.job.Job1;
@Configuration
@ComponentScan("com.lottery.scheduled.job")
public class ScheduledTasks1 {
@Bean(name="Job1")
public JobDetailFactoryBean jobDetailFactoryBean(){
JobDetailFactoryBean factory = new JobDetailFactoryBean();
factory.setJobClass(Job1.class);
factory.setGroup("mygroup");
factory.setName("myjob");
return factory;
}
//Job is scheduled after every 1 minute
@Bean(name="Cron1")
public CronTriggerFactoryBean cronTriggerFactoryBean(){
CronTriggerFactoryBean stFactory = new CronTriggerFactoryBean();
stFactory.setJobDetail(jobDetailFactoryBean().getObject());
stFactory.setStartDelay(1000);
stFactory.setName("mytrigger");
stFactory.setGroup("mygroup");
stFactory.setCronExpression("0 0/1 * 1/1 * ? *");
return stFactory;
}
}
- 這邊設定了排程時間 setCronExpression
- 如果還有不同支的排程記得 setName 要不一樣
- @Bean(name="Cron1") 是因為有多組排程要讓 Spring 區分用
QuartzConfig
package com.lottery.scheduled;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
@Configuration
public class QuartzConfiguration {
@Autowired
@Qualifier("Cron1")
private CronTriggerFactoryBean cron1;
@Autowired
@Qualifier("Cron2")
private CronTriggerFactoryBean cron2;
@Bean
public AutoWiringSpringBeanJobFactory autoWiringSpringBeanJobFactory(){
return new AutoWiringSpringBeanJobFactory();
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
scheduler.setJobFactory(autoWiringSpringBeanJobFactory());
scheduler.setTriggers(cron1.getObject(), cron2.getObject());
return scheduler;
}
}
- 把之前設定好的 CronTriggerFactoryBean 用名稱注入後加到 SchedulerFactoryBean 啟動
設定檔
spring:
datasource:
driverClassName: org.postgresql.Driver
url: jdbc:postgresql://10.1.99.99:5432/Lottery
username: root
password: 123456789
testWhileIdle: true
validationQuery: SELECT 1
jpa.show-sql: true
buildscript {
ext {
springBootVersion = '1.2.5.RELEASE'
}
repositories {
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("io.spring.gradle:dependency-management-plugin:0.5.2.RELEASE")
classpath "info.robotbrain.gradle.lombok:lombok-gradle:1.1"
}
}
apply plugin: "info.robotbrain.lombok"
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
apply plugin: 'io.spring.dependency-management'
jar {
baseName = 'lotterySchedule'
version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile 'org.quartz-scheduler:quartz:2.2.1'
compile 'org.springframework:spring-context-support:4.1.6.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-data-jpa:1.2.5.RELEASE'
compile 'org.postgresql:postgresql:9.4-1202-jdbc42'
testCompile group: 'junit', name: 'junit', version: '4.+'
}
eclipse {
classpath {
containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
}
}
task wrapper(type: Wrapper) {
gradleVersion = '2.3'
}
執行結果
Job2 Start:14
Job1 Start:13
Hibernate: select biglotto0_.id as id1_0_, biglotto0_.b10count as b2_0_, biglotto0_.b15count as b3_0_, biglotto0_.b1count as b4_0_, biglotto0_.b5count as b5_0_, biglotto0_.date as date6_0_, biglotto0_.dcount as dcount7_0_, biglotto0_.num1 as num8_0_, biglotto0_.num2 as num9_0_, biglotto0_.num3 as num10_0_, biglotto0_.num4 as num11_0_, biglotto0_.num5 as num12_0_, biglotto0_.num6 as num13_0_, biglotto0_.num7 as num14_0_, biglotto0_.numsum as numsum15_0_, biglotto0_.period as period16_0_, biglotto0_.scount as scount17_0_, biglotto0_.t10count as t18_0_, biglotto0_.t15count as t19_0_, biglotto0_.t5count as t20_0_, biglotto0_.week as week21_0_ from biglotto biglotto0_ order by biglotto0_.id asc
Hibernate: select biglotto0_.id as id1_0_, biglotto0_.b10count as b2_0_, biglotto0_.b15count as b3_0_, biglotto0_.b1count as b4_0_, biglotto0_.b5count as b5_0_, biglotto0_.date as date6_0_, biglotto0_.dcount as dcount7_0_, biglotto0_.num1 as num8_0_, biglotto0_.num2 as num9_0_, biglotto0_.num3 as num10_0_, biglotto0_.num4 as num11_0_, biglotto0_.num5 as num12_0_, biglotto0_.num6 as num13_0_, biglotto0_.num7 as num14_0_, biglotto0_.numsum as numsum15_0_, biglotto0_.period as period16_0_, biglotto0_.scount as scount17_0_, biglotto0_.t10count as t18_0_, biglotto0_.t15count as t19_0_, biglotto0_.t5count as t20_0_, biglotto0_.week as week21_0_ from biglotto biglotto0_ order by biglotto0_.id asc
Job2 End
Job1 End
Job1 Start:15
Hibernate: select biglotto0_.id as id1_0_, biglotto0_.b10count as b2_0_, biglotto0_.b15count as b3_0_, biglotto0_.b1count as b4_0_, biglotto0_.b5count as b5_0_, biglotto0_.date as date6_0_, biglotto0_.dcount as dcount7_0_, biglotto0_.num1 as num8_0_, biglotto0_.num2 as num9_0_, biglotto0_.num3 as num10_0_, biglotto0_.num4 as num11_0_, biglotto0_.num5 as num12_0_, biglotto0_.num6 as num13_0_, biglotto0_.num7 as num14_0_, biglotto0_.numsum as numsum15_0_, biglotto0_.period as period16_0_, biglotto0_.scount as scount17_0_, biglotto0_.t10count as t18_0_, biglotto0_.t15count as t19_0_, biglotto0_.t5count as t20_0_, biglotto0_.week as week21_0_ from biglotto biglotto0_ order by biglotto0_.id asc
Job2 Start:16
Hibernate: select biglotto0_.id as id1_0_, biglotto0_.b10count as b2_0_, biglotto0_.b15count as b3_0_, biglotto0_.b1count as b4_0_, biglotto0_.b5count as b5_0_, biglotto0_.date as date6_0_, biglotto0_.dcount as dcount7_0_, biglotto0_.num1 as num8_0_, biglotto0_.num2 as num9_0_, biglotto0_.num3 as num10_0_, biglotto0_.num4 as num11_0_, biglotto0_.num5 as num12_0_, biglotto0_.num6 as num13_0_, biglotto0_.num7 as num14_0_, biglotto0_.numsum as numsum15_0_, biglotto0_.period as period16_0_, biglotto0_.scount as scount17_0_, biglotto0_.t10count as t18_0_, biglotto0_.t15count as t19_0_, biglotto0_.t5count as t20_0_, biglotto0_.week as week21_0_ from biglotto biglotto0_ order by biglotto0_.id asc
Job2 End
Job1 End
- 可以看到是不同的 Thread Id 在執行指定的工作
- JPA 底層好像是 Hibernate 吧?...XD
如果你想把排程狀態存在資料庫的話可以這樣改
增加 quartz.properties
org.quartz.threadPool.threadCount = 3
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#org.quartz.jobStore.class = org.springframework.scheduling.quartz.LocalDataSourceJobStore
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
package com.lottery.scheduled;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
@Configuration
public class QuartzConfiguration {
@Autowired
private DataSource dataSource;
@Autowired
private ResourceLoader resourceLoader;
@Autowired
@Qualifier("Cron1")
private CronTriggerFactoryBean cron1;
@Autowired
@Qualifier("Cron2")
private CronTriggerFactoryBean cron2;
@Bean
public AutoWiringSpringBeanJobFactory autoWiringSpringBeanJobFactory(){
return new AutoWiringSpringBeanJobFactory();
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
Resource resource = resourceLoader.getResource("classpath:quartz.properties");
scheduler.setConfigLocation(resource);
scheduler.setDataSource(dataSource);
scheduler.setJobFactory(autoWiringSpringBeanJobFactory());
scheduler.setTriggers(cron1.getObject(), cron2.getObject());
return scheduler;
}
}
- 增加 DataSource 跟 ResourceLoader
還要在資料庫新增相關表格後就可以在 qrtz_fired_triggers 這個資料表看到目前是誰正在執行
完成
參考資料
Quartz工作机制
Spring 4 + Quartz 2 Scheduler Integration Annotation Example using JavaConfig
quartz-cron-触发器规则