25.ef18cc01.js 39.9 KB
Newer Older
茶陵後's avatar
茶陵後 已提交
1
(window.webpackJsonp=window.webpackJsonp||[]).push([[25],{452:function(e,t,a){"use strict";a.r(t);var o=a(56),n=Object(o.a)({},(function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[a("h1",{attrs:{id:"the-domain-language-of-batch"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#the-domain-language-of-batch"}},[e._v("#")]),e._v(" The Domain Language of Batch")]),e._v(" "),a("h2",{attrs:{id:"the-domain-language-of-batch-2"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#the-domain-language-of-batch-2"}},[e._v("#")]),e._v(" The Domain Language of Batch")]),e._v(" "),a("p",[e._v("XMLJavaBoth")]),e._v(" "),a("p",[e._v('To any experienced batch architect, the overall concepts of batch processing used in\nSpring Batch should be familiar and comfortable. There are "Jobs" and "Steps" and\ndeveloper-supplied processing units called '),a("code",[e._v("ItemReader")]),e._v(" and "),a("code",[e._v("ItemWriter")]),e._v(". However,\nbecause of the Spring patterns, operations, templates, callbacks, and idioms, there are\nopportunities for the following:")]),e._v(" "),a("ul",[a("li",[a("p",[e._v("Significant improvement in adherence to a clear separation of concerns.")])]),e._v(" "),a("li",[a("p",[e._v("Clearly delineated architectural layers and services provided as interfaces.")])]),e._v(" "),a("li",[a("p",[e._v("Simple and default implementations that allow for quick adoption and ease of use\nout-of-the-box.")])]),e._v(" "),a("li",[a("p",[e._v("Significantly enhanced extensibility.")])])]),e._v(" "),a("p",[e._v("The following diagram is a simplified version of the batch reference architecture that\nhas been used for decades. It provides an overview of the components that make up the\ndomain language of batch processing. This architecture framework is a blueprint that has\nbeen proven through decades of implementations on the last several generations of\nplatforms (COBOL/Mainframe, C/Unix, and now Java/anywhere). JCL and COBOL developers\nare likely to be as comfortable with the concepts as C, C#, and Java developers. Spring\nBatch provides a physical implementation of the layers, components, and technical\nservices commonly found in the robust, maintainable systems that are used to address the\ncreation of simple to complex batch applications, with the infrastructure and extensions\nto address very complex processing needs.")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-batch/docs/current/reference/html/images/spring-batch-reference-model.png",alt:"Figure 2.1: Batch Stereotypes"}})]),e._v(" "),a("p",[e._v("Figure 1. Batch Stereotypes")]),e._v(" "),a("p",[e._v("The preceding diagram highlights the key concepts that make up the domain language of\nSpring Batch. A Job has one to many steps, each of which has exactly one "),a("code",[e._v("ItemReader")]),e._v(",\none "),a("code",[e._v("ItemProcessor")]),e._v(", and one "),a("code",[e._v("ItemWriter")]),e._v(". A job needs to be launched (with"),a("code",[e._v("JobLauncher")]),e._v("), and metadata about the currently running process needs to be stored (in"),a("code",[e._v("JobRepository")]),e._v(").")]),e._v(" "),a("h3",{attrs:{id:"job"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#job"}},[e._v("#")]),e._v(" Job")]),e._v(" "),a("p",[e._v("This section describes stereotypes relating to the concept of a batch job. A "),a("code",[e._v("Job")]),e._v(" is an\nentity that encapsulates an entire batch process. As is common with other Spring\nprojects, a "),a("code",[e._v("Job")]),e._v(' is wired together with either an XML configuration file or Java-based\nconfiguration. This configuration may be referred to as the "job configuration". However,'),a("code",[e._v("Job")]),e._v(" is just the top of an overall hierarchy, as shown in the following diagram:")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-batch/docs/current/reference/html/images/job-heirarchy.png",alt:"Job Hierarchy"}})]),e._v(" "),a("p",[e._v("Figure 2. Job Hierarchy")]),e._v(" "),a("p",[e._v("In Spring Batch, a "),a("code",[e._v("Job")]),e._v(" is simply a container for "),a("code",[e._v("Step")]),e._v(" instances. It combines multiple\nsteps that belong logically together in a flow and allows for configuration of properties\nglobal to all steps, such as restartability. The job configuration contains:")]),e._v(" "),a("ul",[a("li",[a("p",[e._v("The simple name of the job.")])]),e._v(" "),a("li",[a("p",[e._v("Definition and ordering of "),a("code",[e._v("Step")]),e._v(" instances.")])]),e._v(" "),a("li",[a("p",[e._v("Whether or not the job is restartable.")])])]),e._v(" "),a("p",[e._v("For those who use Java configuration, Spring Batch provides a default implementation of\nthe Job interface in the form of the "),a("code",[e._v("SimpleJob")]),e._v(" class, which creates some standard\nfunctionality on top of "),a("code",[e._v("Job")]),e._v(". When using java based configuration, a collection of\nbuilders is made available for the instantiation of a "),a("code",[e._v("Job")]),e._v(", as shown in the following\nexample:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('@Bean\npublic Job footballJob() {\n    return this.jobBuilderFactory.get("footballJob")\n                     .start(playerLoad())\n                     .next(gameLoad())\n                     .next(playerSummarization())\n                     .build();\n}\n')])])]),a("p",[e._v("For those who use XML configuration, Spring Batch provides a default implementation of the"),a("code",[e._v("Job")]),e._v(" interface in the form of the "),a("code",[e._v("SimpleJob")]),e._v(" class, which creates some standard\nfunctionality on top of "),a("code",[e._v("Job")]),e._v(". However, the batch namespace abstracts away the need to\ninstantiate it directly. Instead, the "),a("code",[e._v("<job>")]),e._v(" element can be used, as shown in the\nfollowing example:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('<job id="footballJob">\n    <step id="playerload" next="gameLoad"/>\n    <step id="gameLoad" next="playerSummarization"/>\n    <step id="playerSummarization"/>\n</job>\n')])])]),a("h4",{attrs:{id:"jobinstance"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#jobinstance"}},[e._v("#")]),e._v(" JobInstance")]),e._v(" "),a("p",[e._v("A "),a("code",[e._v("JobInstance")]),e._v(" refers to the concept of a logical job run. Consider a batch job that\nshould be run once at the end of the day, such as the 'EndOfDay' "),a("code",[e._v("Job")]),e._v(" from the preceding\ndiagram. There is one 'EndOfDay' job, but each individual run of the "),a("code",[e._v("Job")]),e._v(" must be\ntracked separately. In the case of this job, there is one logical "),a("code",[e._v("JobInstance")]),e._v(" per day.\nFor example, there is a January 1st run, a January 2nd run, and so on. If the January 1st\nrun fails the first time and is run again the next day, it is still the January 1st run.\n(Usually, this corresponds with the data it is processing as well, meaning the January\n1st run processes data for January 1st). Therefore, each "),a("code",[e._v("JobInstance")]),e._v(" can have multiple\nexecutions ("),a("code",[e._v("JobExecution")]),e._v(" is discussed in more detail later in this chapter), and only\none "),a("code",[e._v("JobInstance")]),e._v(" corresponding to a particular "),a("code",[e._v("Job")]),e._v(" and identifying "),a("code",[e._v("JobParameters")]),e._v(" can\nrun at a given time.")]),e._v(" "),a("p",[e._v("The definition of a "),a("code",[e._v("JobInstance")]),e._v(" has absolutely no bearing on the data to be loaded.\nIt is entirely up to the "),a("code",[e._v("ItemReader")]),e._v(" implementation to determine how data is loaded. For\nexample, in the EndOfDay scenario, there may be a column on the data that indicates the\n'effective date' or 'schedule date' to which the data belongs. So, the January 1st run\nwould load only data from the 1st, and the January 2nd run would use only data from the\n2nd. Because this determination is likely to be a business decision, it is left up to the"),a("code",[e._v("ItemReader")]),e._v(" to decide. However, using the same "),a("code",[e._v("JobInstance")]),e._v(" determines whether or not\nthe 'state' (that is, the "),a("code",[e._v("ExecutionContext")]),e._v(", which is discussed later in this chapter)\nfrom previous executions is used. Using a new "),a("code",[e._v("JobInstance")]),e._v(" means 'start from the\nbeginning', and using an existing instance generally means 'start from where you left\noff'.")]),e._v(" "),a("h4",{attrs:{id:"jobparameters"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#jobparameters"}},[e._v("#")]),e._v(" JobParameters")]),e._v(" "),a("p",[e._v("Having discussed "),a("code",[e._v("JobInstance")]),e._v(' and how it differs from Job, the natural question to ask\nis: "How is one '),a("code",[e._v("JobInstance")]),e._v(' distinguished from another?" The answer is:'),a("code",[e._v("JobParameters")]),e._v(". A "),a("code",[e._v("JobParameters")]),e._v(" object holds a set of parameters used to start a batch\njob. They can be used for identification or even as reference data during the run, as\nshown in the following image:")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-batch/docs/current/reference/html/images/job-stereotypes-parameters.png",alt:"Job Parameters"}})]),e._v(" "),a("p",[e._v("Figure 3. Job Parameters")]),e._v(" "),a("p",[e._v("In the preceding example, where there are two instances, one for January 1st, and another\nfor January 2nd, there is really only one "),a("code",[e._v("Job")]),e._v(", but it has two "),a("code",[e._v("JobParameter")]),e._v(" objects:\none that was started with a job parameter of 01-01-2017 and another that was started with\na parameter of 01-02-2017. Thus, the contract can be defined as: "),a("code",[e._v("JobInstance")]),e._v(" = "),a("code",[e._v("Job")]),e._v("+ identifying "),a("code",[e._v("JobParameters")]),e._v(". This allows a developer to effectively control how a"),a("code",[e._v("JobInstance")]),e._v(" is defined, since they control what parameters are passed in.")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("Not all job parameters are required to contribute to the identification of a"),a("code",[e._v("JobInstance")]),e._v(". By default, they do so. However, the framework also allows the submission"),a("br"),e._v("of a "),a("code",[e._v("Job")]),e._v(" with parameters that do not contribute to the identity of a "),a("code",[e._v("JobInstance")]),e._v(".")])])]),e._v(" "),a("tbody")]),e._v(" "),a("h4",{attrs:{id:"jobexecution"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#jobexecution"}},[e._v("#")]),e._v(" JobExecution")]),e._v(" "),a("p",[e._v("A "),a("code",[e._v("JobExecution")]),e._v(" refers to the technical concept of a single attempt to run a Job. An\nexecution may end in failure or success, but the "),a("code",[e._v("JobInstance")]),e._v(" corresponding to a given\nexecution is not considered to be complete unless the execution completes successfully.\nUsing the EndOfDay "),a("code",[e._v("Job")]),e._v(" described previously as an example, consider a "),a("code",[e._v("JobInstance")]),e._v(" for\n01-01-2017 that failed the first time it was run. If it is run again with the same\nidentifying job parameters as the first run (01-01-2017), a new "),a("code",[e._v("JobExecution")]),e._v(" is\ncreated. However, there is still only one "),a("code",[e._v("JobInstance")]),e._v(".")]),e._v(" "),a("p",[e._v("A "),a("code",[e._v("Job")]),e._v(" defines what a job is and how it is to be executed, and a "),a("code",[e._v("JobInstance")]),e._v(" is a\npurely organizational object to group executions together, primarily to enable correct\nrestart semantics. A "),a("code",[e._v("JobExecution")]),e._v(", however, is the primary storage mechanism for what\nactually happened during a run and contains many more properties that must be controlled\nand persisted, as shown in the following table:")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th",[e._v("Property")]),e._v(" "),a("th",[e._v("Definition")])])]),e._v(" "),a("tbody",[a("tr",[a("td",[e._v("Status")]),e._v(" "),a("td",[e._v("A "),a("code",[e._v("BatchStatus")]),e._v(" object that indicates the status of the execution. While running, it is"),a("code",[e._v("BatchStatus#STARTED")]),e._v(". If it fails, it is "),a("code",[e._v("BatchStatus#FAILED")]),e._v(". If it finishes"),a("br"),e._v("successfully, it is "),a("code",[e._v("BatchStatus#COMPLETED")])])]),e._v(" "),a("tr",[a("td",[e._v("startTime")]),e._v(" "),a("td",[e._v("A "),a("code",[e._v("java.util.Date")]),e._v(" representing the current system time when the execution was started."),a("br"),e._v("This field is empty if the job has yet to start.")])]),e._v(" "),a("tr",[a("td",[e._v("endTime")]),e._v(" "),a("td",[e._v("A "),a("code",[e._v("java.util.Date")]),e._v(" representing the current system time when the execution finished,"),a("br"),e._v("regardless of whether or not it was successful. The field is empty if the job has yet to"),a("br"),e._v("finish.")])]),e._v(" "),a("tr",[a("td",[e._v("exitStatus")]),e._v(" "),a("td",[e._v("The "),a("code",[e._v("ExitStatus")]),e._v(", indicating the result of the run. It is most important, because it"),a("br"),e._v("contains an exit code that is returned to the caller. See chapter 5 for more details. The"),a("br"),e._v("field is empty if the job has yet to finish.")])]),e._v(" "),a("tr",[a("td",[e._v("createTime")]),e._v(" "),a("td",[e._v("A "),a("code",[e._v("java.util.Date")]),e._v(" representing the current system time when the "),a("code",[e._v("JobExecution")]),e._v(" was"),a("br"),e._v("first persisted. The job may not have been started yet (and thus has no start time), but"),a("br"),e._v("it always has a createTime, which is required by the framework for managing job level"),a("code",[e._v("ExecutionContexts")]),e._v(".")])]),e._v(" "),a("tr",[a("td",[e._v("lastUpdated")]),e._v(" "),a("td",[e._v("A "),a("code",[e._v("java.util.Date")]),e._v(" representing the last time a "),a("code",[e._v("JobExecution")]),e._v(" was persisted. This field"),a("br"),e._v("is empty if the job has yet to start.")])]),e._v(" "),a("tr",[a("td",[e._v("executionContext")]),e._v(" "),a("td",[e._v('The "property bag" containing any user data that needs to be persisted between'),a("br"),e._v("executions.")])]),e._v(" "),a("tr",[a("td",[e._v("failureExceptions")]),e._v(" "),a("td",[e._v("The list of exceptions encountered during the execution of a "),a("code",[e._v("Job")]),e._v(". These can be useful"),a("br"),e._v("if more than one exception is encountered during the failure of a "),a("code",[e._v("Job")]),e._v(".")])])])]),e._v(" "),a("p",[e._v("These properties are important because they are persisted and can be used to completely\ndetermine the status of an execution. For example, if the EndOfDay job for 01-01 is\nexecuted at 9:00 PM and fails at 9:30, the following entries are made in the batch\nmetadata tables:")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th",[e._v("JOB_INST_ID")]),e._v(" "),a("th",[e._v("JOB_NAME")])])]),e._v(" "),a("tbody",[a("tr",[a("td",[e._v("1")]),e._v(" "),a("td",[e._v("EndOfDayJob")])])])]),e._v(" "),a("table",[a("thead",[a("tr",[a("th",[e._v("JOB_EXECUTION_ID")]),e._v(" "),a("th",[e._v("TYPE_CD")]),e._v(" "),a("th",[e._v("KEY_NAME")]),e._v(" "),a("th",[e._v("DATE_VAL")]),e._v(" "),a("th",[e._v("IDENTIFYING")])])]),e._v(" "),a("tbody",[a("tr",[a("td",[e._v("1")]),e._v(" "),a("td",[e._v("DATE")]),e._v(" "),a("td",[e._v("schedule.Date")]),e._v(" "),a("td",[e._v("2017-01-01")]),e._v(" "),a("td",[e._v("TRUE")])])])]),e._v(" "),a("table",[a("thead",[a("tr",[a("th",[e._v("JOB_EXEC_ID")]),e._v(" "),a("th",[e._v("JOB_INST_ID")]),e._v(" "),a("th",[e._v("START_TIME")]),e._v(" "),a("th",[e._v("END_TIME")]),e._v(" "),a("th",[e._v("STATUS")])])]),e._v(" "),a("tbody",[a("tr",[a("td",[e._v("1")]),e._v(" "),a("td",[e._v("1")]),e._v(" "),a("td",[e._v("2017-01-01 21:00")]),e._v(" "),a("td",[e._v("2017-01-01 21:30")]),e._v(" "),a("td",[e._v("FAILED")])])])]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("Column names may have been abbreviated or removed for the sake of clarity and"),a("br"),e._v("formatting.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("p",[e._v("Now that the job has failed, assume that it took the entire night for the problem to be\ndetermined, so that the 'batch window' is now closed. Further assuming that the window\nstarts at 9:00 PM, the job is kicked off again for 01-01, starting where it left off and\ncompleting successfully at 9:30. Because it is now the next day, the 01-02 job must be\nrun as well, and it is kicked off just afterwards at 9:31 and completes in its normal one\nhour time at 10:30. There is no requirement that one "),a("code",[e._v("JobInstance")]),e._v(" be kicked off after\nanother, unless there is potential for the two jobs to attempt to access the same data,\ncausing issues with locking at the database level. It is entirely up to the scheduler to\ndetermine when a "),a("code",[e._v("Job")]),e._v(" should be run. Since they are separate "),a("code",[e._v("JobInstances")]),e._v(", Spring\nBatch makes no attempt to stop them from being run concurrently. (Attempting to run the\nsame "),a("code",[e._v("JobInstance")]),e._v(" while another is already running results in a"),a("code",[e._v("JobExecutionAlreadyRunningException")]),e._v(" being thrown). There should now be an extra entry\nin both the "),a("code",[e._v("JobInstance")]),e._v(" and "),a("code",[e._v("JobParameters")]),e._v(" tables and two extra entries in the"),a("code",[e._v("JobExecution")]),e._v(" table, as shown in the following tables:")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th",[e._v("JOB_INST_ID")]),e._v(" "),a("th",[e._v("JOB_NAME")])])]),e._v(" "),a("tbody",[a("tr",[a("td",[e._v("1")]),e._v(" "),a("td",[e._v("EndOfDayJob")])]),e._v(" "),a("tr",[a("td",[e._v("2")]),e._v(" "),a("td",[e._v("EndOfDayJob")])])])]),e._v(" "),a("table",[a("thead",[a("tr",[a("th",[e._v("JOB_EXECUTION_ID")]),e._v(" "),a("th",[e._v("TYPE_CD")]),e._v(" "),a("th",[e._v("KEY_NAME")]),e._v(" "),a("th",[e._v("DATE_VAL")]),e._v(" "),a("th",[e._v("IDENTIFYING")])])]),e._v(" "),a("tbody",[a("tr",[a("td",[e._v("1")]),e._v(" "),a("td",[e._v("DATE")]),e._v(" "),a("td",[e._v("schedule.Date")]),e._v(" "),a("td",[e._v("2017-01-01 00:00:00")]),e._v(" "),a("td",[e._v("TRUE")])]),e._v(" "),a("tr",[a("td",[e._v("2")]),e._v(" "),a("td",[e._v("DATE")]),e._v(" "),a("td",[e._v("schedule.Date")]),e._v(" "),a("td",[e._v("2017-01-01 00:00:00")]),e._v(" "),a("td",[e._v("TRUE")])]),e._v(" "),a("tr",[a("td",[e._v("3")]),e._v(" "),a("td",[e._v("DATE")]),e._v(" "),a("td",[e._v("schedule.Date")]),e._v(" "),a("td",[e._v("2017-01-02 00:00:00")]),e._v(" "),a("td",[e._v("TRUE")])])])]),e._v(" "),a("table",[a("thead",[a("tr",[a("th",[e._v("JOB_EXEC_ID")]),e._v(" "),a("th",[e._v("JOB_INST_ID")]),e._v(" "),a("th",[e._v("START_TIME")]),e._v(" "),a("th",[e._v("END_TIME")]),e._v(" "),a("th",[e._v("STATUS")])])]),e._v(" "),a("tbody",[a("tr",[a("td",[e._v("1")]),e._v(" "),a("td",[e._v("1")]),e._v(" "),a("td",[e._v("2017-01-01 21:00")]),e._v(" "),a("td",[e._v("2017-01-01 21:30")]),e._v(" "),a("td",[e._v("FAILED")])]),e._v(" "),a("tr",[a("td",[e._v("2")]),e._v(" "),a("td",[e._v("1")]),e._v(" "),a("td",[e._v("2017-01-02 21:00")]),e._v(" "),a("td",[e._v("2017-01-02 21:30")]),e._v(" "),a("td",[e._v("COMPLETED")])]),e._v(" "),a("tr",[a("td",[e._v("3")]),e._v(" "),a("td",[e._v("2")]),e._v(" "),a("td",[e._v("2017-01-02 21:31")]),e._v(" "),a("td",[e._v("2017-01-02 22:29")]),e._v(" "),a("td",[e._v("COMPLETED")])])])]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("Column names may have been abbreviated or removed for the sake of clarity and"),a("br"),e._v("formatting.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("h3",{attrs:{id:"step"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#step"}},[e._v("#")]),e._v(" Step")]),e._v(" "),a("p",[e._v("A "),a("code",[e._v("Step")]),e._v(" is a domain object that encapsulates an independent, sequential phase of a batch\njob. Therefore, every Job is composed entirely of one or more steps. A "),a("code",[e._v("Step")]),e._v(" contains\nall of the information necessary to define and control the actual batch processing. This\nis a necessarily vague description because the contents of any given "),a("code",[e._v("Step")]),e._v(" are at the\ndiscretion of the developer writing a "),a("code",[e._v("Job")]),e._v(". A "),a("code",[e._v("Step")]),e._v(" can be as simple or complex as the\ndeveloper desires. A simple "),a("code",[e._v("Step")]),e._v(" might load data from a file into the database,\nrequiring little or no code (depending upon the implementations used). A more complex"),a("code",[e._v("Step")]),e._v(" may have complicated business rules that are applied as part of the processing. As\nwith a "),a("code",[e._v("Job")]),e._v(", a "),a("code",[e._v("Step")]),e._v(" has an individual "),a("code",[e._v("StepExecution")]),e._v(" that correlates with a unique"),a("code",[e._v("JobExecution")]),e._v(", as shown in the following image:")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-batch/docs/current/reference/html/images/jobHeirarchyWithSteps.png",alt:"Figure 2.1: Job Hierarchy With Steps"}})]),e._v(" "),a("p",[e._v("Figure 4. Job Hierarchy With Steps")]),e._v(" "),a("h4",{attrs:{id:"stepexecution"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#stepexecution"}},[e._v("#")]),e._v(" StepExecution")]),e._v(" "),a("p",[e._v("A "),a("code",[e._v("StepExecution")]),e._v(" represents a single attempt to execute a "),a("code",[e._v("Step")]),e._v(". A new "),a("code",[e._v("StepExecution")]),e._v("is created each time a "),a("code",[e._v("Step")]),e._v(" is run, similar to "),a("code",[e._v("JobExecution")]),e._v(". However, if a step fails\nto execute because the step before it fails, no execution is persisted for it. A"),a("code",[e._v("StepExecution")]),e._v(" is created only when its "),a("code",[e._v("Step")]),e._v(" is actually started.")]),e._v(" "),a("p",[a("code",[e._v("Step")]),e._v(" executions are represented by objects of the "),a("code",[e._v("StepExecution")]),e._v(" class. Each execution\ncontains a reference to its corresponding step and "),a("code",[e._v("JobExecution")]),e._v(" and transaction related\ndata, such as commit and rollback counts and start and end times. Additionally, each step\nexecution contains an "),a("code",[e._v("ExecutionContext")]),e._v(", which contains any data a developer needs to\nhave persisted across batch runs, such as statistics or state information needed to\nrestart. The following table lists the properties for "),a("code",[e._v("StepExecution")]),e._v(":")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th",[e._v("Property")]),e._v(" "),a("th",[e._v("Definition")])])]),e._v(" "),a("tbody",[a("tr",[a("td",[e._v("Status")]),e._v(" "),a("td",[e._v("A "),a("code",[e._v("BatchStatus")]),e._v(" object that indicates the status of the execution. While running, the"),a("br"),e._v("status is "),a("code",[e._v("BatchStatus.STARTED")]),e._v(". If it fails, the status is "),a("code",[e._v("BatchStatus.FAILED")]),e._v(". If it"),a("br"),e._v("finishes successfully, the status is "),a("code",[e._v("BatchStatus.COMPLETED")]),e._v(".")])]),e._v(" "),a("tr",[a("td",[e._v("startTime")]),e._v(" "),a("td",[e._v("A "),a("code",[e._v("java.util.Date")]),e._v(" representing the current system time when the execution was started."),a("br"),e._v("This field is empty if the step has yet to start.")])]),e._v(" "),a("tr",[a("td",[e._v("endTime")]),e._v(" "),a("td",[e._v("A "),a("code",[e._v("java.util.Date")]),e._v(" representing the current system time when the execution finished,"),a("br"),e._v("regardless of whether or not it was successful. This field is empty if the step has yet to"),a("br"),e._v("exit.")])]),e._v(" "),a("tr",[a("td",[e._v("exitStatus")]),e._v(" "),a("td",[e._v("The "),a("code",[e._v("ExitStatus")]),e._v(" indicating the result of the execution. It is most important, because"),a("br"),e._v("it contains an exit code that is returned to the caller. See chapter 5 for more details."),a("br"),e._v("This field is empty if the job has yet to exit.")])]),e._v(" "),a("tr",[a("td",[e._v("executionContext")]),e._v(" "),a("td",[e._v('The "property bag" containing any user data that needs to be persisted between'),a("br"),e._v("executions.")])]),e._v(" "),a("tr",[a("td",[e._v("readCount")]),e._v(" "),a("td",[e._v("The number of items that have been successfully read.")])]),e._v(" "),a("tr",[a("td",[e._v("writeCount")]),e._v(" "),a("td",[e._v("The number of items that have been successfully written.")])]),e._v(" "),a("tr",[a("td",[e._v("commitCount")]),e._v(" "),a("td",[e._v("The number of transactions that have been committed for this execution.")])]),e._v(" "),a("tr",[a("td",[e._v("rollbackCount")]),e._v(" "),a("td",[e._v("The number of times the business transaction controlled by the "),a("code",[e._v("Step")]),e._v(" has been rolled"),a("br"),e._v("back.")])]),e._v(" "),a("tr",[a("td",[e._v("readSkipCount")]),e._v(" "),a("td",[e._v("The number of times "),a("code",[e._v("read")]),e._v(" has failed, resulting in a skipped item.")])]),e._v(" "),a("tr",[a("td",[e._v("processSkipCount")]),e._v(" "),a("td",[e._v("The number of times "),a("code",[e._v("process")]),e._v(" has failed, resulting in a skipped item.")])]),e._v(" "),a("tr",[a("td",[e._v("filterCount")]),e._v(" "),a("td",[e._v("The number of items that have been 'filtered' by the "),a("code",[e._v("ItemProcessor")]),e._v(".")])]),e._v(" "),a("tr",[a("td",[e._v("writeSkipCount")]),e._v(" "),a("td",[e._v("The number of times "),a("code",[e._v("write")]),e._v(" has failed, resulting in a skipped item.")])])])]),e._v(" "),a("h3",{attrs:{id:"executioncontext"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#executioncontext"}},[e._v("#")]),e._v(" ExecutionContext")]),e._v(" "),a("p",[e._v("An "),a("code",[e._v("ExecutionContext")]),e._v(" represents a collection of key/value pairs that are persisted and\ncontrolled by the framework in order to allow developers a place to store persistent\nstate that is scoped to a "),a("code",[e._v("StepExecution")]),e._v(" object or a "),a("code",[e._v("JobExecution")]),e._v(" object. For those\nfamiliar with Quartz, it is very similar to JobDataMap. The best usage example is to\nfacilitate restart. Using flat file input as an example, while processing individual\nlines, the framework periodically persists the "),a("code",[e._v("ExecutionContext")]),e._v(" at commit points. Doing\nso allows the "),a("code",[e._v("ItemReader")]),e._v(" to store its state in case a fatal error occurs during the run\nor even if the power goes out. All that is needed is to put the current number of lines\nread into the context, as shown in the following example, and the framework will do the\nrest:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("executionContext.putLong(getKey(LINES_READ_COUNT), reader.getPosition());\n")])])]),a("p",[e._v("Using the EndOfDay example from the "),a("code",[e._v("Job")]),e._v(" Stereotypes section as an example, assume there\nis one step, 'loadData', that loads a file into the database. After the first failed run,\nthe metadata tables would look like the following example:")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th",[e._v("JOB_INST_ID")]),e._v(" "),a("th",[e._v("JOB_NAME")])])]),e._v(" "),a("tbody",[a("tr",[a("td",[e._v("1")]),e._v(" "),a("td",[e._v("EndOfDayJob")])])])]),e._v(" "),a("table",[a("thead",[a("tr",[a("th",[e._v("JOB_INST_ID")]),e._v(" "),a("th",[e._v("TYPE_CD")]),e._v(" "),a("th",[e._v("KEY_NAME")]),e._v(" "),a("th",[e._v("DATE_VAL")])])]),e._v(" "),a("tbody",[a("tr",[a("td",[e._v("1")]),e._v(" "),a("td",[e._v("DATE")]),e._v(" "),a("td",[e._v("schedule.Date")]),e._v(" "),a("td",[e._v("2017-01-01")])])])]),e._v(" "),a("table",[a("thead",[a("tr",[a("th",[e._v("JOB_EXEC_ID")]),e._v(" "),a("th",[e._v("JOB_INST_ID")]),e._v(" "),a("th",[e._v("START_TIME")]),e._v(" "),a("th",[e._v("END_TIME")]),e._v(" "),a("th",[e._v("STATUS")])])]),e._v(" "),a("tbody",[a("tr",[a("td",[e._v("1")]),e._v(" "),a("td",[e._v("1")]),e._v(" "),a("td",[e._v("2017-01-01 21:00")]),e._v(" "),a("td",[e._v("2017-01-01 21:30")]),e._v(" "),a("td",[e._v("FAILED")])])])]),e._v(" "),a("table",[a("thead",[a("tr",[a("th",[e._v("STEP_EXEC_ID")]),e._v(" "),a("th",[e._v("JOB_EXEC_ID")]),e._v(" "),a("th",[e._v("STEP_NAME")]),e._v(" "),a("th",[e._v("START_TIME")]),e._v(" "),a("th",[e._v("END_TIME")]),e._v(" "),a("th",[e._v("STATUS")])])]),e._v(" "),a("tbody",[a("tr",[a("td",[e._v("1")]),e._v(" "),a("td",[e._v("1")]),e._v(" "),a("td",[e._v("loadData")]),e._v(" "),a("td",[e._v("2017-01-01 21:00")]),e._v(" "),a("td",[e._v("2017-01-01 21:30")]),e._v(" "),a("td",[e._v("FAILED")])])])]),e._v(" "),a("table",[a("thead",[a("tr",[a("th",[e._v("STEP_EXEC_ID")]),e._v(" "),a("th",[e._v("SHORT_CONTEXT")])])]),e._v(" "),a("tbody",[a("tr",[a("td",[e._v("1")]),e._v(" "),a("td",[e._v("{piece.count=40321}")])])])]),e._v(" "),a("p",[e._v("In the preceding case, the "),a("code",[e._v("Step")]),e._v(" ran for 30 minutes and processed 40,321 'pieces', which\nwould represent lines in a file in this scenario. This value is updated just before each\ncommit by the framework and can contain multiple rows corresponding to entries within the"),a("code",[e._v("ExecutionContext")]),e._v(". Being notified before a commit requires one of the various"),a("code",[e._v("StepListener")]),e._v(" implementations (or an "),a("code",[e._v("ItemStream")]),e._v("), which are discussed in more detail\nlater in this guide. As with the previous example, it is assumed that the "),a("code",[e._v("Job")]),e._v(" is\nrestarted the next day. When it is restarted, the values from the "),a("code",[e._v("ExecutionContext")]),e._v(" of\nthe last run are reconstituted from the database. When the "),a("code",[e._v("ItemReader")]),e._v(" is opened, it can\ncheck to see if it has any stored state in the context and initialize itself from there,\nas shown in the following example:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('if (executionContext.containsKey(getKey(LINES_READ_COUNT))) {\n    log.debug("Initializing for restart. Restart data is: " + executionContext);\n\n    long lineCount = executionContext.getLong(getKey(LINES_READ_COUNT));\n\n    LineReader reader = getReader();\n\n    Object record = "";\n    while (reader.getPosition() < lineCount && record != null) {\n        record = readLine();\n    }\n}\n')])])]),a("p",[e._v("In this case, after the above code runs, the current line is 40,322, allowing the "),a("code",[e._v("Step")]),e._v("to start again from where it left off. The "),a("code",[e._v("ExecutionContext")]),e._v(" can also be used for\nstatistics that need to be persisted about the run itself. For example, if a flat file\ncontains orders for processing that exist across multiple lines, it may be necessary to\nstore how many orders have been processed (which is much different from the number of\nlines read), so that an email can be sent at the end of the "),a("code",[e._v("Step")]),e._v(" with the total number\nof orders processed in the body. The framework handles storing this for the developer, in\norder to correctly scope it with an individual "),a("code",[e._v("JobInstance")]),e._v(". It can be very difficult to\nknow whether an existing "),a("code",[e._v("ExecutionContext")]),e._v(" should be used or not. For example, using the\n'EndOfDay' example from above, when the 01-01 run starts again for the second time, the\nframework recognizes that it is the same "),a("code",[e._v("JobInstance")]),e._v(" and on an individual "),a("code",[e._v("Step")]),e._v(" basis,\npulls the "),a("code",[e._v("ExecutionContext")]),e._v(" out of the database, and hands it (as part of the"),a("code",[e._v("StepExecution")]),e._v(") to the "),a("code",[e._v("Step")]),e._v(" itself. Conversely, for the 01-02 run, the framework\nrecognizes that it is a different instance, so an empty context must be handed to the"),a("code",[e._v("Step")]),e._v(". There are many of these types of determinations that the framework makes for the\ndeveloper, to ensure the state is given to them at the correct time. It is also important\nto note that exactly one "),a("code",[e._v("ExecutionContext")]),e._v(" exists per "),a("code",[e._v("StepExecution")]),e._v(" at any given time.\nClients of the "),a("code",[e._v("ExecutionContext")]),e._v(" should be careful, because this creates a shared\nkeyspace. As a result, care should be taken when putting values in to ensure no data is\noverwritten. However, the "),a("code",[e._v("Step")]),e._v(" stores absolutely no data in the context, so there is no\nway to adversely affect the framework.")]),e._v(" "),a("p",[e._v("It is also important to note that there is at least one "),a("code",[e._v("ExecutionContext")]),e._v(" per"),a("code",[e._v("JobExecution")]),e._v(" and one for every "),a("code",[e._v("StepExecution")]),e._v(". For example, consider the following\ncode snippet:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("ExecutionContext ecStep = stepExecution.getExecutionContext();\nExecutionContext ecJob = jobExecution.getExecutionContext();\n//ecStep does not equal ecJob\n")])])]),a("p",[e._v("As noted in the comment, "),a("code",[e._v("ecStep")]),e._v(" does not equal "),a("code",[e._v("ecJob")]),e._v(". They are two different"),a("code",[e._v("ExecutionContexts")]),e._v(". The one scoped to the "),a("code",[e._v("Step")]),e._v(" is saved at every commit point in the"),a("code",[e._v("Step")]),e._v(", whereas the one scoped to the Job is saved in between every "),a("code",[e._v("Step")]),e._v(" execution.")]),e._v(" "),a("h3",{attrs:{id:"jobrepository"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#jobrepository"}},[e._v("#")]),e._v(" JobRepository")]),e._v(" "),a("p",[a("code",[e._v("JobRepository")]),e._v(" is the persistence mechanism for all of the Stereotypes mentioned above.\nIt provides CRUD operations for "),a("code",[e._v("JobLauncher")]),e._v(", "),a("code",[e._v("Job")]),e._v(", and "),a("code",[e._v("Step")]),e._v(" implementations. When a"),a("code",[e._v("Job")]),e._v(" is first launched, a "),a("code",[e._v("JobExecution")]),e._v(" is obtained from the repository, and, during\nthe course of execution, "),a("code",[e._v("StepExecution")]),e._v(" and "),a("code",[e._v("JobExecution")]),e._v(" implementations are persisted\nby passing them to the repository.")]),e._v(" "),a("p",[e._v("The Spring Batch XML namespace provides support for configuring a "),a("code",[e._v("JobRepository")]),e._v(" instance\nwith the "),a("code",[e._v("<job-repository>")]),e._v(" tag, as shown in the following example:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('<job-repository id="jobRepository"/>\n')])])]),a("p",[e._v("When using Java configuration, the "),a("code",[e._v("@EnableBatchProcessing")]),e._v(" annotation provides a"),a("code",[e._v("JobRepository")]),e._v(" as one of the components automatically configured out of the box.")]),e._v(" "),a("h3",{attrs:{id:"joblauncher"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#joblauncher"}},[e._v("#")]),e._v(" JobLauncher")]),e._v(" "),a("p",[a("code",[e._v("JobLauncher")]),e._v(" represents a simple interface for launching a "),a("code",[e._v("Job")]),e._v(" with a given set of"),a("code",[e._v("JobParameters")]),e._v(", as shown in the following example:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("public interface JobLauncher {\n\npublic JobExecution run(Job job, JobParameters jobParameters)\n            throws JobExecutionAlreadyRunningException, JobRestartException,\n                   JobInstanceAlreadyCompleteException, JobParametersInvalidException;\n}\n")])])]),a("p",[e._v("It is expected that implementations obtain a valid "),a("code",[e._v("JobExecution")]),e._v(" from the"),a("code",[e._v("JobRepository")]),e._v(" and execute the "),a("code",[e._v("Job")]),e._v(".")]),e._v(" "),a("h3",{attrs:{id:"item-reader"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#item-reader"}},[e._v("#")]),e._v(" Item Reader")]),e._v(" "),a("p",[a("code",[e._v("ItemReader")]),e._v(" is an abstraction that represents the retrieval of input for a "),a("code",[e._v("Step")]),e._v(", one\nitem at a time. When the "),a("code",[e._v("ItemReader")]),e._v(" has exhausted the items it can provide, it\nindicates this by returning "),a("code",[e._v("null")]),e._v(". More details about the "),a("code",[e._v("ItemReader")]),e._v(" interface and its\nvarious implementations can be found in"),a("RouterLink",{attrs:{to:"/en/spring-batch/readersAndWriters.html#readersAndWriters"}},[e._v("Readers And Writers")]),e._v(".")],1),e._v(" "),a("h3",{attrs:{id:"item-writer"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#item-writer"}},[e._v("#")]),e._v(" Item Writer")]),e._v(" "),a("p",[a("code",[e._v("ItemWriter")]),e._v(" is an abstraction that represents the output of a "),a("code",[e._v("Step")]),e._v(", one batch or chunk\nof items at a time. Generally, an "),a("code",[e._v("ItemWriter")]),e._v(" has no knowledge of the input it should\nreceive next and knows only the item that was passed in its current invocation. More\ndetails about the "),a("code",[e._v("ItemWriter")]),e._v(" interface and its various implementations can be found in"),a("RouterLink",{attrs:{to:"/en/spring-batch/readersAndWriters.html#readersAndWriters"}},[e._v("Readers And Writers")]),e._v(".")],1),e._v(" "),a("h3",{attrs:{id:"item-processor"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#item-processor"}},[e._v("#")]),e._v(" Item Processor")]),e._v(" "),a("p",[a("code",[e._v("ItemProcessor")]),e._v(" is an abstraction that represents the business processing of an item.\nWhile the "),a("code",[e._v("ItemReader")]),e._v(" reads one item, and the "),a("code",[e._v("ItemWriter")]),e._v(" writes them, the"),a("code",[e._v("ItemProcessor")]),e._v(" provides an access point to transform or apply other business processing.\nIf, while processing the item, it is determined that the item is not valid, returning"),a("code",[e._v("null")]),e._v(" indicates that the item should not be written out. More details about the"),a("code",[e._v("ItemProcessor")]),e._v(" interface can be found in"),a("RouterLink",{attrs:{to:"/en/spring-batch/readersAndWriters.html#readersAndWriters"}},[e._v("Readers And Writers")]),e._v(".")],1),e._v(" "),a("h3",{attrs:{id:"batch-namespace"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#batch-namespace"}},[e._v("#")]),e._v(" Batch Namespace")]),e._v(" "),a("p",[e._v("Many of the domain concepts listed previously need to be configured in a Spring"),a("code",[e._v("ApplicationContext")]),e._v(". While there are implementations of the interfaces above that can be\nused in a standard bean definition, a namespace has been provided for ease of\nconfiguration, as shown in the following example:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('<beans:beans xmlns="http://www.springframework.org/schema/batch"\nxmlns:beans="http://www.springframework.org/schema/beans"\nxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\nxsi:schemaLocation="\n   http://www.springframework.org/schema/beans\n   https://www.springframework.org/schema/beans/spring-beans.xsd\n   http://www.springframework.org/schema/batch\n   https://www.springframework.org/schema/batch/spring-batch.xsd">\n\n<job id="ioSampleJob">\n    <step id="step1">\n        <tasklet>\n            <chunk reader="itemReader" writer="itemWriter" commit-interval="2"/>\n        </tasklet>\n    </step>\n</job>\n\n</beans:beans>\n')])])]),a("p",[e._v("As long as the batch namespace has been declared, any of its elements can be used. More\ninformation on configuring a Job can be found in "),a("RouterLink",{attrs:{to:"/en/spring-batch/job.html#configureJob"}},[e._v("Configuring and\nRunning a Job")]),e._v(". More information on configuring a "),a("code",[e._v("Step")]),e._v(" can be found in"),a("RouterLink",{attrs:{to:"/en/spring-batch/step.html#configureStep"}},[e._v("Configuring a Step")]),e._v(".")],1)])}),[],!1,null,null,null);t.default=n.exports}}]);