企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# Spring Batch + Quartz + H2 Jdbcjobstore 示例 > 原文: [https://howtodoinjava.com/spring-batch/quartz-h2-jdbcjobstore-example/](https://howtodoinjava.com/spring-batch/quartz-h2-jdbcjobstore-example/) 学习使用 Quartz 调度程序和 Quartz 用于记录作业和触发信息的持久性数据库存储记录来执行**多个 Spring Batch 作业**。 我在启用 Web 控制台的情况下使用`H2`数据库来查看数据库表中的数据。 您可以根据需要选择数据库。 ## 项目结构 #### 目标 在本教程中,我们将创建一个 Spring 应用程序并执行以下任务。 1. 创建 2 个 Spring Batch 作业。 每个作业都有多个步骤。 2. 创建 Quartz 作业和触发器,它们将运行在步骤 1 中创建的 SpringBatch。 3. Quartz 作业和触发器将存储在文件系统中的持久性 H2 数据库中。 4. 通过 **H2 控制台**验证存储在 DB 中的触发信息。 5. 使用`LoggingTriggerHistoryPlugin`在日志中记录完整的触发器历史记录。 #### 项目结构 ![Project Structure](https://img.kancloud.cn/a6/b2/a6b289329355d28741904be16f4d1d2e_301x507.jpg) 项目结构 ## Maven 依赖 我们需要具有以下依赖才能运行此项目。 `pom.xml` ```java <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd; <modelVersion>4.0.0</modelVersion> <groupId>com.howtodoinjava</groupId> <artifactId>App</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>App</name> <url>http://maven.apache.org</url> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-batch</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>repository.spring.release</id> <name>Spring GA Repository</name> <url>http://repo.spring.io/release</url> </repository> </repositories> </project> ``` ## 配置 Spring Batch 作业和任务 创建一些`Tasklets`和批处理作业以执行。 `MyTaskOne.java and MyTaskTwo.java` ```java import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; public class MyTaskOne implements Tasklet { public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { System.out.println("MyTaskOne start.."); // ... your code System.out.println("MyTaskOne done.."); return RepeatStatus.FINISHED; } } public class MyTaskTwo implements Tasklet { public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { System.out.println("MyTaskTwo start.."); // ... your code System.out.println("MyTaskTwo done.."); return RepeatStatus.FINISHED; } } ``` `BatchConfig.java` ```java 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.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.howtodoinjava.demo.tasks.MyTaskOne; import com.howtodoinjava.demo.tasks.MyTaskTwo; @Configuration @EnableBatchProcessing public class BatchConfig { @Autowired private JobBuilderFactory jobs; @Autowired private StepBuilderFactory steps; @Bean public Step stepOne(){ return steps.get("stepOne") .tasklet(new MyTaskOne()) .build(); } @Bean public Step stepTwo(){ return steps.get("stepTwo") .tasklet(new MyTaskTwo()) .build(); } @Bean(name="demoJobOne") public Job demoJobOne(){ return jobs.get("demoJobOne") .start(stepOne()) .next(stepTwo()) .build(); } @Bean(name="demoJobTwo") public Job demoJobTwo(){ return jobs.get("demoJobTwo") .flow(stepOne()) .build() .build(); } } ``` `application.properties` ```java #Disable batch job's auto start spring.batch.job.enabled=false ``` ## 配置 Quartz 作业和触发器 现在创建 Quartz 作业,它将运行 SpringBatch。 `CustomQuartzJob.java` ```java import org.quartz.DisallowConcurrentExecution; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.PersistJobDataAfterExecution; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.JobParametersBuilder; import org.springframework.batch.core.configuration.JobLocator; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.context.ApplicationContext; import org.springframework.scheduling.quartz.QuartzJobBean; @PersistJobDataAfterExecution @DisallowConcurrentExecution public class CustomQuartzJob extends QuartzJobBean { private String jobName; private JobLauncher jobLauncher; private JobLocator jobLocator; public String getJobName() { return jobName; } public void setJobName(String jobName) { this.jobName = jobName; } @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { try { ApplicationContext applicationContext = (ApplicationContext) context.getScheduler().getContext().get("applicationContext"); jobLocator = (JobLocator) applicationContext.getBean(JobLocator.class); jobLauncher = (JobLauncher) applicationContext.getBean(JobLauncher.class); Job job = jobLocator.getJob(jobName); JobParameters params = new JobParametersBuilder() .addString("JobID", String.valueOf(System.currentTimeMillis())) .toJobParameters(); jobLauncher.run(job, params); } catch (Exception e) { e.printStackTrace(); } } } ``` `QuartzConfig.java` ```java import java.io.IOException; import java.util.Properties; import org.quartz.JobBuilder; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.SchedulerException; import org.quartz.SimpleScheduleBuilder; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import com.howtodoinjava.demo.jobs.CustomQuartzJob; @Configuration public class QuartzConfig { @Bean public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) { JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor(); jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry); return jobRegistryBeanPostProcessor; } @Bean public JobDetail jobOneDetail() { //Set Job data map JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("jobName", "demoJobOne"); return JobBuilder.newJob(CustomQuartzJob.class) .withIdentity("demoJobOne",null) .setJobData(jobDataMap) .storeDurably() .build(); } @Bean public JobDetail jobTwoDetail() { //Set Job data map JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("jobName", "demoJobTwo"); return JobBuilder.newJob(CustomQuartzJob.class) .withIdentity("demoJobTwo",null) .setJobData(jobDataMap) .storeDurably() .build(); } @Bean public Trigger jobOneTrigger() { SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder .simpleSchedule() .withIntervalInSeconds(10) .repeatForever(); return TriggerBuilder .newTrigger() .forJob(jobOneDetail()) .withIdentity("jobOneTrigger",null) .withSchedule(scheduleBuilder) .build(); } @Bean public Trigger jobTwoTrigger() { SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder .simpleSchedule() .withIntervalInSeconds(20) .repeatForever(); return TriggerBuilder .newTrigger() .forJob(jobTwoDetail()) .withIdentity("jobTwoTrigger",null) .withSchedule(scheduleBuilder) .build(); } @Bean public SchedulerFactoryBean schedulerFactoryBean() throws IOException, SchedulerException { SchedulerFactoryBean scheduler = new SchedulerFactoryBean(); scheduler.setTriggers(jobOneTrigger(), jobTwoTrigger()); scheduler.setQuartzProperties(quartzProperties()); scheduler.setJobDetails(jobOneDetail(), jobTwoDetail()); scheduler.setApplicationContextSchedulerContextKey("applicationContext"); return scheduler; } public Properties quartzProperties() throws IOException { PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties")); propertiesFactoryBean.afterPropertiesSet(); return propertiesFactoryBean.getObject(); } } ``` `quartz.properties` ```java #scheduler name will be "MyScheduler" org.quartz.scheduler.instanceName=TestScheduler org.quartz.scheduler.instanceId=AUTO #maximum of 3 jobs can be run simultaneously org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount=50 #Log trigger history org.quartz.plugin.triggerHistory.class=org.quartz.plugins.history.LoggingTriggerHistoryPlugin org.quartz.plugin.triggerHistory.triggerFiredMessage=Trigger [{1}.{0}] fired job [{6}.{5}] scheduled at: {2, date, dd-MM-yyyy HH:mm:ss.SSS}, next scheduled at: {3, date, dd-MM-yyyy HH:mm:ss.SSS} org.quartz.plugin.triggerHistory.triggerCompleteMessage=Trigger [{1}.{0}] completed firing job [{6}.{5}] with resulting trigger instruction code: {9}. Next scheduled at: {3, date, dd-MM-yyyy HH:mm:ss.SSS} org.quartz.plugin.triggerHistory.triggerMisfiredMessage=Trigger [{1}.{0}] misfired job [{6}.{5}]. Should have fired at: {3, date, dd-MM-yyyy HH:mm:ss.SSS} ``` ## 配置 H2 数据库 使用`quartz.properties`文件中的属性将 quartz 配置为使用 H2 数据库。 `quartz.properties` ```java #Quartz persistent jobStore config org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.tablePrefix=QRTZ_ org.quartz.jobStore.dataSource=myDS org.quartz.jobStore.useProperties=false org.quartz.jobStore.isClustered=false #Quartz dataSource org.quartz.dataSource.myDS.driver=org.h2.Driver org.quartz.dataSource.myDS.URL=jdbc:h2:file:~/h2/testdb;INIT=RUNSCRIPT FROM 'classpath:schema.sql' org.quartz.dataSource.myDS.user=sa org.quartz.dataSource.myDS.password = org.quartz.dataSource.myDS.maxConnections=5 org.quartz.dataSource.myDS.validationQuery=select 1 ``` Quartz 不会自动在数据库中创建必要的表,您需要在应用程序启动时创建它们。 我已经使用了上面的`org.quartz.dataSource.myDS.URL`属性。 模式查询位于`schema.sql`文件中。 `schema.sql` ```java DROP TABLE IF EXISTS QRTZ_CALENDARS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS qrtz_simprop_triggers; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS qrtz_simprop_triggers; CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR (200) NOT NULL , CALENDAR IMAGE NOT NULL ); CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR (200) NOT NULL , TRIGGER_GROUP VARCHAR (200) NOT NULL , CRON_EXPRESSION VARCHAR (120) NOT NULL , TIME_ZONE_ID VARCHAR (80) ); CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR (95) NOT NULL , TRIGGER_NAME VARCHAR (200) NOT NULL , TRIGGER_GROUP VARCHAR (200) NOT NULL , INSTANCE_NAME VARCHAR (200) NOT NULL , FIRED_TIME BIGINT NOT NULL , SCHED_TIME BIGINT NOT NULL , PRIORITY INTEGER NOT NULL , STATE VARCHAR (16) NOT NULL, JOB_NAME VARCHAR (200) NULL , JOB_GROUP VARCHAR (200) NULL , IS_NONCONCURRENT BOOLEAN NULL , REQUESTS_RECOVERY BOOLEAN NULL ); CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR (200) NOT NULL ); CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR (200) NOT NULL , LAST_CHECKIN_TIME BIGINT NOT NULL , CHECKIN_INTERVAL BIGINT NOT NULL ); CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR (40) NOT NULL ); CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR (200) NOT NULL , JOB_GROUP VARCHAR (200) NOT NULL , DESCRIPTION VARCHAR (250) NULL , JOB_CLASS_NAME VARCHAR (250) NOT NULL , IS_DURABLE BOOLEAN NOT NULL , IS_NONCONCURRENT BOOLEAN NOT NULL , IS_UPDATE_DATA BOOLEAN NOT NULL , REQUESTS_RECOVERY BOOLEAN NOT NULL , JOB_DATA IMAGE NULL ); CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR (200) NOT NULL , TRIGGER_GROUP VARCHAR (200) NOT NULL , REPEAT_COUNT BIGINT NOT NULL , REPEAT_INTERVAL BIGINT NOT NULL , TIMES_TRIGGERED BIGINT NOT NULL ); CREATE TABLE qrtz_simprop_triggers ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INTEGER NULL, INT_PROP_2 INTEGER NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 BOOLEAN NULL, BOOL_PROP_2 BOOLEAN NULL, ); CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR (200) NOT NULL , TRIGGER_GROUP VARCHAR (200) NOT NULL , BLOB_DATA IMAGE NULL ); CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR (200) NOT NULL , TRIGGER_GROUP VARCHAR (200) NOT NULL , JOB_NAME VARCHAR (200) NOT NULL , JOB_GROUP VARCHAR (200) NOT NULL , DESCRIPTION VARCHAR (250) NULL , NEXT_FIRE_TIME BIGINT NULL , PREV_FIRE_TIME BIGINT NULL , PRIORITY INTEGER NULL , TRIGGER_STATE VARCHAR (16) NOT NULL , TRIGGER_TYPE VARCHAR (8) NOT NULL , START_TIME BIGINT NOT NULL , END_TIME BIGINT NULL , CALENDAR_NAME VARCHAR (200) NULL , MISFIRE_INSTR SMALLINT NULL , JOB_DATA IMAGE NULL ); ALTER TABLE QRTZ_CALENDARS ADD CONSTRAINT PK_QRTZ_CALENDARS PRIMARY KEY ( SCHED_NAME, CALENDAR_NAME ); ALTER TABLE QRTZ_CRON_TRIGGERS ADD CONSTRAINT PK_QRTZ_CRON_TRIGGERS PRIMARY KEY ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ); ALTER TABLE QRTZ_FIRED_TRIGGERS ADD CONSTRAINT PK_QRTZ_FIRED_TRIGGERS PRIMARY KEY ( SCHED_NAME, ENTRY_ID ); ALTER TABLE QRTZ_PAUSED_TRIGGER_GRPS ADD CONSTRAINT PK_QRTZ_PAUSED_TRIGGER_GRPS PRIMARY KEY ( SCHED_NAME, TRIGGER_GROUP ); ALTER TABLE QRTZ_SCHEDULER_STATE ADD CONSTRAINT PK_QRTZ_SCHEDULER_STATE PRIMARY KEY ( SCHED_NAME, INSTANCE_NAME ); ALTER TABLE QRTZ_LOCKS ADD CONSTRAINT PK_QRTZ_LOCKS PRIMARY KEY ( SCHED_NAME, LOCK_NAME ); ALTER TABLE QRTZ_JOB_DETAILS ADD CONSTRAINT PK_QRTZ_JOB_DETAILS PRIMARY KEY ( SCHED_NAME, JOB_NAME, JOB_GROUP ); ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD CONSTRAINT PK_QRTZ_SIMPLE_TRIGGERS PRIMARY KEY ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ); ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD CONSTRAINT PK_QRTZ_SIMPROP_TRIGGERS PRIMARY KEY ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ); ALTER TABLE QRTZ_TRIGGERS ADD CONSTRAINT PK_QRTZ_TRIGGERS PRIMARY KEY ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ); ALTER TABLE QRTZ_CRON_TRIGGERS ADD CONSTRAINT FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ) REFERENCES QRTZ_TRIGGERS ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ) ON DELETE CASCADE; ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD CONSTRAINT FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ) REFERENCES QRTZ_TRIGGERS ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ) ON DELETE CASCADE; ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD CONSTRAINT FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ) REFERENCES QRTZ_TRIGGERS ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ) ON DELETE CASCADE; ALTER TABLE QRTZ_TRIGGERS ADD CONSTRAINT FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS FOREIGN KEY ( SCHED_NAME, JOB_NAME, JOB_GROUP ) REFERENCES QRTZ_JOB_DETAILS ( SCHED_NAME, JOB_NAME, JOB_GROUP ); COMMIT; ``` ## 运行演示 #### 配置日志记录并启动应用程序 为了格式化日志语句,我创建了自定义`logback.xml`。 `logback.xml` ```java <?xml version="1.0" encoding="UTF-8"?> <configuration scan="true"> <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <charset>UTF-8</charset> <Pattern>%d{yyyy-MM-dd HH:mm:ss} %p %X{TXNID} - %m%n</Pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="consoleAppender" /> </root> </configuration> ``` 作为 Spring 启动应用程序启动该应用程序。 `App.java` ```java package com.howtodoinjava.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } } ``` #### 验证作业执行 在服务器控制台中验证正在执行的 Spring Batch 作业。 `Console` ```java . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.0.3.RELEASE) 2018-07-06 09:29:07 INFO - Starting App on MACHINE-ID with PID 13456 (C:\Users\lokesh\workspace\App\target\classes started by lokesh in C:\Users\lokesh\workspace\App) 2018-07-06 09:29:07 INFO - No active profile set, falling back to default profiles: default 2018-07-06 09:29:08 INFO - Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@23529fee: startup date [Fri Jul 06 09:29:08 IST 2018]; root of context hierarchy 2018-07-06 09:29:10 INFO - Bean 'quartzConfig' of type [com.howtodoinjava.demo.config.QuartzConfig$EnhancerBySpringCGLIB$78eca775] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2018-07-06 09:29:10 INFO - Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$EnhancerBySpringCGLIB$11c34f14] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2018-07-06 09:29:10 INFO - Bean 'org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari' of type [org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2018-07-06 09:29:10 INFO - Bean 'org.springframework.boot.context.properties.ConversionServiceDeducer$Factory' of type [org.springframework.boot.context.properties.ConversionServiceDeducer$Factory] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2018-07-06 09:29:10 INFO - Bean 'spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties' of type [org.springframework.boot.autoconfigure.jdbc.DataSourceProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2018-07-06 09:29:10 INFO - Bean 'dataSource' of type [com.zaxxer.hikari.HikariDataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2018-07-06 09:29:10 INFO - HikariPool-1 - Starting... 2018-07-06 09:29:11 INFO - HikariPool-1 - Start completed. 2018-07-06 09:29:11 INFO - Executing SQL script from URL [file:/C:/Users/lokesh/workspace/App/target/classes/schema.sql] 2018-07-06 09:29:11 INFO - Executed SQL script from URL [file:/C:/Users/lokesh/workspace/App/target/classes/schema.sql] in 66 ms. 2018-07-06 09:29:11 INFO - Bean 'org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker' of type [org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2018-07-06 09:29:11 INFO - Bean 'org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration' of type [org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$EnhancerBySpringCGLIB$1186297a] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2018-07-06 09:29:11 INFO - Bean 'jobRegistry' of type [com.sun.proxy.$Proxy49] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2018-07-06 09:29:12 INFO - Tomcat initialized with port(s): 8080 (http) 2018-07-06 09:29:12 INFO - Initializing ProtocolHandler ["http-nio-8080"] 2018-07-06 09:29:12 INFO - Starting service [Tomcat] 2018-07-06 09:29:12 INFO - Starting Servlet Engine: Apache Tomcat/8.5.31 2018-07-06 09:29:12 INFO - Initializing Spring embedded WebApplicationContext 2018-07-06 09:29:12 INFO - Root WebApplicationContext: initialization completed in 4505 ms 2018-07-06 09:29:12 INFO - Servlet webServlet mapped to [/console/*] 2018-07-06 09:29:12 INFO - Servlet dispatcherServlet mapped to [/] 2018-07-06 09:29:12 INFO - Mapping filter: 'characterEncodingFilter' to: [/*] 2018-07-06 09:29:12 INFO - Mapping filter: 'hiddenHttpMethodFilter' to: [/*] 2018-07-06 09:29:12 INFO - Mapping filter: 'httpPutFormContentFilter' to: [/*] 2018-07-06 09:29:12 INFO - Mapping filter: 'requestContextFilter' to: [/*] 2018-07-06 09:29:13 INFO - No database type set, using meta data indicating: H2 2018-07-06 09:29:13 INFO - No TaskExecutor has been set, defaulting to synchronous executor. 2018-07-06 09:29:13 INFO - Executing SQL script from class path resource [org/quartz/impl/jdbcjobstore/tables_h2.sql] 2018-07-06 09:29:13 INFO - Executed SQL script from class path resource [org/quartz/impl/jdbcjobstore/tables_h2.sql] in 21 ms. 2018-07-06 09:29:13 INFO - Using ConnectionProvider class 'org.quartz.utils.C3p0PoolingConnectionProvider' for data source 'myDS' 2018-07-06 09:29:13 INFO - MLog clients using slf4j logging. 2018-07-06 09:29:13 INFO - Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10] 2018-07-06 09:29:13 INFO - Using default implementation for ThreadExecutor 2018-07-06 09:29:14 INFO - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl 2018-07-06 09:29:14 INFO - Quartz Scheduler v.2.3.0 created. 2018-07-06 09:29:14 INFO - Using thread monitor-based data access locking (synchronization). 2018-07-06 09:29:14 INFO - JobStoreTX initialized. 2018-07-06 09:29:14 INFO - Scheduler meta-data: Quartz Scheduler (v2.3.0) 'schedulerFactoryBean' with instanceId 'NON_CLUSTERED' Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally. NOT STARTED. Currently in standby mode. Number of jobs executed: 0 Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 50 threads. Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is not clustered. 2018-07-06 09:29:14 INFO - Quartz scheduler 'schedulerFactoryBean' initialized from an externally provided properties instance. 2018-07-06 09:29:14 INFO - Quartz scheduler version: 2.3.0 2018-07-06 09:29:14 INFO - JobFactory set to: org.springframework.scheduling.quartz.AdaptableJobFactory@3db64bd4 2018-07-06 09:29:14 INFO - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 8jgsvm9wug78a3djgl4a|73ab3aac, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> org.h2.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 8jgsvm9wug78a3djgl4a|73ab3aac, idleConnectionTestPeriod -> 50, initialPoolSize -> 3, jdbcUrl -> jdbc:h2:file:~/h2/testdb;INIT=RUNSCRIPT FROM 'classpath:schema.sql', maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 5, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, preferredTestQuery -> select 1, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> true, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ] 2018-07-06 09:29:15 INFO - Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2018-07-06 09:29:15 INFO - Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@23529fee: startup date [Fri Jul 06 09:29:08 IST 2018]; root of context hierarchy 2018-07-06 09:29:15 INFO - Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest) 2018-07-06 09:29:15 INFO - Mapped "{[/error],produces=}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) 2018-07-06 09:29:15 INFO - Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2018-07-06 09:29:15 INFO - Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2018-07-06 09:29:15 INFO - Executing SQL script from class path resource [org/springframework/batch/core/schema-h2.sql] 2018-07-06 09:29:16 INFO - Executed SQL script from class path resource [org/springframework/batch/core/schema-h2.sql] in 105 ms. 2018-07-06 09:29:16 INFO - Registering beans for JMX exposure on startup 2018-07-06 09:29:16 INFO - Bean with name 'dataSource' has been autodetected for JMX exposure 2018-07-06 09:29:16 INFO - Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource] 2018-07-06 09:29:16 INFO - Starting beans in phase 2147483647 2018-07-06 09:29:16 INFO - Starting Quartz Scheduler now 2018-07-06 09:29:16 INFO - Freed 0 triggers from 'acquired' / 'blocked' state. 2018-07-06 09:29:16 INFO - Recovering 0 jobs that were in-progress at the time of the last shut-down. 2018-07-06 09:29:16 INFO - Recovery complete. 2018-07-06 09:29:16 INFO - Removed 0 'complete' triggers. 2018-07-06 09:29:16 INFO - Removed 0 stale fired job entries. 2018-07-06 09:29:16 INFO - Scheduler schedulerFactoryBean_$_NON_CLUSTERED started. 2018-07-06 09:29:16 INFO - Starting ProtocolHandler ["http-nio-8080"] 2018-07-06 09:29:16 INFO - Using a shared selector for servlet write/read 2018-07-06 09:29:16 INFO - Tomcat started on port(s): 8080 (http) with context path '' 2018-07-06 09:29:16 INFO - Started App in 9.709 seconds (JVM running for 10.821) 2018-07-06 09:29:17 INFO - Trigger [DEFAULT.jobOneTrigger] fired job [DEFAULT.demoJobOne] scheduled at: 06-07-2018 09:29:13.524, next scheduled at: 06-07-2018 09:29:23.524 2018-07-06 09:29:17 INFO - Trigger [DEFAULT.jobTwoTrigger] fired job [DEFAULT.demoJobTwo] scheduled at: 06-07-2018 09:29:13.565, next scheduled at: 06-07-2018 09:29:33.565 2018-07-06 09:29:17 INFO - Job: [FlowJob: [name=demoJobTwo]] launched with the following parameters: [{JobID=1530849557198}] 2018-07-06 09:29:17 INFO - Executing step: [stepOne] MyTaskOne start.. MyTaskOne done.. 2018-07-06 09:29:17 INFO - Job: [FlowJob: [name=demoJobTwo]] completed with the following parameters: [{JobID=1530849557198}] and the following status: [COMPLETED] 2018-07-06 09:29:17 INFO - Trigger [DEFAULT.jobTwoTrigger] completed firing job [DEFAULT.demoJobTwo] with resulting trigger instruction code: DO NOTHING. Next scheduled at: 06-07-2018 09:29:33.565 2018-07-06 09:29:17 INFO - Job: [SimpleJob: [name=demoJobOne]] launched with the following parameters: [{JobID=1530849557093}] 2018-07-06 09:29:17 INFO - Executing step: [stepOne] MyTaskOne start.. MyTaskOne done.. 2018-07-06 09:29:17 INFO - Executing step: [stepTwo] MyTaskTwo start.. MyTaskTwo done.. 2018-07-06 09:29:17 INFO - Job: [SimpleJob: [name=demoJobOne]] completed with the following parameters: [{JobID=1530849557093}] and the following status: [COMPLETED] 2018-07-06 09:29:17 INFO - Trigger [DEFAULT.jobOneTrigger] completed firing job [DEFAULT.demoJobOne] with resulting trigger instruction code: DO NOTHING. Next scheduled at: 06-07-2018 09:29:23.524 2018-07-06 09:29:23 INFO - Trigger [DEFAULT.jobOneTrigger] fired job [DEFAULT.demoJobOne] scheduled at: 06-07-2018 09:29:23.524, next scheduled at: 06-07-2018 09:29:33.524 2018-07-06 09:29:23 INFO - Job: [SimpleJob: [name=demoJobOne]] launched with the following parameters: [{JobID=1530849563538}] 2018-07-06 09:29:23 INFO - Executing step: [stepOne] MyTaskOne start.. MyTaskOne done.. 2018-07-06 09:29:23 INFO - Executing step: [stepTwo] MyTaskTwo start.. MyTaskTwo done.. 2018-07-06 09:29:23 INFO - Job: [SimpleJob: [name=demoJobOne]] completed with the following parameters: [{JobID=1530849563538}] and the following status: [COMPLETED] 2018-07-06 09:29:23 INFO - Trigger [DEFAULT.jobOneTrigger] completed firing job [DEFAULT.demoJobOne] with resulting trigger instruction code: DO NOTHING. Next scheduled at: 06-07-2018 09:29:33.524 ``` #### 验证 H2 控制台 使用浏览器 URL 访问 H2 控制台:`http://localhost:8080/console/` ![H2 Console](https://img.kancloud.cn/0f/bf/0fbf5a6755772ec45538974296f16c4f_1086x420.jpg) H2 控制台 将我的问题放在评论部分。 学习愉快!