上次手邊一個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 中如何动态配置时间
沒有留言:
張貼留言