Saltar al contenido

Spring batch jpaPagingItemReader ¿por qué no se leen algunas filas?

Solución:

org.springframework.batch.item.database.JpaPagingItemReader crea su propia instancia de entityManager

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

entityManager = entityManagerFactory.createEntityManager(jpaPropertyMap);

Si está dentro de una transacción, como parece ser, las entidades del lector no se separan (de org.springframework.batch.item.database.JpaPagingItemReader # doReadPage):

    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();
    }

Por esta razón, cuando actualiza un elemento en el procesador o escritor, el entityManager del lector aún administra este elemento.

Cuando el lector de elementos lee el siguiente fragmento de datos, descarga el contexto en la base de datos.

Entonces, si miramos su caso, después de la primera parte de los procesos de datos, tenemos en la base de datos:

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

org.springframework.batch.item.database.JpaPagingItemReader usa limit & offset para recuperar datos paginados. Entonces, la siguiente selección creada por el lector se ve así:

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

El lector perderá los elementos con id 4,5,6, porque ahora son las primeras filas recuperadas por la base de datos.

Lo que puede hacer, como solución alternativa, es usar la implementación de jdbc (org.springframework.batch.item.database.JdbcPagingItemReader) ya que no usa limit & offset. Se basa en una columna ordenada (normalmente la columna de identificación), por lo que no perderá ningún dato. Por supuesto, tendrá que actualizar sus datos en el escritor (utilizando JPA o una implementación pura de JDBC)

El lector será más detallado:

@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;

Enfrenté el mismo caso, mi lector era un JpaPagingItemReader que consultaba en un campo que se actualizó en el escritor. En consecuencia, omitir la mitad de los elementos que debían actualizarse, debido a que la ventana de la página avanzaba mientras los elementos ya leídos ya no estaban en el alcance del lector.

La solución más simple para mí fue anular el método getPage en JpaPagingItemReader para devolver siempre la primera página.

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

Un par de cosas a tener en cuenta:

  1. Todas las entidades que se devuelven del JpaPagingItemReader están separados. Logramos esto de dos maneras. Creamos una transacción antes de consultar la página, luego confirmamos la transacción (que separa todas las entidades asociadas con el EntityManager para esa transacción) o llamamos explícitamente entityManager.detach. Hacemos esto para que las funciones como reintentar y omitir se puedan realizar correctamente.
  2. Si bien no publicó todo el código en su procesador, mi corazonada es que en el //do some stuff sección, su artículo se vuelve a adjuntar, por lo que se está realizando la actualización. Sin embargo, sin poder ver ese código, no puedo estar seguro.
  3. En cualquier caso, el uso de un explícito ItemWriter debería estar hecho. De hecho, considero que es un error que no necesitemos un ItemWriter al usar la configuración de Java (lo hacemos para XML).
  4. Para su problema específico de registros faltantes, debe tener en cuenta que ninguno de los *PagingItemReaders. Todos ejecutan consultas independientes para cada página de datos. Entonces, si actualiza los datos subyacentes entre cada página, puede tener un impacto en los elementos devueltos en páginas futuras. Por ejemplo, si mi consulta de paginación especifica where val1 > 4 y tengo un registro de que val1 era 1 para ser 5, en el fragmento 2, ese artículo puede devolverse ya que ahora cumple con los criterios. Si necesita actualizar los valores que están en su cláusula where (lo que impacta en lo que cae en el conjunto de datos que estaría procesando), es mejor agregar una marca procesada de algún tipo que pueda consultar en su lugar.
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *