processes jobbuilderfactory job example chunk batch baeldung java spring spring-batch

jobbuilderfactory - ¿Por qué falla el método de destrucción ''cierre'' para JPAPagingItemReader configurado con la configuración de Java?



spring batch processes (1)

Estamos intentando convertir nuestros trabajos Spring-Batch de la configuración XML a la configuración de Java. Estamos utilizando Spring 4.0.1.RELEASE y Spring Batch 2.2.1.RELEASE.

Después de convertir un trabajo, la siguiente advertencia comenzó a aparecer en el archivo de registro:

15-Apr-2014 09: 59: 26.335 [Thread-2] WARN osbfsDisposableBeanAdapter - La invocación del método de destrucción ''cerrar'' falló en el bean con el nombre ''fileReader'': org.springframework.batch.item.ItemStreamException: Error al cerrar el lector de artículos

El stacktrace completo es:

org.springframework.batch.item.ItemStreamException: Error while closing item reader at org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader.close(AbstractItemCountingItemStreamItemReader.java:131) ~[spring-batch-infrastructure-2.2.1.RELEASE.jar:na] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.6.0_25] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[na:1.6.0_25] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[na:1.6.0_25] at java.lang.reflect.Method.invoke(Method.java:597) ~[na:1.6.0_25] at org.springframework.beans.factory.support.DisposableBeanAdapter.invokeCustomDestroyMethod(DisposableBeanAdapter.java:349) [spring-beans-4.0.1.RELEASE.jar:4.0.1.RELEASE] at org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:272) [spring-beans-4.0.1.RELEASE.jar:4.0.1.RELEASE] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroyBean(DefaultSingletonBeanRegistry.java:540) [spring-beans-4.0.1.RELEASE.jar:4.0.1.RELEASE] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingleton(DefaultSingletonBeanRegistry.java:516) [spring-beans-4.0.1.RELEASE.jar:4.0.1.RELEASE] at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingleton(DefaultListableBeanFactory.java:824) [spring-beans-4.0.1.RELEASE.jar:4.0.1.RELEASE] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingletons(DefaultSingletonBeanRegistry.java:485) [spring-beans-4.0.1.RELEASE.jar:4.0.1.RELEASE] at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:921) [spring-context-4.0.1.RELEASE.jar:4.0.1.RELEASE] at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:895) [spring-context-4.0.1.RELEASE.jar:4.0.1.RELEASE] at org.springframework.context.support.AbstractApplicationContext$1.run(AbstractApplicationContext.java:809) [spring-context-4.0.1.RELEASE.jar:4.0.1.RELEASE] Caused by: java.lang.IllegalStateException: EntityManager is closed at org.hibernate.ejb.EntityManagerImpl.close(EntityManagerImpl.java:132) ~[hibernate-entitymanager-4.2.5.Final.jar:4.2.5.Final] at sun.reflect.GeneratedMethodAccessor14.invoke(Unknown Source) ~[na:na] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[na:1.6.0_25] at java.lang.reflect.Method.invoke(Method.java:597) ~[na:1.6.0_25] at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:334) ~[spring-orm-4.0.1.RELEASE.jar:4.0.1.RELEASE] at $Proxy67.close(Unknown Source) ~[na:na] at org.springframework.batch.item.database.JpaPagingItemReader.doClose(JpaPagingItemReader.java:236) ~[spring-batch-infrastructure-2.2.1.RELEASE.jar:na] at org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader.close(AbstractItemCountingItemStreamItemReader.java:128) ~[spring-batch-infrastructure-2.2.1.RELEASE.jar:na] ... 13 common frames omitted

Este error solo aparece cuando se utiliza la configuración de Java para el trabajo, pero no la configuración XML. El paso configurado usando XML se ve así:

<batch:step id="createFile" next="insertFile"> <batch:tasklet> <batch:chunk reader="fileReader" writer="fileWriter" commit-interval="#{jobProperties[commit_interval]}" /> </batch:tasklet> </batch:step> <bean id="fileReader" class="org.springframework.batch.item.database.JpaPagingItemReader"> <property name="entityManagerFactory" ref="entityManagerFactory" /> <property name="queryString" value="select mt from MyTable mt where status in (''1'',''2'',''3'')" /> <property name="pageSize" value="1000" /> </bean>

La configuración de Java es:

@Bean public Job fileProcessJob(JobBuilderFactory jobBuilders, Step loadConfig, Step createFile, Step insertFile ) { return jobBuilders.get(moduleName) .start(loadConfig) .next(createFile) .next(insertFile) .build() .build(); } @Bean public ItemReader<MyTable> cetFileReader(EntityManagerFactory entityManagerFactory) { JpaPagingItemReader<MyTable> itemReader = new JpaPagingItemReader<MyTable>(); itemReader.setEntityManagerFactory(entityManagerFactory); itemReader.setQueryString("select mt from MyTable mt where status in (''1'',''2'',''3'')"); itemReader.setPageSize(1000); return itemReader; }

¿Por qué aparece esta advertencia en los registros cuando se utiliza la configuración de Java pero no la configuración de XML?


TLDR;

Spring intenta inferir automáticamente un destroyMethod cuando usa la configuración de Java (pero no lo hace cuando usa la configuración de XML). Para deshabilitar esta inferencia automática, use:

@Bean(destroyMethod="")

La respuesta está en el JavaDoc de la anotación @Bean ; específicamente en el método org.springframework.context.annotation.Bean.destroyMethod() (el énfasis es mío):

El nombre opcional de un método para llamar a la instancia de bean al cerrar el contexto de la aplicación, por ejemplo, un método close () en una implementación JDBC DataSource, o un objeto Hibernate SessionFactory. El método no debe tener argumentos, pero puede lanzar cualquier excepción.

Para conveniencia del usuario, el contenedor intentará inferir un método de destrucción contra un objeto devuelto por el método @Bean. Por ejemplo, dado un método @Bean que devuelve un DBCP BasicDataSource de Apache Commons, el contenedor notará el método close () disponible en ese objeto y lo registrará automáticamente como destroyMethod. Esta ''inferencia del método de destrucción'' se limita actualmente a la detección de solo los métodos públicos, sin argumentos, llamados ''cerrar''. El método se puede declarar en cualquier nivel de la jerarquía de herencia y se detectará independientemente del tipo de retorno del método @Bean (es decir, la detección se produce de forma reflectiva contra la propia instancia de bean en el momento de la creación).

Para deshabilitar la inferencia del método de destrucción para un @Bean particular, especifique una cadena vacía como el valor, por ejemplo, @Bean (destroyMethod = ""). Tenga en cuenta que las interfaces org.springframework.beans.factory.DisposableBean y java.io.Closeable / java.lang.AutoCloseable no se detectarán y se invocará el correspondiente método destroy / close.

Nota: Solo se invoca en beans cuyo ciclo de vida está bajo el control total de la fábrica, lo cual es siempre el caso para singletons pero no está garantizado para ningún otro alcance.

Después de cambiar la configuración de Java a:

@Bean(destroyMethod="") public ItemReader<MyTable> cetFileReader(EntityManagerFactory entityManagerFactory) { JpaPagingItemReader<MyTable> itemReader = new JpaPagingItemReader<MyTable>(); itemReader.setEntityManagerFactory(entityManagerFactory); itemReader.setQueryString("select mt from MyTable mt where status in (''1'',''2'',''3'')"); itemReader.setPageSize(1000); return itemReader; }

La advertencia ya no apareció. Pude confirmar esto colocando un punto de interrupción en el método org.springframework.beans.factory.support.DisposableBeanAdapter.destroy() y org.springframework.beans.factory.support.DisposableBeanAdapter.destroy() el trabajo configurado XML y el trabajo configurado Java.

Para la configuración XML:

  • DisposableBeanAdapter.invokeDisposableBean = false
  • DisposableBeanAdapter.destroyMethod = null
  • DisposableBeanAdapter.destroyMethodName = null

Para la configuración de Java (sin destroyMethod="" set):

  • DisposableBeanAdapter.invokeDisposableBean = false
  • DisposableBeanAdapter.destroyMethod = public void org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader.close () lanza org.springframework.batch.item.ItemStreamException
  • DisposableBeanAdapter.destroyMethodName = close

Para la configuración de Java (con destroyMethod="" set):

  • DisposableBeanAdapter.invokeDisposableBean = false
  • DisposableBeanAdapter.destroyMethod = null
  • DisposableBeanAdapter.destroyMethodName = null

Basándome en estas observaciones, llego a la conclusión de que el contenedor no intenta inferir un método de destrucción cuando se configura a través de XML; pero lo hace cuando se configura a través de Java. Es por eso que la advertencia se muestra para la configuración de Java y no para la configuración XML.

Además, el método que el contenedor deduce es que destroyMethod parece provenir de org.springframework.batch.item.ItemStreamSupport.close() . Por lo tanto, esto podría ocurrir en cualquier bean que implemente la interfaz ItemStreamSupport que se configura a través de la anotación @Bean .

Se ha agregado una nota al material de referencia de Spring Framework para @Bean que describe este comportamiento:

De forma predeterminada, los beans definidos con la configuración de Java que tienen un método de cierre o cierre público se enlistan automáticamente con una devolución de llamada de destrucción. Si tiene un método de cierre o cierre público y no desea que se llame cuando se cierre el contenedor, simplemente agregue @Bean (destroyMethod = "") a su definición de bean para deshabilitar el modo predeterminado (inferido).