Quartz ศึกษาด้วยตนเอง part 2

Phai Panda
Tech INNO
Published in
5 min readJun 19, 2020

Job ใน JobDetail จะถูก fires (ได้หลายครั้งแล้วแต่เงื่อนไข) โดย Trigger แน่นอนว่าพวกมันจะทำงานได้ก็ต่อเมื่ออยู่พร้อมกันใน Scheduler

http://www.quartz-scheduler.org

ความเดิมจากตอนที่แล้ว

เราได้รู้จัก Quartz ไปบ้างแล้ว รู้ว่ามันประกอบด้วย Job, JobDetail, Trigger และ Scheduler ดังนั้นใน part นี้เราจะมาเขียนโค้ดกันครับ

เริ่มจาก

Hello World

  • เครื่องมือสำหรับโค้ด IDEA
  • ภาษาที่ใช้ Kotlin

สร้างโปรเจกต์ด้วยภาษา Kotlin จากนั้นทดสอบ Hello World กันก่อน

Hello World ชาวโลก

Hello Quartz

  • โหลด Quartz library ได้ที่นี่ เลือกเป็น latest stable releases
  • แกะออกแล้วมองหา folder ที่ชื่อ lib เฉพาะ folder นี้ลากไปวางไว้ที่ root project
i see lib folder!
  • คลิกขวาที่ lib เลือก Add as Library…
  • เลือก Level เป็น Project Library
  • หรือเลือก File > Project Structure… ตรวจดูที่ Libraries ก็จะพบ lib ดังกล่าว

และเนื่องจากว่า Quarzt ใช้ log4j เราจึงควร configuration ให้เรียบร้อย

  • ที่ src ของโปรเจกต์ คลิกขวาสร้างไฟล์ log4j.properties
  • เพิ่ม config ต่อไปนี้ลงไปใน log4j.properties
log4j.rootLogger=INFO, theConsoleAppender

log4j.appender.theConsoleAppender=org.apache.log4j.ConsoleAppender
log4j.appender.theConsoleAppender.layout=org.apache.log4j.PatternLayout

ถึงตอนนี้เราก็พร้อม Hello Quartz แล้วครับ

เดิมเรามีฟังก์ชัน main อยู่แล้วก็ให้เพิ่มคลาส SimpleJob ที่ใช้ Job interface ดังนี้

import org.quartz.Job
import org.quartz.JobExecutionContext

class SimpleJob: Job {
override fun execute(context: JobExecutionContext?) {
println("Hello Quartz")
}
}

ที่ฟังก์ชัน main สร้าง JobDetail ของ SimpleJob

fun main() {
val jobDetail = JobBuilder.newJob().ofType(SimpleJob::class.java).build()
}

หลังจากนั้นสร้างกลไกของเวลารอไว้ด้วย

fun main() {
val jobDetail = JobBuilder.newJob().ofType(SimpleJob::class.java).build()
val trigger = TriggerBuilder.newTrigger().build()
}

สุดท้ายสร้าง scheduler แล้วมอบ jobDetail กับ trigger ให้กับมัน

fun main() {
val jobDetail = JobBuilder.newJob().ofType(SimpleJob::class.java).build()
val trigger = TriggerBuilder.newTrigger().build()

val scheduler = StdSchedulerFactory.getDefaultScheduler()
scheduler.scheduleJob(jobDetail, trigger)
scheduler.start()

}

ผลลัพธ์

Hello Quartz

เพื่อนๆจะพบว่าโค้ดข้างต้นนั้นเรียบง่าย ประกอบด้วย 1 JobDetail, 1 Trigger และ 1 Scheduler

หากเราเปลี่ยน “Hello Quartz” เป็น java.util.Date เราก็จะได้ค่าของเวลาปัจจุบันของเครื่องออกมา

ผลลัพธ์

Tue Jun 16 14:25:40 ICT 2020

repeatForever and withIntervalInSeconds

เราจะกำหนดให้ค่าของ Date นี้ถูกพิมพ์ออกมาทุกๆ 3 วินาที เรื่อยไปครับ จุดหมายนี้จึงต้องไปบอกกับ Trigger

fun main() {
val jobDetail = JobBuilder.newJob().ofType(SimpleJob::class.java).build()
val trigger = TriggerBuilder.newTrigger()
.withSchedule(
SimpleScheduleBuilder.simpleSchedule()
.repeatForever()
.withIntervalInSeconds(3)
)

.build()

val scheduler = StdSchedulerFactory.getDefaultScheduler()
scheduler.scheduleJob(jobDetail, trigger)
scheduler.start()
}

ผลลัพธ์

Tue Jun 16 14:33:09 ICT 2020
Tue Jun 16 14:33:12 ICT 2020
Tue Jun 16 14:33:15 ICT 2020
Tue Jun 16 14:33:18 ICT 2020
Tue Jun 16 14:33:21 ICT 2020
Tue Jun 16 14:33:24 ICT 2020
Tue Jun 16 14:33:27 ICT 2020
Tue Jun 16 14:33:30 ICT 2020

มาถึงตรงนี้เราก็พอจะเห็นภาพการทำงานของ Scheduler บ้างแล้ว มาต่อที่ตัวอย่างถัดไป

JobDataMap

หนึ่งในวิธีการส่ง data จากภายนอกให้กับ Job (ที่ถูก JobDetail หุ้มไว้) ในขณะ runtime คือการใช้ JobDataMap

JobDataMap ตัวมันเองสร้างจาก Map interface (Data Structure) มี key กับ value เป็นคู่กัน

ตัวอย่าง

val jobDetail = JobBuilder.newJob().ofType(SimpleJob::class.java)
.usingJobData("msg1", "Job Execute Time")
.usingJobData("count", 1)

.build()

จากนั้นแก้ไขใน SimpleJob เพื่อเรียกใช้

class SimpleJob: Job {
override fun execute(context: JobExecutionContext?) {
val jobDataMap = context!!.jobDetail.jobDataMap
println
(jobDataMap.getInt("count").toString() + " job is running...")
println(jobDataMap.getString("msg1") + ": "+ Date())
}
}

ผลลัพธ์

1 job is running…
Job Execute Time: Sat Jun 20 01:20:12 ICT 2020
1 job is running…
Job Execute Time: Sat Jun 20 01:20:15 ICT 2020
1 job is running…
Job Execute Time: Sat Jun 20 01:20:18 ICT 2020

CronTrigger

CronTrigger เป็น interface ที่สืบทอดจาก Trigger มีความสามารถเสมือนตารางเวลาในปฏิทิน เราจึงกำหนดได้ว่าจะให้ทำงานวันที่เท่าไรเวลาไหนหรือช่วงไหนของวัน เรียกได้ว่ามีประโยชน์มากหากต้องการระบบอัตโนมัติที่ตั้งเวลาต่อเนื่องได้

CronTrigger ใช้สิ่งที่เรียกว่า Cron-Expressions เป็นเงื่อนไขกำหนดตารางเวลาทำงาน (เรียกว่าการ fire) อ่านเพิ่มเติมได้ที่นี่

ประกอบด้วยสตริงที่สร้างจาก 6 ถึง 7 นิพจน์ย่อย แต่ละนิพจน์มีความหมายของมันเอง

Cron Expressions Allowed Fields and Values

นำมาเรียงต่อกัน คั่นแต่ละนิพจน์ด้วยช่องว่าง ได้ดังนี้

Seconds Minutes Hours Day of month Month Day of week Year

โดยที่ Year ไม่ใส่ก็ได้

เช่น

0 0 12 * * ?
fire เวลา 12:00 PM (เที่ยงวัน) ในทุกวัน

0 15 10 ? * *
fire เวลา 10:15 AM ในทุกวัน

0 15 10 * * ?
fire เวลา 10:15 AM ในทุกวัน

0 15 10 * * ? *
fire เวลา 10:15 AM ในทุกวัน

0 15 10 * * ? 2005
fire เวลา 10:15 AM ทุกวัน ตลอดปี 2005

0 * 14 * * ?
fire ทุกนาที เริ่มที่เวลา 2:00 PM จบที่เวลา 2:59 PM ในทุกวัน

0 0/5 14 * * ?
fire ทุก 5 นาที เริ่มที่เวลา 2:00 PM จบที่เวลา 2:55 PM ในทุกวัน

0 0/5 14,18 * * ?
fire ทุก 5 นาที เริ่มที่เวลา 2:00 PM จบที่เวลา 2:55 PM
และ fire ทุก 5 นาที เริ่มที่เวลา 6:00 PM จบที่เวลา 6:55 PM ในทุกวัน

0 0–5 14 * * ?
fire ทุกนาที เริ่มที่เวลา 2:00 PM จบที่เวลา 2:05 PM ในทุกวัน

0 10,44 14 ? 3 WED
fire เวลา 2:10 PM และเวลา 2:44 PM ทุกวันพุธในเดือนมีนาคม

0 15 10 ? * MON-FRI
fire เวลา 10:15 AM ทุกวันจันทร์ อังคาร พุธ พฤหัสบดีและศุกร์

0 15 10 15 * ?
fire เวลา 10:15 AM ทุกวันที่ 15 ของทุกเดือน

0 15 10 L * ?
fire เวลา 10:15 AM ทุกวันสิ้นเดือน

0 15 10 ? * 6L
fire เวลา 10:15 AM ทุกศุกร์สุดท้ายของทุกเดือน

0 15 10 ? * 6L 2002–2005
fire เวลา 10:15 AM ทุกศุกร์สุดท้ายของทุกเดือน ระหว่างปี 2002, 2003, 2004 และ 2005

0 15 10 ? * 6#3
fire เวลา 10:15 AM ของศุกร์ที่สามของทุกเดือน

0 0 12 1/5 * ?
fire เวลา 12 PM (เที่ยงคืน) ทุก 5 วันในทุกเดือน โดยเริ่มต้นที่วันแรกของเดือน

0 11 11 11 11 ?
fire ทุกเดือนพฤศจิกายน เวลา 11 ถึง 11:11 AM

ตัวอย่าง

val trigger = TriggerBuilder.newTrigger()
.withSchedule(
CronScheduleBuilder.cronSchedule("0 0-20 3 * * ?")
)
.build()

ผลลัพธ์

1 job is running…
Job Execute Time: Sat Jun 20 03:17:00 ICT 2020
1 job is running…
Job Execute Time: Sat Jun 20 03:18:00 ICT 2020
1 job is running…
Job Execute Time: Sat Jun 20 03:19:00 ICT 2020
1 job is running…
Job Execute Time: Sat Jun 20 03:20:00 ICT 2020

ตัวอย่างข้างต้น (ในขณะที่เขียนบทความอยู่นี้) จะ fire ทุกนาที เวลาตีสามถึงตีสามยี่สิบนาทีในทุกวัน (ผมเริ่ม execute เวลา ตีสามสิบหกนาที)

หมายเหตุ
การจะหยุด schedule ได้ต้องเรียกคำสั่ง scheduler.shutdown() ดังนั้นจึงต้องหาวิธีเก็บค่าของตัวแปร scheduler นี้ไว้เพื่อเรียกใช้ในภายหลัง

อ้างอิง

https://docs.oracle.com/cd/E12058_01/doc/doc.1014/e12030/cron_expressions.htm

https://www.baeldung.com/quartz

--

--