我尝试创建一个允许客户创建Jobs的Jersey Web服务。这些作业存储在数据库中,使用Hibernate作为持久性提供程序。这些作业将在后台由Scheduled服务执行,我想用Spring安排。
我创建了一个Spring Scheduled方法,如下所示:
@Service
public class MyTimedService
{
@Inject
IJobs allJobs;
private static final Logger LOG = LoggerFactory.getLogger( MyTimedService.class );
@Scheduled(fixedRate=5000)
public void processJobs()
{
for(BaseJob job: allJobs.getQueuedJobs())
{
processJob(job, new JobContext());
}
}
private void processJob( final BaseJob job, JobContext context ) throws JobException
{
job.start();
LOG.info( "Starting: " + job.getName() );
job.execute( context );
LOG.info( "Finished: " + job.getName() );
if ( job.getErrors().size() > 0 )
{
Throwable e = job.getErrors().get( 0 );
throw new JobException( e );
}
job.finished();
}
...
}
因为Job将运行很长时间,所以我需要使job.start()报告状态更改(从QUEUE到IN_PROGRESS)到数据库中。在此之前,我使用了命令行实现并拥有自己的事务管理,基本上begin()
和commit()
位于job.start()
附近。
现在我需要使用Spring ...
关于如何分离问题并使其发挥作用的任何建议?
答案 0 :(得分:2)
修改强>
我不太明白的一件事是为什么doWork需要一个大的交易。
不一定是这样。两个方向都有警告。我在doWork(...)方法之上的修订类打击(JobRunnerService)中注意到了其中一些。这些笔记值得注意。
我想要实现的是,doWork可以定期设置工作进度
这可能会或可能不会很难实现,具体取决于您是否希望doWork(...)绑定到一个事务,以及每个Job是否可以以相同的方式分解(即:更新将始终发生在代码中的静态位置)。我不知道你的所有要求,所以我不能真正回答这个问题。但是,我会重申我对查看Spring Batch的建议。
<强> JobRunnerService 强>
import me.mike.jobs.model.Job;
import me.mike.jobs.model.JobState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
/**
* !!This bean is STATEFUL!!
*/
@Service
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class JobRunnerService {
@Autowired
private JobService js;
public void processJob(Job job) {
job.setState(JobState.WORKING_0);
js.update(job);
try {
doWork(job);
job.setState(JobState.COMPLETE);
} catch (Exception e) {
job.setState(JobState.FAILED);
}
System.out.println("I'm done working.");
js.update(job);
}
/**
* Be sure that any unchecked exception you throw gets added into the "rollbackFor" since it won't trigger
* a rollback if you don't...
*
* The @Transactional is optional - I assumed you would want the work performed in the job to be transactional.
*
* Note: Remember, when doing the work represented by these jobs, that your EntityManager (or SessionFactory) is
* configured with a TransactionManager and, as such, will throw exceptions when you attempt to do work within them
* without a Transaction. You will either need a separate EntityManager (SessionFactory) or something like a
* JdbcTemplate.
*
* Note: If the Job's work DOES need to be Transactional, this will probably not work. A very simple solution
* would to be to split up the work within the job into "steps" or "stages." The processJob(...) method above
* could then call each stage and, at the conclusion, update the Job's state appropriately. This, of course,
* would not work if each Job had N number of stages where N could vary an indeterminate amount.
*/
//@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = { IllegalArgumentException.class })
public void doWork(Job job) throws IllegalArgumentException {
// This method begins its own transaction, every single time its called. Period.
// Do some work...
job.setState(JobState.WORKING_10);
js.update(job);
// Do more work...
job.setState(JobState.WORKING_90);
js.update(job);
// At the conclusion, the transaction bound to this method is committed, unless a rollback was initiated.
}
}
序: 我认为利用像SpringBatch这样的东西来研究一下是明智之举。它可能需要更多的配置,但它也提供了更多的支持。
如果我理解正确,您希望将“作业”存储在表格中(RESTful创建)。您需要一个可以在后台定期运行的@Scheduled任务,以执行每个作业所代表的工作。您还希望在处理它们之前和之后更改每个实体上的状态(heh)。需要注意的是,初始状态变化需要在其自身的事务边界内发生,而不可避免的结束状态变化也是如此。
我使用Spring,JPA和Hibernate对MySQL 5.x数据库运行此代码。如果需要,我可以为您提供我的applicationContext和rest-servlet xml文件。
这将执行我理解您的既定目标:
型号:
import org.hibernate.validator.constraints.Length;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.UUID;
@Entity
public class Job {
@Id
private String id;
@Column
@NotNull
@Length(min = 3, max = 50)
private String name;
@Enumerated(EnumType.STRING)
@Column(length = 50, nullable = false)
private JobState state;
public UUID getId() {
return UUID.fromString(id);
}
public void setId(UUID id) {
this.id = id.toString();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public JobState getState() {
return state;
}
public void setState(JobState state) {
this.state = state;
}
}
存储库:
import me.mike.jobs.model.Job;
import me.mike.jobs.model.JobState;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@Repository
public class JobDao {
@PersistenceContext
private EntityManager em;
@Transactional(propagation = Propagation.REQUIRED)
public void create(Job job) {
// ...
}
@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
public Set<Job> readAll() {
// ...
}
@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
public Job readById(UUID id) {
// ...
}
@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
public Set<Job> readByState(JobState state) {
// ...
}
@Transactional(propagation = Propagation.REQUIRED)
public void update(Job job) {
// ...
}
@Transactional(propagation = Propagation.REQUIRED)
public void delete(Job job) {
// ...
}
}
JobService (这会处理您的Job实体上的RESTful操作)
import me.mike.jobs.dao.JobDao;
import me.mike.jobs.model.Job;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.Set;
@Service
public class JobService {
@Autowired
private JobDao jd;
@Transactional(propagation = Propagation.REQUIRED)
public void create(Job job) {
// Business logic...
jd.create(job);
// More business logic...
}
@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
public Set<Job> read() {
// Business logic...
Set<Job> jobs = jd.readAll();
// More business logic...
return jobs;
}
@Transactional(propagation = Propagation.REQUIRED)
public void update(Job job) {
// Business logic...
jd.update(job);
// More business logic...
}
@Transactional(propagation = Propagation.REQUIRED)
public void delete(Job job) {
// Business logic...
jd.delete(job);
// More business logic...
}
}
维护服务(这家伙会持有你所有的@ScheduledTask方法)
import me.mike.jobs.dao.JobDao;
import me.mike.jobs.model.Job;
import me.mike.jobs.model.JobState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class MaintenanceService {
@Autowired
private JobRunnerService jrs;
@Autowired
private JobDao jd;
@Scheduled(fixedDelay = 5000, initialDelay = 5000)
public void processQueuedJobs() {
// This may be somewhat dangerous depending on how many jobs could potentially be racked up during the 'downtime'
for (Job curJob : jd.readByState(JobState.QUEUED))
jrs.processJob(curJob);
}
// Any other timed service methods...
}
JobRunnerService 这是实际运行作业的服务
import me.mike.jobs.model.Job;
import me.mike.jobs.model.JobState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* !!This bean is STATEFUL!!
*/
@Service
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class JobRunnerService {
@Autowired
private JobService js;
public void processJob(Job job) {
job.setState(JobState.WORKING);
js.update(job);
try {
doWork(job);
job.setState(JobState.COMPLETE);
} catch (Exception e) {
job.setState(JobState.FAILED);
}
System.out.println("I'm done working.");
js.update(job);
}
/**
* Be sure that any unchecked exception you throw gets added into the "rollbackFor" since it won't trigger
* a rollback if you don't...
*/
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = { IllegalArgumentException.class })
public void doWork(Job job) throws IllegalArgumentException {
// This method begins its own transaction, every single time its called. Period.
// Do your work here...
// At the conclusion, the transaction bound to this method is committed, unless a rollback was initiated.
}
}
答案 1 :(得分:1)
我假设您在弹簧配置中启用了注释驱动的事务管理
@Service
public class MyTimedService {
@Inject
IJobs allJobs;
@Inject
JobService jobService;
private static final Logger LOG = LoggerFactory.getLogger( MyTimedService.class );
@Scheduled(fixedRate=5000)
public void processJobs() {
for(BaseJob job: allJobs.getQueuedJobs()) {
processJob(job, new JobContext());
}
}
private void processJob( final BaseJob job, JobContext context ) throws JobException {
jobService.start(job);
LOG.info( "Starting: " + job.getName() );
job.execute( context );
LOG.info( "Finished: " + job.getName() );
if ( job.getErrors().size() > 0 ) {
Throwable e = job.getErrors().get( 0 );
throw new JobException( e );
}
jobService.complete(job);
}
}
@Service
public class JobService {
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void start(BaseJob job){
job.start();
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void complete(BaseJob job){
job.finished();
}
}
要记住的另一点
如果处理作业时出现异常,其状态将保持IN_PROGRESS
而不是COMPLETED_WITH_EXCEPTION
。
答案 2 :(得分:0)
在我提出我的想法之前,我应该说你所描述的问题是非常笼统的,可以用不同的观点来处理。我尝试尽可能多地重用您的代码。
@Transactional
。IJobs
所表示的是一个工作存储库,它遵循标准的Spring持久性实现之一,例如Spring JPA或Spring Repositories JobModel
)保持一致(ExecutableJob
)。您可以使用一种简单的方法将这两者映射到一起。 updateJobStatus
有一个更新作业状态的职责。 重用原理图代码:
@Service
public class LongRunningJobService {
@Inject
JobRepository jobs; // IJobs
@Scheduled(fixedDelay = 60000)
public void processJobs() {
for (JobModel j : jobs.getQueuedJobs()) {
JobContext context = null;
processJob(j, context);
}
}
protected void processJob(JobModel jobModel, JobContext context) {
// update the status of the job
updateJobStatus(jobModel, JobStatus.RUNNING);
ExecutableJob job = null; // createJob(jobModel);
job.execute(context);
// process job results
// if necessary, catch exceptions and again update job status
// success
updateJobStatus(jobModel, JobStatus.FINISHED);
}
@Transactional
protected void updateJobStatus(JobModel jobModel, JobStatus status) {
jobs.updateJobStatus(jobModel, status);
}
static enum JobStatus {
QUEUED, RUNNING, FINISHED;
}
}