此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Batch 文档 5.2.2spring-doc.cadn.net.cn

数据库

与大多数企业应用程序样式一样,数据库是 批。但是,由于 batch 的绝对大小,批次与其他应用程序样式不同 系统必须使用的数据集。如果 SQL 语句返回 100 万行,则 结果集可能会将所有返回的结果保存在内存中,直到读取所有行。 Spring Batch 针对此问题提供了两种类型的解决方案:spring-doc.cadn.net.cn

基于游标ItemReader实现

使用数据库游标通常是大多数批处理开发人员的默认方法, 因为它是数据库对“流式传输”关系数据问题的解决方案。这 JavaResultSetclass 本质上是一种面向对象的机制,用于作 光标。一个ResultSet维护当前数据行的光标。叫nextResultSet将此光标移动到下一行。基于游标的 Spring BatchItemReader实现在初始化时打开一个游标,并将游标向前移动一行,以 每次调用read,返回可用于处理的映射对象。这close然后调用方法以确保释放所有资源。Spring 核心JdbcTemplate通过使用回调模式完全映射来解决这个问题 所有行ResultSet并关闭,然后再将控制权返回给方法调用方。 但是,在批处理中,这必须等到步骤完成。下图显示了 基于游标的通用图ItemReader工程。请注意,虽然示例 使用 SQL(因为 SQL 广为人知),任何技术都可以实现基本的 方法。spring-doc.cadn.net.cn

光标示例
图 1.光标示例

此示例说明了基本模式。给定一个“FOO”表,它有三列:ID,NAMEBAR,选择 ID 大于 1 但小于 7 的所有行。这 将光标的开头(第 1 行)放在 ID 2 上。此行的结果应为 完全映射Foo对象。叫read()再次将光标移动到下一行, 这是FooID 为 3。这些读取的结果在每次读取后都会写出read,允许对对象进行垃圾回收(假设没有实例变量 维护对它们的引用)。spring-doc.cadn.net.cn

JdbcCursorItemReader

JdbcCursorItemReader是基于游标的技术的 JDBC 实现。它有效 直接与ResultSet并且需要针对连接运行 SQL 语句 从DataSource.以下数据库模式用作示例:spring-doc.cadn.net.cn

CREATE TABLE CUSTOMER (
   ID BIGINT IDENTITY PRIMARY KEY,
   NAME VARCHAR(45),
   CREDIT FLOAT
);

许多人更喜欢为每一行使用一个域对象,因此以下示例使用 实现RowMapper接口映射CustomerCredit对象:spring-doc.cadn.net.cn

public class CustomerCreditRowMapper implements RowMapper<CustomerCredit> {

    public static final String ID_COLUMN = "id";
    public static final String NAME_COLUMN = "name";
    public static final String CREDIT_COLUMN = "credit";

    public CustomerCredit mapRow(ResultSet rs, int rowNum) throws SQLException {
        CustomerCredit customerCredit = new CustomerCredit();

        customerCredit.setId(rs.getInt(ID_COLUMN));
        customerCredit.setName(rs.getString(NAME_COLUMN));
        customerCredit.setCredit(rs.getBigDecimal(CREDIT_COLUMN));

        return customerCredit;
    }
}

因为JdbcCursorItemReaderJdbcTemplate,它对 请参阅如何使用JdbcTemplate,以对比它 使用ItemReader.出于此示例的目的,假设 这CUSTOMER数据库。第一个示例使用JdbcTemplate:spring-doc.cadn.net.cn

//For simplicity sake, assume a dataSource has already been obtained
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
List customerCredits = jdbcTemplate.query("SELECT ID, NAME, CREDIT from CUSTOMER",
                                          new CustomerCreditRowMapper());

运行前面的代码片段后,customerCredits列表包含 1,000 个CustomerCredit对象。在查询方法中,从DataSource,提供的 SQL 将针对它运行,并且mapRow方法被调用 中的每一行ResultSet.将此与JdbcCursorItemReader,如以下示例所示:spring-doc.cadn.net.cn

JdbcCursorItemReader itemReader = new JdbcCursorItemReader();
itemReader.setDataSource(dataSource);
itemReader.setSql("SELECT ID, NAME, CREDIT from CUSTOMER");
itemReader.setRowMapper(new CustomerCreditRowMapper());
int counter = 0;
ExecutionContext executionContext = new ExecutionContext();
itemReader.open(executionContext);
Object customerCredit = new Object();
while(customerCredit != null){
    customerCredit = itemReader.read();
    counter++;
}
itemReader.close();

运行前面的代码片段后,计数器等于 1,000。如果上面的代码有 将返回的customerCredit到列表中,结果将完全是 与JdbcTemplate例。然而,最大的优势ItemReader是它允许项目被“流式传输”。这read方法可以调用一次,则该项 可以由ItemWriter,然后可以通过以下方式获得下一个项目read.这允许在“块”中完成项目读取和写入并提交 定期,这是高性能批处理的本质。此外,它 易于配置为注入 Spring BatchStep.spring-doc.cadn.net.cn

以下示例演示如何注入ItemReader变成一个Step在 Java 中:spring-doc.cadn.net.cn

Java 配置
@Bean
public JdbcCursorItemReader<CustomerCredit> itemReader() {
	return new JdbcCursorItemReaderBuilder<CustomerCredit>()
			.dataSource(this.dataSource)
			.name("creditReader")
			.sql("select ID, NAME, CREDIT from CUSTOMER")
			.rowMapper(new CustomerCreditRowMapper())
			.build();

}

以下示例演示如何注入ItemReader变成一个Step在 XML 中:spring-doc.cadn.net.cn

XML 配置
<bean id="itemReader" class="org.spr...JdbcCursorItemReader">
    <property name="dataSource" ref="dataSource"/>
    <property name="sql" value="select ID, NAME, CREDIT from CUSTOMER"/>
    <property name="rowMapper">
        <bean class="org.springframework.batch.samples.domain.CustomerCreditRowMapper"/>
    </property>
</bean>

其他属性

因为在 Java 中打开光标有很多不同的选项,所以有很多 属性JdbcCursorItemReader可以设置,如下所述 桌子:spring-doc.cadn.net.cn

表 1.JdbcCursorItemReader 属性

忽略警告spring-doc.cadn.net.cn

确定是否记录 SQLWarnings 或导致异常。 默认值为true(意味着记录警告)。spring-doc.cadn.net.cn

fetch大小spring-doc.cadn.net.cn

向 JDBC 驱动程序提供有关应获取的行数的提示 当需要更多行时,从数据库中ResultSet对象ItemReader.默认情况下,不给出任何提示。spring-doc.cadn.net.cn

最大行数spring-doc.cadn.net.cn

设置基础的最大行数限制ResultSet能 在任何时候保持。spring-doc.cadn.net.cn

queryTimeoutspring-doc.cadn.net.cn

设置驱动程序等待Statement反对 跑。如果超过限制,则DataAccessException被抛出。(请咨询您的Drivers 提供商文档了解详细信息)。spring-doc.cadn.net.cn

verifyCursorPositionspring-doc.cadn.net.cn

因为同样ResultSetItemReader被传递给 这RowMapper,用户可以调用ResultSet.next()他们自己,其中 可能会导致读者内部计数出现问题。将此值设置为true原因 如果光标位置在RowMapper像以前一样打电话。spring-doc.cadn.net.cn

saveStatespring-doc.cadn.net.cn

指示是否应将读取器的状态保存在ExecutionContext提供方ItemStream#update(ExecutionContext).默认值为true.spring-doc.cadn.net.cn

driverSupports绝对spring-doc.cadn.net.cn

指示 JDBC 驱动程序是否支持 在ResultSet.建议将其设置为true对于支持ResultSet.absolute(),因为它可以提高性能, 特别是如果一个步骤在处理大型数据集时失败。默认为false.spring-doc.cadn.net.cn

setUseSharedExtendedConnection(设置使用共享扩展连接)spring-doc.cadn.net.cn

指示连接是否 用于游标的 应由所有其他处理使用,从而共享相同的 交易。如果将其设置为false,则光标将以其自己的连接打开 并且不参与为步骤处理的其余部分启动的任何事务。 如果将此标志设置为true那么您必须将 DataSource 包装在ExtendedConnectionDataSourceProxy以防止连接关闭和 每次提交后释放。当您将此选项设置为true,用于 打开光标是使用“READ_ONLY”和“HOLD_CURSORS_OVER_COMMIT”选项创建的。 这允许在事务启动和提交时保持游标打开 步骤处理。要使用此功能,您需要一个支持此功能的数据库和一个 JDBC 支持 JDBC 3.0 或更高版本的驱动程序。默认为false.spring-doc.cadn.net.cn

StoredProcedureItemReader

有时需要使用存储过程获取游标数据。这StoredProcedureItemReader工作方式类似于JdbcCursorItemReader,但相反,相反 运行查询以获取游标时,它会运行返回游标的存储过程。 存储过程可以通过三种不同的方式返回游标:spring-doc.cadn.net.cn

以下 Java 示例配置使用与 早期示例:spring-doc.cadn.net.cn

Java 配置
@Bean
public StoredProcedureItemReader reader(DataSource dataSource) {
	StoredProcedureItemReader reader = new StoredProcedureItemReader();

	reader.setDataSource(dataSource);
	reader.setProcedureName("sp_customer_credit");
	reader.setRowMapper(new CustomerCreditRowMapper());

	return reader;
}

以下 XML 示例配置使用与前面相同的“客户信用”示例 例子:spring-doc.cadn.net.cn

XML 配置
<bean id="reader" class="o.s.batch.item.database.StoredProcedureItemReader">
    <property name="dataSource" ref="dataSource"/>
    <property name="procedureName" value="sp_customer_credit"/>
    <property name="rowMapper">
        <bean class="org.springframework.batch.samples.domain.CustomerCreditRowMapper"/>
    </property>
</bean>

前面的示例依赖于存储过程来提供ResultSet作为 返回的结果(前面的选项 1)。spring-doc.cadn.net.cn

如果存储过程返回了ref-cursor(选项 2),那么我们需要提供 返回的 out 参数的位置ref-cursor.spring-doc.cadn.net.cn

以下示例显示如何使用第一个参数作为 ref-cursor Java:spring-doc.cadn.net.cn

Java 配置
@Bean
public StoredProcedureItemReader reader(DataSource dataSource) {
	StoredProcedureItemReader reader = new StoredProcedureItemReader();

	reader.setDataSource(dataSource);
	reader.setProcedureName("sp_customer_credit");
	reader.setRowMapper(new CustomerCreditRowMapper());
	reader.setRefCursorPosition(1);

	return reader;
}

以下示例显示如何使用第一个参数作为 ref-cursor XML:spring-doc.cadn.net.cn

XML 配置
<bean id="reader" class="o.s.batch.item.database.StoredProcedureItemReader">
    <property name="dataSource" ref="dataSource"/>
    <property name="procedureName" value="sp_customer_credit"/>
    <property name="refCursorPosition" value="1"/>
    <property name="rowMapper">
        <bean class="org.springframework.batch.samples.domain.CustomerCreditRowMapper"/>
    </property>
</bean>

如果游标是从存储函数返回的(选项 3),我们需要将 属性 “function” 设置为true.它默认为false.spring-doc.cadn.net.cn

以下示例显示属性true在 Java 中:spring-doc.cadn.net.cn

Java 配置
@Bean
public StoredProcedureItemReader reader(DataSource dataSource) {
	StoredProcedureItemReader reader = new StoredProcedureItemReader();

	reader.setDataSource(dataSource);
	reader.setProcedureName("sp_customer_credit");
	reader.setRowMapper(new CustomerCreditRowMapper());
	reader.setFunction(true);

	return reader;
}

以下示例显示属性true在 XML 中:spring-doc.cadn.net.cn

XML 配置
<bean id="reader" class="o.s.batch.item.database.StoredProcedureItemReader">
    <property name="dataSource" ref="dataSource"/>
    <property name="procedureName" value="sp_customer_credit"/>
    <property name="function" value="true"/>
    <property name="rowMapper">
        <bean class="org.springframework.batch.samples.domain.CustomerCreditRowMapper"/>
    </property>
</bean>

在所有这些情况下,我们都需要定义一个RowMapper以及DataSource和 实际过程名称。spring-doc.cadn.net.cn

如果存储过程或函数接受参数,则必须声明它们并 使用parameters财产。以下示例对于 Oracle,声明了三个 参数。第一个是out返回 ref-cursor 的参数,以及 第二个和第三个在参数中,该参数采用类型INTEGER.spring-doc.cadn.net.cn

以下示例显示了如何在 Java 中使用参数:spring-doc.cadn.net.cn

Java 配置
@Bean
public StoredProcedureItemReader reader(DataSource dataSource) {
	List<SqlParameter> parameters = new ArrayList<>();
	parameters.add(new SqlOutParameter("newId", OracleTypes.CURSOR));
	parameters.add(new SqlParameter("amount", Types.INTEGER);
	parameters.add(new SqlParameter("custId", Types.INTEGER);

	StoredProcedureItemReader reader = new StoredProcedureItemReader();

	reader.setDataSource(dataSource);
	reader.setProcedureName("spring.cursor_func");
	reader.setParameters(parameters);
	reader.setRefCursorPosition(1);
	reader.setRowMapper(rowMapper());
	reader.setPreparedStatementSetter(parameterSetter());

	return reader;
}

以下示例演示如何使用 XML 中的参数:spring-doc.cadn.net.cn

XML 配置
<bean id="reader" class="o.s.batch.item.database.StoredProcedureItemReader">
    <property name="dataSource" ref="dataSource"/>
    <property name="procedureName" value="spring.cursor_func"/>
    <property name="parameters">
        <list>
            <bean class="org.springframework.jdbc.core.SqlOutParameter">
                <constructor-arg index="0" value="newid"/>
                <constructor-arg index="1">
                    <util:constant static-field="oracle.jdbc.OracleTypes.CURSOR"/>
                </constructor-arg>
            </bean>
            <bean class="org.springframework.jdbc.core.SqlParameter">
                <constructor-arg index="0" value="amount"/>
                <constructor-arg index="1">
                    <util:constant static-field="java.sql.Types.INTEGER"/>
                </constructor-arg>
            </bean>
            <bean class="org.springframework.jdbc.core.SqlParameter">
                <constructor-arg index="0" value="custid"/>
                <constructor-arg index="1">
                    <util:constant static-field="java.sql.Types.INTEGER"/>
                </constructor-arg>
            </bean>
        </list>
    </property>
    <property name="refCursorPosition" value="1"/>
    <property name="rowMapper" ref="rowMapper"/>
    <property name="preparedStatementSetter" ref="parameterSetter"/>
</bean>

除了参数声明之外,我们还需要指定一个PreparedStatementSetter设置调用参数值的实现。这与 这JdbcCursorItemReader以上。其他属性中列出的所有附加属性都适用于StoredProcedureItemReader也。spring-doc.cadn.net.cn

寻呼ItemReader实现

使用数据库游标的替代方法是运行多个查询,其中每个查询 获取部分结果。我们将这一部分称为页面。每个查询都必须 指定起始行号和我们希望在页面中返回的行数。spring-doc.cadn.net.cn

JdbcPagingItemReader

分页的一个实现ItemReaderJdbcPagingItemReader.这JdbcPagingItemReader需要一个PagingQueryProvider负责提供 SQL 用于检索构成页面的行的查询。由于每个数据库都有自己的 策略来提供分页支持,我们需要使用不同的PagingQueryProvider对于每种受支持的数据库类型。还有SqlPagingQueryProviderFactoryBean自动检测正在使用的数据库并确定适当的PagingQueryProvider实现。这简化了配置,并且是 推荐的最佳做法。spring-doc.cadn.net.cn

SqlPagingQueryProviderFactoryBean要求您指定select子句和from第。您还可以提供可选的where第。这些条款和 必填sortKey用于构建 SQL 语句。spring-doc.cadn.net.cn

sortKey以保证 执行之间不会丢失任何数据。

打开读取器后,它每次调用都会将一个项目传递给read在同一个 基本时尚与其他时尚一样ItemReader.分页发生在幕后,当 需要额外的行。spring-doc.cadn.net.cn

以下 Java 示例配置使用与 基于游标ItemReaders之前显示:spring-doc.cadn.net.cn

Java 配置
@Bean
public JdbcPagingItemReader itemReader(DataSource dataSource, PagingQueryProvider queryProvider) {
	Map<String, Object> parameterValues = new HashMap<>();
	parameterValues.put("status", "NEW");

	return new JdbcPagingItemReaderBuilder<CustomerCredit>()
           				.name("creditReader")
           				.dataSource(dataSource)
           				.queryProvider(queryProvider)
           				.parameterValues(parameterValues)
           				.rowMapper(customerCreditMapper())
           				.pageSize(1000)
           				.build();
}

@Bean
public SqlPagingQueryProviderFactoryBean queryProvider() {
	SqlPagingQueryProviderFactoryBean provider = new SqlPagingQueryProviderFactoryBean();

	provider.setSelectClause("select id, name, credit");
	provider.setFromClause("from customer");
	provider.setWhereClause("where status=:status");
	provider.setSortKey("id");

	return provider;
}

以下 XML 示例配置使用与 基于游标ItemReaders之前显示:spring-doc.cadn.net.cn

XML 配置
<bean id="itemReader" class="org.spr...JdbcPagingItemReader">
    <property name="dataSource" ref="dataSource"/>
    <property name="queryProvider">
        <bean class="org.spr...SqlPagingQueryProviderFactoryBean">
            <property name="selectClause" value="select id, name, credit"/>
            <property name="fromClause" value="from customer"/>
            <property name="whereClause" value="where status=:status"/>
            <property name="sortKey" value="id"/>
        </bean>
    </property>
    <property name="parameterValues">
        <map>
            <entry key="status" value="NEW"/>
        </map>
    </property>
    <property name="pageSize" value="1000"/>
    <property name="rowMapper" ref="customerMapper"/>
</bean>

这配置了ItemReader返回CustomerCredit对象使用RowMapper, 必须指定。“pageSize”属性确定读取的实体数 来自数据库的每个查询运行。spring-doc.cadn.net.cn

'parameterValues' 属性可用于指定Map的参数值 查询。如果您在where子句中,每个条目的键应该 匹配命名参数的名称。如果您使用传统的“?” 占位符,则 每个条目的键应该是占位符的编号,从 1 开始。spring-doc.cadn.net.cn

JpaPagingItemReader

分页的另一种实现ItemReaderJpaPagingItemReader.JPA 确实如此 没有类似于 Hibernate 的概念StatelessSession,所以我们必须使用其他 JPA 规范提供的功能。由于 JPA 支持分页,因此这是自然的 在使用 JPA 进行批处理时的选择。阅读每一页后, 实体将分离,并且持久性上下文被清除,以允许实体 处理页面后进行垃圾回收。spring-doc.cadn.net.cn

JpaPagingItemReader允许您声明 JPQL 语句并传入EntityManagerFactory.然后,它每次调用都会传回一个项目,以读取相同的基本 时尚与其他时尚一样ItemReader.分页发生在幕后,当额外的 需要实体。spring-doc.cadn.net.cn

以下 Java 示例配置使用与 JDBC 读取器前面显示:spring-doc.cadn.net.cn

Java 配置
@Bean
public JpaPagingItemReader itemReader() {
	return new JpaPagingItemReaderBuilder<CustomerCredit>()
           				.name("creditReader")
           				.entityManagerFactory(entityManagerFactory())
           				.queryString("select c from CustomerCredit c")
           				.pageSize(1000)
           				.build();
}

以下 XML 示例配置使用与 JDBC 读取器前面显示:spring-doc.cadn.net.cn

XML 配置
<bean id="itemReader" class="org.spr...JpaPagingItemReader">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
    <property name="queryString" value="select c from CustomerCredit c"/>
    <property name="pageSize" value="1000"/>
</bean>

这配置了ItemReader返回CustomerCredit对象,其方式与 描述为JdbcPagingItemReader上面,假设CustomerCreditobject 具有 更正 JPA 注释或 ORM 映射文件。'pageSize' 属性确定 每次查询执行从数据库读取的实体数。spring-doc.cadn.net.cn

数据库项编写器

虽然平面文件和 XML 文件都有特定的ItemWriter实例,没有完全等效的 在数据库世界中。这是因为事务提供了所有需要的功能。ItemWriter实现对于文件来说是必要的,因为它们必须像事务性一样运行, 跟踪书面物品并在适当的时间冲洗或清理。 数据库不需要此功能,因为写入已经包含在 交易。用户可以创建自己的 DAO,实现ItemWriterinterface 或 使用自定义中的一个ItemWriter这是为一般处理问题而写的。也 方式,它们应该可以毫无问题地工作。需要注意的一件事是性能 以及通过批处理输出提供的错误处理功能。这是最 使用 Hibernate 作为ItemWriter但在使用时可能会遇到同样的问题 JDBC 批处理模式。批处理数据库输出没有任何固有缺陷,假设我们 小心刷新,数据中没有错误。但是,在 书写可能会引起混乱,因为无法知道是哪一个项目导致了 例外情况,或者即使任何单个项目负责,如 下图:spring-doc.cadn.net.cn

刷新时出错
图 2.刷新时出错

如果在写入之前缓冲了项目,则在缓冲区之前不会抛出任何错误 在提交之前刷新。例如,假设每个块写入 20 个项目, 第 15 项抛出一个DataIntegrityViolationException.至于Step涉及,所有 20 项都写成功,因为没有办法知道 错误发生,直到它们被实际写入。一次Session#flush()调用,则 buffer 被清空并命中异常。此时,没有任何Step可以做。必须回滚事务。通常,此异常可能会导致 要跳过的项目(取决于跳过/重试策略),然后不写入 再。但是,在批处理方案中,无法知道是哪个项目导致了 问题。发生故障时,正在写入整个缓冲区。唯一的方法 解决此问题是在每个项目之后刷新,如下图所示:spring-doc.cadn.net.cn

写入错误
图 3.写入错误

这是一个常见的用例,尤其是在使用 Hibernate 时,以及 的实现ItemWriter是在每次调用时刷新write().这样做可以 对于要可靠地跳过的项目,Spring Batch 在内部处理 调用的粒度ItemWriter错误后。spring-doc.cadn.net.cn