数据库

像大多数企业应用风格一样,数据库是 批。然而,批次与其他施用方式不同,因为其体积庞大 系统必须使用的数据集。如果一个SQL语句返回了100万行,则 结果集通常会在所有返回的结果被读取前存储在内存中。 Spring Batch为该问题提供了两种解决方案:spring-doc.cadn.net.cn

基于光标的ItemReader实现

使用数据库光标通常是大多数批开发者的默认方式, 因为它是数据库解决“流式”关系数据问题的解决方案。这 Java结果集类本质上是一种面向对象的机制,用于作 光标。一个结果集保持当前数据行的光标。叫下一个结果集将该光标移至下一行。基于Spring Batch的光标ItemReader实现在初始化时打开光标,并将光标向前移动一行,满足 每一次呼叫返回一个可用于处理的映射对象。这关闭然后调用方法以确保所有资源都被释放。春季核心Jdbc模板通过使用回调模式完全映射来解决这个问题 所有行结果集关闭后,将控制权还给方法调用者。 但在批处理中,必须等到步骤完成后才能完成。下图显示了 基于光标的通用图ItemReader工程。注意,虽然示例 使用SQL(因为SQL非常知名),任何技术都可以实现基础 方法。spring-doc.cadn.net.cn

光标示例
图1。光标示例

这个例子说明了基本模式。给定一个有三列的“FOO”表:身份证,名称酒吧选择所有ID大于1但小于7的行。这 将光标的开头(第1行)放在ID2上。该行的结果应为 完全绘制对象。叫read()再次将光标移至下一行, 即编号为3。每次读取后都会写出这些读取的结果允许对象进行垃圾回收(假设没有实例变量 保持对他们的引用)。spring-doc.cadn.net.cn

JdbcCursorItemReader

JdbcCursorItemReader是基于光标技术的JDBC实现。这招奏效了 直接与结果集并且需要对连接运行SQL语句 从一个数据来源.以下数据库模式作为示例:spring-doc.cadn.net.cn

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

许多人更喜欢为每一行使用域对象,因此以下示例使用了 实现行图仪与映射A 的接口客户信用对象: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;
    }
}

因为JdbcCursorItemReaderJdbc模板,它有用于 请参见该数据中如何读取的示例,表示Jdbc模板,以对比 其中ItemReader.在此示例中,假设有1000行 这客户端数据库。第一个例子使用Jdbc模板: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());

在运行上述代码片段后,客户致谢列表包含1000条客户信用对象。在查询方法中,连接由数据来源,提供的SQL会针对它运行,并且mapRow方法被调用为 每行结果集.与此形成对比的是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();

运行前一代码片段后,计数器等于1000。如果上述代码 放回去客户信用结果将完全是 与Jdbc模板例。然而,最大的优势是ItemReader它允许物品被“流式传输”。这方法可以调用一次,该项 可以写出ItemWriter,然后下一个项目可以得到.这使得项目的读写可以分成“块”完成并提交 周期性处理,这正是高性能批处理的核心。此外,它 易于配置以注入到Spring Batch中.spring-doc.cadn.net.cn

以下示例展示了如何注入ItemReader变成了在爪哇语中: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变成了以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

决定是否记录SQLWarning或导致异常。 默认为true(意味着警告会被记录下来)。spring-doc.cadn.net.cn

fetchSizespring-doc.cadn.net.cn

这会给JDBC驱动提示应该取出的行数 当需要更多行时,从数据库中结果集用于ItemReader.默认情况下,游戏中没有任何提示。spring-doc.cadn.net.cn

maxRowsspring-doc.cadn.net.cn

设定底层最大行数的极限结果集能 随时保持。spring-doc.cadn.net.cn

queryTimeoutspring-doc.cadn.net.cn

设定Drivers等待的秒数陈述对象为 跑。如果超过了该限制,则DataAccessException被抛出。(请咨询你的Drivers 详情请见提供商文档)。spring-doc.cadn.net.cn

verifyCursorPositionspring-doc.cadn.net.cn

因为一样结果集ItemReader传递为 这行图仪,用户可以调用ResultSet.next()他们自己, 可能会影响读卡器内部计数。将该值设为true原因 如果光标位置在行图仪电话和以前一样。spring-doc.cadn.net.cn

saveStatespring-doc.cadn.net.cn

表示是否应将读者状态保存在执行上下文提供ItemStream#update(ExecutionContext).默认为true.spring-doc.cadn.net.cn

驱动支持绝对spring-doc.cadn.net.cn

表示JDBC驱动是否支持。 将绝对行设置为结果集.建议将此设置为true支持 JDBC 驱动ResultSet.absolute(),因为这可能提升性能, 尤其是在处理大量数据集时,步骤失败了。默认false.spring-doc.cadn.net.cn

setUseSharedExtendedConnectionspring-doc.cadn.net.cn

表示连接 用于光标的 应被所有其他处理使用,因此共享 交易。如果 设置为false然后光标以自身连接打开 并且在后续步骤处理中不参与任何已启动的交易。 如果你把这个标志设置为true然后你必须将数据源包裹在ExtendedConnectionDataSourceProxy以防止连接被关闭, 每次提交后释放。当你把这个选项设置为true,该陈述曾用于 打开光标时包含“READ_ONLY”和“HOLD_CURSORS_OVER_COMMIT”两个选项。 这允许在交易开始和提交过程中保持光标开启, 步骤处理。要使用此功能,你需要一个支持此功能的数据库和一个JDBC 支持JDBC 3.0及更高版本的驱动。默认false.spring-doc.cadn.net.cn

存储过程项目读取器

有时需要通过存储过程获取光标数据。这存储过程项目读取器工作方式类似于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>

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

如果存储过程返回参考光标(选项2),那么我们需要提供 返回的输出参数的位置参考光标.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());
	reader.setRefCursorPosition(1);

	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="refCursorPosition" value="1"/>
    <property name="rowMapper">
        <bean class="org.springframework.batch.samples.domain.CustomerCreditRowMapper"/>
    </property>
</bean>

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

以下示例展示了 的属性true在爪哇语中: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>

在所有这些情况下,我们都需要定义一个行图仪以及数据来源以及 实际的程序名称。spring-doc.cadn.net.cn

如果存储过程或函数接受参数,则必须声明它们和 通过使用参数财产。以下例子,对于 Oracle 来说,宣告三 参数。第一个是返回参考光标的参数,以及 第二和第三是参数,取值为整数.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以上。附加属性中列出的所有附加属性适用于存储过程项目读取器也。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要求你指定一个选择条款和a第。你也可以选择哪里第。这些条款以及 必填sortKey用于构建SQL语句。spring-doc.cadn.net.cn

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

读卡器打开后,每次调用返回一项同一时期 基本时尚和其他任何时尚一样ItemReader.分页是在幕后进行的 需要增加排数。spring-doc.cadn.net.cn

以下 Java 示例配置使用了与 基于光标ItemReader之前展示: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示例配置使用了与 基于光标ItemReader之前展示: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返回客户信用使用以下行图仪, 必须具体说明。“pageSize”属性决定了读取的实体数量 每次查询运行时,都来自数据库。spring-doc.cadn.net.cn

“parameterValues”属性可用于指定地图参数值的 查询。如果你在哪里每个条目的密钥应当 匹配命名参数的名称。如果你使用传统的“?”占位符,那么 每个条目的密钥应为占位符的编号,起始于1。spring-doc.cadn.net.cn

JpaPagingItemReader

分页的另一种实现ItemReaderJpaPagingItemReader.JPA会 没有像Hibernate者那样的概念无状态会话,所以我们必须使用 JPA 规范提供了功能。由于JPA支持分页,这很自然 使用 JPA 进行批量处理的选择。每读一页后, 实体变得分离,持久性上下文被清除,以便实体能够 页面处理后会被垃圾回收。spring-doc.cadn.net.cn

JpaPagingItemReader允许你声明一个JPQL语句并传递一个EntityManagerFactory.然后每次调用返回一个项目,读取相同的 basic。 时尚和其他任何东西一样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返回客户信用与 描述为JdbcPagingItemReader上述假设客户信用对象具有 正确的JPA注释或ORM映射文件。“pageSize”属性确定了 每次查询执行从数据库读取的实体数量。spring-doc.cadn.net.cn

数据库 ItemWriters

虽然平面文件和XML文件都有特定的ItemWriter实例中,没有完全等价的 在数据库领域。这是因为事务提供了所有必要的功能。ItemWriter文件必须实现,因为它们必须表现为事务性, 记录书面记录,并在适当时间冲洗或清理。 数据库不需要此功能,因为写入已经包含在 交易。用户可以创建自己的DAO,实现ItemWriter接口或 用自定义的ItemWriter那是针对通用处理问题写的。也 按理说应该没问题。需要注意的是性能 以及通过批量处理输出提供的错误处理能力。这是大多数 使用休眠作为ItemWriter但使用时也可能有同样的问题 JDBC批处理模式。假设我们 注意冲洗,且数据中没有错误。然而,任何错误 写作可能会引起混淆,因为无法知道具体是哪一项导致的 如果是个别物品,或者是否是个别物品的责任,如 下图:spring-doc.cadn.net.cn

同花错误
图2。同花错误

如果在写入前已缓冲了项,任何错误在缓冲区 在提交前被刷新。例如,假设每个区块写入20个项目, 第15个物品会抛出数据完整性违规异常.至于涉及的20项均已成功编写,因为无法确定 错误一直存在,直到它们实际写入。一次会话#同花()称为, 缓冲区被清空,异常被触发。目前,已经没有什么没问题。交易必须被回滚。通常,这种例外可能导致 跳过的项目(取决于跳过/重试策略),然后就不会写入 再。然而,在批量处理场景中,无法知道是哪项导致了 问题。整个缓冲区正在写入时,故障发生了。唯一的办法是 解决方法是每完成一件物品后冲洗,如下图所示:spring-doc.cadn.net.cn

写入时错误
图3。写入时错误

这是一个常见的使用场景,尤其是在使用 Hibernate 时,以及简单的指南 的实现ItemWriter是每次调用 时冲洗write().这样做可以 以保证跳过项目,由Spring Batch内部负责 调用的细度ItemWriter在一个错误之后。spring-doc.cadn.net.cn