Spring batch jpaPagingItemReader why some rows are not read?

后端 未结 4 465
Happy的楠姐
Happy的楠姐 2020-12-03 05:46

I \'m using Spring Batch(3.0.1.RELEASE) / JPA and an HSQLBD server database. I need to browse an entire table (using paging) and update items (one by one). So I used a jpaP

4条回答
  •  心在旅途
    2020-12-03 06:32

    org.springframework.batch.item.database.JpaPagingItemReader creates is own entityManager instance

    (from org.springframework.batch.item.database.JpaPagingItemReader#doOpen) :

    entityManager = entityManagerFactory.createEntityManager(jpaPropertyMap);
    

    If you are within a transaction, as it seems to be, reader entities are not detached (from org.springframework.batch.item.database.JpaPagingItemReader#doReadPage):

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

    For this reason, when you update an item into processor, or writer, this item is still managed by reader's entityManager.

    When the item reader reads the next chunk of data, it flushes the context to the database.

    So, if we look at your case, after the first chunk of data processes, we have in database:

    |id|active
    |1 | false
    |2 | false
    |3 | false
    

    org.springframework.batch.item.database.JpaPagingItemReader uses limit & offset to retrieve paginated data. So the next select created by the reader looks like :

    select * from table where active = true offset 3 limits 3. 
    

    Reader will miss the items with id 4,5,6, because they are now the first rows retrieved by database.

    What you can do, as a workaround, is to use jdbc implementation (org.springframework.batch.item.database.JdbcPagingItemReader) as it does not use limit & offset. It is based on a sorted column (typically the id column), so you will not miss any data. Of course, you will have to update your data into the writer (using either JPA ou pure JDBC implementation)

    Reader will be more verbose:

    @Bean
    public ItemReader reader() {
        JdbcPagingItemReader reader = new JdbcPagingItemReader();
        final SqlPagingQueryProviderFactoryBean sqlPagingQueryProviderFactoryBean = new SqlPagingQueryProviderFactoryBean();
        sqlPagingQueryProviderFactoryBean.setDataSource(dataSource);
        sqlPagingQueryProviderFactoryBean.setSelectClause("select *");
        sqlPagingQueryProviderFactoryBean.setFromClause("from ");
        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.class));
        return reader;
    

提交回复
热议问题