6.9.2 可分页的 ItemReader

另一种是使用数据库游标执行多次查询,每次查询只返回一部分结果。 我们将这一部分称为一页(a page)。 分页时每次查询必须指定想要这一页的起始行号和想要返回的行数。

JdbcPagingItemReader

分页 ItemReader 的一个实现是 JdbcPagingItemReaderJdbcPagingItemReader 需要一个 PagingQueryProvider 来负责提供获取每一页所需的查询SQL。由于每个数据库都有不同的分页策略, 所以我们需要为各种数据库使用对应的 PagingQueryProvider 。 也有自动检测所使用数据库类型的 SqlPagingQueryProviderFactoryBean,会根据数据库类型选用适当的 PagingQueryProvider 实现。 这简化了配置,同时也是推荐的最佳实践。

SqlPagingQueryProviderFactoryBean 需要指定一个 select 子句以及一个 from 子句(clause). 当然还可以选择提供 where 子句. 这些子句加上所需的排序列 sortKey 被组合成为一个 SQL 语句(statement).

在 reader 被打开以后, 每次调用 read 方法则返回一个 item,和其他的 ItemReader一样. 使用分页是因为可能需要额外的行.

下面是一个类似 ‘customer credit’ 示例的例子,使用上面提到的基于 cursor的ItemReaders:

  1. <bean id="itemReader" class="org.spr...JdbcPagingItemReader">
  2. <property name="dataSource" ref="dataSource"/>
  3. <property name="queryProvider">
  4. <bean class="org.spr...SqlPagingQueryProviderFactoryBean">
  5. <property name="selectClause" value="select id, name, credit"/>
  6. <property name="fromClause" value="from customer"/>
  7. <property name="whereClause" value="where status=:status"/>
  8. <property name="sortKey" value="id"/>
  9. </bean>
  10. </property>
  11. <property name="parameterValues">
  12. <map>
  13. <entry key="status" value="NEW"/>
  14. </map>
  15. </property>
  16. <property name="pageSize" value="1000"/>
  17. <property name="rowMapper" ref="customerMapper"/>
  18. </bean>

这里配置的ItemReader将返回CustomerCredit对象, 必须指定使用的RowMapper。 ‘pageSize‘属性决定了每次数据库查询返回的实体数量。

parameterValues‘属性可用来为查询指定参数映射map。如果在where子句中使用了命名参数,那么这些entry的key应该和命名参数一一对应。如果使用传统的 ‘?‘ 占位符, 则每个entry的key就应该是占位符的数字编号,和JDBC占位符一样索引都是从1开始。

JpaPagingItemReader

另一个分页ItemReader的实现是 JpaPagingItemReader。JPA没有 Hibernate 中StatelessSession 之类的概念,所以我们必须使用JPA规范提供的其他功能。因为JPA支持分页,所以在使用JPA来处理分页时这是一种很自然的选择。读取每页后, 实体将会分离而且持久化上下文将会被清除,以允许在页面处理完成后实体会被垃圾回收。

JpaPagingItemReader 允许您声明一个JPQL语句,并传入一个 EntityManagerFactory 。然后就和其他的 ItemReader 一样,每次调用它的 read 方法都会返回一个 item. 当需要更多实体,则内部就会自动发生分页。下面是一个示例配置,和上面的JDBC reader一样,都是 ‘customer credit’:

  1. <bean id="itemReader" class="org.spr...JpaPagingItemReader">
  2. <property name="entityManagerFactory" ref="entityManagerFactory"/>
  3. <property name="queryString" value="select c from CustomerCredit c"/>
  4. <property name="pageSize" value="1000"/>
  5. </bean>

这里配置的ItemReader和前面所说的 JdbcPagingItemReader 返回一样的 CustomerCredit对象, 假设 Customer 对象有正确的JPA注解或者ORM映射文件。 ‘pageSize‘ 属性决定了每次查询时读取的实体数量。

IbatisPagingItemReader

[Note] 注意事项

这个 reader 在 Spring Batch 3.0中已经被废弃(deprecated).

如果使用 IBATIS/MyBatis, 则可以使用 IbatisPagingItemReader, 顾名思义, 也是一种实现分页的ItemReader。IBATIS不对分页提供直接支持, 但通过提供一些标准变量就可以为IBATIS查询提供分页支持。

下面是和上面的示例同样功能的配置,使用IbatisPagingItemReader来读取CustomerCredits:

  1. <bean id="itemReader" class="org.spr...IbatisPagingItemReader">
  2. <property name="sqlMapClient" ref="sqlMapClient"/>
  3. <property name="queryId" value="getPagedCustomerCredits"/>
  4. <property name="pageSize" value="1000"/>
  5. </bean>

上述 IbatisPagingItemReader 配置引用了一个IBATIS查询,名为“getPagedCustomerCredits”。如果使用MySQL,那么查询XML应该类似于下面这样。

  1. <select id="getPagedCustomerCredits" resultMap="customerCreditResult">
  2. select id, name, credit from customer order by id asc LIMIT #_skiprows#, #_pagesize#
  3. </select>

_skiprows_pagesize 变量都是 IbatisPagingItemReader 提供的,还有一个 _page 变量,需要时也可以使用。分页查询的语法根据数据库不同使用。下面是使用Oracle的一个例子(但我们需要使用CDATA来包装某些特殊符号,因为是放在XML文档中嘛):

  1. <select id="getPagedCustomerCredits" resultMap="customerCreditResult">
  2. select * from (
  3. select * from (
  4. select t.id, t.name, t.credit, ROWNUM ROWNUM_ from customer t order by id
  5. )) where ROWNUM_ <![CDATA[ > ]]> ( #_page# * #_pagesize# )
  6. ) where ROWNUM <![CDATA[ <= ]]> #_pagesize#
  7. </select>