首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Spring Batch 實戰:高效讀取大量文本數據並批量寫入 MySQL

     編輯:Java 相關知識

面對日漸增長的用戶推薦數據,單純使用 POI 或逐條 JDBC 插入早已力不從心。本文示範如何利用 Spring Batch 的 Chunk-oriented 處理模型,結合 FlatFileItemReader、ItemProcessor 與 JdbcBatchItemWriter,實現從管道符分隔的文本文件讀取、數據清洗驗證,到批量入庫/更新的完整流程。

業務場景與技術選型

數據分析團隊每日產出包含 uid、tag、type 的文本文件,需完成:

  1. 讀取文件原始數據
  2. 清洗標籤(去除特殊符號、截斷過長內容)
  3. 根據 UID 判斷新增或更新 MySQL 記錄

考慮到數據量大、需高吞吐,選用 Spring Batch 而非 POI,並採用 MyBatis + Druid 連接池作為持久層支撐。

核心依賴配置

<dependencies>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-batch</artifactId></dependency>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
    <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.2.0</version></dependency>
    <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency>
    <dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.26</version></dependency>
    <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.12.6</version></dependency>
    <dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.4</version></dependency>
</dependencies>

領域模型

@Data @EqualsAndHashCode @NoArgsConstructor @AllArgsConstructor
public class UserMap extends BaseModel {
    @Column(name = "uid", unique = true, nullable = false) private Long uid;
    @Column(name = "tag") private String tag;
    @Column(name = "type") private Integer type;
}

批處理核心配置類

@Configuration @EnableBatchProcessing
public class BatchConfiguration {
    @Autowired private JobBuilderFactory jobBuilderFactory;
    @Autowired private StepBuilderFactory stepBuilderFactory;
    @Autowired @Qualifier("prodDataSource") DataSource prodDataSource;

    // 1. 讀取器:管道符分隔,映射到 UserMap
    @Bean
    public FlatFileItemReader<UserMap> reader() {
        FlatFileItemReader<UserMap> reader = new FlatFileItemReader<>();
        reader.setResource(new ClassPathResource("c152.txt"));
        reader.setLineMapper(new DefaultLineMapper<UserMap>() {{
            setLineTokenizer(new DelimitedLineTokenizer("|") {{
                setNames(new String[]{"uid", "tag", "type"});
            }});
            setFieldSetMapper(new BeanWrapperFieldSetMapper<UserMap>() {{
                setTargetType(UserMap.class);
            }});
        }});
        return reader;
    }

    // 2. 新增寫入器
    @Bean
    public JdbcBatchItemWriter<UserMap> importWriter() {
        JdbcBatchItemWriter<UserMap> w = new JdbcBatchItemWriter<>();
        w.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());
        w.setSql("INSERT INTO user_map (uid,tag,type) VALUES (:uid, :tag, :type)");
        w.setDataSource(prodDataSource);
        return w;
    }

    // 3. 更新寫入器
    @Bean
    public JdbcBatchItemWriter<UserMap> updateWriter() {
        JdbcBatchItemWriter<UserMap> w = new JdbcBatchItemWriter<>();
        w.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());
        w.setSql("UPDATE user_map SET type = :type, tag = :tag WHERE uid = :uid");
        w.setDataSource(prodDataSource);
        return w;
    }

    // 4. 處理器:根據狀態決定新增/更新邏輯
    @Bean
    public UserMapItemProcessor processor(UserMapItemProcessor.ProcessStatus status) {
        return new UserMapItemProcessor(status);
    }

    // 5. Job & Step 組裝
    @Bean public Job importUserJob(JobCompletionNotificationListener listener) { ... }
    @Bean public Step importStep() { return stepBuilderFactory.get("importStep")
        .<UserMap, UserMap>chunk(100).reader(reader()).processor(processor(IMPORT)).writer(importWriter()).build(); }
    @Bean public Job updateUserJob(JobCompletionNotificationListener listener) { ... }
    @Bean public Step updateStep() { return stepBuilderFactory.get("updateStep")
        .<UserMap, UserMap>chunk(100).reader(reader()).processor(processor(UPDATE)).writer(updateWriter()).build(); }
}

數據清洗處理器詳解

public class UserMapItemProcessor implements ItemProcessor<UserMap, UserMap> {
    private static final int MAX_TAG_LENGTH = 200;
    private static final Pattern TAG_PATTERN = Pattern.compile("^[a-zA-Z0-9\\u4E00-\\u9FA5_-]+$");
    private final ProcessStatus processStatus;
    @Autowired private IUserMapService userMapService;

    public UserMapItemProcessor(ProcessStatus status) { this.processStatus = status; }

    @Override
    public UserMap process(UserMap item) throws Exception {
        String cleanTag = cleanTag(item.getTag());
        Integer type = item.getType() != null ? item.getType() : 0;
        if (StringUtils.isBlank(cleanTag)) return null;

        UserMap exist = userMapService.selectOne(Map.of("uid", item.getUid()));
        if (exist == null) {
            return (processStatus == IMPORT) ? new UserMap(item.getUid(), cleanTag, type) : null;
        } else {
            if (processStatus == UPDATE && !cleanTag.equals(exist.getTag()) || !type.equals(exist.getType())) {
                exist.setTag(cleanTag); exist.setType(type);
                return exist;
            }
        }
        return null;
    }

    private static String cleanTag(String raw) {
        if (StringUtils.isBlank(raw)) return null;
        try {
            // 原始格式:Counter({'標籤1': 3, '標籤2': 2, ...})
            String inner = raw.substring(raw.indexOf("{")+1, raw.lastIndexOf("}"));
            return Arrays.stream(inner.split(","))
                .map(s -> s.split(":")[0].replaceAll("['\\s]", ""))
                .filter(TAG_PATTERN.asPredicate())
                .reduce((a, b) -> a + "," + b)
                .map(s -> s.length() > MAX_TAG_LENGTH ? s.substring(0, MAX_TAG_LENGTH) : s)
                .orElse(null);
        } catch (Exception e) { log.error("Tag clean failed", e); return null; }
    }

    enum ProcessStatus { IMPORT, UPDATE }
}

清洗邏輯利用 Java 8 Stream API 並行處理,正則過濾非法字符,最終以逗號拼接並截斷至 200 字符。

任務完成監聽器

@Component
public class JobCompletionNotificationListener extends JobExecutionListenerSupport {
    private final JdbcTemplate jdbcTemplate;
    @Autowired public JobCompletionNotificationListener(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; }
    @Override public void afterJob(JobExecution jobExecution) { System.out.println("Batch job finished."); }
}

運行效果

批處理執行結果截圖

啟動專案後,Spring Batch 會自動執行配置的 Job,將文本數據高效導入數據庫,控制台輸出「end .....」表示完成。

總結

Spring Batch 的 Chunk 模式(讀-處理-寫)配合批量 SQL,極大提升大數據量導入效能。本文展示的「讀取→清洗→分流新增/更新」模式,可直接套用至日誌分析、數據遷移等場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved