Una panoramica dell’elaborazione batch in Java EE 7.0

Esamina la nuova capacità di elaborazione batch fornita da JSR 352 per Java EE 7.

L’elaborazione batch viene utilizzata in molti settori per attività che vanno dall’elaborazione del libro paga, alla generazione di istruzioni, ai lavori di fine giornata come il calcolo degli interessi e l’ETL (extract, load e transform) in un data warehouse e molti altri. In genere, l’elaborazione batch è orientata alla massa, non interattiva e di lunga durata, e potrebbe essere dati o calcolo intensivo. I lavori batch possono essere eseguiti nei tempi previsti o avviati su richiesta. Inoltre, poiché i lavori batch sono in genere lavori di lunga durata, il check-pointing e il riavvio sono caratteristiche comuni nei lavori batch.

JSR 352 (Batch Processing for Java Platform), parte della piattaforma Java EE 7 recentemente introdotta, definisce il modello di programmazione per le applicazioni batch più un runtime per eseguire e gestire i processi batch. Questo articolo illustra alcuni concetti chiave, tra cui i punti salienti delle funzionalità, una panoramica delle API selezionate, la struttura del linguaggio delle specifiche del lavoro e un’applicazione batch di esempio. L’articolo descrive anche come è possibile eseguire applicazioni batch utilizzando GlassFish Server Open Source Edition 4.0.

Architettura di elaborazione batch

Questa sezione e la Figura 1 descrivono i componenti di base dell’architettura di elaborazione batch.

Figura 1

Figura 1

  • Un lavoro incapsula l’intero processo batch. Un lavoro contiene uno o più passaggi. Un lavoro viene creato utilizzando un Job Specification Language (JSL) che specifica la sequenza in cui i passaggi devono essere eseguiti. In JSR 352, JSL viene specificato in un file XML chiamato file job XML. In breve, un lavoro (con JSR 352) è fondamentalmente un contenitore per i passaggi.
  • Un passo è un oggetto di dominio che incapsula una fase sequenziale indipendente del lavoro. Un passaggio contiene tutta la logica e i dati necessari per eseguire l’elaborazione effettiva. La specifica batch lascia deliberatamente la definizione di un passo vaga perché il contenuto di un passo è puramente specifico dell’applicazione e può essere complesso o semplice come lo sviluppatore desidera. Ci sono due tipi di passaggi: chunk e batchlet.
    • Un passaggio in stile chunk contiene esattamente unItemReader, unItemProcessore unItemWriter. In questo modello,ItemReader legge un elemento alla volta,ItemProcessor elabora l’elemento in base alla logica di business (ad esempio “calcola il saldo del conto”) e lo passa al runtime batch per l’aggregazione. Una volta che il numero di elementi” chunk-size”viene letto ed elaborato, viene assegnato a un ItemWriter, che scrive i dati (ad esempio, in una tabella di database o in un file flat). La transazione viene quindi impegnata.
    • JSR 352 definisce anche un tipo di passo roll-your-own chiamato batchlet. Un batchlet è libero di utilizzare qualsiasi cosa per compiere il passo, come l’invio di una e-mail.
  • JobOperator fornisce un’interfaccia per gestire tutti gli aspetti dell’elaborazione dei lavori, inclusi i comandi operativi, come start, restart e stop, nonché i comandi del repository dei lavori, come il recupero delle esecuzioni dei lavori e dei passaggi. Vedere la sezione 10.4 della specifica JSR 352 per maggiori dettagli su JobOperator.
  • JobRepository contiene informazioni sui lavori attualmente in esecuzione e sui lavori eseguiti in passato. JobOperator fornisce API per accedere a questo repository. Un JobRepository potrebbe essere implementato usando, ad esempio, un database o un file system.

Sviluppo di una semplice applicazione di elaborazione dei salari

Questo articolo illustra alcune delle caratteristiche chiave di JSR 352 utilizzando una semplice applicazione di elaborazione dei salari. L’applicazione è stata intenzionalmente mantenuta abbastanza semplice per concentrarsi sui concetti chiave di JSR 352.

Il processo batchSimplePayrollJob comporta la lettura dei dati di input per l’elaborazione del libro paga da un file CSV (comma-Separated Values). Ogni riga del file contiene un ID dipendente e lo stipendio base (al mese) per un dipendente. Il lavoro batch calcola quindi l’imposta da trattenere, il bonus e lo stipendio netto. Il lavoro deve infine scrivere i record del libro paga elaborati in una tabella di database.

Usiamo un file CSV in questo esempio solo per dimostrare che JSR 352 consente alle applicazioni batch di leggere e scrivere da qualsiasi fonte arbitraria.

Job Specification Language for the Payroll Processing Application

Abbiamo discusso che un passo è un oggetto di dominio che incapsula una fase indipendente e sequenziale del lavoro e un lavoro è fondamentalmente un contenitore per uno o più passaggi.

In JSR 352, un JSL specifica fondamentalmente l’ordine in cui i passaggi devono essere eseguiti per eseguire il lavoro. Il JSL è abbastanza potente da consentire l’esecuzione condizionale dei passaggi e consente inoltre a ciascun passaggio di avere le proprie proprietà, listener e così via.

Un’applicazione batch può avere tutti i JSL che desidera, consentendo così di avviare tutti i lavori batch necessari. Ad esempio, un’applicazione può avere due JSL, uno per l’elaborazione del libro paga e un altro per la generazione di report. Ogni JSL deve essere denominato in modo univoco e deve essere inserito nella directoryMETA-INF/batch-jobs. Le sottodirectory sotto META-INF/batch-jobs vengono ignorate.

Il nostro JSL per l’elaborazione dei salari è inserito in un file chiamato SimplePayrollJob.xm l e assomiglia al Listato 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>

Elenco 1

Il nostro SimplePayrollJob il processo batch ha un solo passaggio (chiamato “processo”). È un passo in stile chunk e ha (come richiesto per un passo in stile chunk), un ItemReader, un ItemProcessor e un ItemWriter. Le implementazioni ItemReaderItemProcessor e ItemWriter per questo passaggio vengono specificati utilizzando il ref attributo nel tag <reader><processor> e <writer> elementi.

Quando il lavoro viene inviato (vedremo più avanti come inviare i lavori batch), il runtime batch inizia con il primo passaggio nel JSL e si fa strada fino a quando l’intero lavoro non viene completato o uno dei passaggi non riesce. Il JSL è abbastanza potente da consentire sia passaggi condizionali che l’esecuzione parallela di passaggi, ma non tratteremo questi dettagli in questo articolo.

L’attributo item-count, definito come 2 nel listato 1, definisce la dimensione del blocco del blocco.

Ecco una panoramica di alto livello di come vengono eseguiti i passaggi in stile chunk. Si prega di consultare la sezione 11.6 (“Regular Chunk Processing”) della specifica JSR 352 per maggiori dettagli.

  1. Avvia una transazione.
  2. InvocaItemReader e passa l’elemento letto daItemReader aItemProcessorItemProcessor elabora l’elemento e restituisce l’elemento elaborato al runtime batch.
  3. Il runtime batch ripete il passaggio 2item-count volte e mantiene un elenco di elementi elaborati.
  4. Il runtime batch richiama ilItemWriterche scriveitem-count numero di elementi elaborati.
  5. Se vengono generate eccezioni daItemReaderItemProcessor, oItemWriter, la transazione fallisce e il passaggio viene contrassegnato come “NON RIUSCITO.”Fare riferimento alla Sezione 5.2.1.2.1 (“Saltare le eccezioni”) nella specifica JSR 352.
  6. Se non ci sono eccezioni, il runtime batch ottiene i dati di checkpoint daItemReader eItemWriter (vedere sezione 2.5 nella specifica JSR 352 per maggiori dettagli). Il runtime batch esegue il commit della transazione.
  7. I passaggi da 1 a 6 vengono ripetuti seItemReader ha più dati da leggere.

Ciò significa che nel nostro esempio, il runtime batch leggerà ed elaborerà due record e ItemWriter scriverà due record per transazione.

Scrivere ItemReaderItemProcessor e ItemWriter

Scrivere ItemReader

il Nostro libro paga di elaborazione batch JSL definisce un singolo blocco in stile passo e specifica che il passaggio viene utilizzato un ItemReader nome simpleItemReader. La nostra applicazione contiene un’implementazione di ItemReader per leggere i dati CSV di input. Il listato 2 mostra un frammento del nostro ItemReader:

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

Listato 2

Si noti che la classe è annotata con l’annotazione @Named. Poiché l’annotazione@Named utilizza il valore predefinito, il nome CDI (Contexts and Dependency Injection) per questo bean èsimpleItemReader. Il JSL specifica il nome CDI dell’elementoItemReader nell’elemento<reader>. Ciò consente al runtime batch di istanziare (tramite CDI) il nostro ItemReader quando viene eseguito il passaggio.

Il nostroItemReaderinietta anche unJobContextJobContext consente all’artefatto batch (ItemReader, in questo caso) di leggere i valori passati durante l’invio del lavoro.

Il nostro libro paga SimpleItemReader sostituisce il metodo open() per aprire l’input da cui vengono letti i dati di input del libro paga. Come vedremo più avanti, il parametro prevCheckpointInfo non sarà nullo se il lavoro viene riavviato.

Nel nostro esempio, il metodo open(), che è mostrato nel listato 3, apre il file di input del libro paga (che è stato confezionato insieme all’applicazione).

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

Listato 3

Il metodo readItem() legge fondamentalmente una riga di dati dal file di input e determina se la riga contiene due numeri interi (uno per l’ID dipendente e uno per lo stipendio base). Se ci sono due numeri interi, crea e restituisce una nuova istanza di PayrollInputRecord e ritorna al runtime batch (che viene quindi passato 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;}

Listato 4

Il metodo checkpointInfo() viene chiamato dal runtime batch alla fine di ogni transazione di blocco riuscita. Ciò consente al lettore di controllare l’ultima posizione di lettura riuscita.

Nel nostro esempio, checkpointInfo() restituisce recordNumber indicando il numero di record che sono stati letti con successo, come mostrato nel listato 5.

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

Listato 5

Scrivere ItemProcessor

il SimpleItemProcessor segue un modello simile per il modello SimpleItemReader.

Il metodoprocessItem() riceve (dal runtime batch) ilPayrollInputRecord. Quindi calcola l’imposta e il netto e restituisce unPayrollRecord come output. Si noti nel listato 6 che il tipo di oggetto restituito da un ItemProcessor può essere molto diverso dal tipo di oggetto ricevuto da 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; } }

Listato 6

Scrivere il ItemWriter

A questo punto, SimpleItemWriter deve seguire linee prevedibili per te.

L’unica differenza è che inietta unEntityManagerin modo che possa mantenere le istanzePayrollRecord (che sono entità JPA) in un database, come mostrato nel listato 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); } }}

Listato 7

Il writeItems()metodo persiste tutte le PayrollRecord istanze in una tabella di database utilizzando JPA. Ci saranno al massimoitem-count voci (la dimensione del blocco) nell’elenco.

Ora che abbiamo il nostro JSL,ItemReaderItemProcessor, eItemWriter pronto, vediamo come un lavoro batch può essere inviato.

Avvio di un lavoro batch da un Servlet

Si noti che la semplice presenza di un file XML di lavoro o di altri artefatti batch (comeItemReader) non significa che un lavoro batch venga avviato automaticamente quando l’applicazione viene distribuita. Un processo batch deve essere avviato esplicitamente, ad esempio, da un servlet o da un timer Enterprise JavaBeans (EJB) o da un metodo di business EJB.

Nella nostra applicazione payroll, usiamo un servlet (chiamato PayrollJobSubmitterServlet) per inviare un lavoro batch. Il servlet visualizza una pagina HTML che presenta all’utente un modulo contenente due pulsanti. Quando si fa clic sul primo pulsante, etichettato Calculate Payroll, il servlet richiama il metodo startNewBatchJob, mostrato nel listato 8, che avvia un nuovo lavoro batch.

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

Listato 8

Il primo passo è ottenere un’istanza di JobOperator. Questo può essere fatto chiamando il seguente:

JobOperator jobOperator = BatchRuntime.getJobOperator();

Il servlet crea quindi un oggetto Properties e memorizza il nome del file di input in esso. Infine, un nuovo lavoro batch viene avviato chiamando quanto segue:

jobOperator.start(jobName, properties)

Iljobname non è altro che il nome del file JSL XML del lavoro (meno l’estensione .xml). Il parametroproperties serve a passare tutti i dati di input al lavoro. L’oggettoProperties (contenente il nome del file di input del libro paga) viene reso disponibile ad altri artefatti batch (comeItemReaderItemProcessor e così via) attraverso l’interfacciaJobContext.

Il runtime batch assegna un ID univoco, chiamato ID esecuzione, per identificare ogni esecuzione di un lavoro, sia che si tratti di un lavoro appena inviato o di un lavoro riavviato. Molti dei metodiJobOperator prendono l’ID di esecuzione come parametro. Utilizzando l’ID di esecuzione, un programma può ottenere lo stato di esecuzione corrente (e passato) e altre statistiche sul lavoro. Il metodoJobOperator.start() restituisce l’ID di esecuzione del lavoro avviato.

Recupero dei dettagli sui lavori batch

Quando viene inviato un lavoro batch, il runtime batch crea un’istanza diJobExecution per rintracciarlo. JobExecution ha metodi per ottenere vari dettagli come l’ora di inizio del lavoro, il tempo di completamento del lavoro, lo stato di uscita del lavoro e così via. Per ottenere ilJobExecution per un ID di esecuzione, è possibile utilizzare ilJobOperator.getJobExecution(executionId) metodo. Listato 9 mostra la definizione di 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();}

Listato 9

Confezione l’Applicazione

Ora che abbiamo il nostro JSL ItemReaderItemProcessorItemWriter, e la nostra servlet pronto, è il momento del pacchetto e ottenere pronto per la distribuzione.

È possibile distribuire l’applicazione batch come uno qualsiasi degli archivi Java EE supportati (ad esempio,.war.jaro.ear). È possibile raggruppare le classi artefatto batch insieme ad altre classi Java EE (come bean e servlet EJB).

L’unico requisito speciale è che devi inserire il tuo job JSLs nella directory META-INF/batch-jobsper i file .jar. Per i tipi di archivio.war, posizionare il JSLs del lavoro nella directoryWEB-INF/classes/META-INF/batch-jobs.

Distribuzione ed esecuzione dell’applicazione di esempio Payroll in GlassFish 4.0

Distribuiamo l’applicazione payroll che abbiamo sviluppato in GlassFish 4.0 application server. GlassFish 4.0 è l’implementazione di riferimento (RI) per la specifica Java EE 7.0 e contiene anche il RI per JSR 352. Puoi trovare maggiori informazioni su GlassFish 4.0 a http://glassfish.org e sul Java Batch 1.0 RI a https://javaee.github.io/.

Installazione e avvio di GlassFish 4.0

È possibile scaricare GlassFish 4.0 da https://glassfish.java.net/download.html e quindi installarlo. Avviare GlassFish 4.0 aprendo una finestra di comando ed eseguendo il seguente comando:

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

Poiché l’applicazione payroll di esempio utilizza un database (per scrivere i dati elaborati), abbiamo bisogno di un database in esecuzione prima di poter eseguire la nostra applicazione. È possibile avviare il database Apache Derby eseguendo il seguente comando:

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

Compilare, impacchettare e distribuire l’applicazione Payroll

Innanzitutto, creare una nuova directory denominata hello-batch. Quindi passare alla directoryhello-batch :

cd hello-batch

Per compilare e pacchetto, eseguire il comando riportato di seguito, che crea hello-batch.war sotto la directory di destinazione:

mvn clean package

distribuzione hello-batch.war eseguire il seguente comando:

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

Se si desidera distribuire nuovamente l’applicazione, è possibile eseguire il comando riportato di seguito:

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

Esecuzione dell’applicazione Payroll

Una volta distribuito il filehello-batch.war, è possibile eseguire l’applicazione accedendo a http://localhost:8080/hello-batch/PayrollJobSubmitterServlet da un browser. L’accesso a questo URL dovrebbe presentare la schermata mostrata in Figura 2.

Figura 2

Figura 2

Fare clic sul pulsante Calcola libro paga e si dovrebbe vedere una nuova voce nella tabella, come mostrato in Figura 3.

Figura 3

Figura 3

Fare clic sul pulsante Aggiorna per visualizzare le colonne Stato di uscita e Ora di fine aggiornate per l’ultimo lavoro (vedere Figura 4). La colonna Stato di uscita mostra se il lavoro non è riuscito o è stato completato correttamente. Dal momento che il nostro SimplePayrollJob non ha errori (almeno non ancora!), lo stato di uscita viene visualizzato COMPLETATO.

Figura 4

Figura 4

Fare clic sui pulsanti Calcola paghe e Aggiorna un paio di volte. Si noti che ogni volta che viene avviato un lavoro, viene assegnato un nuovo ID di esecuzione (e ID istanza) al lavoro, come mostrato in Figura 5.

Figura 5

Figura 5

Riavvio dei lavori falliti

Finora, avevamo avviato i lavori batch utilizzando il metodo jobOperator.start(). Diciamo che il nostro file di input del libro paga ha alcuni errori. ItemReader oItemProcessor potrebbero rilevare record non validi e non riuscire il passaggio corrente e il lavoro. L’amministratore o l’utente finale possono correggere l’errore e riavviare il processo batch. Questo approccio di avvio di un nuovo lavoro che inizia dall’inizio dopo il ripristino da errori potrebbe non scalare se la quantità di dati da elaborare è grande. JobOperator fornisce un altro metodo chiamatorestart() per risolvere esattamente questo problema.

Panoramica rapida di JobInstancee JobExecution

Abbiamo visto in precedenza che un lavoro è essenzialmente un contenitore per i passaggi. Quando un lavoro viene avviato, deve essere monitorato, quindi il runtime batch crea un JobInstance. A JobInstance si riferisce al concetto di esecuzione logica. Nel nostro esempio, abbiamo un PayrollJob e se il PayrollJob viene eseguito ogni mese, ci sarà una Gen-2013 JobInstance e ci sarà un’altra Feb-2013 JobInstance, e così via.

Se l’elaborazione del libro paga per Jan-2013 fallisce, deve essere riavviata (dopo aver presumibilmente corretto l’errore), ma è ancora l’esecuzione di Jan-2013 perché sta ancora elaborando i record di Jan-2013.

A JobExecution si riferisce al concetto di un singolo tentativo di eseguire un lavoro. Ogni volta che un lavoro viene avviato o riavviato, viene creato un nuovo JobExecution che appartiene allo stesso JobInstance. Nel nostro esempio, se il Jan-2013 JobInstance viene riavviato, è sempre lo stesso Jan-2013 JobInstance ma viene creato un nuovo JobExecution che appartiene allo stesso JobInstance.

In sintesi, un lavoro può avere una o più istanze diJobInstance e ogniJobInstance può avere una o piùJobExecution istanze. Usare un nuovo JobInstancesignifica”iniziare dall’inizio”e usare unJobInstance significa generalmente ” iniziare da dove si era interrotto.”

Ripristino dei lavori non riusciti

Se si ricorda, viene eseguito un passaggio in stile chunk in una transazione in cui le vociitem-count vengono lette, elaborate e scritte. Dopo che il ItemWriter‘s writeItems() è stato invocato, il runtime batch chiama il checkpointInfo() metodo su entrambi ItemReader e ItemWriter. Ciò consente sia ItemReadercheItemWriter di contrassegnare (salvare) i loro progressi attuali. I dati che sono segnalati per un ItemReader potrebbero essere qualsiasi cosa che lo aiuti a riprendere la lettura. Ad esempio, il nostro SimpleItemReader deve salvare il numero di riga fino al quale ha letto con successo finora.

La sezione 10.8 della specifica JSR 352 descrive in dettaglio l’elaborazione del riavvio.

Prendiamo un momento per esaminare il file di log in cui il nostroSimpleItemReader emette alcuni messaggi utili dai metodiopen() echeckpoint(). Ogni messaggio è preceduto dalla stringa in modo da poter identificare rapidamente i messaggi. Il file di registro si trova in <GlassFish install Dir>/domains/domain1/logs/server.log.

Il listato 10 mostra i messaggi preceduti dalla stringa:

 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.]]

Listato 10

Nota: È possibile utilizzare anche il comando tail -f server.log | grep SimpleItemReader.

Perché il nostro lavoro file XML (SimplePayrollJob.xml consente di specificare un valore di 2 per item-count come la dimensione del blocco, lotto runtime chiama checkpointInfo() nostra ItemReader ogni due record. Il runtime batch memorizza queste informazioni di checkpoint in JobRepository. Quindi, se si verifica un errore durante il bel mezzo della nostra elaborazione del blocco, l’applicazione batch deve essere in grado di riprendere dall’ultimo checkpoint riuscito.

Introduciamo alcuni errori nel nostro file di dati di input e vediamo come possiamo recuperare dagli errori di input.

Se guardi l’output del nostro servlet, che si trova sotto<GlassFish install Dir>/domains/domain1/applications/hello-batch/WEB-INF/classes/payroll-data/payroll-data.csv, vedi che visualizza la posizione del file di input da cui vengono letti i dati CSV per la nostra applicazione payroll. Il listato 11 mostra il contenuto del file:

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

Listato 11

Apri il tuo editor preferito e inserisci un errore. Ad esempio, diciamo che aggiungiamo alcuni caratteri al campo stipendio sull’ottavo record, come mostrato nel listato 12:

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

Listato 12

Salvare il file e chiudere l’editor. Torna al tuo browser e fai clic sul pulsante Calcola paghe seguito dal pulsante Aggiorna. Si potrebbe vedere che il lavoro inviato di recente non è riuscito, come mostrato in Figura 6. (Guarda la colonna Stato di uscita.)

Figura 6

Figura 6

Si noterà anche che un pulsante di riavvio appare accanto all’ID di esecuzione del lavoro che ha appena fallito. Se si fa clic su Aggiorna, il processo avrà esito negativo (perché non è stato ancora risolto il problema). Figura 7 mostra ciò che viene visualizzato dopo pochi clic del pulsante Aggiorna.

Figura 7

Figura 7

Se si guarda nel GlassFish server di log (che si trova in <GlassFish install Dir>/domains/domain1/logs/server.log), potrete vedere un’eccezione, come mostrato nel Listato 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

Si dovrebbe anche notare che quando si fa clic sul pulsante di Riavvio, un nuovo processo di esecuzione è creato, ma il suo lavoro ID istanza rimane lo stesso. Quando si fa clic sul pulsante di Aggiornamento, il nostro PayrollJobSubmitter servlet chiama un metodo denominato restartBatchJob(), che è mostrato nel Listato 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);}

Quotazione 14

La linea della chiave nel Listato 14 è la chiamata alla JobOperator‘s restart() metodo. Questo metodo accetta un oggettoProperties proprio comestart(), ma invece di passare un nome di file XML del lavoro, passa l’ID di esecuzione del lavoro fallito più di recente. Utilizzando l’ID di esecuzione del processo non riuscito più di recente, il runtime batch può recuperare l’ultimo checkpoint riuscito dell’esecuzione precedente. I dati di checkpoint recuperati vengono passati al metodoopen() del nostroSimpleItemReader (eItemWriter) per consentire loro di riprendere la lettura (e la scrittura) dall’ultimo checkpoint riuscito.

Assicurando che il browser mostri la pagina con un pulsante di riavvio, modifica nuovamente il file e rimuovi i caratteri estranei dall’ottavo record. Quindi fare clic sui pulsanti Riavvia e Aggiorna. L’ultima esecuzione dovrebbe visualizzare uno stato COMPLETATO, come mostrato in Figura 8.

Figura 8

Figura 8

È ora di esaminare il file di registro per capire cosa è appena successo. Di nuovo, cercando messaggi con prefisso SimpleItemReader, il listato 15 mostra ciò che potresti vedere:

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

Quotazione 15

Come potete vedere, il nostro SimpleItemReader‘s open() metodo è stato chiamato con il precedente valore di checkpoint (che è stato registrato il numero 7) consentendo al nostro SimpleItemReader per saltare il primo record di sei e riprendere la lettura dal settimo record.

Visualizzazione dei lavori batch Utilizzando la console di amministrazione GlassFish 4.0

È possibile visualizzare l’elenco di tutti i lavori batch nelJobRepository. Accendi una finestra del browser e vai su localhost:4848. Quindi fare clic su server (Admin Server) nel pannello di sinistra, come mostrato in Figura 9.

Figura 9

Figura 9

È possibile fare clic sulla scheda Batch, che dovrebbe elencare tutti i processi batch inviati a questo server GlassFish. Si noti che JobRepository è implementato utilizzando un database e, quindi, i dettagli del lavoro sopravvivono al riavvio del server GlassFish 4.0. Figura 10 mostra tutti i processi batch nel JobRepository.

Figura 10

Figura 10

È anche possibile fare clic su uno degli ID elencati in ID esecuzione. Ad esempio, facendo clic su 293 vengono rivelati dettagli su tale esecuzione:

Figura 11

Figura 11

È possibile ottenere ulteriori dettagli sull’esecuzione facendo clic sulla scheda Passaggi di esecuzione in alto.

Figura 12

Figura 12

Guarda le statistiche fornite da questa pagina. Mostra quante letture, scritture e commit sono stati eseguiti durante questa esecuzione.

Visualizzazione dei lavori batch Utilizzando la CLI GlassFish 4.0

È inoltre possibile visualizzare i dettagli sui lavori in esecuzione in GlassFish 4.0 server utilizzando l’interfaccia della riga di comando (CLI).

Per visualizzare l’elenco dei processi batch di aprire una finestra di comando ed eseguire il seguente comando:

asadmin list-batch-jobs -l

Si dovrebbe vedere un output simile a Figura 13:

Figura 13

Figura 13

Per visualizzare l’elenco dei batch JobExecutions, è possibile eseguire questo comando:

asadmin list-batch-job-executions -l

Si dovrebbe vedere un output simile a quella in Figura 14:

Figura 14

Figura 14

Il comando elenca lo stato di completamento di ogni esecuzione e anche i parametri di lavoro passati ad ogni esecuzione.

Infine, per vedere i dettagli di ogni passo in un JobExecution, è possibile utilizzare il seguente comando:

asadmin list-batch-job-steps -l

Si dovrebbe vedere un output simile a Figura 15:

Figura 15

Figura 15

Prendere nota del STEPMETRICS colonna. Indica quante volte sono stati chiamatiItemReader eItemWriter e anche quanti commit e rollback sono stati eseguiti. Queste sono metriche estremamente preziose.

L’output CLI deve corrispondere alla vista della console di amministrazione perché entrambi interrogano lo stessoJobRepository.

È possibile utilizzare asadmin help <command-name> per ottenere maggiori dettagli sui comandi CLI.

Conclusione

In questo articolo, abbiamo visto come scrivere, confezionare ed eseguire semplici applicazioni batch che utilizzano passaggi in stile chunk. Abbiamo anche visto come la funzione checkpoint del runtime batch consente il facile riavvio dei lavori batch falliti. Eppure, abbiamo appena scalfito la superficie di JSR 352. Con il set completo di componenti e funzionalità Java EE a vostra disposizione, tra cui servlet, bean EJB, bean CDI, timer automatici EJB e così via, le applicazioni batch ricche di funzionalità possono essere scritte abbastanza facilmente.

Questo articolo riguardava anche (brevemente) la console di amministrazione GlassFish 4.0 e il supporto CLI per l’interrogazione del batchJobRepository. Sia la console di amministrazione che la CLI forniscono preziosi dettagli sui lavori e sui passaggi che possono essere utilizzati per rilevare potenziali colli di bottiglia.

JSR 352 supporta molte funzionalità più interessanti come batchlet, split, flussi e checkpoint personalizzati, che saranno trattati in articoli futuri.

Vedi anche

JSR 352

Informazioni sull’autore

Mahesh Kannan è un senior software engineer del team Oracle Cloud Application Foundation ed è il membro del gruppo di esperti per Java Batch JSR. Grazie alla sua vasta esperienza con application server, container e sistemi distribuiti, è stato lead architect e “consultant at large” in molti progetti che costruiscono soluzioni innovative per i prodotti Oracle.

Partecipa alla conversazione

Partecipa alla conversazione della comunità Java su Facebook, Twitter e il blog Oracle Java!

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.