创建作业时需要访问作业启动器参数

时间:2016-08-05 12:00:25

标签: spring-batch

我通过命令行调用我的批处理服务,并为作业提供一些参数。我在创建作业时需要访问这些参数,因为我需要从数据库中查找网站名称的数据'作为参数提供并动态创建多个步骤。问题在于' createJob'方法。我目前对网站ID进行了硬编码,但itemizedReader方法有一个例外:

package billing;

import billing.components.AspiviaFieldSetter;
import billing.components.AspiviaPrepStatementSetter;
import billing.components.SummaryProcessor;
import billing.mapper.ItemizedCostingMapper;
import billing.model.BillingItem;
import billing.model.ItemizedCosting;
import billing.tasklet.SummaryOutputTasklet;
import billing.batch.common.AppProps;
import billing.batch.common.SqlConst;
import billing.batch.common.model.ItemizedPartner;
import billing.batch.common.repo.PartnerBillingRepo;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.SimpleJobBuilder;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.database.JdbcCursorItemReader;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.FlatFileItemWriter;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineAggregator;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.batch.item.file.transform.FieldExtractor;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.FileSystemResource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

@ComponentScan(basePackages = {"billing", "billing.batch.common"})
@Configuration
@EnableBatchProcessing
@EnableAspectJAutoProxy
@PropertySource("classpath:/app.properties")
public class BillingConfig {

    private static final Logger LOG = LogManager.getLogger();

    @Autowired
    private AppProps appProps;

    @Autowired
    private PartnerBillingRepo billingRepo;

    @Bean
    @Profile("prod")
    public DataSource datasource() {
        final HikariConfig cfg = new HikariConfig();
        cfg.setJdbcUrl(appProps.getPartnerBillingUrl());
        cfg.setUsername(appProps.getPartnerBillingUsername());
        cfg.setPassword(appProps.getPartnerBillingPassword());
        cfg.addDataSourceProperty("cachePrepStmts", appProps.getCachePrepStatements());
        cfg.addDataSourceProperty("prepStmtCacheSize", appProps.getPrepStatementCacheSize());
        cfg.addDataSourceProperty("prepStmtCacheSqlLimit", appProps.getPrepStatementCacheSqlLimit());

        HikariDataSource ds = new HikariDataSource(cfg);

        return ds;
    }

    @Bean
    public JdbcTemplate template(DataSource ds) {
        return new JdbcTemplate(ds);
    }

    @Bean
    @StepScope
    public FlatFileItemReader billingFileReader(@Value("#{jobParameters['input.file']}") String inputFile) {
        DefaultLineMapper lineMapper = new DefaultLineMapper();
        lineMapper.setFieldSetMapper(new BillingFieldSetter());
        lineMapper.setLineTokenizer(new DelimitedLineTokenizer());

        FlatFileItemReader reader = new FlatFileItemReader();
        reader.setLineMapper(lineMapper);
        reader.setResource(new FileSystemResource(inputFile));

        return reader;
    }

    @Bean
    @StepScope
    public JdbcBatchItemWriter BillingWriter(DataSource ds, BillingPrepStatementSetter setter) {
        JdbcBatchItemWriter writer = new JdbcBatchItemWriter();
        writer.setDataSource(ds);
        writer.setItemPreparedStatementSetter(setter);
        writer.setSql(SqlConst.INSERT_INTO_BILLING);

        return writer;
    }

    @Bean
    @StepScope
    public BillingPrepStatementSetter prepStatementSetter() {
        return new BillingPrepStatementSetter();
    }

    @Bean
    @StepScope
    public SummaryProcessor summaryProc() {
        return new SummaryProcessor();
    }

    @Bean
    @StepScope
    public SummaryOutputTasklet summaryTask() {
        return new SummaryOutputTasklet();
    }

    @Bean
    @StepScope
    public ItemReader<ItemizedCosting> itemizedReader(@Value("#{jobParameters['site.id']}") Integer siteId, String accountCodes,
            @Value("#{jobParameter['start.date']") String startDate, @Value("#{jobParameters['end.date']") String endDate) {

        JdbcCursorItemReader reader = new JdbcCursorItemReader();
        reader.setDataSource(datasource());
        reader.setSql(SqlConst.SELECT_ITEMIZED_BILLING_FOR_ACCOUNT_CODES);
        reader.setRowMapper(new ItemizedCostingMapper());
        reader.setPreparedStatementSetter((ps) -> {
            try {
                SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");

                ps.setTimestamp(0, new Timestamp(formatter.parse(startDate).getTime()));
                ps.setTimestamp(1, new Timestamp(formatter.parse(endDate).getTime()));
            } catch (Exception err) {
                LOG.error("Unable to parse dates, start: {} end: {}", startDate, endDate);
            }
            ps.setString(2, accountCodes);
            ps.setInt(3, siteId);
        });

        return reader;
    }

    @Bean
    @StepScope
    public ItemWriter<ItemizedCosting> itemizedWriter(@Value("start.date") String startDate,
            String partnerName) {

        DelimitedLineAggregator lineAgg = new DelimitedLineAggregator();
        FieldExtractor<ItemizedCosting> extractor = (f) -> {
            Object[] output = new Object[9];
            output[0] = f.getExtension();
            output[1] = f.getPbxCallTime();
            output[2] = f.getDuration();
            output[3] = f.getAccountCode();
            output[4] = f.getDigits();
            output[5] = f.getCost();
            output[6] = f.getDestination();
            output[7] = f.getCarrier();
            output[8] = f.getAttribute();

            return output;
        };
        lineAgg.setFieldExtractor(extractor);

        Timestamp start = null;

        try {
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");

            start = new Timestamp(formatter.parse(startDate).getTime());
        } catch (Exception e) {
            LOG.error("Unable to parse date: {}", startDate);
        }

        FlatFileItemWriter<ItemizedCosting> writer = new FlatFileItemWriter<>();
        writer.setEncoding("UTF-8");
        writer.setLineAggregator(lineAgg);
        writer.setResource(new FileSystemResource(String.format("%s/%2$tY-%2$tm_%s_",
                appProps.getItemizedBillingOutputPath(), start, partnerName)));

        return writer;
    }

    @Bean
    public Job createJob(JobBuilderFactory jobBuilder, StepBuilderFactory stepBuilders, DataSource ds, FlatFileItemReader reader)
            throws Exception {
        Step findSiteIdStep = stepBuilders.get("find.site.id").tasklet((contribution, chunkContext) -> {
            String siteName
                    = (String) chunkContext.getStepContext().getJobParameters().get(BillingConst.PARAM_SITE);

            Integer siteId = billingRepo.findSiteIdByName(siteName);

            chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext().put(
                    BillingConst.SITE_ID, siteId);

            return RepeatStatus.FINISHED;
        }).build();

        Step processFileStep = stepBuilders.get("process.file").<BillingItem, BillingItem>chunk(appProps.getChunkSize())
                .reader(reader)
                .processor(summaryProc())
                .writer(aspiviaWriter(ds, prepStatementSetter())).build();

        Step outputSummary = stepBuilders.get("output.summary").tasklet(summaryTask()).build();

        SimpleJobBuilder builder = jobBuilder.get("process.aspivia").incrementer(new RunIdIncrementer())
                .start(findSiteIdStep)
                .next(processFileStep)
                .next(outputSummary);

        List<ItemizedPartner> partners = billingRepo.findPartnersForSite("CPT");
        Integer siteId = billingRepo.findSiteIdByName("CPT");

        Map<String, String> partnerAccCodes = new HashMap<>();

        partners.stream().forEach(i -> {
            if (!partnerAccCodes.containsKey(i.getPartnerName())) {
                partnerAccCodes.put(i.getPartnerName(), "");
            }

            String accCodes = partnerAccCodes.get(i.getPartnerName());
            accCodes += i.getAccountCode().toString() + ", ";

            partnerAccCodes.put(i.getPartnerName(), accCodes);
        });

        partnerAccCodes.forEach((k, v) -> {
            Step itemizedReport = stepBuilders.get("itemized." + k).<ItemizedCosting, ItemizedCosting>chunk(appProps.getChunkSize())
                    .reader(itemizedReader(siteId, v, null, null))
                    .writer(itemizedWriter(null, k)).build();

            builder.next(itemizedReport);
        });

        return builder.build();
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer propCfg() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public DataSourceTransactionManager transactionManager(DataSource datasource) {
        return new DataSourceTransactionManager(datasource);
    }
}

Spring配置

from __future__ import unicode_literals
from tande.models import Project, Person
from django.db import models
from django.utils import timezone
from django.utils.text import slugify
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
from django.db.models.signals import pre_save
from django.conf import settings

# Create your models here.
class buildrisk(models.Model):
    risk = models.CharField(max_length=20, default = 'Select Risk')
    def __unicode__ (self):
            return self.risk

class sens(models.Model):
    sens = models.CharField(max_length=20, default = 'Select Sensitivity')
    def __unicode__ (self):
            return self.sens

class MOI(models.Model):
    method = models.CharField(max_length=20, default = 'Select Method')
    def __unicode__ (self):
            return self.method

def number():
    no = PartRequest.objects.count()
    if no == None:
        return "PR00001"
    else:
        return "PR00" + str(no + 100)

def snumber():
    no = PartRequest.objects.count()
    add = sum(map(int, str(no)))
    if no == None:
        return "SN00001"
    else:
        return "SN00" + str(no + 100 + add)
    # while n:
    #   sn, n = sn + n % 10, n // 10
    # return sn

class PartRequest(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1)
    part_request_number = models.CharField(_('Part Request Number'),max_length=10, default = number)
    serialnumber = models.CharField(_('Serial Number'),max_length=10, default= snumber)
    project_manager = models.ForeignKey(Person, related_name = 'Manager', null = True)
    requester = models.ForeignKey(Person, related_name = 'Requester', null = True)  #drop down
    project_id = models.ForeignKey(Project, related_name = 'Project', null=True)
    ordernumber = models.PositiveIntegerField(_('Order Number'), default=0)
    description = models.CharField(_('Description'), max_length=500)
    quantityrequired = models.PositiveIntegerField(_('Quantity'), default=0)
    sensitivity = models.ForeignKey(sens, related_name='sensitivity', null=True) #drop down
    identification_method = models.ForeignKey(MOI, related_name= 'MOI', null=True)
    build_risk = models.ForeignKey(buildrisk, related_name= 'Risk', null=True)
    daterequired = models.DateField(_('Date Required'), default = '04/08/16')
    slug = models.SlugField(unique=True, null=True)
    def __str__(self):
        return unicode(self.requester)

    def get_absolute_url(self):
        return "/buildpage/%s/" %(self.slug)
#return ("buildpage:page3", kwargs = {"slug": self.slug})

def create_slug(instance, new_slug=None):
    slug = slugify(instance.part_request_number)
    if new_slug is not None:
        slug = new_slug
    qs = PartRequest.objects.filter(slug=slug).order_by("-id")
    exists = qs.exists()
    if exists:
        new_slug = "%s-%s" %(slug, qs.first().id)
        return create_slug(instance, new_slug=new_slug)
    return slug

def pre_save_receiver(sender, instance, *args, **kwargs):
    if not instance.slug:
        instance.slug = create_slug(instance)
pre_save.connect(pre_save_receiver, sender = PartRequest)

1 个答案:

答案 0 :(得分:0)

问题在于弹簧批处理的生命周期。如果为@StepScope修饰bean,则作业参数仅在启动后才可用。

    final Job loadAspiviaDataJob = context.getBean(Job.class);
    final JobLauncher launcher = context.getBean(JobLauncher.class);

    JobParametersBuilder paramBuilder = new JobParametersBuilder();
    paramBuilder.addString(AspiviaConst.PARAM_INPUT_FILE, inputFile);
    paramBuilder.addString(AspiviaConst.PARAM_SITE, site);
    paramBuilder.addString(AspiviaConst.PARAM_OUTPUT_FILE_PATH, summaryFile);

JobExecution runStatus = launcher.run(loadAspiviaDataJob,paramBuilder.toJobParameters());

在上面的代码中,我们检索 Job ,它是在我的配置中通过 createJob bean方法设置的。作业参数不可用。

我所做的是访问我需要的值如下:

  • 添加了额外的@PropertySource(&#34;类路径:cli-runtime.properties&#34;)
  • 启动spring批处理作业的Application.java将保存cli-runtime.properties所需的属性。在@Configuration类中创建Job时,将从属性文件加载值,我可以在我需要的作业中创建其他步骤