Descripción general del procesamiento por lotes en Java EE 7.0

Examine la nueva capacidad de procesamiento por lotes proporcionada por JSR 352 para Java EE 7.

El procesamiento por lotes se utiliza en muchas industrias para tareas que van desde el procesamiento de nóminas, la generación de estados de cuenta, trabajos al final del día, como el cálculo de intereses y ETL (extraer, cargar y transformar) en un almacén de datos, y muchos más. Por lo general, el procesamiento por lotes es orientado a granel, no interactivo y de larga duración, y puede ser intensivo en datos o computación. Los trabajos por lotes pueden ejecutarse según lo programado o iniciarse a pedido. Además, dado que los trabajos por lotes suelen ser trabajos de larga duración, la comprobación y el reinicio son características comunes que se encuentran en los trabajos por lotes.

JSR 352 (Procesamiento por lotes para la plataforma Java), parte de la plataforma Java EE 7 recientemente introducida, define el modelo de programación para aplicaciones por lotes más un tiempo de ejecución para ejecutar y administrar trabajos por lotes. Este artículo cubre algunos de los conceptos clave, incluidos los aspectos destacados de las características, una descripción general de las API seleccionadas, la estructura del lenguaje de especificación del trabajo y una aplicación por lotes de muestra. El artículo también describe cómo puede ejecutar aplicaciones por lotes utilizando GlassFish Server Open Source Edition 4.0.

Arquitectura de procesamiento por lotes

Esta sección y la Figura 1 describen los componentes básicos de la arquitectura de procesamiento por lotes.

Figura 1.

Figura 1.

  • Un trabajo encapsula todo el proceso por lotes. Un trabajo contiene uno o más pasos. Un trabajo se elabora utilizando un Lenguaje de especificación de trabajos (JSL) que especifica la secuencia en la que se deben ejecutar los pasos. En JSR 352, JSL se especifica en un archivo XML llamado archivo XML de trabajo. En resumen, un trabajo (con JSR 352) es básicamente un contenedor para pasos.
  • Un paso es un objeto de dominio que encapsula una fase secuencial independiente del trabajo. Un paso contiene toda la lógica y los datos necesarios para realizar el procesamiento real. La especificación de lote deja deliberadamente la definición de un paso vaga porque el contenido de un paso es puramente específico de la aplicación y puede ser tan complejo o simple como el desarrollador desea. Hay dos tipos de pasos: chunk y batchlet.
    • Un pedazo de estilo paso contiene exactamente un ItemReader, un ItemProcessor y un ItemWriter. En este patrón, ItemReader lee un elemento a la vez, ItemProcessor procesa el elemento según la lógica de negocio (como «calcular el saldo de la cuenta») y lo entrega al tiempo de ejecución por lotes para agregarlo. Una vez que el número de elementos» tamaño de fragmento»se lee y procesa, se entregan a un ItemWriter, que escribe los datos (por ejemplo, en una tabla de base de datos o en un archivo plano). A continuación, se confirma la transacción.
    • JSR 352 también define un paso de tipo roll-your-own llamado batchlet. Un lote es libre de usar cualquier cosa para llevar a cabo el paso, como enviar un correo electrónico.
  • JobOperator proporciona una interfaz para administrar todos los aspectos del procesamiento de trabajos, incluidos los comandos operativos, como iniciar, reiniciar y detener, así como los comandos del repositorio de trabajos, como la recuperación de ejecuciones de trabajos y pasos. Consulte la sección 10.4 de la especificación JSR 352 para obtener más detalles sobre JobOperator.
  • JobRepository contiene información sobre los trabajos que se ejecutan actualmente y los trabajos que se ejecutaron en el pasado. JobOperator proporciona API para acceder a este repositorio. Un JobRepository podría implementarse usando, por ejemplo, una base de datos o un sistema de archivos.

Desarrollo de una Aplicación de Procesamiento de nómina Simple

Este artículo muestra algunas de las características clave de JSR 352 utilizando una aplicación de procesamiento de nómina simple. La aplicación se ha mantenido intencionalmente bastante simple para centrarse en los conceptos clave de JSR 352.

El SimplePayrollJob el trabajo por lotes implica leer los datos de entrada para el procesamiento de nóminas a partir de un archivo de valores separados por comas (CSV). Cada línea en el archivo contiene una identificación de empleado y el salario base (por mes) de un empleado. El trabajo por lotes luego calcula el impuesto a retener, el bono y el salario neto. El trabajo finalmente necesita escribir los registros de nómina procesados en una tabla de base de datos.

Usamos un archivo CSV en este ejemplo solo para demostrar que JSR 352 permite que las aplicaciones por lotes lean y escriban desde cualquier fuente arbitraria.

Lenguaje de especificación de trabajo para la Aplicación de Procesamiento de nómina

Discutimos que un paso es un objeto de dominio que encapsula una fase secuencial independiente del trabajo, y un trabajo es básicamente un contenedor para uno o más pasos.

En JSR 352, un JSL básicamente especifica el orden en el que se deben ejecutar los pasos para realizar el trabajo. El JSL es lo suficientemente potente como para permitir la ejecución condicional de pasos, y también permite que cada paso tenga sus propias propiedades, oyentes, etc.

Una aplicación por lotes puede tener tantos JSL como desee, lo que le permite iniciar tantos trabajos por lotes como sea necesario. Por ejemplo, una aplicación puede tener dos JSL, uno para el procesamiento de nóminas y otro para la generación de informes. Cada JSL debe tener un nombre único y debe colocarse en el directorio META-INF/batch-jobs. Los subdirectorios bajo META-INF/batch-jobs se ignoran.

Nuestro JSL para el procesamiento de nóminas se coloca en un archivo llamado SimplePayrollJob.xm l y se parece a la Lista 1:

 <job xmlns=http://xmlns.jcp.org/xml/ns/javaee version="1.0"> <step> <chunk item-count="2"> <reader ref="simpleItemReader/> <processor ref="simpleItemProcessor/> <writer ref="simpleItemWriter/> </chunk> </step></job>

Listado 1

Nuestro SimplePayrollJob trabajo por lotes de un solo paso (llamado «proceso»). Es un paso de estilo fragmento y tiene (como se requiere para un paso de estilo fragmento), un ItemReader, un ItemProcessor y un ItemWriter. Las implementaciones para ItemReaderItemProcessor y ItemWriter para este paso se especifica el uso de la etiqueta ref atributo en la etiqueta <reader><processor>, y <writer> elementos.

Cuando se envía el trabajo (veremos más adelante cómo enviar trabajos por lotes), el tiempo de ejecución por lotes comienza con el primer paso en el JSL y avanza hasta que se completa todo el trabajo o falla uno de los pasos. El JSL es lo suficientemente potente como para permitir tanto pasos condicionales como la ejecución paralela de pasos, pero no cubriremos esos detalles en este artículo.

El atributo item-count, que se define como 2 en el listado 1, define el tamaño del fragmento del fragmento.

Aquí hay una descripción general de alto nivel de cómo se ejecutan los pasos de estilo fragmento. Consulte la sección 11.6 («Procesamiento regular de fragmentos») de la especificación JSR 352 para obtener más detalles.

  1. Iniciar una transacción.
  2. Invocar el ItemReader y pasar el elemento de leer por el ItemReader para el ItemProcessorItemProcessor procesa el elemento y devuelve el elemento procesado al tiempo de ejecución por lotes.
  3. El tiempo de ejecución por lotes repite el paso 2 item-count y mantiene una lista de elementos procesados.
  4. El tiempo de ejecución por lotes invoca el ItemWriter que escribe item-count número de elementos procesados.
  5. Si se producen excepciones de ItemReaderItemProcessor o ItemWriter, falla la transacción y el paso es marcado como «ERROR.»Consulte la sección 5.2.1.2.1 («Excepciones de omisión») de la especificación JSR 352.
  6. Si no hay excepciones, el tiempo de ejecución por lotes obtiene datos de punto de control de ItemReader y ItemWriter (consulte la sección 2.5 de la especificación JSR 352 para obtener más detalles). El tiempo de ejecución por lotes confirma la transacción.
  7. Los pasos del 1 al 6 se repiten si ItemReader tiene más datos para leer.

Esto significa que en nuestro ejemplo, el tiempo de ejecución por lotes leerá y procesará dos registros y el ItemWriter escribirá dos registros por transacción.

Escribiendo el ItemReaderItemProcessor, y ItemWriter

Escribiendo el ItemReader

Nuestro lote de procesamiento de nómina JSL define un solo paso de estilo de fragmento y especifica que el paso usa un ItemReader con el nombre simpleItemReader. Nuestra aplicación contiene una implementación de ItemReader para leer datos CSV de entrada. El listado 2 muestra un fragmento de nuestro ItemReader:

 @Namedpublic class SimpleItemReader extends AbstractItemReader { @Inject private JobContext jobContext; ...}

Listado 2

tenga en cuenta que la clase está anotado con la etiqueta @Named anotación. Debido a que la anotación @Named utiliza el valor predeterminado, el nombre de Inyección de Contextos e Dependencias (CDI) para este frijol es simpleItemReader. El JSL especifica el nombre CDI del elemento ItemReader en el elemento <reader>. Esto permite que el tiempo de ejecución por lotes instancie (a través de CDI) nuestro ItemReader cuando se ejecuta el paso.

Nuestro ItemReader también se inyecta un JobContextJobContext permite que el artefacto por lotes (ItemReader, en este caso) lea los valores que se pasaron durante el envío del trabajo.

Nuestra nómina SimpleItemReaderanula el método open() para abrir la entrada desde la que se leen los datos de entrada de nómina. Como veremos más adelante, el parámetro prevCheckpointInf o no será nulo si se reinicia el trabajo.

En nuestro ejemplo, el método open(), que se muestra en el Listado 3, abre el archivo de entrada de nómina (que se ha empaquetado junto con la aplicación).

public void open(Serializable prevCheckpointInfo) throws Exception { JobOperator jobOperator = BatchRuntime.getJobOperator(); Properties jobParameters = jobOperator.getParameters(jobContext.getExecutionId()); String resourceName = (String) jobParameters.get("payrollInputDataFileName"); inputStream = new FileInputStream(resourceName); br = new BufferedReader(new InputStreamReader(inputStream)); if (prevCheckpointInfo != null) recordNumber = (Integer) prevCheckpointInfo; for (int i=1; i<recordNumber; i++) { //Skip upto recordNumber br.readLine(); } System.out.println(" Opened Payroll file for reading from record number: " + recordNumber); }

Listing 3

El método readItem() básicamente lee una línea de datos del archivo de entrada y determina si la línea contiene dos enteros (uno para ID de empleado y otro para salario base). Si hay dos enteros, crea y devuelve una nueva instancia de PayrollInputRecord y vuelve al tiempo de ejecución por lotes (que luego se pasa a ItemWriter).

public Object readItem() throws Exception { Object record = null; if (line != null) { String fields = line.split("+"); PayrollInputRecord payrollInputRecord = new PayrollInputRecord(); payrollInputRecord.setId(Integer.parseInt(fields)); payrollInputRecord.setBaseSalary(Integer.parseInt(fields)); record = payrollInputRecord; //Now that we could successfully read, Increment the record number recordNumber++; } return record;}

Listing 4

El método checkpointInfo() es llamado por el tiempo de ejecución por lotes al final de cada transacción de fragmento exitosa. Esto permite al Lector comprobar la última posición de lectura correcta.

En nuestro ejemplo, el checkpointInfo() devuelve el recordNumber que indica el número de registros que se han leído correctamente, como se muestra en el Listado 5.

@Overridepublic Serializable checkpointInfo() throws Exception { return recordNumber;}

Listado 5

la Escritura de la etiqueta ItemProcessor

Nuestro SimpleItemProcessor sigue un patrón similar al patrón de SimpleItemReader.

El métodoprocessItem() recibe (del tiempo de ejecución por lotes) elPayrollInputRecord. A continuación, calcula el impuesto y la red y devuelve un PayrollRecord como salida. Aviso en el Listado 6 que el tipo de objeto devuelto por un ItemProcessor puede ser muy diferente del tipo de objeto que recibió de ItemReader.

 @Namedpublic class SimpleItemProcessor implements ItemProcessor { @Inject private JobContext jobContext; public Object processItem(Object obj) throws Exception { PayrollInputRecord inputRecord = (PayrollInputRecord) obj; PayrollRecord payrollRecord = new PayrollRecord(); int base = inputRecord.getBaseSalary(); float tax = base * 27 / 100.0f; float bonus = base * 15 / 100.0f; payrollRecord.setEmpID(inputRecord.getId()); payrollRecord.setBase(base); payrollRecord.setTax(tax); payrollRecord.setBonus(bonus); payrollRecord.setNet(base + bonus - tax); return payrollRecord; } }

Listado 6

la Escritura de la etiqueta ItemWriter

Por ahora, SimpleItemWriter debe ser la siguiente predecible líneas para usted.

La única diferencia es que inyecta una instancia EntityManager para que pueda conservar las instancias PayrollRecord (que son entidades JPA) en una base de datos, como se muestra en el Listado 7.

 @Namedpublic class SimpleItemWriter extends AbstractItemWriter { @PersistenceContext EntityManager em; public void writeItems(List list) throws Exception { for (Object obj : list) { System.out.println("PayrollRecord: " + obj); em.persist(obj); } }}

el Listado 7

El writeItems() método persiste todo el PayrollRecord instancias en una tabla de base de datos usando JPA. Como máximo, habrá entradas item-count (el tamaño del fragmento) en la lista.

Ahora que tenemos nuestro JSL, ItemReaderItemProcessor, y ItemWriter, veamos cómo se puede enviar un trabajo por lotes.

Iniciar un trabajo por lotes desde un Servlet

Tenga en cuenta que la mera presencia de un archivo XML de trabajo u otros artefactos por lotes (como ItemReader) no significa que un trabajo por lotes se inicie automáticamente cuando se implemente la aplicación. Un trabajo por lotes debe iniciarse explícitamente, por ejemplo, desde un servlet o desde un temporizador JavaBeans Empresarial (EJB) o un método de negocio EJB.

En nuestra aplicación de nómina, usamos un servlet (llamado PayrollJobSubmitterServlet) para enviar un trabajo por lotes. El servlet muestra una página HTML que presenta al usuario un formulario que contiene dos botones. Cuando se hace clic en el primer botón, denominado Calcular nómina, el servlet invoca el método startNewBatchJob, que se muestra en la Lista 8, que inicia un nuevo trabajo por lotes.

 private long startNewBatchJob()throws Exception { JobOperator jobOperator = BatchRuntime.getJobOperator(); Properties props = new Properties(); props.setProperty("payrollInputDataFileName", payrollInputDataFileName); return jobOperator.start(JOB_NAME, props);}

Listado 8

El primer paso es obtener una instancia de JobOperator. Esto se puede hacer llamando a lo siguiente:

JobOperator jobOperator = BatchRuntime.getJobOperator();

El servlet crea un objeto Properties y almacena el nombre del archivo de entrada en él. Finalmente, se inicia un nuevo trabajo por lotes llamando a lo siguiente:

jobOperator.start(jobName, properties)

El jobnameno es más que el nombre de archivo XML JSL del trabajo (menos la extensión .xml). El parámetro properties sirve para pasar cualquier dato de entrada al trabajo. El objeto Properties (que contiene el nombre del archivo de entrada de nómina) se pone a disposición de otros artefactos por lotes (como ItemReaderItemProcessor, etc.) a través de la interfaz JobContext.

El tiempo de ejecución por lotes asigna un ID único, llamado ID de ejecución, para identificar cada ejecución de un trabajo, ya sea un trabajo recién enviado o un trabajo reiniciado. Muchos de los métodos JobOperator toman el ID de ejecución como parámetro. Usando el ID de ejecución, un programa puede obtener el estado de ejecución actual (y pasado) y otras estadísticas sobre el trabajo. El método JobOperator.start() devuelve el ID de ejecución del trabajo que se inició.

Recuperar detalles Sobre los trabajos por lotes

Cuando se envía un trabajo por lotes, el tiempo de ejecución por lotes crea una instancia de JobExecution para rastrearlo. JobExecution tiene métodos para obtener varios detalles, como la hora de inicio del trabajo, la hora de finalización del trabajo, el estado de salida del trabajo, etc. Para obtener el JobExecution para un ID de ejecución, puede usar el método JobOperator.getJobExecution(executionId). Listado 9 muestra la definición de JobExecution:

 package javax.batch.runtime;public interface JobExecution { long getExecutionId(); java.lang.String getJobName(); javax.batch.runtime.BatchStatus getBatchStatus(); java.util.Date getStartTime(); java.util.Date getEndTime(); java.lang.String getExitStatus(); java.util.Date getCreateTime(); java.util.Date getLastUpdatedTime(); java.util.Properties getJobParameters();}

Listado 9

Empaquetado de la Aplicación

Ahora que ya tenemos nuestro JSL, ItemReaderItemProcessorItemWriter, y que nuestro servlet listo, es hora de que el paquete de ellos y conseguir listo para implementar.

Puede implementar su aplicación por lotes como cualquiera de los archivos Java EE compatibles (por ejemplo, .war.jar, o .ear). Puede agrupar sus clases de artefactos por lotes junto con otras clases de Java EE (como EJB beans y servlets).

El único requisito especial es que debe colocar sus JSL de trabajo en el directorio META-INF/batch-jobs para los archivos .jar. Para los tipos de archivo .war, coloque sus JSL de trabajo en el directorio WEB-INF/classes/META-INF/batch-jobs.

Implementar y ejecutar la Aplicación de ejemplo de Nómina en GlassFish 4.0

Vamos a implementar la aplicación de nómina que hemos desarrollado en el servidor de aplicaciones GlassFish 4.0. GlassFish 4.0 es la implementación de referencia (RI) para la especificación Java EE 7.0 y también contiene la RI para JSR 352. Puede encontrar más información sobre GlassFish 4.0 en http://glassfish.org y sobre Java Batch 1.0 RI en https://javaee.github.io/.

Instalación e inicio de GlassFish 4.0

Puede descargar GlassFish 4.0 desde https://glassfish.java.net/download.html y luego instalarlo. Inicie GlassFish 4.0 abriendo una ventana de comandos y ejecutando el siguiente comando:

<GlassFish Install Dir>/bin/asadmin start-domain

Debido a que la aplicación de nómina de muestra utiliza una base de datos (para escribir datos procesados), necesitamos una base de datos que se ejecute antes de poder ejecutar nuestra aplicación. Puede iniciar la base de datos de Apache Derby ejecutando el siguiente comando:

<GlassFish Install Dir>/bin/asadmin start-database

Compilar, Empaquetar e Implementar la aplicación de nómina

Primero, cree un nuevo directorio llamadohello-batch. A continuación, cambie al directorio hello-batch :

cd hello-batch

Para compilar y empaquetar, ejecute el siguiente comando, que se crea hello-batch.war en el directorio de destino:

mvn clean package

Para desplegar hello-batch.war, ejecute el siguiente comando:

<GlassFish Install Dir>/bin/asadmin deploy target/hello-batch.war

Si desea volver a implementar la aplicación, puede ejecutar el siguiente comando:

<GlassFish Install Dir>/bin/asadmin deploy -force target/hello-batch.war

Ejecutar la aplicación de nómina

Una vez que implemente el archivo hello-batch.war, puede ejecutar la aplicación accediendo a http://localhost:8080/hello-batch/PayrollJobSubmitterServlet desde un navegador. El acceso a esta URL debe presentar la pantalla que se muestra en la Figura 2.

Figura 2

Figura 2

Haga clic en el botón Calcular nómina y verá una nueva entrada en la tabla, como se muestra en la Figura 3.

Figura 3

Figura 3

Haga clic en el botón Actualizar y verá las columnas Estado de salida y Hora de finalización actualizadas para el último trabajo (consulte la Figura 4). La columna Estado de salida muestra si el trabajo falló o se completó correctamente. Dado que nuestro SimplePayrollJob no tiene ningún error (al menos no todavía!), el Estado de salida se muestra COMPLETADO.

Figura 4

Figura 4

Haga clic en los botones Calcular nómina y Actualizar un par de veces más. Tenga en cuenta que cada vez que se inicia un trabajo, se le da un nuevo ID de ejecución (e ID de instancia), como se muestra en la Figura 5.

Figura 5

Figura 5

Reiniciar trabajos fallidos

Hasta ahora, habíamos estado iniciando trabajos por lotes utilizando el método jobOperator.start(). Digamos que nuestro archivo de entrada de nómina tiene algunos errores. El ItemReader o el ItemProcessor podrían detectar registros no válidos y fallar el paso actual y el trabajo. El administrador o el usuario final pueden corregir el error y reiniciar el trabajo por lotes. Este enfoque de lanzar un nuevo trabajo que comienza desde el principio después de recuperarse de errores podría no escalar si la cantidad de datos que se procesarán es grande. JobOperator proporciona otro método llamado restart() para resolver exactamente este problema.

Visión general Rápida de JobInstance y JobExecution

Hemos visto anteriormente que un trabajo es básicamente un contenedor de pasos. Cuando se inicia un trabajo, se debe realizar un seguimiento para que el tiempo de ejecución por lotes cree un JobInstance. A JobInstance se refiere al concepto de una ejecución lógica. En nuestro ejemplo, tenemos un PayrollJob y si el PayrollJob se ejecuta cada mes, habrá un Ene-2013 JobInstance y habrá otra Feb-2013 JobInstance, y así sucesivamente.

Si el procesamiento de nómina para Enero de 2013 falla, debe reiniciarse (presumiblemente después de corregir el error), pero sigue siendo la ejecución de enero de 2013 porque aún está procesando registros de enero de 2013.

A JobExecution se refiere al concepto de un único intento de ejecutar un trabajo. Cada vez que se inicia o reinicia un trabajo, se crea un nuevo JobExecution que pertenece al mismo JobInstance. En nuestro ejemplo, si el Jan-2013 JobInstance se reinicia, es el mismo Jan-2013 JobInstance pero un nuevo JobExecution se crea que pertenece a la misma JobInstance.

En resumen, un trabajo puede tener uno o más instancias de JobInstance y cada JobInstance puede tener uno o más JobExecution instancias. Usar un nuevo JobInstancesignifica «comenzar desde el principio» y usar un JobInstance generalmente significa «comenzar desde donde lo dejó.»

Reanudar trabajos fallidos

Si recuerda, se ejecuta un paso de estilo fragmento en una transacción en la que las entradas item-count se leen, procesan y escriben. Después de la etiqueta ItemWriter‘s writeItems() ha sido invocada, el lote tiempo de ejecución llama el checkpointInfo() método en ambos ItemReader y ItemWriter. Esto permite tanto ItemReader como ItemWriter marcar (guardar) su progreso actual. Los datos marcados para un ItemReader podrían ser cualquier cosa que lo ayude a reanudar la lectura. Por ejemplo, nuestro SimpleItemReader necesita guardar el número de línea hasta el que ha leído con éxito hasta ahora.

La sección 10.8 de la especificación JSR 352 describe el procesamiento de reinicio en detalle.

Tomemos un momento para ver el archivo de registro donde nuestrosSimpleItemReader muestra algunos mensajes útiles de los métodosopen() ycheckpoint(). Cada mensaje tiene el prefijo para que pueda identificar rápidamente los mensajes. El archivo de registro se encuentra en <GlassFish install Dir>/domains/domain1/logs/server.log.

el Listado 10 muestra los mensajes que están precedidos por la cadena :

 Opened Payroll File. Will start reading from record number: 0]] checkpointInfo() called. Returning current recordNumber: 2]] checkpointInfo() called. Returning current recordNumber: 4]] checkpointInfo() called. Returning current recordNumber: 6]] checkpointInfo() called. Returning current recordNumber: 8]] checkpointInfo() called. Returning current recordNumber: 9]] close called.]]

el Listado 10

Nota: también puede utilizar el comando tail -f server.log | grep SimpleItemReader.

Porque, nuestro archivo XML de trabajo (SimplePayrollJob.xml) especifica un valor de 2 para item-count como tamaño de fragmento, el tiempo de ejecución por lotes llama a checkpointInfo() en nuestro ItemReader cada dos registros. El tiempo de ejecución por lotes almacena esta información de control en JobRepository. Por lo tanto, si se produce un error durante el procesamiento de fragmentos, la aplicación por lotes debe poder reanudarse desde el último punto de control exitoso.

Vamos a introducir algunos errores en nuestro archivo de datos de entrada y ver cómo podemos recuperarnos de los errores de entrada.

Si observa la salida de nuestro servlet, que se encuentra en <GlassFish install Dir>/domains/domain1/applications/hello-batch/WEB-INF/classes/payroll-data/payroll-data.csv, verá que muestra la ubicación del archivo de entrada desde donde se leen los datos CSV para nuestra aplicación de nómina. Listing 11 muestra el contenido del archivo:

1, 81002, 82003, 83004, 84005, 85006, 86007, 87008, 88009, 8900

Listing 11

Abra su editor favorito e introduzca un error. Por ejemplo, digamos que agregamos algunos caracteres al campo salario en el octavo registro, como se muestra en el Listado 12:

1, 81002, 82003, 83004, 84005, 85006, 86007, 87008, abc88009, 8900

Listado de 12

Guarde el archivo y salga del editor. Vuelva a su navegador y haga clic en el botón Calcular nómina seguido del botón Actualizar. Verá que el trabajo enviado recientemente falló, como se muestra en la Figura 6. (Mira la columna de Estado de salida.)

Figura 6

Figura 6

También verá que aparece un botón de reinicio junto al ID de ejecución del trabajo que acaba de fallar. Si hace clic en Actualizar, el trabajo fallará (porque aún no hemos solucionado el problema). La Figura 7 muestra lo que se muestra después de unos pocos clics del botón Actualizar.

Figura 7

Figura 7

Si observa el registro del servidor GlassFish (ubicado en <GlassFish install Dir>/domains/domain1/logs/server.log), verá una excepción, como se muestra en el Listado 13:

Caught exception executing step: com.ibm.jbatch.container.exception.BatchContainerRuntimeException: Failure in Read-Process-Write Loop......Caused by: java.lang.NumberFormatException: For input string: "abc8800" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Integer.parseInt(Integer.java:492) at java.lang.Integer.parseInt(Integer.java:527) at com.oracle.javaee7.samples.batch.hello.SimpleItemReader.readItem(SimpleItemReader.java:100) 

Listing 13

También debe notar que cuando hace clic en el botón Reiniciar, se crea una nueva ejecución de trabajo, pero su ID de instancia de trabajo sigue siendo el mismo. Al hacer clic en el botón Actualizar, nuestro servlet PayrollJobSubmitter llama a un método llamado restartBatchJob(), que se muestra en el Listado 14:

private long restartBatchJob(long lastExecutionId) throws Exception { JobOperator jobOperator = BatchRuntime.getJobOperator(); Properties props = new Properties(); props.setProperty("payrollInputDataFileName", payrollInputDataFileName); return jobOperator.restart(lastExecutionId, props);}

Listing 14

La línea clave en el Listado 14 es llame al método JobOperator‘s restart(). Este método toma un objeto Properties al igual que start(), pero en lugar de pasar un nombre de archivo XML de trabajo, pasa el ID de ejecución del trabajo que ha fallado más recientemente. Utilizando el ID de ejecución del trabajo que ha fallado más recientemente, el tiempo de ejecución por lotes puede recuperar el último punto de comprobación de la ejecución anterior que se ha realizado correctamente. Los datos de punto de control recuperados se pasan al método open() de nuestro SimpleItemReader(y ItemWriter) para permitirles reanudar la lectura (y escritura) desde el último punto de control exitoso.

Mientras se asegura de que su navegador muestre la página con un botón de reinicio, edite el archivo de nuevo y elimine los caracteres extraños del octavo registro. A continuación, haga clic en los botones Reiniciar y Actualizar. La última ejecución debe mostrar un estado COMPLETADO, como se muestra en la Figura 8.

Figura 8

Figura 8

Es hora de buscar en el archivo de registro para comprender lo que acaba de suceder. De nuevo, buscando mensajes con el prefijo SimpleItemReader, la lista 15 muestra lo que podría ver:

 Opened Payroll File. Will start reading from record number: 7]] checkpointInfo() called. Returning current recordNumber: 9]] checkpointInfo() called. Returning current recordNumber: 10]] close called.]]

Listando 15

Como puede ver, nuestro método SimpleItemReaderopen() fue llamado con el valor de punto de control anterior (que era el número de registro 7) permitiendo que nuestro SimpleItemReader para omitir los primeros seis registros y reanudar la lectura desde el séptimo registro.

Visualización de trabajos por lotes Con la Consola de administración GlassFish 4.0

Puede ver la lista de todos los trabajos por lotes en JobRepository. Abra una ventana del navegador y vaya a localhost:4848. A continuación, haga clic en servidor (Servidor de administración) en el panel izquierdo, como se muestra en la Figura 9.

Figura 9

Figura 9

Puede hacer clic en la pestaña Lote, que debería mostrar todos los trabajos por lotes enviados a este servidor GlassFish. Tenga en cuenta que JobRepository se implementa utilizando una base de datos y, por lo tanto, los detalles del trabajo sobreviven a los reinicios del servidor GlassFish 4.0. La Figura 10 muestra todos los trabajos por lotes en JobRepository.

Figura 10

Figura 10

También puede hacer clic en uno de los ID que aparecen en ID de ejecución. Por ejemplo, al hacer clic en 293 se muestran detalles sobre la ejecución:

Figura 11

Figura 11

Se pueden obtener más detalles sobre la ejecución haciendo clic en la pestaña Pasos de ejecución en la parte superior.

Figura 12

la Figura 12

Mira las estadísticas proporcionadas por esta página. Muestra cuántas lecturas, escrituras y confirmaciones se realizaron durante esta ejecución.

Visualización de trabajos por lotes Con la interfaz de línea de comandos GlassFish 4.0

También puede ver los detalles de los trabajos que se ejecutan en GlassFish 4.0 servidor mediante la interfaz de línea de comandos (CLI).

Para ver la lista de trabajos por lotes, abra una ventana de comandos y ejecute el siguiente comando:

asadmin list-batch-jobs -l

la salida debe ser similar al de la Figura 13:

Figura 13

la Figura 13

Para ver la lista de lotes JobExecutions, se puede ejecutar este comando:

asadmin list-batch-job-executions -l

la salida debe ser similar al de la Figura 14:

Figura 14

Figura 14

El comando enumera el estado de finalización de cada ejecución y también los parámetros de trabajo pasados a cada ejecución.

Finalmente, para ver detalles sobre cada paso en un JobExecution, puede usar el siguiente comando:

asadmin list-batch-job-steps -l

Debería ver una salida similar a la Figura 15:

Figura 15

Figura 15

Tome nota de la columna de STEPMETRICS. Indica cuántas veces se llamó a ItemReader y ItemWriter y también cuántas confirmaciones y reversiones se hicieron. Estas son métricas extremadamente valiosas.

La salida de la CLI debe coincidir con la vista de la consola de administración porque ambas consultan el mismo JobRepository.

Puede usar asadmin help <command-name> para obtener más detalles sobre los comandos de la CLI.

Conclusión

En este artículo, vimos cómo escribir, empaquetar y ejecutar aplicaciones por lotes simples que usan pasos de estilo de fragmento. También vimos cómo la función checkpoint del tiempo de ejecución por lotes permite reiniciar fácilmente los trabajos por lotes fallidos. Sin embargo, apenas hemos arañado la superficie del JSR 352. Con el conjunto completo de componentes y características de Java EE a su disposición, incluidos servlets, EJB beans, CDI beans, temporizadores automáticos EJB, etc., las aplicaciones por lotes ricas en características se pueden escribir con bastante facilidad.

Este artículo también cubrió (brevemente) la consola de administración GlassFish 4.0 y la compatibilidad con CLI para consultar el lote JobRepository. Tanto la Consola de administración como la CLI proporcionan detalles valiosos sobre los trabajos y pasos que se pueden usar para detectar posibles cuellos de botella.

JSR 352 admite muchas más funciones interesantes, como lotes, divisiones, flujos y puntos de control personalizados, que se tratarán en artículos futuros.

Véase también

JSR 352

Acerca del autor

Mahesh Kannan es ingeniero de software sénior del equipo de Oracle Cloud Application Foundation y miembro del Grupo de Expertos de Java Batch JSR. Debido a su amplia experiencia con servidores de aplicaciones, contenedores y sistemas distribuidos, se ha desempeñado como arquitecto principal y «consultor en general» en muchos proyectos que crean soluciones innovadoras para productos Oracle.

Únete a la conversación

¡Únete a la conversación de la comunidad Java en Facebook, Twitter y el Blog de Oracle Java!

Deja una respuesta

Tu dirección de correo electrónico no será publicada.