后台事务开发文档模板.docx
《后台事务开发文档模板.docx》由会员分享,可在线阅读,更多相关《后台事务开发文档模板.docx(37页珍藏版)》请在冰豆网上搜索。
后台事务开发文档模板
后台事务开发文档
目录
1.简单后台事务示例3
2.后台事务执行过程及原理13
2.1.后台事务定义13
2.2.后台事务发布14
2.3.后台事务执行15
3.代码实现后台事务定义22
3.1.非持久化后台事务22
3.2.错过策略25
3.3.人工干预(撤销,挂起,唤醒)28
3.4.调度计划时间33
1.
简单后台事务示例
在进行后台事务示例测试之前,我们需要做一些准备工作,首先,我们要在BOSStudio的BIM视图下新建几个元数据,如图1所示:
如上图所示,新建了四个元数据(其中T_BO_BandOffice.table是根据实体右键导出表直接导出得到的),其实这四个元数据很简单,FileLogFacade.facade只有一个方法logWriter(Stringstr);该方法实现了向服务器上的C:
/file.log输入日志。
BandOffice.entity上新建了一个方法,该方法和FileLogFacade.facade的方法logWriter(Stringstr)作用一样,名字为testLog(Stringstr),只是为了示范两个不同的调用。
实体BandOffice.entity还新建了一个TestLogEvent的事件,该事件可以引用实体本身的方法logWriter,也可以引用功能FileLogFacade的testLog方法。
再定义了一个业务功能FileLogFunction,在这个业务功能中定义了一个操作fileLog;通过应用,与实体上的事件关联起来。
在定义好这些元数据后,我们需要把这些元数据进行发布,使服务器端在运行时能够加载这些元数据。
上面是我们对解决方案的发布方案设置,如红框标识的,我把元数据发布后生成的代码放在W:
\workspace\bs_job\dev\test目录中,如下图所示,元数据发布后在这个目录下生成的代码,这是实体和功能(facade)发布时生成的代码,其他元数据发布时不生成代码。
在FileLogFacadeControllerBean中会生成功能定义的_logWriter方法,通过在这个方法中写逻辑即可实现,而实体定义的方法会在BandOfficeControllerBean中生成_testLog方法。
元数据发布后,我们需要重新生成系统子树,这样我们在重启服务器后可以在事务任务选择树中看到我们定义的功能。
具体生成系统子树的方法,请参照《生成系统子树的两种方法.doc》。
登陆EAS后,我们选择系统平台——>后台事务定义,进入后台事务定义界面:
接着我们就按要求进行定义,下面我们定义调度计划设置:
定义好后,我们进行保存,这个时候我们可以进行发布了,如下图:
我们再进入:
系统平台——>后台事务定义表,我们可以看到我们刚才定义的那个事务了
我们设置好了调度计划,当时间到达时,触发该事务,就会执行一个事务实例,我们可以查看该事务实例,我们也可以直接点击工具栏中的启动,这样会立刻启动一个事务实例。
从上面可以看出,事务执行成功。
我们再来看看我们实现的功能,就是在服务器目录下写入一条时间信息记录:
2.
后台事务执行过程及原理
首先我们要弄清楚,在EAS系统的后台事务管理平台中定义一个后台事务的过程是怎样的,你可以通过对代码的跟踪进行了解,
2.1.后台事务定义
首先我们在后台事务定义界面上新建一个后台事务(如下图所示),这些后台事务定义信息会被保存在T_WFD_PROCESSDEF中,可以在这个界面对已经定义好的后台事务定义进行修改和删除,当我们选择左边系统子树的某个后台事务定义节点,它对应的信息就会从该表中获得并显示在右边的界面上。
现在我想描述一下右边这颗系统子树的生成过程。
这可系统子树是在该界面的构造函数的initializeUI()方法中进行初始化的
finalBusinessTreeUtiltempPTU=newBusinessTreeUtil(
BusinessTreeUtil.PROCESS);
((KingdeeTreeModel)this.packagekDTree.getModel()).setRoot(null);
JobUtils.resetStartTimeMillis();
tempPTU.initialBusinessTreeView(this.packagekDTree,true);
JobUtils.printTimeMillis("initialBusinessTreeView");
this.packagekDTree.addTreeSelectionListener(new
InnerTreeSelectionListener());
BusinessTreeUtil中有三个静态变量,分别为PROCESS,FUNCTION,ENTITY,分别用来生成不同的系统子树,在对系统子树进行初始化调用了两个方法,第一个是initialPackageTree(tree,flag);这个方法是获得标准包结构的树,第二个是initalFunctionOrJobProcessTree(tree,flag);这个是获得客户化的树形结构,通过该方法可以在标准化得包结构下添加功能节点或后台事务定义节点。
如果是生成功能(function)子树或实体(entity)子树,这就得获取这些元数据的集合,而它们对应的集合类:
FunctionObjectCollection和EntityObjectCollection,获取这些集合的方法为:
IMetaDataLoaderloader=MetaDataLoaderFactory.
getRemoteMetaDataLoader()
FunctionObjectCollectionfuncol=loader.getFunctions()
EntityObjectCollectionentcol=loader.getEntityCollection();
2.2.后台事务发布
后台事务定义完成以后,通过发布,我们就可以在后台事务定义报表看到我们定义的后台事务了,已发布的后台事务不允许进行删除。
publicvoidrelease(ProcessDefprocess)throwsBOSException{
JobDefdef=JobServiceUtil.toJobDef(ctx,process);
Triggertrigger=JobServiceUtil.toTrigger(def,process);
IJobServicesvc=JobServiceFactory.getLocalInstance(ctx);
svc.createJobDef(def,trigger,true);
}
后台事务定义被发布以后,产生了一个新的对象JobDef,对用在数据库中的表是T_JOB_DEF。
后台事务实例(Job)就是根据这个定义来产生的。
由上面的代码可知,后台事务定义(JobDef)和触发器(Trigger)是由流程定义(ProcessDef)转换过来的,这是因为在以前的后台事务是按照工作流的方式进行处理的,每一个后台事务定义都会生成一个流程定义保存在数据库表(T_WFD_PROCESSDEF)的一个字段中,我们用代码实现后台事务定义就不必通过这样获取流程定义(JobDef)。
有上面代码可知,通过调用JobService的createJobDef(JobDefdef,Triggertrigger,booleanenable);方法创建后台事务定义(实际上是通过反射调用了JobManager的createJobDef(JobDefdef,Triggertrigger,booleanenable)方法),通过该方法将JobDef,Trigger对象保存到数据库中的T_JOB_DEF和T_TRIGGER表中。
从而完成了后台事务定义的发布功能。
2.3.后台事务执行
后台事务每执行一次就会产出一个后台事务实例(Job),持久化后台事务会将后台事务实例信息保存在T_JOB_INST表中。
我们可以通过两种方式来触发后台事务执行,第一种是通过后台事务界面上的“启动”按钮直接触发产生一个后台事务实例,第二种是通过定义一个触发器(Trigger)来与该后台事务定义关联起来,用触发器来触发产生一个后台事务实例。
下面我们分别来讨论通过这两种方法是怎样触发产生后台事务实例的。
“启动”按钮触发后台事务。
当我们点击“启动”按钮会触发该按钮的事件,会调用到该界面JobProcessDefineListUI的actionStart_actionPerformed方法,通过方法调用,我们可以知道它调用了JobServiceUIFacade的createJobInstance(jobDefId,null,null),在这个类中,通过获取到了IjobService接口,调用了jobservice的createJobInstance方法;
publicStringcreateJobInstance(StringjobDefId,
Objectparam,TimestampscheduledTime)throwsBOSException{
JobDefdef=JobDefCache.getJobDef(ctx.getAIS(),jobDefId);
if(def==null)thrownewBOSException("jobdefdoesn'texist,itsid
is{"+jobDefId+"},datacenter:
"+ctx.getAIS());
if(scheduledTime==null)
scheduledTime=newTimestamp(System.currentTimeMillis()+1000);
Jobjob=def.newInstance(ctx,param,scheduledTime);
manager.add(job);
returnjob.getTitle();
}
从上面这个方法可以看出,首先由jobDefId获取到JobDef对象def,在设置这个产生的任务实例的计划执行时间为当前时间的下一秒。
然后由后台事务定义对象def的newInstance(ctx,param,scheduledTime)产生一个后台事务实例对象job,再由后台事务管理器JobManager(单子模式)调用add(job)方法把这个任务实例加载到等待队列或者就需队列中去;
触发器触发后台事务。
如果我们在后台事务定义的时候对其关联了相应的触发器,则当下一次触发时间到达时,触发器就会触发与之关联的后台事务定义(JobDef)产生一个后台事务实例(Job)。
触发器触发后台事务的过程是怎样的呢?
首先:
服务器启动时会初始化触发器加载类(TriggerLoader),该类实现了IJobHandler,IcoreJobHandler两个接口,本身就是一个任务处理器,这个类的作用是:
触发器加载者,定期从数据库加载新增的触发器。
其load()方法会定期被调用执行
/**加载触发器*/
privateintload()throwsException{
StringBuffersql=newStringBuffer();
sql.append("select*fromT_JOB_TRIGGERwherefisvalid='Y'AND
fisAutoLoad='Y'AND");
sql.append("(").append(TriggerIsolationLevel.sqlWhere(dc))
.append(")");//符合隔离边界
sql.append("andfholderidisnull");//无持有者
//只加载已生效的触发器
sql.append("andfvalidateTime<=?
");
int[]types=newint[]{Types.TIMESTAMP};
Timestampnow=newTimestamp(System.currentTimeMillis());
Object[]values=newObject[]{now};
ArrayListitems=SQL.executeQuery(dc,sql.toString(),types,
values);
Triggertr;
for(inti=0;itr=null;
try{
tr=Trigger.from(dc,(HashMap)items.get(i));
PulseSourceps=tr.getPulseSource();
if(ps==null){
thrownewBOSException
("couldn'tfindpulsesourceforthistrigger!
");
}
ps.addTrigger(tr);
}catch(Throwablet){
。
。
。
。
。
。
(省略)
}
}
。
。
。
。
。
。
(省略)
//触发器加载间隔为任务加载间隔的5倍
returnConfiguration.persistantJobCheckInterval()*5;
}
这个方法实现了从数据库表T_JOB_TRIGGER中加载新添加的触发器,并返回触发器加载时间间隔(上面设置的是为持久化任务加载间隔的5倍)
protectedsynchronizedvoidaddTrigger(Triggertr)
throwsException{
TimerPulsepulse=newTimerPulse(tr);
if(pulse.nextFireTime()==null){
tr.inValid();//禁用TRIGGER
thrownewjava.lang.IllegalArgumentException("trigger
{"+tr.getKey()+"}hasnofiretime!
");
}
if(pulse.setLoaded())add(pulse);
}
TimerPulseSource的protectedsynchronizedvoidaddTrigger(Triggertr)方法是通过一个触发器新建一个时间触发发生器(TimerPulse),如果获取不到这个TimerPulse下一次触发时间,就禁用这个触发器(Trigger),否则设置这个TimerPulse对应的触发器(Trigger)的状态是被加载的,如果设置成功,则把这个时间触发发生器(TimerPulse)加入到触发源(TimerPulseSource)中用来放置TimerPulse的堆结构(Heap)中去。
当时间出发发生器(TimerPulse)放到Heap中去以后(会根据下一次触发事件进行排序),当下一次触发时间快到的时候,我们就需要把它从Heap中取出来,去生成其对应的触发器关联的后台事务实例(Job)。
这是通过TimerHandler类来实现的,这个类也实现IJobHandler,IcoreJobHandler接口,是专门用来处理时间触发发生器的,在服务器启动初始化TimerPulseSource的时候,就新建了一个后台事务实例,并添加到了就绪队列或等待队列中去了。
JobInstanceConfigcfg=newJobInstanceConfig(newTimerHandler(this),false,true,0);
Jobjob=newJob(null,"Timerpulsesource!
",cfg,Boolean.TRUE);
JobManager.instance().add(job);
而且这个任务每隔一秒会被执行一次,因为execute方法返回值为Delay
(1)。
而且在execute方法中调用的是schedule()方法。
privatevoidschedule(){
Datenow=newTimestamp(System.currentTimeMillis()
+Configuration.persistantJobCheckInterval()*1000);
while(true){
TimerPulsepulse=pulseSource.top();
if(pulse==null)return;
if(pulse.nextFireTime().after(now))return;
pulse=pulseSource.pop();
if(pulse.fire())
pulseSource.add(pulse);
}
}
该方法的作用是:
首先设定一个时间,该时间是距离现在还有一个持久化任务加载间隔时间的未来时间,先读取堆中的堆顶元素,看下一次触发时间是否晚于设定的那个时间,如果是,就直接返回,如果不是,就把堆顶元素pop出来,并触发时间触发发生器(TimerPulse),即调用了fire()方法。
publicbooleanfire(){
if(!
trigger.triggered(nextFireTime))
returnfalse;//触发失败,触发发生器应失效
lastTriggeredTime=nextFireTime;
nextFireTime=quartzTrigger.getFireTimeAfter(lastTriggeredTime);
booleanb=nextFireTime!
=null;
if(!
b)trigger.inValid();//以后不需触发,触发发生器应失效
returnb;
}
publicbooleantriggered(DatelastTriggeredTime){
try{
Jobjob=newJob(lastTriggeredTime);
if(!
modifyState(lastTriggeredTime))returnfalse;
JobManager.instance().add(job);
returntrue;
}catch(Throwablet){
。
。
。
。
。
。
(省略)
}
}
从上面两个方法知,TimerPulse雷中的fire()方法调用了Trigger类中triggered(DatelastTriggeredTime)方法,在triggered方法中,由下一次触发时间得到了这个触发器关联的后台事务实例对象(Job),modifyState()方法是同步数据库,更新该触发器的触发次数和最后一次触发时间等信息。
如果更新成功则把得到的后台事务实例加入到等待或就绪队列中去。
fire()在执行完triggered方法后,会更新一下自身(TimerPulse)的最后一次触发时间和下一次触发时间。
如果fire()方法返回值为true,则把这个更新好的TimerPulse又放入到堆(Heap)结构中去,等待下一次调度。
注意:
在使用任务管理器JobManage提交后台事务实例(Job)中,会调用job.setState(JobState.Created)语句,该语句会根据后台事务实例是否是可持久化事物进行判断,如果是持久化任务就把它保存到数据库中去,并设置为“创建”的状态,如果是非持久化任务就直接设置其状态为“创建”,不做其他任何操作。
接着再一次进行判断,如果是持久化任务则返回,因为持久化任务实例已经保存到数据库中去了,此时需要通过任务加载器(JobLoader)从数据库进行加载。
非持久化任务就可以被加入到等待或就绪队列中去了。
if(!
job.setState(JobState.Created))
。
。
。
。
。
。
(省略)
//持久化任务由任务加载器从数据库加载
if(job.isPersistent())return;
if(job.getScheduledTime().after(newDate())){
b=WaitingJobs.in(job);
}else{
b=ReadyJobs.in(job);
}
持久化任务加载器(JobLoader)实现了IJobHandler,IcoreJobHandler接口,该类在启动服务器的时候就被初始化了,开启了一个加载后台事务实例加载事务,该事务会不行地执行,因为该任务处理器对应的execuse()方法返回值为newDelay(t);在execuse()方法中调用了load()方法,该方法就是从数据库表T_JOB_INST中取得计划执行时间快到得后台事务实例(Job)。
并直接放到等待队列或就绪队列中去。
到现在为止,后台事务实例(Job)已经都已经放到后台事务等待队列或就绪队列中去了,还要完成的工作就是按照后台事务实例的计划执行时间(scheduledTime)来从就绪队列中取出,并执行这个后台事务实例(Job)了。
处理这项工作的是负责后台事务执行的工作线程类ThreadWorker,在服务器启动的时候会进行初始化,新建配置文件上指定个数的工作线程,然后调用start方法启动这些线程。
下面是线程对象的run()方法。
publicvoidrun(){
Jobjob;
booleanexecuted;
while(true){
//服务停止则退出线程
if(!
BOSSchedulerService.isRunning())break;
job=null;
executed=false;
try{
//获取下一个任务
job=ReadyJobs.out();
if(!
RunningJobs.put(job)){
job=null;
continue;
}
//检查是否错过最后执行期限
Datee=job.getExpiredTime();
if(e!
=null){
if(System.currentTimeMillis()>e.getTime()){
job.setState(JobState.Missed);
continue;
}
}
//执行
executed=execute(job);
}catch(Throwablet){
log.error("executejob"+job+"failed!
",t);
}finally{
//释放任务
RunningJobs.dispose(job);
}
if(executed)