乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 批处理概述与 Spring Batch 核心理念

    • Spring Batch 概述
    • Job-Instance-Execution 三层生命周期
    • 三层架构
    • Chunk 处理模型
    • Tasklet 处理模型
  • 第2章 Job与作业配置

    • Job 详解
    • Job 配置
    • JobLauncher 详解
    • JobParameters 详解
    • JobRepository 详解
  • 第3章 Step与执行模型

    • Step 详解
    • Step 配置
    • ExecutionContext 详解
    • ItemStream 与状态管理
    • StepScope 与 JobScope
  • 第4章 ItemReader数据读取

    • ItemReader 详解
    • FlatFileItemReader 详解
    • JdbcItemReader 详解
    • MultiResourceItemReader 详解
  • 第5章 ItemProcessor 数据处理

    • ItemProcessor 详解
  • 第6章 ItemWriter 数据写出

    • ItemWriter 详解
    • FlatFileItemWriter 详解
    • CompositeItemWriter 详解
    • JdbcBatchItemWriter 详解
  • 第7章 Chunk 处理与事务边界

    • Chunk 处理模型详解
    • 事务边界
  • 第8章 作业参数与启动

    • 命令行与 Web 启动
  • 第9章 监听器与拦截器

    • 监听器详解
  • 第10章 重启重试与跳过策略

    • 重启与重试
    • 跳过策略
  • 第11章 作业流与条件决策

    • 作业流与条件决策
  • 第12章 分区与并行处理

    • 分区详解
    • 并行 Step 与 Split
  • 第13章 与 Spring Boot 集成实践

    • Spring Boot 集成
  • 第15章 运维与监控

    • 大作业设计模式
    • 运维与监控

JdbcItemReader 详解

本章定位:深入掌握从数据库读取数据的两种模式——JdbcCursorItemReader 和 JdbcPagingItemReader,理解它们的原理、配置、线程安全性和选型标准。

定义与作用

Spring Batch 提供了两种从 JDBC 数据源读取数据的内置 Reader:

Reader读取模式核心机制
JdbcCursorItemReader游标(Cursor)打开 PreparedStatement,逐行 ResultSet.next()
JdbcPagingItemReader分页(Paging)每页执行 SELECT ... LIMIT ? OFFSET ?

飞翔科技架构师白歌的区分:

"游标模式像用吸管喝饮料——一根吸管插到底,一口一口吸。分页模式像用小杯子舀——每次舀一杯喝完,放下杯子,再拿新杯子舀下一杯。"

核心原理

两种模式对比

性能特征对比

维度Cursor 模式Paging 模式
数据库连接全程占用 1 个连接每页获取/释放
数据库端开销低(游标保持打开)略高(每页 SQL 解析)
内存占用低(逐行)低到中(每页缓存)
线程安全否是(分页参数隔离)
适合数据量< 10 万任意大小
分区支持不支持支持
必须排序否是(sortKeys 必须设置)

完整示例

场景一:游标模式——中小数据量的学生查询

@Bean
public JdbcCursorItemReader<Student> studentCursorReader(DataSource dataSource) {
    return new JdbcCursorItemReaderBuilder<Student>()
            .name("studentCursorReader")
            .dataSource(dataSource)
            .sql("SELECT id, name, email, age FROM students WHERE status = 'ACTIVE'")
            .rowMapper(new BeanPropertyRowMapper<>(Student.class))
            .fetchSize(100)        // JDBC fetchSize 提示
            .driverSupportsAbsolute(false)
            .saveState(true)       // 支持断点续传
            .maxItemCount(10000)   // 最多读取 1 万条
            .build();
}

场景二:分页模式——千万级订单数据

@Bean
@StepScope
public JdbcPagingItemReader<Order> orderPagingReader(
        DataSource dataSource,
        @Value("#{jobParameters['orderDate']}") String orderDate) {

    Map<String, Object> parameters = new HashMap<>();
    parameters.put("orderDate", orderDate);
    parameters.put("status", "PENDING");

    return new JdbcPagingItemReaderBuilder<Order>()
            .name("orderPagingReader")
            .dataSource(dataSource)
            .selectClause("SELECT order_id, customer_name, amount, created_at")
            .fromClause("FROM orders")
            .whereClause("WHERE order_date = :orderDate AND status = :status")
            .parameterValues(parameters)
            .sortKeys(Map.of("order_id", Order.ASCENDING))  // ⚠️ 必须排序
            .rowMapper((rs, rowNum) -> {
                Order order = new Order();
                order.setOrderId(rs.getLong("order_id"));
                order.setCustomerName(rs.getString("customer_name"));
                order.setAmount(rs.getBigDecimal("amount"));
                order.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());
                return order;
            })
            .pageSize(500)        // 每页 500 条
            .saveState(true)
            .build();
}

操作前后对比:

操作前:手动 JDBC
  Connection conn = dataSource.getConnection();
  PreparedStatement ps = conn.prepareStatement("SELECT * FROM orders");
  ResultSet rs = ps.executeQuery();
  // 500 万条数据 → 内存暴增?逐条处理 → 连接长时间占用?
  while (rs.next()) { ... }

操作后:JdbcPagingItemReader
  Page 1: SELECT ... LIMIT 500 OFFSET 0    → 连接释放
  Page 2: SELECT ... LIMIT 500 OFFSET 500  → 连接释放
  ...
  Page 10000: SELECT ... LIMIT 500 OFFSET 4999500 → 连接释放
  崩溃后自动从上次页码恢复

易错场景与避坑

反例一:Paging 模式忘记设置 sortKeys

// ❌ 不设置 sortKeys → IllegalArgumentException
return new JdbcPagingItemReaderBuilder<Order>()
        .name("reader")
        .dataSource(dataSource)
        .selectClause("SELECT * FROM orders")
        .fromClause("FROM orders")
        .pageSize(100)
        // 缺少 .sortKeys() → 启动报错!
        .build();
// IllegalArgumentException: sortKey is required for paging

原因:分页依赖 ORDER BY 保证每次分页的数据范围一致。没有排序 → 同一条数据可能出现在两个分页中(重复处理)或漏掉。

反例二:Cursor 模式用于分区 Step

// ❌ Partition 场景下 JdbcCursorItemReader 非线程安全
@Bean
@StepScope
public JdbcCursorItemReader<Order> reader(DataSource ds) {
    return new JdbcCursorItemReaderBuilder<Order>()
            .name("reader")
            .dataSource(ds)
            .sql("SELECT * FROM orders")
            // ... 分区时 4 个线程共享同一个 ResultSet → 数据错乱
            .build();
}

正确做法:分区场景使用 JdbcPagingItemReader,或使用 JdbcCursorItemReader + @StepScope 为每个分区提供不同的 SQL 条件。

面试高频考点

Q1:为什么 Paging 模式必须设置 sortKeys?

分页通过 ORDER BY 保证每次查询结果的确定性和连续性。如果没有排序,数据库可能返回不同顺序的结果,导致同一行出现在两个分页中(重复处理)或被跳过(遗漏)。sortKeys 确保 RESTART 后分页位置的计算基于确定的数据顺序。

Q2:多数据源场景如何为 Reader 指定特定的事务管理器?

通过 StepBuilder.transactionManager(tx) 为整个 Step 指定事务管理器,或通过 JdbcCursorItemReaderBuilder / JdbcPagingItemReaderBuilder 的 dataSource() 指定不同数据源。注意事务管理器需要和 Reader 的数据源一致,否则事务不会覆盖 Reader 的读操作。


上一章:FlatFileItemReader 详解下一章:MultiResourceItemReader 详解

上一页
FlatFileItemReader 详解
下一页
MultiResourceItemReader 详解