上次手邊一個Project source code 翻開spring 的ApplicationContext.xml 一看, 可以看到Quartz基本的設定,如SchedulerFactoryBean,JobDetail,Trigger....等,很正常,當我幫忙找一個Bug時就發現了一些問題,
當他們想要設定一個Job, 裡面有個TaskExecutor , 每次會做10個Thread , 然後每個Thread做的事都是1條Transaction , 所以自己寫了一個MyQuartzScheduler.class , 去幹一些事,每個Job裡面會有一個TaskExecutor , TaskExecutor 呼叫Service method 然後開一個Transaction, 這時問題就來了, Transaction 並沒有預期的開啟,Spring 連create Transaction 都沒啟動, 就去看了一下..
MyQuartzScheduler.java
public class MyQuartzScheduler { private static final Log logger = MyUtils.getLog(); // private SchedulerFactoryBean schedulerFactory; private TaskExecutor taskExecutor; private Scheduler scheduler; private int job_seq = 0; private int trigger_seq = 0; /** * constructor * @param inTaskExecutor */ public MyQuartzScheduler(TaskExecutor inTaskExecutor ) { // this.taskExecutor = inTaskExecutor; try { SchedulerFactory sf = new StdSchedulerFactory(); this.scheduler = sf.getScheduler(); } catch (Exception e) { logger.error("MyQuartzScheduler exception", e); } } . . . . . . . . . }
上面的Code在測試之後發現, 雖然MyQuartzScheduler在ApplicationContext.xml 有定義bean的相關設定,但是最重要的Quartz Scheduler不是尤Spring所給的, SchedulerFactory sf = new StdSchedulerFactory(), 然後Transaction 設定在Service Layer, Service 傳進來的Spring也無法在Scheduler啟動時辦別,最主要的是這個的目的只是為了手動去設定,啟動,停止Quartz Scheduler 的部份。
關於Quartz Scheduler跟Spring 的相關設定,Quartz Job with Transaction 是可行的,
官方的Documentation有寫到
Transactions
- Quartz can participate in JTA transactions, via the use of JobStoreCMT (a subclass of JDBCJobStore).
- Quartz can manage JTA transactions (begin and commit them) around the execution of a Job, so that the work performed by the Job automatically happens within a JTA transaction.
如果想了解Spring + Quartz 的設定, 跟基本的Example 可以參考如下:
Spring + Quartz scheduler example
25.6 Using the OpenSymphony Quartz Scheduler
首先是TaskExceutor 在executor service 時沒有啟動Transaction , 主因是原本設計上的錯誤, 原本在ServiceA.method() 呼叫時,裡面做了一些事,取得TaskExecutor , 但是又把this 自己傳進去,然後再呼叫method, 這邊沒去追Spring 的Source code , 但是應該設計上是不能如此,從Spring Fourm 找到的解如下:
other code.... . . taskExecutor.execute(new Runnable() { @Override public void run() { try { newCaseService.deleteCaseReferenceData(deleteCase); } catch (Exception e) { logger.info("Exception for taskExecutor " e); } } });
現在來解解Spring + Quartz 的問題,
我們有一個Interface TestManullySchedulerService 跟implements TestManullySchedulerServiceImpl 如下
TestManullySchedulerService.java
package com.gfactor.emaildiscovery.service; public interface TestManullySchedulerService { public void testTransactionFunction(); }
TestManullySchedulerServiceImpl .java
package com.gfactor.emaildiscovery.service.impl; import org.apache.commons.logging.Log; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.hibernate3.HibernateSystemException; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional @Transactional(readOnly = false, propagation = Propagation.REQUIRED,rollbackFor={Exception.class,HibernateSystemException.class}) public class TestManullySchedulerServiceImpl implements TestManullySchedulerService { private static final Log logger = MyUtils.getLog(); @Autowired private UserRoleFunDAO userRoleFunDAO; @Transactional public void testTransactionFunction() { DbUser[] dbuser = userRoleFunDAO.getAllUsers(); logger.info("TestManullySchedulerServiceImpl check user object result list = " dbuser.length); } public UserRoleFunDAO getUserRoleFunDAO() { return userRoleFunDAO; } public void setUserRoleFunDAO(UserRoleFunDAO userRoleFunDAO) { this.userRoleFunDAO = userRoleFunDAO; } }
單純對針testTransactionFunction() 定義相關的Transaction , 再來我們要定義自己的QuartzJobBean,
package com.gfactor.emaildiscovery.schedule; import org.apache.commons.logging.Log; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.quartz.QuartzJobBean; import com.gfactor.emaildiscovery.service.TestManullySchedulerService; import com.gfactor.emaildiscovery.utils.MyUtils; import com.gfactor.emaildiscovery.utils.TestRumJobMethod; public class TestManuallyJob extends QuartzJobBean{ private static final Log logger = MyUtils.getLog(); @Autowired private TestRumJobMethod testRumJobMethod; @Autowired private TestManullySchedulerService testManullySchedulerService; // private public void setTestRumJobMethod(TestRumJobMethod testRumJobMethod) { this.testRumJobMethod = testRumJobMethod; } public TestManullySchedulerService getTestManullySchedulerService() { return testManullySchedulerService; } public void setTestManullySchedulerService( TestManullySchedulerService testManullySchedulerService) { this.testManullySchedulerService = testManullySchedulerService; } public TestRumJobMethod getTestRumJobMethod() { return testRumJobMethod; } @Override protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException { logger.info("**** TestManuallyJob start ... "); printTestManuallyJobObjectValues(); testManullySchedulerService.testTransactionFunction(); testRumJobMethod.runMeLogs(); } public void printTestManuallyJobObjectValues(){ logger.info("* testRumJobMethod = " testRumJobMethod); logger.info("* testManullySchedulerService = " testManullySchedulerService); } }
然後隨便寫隻util來測試,
TestManuallySchedulerUtil.java
package com.gfactor.emaildiscovery.schedule; import org.apache.commons.logging.Log; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SimpleTrigger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.scheduling.quartz.SimpleTriggerBean; import com.gfactor.emaildiscovery.utils.MyUtils; public class TestManuallySchedulerUtil { private static final Log logger = MyUtils.getLog(); @Autowired @Qualifier("manuallySchedulerFactoryBean") private Scheduler scheduler; @Autowired private SimpleTrigger testSimpleTrigger; @Autowired @Qualifier("testManuallyJob") private JobDetail testManuallyJob; public JobDetail getTestManuallyJob() { return testManuallyJob; } public void setTestManuallyJob(JobDetail testManuallyJob) { this.testManuallyJob = testManuallyJob; } public SimpleTrigger getTestSimpleTrigger() { return testSimpleTrigger; } public void setTestSimpleTrigger(SimpleTrigger testSimpleTrigger) { this.testSimpleTrigger = testSimpleTrigger; } public Scheduler getScheduler() { return scheduler; } public void setScheduler(Scheduler scheduler) { this.scheduler = scheduler; } public void printInitObjectValues(){ logger.info(" ***** TestManuallySchedulerUtil.printInitObjectValues "); logger.info("scheduler,Get() = " getScheduler()); logger.info("testSimpleTrigger,Get() = " getTestSimpleTrigger()); logger.info("getTestSimpleTrigger getJobName = " getTestSimpleTrigger().getJobName()); logger.info("getTestManuallyJob testManuallyJob = " getTestManuallyJob()); } public void startScheduler(){ logger.info("To start Scheduler , source job from Spring DI bean...."); try { logger.info("tray block start...."); // scheduler.scheduleJob(testSimpleTrigger); scheduler.scheduleJob(testManuallyJob, testSimpleTrigger); scheduler.start(); logger.info("schedler start....."); } catch (SchedulerException e) { logger.error("Exception for TestManuallySchedulerUtil , startScheduler() : " e); } } public void stopScheduler(){ try { logger.info("shutdown scheduler...."); scheduler.shutdown(); } catch (SchedulerException e) { logger.error("Exception for TestManuallySchedulerUtil , stopScheduler() : " e); } } // public void setScheduler(Scheduler scheduler) { // this.scheduler = scheduler; // } }這邊會需要Scheduler, Trigger(SimpleTrigger) 跟JobDetail, 但是要注意的是scheduler跟testManuallyJob這邊指定了@Qualifier, 主要是因為相同的class instance可能會有2個以上,Spring會不知道要DI那一個進來,所以必須指定相關的bean name.
再來是org.springframework.scheduling.quartz.SchedulerFactoryBean 在做DI的時候回傳的實例是一個Scheduler , org.springframework.scheduling.quartz.JobDetailBean 則是一個JobDetail, org.springframework.scheduling.quartz.SimpleTriggerBean則是一個SimpleTrigger。
下面是Spring applicationContext.xml 的設定
<bean id="manuallySchedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="autoStartup" value="false" /> </bean> <bean id="testRumJobMethod" class="com.gfactor.emaildiscovery.utils.TestRumJobMethod" /> <bean id="testManullySchedulerService" class="com.gfactor.emaildiscovery.service.impl.TestManullySchedulerServiceImpl"> <property name="userRoleFunDAO" ref="userRoleFunDAO" /> </bean> <bean id="testManuallyJob" class="org.springframework.scheduling.quartz.JobDetailBean"> <property name="jobClass" value="com.gfactor.emaildiscovery.schedule.TestManuallyJob" /> <property name="jobDataAsMap"> <map> <entry key="testRumJobMethod" value-ref="testRumJobMethod" /> <entry key="testManullySchedulerService" value-ref="testManullySchedulerService" /> </map> </property> </bean> <bean id="testSimpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail" ref="testManuallyJob" /> <property name="repeatInterval" value="15000" /> <property name="startDelay" value="5000" /> </bean> <bean id="testManuallySchedulerUtil" class="com.gfactor.emaildiscovery.schedule.TestManuallySchedulerUtil"> <!-- <property name="triggers"> <list> <ref bean="testSimpleTrigger" /> </list> </property> --> <property name="scheduler" ref="manuallySchedulerFactoryBean"/> </bean>
這樣就能透過Spring 取得相關的Scheduler,Trigger,JobDetail, 然後去實作相關的QuartzJobBean 來達成需要的功能,如果需要定義Trigger的屬性,可以實作CronTrigger, 再定義自己的屬性,這邊主要是透過自行取得的Scheduler 來達成手動啟動,關閉每一個Scheduler 。
Reference:
Quartz job fires twice when manually starting job :
How do I startup quartz scheduler manually with SchedulerFactoryBean? :
Quartz任务监控管理 (1)
Quartz 在 Spring 中如何动态配置时间