Skupna obrada Java EE 7

1. Uvod

Zamislite da smo morali ručno dovršavati zadatke poput obrade platnih listića, izračunavanja kamata i generiranja računa. Postao bi prilično dosadan, sklon pogreškama i beskrajan popis ručnih zadataka!

U ovom ćemo uputstvu pogledati Java Batch Processing (JSR 352), dio Jakarta EE platforme, i sjajne specifikacije za automatizaciju zadataka poput ovih. Nudi programerima aplikacija model za razvoj robusnih sustava batch obrade kako bi se mogli usredotočiti na poslovnu logiku.

2. Ovisnosti Mavena

Budući da je JSR 352 samo specifikacija, morat ćemo uključiti njegov API i implementaciju, na primjer jberet:

 javax.batch javax.batch-api 1.0.1 org.jberet jberet-core 1.0.2.Final org.jberet jberet-support 1.0.2.Final org.jberet jberet-se 1.0.2.Final 

Također ćemo dodati bazu podataka u memoriji kako bismo mogli pogledati neke realnije scenarije.

3. Ključni pojmovi

JSR 352 uvodi nekoliko koncepata koje možemo gledati na ovaj način:

Prvo definirajmo svaki komad:

  • Počevši s lijeve strane, imamo JobOperator. To upravlja svim aspektima obrade poslova, poput pokretanja, zaustavljanja i ponovnog pokretanja
  • Dalje, imamo Posao. Posao je logična zbirka koraka; obuhvaća čitav šaržni postupak
  • Posao će sadržavati između 1 i n Koraks. Svaki je korak neovisna, sekvencijalna cjelina rada. Korak se sastoji od čitanje ulazni, obrada taj ulaz i pisanje izlaz
  • I posljednje, ali ne najmanje važno, imamo Spremište poslova koja pohranjuje tekuće podatke o poslovima. Pomaže u evidentiranju poslova, njihovog stanja i rezultata završetka

Koraci imaju malo više detalja od ovoga, pa pogledajmo to dalje. Prvo ćemo pogledati Komad stepenice pa na Batchlets.

4. Stvaranje komada

Kao što je ranije rečeno, komad je svojevrsni korak. Često ćemo koristiti komadić da bismo izrazili operaciju koja se izvodi iznova i iznova, recimo preko niza stavki. To je poput posrednih operacija iz Java Streamsa.

Kad opisujemo komad, moramo izraziti odakle ćemo uzeti predmete, kako ih obraditi i gdje ih poslije poslati.

4.1. Čitanje predmeta

Da bismo čitali stavke, trebat ćemo ih implementirati Predmet za čitanje.

U ovom ćemo slučaju stvoriti čitač koji će jednostavno emitirati brojeve od 1 do 10:

@Named javna klasa SimpleChunkItemReader proširuje žetone AbstractItemReader {private Integer []; privatno brojanje cijelih brojeva; @Inject JobContext jobContext; @Override public Integer readItem () baca izuzetak {if (count> = tokens.length) {return null; } jobContext.setTransientUserData (count); povratni tokeni [count ++]; } @Override public void open (Serializable checkpoint) baca iznimku {tokens = new Integer [] {1,2,3,4,5,6,7,8,9,10}; broj = 0; }}

Sad ovdje samo čitamo iz unutarnjeg stanja u razredu. Ali, naravno, readItem mogao povući iz baze podataka, iz datotečnog sustava ili nekog drugog vanjskog izvora.

Imajte na umu da neke od ovog unutarnjeg stanja spremamo pomoću JobContext # setTransientUserData () koja će mi kasnije dobro doći.

Također, imajte na umu kontrolna točka parametar. I to ćemo opet pokupiti.

4.2. Obrada predmeta

Naravno, razlog zbog kojeg se drobimo je taj što želimo izvršiti neku vrstu operacije na našim predmetima!

Kad god se vratimo null iz procesora predmeta, ispuštamo tu stavku iz serije.

Dakle, recimo ovdje da želimo zadržati samo parne brojeve. Možemo koristiti PredmetProcesor koja vraćanje odbacuje one neobične null:

@Namena javna klasa SimpleChunkItemProcessor implementira ItemProcessor {@Prevlada javni Integer processItem (Object t) {Integer item = (Integer) t; vratiti stavku% 2 == 0? stavka: null; }}

procesItem pozvat će se jednom za svaku stavku koju naš Predmet za čitanje emitira.

4.3. Pisanje predmeta

Napokon, posao će pozvati ItemWriter kako bismo mogli napisati svoje transformirane predmete:

@Named javna klasa SimpleChunkWriter proširuje AbstractItemWriter {Popis obrađen = novi ArrayList (); @Override public void writeItems (Stavke popisa) bacaju iznimku {items.stream (). Map (Integer.class :: cast) .forEach (obradeno :: dodaj); }} 

Koliko je dugo predmeta? Za trenutak ćemo definirati veličinu dijela, koja će odrediti veličinu popisa na koji se šalje predmeti za pisanje.

4.4. Utvrđivanje dijela na poslu

Sada smo sve to stavili u XML datoteku koristeći JSL ili jezik za specifikaciju posla. Imajte na umu da ćemo navesti naš čitač, procesor, komadnik, a također i veličinu komada:

Veličina dijela je koliko je često napredak u komadu predan spremištu poslova, što je važno za jamčenje završetka, ako dio sustava zakaže.

Morat ćemo smjestiti ovu datoteku u META-INF / batch-poslovi za.staklenka datoteke i u WEB-INF / klase / META-INF / batch-poslovi za .rat datoteke.

Dali smo svom poslu id "SimpleChunk", pa probajmo to u jediničnom testu.

Sada se poslovi izvršavaju asinkrono, što ih čini nezgodnim za testiranje. U uzorku provjerite našu BatchTestHelper koja anketira i čeka dok posao ne bude gotov:

@Test javna praznina givenChunk_thenBatch_completesWithSuccess () baca iznimku {JobOperator jobOperator = BatchRuntime.getJobOperator (); Dugo izvršenje = posaoOperator.start ("simpleChunk", nova svojstva ()); JobExecution jobExecution = jobOperator.getJobExecution (ExecuId); jobExecution = BatchTestHelper.keepTestAlive (jobExecution); assertEquals (jobExecution.getBatchStatus (), BatchStatus.COMPLETED); } 

Pa to su komadi. Sada, pogledajmo serije.

5. Izrada Batchleta

Nije sve lijepo u iterativnom modelu. Na primjer, možemo imati zadatak koji nam je jednostavno potreban pozovite jednom, pokrenite do kraja i vratite status izlaza.

Ugovor za batchlet je vrlo jednostavan:

@Named javna klasa SimpleBatchLet proširuje AbstractBatchlet {@Override postupak javnog niza () baca izuzetak {return BatchStatus.COMPLETED.toString (); }}

Kao i JSL:

A možemo ga testirati koristeći isti pristup kao i prije:

@Test javna praznina givenBatchlet_thenBatch_completeWithSuccess () baca izuzetak {JobOperator jobOperator = BatchRuntime.getJobOperator (); Dugo izvršenje = posaoOperator.start ("simpleBatchLet", nova svojstva ()); JobExecution jobExecution = jobOperator.getJobExecution (ExecuId); jobExecution = BatchTestHelper.keepTestAlive (jobExecution); assertEquals (jobExecution.getBatchStatus (), BatchStatus.COMPLETED); }

Dakle, pogledali smo nekoliko različitih načina za provedbu koraka.

Pogledajmo sada mehanizme za obilježavanje i jamčenje napretka.

6. Prilagođena kontrolna točka

Neuspjesi će se sigurno dogoditi usred posla. Trebamo li započeti cijelu stvar ili možemo nekako započeti tamo gdje smo stali?

Kao što i samo ime govori, kontrolne točke pomozite nam da povremeno postavimo oznaku u slučaju kvara.

Prema zadanim postavkama, kraj obrade dijelova prirodna je kontrolna točka.

Međutim, možemo ga prilagoditi vlastitim CheckpointAlgorithm:

@Name javna klasa CustomCheckPoint proširuje AbstractCheckpointAlgoritam {@Inject JobContext jobContext; @Override public boolean isReadyToCheckpoint () baca izuzetak {int counterRead = (Integer) jobContext.getTransientUserData (); return counterRead% 5 == 0; }}

Sjećate se broja koji smo ranije unijeli u privremene podatke? Ovdje, možemo ga izvući sa JobContext # getTransientUserDataizjaviti da se želimo obvezati na svaki 5. obrađeni broj.

Bez toga bi se urezivanje dogodilo na kraju svakog dijela, ili u našem slučaju, svakog 3. broja.

A onda, to uskladimo s algoritam za naplatu direktivu u našem XML-u ispod našeg dijela:

Isprobajmo kôd, napominjući opet da su neki koraci u tablici skriveni BatchTestHelper:

@Test javna praznina givenChunk_whenCustomCheckPoint_thenCommitCountIsThree () baca izuzetak {// ... pokreni posao i pričekaj završetak jobOperator.getStepExecutions (ExecuId) .stream () .map (BatchTestHelper :: getCommitCount) .forEach (count -> assert, count -> assert .longValue ())); assertEquals (jobExecution.getBatchStatus (), BatchStatus.COMPLETED); }

Dakle, mogli bismo očekivati ​​broj urezivanja 2, jer imamo deset stavki i konfigurirali smo urezivanje da bude svaka 5. stavka. Ali, okvir na kraju izvršava još jedan završni zapis čitanja kako bismo osigurali da je sve obrađeno, a to je ono što nas dovodi do 3.

Dalje, pogledajmo kako postupati s pogreškama.

7. Rukovanje iznimkama

Prema zadanim postavkama, operator posla označit će naš posao kao NEUSPJEH u slučaju iznimke.

Promijenimo čitač predmeta kako bismo bili sigurni da ne uspije:

@Override public Integer readItem () baca izuzetak {if (tokens.hasMoreTokens ()) {String tempTokenize = tokens.nextToken (); baciti novi RuntimeException (); } return null; }

A zatim testirajte:

@Test public void whenChunkError_thenBatch_CompletesWithFailed () baca iznimku {// ... pokretanje posla i čekanje završetka assertEquals (jobExecution.getBatchStatus (), BatchStatus.FAILED); }

Ali, ovo nadmašeno ponašanje možemo nadvladati na više načina:

  • preskoči-limit određuje broj iznimki koje će ovaj korak zanemariti prije nego što uspije
  • ponoviti-ograničenje određuje koliko puta bi operater posla trebao ponoviti korak prije neuspjeha
  • klasa preskočivih-iznimki određuje skup iznimaka koje će obrada dijelova zanemariti

Dakle, svoj posao možemo urediti tako da ignorira RuntimeException, kao i nekoliko drugih, samo radi ilustracije:

A sada će naš kod proći:

@Test javna praznina givenChunkError_thenErrorSkipped_CompletesWithSuccess () baca izuzetak {// ... pokreni posao i sačekaj završetak jobOperator.getStepExecutions (ExecuId) .stream () .map (BatchTestHelper :: getProcessSkipCountE. SkiEps (skip. Skip .longValue ())); assertEquals (jobExecution.getBatchStatus (), BatchStatus.COMPLETED); }

8. Izvođenje više koraka

Ranije smo spomenuli da posao može imati bilo koji broj koraka, pa da vidimo to sada.

8.1. Ispaljivanje sljedećeg koraka

Prema zadanim postavkama, svaki korak je zadnji korak u poslu.

Da bismo izvršili sljedeći korak u batch poslu, morat ćemo izričito navesti pomoću Sljedeći atribut unutar definicije koraka:

Ako zaboravimo ovaj atribut, tada se sljedeći korak u nizu neće izvršiti.

A kako to izgleda možemo vidjeti u API-ju:

@Test javna praznina givenTwoSteps_thenBatch_CompleteWithSuccess () baca iznimku {// ... pokreni posao i pričekaj završetak assertEquals (2, jobOperator.getStepExecutions (ExecuId) .size ()); assertEquals (jobExecution.getBatchStatus (), BatchStatus.COMPLETED); }

8.2. Teče

Slijed koraka također se može uvrstiti u a teći. Kad je tok završen, cijeli je tok koji prelazi u izvršni element. Također, elementi unutar protoka ne mogu prijeći na elemente izvan protoka.

Recimo, možemo izvršiti dva koraka unutar protoka, a zatim taj prijelaz protoka prebaciti u izolirani korak:

I još uvijek možemo vidjeti svako izvršenje koraka neovisno:

@Test javna praznina givenFlow_thenBatch_CompleteWithSuccess () baca izuzetak {// ... pokreni posao i pričekaj završetak assertEquals (3, jobOperator.getStepExecutions (ExecuId) .size ()); assertEquals (jobExecution.getBatchStatus (), BatchStatus.COMPLETED); }

8.3. Odluke

Također imamo podršku if / else u obliku odluke. Odluke pružaju prilagođeni način određivanja slijeda među koracima, tijekovima i dijeljenjima.

Poput koraka, radi na prijelaznim elementima kao što su Sljedeći koji može usmjeriti ili prekinuti izvršenje posla.

Pogledajmo kako se posao može konfigurirati:

Bilo koji odluka element treba konfigurirati s klasom koja implementira Odlučitelj. Njegov posao je vratiti odluku kao Niz.

Svaki Sljedeći iznutra odluka je poput a slučaj u sklopka izjava.

8.4. Podjele

Podjele su zgodni jer nam omogućuju istodobno izvršavanje tokova:

Naravno, to znači da narudžba nije zajamčena.

Potvrdimo da još uvijek svi trče. Koraci protoka izvodit će se proizvoljnim redoslijedom, ali izolirani korak uvijek će biti zadnji:

@Test javna praznina givenSplit_thenBatch_CompletesWithSuccess () baca izuzetak {// ... pokretanje posla i čekanje završetka Popis stepExecutions = jobOperator.getStepExecutions (ExecuId); assertEquals (3, stepExecutions.size ()); assertEquals ("splitJobSequenceStep3", stepExecutions.get (2) .getStepName ()); assertEquals (jobExecution.getBatchStatus (), BatchStatus.COMPLETED); }

9. Podjela posla

Također možemo konzumirati svojstva serije unutar našeg Java koda koja su definirana u našem poslu.

Mogu se obuhvatiti na tri razine - posao, korak i šaržni artefakt.

Pogledajmo nekoliko primjera kako su ih konzumirali.

Kada želimo potrošiti svojstva na razini posla:

@Inject JobContext jobContext; ... jobProperties = jobContext.getProperties (); ...

To se može potrošiti i na stupnjevitoj razini:

@Inject StepContext stepContext; ... stepProperties = stepContext.getProperties (); ...

Kada želimo potrošiti svojstva na razini batch-artefakta:

@Inject @BatchProperty (name = "name") private String nameString;

To dobro dođe kod particija.

Vidite, dijeljenjem možemo istodobno pokretati tokove. Ali možemo i mi pregrada korak u n skupove stavki ili postavite zasebne ulaze, omogućujući nam još jedan način da podijelimo rad na više niti.

Da bismo shvatili segment posla koji bi trebala raditi svaka particija, možemo kombinirati svojstva s particijama:

10. Zaustavite se i ponovo pokrenite

To je to za definiranje poslova. Sada razgovarajmo na trenutak o upravljanju njima.

Već smo vidjeli u našim jediničnim testovima da možemo dobiti primjerak JobOperator iz BatchRuntime:

JobOperator jobOperator = BatchRuntime.getJobOperator ();

A onda možemo započeti posao:

Duga izvedbaId = jobOperator.start ("simpleBatchlet", nova svojstva ());

Međutim, također možemo zaustaviti posao:

jobOperator.stop (execuId);

I na kraju, možemo ponovno pokrenuti posao:

izvršenje = posaoOperator.restart (izvršenje, nova svojstva ());

Pogledajmo kako možemo zaustaviti tekući posao:

@Test javna praznina givenBatchLetStarted_whenStopped_thenBatchStopped () baca izuzetak {JobOperator jobOperator = BatchRuntime.getJobOperator (); Dugo izvršenje = posaoOperator.start ("simpleBatchLet", nova svojstva ()); JobExecution jobExecution = jobOperator.getJobExecution (ExecuId); jobOperator.stop (execuId); jobExecution = BatchTestHelper.keepTestStopped (jobExecution); assertEquals (jobExecution.getBatchStatus (), BatchStatus.STOPPED); }

A ako je serija ZAUSTAVLJENO, onda ga možemo ponovno pokrenuti:

@Test javna praznina givenBatchLetStopped_whenRestarted_thenBatchCompletesSuccess () {// ... pokretanje i zaustavljanje posla assertEquals (jobExecution.getBatchStatus (), BatchStatus.STOPPED); executid = jobOperator.restart (jobExecution.getExecutionId (), nova svojstva ()); jobExecution = BatchTestHelper.keepTestAlive (jobOperator.getJobExecution (ExecuId)); assertEquals (jobExecution.getBatchStatus (), BatchStatus.COMPLETED); }

11. Dohvaćanje poslova

Kad se tada podnosi batch posao batch runtime stvara instancu Izvršenje posla da ga prati.

Da biste dobili Izvršenje posla za ID izvršenja možemo koristiti JobOperator # getJobExecution (ExecuId) metoda.

I, StepExecution pruža korisne informacije za praćenje izvršenja koraka.

Da biste dobili StepExecution za ID izvršenja možemo koristiti JobOperator # getStepExecutions (ExecuId) metoda.

I iz toga možemo dobiti nekoliko mjernih podataka o koraku putem StepExecution # getMetrics:

@Test javna praznina givenChunk_whenJobStarts_thenStepsHaveMetrics () baca iznimku {// ... pokretanje posla i čekanje završetka assertTrue (jobOperator.getJobNames (). Contains ("simpleChunk")); assertTrue (jobOperator.getParameters (ExecuId) .isEmpty ()); StepExecution stepExecution = jobOperator.getStepExecutions (ExecuId) .get (0); Karta metricTest = BatchTestHelper.getMetricsMap (stepExecution.getMetrics ()); assertEquals (10L, metricTest.get (Metric.MetricType.READ_COUNT) .longValue ()); assertEquals (5L, metricTest.get (Metric.MetricType.FILTER_COUNT) .longValue ()); assertEquals (4L, metricTest.get (Metric.MetricType.COMMIT_COUNT) .longValue ()); assertEquals (5L, metricTest.get (Metric.MetricType.WRITE_COUNT) .longValue ()); // ... i još mnogo toga! }

12. Mane

JSR 352 je moćan, iako mu nedostaje na brojnim područjima:

  • Čini se da nedostaje čitatelja i pisaca koji mogu obrađivati ​​druge formate poput JSON-a
  • Ne postoji podrška za generičke lijekove
  • Particioniranje podržava samo jedan korak
  • API ne nudi ništa što bi podržalo raspoređivanje (iako J2EE ima zasebni modul za raspoređivanje)
  • Zbog svoje asinkrone prirode, testiranje može biti izazov
  • API je prilično opširan

13. Zaključak

U ovom smo članku pogledali JSR 352 i saznali više o komadima, batchletima, podjelama, protocima i još mnogo toga. Ipak, jedva da smo ogrebali površinu.

Kao i uvijek, demo kod možete pronaći na GitHub-u.