Spring Batch批处理框架
批处理广泛应用于大数据场景
1、为什么需要批处理
大数据场景下,数据源的数据量一般比较大,不可能一次性将数据源数据拉到内存中,一方面硬件内存不支持,另一方面等待时间过长,在这样的背景下,批处理被提出。
2、批处理的特点
-
批处理按照一组一组的方式从数据源拉取数据到内存中,处理完成后,批量写入目标数据源。
-
由于是一组一组的(取数据 -> 处理数据 -> 写数据),即便是物理机内存不是很大,也能够处理大数据场景需求。
-
一般而言,大数据处理执行时间普遍比较长,中途可能会发生各种意外,那么记录运行元数据尤为必要。当发生意外时,能够从意外处继续重启执行,避免陷入从头再来的尴尬处境。
3、操作
-
导入依赖
<!--批处理--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-batch</artifactId> </dependency>
-
在application.yml编写配置
# 编写批处理的配置 spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql:///batch?useUnicode=true&characterEncoding=utf-8 username: root password: 123456 batch: # 在项目启动时执行建表sql job: # 禁止Spring Batch自动执行,既需要用户触发才能执行 enabled: false names: parentjob jdbc: # spring batch相关表的前缀,默认为batch_ table-prefix: batch_ initialize-schema: always sql: init: schema-locations: classpath:/org/springframework/batch/core/schema-mysql.sql
-
编写配置类
@Configuration @EnableBatchProcessing public class BatchConfig { /** * 用来读取数据 */ @Bean public ItemReader<BuUser> reader() { // FlatFileItemReader是一个用来加载文件的itemReader FlatFileItemReader<BuUser> reader = new FlatFileItemReader<>(); // 跳过第一行的标题 reader.setLinesToSkip(1); //设置csv的位置 reader.setResource(new ClassPathResource("data.csv")); // 设置每一行的数据信息 reader.setLineMapper(new DefaultLineMapper<BuUser>() {{ setLineTokenizer(new DelimitedLineTokenizer() {{ // 配置字段 setNames("userName", "sex", "age", "address"); // 配置列与列之间的间隔符,会通过间隔符对每一行进行切分 setDelimiter(","); }}); // 设置要映射的实体类属性 setFieldSetMapper(new BeanWrapperFieldSetMapper<BuUser>() {{ setTargetType(BuUser.class); }}); }}); return reader; } /** * 用来输出数据 */ @Bean public ItemWriter<BuUser> writer(DataSource dataSource) { // 通过JDBC写入数据库中 JdbcBatchItemWriter<BuUser> writer = new JdbcBatchItemWriter<>(); writer.setDataSource(dataSource); // setItemSqlParameterSourceProvider表示将实体类中的属性和占位符一一映射 writer.setItemSqlParameterSourceProvider( new BeanPropertyItemSqlParameterSourceProvider<>() ); // 设置要执行批处理的SQL语句,其中占位符的写法时 `:属性名` writer.setSql("insert into sys_user(user_name, sex, age, address)" + "values(:userName, :sex, :age, :address)"); return writer; } /** * 配置一个step */ @Bean public Step csvStep( StepBuilderFactory stepBuilderFactory, ItemReader<BuUser> reader, ItemWriter<BuUser> writer) { return stepBuilderFactory.get("cvsStep") // 批处理每次提交10条数据,就是每请求一次,提交一次 .<BuUser, BuUser>chunk(10) // 给step绑定 reader .reader(reader) // 给step绑定 writer .writer(writer) .faultTolerant() // 设定一个我们允许的这个step跳过的异常的数量,加入我们设定为3,则当这个step运行时,只要出现的异常数目不超过3 .skipLimit(3) // 指定我们可以跳过的异常,因为有些异常的出现,我们是可以忽略的 .skip(Exception.class) // 出现这个异常我们不想跳过,因此这种异常出现一次时,计数器就会加一,直到达到上限 .noSkip(FileNotFoundException.class) .build(); } /** * 配置一个要执行的Job任务,包含一个或多个Step */ @Bean public Job cvsJob(JobBuilderFactory jobBuilderFactory,Step step) { // 为job起名为cvsJob return jobBuilderFactory.get("cvsJob").start(step).build(); } }
-
数据实体类
@Data public class BuUser { private String userName; private String sex; private String age; private String address; }
-
编写控制器
@RestController public class JobController { @Autowired private JobLauncher jobLauncher; @Autowired private Job job; @GetMapping("/doJob") public void doJob(){ try { // 同一个job执行多次,由于job定义一样,则无法区分jobInstance,所以增加jobParameter用于区分 JobParametersBuilder jobParametersBuilder = new JobParametersBuilder(); jobParametersBuilder.addDate("jobDate", new Date()); // 执行一个批处理任务 jobLauncher.run(job, jobParametersBuilder.toJobParameters()); } catch (Exception e) { e.printStackTrace(); } } }
注意:上方写的<BuUser, BuUser>chunk(10)这个每请求一次写入10行数据,需要手动请求。