Spring Batch Remote Partitioning(远程分区)简介

来源:互联网 发布:自动发卡对接淘宝 编辑:程序博客网 时间:2024/06/02 02:11

Spring Batch远程分区简介

  • Spring Batch远程分区简介
    • 关于Spring Batch Remote Partitioning
    • 实现Spring Batch Remote Partitioning功能
      • 1 通过官网参考文档的方法实现远程分区
      • 2 改进的远程分区实现方法
    • 完整代码

写此博客的缘由:
题外话:暑假期间实习参与了公司一个SaaS项目,由于是一个新立的项目,因此使用较多的开源框架。在这两个月期间,接触了微服务框架、定时任务调度Quartz、Spring Batch批处理等多个开源框架。由于在某些任务中需要进行批处理作业,使用了Spring batch批处理框架。接触Spring Batch重点学习了Spring Batch Remote Partitioning,完成实现相关功能的Demo。首先,博主本来对java不是太熟悉的,刚来公司的时候连maven都没有用过,更不用说什么微服务、spring框架等,很感谢实习导师教我很多入门的基础,后来自己能够自主学习并参与项目工作。

在学习Spring Batch远程分区的过程中,有一点就是这方面的相关资料或者分享的学习经验很少,最主要的资料就是官方的参考文档,但是参考文档中对Remote Partitioning的介绍也是相当简单,对于初学Spring Batch的人来说很不友好。写这篇博客,总结了自己学习Spring Batch远程分区功能的内容,也希望能够方便大家对Spring Batch远程分区功能有更好的了解。

说明:(1)本博客是针对Spring Batch Remote Partitioning的介绍,读者需要有Spring Batch的基础知识,安装好eclipse、maven、MySQL等;(2)对于想要入门Spring Batch的初学者,推荐阅读刘相的《Spring Batch批处理框架》一书,博客中讲解的例子也是以书中的例子为基础。书中例子的源码地址:https://github.com/jxtaliu/SpringBatchSample 。(3)本文主要关注如何实现远程分区功能,其关键点在于如何通过消息队列实现此功能,具体分区功能请参考《Spring Batch批处理框架》一书。

1. 关于Spring Batch Remote Partitioning

Spring batch是一个轻量级的、完善的批处理框架,对于大数据量和高性能的批处理任务,Spring Batch提供不少高级功能和特性来支持,比如并行step、多线程step、分区step、远程step等功能。其中,远程分区功能是分区step和远程step两者的结合,对于处理大数据量的批处理任务有着重要的作用。这里的远程功能需要使用消息队列接受和发送信息,主要通过Spring Integration实现。所以为了能够了解Remote Partitioning功能,除了Spring Batch,也需要接触到Spring Integration的相关知识。如下面Spring Batch Remote Partitioning的示意图,Master对任务进行分区,把各个分区后的任务交给多个Slave节点执行:
Spring Batch Remote Partitioning

2. 实现Spring Batch Remote Partitioning功能

本节会详细介绍如何配置xml文件实现远程分区功能。这里通过例子详解Spring Batch远程分区功能的实现,以《Spring Batch批处理框架》书中的关于Remote Partitioning的例子为基础。读者们可以从作者的github中下载源代码。

关于书中实现Remote Partitioning的例子不再详细说明,读者有兴趣的话可以尝试运行例子的源代码。但这里需要说明的是,书中的远程分区例子并不完全正确。对于书中例子的配置方法,Master把分区后的任务信息发送到了Slave节点上,Slave收到信息后会执行任务,只是Master不会接收Slave完成任务的信息。所以,当Slave执行任务出错的时候,Master却依旧显示任务completed。

这个例子的远程分区处理如下图所示(来自《Spring Batch批处理框架》书中):

例子远程分区处理示意图

该例子的任务为读取三个文件里的账单记录,并把记录写入数据库中。处理过程为:(1)对任务进行分区,一个文件作为一个分区任务;(2)通过消息队列把文件信息发送给各个Slave节点;(3)Slave收到信息后,处理对应的文件,把记录写入数据库,并返回任务完成的信息;(4)Master收到信息后,结束任务。

所以接下来会给出以不同的方式实现上面远程分区例子,首先是通过官方参考文档的方法实现远程分区,接着会根据我们的需求给出改进的方式实现远程分区。

2.1 通过官网参考文档的方法实现远程分区

官方参考文档地址:https://docs.spring.io/spring-batch/trunk/reference/html/springBatchIntegration.html。参考文档中提供了实现远程分区的方法,但由于该文档写的过于简洁,并且把Master与Slave的配置混在一起,对于初学者来说比较难理解。所以,这里会详细介绍文档的方法,并会清晰地区分Master与Slave的配置,方便用于分别配置为Master、Slave两种不同的程序。

在该例子中,我们使用ActiveMQ作为消息中间件(其它支持jms的MQ都没问题)。读者可以在官网下载ActiveMQ并在本地启动,在 http://localhost:8161/admin 可以观察远程分区发送消息的信息统计。

下图给出了该远程分区方法使用的jms消息队列的示意图:

远程分区jms消息队列示意图

具体的过程如下:
1. 首先,Master对批处理任务job中需要远程分区执行的step进行分区,并将分区后的多条任务消息通过MasterRequestChannel发送到消息中间件的RequestQueue中,并开始监听ReplystQueue;
2. 多个Slave节点相互竞争从RequestQueue获取任务消息,并在收到相应的消息后开始执行任务;
3. Slave完成任务后,把任务完成的信息发送到消息中间件的ReplystQueue中;
4. Master节点收到各个Slave节点完成任务的信息,并把信息放入消息整合器(aggregator)中,统计是否所有分区的任务已经完成,最后判断该step是否成功完成。

为了实现上面的功能,配置文件: job-partition-remote-MasterSlave.xml
将该xml文件保存在《Spring Batch批处理框架》书中的源代码文件夹src\main\resources\ch11\ 中。

(1)修改源代码maven项目pom文件的jar包版本
由于书中源代码使用的版本比较低,为了能够使用新版本的功能,我们需要在pom文件中改为更高的版本。这里的例子使用如下的版本:

<properties>  <spring.version>4.3.4.RELEASE</spring.version>  <spring.batch.version>3.0.7.RELEASE</spring.batch.version>      <spring.integration.version>4.3.4.RELEASE</spring.integration.version></properties>

其中: spring.version、spring.batch.version、spring.integration.version分别表示所有groupId为org.springframework、org.springframework.batch、org.springframework.integration的jar包版本。

(2)配置xml文件中的命名空间

<bean:beans xmlns="http://www.springframework.org/schema/batch"        xmlns:bean="http://www.springframework.org/schema/beans"     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:task="http://www.springframework.org/schema/task"      xmlns:int="http://www.springframework.org/schema/integration"    xmlns:int-jms="http://www.springframework.org/schema/integration/jms"     xmlns:jms="http://www.springframework.org/schema/jms"    xmlns:amq="http://activemq.apache.org/schema/core"    xsi:schemaLocation="http://www.springframework.org/schema/beans     http://www.springframework.org/schema/beans/spring-beans.xsd      http://www.springframework.org/schema/task    http://www.springframework.org/schema/task/spring-task.xsd    http://www.springframework.org/schema/batch     http://www.springframework.org/schema/batch/spring-batch.xsd    http://www.springframework.org/schema/integration     http://www.springframework.org/schema/integration/spring-integration-4.3.xsd    http://www.springframework.org/schema/integration/jms     http://www.springframework.org/schema/integration/jms/spring-integration-jms-4.3.xsd    http://www.springframework.org/schema/jms     http://www.springframework.org/schema/jms/spring-jms.xsd    http://activemq.apache.org/schema/core     http://activemq.apache.org/schema/core/activemq-core.xsd">

注意:在命名空间中,spring-integration-jms和spring-integration的xsd文件需要对应于我们在pom文件中配置的版本,否则容易编译错误。

(3)配置连接的ActiveMQ服务器

<amq:connectionFactory id="connectionFactory" brokerURL="tcp://localhost:61616" />

(4)配置xml中的Master部分

  • Master的Job、partitioner与partitionHandler
<job id="partitionRemoteJob">    <step id="partitionRemoteStep">        <partition partitioner="partitioner" handler="partitionHandler" />    </step></job><bean:bean id="partitioner"        class="org.springframework.batch.core.partition.support.MultiResourcePartitioner">    <bean:property name="keyName" value="fileName"/>    <bean:property name="resources" value="classpath:/ch11/data/*.csv"/></bean:bean><bean:bean id="partitionHandler"        class="org.springframework.batch.integration.partition.MessageChannelPartitionHandler">    <bean:property name="messagingOperations">        <bean:bean class="org.springframework.integration.core.MessagingTemplate">            <bean:property name="defaultChannel" ref="MasterRequestChannel" />            <bean:property name="receiveTimeout" value="30000" />        </bean:bean>    </bean:property>    <bean:property name="replyChannel" ref="AggregatedChannel"/>    <bean:property name="stepName" value="remoteStep" />    <bean:property name="gridSize" value="3" /></bean:bean>    

在需要远程分区执行的step中,配置partition中使用的partitioner和handler。partitioner是自己定义的分区规则,这里使用书中的规则。
关于partitionHandler的配置:
defaultChannel: Master发送任务信息的Channel;
receiveTimeout: 接受Slave返回信息超时时间,超时直接返回任务失败。
replyChannel: 接受Slave返回信息的Channel,注意这是一个AggregatedChannel。因为Master需要把所有Slave返回的任务信息整合在一起,才能判断任务是否成功完成。
stepName: 需要在Slave上远程执行的step,Slave上需要有对应的step。
gridSize: 分区的个数,即把任务拆分为多少份。

  • Master jms配置

配置Master使用的channel: MasterRequestChannel和MasterReplyChannel

<int:channel id="MasterRequestChannel">    <int:dispatcher task-executor="RequestPublishExecutor"/></int:channel><task:executor id="RequestPublishExecutor" pool-size="5-10" queue-capacity="0"/><int:channel id="MasteReplyChannel"/>

MasterRequestChannel使用了task-executor,用多线程加快分区任务的分发速度。

配置outbound-channel-adapter和message-driven-channel-adapter:

<int-jms:outbound-channel-adapter     connection-factory="connectionFactory"     destination-name="RequestQueue"     channel="MasterRequestChannel"/><int-jms:message-driven-channel-adapter     connection-factory="connectionFactory"     destination-name="ReplyQueue"    channel="MasterReplyChannel"/>

分别使用outbound-channel-adapter和message-driven-channel-adapter向消息中间件发送和接受消息。其中,destination-name为消息中间件对应的队列名称。Master向RequestQueue发送消息,接受ReplyQueue的消息。

配置消息整合器

<int:channel id="AggregatedChannel">    <int:queue/></int:channel><int:aggregator ref="partitionHandler"     input-channel="MasterReplyChannel"    output-channel="AggregatedChannel"/>

当Master收到多个Slave发送回来的信息,会通过消息整合器(aggregator)放入AggregatedChannel中,所以AggregatedChannel需要使用队列queue。当接收完所有Slave完成任务的信息,Master才会判断任务成功完成。

(5)配置xml的Slave部分

  • Slave jms配置

配置Slave使用的channel:SlaveRequestChannel和SlaveReplyChannel

<int:channel id="SlaveRequestChannel"/><int:channel id="SlaveReplyChannel"/>

配置outbound-channel-adapter和message-driven-channel-adapter:

<int-jms:message-driven-channel-adapter    connection-factory="connectionFactory"     destination-name="RequestQueue"    channel="SlaveRequestChannel"/><int-jms:outbound-channel-adapter     connection-factory="connectionFactory"     destination-name="ReplyQueue"    channel="SlaveReplyChannel"/>

Slave接受RequestQueue的消息,向ReplyQueue发送消息。

配置service-activator

<int:service-activator ref="stepExecutionRequestHandler"         input-channel="SlaveRequestChannel"        output-channel="SlaveReplyChannel"/>

Slave从SlaveRequestChannel获取信息,并使用stepExecutionRequestHandler处理信息,最后向SlaveReplyChannel返回结果。

配置Slave执行的step

<step id="remoteStep">    <tasklet>        <chunk reader="flatFileItemReader" writer="jdbcItemWriter" commit-interval="10"/>        <listeners>              <listener ref="partitionItemReadListener"></listener>        </listeners>    </tasklet></step> <bean:bean id="stepExecutionRequestHandler"     class="org.springframework.batch.integration.partition.StepExecutionRequestHandler">    <bean:property name="jobExplorer" ref="jobExplorer"/>    <bean:property name="stepLocator" ref="stepLocator"/></bean:bean>

注意: 这里step的id必须与Master中partitionHandler的stepName一致才能正常执行。
其余的flatFileItemReader、jdbcItemWriter、jobExplorer、stepLocator等等于原来源代码中的保持一致,这里不再说明。

根据上面配置完job-partition-remote-MasterSlave.xml后,根据书的源代码src\test\java\test\com\juxtapose\example\ch11中的例子自己写一个JobLaunch.java的文件就可以运行了。

因为这里已经清晰给出了Master和Slave的配置。所以读者很容易自己分别写出job-partition-remote-Master.xml和job-partition-remote-Slave.xml的配置文件,即使用不同的进程作为Master和Slave。(先启动一个或多个Slave,再启动Master分发任务)。读者可以自己尝试去实现。

2.2 改进的远程分区实现方法

上节提到官方参考文档使用的方法实现远程分区是可以正常执行的,但在一种情况下会出现问题,就是复用ActiveMQ的消息队列Queue。例如,上面的例子,如果同时启动两个以上的Master,读者会发现有Master显示任务失败,但实际上Slave已经完成所有的任务了。 为什么会有上面的情况呢?这是因为消息队列Queue中的信息只能被消费一次。当有多个Master同时监听ActiveMQ的ReplyQueue,Slave发送过来的信息只能被其中一个Master接收,所以Master就没有办法收到自己想要接受的信息了。

如何解决这个问题呢?第一种方法是使用多对不同的Queue,但是这并非好的方法。因为当我们需要执行不同的远程分区任务比较多,一般批处理任务都是定时每天或者每月执行的,每个任务使用不同的一对Queue,这是对资源的浪费。所以项目组希望能够找出实现消息队列复用的方式。

那么我们需要寻求另一种解决方法。远程分区的方法本质上是Spring Batch和Spring Integration的两者结合,当时为了解决这个问题,于是就开始学习Spring Integration方面关于jms的知识,也在那里找到的答案。官方参考文档地址:https://docs.spring.io/spring-integration/docs/4.3.12.RELEASE/reference/html/jms.html

为了实现消息队列复用,本质上是利用jms中的消息选择器(message selector)。下面给出了该远程分区方法使用的jms消息队列的示意图:
远程分区jms消息队列示意图

如上图所示,在Master和Slave上都取消了使用outbound-channel-adapter和message-driven-channel-adapter,改为了分别使用outbound-gateway和inbound-gateway。

  • 配置Master的outbound-gateway
    方式一:使用reply-listener
<int-jms:outbound-gateway    connection-factory="connectionFactory"    correlation-key="JMSCorrelationID"    request-channel="MasterRequestChannel"    request-destination-name="RequestQueue"    receive-timeout="30000"    reply-channel="MasterReplyChannel"    reply-destination-name="ReplyQueue"    async="true">    <int-jms:reply-listener /></int-jms:outbound-gateway>

correlation-key: 使Master发送出去的jms消息带有correlationId,并且在接受S回复信息时,会让消息中间件通过消息选择器进行筛选,只有带有与原来发送消息一致的correlationId的消息才会被接受。该值设置为JMSCorrelationID即可正常使用。
async: (注意该属性只有4.3以上的版本支持)是否使用异步的方式发送消息。如果设置为false,发送请求的线程在发送完消息后会挂起知道收到回复。当设置为true,发送请求的线程在发送完消息后会被释放,把监听回复的任务交给reply-listener处理,可以释放线程资源。

方式二:使用idle-reply-listener

<int-jms:outbound-gateway    connection-factory="connectionFactory"    correlation-key="JMSCorrelationID"    request-channel="MasterRequestChannel"    request-destination-name="RequestQueue"    receive-timeout="30000"    reply-channel="MasterReplyChannel"    reply-destination-name="ReplyQueue"    idle-reply-listener-timeout="5000"></int-jms:outbound-gateway>

注意:idle-reply-listener是从4.2版本开始支持。
首先reply-listener的生命周期是与gateway一致的,所以使用reply-listener会到时Master一致在监听replyQueue。在已经收到回复的情况下,reply-listener此时就变成idle的状态,除了占用系统资源,对于broker来说也是一种负担。而idle-reply-listener只有在被需要的时候才会启动,并且在接受完信息后等待一段时间(idle-reply-listener-timeout)会自动释放。

  • 配置Slave的inbound-gateway
<int-jms:inbound-gateway    connection-factory="connectionFactory"    correlation-key="JMSCorrelationID"    request-channel="SlaveRequestChannel"    request-destination-name="RequestQueue"    reply-channel="SlaveReplyChannel"    default-reply-queue-name="ReplyQueue"/>

在Slave中同时把correlation-key的值设置为JMSCorrelationID,那么Slave在接受到带有correlationId的消息,回复的时候也会把该correlationId复制到回复的消息里,从而使得Master能偶收到自己对应的消息。

完整代码

job-partition-remote-MasterSlave.xml

<?xml version="1.0" encoding="UTF-8"?><bean:beans xmlns="http://www.springframework.org/schema/batch"        xmlns:bean="http://www.springframework.org/schema/beans"     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"        xmlns:task="http://www.springframework.org/schema/task"    xmlns:int="http://www.springframework.org/schema/integration"    xmlns:int-jms="http://www.springframework.org/schema/integration/jms"     xmlns:jms="http://www.springframework.org/schema/jms"    xmlns:amq="http://activemq.apache.org/schema/core"    xsi:schemaLocation="http://www.springframework.org/schema/beans     http://www.springframework.org/schema/beans/spring-beans.xsd    http://www.springframework.org/schema/batch     http://www.springframework.org/schema/batch/spring-batch.xsd    http://www.springframework.org/schema/task    http://www.springframework.org/schema/task/spring-task.xsd    http://www.springframework.org/schema/integration     http://www.springframework.org/schema/integration/spring-integration-4.3.xsd    http://www.springframework.org/schema/integration/jms     http://www.springframework.org/schema/integration/jms/spring-integration-jms-4.3.xsd    http://www.springframework.org/schema/jms     http://www.springframework.org/schema/jms/spring-jms.xsd    http://activemq.apache.org/schema/core     http://activemq.apache.org/schema/core/activemq-core.xsd">    <bean:import resource="classpath:ch11/job-context.xml"/>    <bean:import resource="classpath:ch11/job-context-db.xml"/>    <!-- Master job -->    <job id="partitionRemoteJob">        <step id="partitionRemoteStep">            <partition partitioner="partitioner" handler="partitionHandler" />        </step>    </job>    <bean:bean id="partitioner"         class="org.springframework.batch.core.partition.support.MultiResourcePartitioner">        <bean:property name="keyName" value="fileName"/>        <bean:property name="resources" value="classpath:/ch11/data/*.csv"/>    </bean:bean>    <bean:bean id="partitionHandler"         class="org.springframework.batch.integration.partition.MessageChannelPartitionHandler">        <bean:property name="messagingOperations">            <bean:bean class="org.springframework.integration.core.MessagingTemplate">                <bean:property name="defaultChannel" ref="MasterRequestChannel" />                <bean:property name="receiveTimeout" value="30000" />            </bean:bean>        </bean:property>        <bean:property name="replyChannel" ref="AggregatedChannel"/>        <bean:property name="stepName" value="remoteStep" />        <bean:property name="gridSize" value="3" />    </bean:bean>        <!-- Master jms -->    <int:channel id="MasterRequestChannel">        <int:dispatcher task-executor="RequestPublishExecutor"/>    </int:channel>    <task:executor id="RequestPublishExecutor" pool-size="5-10" queue-capacity="0"/><!--    <int-jms:outbound-channel-adapter         connection-factory="connectionFactory"         destination-name="RequestQueue"         channel="MasterRequestChannel"/> -->    <int:channel id="MasterReplyChannel"/><!--    <int-jms:message-driven-channel-adapter         connection-factory="connectionFactory"         destination-name="ReplyQueue"        channel="MasterReplyChannel"/> -->    <int-jms:outbound-gateway        connection-factory="connectionFactory"        correlation-key="JMSCorrelationID"        request-channel="MasterRequestChannel"        request-destination-name="RequestQueue"        receive-timeout="30000"        reply-channel="MasterReplyChannel"        reply-destination-name="ReplyQueue"        async="true">        <int-jms:reply-listener />    </int-jms:outbound-gateway>    <int:channel id="AggregatedChannel">        <int:queue/>    </int:channel>    <int:aggregator ref="partitionHandler"         input-channel="MasterReplyChannel"        output-channel="AggregatedChannel"/>    <!-- Slave jms -->    <int:channel id="SlaveRequestChannel"/><!--    <int-jms:message-driven-channel-adapter        connection-factory="connectionFactory"         destination-name="RequestQueue"        channel="SlaveRequestChannel"/> -->    <int:channel id="SlaveReplyChannel"/><!--    <int-jms:outbound-channel-adapter         connection-factory="connectionFactory"         destination-name="ReplyQueue"        channel="SlaveReplyChannel"/> -->    <int-jms:inbound-gateway        connection-factory="connectionFactory"        correlation-key="JMSCorrelationID"        request-channel="SlaveRequestChannel"        request-destination-name="RequestQueue"        reply-channel="SlaveReplyChannel"        default-reply-queue-name="ReplyQueue"/>    <int:service-activator ref="stepExecutionRequestHandler"         input-channel="SlaveRequestChannel"        output-channel="SlaveReplyChannel"/>    <!-- RemoteStep for Slave -->    <step id="remoteStep">        <tasklet>            <chunk reader="flatFileItemReader" writer="jdbcItemWriter" commit-interval="10"/>            <listeners>                <listener ref="partitionItemReadListener"></listener>             </listeners>        </tasklet>    </step>     <bean:bean id="stepExecutionRequestHandler"         class="org.springframework.batch.integration.partition.StepExecutionRequestHandler">        <bean:property name="jobExplorer" ref="jobExplorer"/>        <bean:property name="stepLocator" ref="stepLocator"/>    </bean:bean>    <amq:broker useJmx="false" persistent="false" schedulerSupport="false">        <amq:transportConnectors>            <amq:transportConnector uri="tcp://localhost:61616"/>        </amq:transportConnectors>    </amq:broker>    <amq:connectionFactory id="connectionFactory" brokerURL="tcp://localhost:61616" trustAllPackages="true"/>    <bean:bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">        <bean:property name="scopes">            <bean:map>                <bean:entry key="thread">                    <bean:bean class="org.springframework.context.support.SimpleThreadScope" />                </bean:entry>            </bean:map>        </bean:property>    </bean:bean>        <bean:bean id="flatFileItemReader" scope="step"        class="org.springframework.batch.item.file.FlatFileItemReader">        <bean:property name="resource"             value="#{stepExecutionContext[fileName]}"/>        <bean:property name="lineMapper" ref="lineMapper" />    </bean:bean>    <bean:bean id="lineMapper"         class="org.springframework.batch.item.file.mapping.DefaultLineMapper" >        <bean:property name="lineTokenizer" ref="delimitedLineTokenizer" />        <bean:property name="fieldSetMapper" ref="creditBillFieldSetMapper"/>    </bean:bean>    <bean:bean id="delimitedLineTokenizer"         class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">        <bean:property name="delimiter" value=","/>        <bean:property name="names" value="id,accountID,name,amount,date,address" />    </bean:bean>    <bean:bean id="creditBillFieldSetMapper"        class="com.juxtapose.example.ch11.partition.CreditBillFieldSetMapper">    </bean:bean>    <bean:bean id="jdbcItemWriter"         class="org.springframework.batch.item.database.JdbcBatchItemWriter">        <bean:property name="dataSource" ref="dataSource"/>        <bean:property name="sql" value="insert into t_destcredit (ID,ACCOUNTID,NAME,AMOUNT,DATE,ADDRESS) values (:id,:accountID,:name,:amount,:date,:address)"/>        <bean:property name="itemSqlParameterSourceProvider">            <bean:bean class="org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider"/>        </bean:property>    </bean:bean>    <bean:bean id="creditBillProcessor" scope="step"        class="com.juxtapose.example.ch11.partition.CreditBillProcessor">    </bean:bean>    <bean:bean id="taskExecutor"        class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">        <bean:property name="corePoolSize" value="5" />        <bean:property name="maxPoolSize" value="5" />    </bean:bean>        <bean:bean id="partitionItemReadListener"        class="com.juxtapose.example.ch11.partition.PartitionStepExecutionListener">    </bean:bean></bean:beans>

注意:在classpath:ch11/job-context-db.xml中,simpleJdbcTemplate可以注释掉,高版本不兼容。

原创粉丝点击