jobbuilderfactory - ¿Cómo podemos compartir datos entre los diferentes pasos de un trabajo en Spring Batch?
spring batch jobbuilderfactory (9)
Como dijo Nenad Bozic en su tercera opción, use tablas temporales para compartir los datos entre pasos, usar contexto para compartir también hace lo mismo, escribe en la tabla y vuelve a cargar en el siguiente paso, pero si escribe en tablas temporales puede limpiar a el final del trabajo
Buscando en Spring Batch, me gustaría saber cómo podemos compartir datos entre los diferentes pasos de un trabajo.
¿Podemos usar JobRepository para esto? Si es así, ¿cómo podemos hacer eso?
¿Hay alguna otra forma de hacer / lograr lo mismo?
Desde un paso, puede colocar datos en StepExecutionContext
. Luego, con un oyente, puede promocionar los datos de StepExecutionContext
a JobExecutionContext
.
Este JobExecutionContext
está disponible en todos los pasos siguientes.
Cuidado: los datos deben ser cortos. Estos contextos se guardan en JobRepository
por serialización y la longitud es limitada (2500 caracteres si recuerdo bien).
Entonces estos contextos son buenos para compartir cadenas o valores simples, pero no para compartir colecciones o grandes cantidades de datos.
Compartir grandes cantidades de datos no es la filosofía de Spring Batch. Spring Batch es un conjunto de acciones distintas, no una gran unidad de procesamiento de negocios.
Esto es lo que hice para guardar un objeto al que se puede acceder a través de los pasos.
- Creó un oyente para configurar el objeto en el contexto del trabajo
@Component("myJobListener")
public class MyJobListener implements JobExecutionListener {
public void beforeJob(JobExecution jobExecution) {
String myValue = someService.getValue();
jobExecution.getExecutionContext().putString("MY_VALUE", myValue);
}
}
- Definido el oyente en el contexto laboral
<listeners>
<listener ref="myJobListener"/>
</listeners>
- Consumió el valor en el paso usando la anotación BeforeStep
@BeforeStep
public void initializeValues(StepExecution stepExecution) {
String value = stepExecution.getJobExecution().getExecutionContext().getString("MY_VALUE");
}
Me dieron una tarea para invocar el trabajo por lotes uno por uno. Cada trabajo depende de otro. El resultado del primer trabajo debe ejecutar el programa de trabajo consecuente. Estaba buscando cómo pasar los datos después de la ejecución del trabajo. Descubrí que este ExecutionContextPromotionListener es útil.
1) He añadido un bean para "ExecutionContextPromotionListener" como a continuación
@Bean
public ExecutionContextPromotionListener promotionListener()
{
ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
listener.setKeys( new String[] { "entityRef" } );
return listener;
}
2) Luego uní a uno de los oyentes a mis Pasos
Step step = builder.faultTolerant()
.skipPolicy( policy )
.listener( writer )
.listener( promotionListener() )
.listener( skiplistener )
.stream( skiplistener )
.build();
3) He agregado stepExecution como referencia en mi implementación de Writer step y me he llenado en Beforestep
@BeforeStep
public void saveStepExecution( StepExecution stepExecution )
{
this.stepExecution = stepExecution;
}
4) al final de mi paso de escritor, he poblado los valores en la ejecución de stepe como las claves a continuación
lStepContext.put( "entityRef", lMap );
5) Después de la ejecución del trabajo, lExecution.getExecutionContext()
los valores de lExecution.getExecutionContext()
y los lExecution.getExecutionContext()
como respuesta de trabajo.
6) del objeto de respuesta de trabajo, obtendré los valores y poblaré los valores requeridos en el resto de los trabajos.
El código anterior es para promocionar los datos de los pasos a ExecutionContext usando ExecutionContextPromotionListener. Se puede hacer en cualquier paso.
Puede almacenar datos en el objeto simple. Me gusta:
AnyObject yourObject = new AnyObject();
public Job build(Step step1, Step step2) {
return jobBuilderFactory.get("jobName")
.incrementer(new RunIdIncrementer())
.start(step1)
.next(step2)
.build();
}
public Step step1() {
return stepBuilderFactory.get("step1Name")
.<Some, Any> chunk(someInteger1)
.reader(itemReader1())
.processor(itemProcessor1())
.writer(itemWriter1(yourObject))
.build();
}
public Step step2() {
return stepBuilderFactory.get("step2Name")
.<Some, Any> chunk(someInteger2)
.reader(itemReader2())
.processor(itemProcessor2(yourObject))
.writer(itemWriter2())
.build();
}
Simplemente agregue datos al objeto en el escritor o cualquier otro método y consígalo en cualquier etapa del siguiente paso
Puede usar un objeto Java Bean
- Ejecute un paso
- Almacenar el resultado en el objeto Java
- El siguiente paso hará referencia al mismo objeto java para obtener el resultado almacenado por el paso 1
De esta manera, puede almacenar una gran colección de datos si lo desea
Use `ExecutionContextPromotionListener.
public class YourItemWriter implements ItemWriter<Object> {
private StepExecution stepExecution;
public void write(List<? extends Object> items) throws Exception {
// Some Business Logic
// put your data into stepexecution context
ExecutionContext stepContext = this.stepExecution.getExecutionContext();
stepContext.put("someKey", someObject);
}
@BeforeStep
public void saveStepExecution(Final StepExecution stepExecution) {
this.stepExecution = stepExecution;
}
}
Ahora necesita agregar promotionListener a su trabajo
@Bean
public Step step1() {
return stepBuilder
.get("step1")<Company,Company> chunk(10)
.reader(reader()).processor(processor()).writer(writer())
.listener(promotionListner()).build();
}
@Bean public ExecutionContextPromotionListener promotionListner () {ExecutionContextPromotionListener listner = new ExecutionContextPromotionListener (); listner.setKeys (new String [] {"someKey"}); listner.setStrict (verdadero); return listner; }
Ahora, en el paso 2 obtenga sus datos del trabajo ExecutionContext
public class RetrievingItemWriter implements ItemWriter<Object> {
private Object someObject;
public void write(List<? extends Object> items) throws Exception {
// ...
}
@BeforeStep
public void retrieveInterstepData(StepExecution stepExecution) {
JobExecution jobExecution = stepExecution.getJobExecution();
ExecutionContext jobContext = jobExecution.getExecutionContext();
this.someObject = jobContext.get("someKey");
}
}
Si está trabajando con tasklets, use follwing para obtener o poner ExecutionContext
List<YourObject> yourObjects = (List<YourObject>) chunkContent.getStepContext().getJobExecutionContext().get("someKey");
Yo diría que tienes 3 opciones:
- Use
StepContext
y promocionarlo aJobContext
y tiene acceso a él desde cada paso; debe, como se indica, obedecer el límite de tamaño - Cree
@JobScope
bean y agregue datos a ese bean,@Autowire
donde sea necesario y@Autowire
(la desventaja es que se trata de una estructura en memoria y si se pierde el trabajo, se pierden datos, lo que puede causar problemas de reiniciabilidad) - Necesitamos procesar conjuntos de datos más grandes en todos los pasos (leer cada línea en csv y escribir en DB, leer de DB, agregar y enviar a API), así que decidimos modelar los datos en la nueva tabla en la misma base de datos como metatablas de lotes de primavera, mantener
ids
enJobContext
y acceda cuando sea necesario y elimine esa tabla temporal cuando el trabajo finaliza correctamente.
el repositorio de trabajos se usa indirectamente para pasar datos entre pasos (Jean-Philippe tiene razón en que la mejor manera de hacerlo es colocar datos en StepExecutionContext
y luego usar el nombre expresamente llamado ExecutionContextPromotionListener
para promover las claves de contexto de ejecución de pasos en JobExecutionContext
.
Es útil observar que hay un oyente que también promueve JobParameter
claves JobParameter
en un StepExecutionContext
(el aún más variado JobParameterExecutionContextCopyListener
); encontrará que los usa mucho si los pasos de su trabajo no son completamente independientes el uno del otro.
De lo contrario, se le deja pasar datos entre pasos usando esquemas aún más elaborados, como colas JMS o ubicaciones de archivos codificadas (no lo permita el cielo).
En cuanto al tamaño de los datos que se pasan en el contexto, también le sugiero que lo mantenga pequeño (pero no tengo ningún detalle sobre el