spring batch item writer详解


github地址:

https://github.com/a18792721831/studybatch.git

文章列表:

spring batch 入门

spring batch连接数据库

spring batch元数据

spring batch Job详解

spring batch step详解

spring batch ItemReader详解

spring batch itemProcess详解

spring batch itemWriter详解

spring batch 作业流

spring batch 健壮性

spring batch 扩展性

ItemWrite

spring batch通过Tasklet完成具体的任务,chunk类型的tasklet定义了标准的读、处理、写的执行步骤。ItemWriter是实现写的重要组件,spring batch框架提供了丰富的写基础设施来完成各种数据源的写入功能。

spring batch框架默认提供了丰富的Writer实现;如果不能满足需求可以快速方便地实现自定义的数据写入;对于已经存在的持久化服务,框架提供了复用现有服务的能力,避免重复开发。

spring batch框架通常针对大数据量进行处理,同时框架需要讲作业处理的状态实时地持久化到数据库中,如果读取一条记录就进行写操作或者状态数据的提交,会大量消耗系统资源,导致批处理框架性能较差。在面向批处理chunk的操作中,可以通过属性commit-interval设置read多少条记录后进行一次提交。通过设置commit-interval的间隔值,减少提交频次,降低资源使用率。

ItemWriter

ItemWriter是Step中对资源的写处理阶段,spring batch框架已经提供了各种类型的写实现。

所有的写操作需要实现ItemWriter接口

image-20201205161807119

写操作的参数是一个List,所以通常情况下是批量写入。

ItemStream

spring batch框架童年时提供了另外一个重要的接口ItemStream。ItemStream接口定义了写操作与执行上下文ExecutionContext交互的能力。可以将已经写的条数通过该接口存放在执行上下文ExecutionContext中(ExecutionContext中的数据在批处理commit的时候会通过JobRepository持久化到数据库中),这样到Job发生异常重新启动Job的时候,写操作可以跳过已经成功写过的数据,继续从上次出错的地方(可以从执行上下文中获取上次成功写的位置)继续写。

ItemStream接口

image-20201205162205841

open操作根据参数executionContext打开需要读取资源的stream;可以根据持久化在执行上下文executionContext中的数据重新定位需要写入记录的位置。

update操作将需要持久化的数据存放在执行上下文executionContext中

close操作关闭读取的资源

系统写组件

spring batch框架提供的写组件

ItemWriter说明
FlatFileItemWriter写Flat类型文件
MultiResourceItemWriter多文件写组件
StaxEventItemWriter写XML类型文件
AmqpItemWriter写AMQP类型消息
ClassifierCompositeItemWriter根据Classifier路由不同的Item到特定的ItemWriter处理
HibernateItemWriter基于Hibernate方式写数据库
IbatisBatchItemWriter基于Ibatis方式写数据库
ItemWriterAdapterItemWriter适配器,可以复用现有的写服务
JdbcBatchItemWriter基于JDBC方式写数据库
JmsItemWriter写JMS队列
JpaItemWriter基于Jpa方式写数据库
GemfireItemWriter基于分布式数据库Gemfire的写组件
SpELMappingGemfireItemWriter基于spring表达式语言写分布式数据库Gemfire的组件
MimeMessageItemWriter发送邮件的写组件
MongoItemWriter基于分布式文件存储的数据库MongoDB写组件
Neo4jItemWriter面向网络的数据库Neo4j的写组件
PropertyExtractingDelegatingItemWriter属性抽取代理写组件:通过调用给定的spring bean方法执行写入,参数有Item中指定的属性字段作为参数
RepositoryItemWriter基于spring Data的写组件
SimpleMailMessageItemWriter发送邮件的写组件
CompositeItemWriter条目写的组合模式,支持组装多个ItemWriter

写数据库

spring batch框架对于写数据库提供了较好的支持,包括基于JDBC和ORM的写入方式。

JdbcBatchItemWriter

spring batch框架提供了对JDBC谢支持的组件JdbcBatchItemWriter。JdbcBatchItemWriter实现了ItemWriter接口,将Item对象转换为数据库中的记录。

JdbcBatchItemWriter对用户屏蔽了数据库访问的操作细节,且提供了批处理的特性,JdbcBatchItemWriter会批量执行一组SQL语句来提高性能,而不是逐条执行SQL语句,每次批量提交的语句数和chunk中定义的提交间隔是一致的。

JdbcBatchItemWriter关键接口

关键类说明
DataSource提供写入数据库的数据源信息
ItemPreparedStatementSetter为SQL语句中有"?"的参数提供赋值接口
ColuniMapItemPreparedStatementSetter接口ItemPreparedStatementSetter的实现类,提供基于列的参数设置
ItemSqlParameterSourceProvider为SQL语句中有命名的参数提供赋值接口
BeanPropertyItemSqlParameterSourceProvider从给定的Item中根据参数名称获取Item对应的属性值作为参数
NamedParameterJdbcOperationsJdbcTemplate操作,提供执行SQL的能力

JdbcBatchItemWriter关键属性

JdbcBatchItemWriter属性类型说明
dataSourecDataSource数据源,通过该属性指定使用的数据库信息
sqlString执行的SQL语句
itemSqlParameterSourceProviderItemSqlParameterSourceProvider为SQL语句中有命名的参数提供赋值
itemPreparedStatementSetterItemPreparedStatementSetter为SQL语句中有"?"的参数提供赋值
assertUpdatesBoolean当没有修改、删除一条记录时,是否抛出异常。默认抛出

使用JdbcBatchItemWriter至少需要配置dataSource,sql两个属性。dataSourec指定访问的数据源,sql用于指定处查询的SQL语句。

首先我们在数据库中创建表

image-20201205171611159

接着创建JdbcBatchItemWriter的写入组件

image-20201205175642396

然后使用这个写入器,完整代码

@EnableBatchProcessing
@Configuration
public class JdbcBatchItemWriterJobConf {

    @Bean
    public String runJob(JobLauncher jobLauncher, Job job) throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
        jobLauncher.run(job, new JobParametersBuilder()
                .addDate("date", new Date()).toJobParameters());
        return "";
    }

    @Bean
    public Job job(JobBuilderFactory jobBuilderFactory, Step step) {
        return jobBuilderFactory.get("jdbc-batch-item-writer-step")
                .start(step)
                .build();
    }

    @Bean
    public Step step(StepBuilderFactory stepBuilderFactory, JdbcBatchItemWriter<People> writer) {
        AtomicLong atomicLong = new AtomicLong();
        return stepBuilderFactory.get("jdbc-batch-item-writer-step")
                .<People, People>chunk(3)
                .reader(() -> atomicLong.get() > 20 ? null : new People(null, "name : " + atomicLong.getAndIncrement()))
                .processor((Function<People, People>) item -> {
                    System.out.println("process : " + item);
                    return item;
                })
                .writer(writer)
                .build();
    }

    @Bean
    public JdbcBatchItemWriter<People> writer(DataSource dataSource) {
        return new JdbcBatchItemWriterBuilder<People>()
                .dataSource(dataSource)
                .sql("insert into people(id,name) values(null,?)")
                .itemPreparedStatementSetter(((item, ps) -> ps.setString(1, item.getName())))
                .assertUpdates(false)
                .build();
    }

}

执行结果

image-20201205175728974

数据库中查看结果

image-20201205175753272

我们这里使用的是问号,也可以使用变量名

image-20201205175908220

这次少写入点,写入10个

image-20201205175955925

数据库中也有了

image-20201205180012514

JpaItemWriter

对象关系映射(Object Relational Mapping,ORM)是一种为解决面向对象与关系数据库存在的互不匹配现象的技术。简单地说,ORM是通过使用描述对象和数据库之间映射的元数据,将Java程序中的对象自动持久化到关系数据库中。

JPA(Java Persistence API)是Sun官方提出的Java持久化规范。JPA通过注解或者XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中;它为Java开发人员提供了一种对象/关系映射工具来管理Java应用中的关系数据。spring batch框架对ORM类型的JPA提供了基于写的ItemWriter。

JpaItemWriter实现了ItemWriter接口,核心作用是将Item对象转换为数据库中的记录。

JpaItemWriter关键属性

JpaItemWriter类型说明
entityManagerFactoryEntityManagerFactoryJPA提供的实体管理器的工厂类,用于生成实体管理EntityManager对象

使用JpaItemWriter需要配置属性entityManagerFactory:entityManagerFactory负责创建EntityManager,EntityManager负责完成对实体的增删改查。

首先引入JPA的依赖

image-20201205181431233

接着配置Jpa的EntityManagerFactory

image-20201207085031412

除此之外,还需要配置Jpa事务管理器(Jpa有默认的,事务级别和spring相同,做了适配)

image-20201207085115566

整体代码

@EnableBatchProcessing
@Configuration
public class JpaItemWriterJobConf {

    @Bean
    public String runJob(JobLauncher jobLauncher, Job job) throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
        jobLauncher.run(job, new JobParametersBuilder()
                .addDate("date", new Date()).toJobParameters());
        return "";
    }

    @Bean
    public Job job(JobBuilderFactory jobBuilderFactory, Step step) {
        return jobBuilderFactory.get("jpa-item-writer-step")
                .start(step)
                .build();
    }

    @Bean
    public Step step(StepBuilderFactory stepBuilderFactory, JpaItemWriter<People> writer,JpaTransactionManager jpaTransactionManager) {
        AtomicLong atomicLong = new AtomicLong();
        return stepBuilderFactory.get("jpa-item-writer-step")
                .<People, People>chunk(3)
                .reader(() -> atomicLong.get() > 10 ? null : new People(null, "jpa : " + atomicLong.getAndIncrement()))
                .processor((Function<People, People>) item -> {
                    System.out.println("jpa : " + item);
                    return item;
                })
                .writer(writer)
                .transactionManager(jpaTransactionManager)
                .build();
    }

    @Bean
    public JpaItemWriter<People> writer(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
        return new JpaItemWriterBuilder<People>()
                .entityManagerFactory(entityManagerFactoryBean.getObject())
                .build();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(DataSource dataSource, EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(dataSource)
                .packages(Study8ItemwriterApplication.class)
                .build();
    }

    @Bean
    public JpaTransactionManager transactionManager(DataSource dataSource) {
        final JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

执行结果

image-20201207085204404

数据库查询

image-20201207085219456

MyBatisItemWriter

我们常用的ORM还有MyBatis,相比JPA,MyBatis的SQL可能更加直观(xml方式)。

MyBatisItemWriter需要指定数据源和SQL。在MyBatis中配置的是SqlSessionFactory.

首先引入MyBatis的依赖

image-20201207085246245

接着配置MyBatis的SqlSessionFactory

image-20201207091441242

增加MyBatis的接口

image-20201207091507759

以及XML文件

image-20201207091524061

增加扫描的Mapper注解

image-20201207091541289

设置实体的作用域

image-20201207091602935

创建MyBatis的写入器

image-20201207091628017

assertUpdates默认为true,表示当1条记录都没有被插入时,会抛出异常。设置为false,则忽略这个问题 ,即使没有数据被插入,也不会抛出异常。

同样的,对于MyBatis,一定要保证Mapper先读取

image-20201207091753651

Job的完整代码

@EnableBatchProcessing
@Configuration
public class MyBatisBatchItemWriterJobConf {

    @Bean
    public String runJob(JobLauncher jobLauncher, Job job, PeopleDao peopleDao) throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
        jobLauncher.run(job, new JobParametersBuilder()
                .addDate("date", new Date()).toJobParameters());
        return "";
    }

    @Bean
    public Job job(JobBuilderFactory jobBuilderFactory, Step step) {
        return jobBuilderFactory.get("mybatis-batch-item-writer-step")
                .start(step)
                .build();
    }

    @Bean
    public Step step(StepBuilderFactory stepBuilderFactory, MyBatisBatchItemWriter<People> writer) {
        AtomicLong atomicLong = new AtomicLong();
        return stepBuilderFactory.get("mybatis-batch-item-writer-step")
                .<People, People>chunk(3)
                .reader(() -> atomicLong.get() > 10 ? null : new People(null, "mybatis : " + atomicLong.getAndIncrement()))
                .processor((Function<People, People>) item -> {
                    System.out.println("mybatis : " + item);
                    return item;
                })
                .writer(writer)
                .build();
    }

    @Bean
    public MyBatisBatchItemWriter<People> writer(SqlSessionFactoryBean sqlSessionFactoryBean) throws Exception {
        return new MyBatisBatchItemWriterBuilder<People>()
                .sqlSessionFactory(sqlSessionFactoryBean.getObject())
                .assertUpdates(false)
                .statementId("addPeople")
                .build();
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource bossDataSource) {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(bossDataSource);
        factoryBean.setTypeAliasesPackage("com.study.study8itemwriter.domain");
        return factoryBean;
    }

}

执行结果

image-20201207091828518

数据库记录

image-20201207091839177

组合写

在spring batch框架中对于chunk只能配置一个ItemWriter,但在有些业务场景中需要将一个Item同时写到多个不同的资源文件中,即需要写入到多个ItemWriter中。spring batch框架提供了组合ItemWriter(CompositeItemWriter)的模式满足需求。

但是吧,经过我自己验证,对于同一个数据库,同时使用MyBtais和Jpa,似乎只有Jpa起作用。

我们在上面的例子中进行:

首先是两个Jpa的writer

image-20201207163521980

以及一个MyBatis的Writer

image-20201207163543642

最后全部扔到组合写处理器中

image-20201207163628356

启动

image-20201207163647478

发现数据库中只存入了5条记录,而不是预期的15条

image-20201207163718146

而多数据源,或者不同种类的数据源,是能预期写入的。

如果我们去除Jpa,只有Mybatis

image-20201207170612018

完整代码

@EnableBatchProcessing
@Configuration
public class CompositeItemWriterJobConf {

    @Bean
    public String runJob(JobLauncher jobLauncher, Job job, PeopleDao peopleDao) throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
        jobLauncher.run(job, new JobParametersBuilder()
                .addDate("date", new Date()).toJobParameters());
        return "";
    }

    @Bean
    public Job job(JobBuilderFactory jobBuilderFactory, Step step) {
        return jobBuilderFactory.get("compo-batch-item-writer-step")
                .start(step)
                .build();
    }

    @Bean
    public Step step(StepBuilderFactory stepBuilderFactory, MyBatisBatchItemWriter<People> myBatisWriter1, MyBatisBatchItemWriter<People> myBatisWriter2) {
        AtomicLong atomicLong = new AtomicLong();
        CompositeItemWriter<People> writer = new CompositeItemWriterBuilder<People>().delegates(Arrays.asList(myBatisWriter1, myBatisWriter2))
                .ignoreItemStream(true).build();
        return stepBuilderFactory.get("compo-batch-item-writer-step")
                .<People, People>chunk(3)
                .reader(() -> atomicLong.get() > 5 ? null : new People(null, "compo : " + atomicLong.getAndIncrement()))
                .processor((Function<People, People>) item -> {
                    System.out.println("compo : " + item);
                    return item;
                })
                .writer(writer)
                .build();
    }

    @Bean
    public MyBatisBatchItemWriter<People> myBatisWriter1(SqlSessionFactoryBean sqlSessionFactoryBean) throws Exception {
        return new MyBatisBatchItemWriterBuilder<People>()
                .sqlSessionFactory(sqlSessionFactoryBean.getObject())
                .assertUpdates(false)
                .statementId("addPeople")
                .build();
    }

    @Bean
    public MyBatisBatchItemWriter<People> myBatisWriter2(SqlSessionFactoryBean sqlSessionFactoryBean) throws Exception {
        return new MyBatisBatchItemWriterBuilder<People>()
                .sqlSessionFactory(sqlSessionFactoryBean.getObject())
                .assertUpdates(false)
                .statementId("addPeople")
                .build();
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource bossDataSource) {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(bossDataSource);
        factoryBean.setTypeAliasesPackage("com.study.study8itemwriter.domain");
        return factoryBean;
    }
}

数据还是5条

image-20201207170631566

但是数据库中存入了10条

image-20201207170653236

因为我们配置的是chunk3,所以每3条数据为一组。

Item路由Writer

在组合写中,我们是将1条记录,写到多处。

对于每一条记录都是多处写。相当于1条记录,拷贝了好多份。

还有一些场景:对于全部的数据,我们只希望存储1份,但是根据数据的重要程度,存储在不同的设备上。

比如:奇数记录在A数据库,偶数记录在B数据库。

总体还是1份数据。

spring batch框架提供了支持Item路由写的组件ClassifierCompositeItemWriter。

image-20201207171108204

我们在原有代码的基础上,增加两个方法:

image-20201207180641769

在真正持久化的时候,会标出是哪个方法写入的

image-20201207180753074

接着创建这两个写入器

image-20201207180823601

然后配置

image-20201207180947762

当然还有其他的路由器

image-20201207181010896

完整代码

@EnableBatchProcessing
@Configuration
public class ClassifierCompositeItemWriterJobConf {

    @Bean
    public String runJob(JobLauncher jobLauncher, Job job, PeopleDao peopleDao) throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
        jobLauncher.run(job, new JobParametersBuilder()
                .addDate("date", new Date()).toJobParameters());
        return "";
    }

    @Bean
    public Job job(JobBuilderFactory jobBuilderFactory, Step step) {
        return jobBuilderFactory.get("compo-batch-item-writer-step")
                .start(step)
                .build();
    }

    @Bean
    public Step step(StepBuilderFactory stepBuilderFactory, MyBatisBatchItemWriter<People> myBatisWriter1, MyBatisBatchItemWriter<People> myBatisWriter2) {
        AtomicLong atomicLong = new AtomicLong();
        ClassifierCompositeItemWriter<People> itemWriter = new ClassifierCompositeItemWriterBuilder<People>()
                .classifier(new BackToBackPatternClassifier<People, ItemWriter<? super People>>(people -> {
            String string = people.getName();
            return string.substring(string.lastIndexOf(":") + 1, string.length());
        }, str -> {
            Integer integer = Integer.parseInt(str.trim());
            if (integer % 2 == 0) {
                return myBatisWriter2;
            }
            return myBatisWriter1;
        })).build();
        return stepBuilderFactory.get("compo-batch-item-writer-step")
                .<People, People>chunk(3)
                .reader(() -> atomicLong.get() > 5 ? null : new People(null, "compo : " + atomicLong.getAndIncrement()))
                .processor((Function<People, People>) item -> {
                    System.out.println("compo : " + item);
                    return item;
                })
                .writer(itemWriter)
                .build();
    }

    @Bean
    public MyBatisBatchItemWriter<People> myBatisWriter1(SqlSessionFactoryBean sqlSessionFactoryBean) throws Exception {
        return new MyBatisBatchItemWriterBuilder<People>()
                .sqlSessionFactory(sqlSessionFactoryBean.getObject())
                .assertUpdates(false)
                .statementId("addPeople1")
                .build();
    }

    @Bean
    public MyBatisBatchItemWriter<People> myBatisWriter2(SqlSessionFactoryBean sqlSessionFactoryBean) throws Exception {
        return new MyBatisBatchItemWriterBuilder<People>()
                .sqlSessionFactory(sqlSessionFactoryBean.getObject())
                .assertUpdates(false)
                .statementId("addPeople2")
                .build();
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource bossDataSource) {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(bossDataSource);
        factoryBean.setTypeAliasesPackage("com.study.study8itemwriter.domain");
        return factoryBean;
    }

}

执行结果

image-20201207181050915

数据库中的记录

image-20201207181104861

可以很明显的看出,奇数使用的是1写入器,偶数使用2写入器。

服务复用

复用现有的企业资产和服务是提高企业应用开发的快捷手段,spring batch框架的写组件提供了复用现有服务的能力,利用spring batch框架提供的ItemWriterAdapter、PropertyExtractingDelegatingItemWriter可以方便地复用业务服务、spring bean、EJB或者其他远程服务。ItemWriterAdapter代理的现有服务需要能够处理Item对象;PropertyExtractingDelegatingItemWriter代理的服务支持更复杂的参数,参数可以根据指定的属性从Item中抽取。

ItemWriterAdapter

ItemWriterAdapter关键属性

ItemWriterAdapter属性类型说明
targetObjectObject需要调用的目标服务对象
targetMethodString需要调用的目标操作名称
argumentsObject[]需要调用的操作参数。默认不需要传入参数,默认情况下,会将每次处理的item作为参数传入

首先创建一个简单服务

image-20201207182847104

接着创建相关的job使用这个Service

创建写入器,并使用

image-20201207182931454

完整代码

@EnableBatchProcessing
@Configuration
public class ItemWriterAdapterJobConf {

    @Bean
    public String runJob(JobLauncher jobLauncher, Job job) throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
        jobLauncher.run(job, new JobParametersBuilder()
                .addDate("date", new Date()).toJobParameters());
        return "";
    }

    @Bean
    public Job job(JobBuilderFactory jobBuilderFactory, Step step) {
        return jobBuilderFactory.get("item-writer-adapter-job")
                .start(step)
                .build();
    }

    @Bean
    public Step step(StepBuilderFactory stepBuilderFactory, ItemWriterAdapter<People> writer) {
        AtomicLong atomicLong = new AtomicLong();
        return stepBuilderFactory.get("item-writer-adapter-step")
                .<People, People>chunk(3)
                .reader(() -> atomicLong.get() > 5 ? null : new People(null, "name : " + atomicLong.getAndIncrement()))
                .processor((Function<People, People>) item -> {
                    System.out.println("process : " + item);
                    return item;
                })
                .writer(writer)
                .build();
    }

    @Bean
    public ItemWriterAdapter<People> writer(PeopleService peopleService) {
        ItemWriterAdapter writerAdapter = new ItemWriterAdapter();
        writerAdapter.setTargetObject(peopleService);
        writerAdapter.setTargetMethod("print");
        return writerAdapter;
    }
}

执行结果

image-20201207183000079

PropertyExtractingDelegatingItemWriter

PropertyExtractingDelegatingItemWriter代理的服务支持更加复杂的参数 ,参数可以根据指定的属性值从item中抽取(ItemWriterAdapter仅支持参数类型为具体的Item对象)。

比如增加如下服务

image-20201207184313388

创建写入器

image-20201207184348357

完整代码

@EnableBatchProcessing
@Configuration
public class PropertyExtractingDelegatingJobConf {

    @Bean
    public String runJob(JobLauncher jobLauncher, Job job) throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
        jobLauncher.run(job, new JobParametersBuilder()
                .addDate("date", new Date()).toJobParameters());
        return "";
    }

    @Bean
    public Job job(JobBuilderFactory jobBuilderFactory, Step step) {
        return jobBuilderFactory.get("pro-item-writer-adapter-job")
                .start(step)
                .build();
    }

    @Bean
    public Step step(StepBuilderFactory stepBuilderFactory, PropertyExtractingDelegatingItemWriter<People> writer) {
        AtomicLong atomicLong = new AtomicLong();
        return stepBuilderFactory.get("pro-item-writer-adapter-step")
                .<People, People>chunk(3)
                .reader(() -> atomicLong.get() > 5 ? null : new People(null, " " + atomicLong.getAndIncrement()))
                .processor((Function<People, People>) item -> {
                    System.out.println("process : " + item);
                    return item;
                })
                .writer(writer)
                .build();
    }

    @Bean
    public PropertyExtractingDelegatingItemWriter<People> writer(PeopleService peopleService) {
        PropertyExtractingDelegatingItemWriter<People> writer = new PropertyExtractingDelegatingItemWriter<>();
        writer.setTargetObject(peopleService);
        writer.setTargetMethod("printName");
        writer.setFieldsUsedAsTargetMethodArguments(new String[]{"name"});
        return writer;
    }

}

执行结果

image-20201207184416996

自定义ItemWriter

spring batch框架提供了丰富的ItemWriter组件,当这些默认的系统组件不能满足需求时,我们可以自己实现ItemWriter接口来完成需要的业务操作。自定义实现ItemWriter非常简单,只需要实现接口ItemWriter接口。只实现ItemWriter接口的写入器不支持重启,为了支持可重启的自定义ItemWriter需要新增实现接口ItemStream。

不可重启ItemWriter

接口定义

image-20201207184701772

我们实现一个自己的写入器

image-20201207185759822

然后使用这个写入器

@EnableBatchProcessing
@Configuration
public class MyItemWriterJobConf {

    @Bean
    public String runJob(JobLauncher jobLauncher, Job job) throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
        jobLauncher.run(job, new JobParametersBuilder()
                .toJobParameters());
        return "";
    }

    @Bean
    public Job job(JobBuilderFactory jobBuilderFactory, Step step) {
        return jobBuilderFactory.get("my-item-writer-job")
                .start(step)
                .build();
    }

    @Bean
    public Step step(StepBuilderFactory stepBuilderFactory) {
        AtomicLong atomicLong = new AtomicLong();
        return stepBuilderFactory.get("my-item-writer-step")
                .<People, People>chunk(3)
                .reader(() -> atomicLong.get() > 5 ? null : new People(null, "name : " + atomicLong.getAndIncrement()))
                .processor((Function<People, People>) item -> {
                    System.out.println("process : " + item);
                    return item;
                })
                .writer(new MyItemWriter<People>())
                .build();
    }

}

我们写的写入器,在写包含4这个数据的时候,一定会抛出异常。

image-20201207190044965

在不修改参数的情况下重启

image-20201207190217501

它还是从0开始,从头开始。不能接着上次写入失败的记录或者chunk继续写入。

初次之外,如果写入器比较简单,也可以直接使用lambda表达式写

image-20201207191012080

其结果是相同的

image-20201207191226254

可重启ItemWriter

spring batch框架对job提供了可重启的能力,spring batch框架提供的写组件中,文件写入等没有事务管理的都实现了ItemStream接口。

和ItemReader不一样的是,ItemReader的系统组件基本上都实现了ItemStream接口,而ItemWriter仅有部分的系统组件实现了ItemStream接口。因为通常情况下如果写的资源本身是事务性的,那么单个写入失败,会导致真个事务失败,从而导致本批次写入失败。所以下次写入时,需要从开始重头写入。因此本身具有事务性的写操作不需要事先ItemStream就支持可重启的特性。

如果写操作本身是有状态的,为了支持可重启的特性必须实现ItemStream。

实现我们自己的可重启的写入器。

image-20201207193706090

核心写入逻辑相同,遇到4的时候异常,但是需要用到我们保存的number

image-20201207200147357
然后使用自定义的可重启的写入器

image-20201207193804849

完整代码

@EnableBatchProcessing
@Configuration
public class RestartItemWriterJobConf {

    @Bean
    public String runJob(JobLauncher jobLauncher, Job job) throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
        jobLauncher.run(job, new JobParametersBuilder()
                .toJobParameters());
        return "";
    }

    @Bean
    public Job job(JobBuilderFactory jobBuilderFactory, Step step) {
        return jobBuilderFactory.get("restartv-item-writer-job")
                .start(step)
                .build();
    }

    @Bean
    public Step step(StepBuilderFactory stepBuilderFactory) {
        AtomicLong atomicLong = new AtomicLong();
        return stepBuilderFactory.get("restart-item-writer-step")
                .<People, People>chunk(3)
                .reader(() -> atomicLong.get() > 5 ? null : new People(null, "name : " + atomicLong.getAndIncrement()))
                .processor((Function<People, People>) item -> {
                    System.out.println("process : " + item);
                    return item;
                })
                .writer(new RestartItemWriter<People>())
                .build();
    }

}

第一次执行异常

image-20201207193830028

第二次执行

image-20201207200019839

直接从上次失败的地方开始写入。核心实现还是我们的read方法如何处理从exectionContext中拿到的记录号。

拦截器

spring batch框架在ItemWriter执行阶段提供了拦截器,使得在ItemWriter执行前后能够加入自定义的业务逻辑。

接口

接口定义

image-20201207200342737

实现自己的写入拦截器

image-20201207200717094

然后使用自己的写入拦截器

image-20201207201616598

运行结果

image-20201207201713814

完整代码

@EnableBatchProcessing
@Configuration
public class MyItemWriterLisJobConf {

    @Bean
    public String runJob(JobLauncher jobLauncher, Job job) throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
        jobLauncher.run(job, new JobParametersBuilder()
                .addLong("id", 2L).toJobParameters());
        return "";
    }

    @Bean
    public Job job(JobBuilderFactory jobBuilderFactory, Step step) {
        return jobBuilderFactory.get("my-item-writer-lis-job")
                .start(step)
                .build();
    }

    @Bean
    public Step step(StepBuilderFactory stepBuilderFactory) {
        AtomicLong atomicLong = new AtomicLong();
        return stepBuilderFactory.get("my-item-writer-lis-step")
                .<People, People>chunk(3)
                .reader(() -> atomicLong.get() > 5 ? null : new People(atomicLong.getAndIncrement(), "lis"))
                .processor((Function<People, People>) item -> {
                    System.out.println(" process : " + item);
                    return item;
                })
                .writer(items -> {
                    for (People people : items) {
                        if (people.getId() == 4) {
                            throw new RuntimeException("people id is 4");
                        }
                        System.out.println(" writer : " + people);
                    }
                })
                .listener(new MyItemWriterLis())
                .build();
    }

}

Annotation

除了实现接口,还可以使用注解定义拦截器:

  • @BeanforeWriter
  • @AfterWriter
  • @OnWriterError

比如

image-20201207201913282

使用

image-20201207201945952

结果

image-20201207202332235

执行顺序

配置的多个ItemWriterListener,拦截器之间的执行顺序按照配置的顺序执行。beforeWriter方法和配置顺序完全相同,afterWriter方法和配置顺序完全相反。

onWriteError方法和配置顺序相同

image-20201207202332235

拦截器异常

拦截器异常会导致整个Job异常,所以在执行自定义的拦截器的时候,需要考虑对拦截器发生的异常做处理,避免影响业务。

比如

image-20201207202730315

执行结果

image-20201207202859319

确实是因为我们抛出的异常导致失败

image-20201207202933349

Job失败

image-20201207202843291

Merge

spring batch框架提供了多处配置拦截器执行,可以在chunk配置,也可以在tasklet配置。而且基于step的抽象和继承,可以在子step中控制是否执行父step。

通过在子step中使用super调用父step的监听,就可以实现将父、子step的拦截器全部注册。

如果在子step中没有调用父step中注册拦截器的方法,那么父step中的拦截器就不会注册,也就不会执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值