如何使长时间运行的@Scheduled spring方法、hibernate工作?

如何使长时间运行的@Scheduled spring方法、hibernate工作?,spring,hibernate,transactions,autocommit,spring-scheduled,Spring,Hibernate,Transactions,Autocommit,Spring Scheduled,我尝试制作一个Jersey Web服务,允许客户创造就业机会。这些作业存储在数据库中,使用Hibernate作为持久性提供程序。这些作业将在后台由一个预定服务执行,我想用Spring来安排 我创建了一个Spring调度方法,如下所示: @Service public class MyTimedService { @Inject IJobs allJobs; private static final Logger LOG = LoggerFactory.getLogger

我尝试制作一个Jersey Web服务,允许客户创造就业机会。这些作业存储在数据库中,使用Hibernate作为持久性提供程序。这些作业将在后台由一个预定服务执行,我想用Spring来安排

我创建了一个Spring调度方法,如下所示:

@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.start()向数据库报告状态更改(从队列到进行中)。以前,我使用命令行实现,并有自己的事务管理,基本上是
begin()
commit()
围绕
job.start()

现在我需要使用Spring使它工作


在我提出我的想法之前,我想说的是,你所描述的问题是相当普遍的,可以从不同的角度来处理。我尽量重复使用你的代码

  • 为您的项目配置模块。这允许对持久事务的方法使用
    @Transactional
  • 我假设您用
    IJobs
    表示的是一个遵循标准Spring持久化实现之一的作业存储库,如或
  • 我在下面的代码中重用了您的代码
    • 尝试将表示作业并被持久化的模型(
      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;
        }
    
    }
    

    我假设您在spring配置中启用了注释驱动的事务管理

    @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();
        }
    
    }
    
    还有一点要记住

    如果在处理作业时出现异常,则其状态将保持为“正在进行中”,而不是类似于“已完成且异常”的状态

    有一件事我真的不明白,那就是为什么嫁妆需要一笔大交易

    不一定要这样。这两个方向都有警告。我在doWork(…)方法上方的修订版类blow(JobRunnerService)中注意到了其中一些。那些笔记值得…注意

    我想知道的是,定期的嫁妆可以决定工作的进展

    这可能很难实现,也可能不难实现,这取决于您是否希望将doWork(…)绑定到事务,以及每个作业是否可以以相同的方式分解(即:更新将始终发生在代码中的静态位置)。我不知道你所有的要求,所以我不能真正回答这个问题。然而,我会重申我的建议,看看春季批次

    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 DB运行了这段代码。如果需要,我可以为您提供applicationContext和RESTServlet 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) {
            // ...
        }
    }
    
    存储库:

    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) {
            // ...
        }
    }
    
    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.
        }
    }
    

    它不会工作,因为您正在从同一个
    LongRunningJobService
    实例调用
    updateJobStatus
    ,根据我对不同的理解,我相信
    PROPAGATION\u REQUIRES\u NEW
    应该工作,您不这样认为吗?不,只有从另一个实例调用该方法时它才会工作Hanks Mike。有一件事我真的不明白,那就是为什么嫁妆需要一笔大交易。我想实现的是,定期的工作可以在工作领域(当然,在小交易中)设置工作的进度(0%…10%等)?回答第一部分:它实际上不需要交易。我只是自己放在那里的。至于第二部分:如果不对上面的代码进行一些轻微的修改,这是不可行的。我可能会在MaintenanceService中创建另一个方法,并使用@Async注释“更新”该“作业”对象并从任何方法调用它。如果您能给我几分钟时间,我将很高兴地编辑上面的答案以考虑这一点。哦,我忘了感谢您对SpringBatch的提示。我一定会尝试一下,因为它看起来可能适合我的需要,为什么要重新发明轮子:)不客气,罗布。我是否满意地回答了您的原始(和后续)问题?