Spring批处理jpaPagingItemReader为什么不读取某些行?

Spring批处理jpaPagingItemReader为什么不读取某些行?,jpa,spring-batch,Jpa,Spring Batch,我正在使用SpringBatch3.0.1.RELEASE/JPA和一个HSQLBD服务器数据库。 我需要使用分页浏览整个表并逐个更新项。所以我使用了一个jpaPagingItemReader。但是当我运行作业时,我可以看到一些行被跳过,并且跳过的行数等于页面大小。例如,如果我的表有12行,并且jpaPagingItemReader.pagesize=3,则作业将显示:第1、2、3行,然后是第7、8、9行,因此跳过第4、5、6行… 您能告诉我我的代码/配置有什么问题吗,或者可能是HSQLDB分页

我正在使用SpringBatch3.0.1.RELEASE/JPA和一个HSQLBD服务器数据库。 我需要使用分页浏览整个表并逐个更新项。所以我使用了一个jpaPagingItemReader。但是当我运行作业时,我可以看到一些行被跳过,并且跳过的行数等于页面大小。例如,如果我的表有12行,并且jpaPagingItemReader.pagesize=3,则作业将显示:第1、2、3行,然后是第7、8、9行,因此跳过第4、5、6行… 您能告诉我我的代码/配置有什么问题吗,或者可能是HSQLDB分页的问题? 下面是我的代码:

[编辑]:问题在于对POJO实体执行修改的my ItemProcessor。因为JPAPagingItemReader在每次读取之间进行了刷新,所以实体会被更新,这就是我想要的。但是,正如在日志中所看到的那样,游标分页似乎也在增加:行ID 4、5和6已被跳过。我如何处理这个问题

@Configuration
@EnableBatchProcessing(modular=true)
public class AppBatchConfig {
  @Inject
  private InfrastructureConfiguration infrastructureConfiguration;  
  @Inject private JobBuilderFactory jobs;
  @Inject private StepBuilderFactory steps;

  @Bean  public Job job() {
     return jobs.get("Myjob1").start(step1()).build();
  }
  @Bean  public Step step1() {  
      return steps.get("step1")
                .<SNUserPerCampaign, SNUserPerCampaign> chunk(0)
                .reader(reader()).processor(processor()).build();   
  }
  @Bean(destroyMethod = "")
@JobScope 
public ItemStreamReader<SNUserPerCampaign> reader() String trigramme) {
    JpaPagingItemReader reader = new JpaPagingItemReader();
    reader.setEntityManagerFactory(infrastructureConfiguration.getEntityManagerFactory());
    reader.setQueryString("select t from SNUserPerCampaign t where t.isactive=true");
    reader.setPageSize(3));
    return reader;
}
 @Bean @JobScope
 public ItemProcessor<SNUserPerCampaign, SNUserPerCampaign> processor() {   
     return new MyItemProcessor();
 }
}

@Configuration
@EnableBatchProcessing
public class StandaloneInfrastructureConfiguration implements InfrastructureConfiguration {
 @Inject private EntityManagerFactory emf;  
 @Override
public EntityManagerFactory getEntityManagerFactory() {
    return emf;
}
}  
从Spring xml配置文件:

<tx:annotation-driven transaction-manager="transactionManager" />     
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
</bean>

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver" />
    <property name="url" value="jdbc:hsqldb:hsql://localhost:9001/MYAppDB" />
    <property name="username" value="sa" />
    <property name="password" value="" />
</bean>

org.springframework.batch.item.database.JpaPagingItemReader创建的是自己的entityManager实例

从org.springframework.batch.item.database.JpaPagingItemReaderdoOpen:

entityManager = entityManagerFactory.createEntityManager(jpaPropertyMap);
如果您在一个事务中,就像看起来的那样,读卡器实体不会分离 从org.springframework.batch.item.database.jpapagingitemreaderdoradpage:

    if (!transacted) {
        List<T> queryResult = query.getResultList();
        for (T entity : queryResult) {
            entityManager.detach(entity);
            results.add(entity);
        }//end if
    } else {
        results.addAll(query.getResultList());
        tx.commit();
    }

org.springframework.batch.item.database.JpaPagingItemReader使用限制和偏移量检索分页数据。因此,读取器创建的下一个选择如下所示:

select * from table where active = true offset 3 limits 3. 
读卡器将丢失id为4、5、6的项,因为它们现在是数据库检索的第一行

作为一种解决方法,您可以使用jdbc实现org.springframework.batch.item.database.JdbcPagingItemReader,因为它不使用限制和偏移量。它基于排序列(通常是id列),因此不会丢失任何数据。 当然,您必须使用JPA或纯JDBC实现将数据更新到编写器中

读者会更加冗长:

@Bean
public ItemReader<? extends Entity> reader() {
    JdbcPagingItemReader<Entity> reader = new JdbcPagingItemReader<Entity>();
    final SqlPagingQueryProviderFactoryBean sqlPagingQueryProviderFactoryBean = new SqlPagingQueryProviderFactoryBean();
    sqlPagingQueryProviderFactoryBean.setDataSource(dataSource);
    sqlPagingQueryProviderFactoryBean.setSelectClause("select *");
    sqlPagingQueryProviderFactoryBean.setFromClause("from <your table name>");
    sqlPagingQueryProviderFactoryBean.setWhereClause("where active = true");
    sqlPagingQueryProviderFactoryBean.setSortKey("id");
    try {
        reader.setQueryProvider(sqlPagingQueryProviderFactoryBean.getObject());
    } catch (Exception e) {
        e.printStackTrace();
    }
    reader.setDataSource(dataSource);
    reader.setPageSize(3);
    reader.setRowMapper(new BeanPropertyRowMapper<Entity>(Entity.class));
    return reader;

有几件事需要注意:

将分离从JpaPagingItemReader返回的所有实体。我们通过两种方式之一来实现这一点。我们要么在查询页面之前创建一个事务,然后提交该事务,该事务将分离与该事务的EntityManager关联的所有实体,要么显式调用EntityManager.detach。我们这样做是为了能够正确执行重试和跳过等功能。 虽然您没有在处理器中发布所有代码,但我的预感是,在//do some stuff部分,您的项目将被重新附加,这就是更新发生的原因。但是,如果看不到代码,我就不能确定。 无论哪种情况,都应该使用显式ItemWriter。事实上,我认为当我们使用java配置文件时,我们不需要一个ITEMIG。 对于丢失记录的特定问题,您需要记住,任何*PagingItemReader都不会使用光标。它们都对每页数据执行独立的查询。因此,如果您在每个页面之间更新基础数据,它可能会对未来页面中返回的项目产生影响。例如,如果我的分页查询指定val1>4的位置,并且我有一条val1为1到5的记录,则在区块2中,该项可能会返回,因为它现在满足条件。如果需要更新where子句中的值,从而影响要处理的数据集的内容,最好添加一个可以查询的已处理标志。
我也有同样的问题,根据页面大小跳过行。 例如,如果我将pageSize设置为2,它将读取2、忽略2、读取2、忽略2等等

我正在构建一个守护进程处理器,以轮询“请求”数据库表中处于“等待处理”状态的记录。守护进程被设计为永远在后台运行

我有一个在@NamedQuery中定义的“status”字段,它将选择状态为“10”的记录:等待处理。处理记录后,状态字段将更新为“20”:错误或“30”:成功。 这就是问题的原因——我正在更新查询中定义的字段。如果我引入了一个“processedField”并更新了它,而不是“status”字段,那么没有问题——所有记录都将被读取


作为更新状态字段的可能解决方案,我将MaxItemCount设置为与PageSize相同;这将在步骤完成之前正确更新记录。然后我继续执行该步骤,直到发出停止守护进程的请求。好的,可能不是最有效的方法,但我仍然受益于JPA提供的易用性,但我认为使用上面描述的JdbcPagingItemReader可能会更好–谢谢!。欢迎对解决此批处理数据库轮询问题的最佳方法发表意见:

我面临同样的情况,我的r eader是一个JpaPagingItemReader,它在writer中更新的字段上进行查询。因此跳过了需要更新的项目的一半,因为页面窗口正在进行,而已经读取的项目不再在阅读器范围内

对我来说,最简单的解决方法是重写JpaPagingItemReader上的getPage方法,以始终返回第一个页面

JpaPagingItemReader<XXXXX> jpaPagingItemReader = new JpaPagingItemReader() {
    @Override
    public int getPage() {
        return 0;
    }
};

您如何确认正在读取哪些行?您可以提供堆栈跟踪/日志信息吗?我意识到问题是我的ItemProcessor更改了实体pojo,我通过添加tracesI编辑了我的问题,我有点困惑。从JpaPagingItemReader返回的项是分离的,因此在没有显式写入的情况下不应更新它们。你能提供整个作业配置吗?我明天会尝试添加更多详细信息。无论如何,我在Spring JPAPagingItemreader的源代码中可以看到,在阅读下一页时,通过刷新并清除上下文,这些项似乎被分离。这就是为什么我不需要ItemWriter来持久保存更新。一个重要的细节可能是,在我的项目处理器中,我更新了SELECTWHERE子句中的布尔值;我不知道这是否可以修改分页光标。刷新和清除只是一开始的清理。在JpaPagingItemReader的末尾,我们循环遍历元素并显式分离它们,或者提交事务以便分离它们。无论哪种方式,从JpaPagingItemReader返回的实体都应该处于分离状态。你能发布你全部工作的配置吗?听起来不错。我需要做更多的测试来验证这个解决方案。因为我的最终SQL将是更复杂的复合主键,where子句包含select in…我还必须从JdbcPagingItemReader继承来添加一些额外的操作。无论如何,感谢您…对于复合密钥,您可以使用org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBeansetSortKeys。它与alias一起工作,因为Michael Minella修复了thanx Michael!。上面关于不分离的说法是不正确的。tx.commit应该使与在doReadPage方法开头创建的事务相关联的所有实体保持分离状态。我也希望如此,但使用调试器,我可以看到阅读器的entityManager包含在processor/writer中更新的实体。在我的例子中,实体没有分离。我同意,即使在JpaPagingItemReader提交之后,这些项目仍然在entityManager中,并且在阅读下一页时会更新。在我的日志文件中,我没有看到一些SQL请求可能对应于重新附加的请求。感谢您的帖子。在我的处理器中,我处理项目并更改一些值,包括选择条件中的一些值。因此,这解释了问题,但没有看到任何可能重新附加项目的操作或日志跟踪,在处理过程中,这些项目似乎仍在事务中,直到下一页阅读。它太大了,但昨天我可以重现问题,即使我将几乎所有内容都放在注释中。项目处理器使用Pojo实体的getter和setter;出现这个问题是因为,如前所述,我修改了SELECT请求条件中的一些字段。我感到困惑。对于任何数据库读取器来说,使用状态列标记已处理的项并在读取器的where子句中使用它不是一种常见的模式吗?这样做的首选方式是什么?如果*PagingRitemReader为每个页面执行独立的查询,为什么还要跟踪页面@Yves Marie L重写getPage的方法似乎奏效了,但似乎应该有更好的方法来处理这个问题。我错过了什么?
select * from table where active = true offset 3 limits 3. 
@Bean
public ItemReader<? extends Entity> reader() {
    JdbcPagingItemReader<Entity> reader = new JdbcPagingItemReader<Entity>();
    final SqlPagingQueryProviderFactoryBean sqlPagingQueryProviderFactoryBean = new SqlPagingQueryProviderFactoryBean();
    sqlPagingQueryProviderFactoryBean.setDataSource(dataSource);
    sqlPagingQueryProviderFactoryBean.setSelectClause("select *");
    sqlPagingQueryProviderFactoryBean.setFromClause("from <your table name>");
    sqlPagingQueryProviderFactoryBean.setWhereClause("where active = true");
    sqlPagingQueryProviderFactoryBean.setSortKey("id");
    try {
        reader.setQueryProvider(sqlPagingQueryProviderFactoryBean.getObject());
    } catch (Exception e) {
        e.printStackTrace();
    }
    reader.setDataSource(dataSource);
    reader.setPageSize(3);
    reader.setRowMapper(new BeanPropertyRowMapper<Entity>(Entity.class));
    return reader;
JpaPagingItemReader<XXXXX> jpaPagingItemReader = new JpaPagingItemReader() {
    @Override
    public int getPage() {
        return 0;
    }
};