Этот пост Махеш Каннан появляется через Oracle .
Пакетная обработка используется во многих отраслях промышленности для задач, начиная от расчета заработной платы; генерация выписок; задания на конец дня, такие как расчет процентов и ETL (извлечение, загрузка и преобразование) в хранилище данных; и многое другое. Как правило, пакетная обработка является объемно-ориентированной, неинтерактивной и длительной — и может требовать больших объемов данных или вычислений. Пакетные задания могут выполняться по расписанию или инициироваться по требованию. Кроме того, поскольку пакетные задания, как правило, являются длительными, указание и перезапуск являются общими функциями, встречающимися в пакетных заданиях.
JSR 352 (Пакетная обработка для платформы Java), часть недавно представленной платформы Java EE 7, определяет модель программирования для пакетных приложений, а также среду выполнения для запуска и управления пакетными заданиями. В этой статье рассматриваются некоторые ключевые концепции, включая основные функции, обзор выбранных API-интерфейсов, структуру языка планирования заданий и образец пакетного приложения. В статье также описывается, как запускать пакетные приложения с помощью GlassFish Server Open Source Edition 4.0.
Архитектура пакетной обработки
В этом разделе и на рисунке 1 описаны основные компоненты архитектуры пакетной обработки.
фигура 1
- Работа инкапсулирует весь процесс пакетной обработки. Задание содержит один или несколько шагов. Задание составляется с использованием языка спецификации заданий (JSL), который определяет последовательность, в которой должны выполняться шаги. В JSR 352 JSL указывается в XML-файле, который называется XML-файлом задания. Короче говоря, задание (с JSR 352) — это, по сути, контейнер для шагов.
- Шаг является объектом домена , который инкапсулирует независимый, последовательный этап работы. Шаг содержит всю необходимую логику и данные для выполнения фактической обработки. Спецификация пакета намеренно оставляет определение шага расплывчатым, потому что содержание шага является чисто специфическим для приложения и может быть настолько сложным или простым, насколько того пожелает разработчик. Есть два вида шагов: чанк и пакетный.
- Кусок шаг -стиль содержит ровно один
ItemReader
, одинItemProcessor
и одинItemWriter
. В этом шаблонеItemReader
выполняется чтение по одному элементу за раз,ItemProcessor
обработка элемента на основе бизнес-логики (например, «вычисление баланса счета») и передача его во время выполнения пакета для агрегации. После считывания и обработки количества элементов «размера куска» они передаются объектуItemWriter
, который записывает данные (например, в таблицу базы данных или плоский файл). Затем транзакция совершается. - В JSR 352 также определен вид шага, который сам по себе катится, называемый пакетом . Пакет может использовать все, что нужно для выполнения этого шага, например, отправку электронной почты.
- Кусок шаг -стиль содержит ровно один
JobOperator
предоставляет интерфейс для управления всеми аспектами обработки заданий, включая рабочие команды, такие как запуск, перезапуск и остановка, а также команды репозитория заданий, такие как извлечение заданий и выполнение шагов. См. Раздел 10.4 спецификации JSR 352 для более подробной информации оJobOperator
.JobRepository
содержит информацию о работах, которые выполняются в настоящее время, и работах, которые выполнялись в прошломJobOperator
предоставляет API для доступа к этому хранилищу. AJobRepository
может быть реализован с использованием, скажем, базы данных или файловой системы.
Разработка простого приложения для расчета заработной платы
В этой статье демонстрируются некоторые ключевые функции JSR 352 с использованием простого приложения для расчета заработной платы. Приложение намеренно оставлено достаточно простым, чтобы сосредоточиться на ключевых концепциях JSR 352.
SimplePayrollJob
Пакетное задание включает в себя чтение входных данных для обработки заработной платы из файла значения , разделенные запятыми (CSV). Каждая строка в файле содержит идентификатор сотрудника и базовый оклад (в месяц) для одного сотрудника. Затем пакетное задание рассчитывает удерживаемый налог, бонус и чистую зарплату. Наконец, задание должно записать обработанные записи заработной платы в таблицу базы данных.
В этом примере мы используем файл CSV, чтобы продемонстрировать, что JSR 352 позволяет пакетным приложениям читать и писать из любого произвольного источника.
Язык спецификации заданий для приложения по обработке заработной платы
Мы обсуждали, что шаг — это объект домена, который инкапсулирует независимую последовательную фазу работы, а работа — это, по сути, контейнер для одного или нескольких шагов.
В JSR 352 JSL в основном определяет порядок, в котором должны выполняться шаги для выполнения задания. JSL достаточно мощный, чтобы разрешить условное выполнение шагов, и он также позволяет каждому шагу иметь свои собственные свойства, слушателей и так далее.
Пакетное приложение может иметь столько JSL, сколько нужно, что позволяет ему запускать столько пакетных заданий, сколько требуется. Например, приложение может иметь два JSL, один для обработки платежной ведомости, а другой для генерации отчетов. Каждый JSL должен иметь уникальное имя и помещаться в META-INF/batch-jobs
каталог. Подкаталоги под META-INF/batch-jobs
игнорируются.
Наш JSL для обработки заработной платы помещен в файл с именем SimplePayrollJob.xm
l и выглядит как в листинге 1:
<job id="SimplePayrollJob" xmlns=http://xmlns.jcp.org/xml/ns/javaee version="1.0"> <step id="process"> <chunk item-count="2"> <reader ref="simpleItemReader/> <processor ref="simpleItemProcessor/> <writer ref="simpleItemWriter/> </chunk> </step> </job>
Листинг 1
Наша SimplePayrollJob
пакетная работа имеет только один шаг (называется «процесс»). Это шаг в стиле чанка, имеющий (как требуется для шага в стиле чанка), an ItemReader
, an ItemProcessor
и an ItemWriter
. Реализации для ItemReader
, ItemProcessor
и ItemWriter
для этого шага задаются с помощью ref
атрибута в <reader>
, <processor>
и <writer>
элементах.
Когда задание отправлено (мы увидим позже, как отправлять пакетные задания), пакетное выполнение начинается с первого шага в JSL и проходит до тех пор, пока не будет выполнено все задание или пока один из шагов не завершится неудачей. JSL достаточно мощный, чтобы разрешить как условные шаги, так и параллельное выполнение шагов, но мы не будем рассматривать эти детали в этой статье.
item-count
Атрибут, который определяется как 2
в листинге 1, определяет размер порции порции.
Вот общий обзор того, как выполняются шаги в стиле чанков. Пожалуйста, обратитесь к разделу 11.6 («Обычная обработка фрагментов») спецификации JSR 352 для более подробной информации.
- Начать транзакцию.
- Вызовите
ItemReader
и передайте элемент, прочитанный сItemReader
помощьюItemProcessor
.ItemProcessor
обрабатывает элемент и возвращает обработанный элемент во время выполнения пакета. - Пакетная среда выполнения повторяет шаг 2
item-count
раза и ведет список обработанных элементов. - Пакетная среда выполнения вызывает
ItemWriter
записьitem-count
количества обработанных элементов. - Если исключения выброшены из
ItemReader
,ItemProcessor
илиItemWriter
, сделке не удается , и шаг отмечен как «FAILED» . Пожалуйста, обратитесь к Разделу 5.2.1.2.1 («Пропуск исключений») в спецификации JSR 352. - Если исключений нет, пакетная среда выполнения получает данные контрольной точки из
ItemReader
иItemWriter
(см. Раздел 2.5 в спецификации JSR 352 для получения более подробной информации). Пакетная среда выполнения фиксирует транзакцию. - Шаги с 1 по 6 повторяются, если
ItemReader
имеется больше данных для чтения.
Это означает, что в нашем примере, пакетная среда выполнения будет считывать и обрабатывать две записи и ItemWriter
записывать две записи для каждой транзакции.
Записывая ItemReader
, ItemProcessor
и ItemWriter
Написание ItemReader
Наш пакет обработки JSL для расчета заработной платы определяет один стиль шага и указывает, что этот шаг использует ItemReader
именованный simpleItemReader
. Наше приложение содержит реализацию ItemReader
для чтения входных данных CSV. В листинге 2 показан фрагмент нашего ItemReader
:
@Named public class SimpleItemReader extends AbstractItemReader { @Inject private JobContext jobContext; ... }
Перечисление 2
Обратите внимание, что класс помечен @Named
аннотацией. Поскольку в @Named
аннотации используется значение по умолчанию, для этого компонента используется имя «Контексты и внедрение зависимостей» (CDI) simpleItemReader
. JSL определяет имя CDI из ItemReader
в <reader>
элементе. Это позволяет пакетной среде выполнения (через CDI) создавать наш экземпляр ItemReader
при выполнении шага.
Наш ItemReader
также вводит JobContext
. JobContext
позволяет артефакту пакета ( ItemReader
в данном случае) считывать значения, которые были переданы во время передачи задания.
Наша платежная ведомость SimpleItemReader
переопределяет open()
метод, чтобы открыть ввод, из которого считываются входные данные заработной платы. Как мы увидим позже, параметр prevCheckpointInf
o не будет нулевым, если задание перезапускается.
В нашем примере open()
метод, показанный в листинге 3, открывает входной файл заработной платы (который был упакован вместе с приложением).
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("[SimpleItemReader] Opened Payroll file for reading from record number: " + recordNumber); }
Перечисление 3
readItem()
Метод в основном считывает одну строку данных из входного файла и определяет , содержит ли строка два целых числа (один для работника ID и один для базовой заработной платы). Если есть два целых числа, он создает и возвращает новый экземпляр PayrollInputRecord
и возвращается во время выполнения пакета (которое затем передается ItemWriter
).
public Object readItem() throws Exception { Object record = null; if (line != null) { String[] fields = line.split("[, \t\r\n]+"); PayrollInputRecord payrollInputRecord = new PayrollInputRecord(); payrollInputRecord.setId(Integer.parseInt(fields[0])); payrollInputRecord.setBaseSalary(Integer.parseInt(fields[1])); record = payrollInputRecord; //Now that we could successfully read, Increment the record number recordNumber++; } return record; }
Листинг 4
Метод checkpointInfo()
вызывается средой выполнения пакета в конце каждой успешной транзакции фрагмента. Это позволяет Читателю проверить последнюю успешную позицию чтения.
В нашем примере checkpointInfo()
возвращается значение, recordNumber
указывающее количество записей, которые были успешно прочитаны, как показано в листинге 5.
@Override public Serializable checkpointInfo() throws Exception { return recordNumber; }
Листинг 5
Написание ItemProcessor
Наша SimpleItemProcessor
следует шаблону, подобному шаблону для SimpleItemReader
.
processItem()
Метод получает (от периодического выполнения) PayrollInputRecord
. Затем он рассчитывает налог и нетто и возвращает в PayrollRecord
качестве вывода. Обратите внимание, что в листинге 6 тип объекта, возвращаемого объектом, ItemProcessor
может сильно отличаться от типа объекта, от которого он получен ItemReader
.
@Named public 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; } }
Листинг 6
Написание ItemWriter
К настоящему времени SimpleItemWriter
должны следовать предсказуемым линиям для вас.
Единственное отличие состоит в том, что он внедряет a, EntityManager
чтобы он мог сохранять PayrollRecord
экземпляры (которые являются сущностями JPA) в базе данных, как показано в листинге 7.
@Named public 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); } } }
Листинг 7
В writeItems()
метод сохраняется все PayrollRecord
экземпляры в таблице базы данных с помощью JPA. В item-count
списке будет не более записей (размер чанка).
Теперь, когда мы имеем наше JSL, ItemReader
, ItemProcessor
, и ItemWriter
готовы, давайте посмотрим , как пакетное задание может быть представлено.
Запуск пакетной работы с сервлета
Обратите внимание, что простое присутствие XML-файла задания или других артефактов пакета (например, ItemReader
) не означает, что пакетное задание автоматически запускается при развертывании приложения. Пакетное задание должно быть инициировано явно, скажем, из сервлета или из таймера Enterprise JavaBeans (EJB) или бизнес-метода EJB.
В нашем приложении для расчета заработной платы мы используем сервлет (именованный PayrollJobSubmitterServlet
) для отправки пакетного задания. Сервлет отображает HTML-страницу, которая представляет пользователю форму, содержащую две кнопки. Когда нажимается первая кнопка с именем Calculate Payroll , сервлет вызывает startNewBatchJob
метод, показанный в листинге 8, который запускает новое пакетное задание.
private long startNewBatchJob() throws Exception { JobOperator jobOperator = BatchRuntime.getJobOperator(); Properties props = new Properties(); props.setProperty("payrollInputDataFileName", payrollInputDataFileName); return jobOperator.start(JOB_NAME, props); }
Листинг 8
Первым шагом является получение экземпляра JobOperator
. Это можно сделать, вызвав следующее:
JobOperator jobOperator = BatchRuntime.getJobOperator();
Затем сервлет создает Properties
объект и сохраняет в нем имя входного файла. Наконец, новое пакетное задание запускается путем вызова следующего:
jobOperator.start(jobName, properties)
Это jobname
не что иное, как имя файла JSL XML (без .xml
расширения). properties
Параметр служит для передачи каких — либо исходных данных для работы. Properties
Объект (содержащее имя входного файла заработной платы) становятся доступным для других пакетных артефактов (например ItemReader
, ItemProcessor
и так далее) через JobContext
интерфейс.
Пакетная среда выполнения назначает уникальный идентификатор, называемый идентификатором выполнения, чтобы идентифицировать каждое выполнение задания, независимо от того, является ли оно вновь отправленным или перезапущенным заданием. Многие из JobOperator
методов принимают идентификатор выполнения в качестве параметра. Используя идентификатор выполнения, программа может получить текущий (и прошлый) статус выполнения и другую статистику о задании. JobOperator.start()
Метод возвращает идентификатор выполнения задания , которое было начато.
Получение сведений о пакетных заданиях
Когда пакетное задание отправлено, пакетная среда выполнения создает экземпляр JobExecution
для его отслеживания. JobExecution
имеет методы для получения различных деталей, таких как время начала работы, время завершения работы, состояние завершения работы и так далее. Чтобы получить JobExecution
идентификатор выполнения, вы можете использовать JobOperator.getJobExecution(executionId)
метод. В листинге 9 показано определение 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(); }
Листинг 9
Упаковка заявки
Теперь, когда у нас есть JSL, ItemReader
, ItemProcessor
, ItemWriter
, и наш сервлет готов, настало время , чтобы упаковать их и получить готовый к развертыванию.
Вы можете развернуть пакетное приложение , как и любой из поддерживаемых архивов Java EE (например, .war
, .jar
или .ear
). Вы можете связать свои классы артефактов пакета вместе с другими классами Java EE (такими как EJB-компоненты и сервлеты).
Единственное специальное требование — вам нужно поместить JSL вашей работы в META-INF/batch-jobs
каталог для .jar
файлов. Для .war
типов архивов поместите JSL вашей работы в WEB-INF/classes/META-INF/batch-jobs
каталог.
Развертывание и запуск примера приложения для расчета заработной платы в GlassFish 4.0
Давайте развернем разработанное нами приложение для расчета заработной платы на сервере приложений GlassFish 4.0. GlassFish 4.0 является эталонной реализацией (RI) для спецификации Java EE 7.0 и также содержит RI для JSR 352. Вы можете найти больше информации о GlassFish 4.0 на http://glassfish.org и о Java Batch 1.0 RI на https://java.net/projects/jbatch/ .
Установка и запуск GlassFish 4.0
Вы можете скачать GlassFish 4.0 с https://glassfish.java.net/public/downloadsindex.html#top и затем установить его. Запустите GlassFish 4.0, открыв окно командной строки и выполнив следующую команду:
<GlassFish Install Dir>/bin/asadmin start-domain
Поскольку пример приложения для расчета заработной платы использует базу данных (для записи обработанных данных), нам нужно запустить базу данных, прежде чем мы сможем запустить наше приложение. Вы можете запустить базу данных Apache Derby, выполнив следующую команду:
<GlassFish Install Dir>/bin/asadmin start-database
Компиляция, упаковка и развертывание приложения Payroll
Сначала создайте новый каталог с именем hello-batch
. Затем перейдите в hello-batch
каталог:
cd hello-batch
Чтобы скомпилировать и упаковать, запустите следующую команду, которая создается hello-batch.war
в целевом каталоге:
mvn clean package
Для развертывания hello-batch.war
выполните следующую команду:
<GlassFish Install Dir>/bin/asadmin deploy target/hello-batch.war
Если вы хотите повторно развернуть приложение, вы можете выполнить следующую команду:
<GlassFish Install Dir>/bin/asadmin deploy -force target/hello-batch.war
Запуск приложения для расчета заработной платы
Развернув hello-batch.war
файл, вы можете запустить приложение, открыв его http://localhost:8080/hello-batch/PayrollJobSubmitterServlet
из браузера. Доступ к этому URL должен отображаться на экране, показанном на рисунке 2.
фигура 2
Нажмите кнопку Calculate Payroll , и вы увидите новую запись в таблице, как показано на рисунке 3.
Рисунок 3
Нажмите кнопку Refresh , и вы должны увидеть столбцы Exit Status и End Time, обновленные для последней работы (см. Рисунок 4). В столбце «Состояние выхода» показано, было ли задание выполнено неудачно или успешно выполнено. Так как у нас SimplePayrollJob
нет ошибок (по крайней мере, пока!), Статус выхода отображает «ЗАВЕРШЕНО».
Рисунок 4
Нажмите кнопки « Рассчитать зарплату» и « Обновить» еще несколько раз. Обратите внимание, что при каждом запуске задания новый идентификатор выполнения (и идентификатор экземпляра) присваивается заданию, как показано на рисунке 5.
Рисунок 5
Перезапуск неудачных заданий
До сих пор мы запускали пакетные задания с использованием этого jobOperator.start()
метода. Допустим, наш входной файл заработной платы имеет некоторые ошибки. Либо тот, ItemReader
либо ItemProcessor
может обнаружить недействительные записи и не выполнить текущий шаг и задание. Администратор или конечный пользователь может исправить ошибку и может перезапустить пакетное задание. Этот подход запуска нового задания, которое начинается с начала после восстановления после ошибок, может не масштабироваться, если объем обрабатываемых данных велик. JobOperator
предоставляет другой метод, призванный restart()
решить именно эту проблему.
Краткий обзор JobInstance
и JobExecution
Ранее мы видели, что работа по сути является контейнером для шагов. Когда задание запускается, оно должно отслеживаться, поэтому пакетная среда выполнения создает JobInstance
. А JobInstance
относится к понятию логического прогона. В нашем примере у нас есть PayrollJob
и, если PayrollJob
запуск проводится каждый месяц, будет январь-2013 JobInstance
и еще один февраль-2013 JobInstance
, и так далее.
Если обработка расчета заработной платы за январь-2013 не удалась, ее необходимо перезапустить (после предположительного исправления ошибки), но она все еще выполняется в январе-2013, поскольку она все еще обрабатывает записи за январь-2013.
А JobExecution
относится к концепции одной попытки запустить работу. Каждый раз, когда задание запускается или перезапускается, создается новое, JobExecution
которое принадлежит тому же самому JobInstance
. В нашем примере, если январь-2013 JobInstance
перезапущен, это все тот же январь-2013, JobInstance
но создается новый JobExecution
, принадлежащий тому же JobInstance
.
Таким образом, задание может иметь один или несколько экземпляров, JobInstance
и каждый JobInstance
может иметь один или несколько JobExecution
экземпляров. Использование нового JobInstance
означает «начать с начала», а использование существующего JobInstance
обычно означает «начать с того места, где вы остановились».
Возобновление невыполненных работ
Если вы помните, шаг в стиле чанка выполняется в транзакции, в которой item-count
записи читаются, обрабатываются и записываются. После того, как ItemWriter
‘ы’ writeItems()
вызваны, пакетная среда выполнения вызывает checkpointInfo()
метод для обоих ItemReader
и ItemWriter
. Это позволяет как ItemReader
и ItemWriter
в закладки (сохранить) их текущий прогресс. Данные, отмеченные закладкой, ItemReader
могут быть чем угодно, что поможет возобновить чтение. Например, нам SimpleItemReader
нужно сохранить номер строки, до которой он до сих пор успешно прочитал.
Раздел 10.8 спецификации JSR 352 подробно описывает обработку перезапуска.
Давайте на минутку заглянем в файл журнала, где мы SimpleItemReader
выводим некоторые полезные сообщения от методов open()
и checkpoint()
Каждое сообщение имеет префикс строки, [SimpleItemReader]
чтобы вы могли быстро идентифицировать сообщения. Файл журнала находится по адресу <GlassFish install Dir>/domains/domain1/logs/server.log
.
В листинге 10 показаны сообщения с префиксом строки [SimpleItemReader]
:
[SimpleItemReader] Opened Payroll File. Will start reading from record number: 0]] [SimpleItemReader] checkpointInfo() called. Returning current recordNumber: 2]] [SimpleItemReader] checkpointInfo() called. Returning current recordNumber: 4]] [SimpleItemReader] checkpointInfo() called. Returning current recordNumber: 6]] [SimpleItemReader] checkpointInfo() called. Returning current recordNumber: 8]] [SimpleItemReader] checkpointInfo() called. Returning current recordNumber: 9]] [SimpleItemReader] close called.]]
Листинг 10
Примечание : вы также можете использовать команду tail -f server.log | grep SimpleItemReader
.
Поскольку в нашем XML-файле задания ( SimplePayrollJob.xml
) в качестве размера фрагмента указано значение 2
for item-count
, пакетная среда выполнения вызывает checkpointInfo()
наши ItemReader
каждые две записи. Пакетная среда выполнения сохраняет эту информацию контрольной точки в JobRepository
. Таким образом, если во время обработки чанка происходит ошибка, пакетное приложение должно быть в состоянии возобновить работу с последней успешной контрольной точки.
Давайте введем некоторые ошибки в наш файл входных данных и посмотрим, как мы можем исправить ошибки ввода.
If you look at our servlet’s output, which is located under <GlassFish install Dir>/domains/domain1/applications/hello-batch/WEB-INF/classes/payroll-data/payroll-data.csv
, you see that it displays the location of the input file from where CSV data is read for our payroll application. Listing 11 shows the content of the file:
1, 8100 2, 8200 3, 8300 4, 8400 5, 8500 6, 8600 7, 8700 8, 8800 9, 8900
Listing 11
Open your favorite editor and introduce an error. For example, let’s say we add a few characters to the salary field on the eighth record, as shown in Listing 12:
1, 8100 2, 8200 3, 8300 4, 8400 5, 8500 6, 8600 7, 8700 8, abc8800 9, 8900
Listing 12
Save the file and quit the editor. Go back to your browser and click the Calculate Payroll button followed by the Refresh button. You would see that the recently submitted job failed, as shown in Figure 6. (Look at the Exit Status column.)
Figure 6
You will also notice that a Restart button appears next to the execution ID of the job that just failed. If you click Refresh, the job will fail (because we haven’t fixed the issue yet). Figure 7 shows what is displayed after a few clicks of the Refresh button.
Figure 7
If you look into the GlassFish server log (located under <GlassFish install Dir>/domains/domain1/logs/server.log
), you will see an exception, as shown in Listing 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
You should also notice that when you click the Restart button, a new job execution is created but its job instance ID remains the same. When you click the Refresh button, our PayrollJobSubmitter
servlet calls a method named restartBatchJob()
, which is shown in Listing 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
The key line in Listing 14 is the call to JobOperator
‘s restart()
method. This method takes a Properties
object just like start()
, but instead of passing a job XML file name, it passes the execution ID of the most recently failed job. Using the most recently failed job’s execution ID, the batch runtime can retrieve the previous execution’s last successful checkpoint. The retrieved checkpoint data is passed to the open()
method of our SimpleItemReader
(and ItemWriter
) to enable them to resume reading (and writing) from the last successful checkpoint.
While ensuring that your browser shows the page with a Restart button, edit the file again and remove the extraneous characters from the eighth record. Then click the Restart and Refresh buttons. The latest execution should display a COMPLETED status, as shown in Figure 8.
Figure 8
It is time to look into the log file to understand what just happened. Again, looking for messages prefixed with SimpleItemReader
, Listing 15 shows what you might see:
[SimpleItemReader] Opened Payroll File. Will start reading from record number: 7]] [SimpleItemReader] checkpointInfo() called. Returning current recordNumber: 9]] [SimpleItemReader] checkpointInfo() called. Returning current recordNumber: 10]] [SimpleItemReader] close called.]]
Listing 15
As you can see, our SimpleItemReader
‘s open()
method was called with the previous checkpoint value (which was record number 7) allowing our SimpleItemReader
to skip the first six records and resume reading from the seventh record.
Viewing Batch Jobs Using the GlassFish 4.0 Admin Console
You can view the list of all batch jobs in the JobRepository
. Fire up a browser window and go to localhost:4848
. Then click server (Admin Server) in the left panel, as shown in Figure 9.
Figure 9
You can click the Batch tab, which should list all the batch jobs submitted to this GlassFish server. Note that the JobRepository
is implemented using a database and, hence, the job details survive GlassFish 4.0 server restarts. Figure 10 shows all the batch jobs in theJobRepository
.
Figure 10
You can also click one of the IDs listed under Execution IDs. For example, clicking 293 reveals details about just that execution:
Figure 11
More details about the execution can be obtained by clicking the Execution Steps tab on the top.
Figure 12
Look at the statistics provided by this page. It shows how many reads, writes, and commits were performed during this execution.
Viewing Batch Jobs Using the GlassFish 4.0 CLI
You can also view the details about jobs running in the GlassFish 4.0 server by using the command-line interface (CLI).
To view the list of batch jobs, open a command window and run the following command:
asadmin list-batch-jobs -l
You should see output similar to Figure 13:
Figure 13
To view the list of batch JobExecution
s, you can run this command:
asadmin list-batch-job-executions -l
You should see output similar to Figure 14:
Figure 14
The command lists the completion status of each execution and also the job parameters passed to each execution.
Finally, in order to see details about each step in a JobExecution
, you could use the following command:
asadmin list-batch-job-steps -l
You should see output similar to Figure 15:
Figure 15
Take note of the STEPMETRICS column. It tells how many times ItemReader
and ItemWriter
were called and also how many commits and rollbacks were done. These are extremely valuable metrics.
The CLI output must match the Admin Console view because they both query the same JobRepository
.
You can use asadmin help <command-name>
to get more details about the CLI commands.
Conclusion
In this article, we saw how to write, package, and run simple batch applications that use chunk-style steps. We also saw how the checkpoint feature of the batch runtime allows for the easy restart of failed batch jobs. Yet, we have barely scratched the surface of JSR 352. With the full set of Java EE components and features at your disposal, including servlets, EJB beans, CDI beans, EJB automatic timers, and so on, feature-rich batch applications can be written fairly easily.
This article also covered (briefly) the GlassFish 4.0 Admin Console and CLI support for querying the batch JobRepository
. Both the Admin Console and the CLI provide valuable details about jobs and steps that can be used to detect potential bottlenecks.
JSR 352 supports many more exciting features such as batchlets, splits, flows, and custom checkpoints, which will be covered in future articles.
See Also
About the Author
Mahesh Kannan is a senior software engineer with Oracle’s Cloud Application Foundation team, and he is the Expert Group Member for the Java Batch JSR. Due to his extensive experience with application servers, containers, and distributed systems, he has served as lead architect and «consultant at large» on many projects that build innovative solutions for Oracle products.