2

나는 클라이언트가 잡스를 생성 할 수있게 해주는 저지 웹 서비스를 만들려고 노력한다. 이 잡들은 Hibernate를 영속성 제공자로 사용하여 데이터베이스에 저장된다. 작업은 스케줄 된 서비스에 의해 백그라운드에서 실행되며 Spring에서 스케줄을 지정하려고합니다. 작업은 오랜 시간 동안 실행되기 때문에, 어떻게 든)합니다 (job.start을 만들기 위해 대기열에서 (상태 변경을보고해야장기 실행 @ 스케쥴 스프링 방법, 최대 절전 모드를 만드는 방법?

@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(); 

} 
... 
} 

:

나는 이런 봄 예약 방법을 만들어 IN_PROGRESS)를 데이터베이스에 저장합니다. 이전에는 명령 줄 구현을 사용하고 기본적으로 begin()commit()job.start() 주위에 있습니다.

지금이 봄을 사용하여 작동해야 ...

우려를 분리하고이 일을 만드는 방법에 대한 어떤 조언을?

답변

1

편집

한 가지입니다.

반드시 필요하지는 않습니다. 어느 방향 으로든 경고가 있습니다. 나는 doWork (...) 메소드 위의 수정 된 클래스 블로우 (JobRunnerService)에서 이들 중 일부를 언급했다. 그 노트는 가치가 있습니다.

내가 실현하려하고 싶은 것은 doWork 정기적으로 작업의 진행 상황을 설정할 수 있다는 것입니다

이 수도 있고 당신이 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 작업이 필요하다면 각 작업이 나타내는 작업을 수행 할 수 있습니다. 또한 작업하기 전후에 해당 엔티티 각각의 상태 (ㅎ)를 변경하려고합니다. 초기 상태 변경은 필연적 인 끝 상태 변경과 마찬가지로 자체 트랜잭션 범위 내에서 발생해야한다는 경고가 있습니다.

스프링, JPA 및 하이버 네이트를 사용하는 MySQL 5.x DB에 대해이 코드를 실행했습니다. 필요한 경우 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 (이 J의 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... 
    } 
} 

MaintenanceService (이 사람은 당신의 @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. 
    } 
} 
를 실행하는 서비스입니다
+0

감사 Mike. 내가 이해할 수없는 한 가지는 doWork가 큰 거래를해야하는 이유입니다. 내가하고 싶은 것은 doWork가 Job의 분야 (물론 작은 거래에서)에서 job의 진행 상황 (0 % .. 10 % etc.)을 정기적으로 설정할 수 있다는 것입니다. – RobAu

+0

첫 번째 부분에 대답하려면 : 실제로 트랜잭션이 필요하지 않습니다. 나는 그저 거기에 놓았다. 두 번째 부분은 위의 코드를 약간 수정하지 않으면 수행 할 수 없습니다. 나는 아마도 "Job"객체를 "Update"하기 위해 @Async annotation을 가진 MaintenanceService 내부에 또 다른 메소드를 생성 할 것이고, 어떤 메소드가 작업을하고 있더라도 그것을 호출 할 것이다. – Mike

+0

몇 분만 주시면 위의 답을 기꺼이 고려하여이 사실을 고려해 볼 것입니다. – Mike

0

나는 내 생각을 제안하기 전에, 나는 당신이 문제로 묘사 한 것이 아주 일반적이며 다른 시각으로 다룰 수 있다고 말해야한다. 가능한 한 코드를 재사용하려고합니다.

  1. 프로젝트 용으로 Spring transactions (spring-tx) 모듈을 구성하십시오. 이렇게하면 영구 트랜잭션을위한 메서드에 @Transactional을 사용할 수 있습니다.
  2. 나는 당신이 IJobs로 나타내는 것은 Spring JPA 또는 Spring Repositories
  3. 등의 표준 스프링 지속적인 구현 중 하나를 다음 작업 저장소입니다 I 대표하는 모델을 분리하기 위해 다음과 같은
    • 시도에 코드를 다시 사용한다고 가정 해당 작업이 실행 가능한 작업 (ExecutableJob)을 나타내는 개체에서 지속됩니다 (JobModel). 이 두 가지를 함께 매핑하는 간단한 방법을 사용할 수 있습니다.
    • 가능한 "최소"가능한 코드 블록을 "트랜잭션"으로 만듭니다. 방법 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; 
    } 

} 
+0

'LongRunningJo '의 같은 인스턴스에서'updateJobStatus'를 호출하기 때문에 작동하지 않을 것입니다 bService' –

+0

서로 다른 [전파 유형] (http://static.springsource.org/spring/docs/3.0.x/reference/transaction.html#tx-propagation)에서 이해할 수있는대로 'PROPAGATION_REQUIRES_NEW'가 작동해야한다고 생각합니다. 그렇게 생각하지 않아? – nobeh

+0

아니요,이 메서드는 다른 인스턴스에서 호출 된 경우에만 작동합니다 –

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(); 
    } 

} 

또 다른 지점에서 활성화 있으리라 믿고있어 염두에 두어야합니다

프로세서에 예외가있는 경우 노래를 부르면 그 상태는 COMPLETED_WITH_EXCEPTION 대신에 IN_PROGRESS으로 남아있게됩니다.doWork 하나의 큰 거래를 필요로하는 이유는 정말하지 않습니다 이해

관련 문제