4

クライアントがジョブを作成できるようにするジャージー Web サービスを作成しようとしています。これらのジョブは、Hibernate を永続化プロバイダーとして使用してデータベースに保存されます。ジョブは、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 を使用して機能させる必要があります...

懸念事項を分離してこれを機能させる方法について何かアドバイスはありますか?

4

3 に答える 3

3

編集

私がよく理解していないことの 1 つは、なぜ doWork が 1 つの大きなトランザクションを必要とするのかということです。

そうである必要はありません。どちらの方向にも注意事項があります。doWork(...) メソッドの上の修正されたクラス ブロー (JobRunnerService) で、これらのいくつかに注目しました。それらのメモは価値があります...注目に値します。

私が達成したいのは、doWork が定期的にジョブの進行状況を設定できることです。

これは、doWork(...) をトランザクションにバインドするかどうか、および各ジョブを同じ方法で分割できるかどうか (つまり、更新は常に静的に発生するかどうか) に応じて、達成するのが難しい場合とそうでない場合があります。コード内の場所)。私はあなたのすべての要件を知っているわけではないので、この質問に答えることができません. ただし、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 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 (これは 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...
    }
}

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.
    }
}
于 2013-04-18T20:31:42.370 に答える
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();
    }

}

留意すべきもう1つのポイント

ジョブの処理中に例外が発生した場合、そのステータスはIN_PROGRESSのようなものではなく残りCOMPLETED_WITH_EXCEPTIONます。

于 2013-04-17T10:32:45.673 に答える