Spring Batch Mimarisi ve Veri İşleme Stratejileri

Giriş
Yazılım projelerinin karmaşıklığı arttıkça, büyük veri setleri üzerinde belirli iş kurallarını periyodik olarak uygulayan yapılara ihtiyaç duyulmaktadır. Bu amaca hizmet etmek üzere geliştirilen Spring Batch, ek olarak görevleri paralel yürütebilir, durdurup yeniden devam ettirebilir ve hata yönetimini sağlayabilir. Spring Batch, bu gibi karmaşıklıklarla başa çıkabilen, güçlü ve kurumsal ölçekte çözüm sunan tek açık kaynak yazılım çerçevesi (framework) olarak kendini konumlandırmaktadır [1].
Spring Batch, kendi içinde bir zamanlama (scheduling) çerçevesi değildir. Ancak, bu eksikliği dış kaynaklı zamanlayıcılarla birlikte çalışma yeteneği ile giderir. Spring Scheduler, Quartz, Tivoli, Control-M gibi zamanlayıcılarla entegre çalışacak şekilde tasarlanmıştır. Bu sayede, işlemleri belirli aralıklarla çalıştırmak veya özel zamanlama ihtiyaçlarına uyum sağlamak mümkün hale gelir.
Yazılım projelerindeki veri işleme görevlerini daha etkili bir şekilde yönetmek isteyen geliştiriciler için Spring Batch, güvenilir ve esnek bir çözüm olarak öne çıkmaktadır.
Spring Batch Mimarisi
Spring Batch, işlevsel, esnek ve genişletilebilir bir mimariye sahiptir. Toplu işleme uygulamalarını geliştirirken, geliştiricilere iş mantığına odaklanmalarını ve altyapıyı Spring Batch’e bırakmalarını sağlar. Bu sayede, karmaşık toplu işlemleri daha kolay ve düzenli bir şekilde yönetmek mümkün olur.
- Görevleri (Job),
- Görevlerin adımlarını (Step),
- Görevlerden periyodik olarak türeyen örnekleri (JobInstance) ve
- Örneklerin de başarılı veya başarısız her bir tetiklenme işlemini (JobExecution), ayrı ayrı varlıklarda tutan Spring Batch’in kullanımı buna bağlı olarak oldukça esnektir.
Kendini kanıtlamış yapısı ile yıllardır önemli projelerde kullanılan Spring Batch mimarisinin basitleştirilmiş bir çizimi Şekil 1’de görülmektedir.

Şekil 1. Spring Batch Alan Modeli
Spring Batch Genel Kavramları
Spring Batch çerçevesindeki genel kavramları kısaca açıklamak gerekirse;
- Job:
- Yığın (batch) işleminin tamamını kapsayan varlıktır. Job ismi, step bilgileri ve yeniden başlatılabilir olup olmaması bilgilerini içerir. Java bean’i veya xml formatında tanımlanabilir.
- JobInstance:
- Bir Job’ın her bir çalıştırılmasında oluşan varlıktır. Örnek olarak her gün çalışması gereken bir görev için, her bir günde ayrı ayrı çalışan JobInstance’lar bulunur.
- JobParameters:
- Bir görevi başlatmak için kullanılan parametreleri içerir.
- JobExecution:
- Bir JobInstance’ın her bir çalışması için kullanılan terimdir. Bir JobExecution başarılı veya başarısız sonuçlanabilir. JobExecution başarısız sonuçlandığında yeniden tetiklenen JobInstance’dan yeni bir JobExecution oluşturulur. JobExecution’da; status, startTime, endTime gibi bilgiler bulunur.
- Step:
- Bir görevdeki bağımsız ve ardışık aşamaların her biridir. Görevin tanımlanması ve kontrol edilmesi için gerekli olan tüm bilgileri içerir.
- StepExecution:
- Tıpkı JobExecution gibi her bir Step çalıştığında bir StepExecution oluşur. StepExecution’da; status, startTime, endTime gibi bilgiler bulunur.
- ExecutionContext:
- Her bir StepExecution veya JobExecution nesnesine özgü kalıcı durumu, anahtar/değer (key/value) çiftlerinden oluşan veri şeklinde depolayan varlıktır.
- JobRepository:
- Yukarıda bahsedilen her bir varlığın temel veri yönetimi (CRUD) işlemlerini sağlayan mekanizmadır.
- JobLauncher:
- Bir Job’ı verilen JobParameters’ler ile başlatmak için kullanılan arayüzdür (interface).
- ItemReader:
- Step için okunan verinin her seferinde bir tanesinin ele alınmasını sağlayan soyut yapıdır.
- ItemWriter:
- Bir Step’in çıktısının yazılmasını sağlayan soyut yapıdır.
- ItemProcessor:
- ItemReader ile ItemWriter arasında, veriyi dönüştürmek veya diğer iş süreçlerini uygulamak için kullanılan yapıdır.
Spring Batch Job Nasıl Yazılır?
Job, baştan sona yürütülen toplu işlem sürecinin tamamını kapsayan bir yapıdır ve spring-batch-core bağımlılığı tarafından desteklenir.
Temel düzeyde, Job arayüzü implementasyonların Job adını (“getName” metodu) ve Job’ın ne yapması gerektiğini (“execute” metodu) belirtmesi gerekir.
“execute” metodu, JobExecution objesini parametre olarak alır. JobExecution, Job’ın runtime’da yürütülmesini temsil eder. Başlangıç zamanı (start time), bitiş zamanı (end time), çalışma durumu (execution status) gibi runtime ayrıntılarını içerir. Bu bilgiler Spring Batch tarafından metadata repository’de saklanır. Job’ın başarı veya başarısızlık durumu JobExecution status’u incelenerek belirlenmektedir.
1
2
3
4
public interface Job {
String getName();
void execute(JobExecution execution);
}
Spring Batch’in önemli konseptlerinden biri JobRepository’dir. JobRepository, job’lar ve stepler hakkında tüm metadata’ların saklandığı yerdir. JobRepository’nin konfigürasyonu Java configuration aracılığıyla gerçekleşir. JobRepository, persistent store veya in-memory store olabilir. Persistent store (kalıcı depolama), işlemlerin metadata’larını iş bittikten sonra da saklanmasını sağlar. Bu özelliği, analiz, denetim, ve hata ayıklama işlemleri için kritik öneme sahiptir. JobRepository, bir job ilk başlatıldığında JobExecution objesini oluşturur.
1
2
3
4
5
6
@Bean
public Job job(JobRepository jobRepository) {
return new JobBuilder("BillingJob", jobRepository)
.start(step1)
.build();
}
Job Başlatma
Spring Batch’te jobların başlatılması, aşağıdaki arayüzle temsil edilen JobLauncher konsepti aracılığıyla yapılır:
1
2
3
4
5
6
7
8
public interface JobLauncher {
JobExecution run(Job job, JobParameters jobParameters)
throws
JobExecutionAlreadyRunningException,
JobRestartException,
JobInstanceAlreadyCompleteException,
JobParametersInvalidException;
}
“run” metodu, belirli bir Job’ı, JobParameters ile başlatmak için tasarlanmıştır.
Spring Batch kullanıma hazır bir uygulama sağladığından JobLauncher arayüzünü neredeyse hiçbir zaman kendi başınıza uygulamanıza gerek kalmayacaktır. JobLauncher, JobRepository ve Job’ın birbirleriyle nasıl etkileşime girdiği Şekil 2’de gösterilmektedir.

Şekil 2. JobLauncher, JobRepository ve Job Etkileşimi
Komut Satırı Arayüzü (Command Line Interface - CLI)
Spring Batch işlerini CLI üzerinden başlatmak, özellikle zamanlanmış işlemler veya veri işleme görevleri için kullanışlıdır. Spring Boot’un sağladığı destekle, bir Spring Batch uygulaması komut satırı argümanları ile kolayca yapılandırılabilir ve başlatılabilir. CLI üzerinden iş başlatmak, uygulamaların daha esnek bir şekilde yönetilmesini sağlar.
Örneğin, bir Spring Batch işini komut satırından şu şekilde başlatabilirsiniz:
1
java -jar my-spring-batch-app.jar --job.name=myJob param1=val1 param2=val2
Bu komut, myJob isimli job’ı param1 ve param2 parametreleri ile çalıştırır.
Spring Batch job’ınızı komut satırından çalıştırırken, application.properties veya application.yml dosyalarında tanımlanmış yapılandırma değerlerini de geçersiz kılabilirsiniz. Bu, farklı ortamlar arasında uygulamanızı kolayca özelleştirmenize olanak tanır.
Web Konteyner (Container)
Spring Batch uygulamaları, bir web konteyner içinde çalıştırılarak, HTTP üzerinden işlemlerin yönetilmesine olanak tanır. Bu, özellikle dinamik web uygulamaları ve mikroservis mimarileri ile entegrasyon için yararlıdır. Spring Batch işlerini bir web konteynerde çalıştırarak, RESTful API’ler aracılığıyla iş başlatma, durum sorgulama, işlem sonuçlarını almak gibi işlemleri gerçekleştirilebilir.
Spring Boot ve Spring Batch’in entegrasyonu, bu süreci kolaylaştırır. Spring Boot gibi web konteynerleri sağlar ve Spring Batch işlerinin web üzerinden yönetilmesine olanak tanır. REST API’ler aracılığıyla iş yönetimi, modern uygulama geliştirme pratikleri ile uyumludur ve uygulamaların ölçeklenmesine yardımcı olur.
Job Instance
Bir Job bir kez tanımlanabilir, ancak genellikle belirli bir schedule’a göre birçok kez çalıştırılacaktır. Spring Batch’te Job, geliştirici tarafından belirtilen batch process’in genel tanımıdır. JobInstance, Job’ın eşsiz olarak parametrelendirilmesidir.
Örneğin, her günün sonunda veya belirli bir dosya mevcut olduğunda bir kez yürütülmesi gereken bir batch process düşünün. Günde bir kez senaryosunda, GunSonuIslemleriJob’ı oluşturmak için Spring Batch’i kullanabiliriz. Tek bir GunSonuIslemleriJob’ı tanımı var fakat job’ın günde bir kez olmak üzere birden çok instance’ı oluşur. Her instance belirli bir günün verilerini işleyebilir ve diğer instance’lardan farklı sonuca sahip olabilir. Bu nedenle job’ın her bir instance’ının ayrı ayrı izlenmesi gerekmektedir.
Bir JobInstance, parametre açısından diğer JobInstance’lardan farklıdır. Örneğin, bugun.tarih adlı bir parametre belirli bir günü belirtir. Böyle bir parametreye JobParameter adı verilir. JobParameters, bir JobInstance’ını diğerinden ayıran varlıktır. JobParameters’ın JobInstances’ı nasıl tanımladığı Şekil 3’te gösterilmektedir.

Şekil 3. Job Parameter Ve Job Instance Arasındaki İlişki
Job Parameter
JobInstance’lar birbirinden JobParameters ile farklıdır. Bu parametreler genellikle belirli bir JobInstance tarafından işlenmesi amaçlanan verileri temsil eder.
JobExecution, JobInstance’ı çalıştırmaya yöneliktir. Her JobInstance birden fazla JobExecution’a sahip olabilir. Job, JobInstance, JobParameters ve JobExecution kavramları arasındaki ilişki Şekil 4’deki diagramda görselleştirilmiştir.

Şekil 4. Spring Batch Görev Hiyerarşisi
Tamamlanan bir JobInstance yeniden başlatılamaz. Batch ile alakalı önemli bir sorun, bir job’ın yeniden başlatıldığındaki davranışıyla ilgilidir. İnsan hatasından dolayı veya teknik sebeplerden dolayı tekrar çalışırsa, JobInstanceAlreadyCompleteException hatası alınır. Spring batch aynı JobInstance’ı ikinci kez çalışmasını önlemektedir.
Belirli bir JobInstance için JobExecution zaten mevcutsa, bu durumda job’ın tekrar başlatılması restart olarak kabul edilir. İdeal olarak tüm job’ların kaldıkları yerden başlayabilmesi gerekir ancak bunun mümkün olmadığı senaryolar da vardır. Bu senaryoda yeni bir JobInstance oluşturulduğundan emin olmak tamamen geliştiricinin sorumluğundadır. Ancak Spring Batch yukarıdaki örnekteki gibi durumlarda yardımcı olmaktadır. Bir job’ın hiçbir zaman yeniden başlatılamaması isteniyorsa Java’da “preventRestart” metodu ile restartable özelliği false olarak ayarlanabilmektedir. Restartable ayarının false olarak ayarlanması “Bu Job tekrar başlatılmayı desteklemiyor.” anlamına gelmektedir. Tekrar başlatılamayan bir Job’ın yeniden başlatılması, JobRestartException hatasının oluşmasına neden olur.
Peki testte yeniden başlatma durumunu nasıl ele alabiliriz?
Aşağıdaki örnek ile inceleyelim:
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
void testJobExecution(CapturedOutput output) throws Exception {
// given
JobParameters jobParameters = new JobParametersBuilder()
.addString("input.file", "/some/input/file")
.toJobParameters();
// when
JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters);
// then
Assertions.assertTrue(output.getOut().contains("processing billing information from file /some/input/file"));
Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
}
Buradaki örnekte test ilk çalıştırıldığında başarıyla geçmesine rağmen ikinci çalıştırmada JobInstanceAlreadyCompleteException hatası atmıştır. Bu kodu düzeltecek olursak ilk aklımıza gelen, @BeforeEach anotasyonu ile her test çalıştırılmadan önce job execution’ı silmek olabilir.
1
2
3
4
@BeforeEach
public void setUp() {
this.jobRepositoryTestUtils.removeJobExecutions();
}
BeforeEach yöntemi kodumuzun tekrar tekrar çalıştırılabilirliğini sağlamaktadır. Fakat daha verimli olan yöntemi bize Spring Batch sağlamıştır. Spring Batch testte JobLauncherTestUtils’i sağlar. JobLauncherTestUtils.getUniqueJobParameters sayesinde rastgele job parametresi generate ettiği için testi tekrar tekrar çalıştırabilirsiniz. Refactor edilen testimiz aşağıdadır:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
void testJobExecution(CapturedOutput output) throws Exception {
// given
JobParameters jobParameters = this.jobLauncherTestUtils.getUniqueJobParametersBuilder()
.addString("input.file", "/some/input/file")
.toJobParameters();
// when
JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters);
// then
Assertions.assertTrue(output.getOut().contains("processing billing information from file /some/input/file"));
Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
}
Step Nedir?
Spring Batch, iş süreçlerini modüler bölümlere ayırmak için Step adı verilen yapıyı kullanır. Bir Job, iş mantığının farklı bölümlerini temsil eden bir veya birden fazla Step’ten oluşabilir. Her Step, toplu işlemi gerçekleştirmek üzere gerekli olan konfigürasyonları, iş mantığını ve kaynakları içerir.
1
2
3
4
public interface Step {
String getName();
void execute(StepExecution stepExecution) throws JobInterruptedException;
}
Job arayüzüne benzer şekilde Step arayüzü, temel düzeyde, step adını (“getName” metodu) ve step’in ne yapması gerektiğini (“execute” metodu) belirtmek için bir uygulamaya ihtiyaç duyar.
“execute” metodu bir StepExecution objesini parametre olarak alır. Start time, end time, execution status gibi runtime detaylarını içerir. Bu runtime bilgisi, daha önce gördüğümüz, JobExecution’a benzer şekilde Spring Batch tarafından metadata repository’sinde saklanır. “execute” methodu, job’ın belirli bir step’te kesilmesi gerekiyorsa bir JobInterruptedException oluşturacak şekilde tasarlanmıştır.
Step Tipleri
- TaskletStep:
- Basit task’lar (bir dosyayı kopyalamak veya arşiv oluşturmak gibi) veya item-oriented task’lar (bir dosyayı veya veri tabanı tablosunu okumak gibi) için tasarlanmıştır.
- PartitionedStep:
- Input data setini partition’lar halinde işlemek için tasarlanmıştır.
- FlowStep:
- Step’leri mantıksal olarak gruplamak için kullanışlıdır.
- JobStep:
- FlowStep’e benzer ancak aslında belirtilen akıştaki step’ler için ayrı bir job execution oluşturur ve başlatır. Bu, karmaşık job ve sub-jobs akışı oluşturmak için kullanışlıdır.

Şekil 5. Spring Batch Step Tipleri
Yığın Tabanlı İşleme (Chunk-Oriented Processing)
Spring Batch, en yaygın uygulamasında chunk-oriented işleme stilini kullanır. Chunk-oriented işleme, verileri birer birer okumayı ve bir işlemde yazılan ‘chunk’ ları oluşturmayı ifade eder. Chunk yapısı ile hangi veri tipinden, tek transaction’da kaç adet veri okunup işleneceği verilir. Chunk’da verilen ilk parametre okunan verinin hangi tipte olduğunu ikinci parametre ise hangi tipte yazılacağını temsil etmektedir. Chunksize ise transaction başına kaç verinin işleneceğini belirler.
Spring Batch Builder’lar ile aşağıdaki örnekte gösterilmektedir:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
public Job sampleJob(JobRepository jobRepository, Step sampleStep) {
return new JobBuilder("sampleJob", jobRepository)
.start(sampleStep)
.build();
}
@Bean
public Step sampleStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("sampleStep", jobRepository)
.<String, String>chunk(10, transactionManager)
.reader(itemReader())
.writer(itemWriter())
.build();
}
- transactionManager:
- Transaction’ları başlatan ve işleten Spring’in PlatformTransactionManager’ıdır.
- repository:
- İşleme sırasında (commit etmeden hemen önce) StepExecution ve ExecutionContext’i periyodik olarak store eden JobRepository’nin Java’ya özgü adıdır.
- chunk:
- Bunun item-based step olduğunu ve transaction gerçekleştirilmeden önce işlenecek item sayısını belirten bağımlılığın Java’ya özgü adıdır.
Step’in kaç defa çalışmasını istediğimizi kontrol etmek için “startLimit” metodunu kullanabiliriz.
1
2
3
4
5
6
7
8
@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("step1", jobRepository)
.<String, String>chunk(10, transactionManager)
.reader(itemReader())
.writer(itemWriter())
.startLimit(1)
}
Tasklet Step
Javada TaskletStep’i oluşturmak için Tasklet arayüzünü uygulamanız (implemente etmeniz) gerekmektedir. TaskletStep oluştururken chunk yoktur. Basit bir Tasklet örneği aşağıdaki gibidir.
1
2
3
4
5
6
@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("filePreparation", jobRepository)
.tasklet(new FilePreparationTasklet(), transactionManager)
.build();
}
Okuma, İşleme ve Yazma
ItemReader
ItemReader, Spring Batch içinde veri okuma işlevini gerçekleştiren bir arayüzdür. Spring Batch, büyük hacimli veri işlemleri sırasında veriyi kaynaklardan (örneğin, veri tabanları, dosyalar, mesaj kuyrukları) okuma işlemini yönetmek için bu arayüzü kullanır. ItemReader, bir veri parçasını veya bir ögeyi okumak için okuma (read) adı verilen tek bir metot sağlar. Bu yönteme yapılan her çağrının teker teker tek bir öge döndürmesi beklenir.
Spring Batch, öge yığınları oluşturmak için gerektiğinde read’i çağıracaktır. Bu şekilde, Spring Batch hiçbir zaman veri kaynağının tamamını belleğe yüklemez, yalnızca veri yığınlarını yükler.
Spring Batch, çeşitli veri kaynaklarından veri okuma işlemlerini gerçekleştirmek üzere bir dizi ItemReader uygulamasıyla birlikte gelir. Spring Batch geliştiricisi olarak, genellikle bu okuyuculardan birini yapılandırmanız ve bir step’te kullanmanız gerekir.
1
2
3
4
5
6
7
8
9
10
@Bean
public FlatFileItemReader<BillingData> billingDataFileReader() {
return new FlatFileItemReaderBuilder<BillingData>()
.name("billingDataFileReader")
.resource(new FileSystemResource("staging/billing-data.csv"))
.delimited()
.names("dataYear", "dataMonth", "accountId", "phoneNumber", "dataUsage", "callDuration", "smsCount")
.targetType(BillingData.class)
.build();
}
Yukarıdaki örnekte, FlatFileItemReader bir CSV dosyasından veri okumak için yapılandırılmıştır. Detaylı olarak bakarsak;
- FlatFileItemReaderBuilder sınıfı, FlatFileItemReader‘ı kolayca yapılandırmak için kullanılır.
- .name(“billingDataFileReader”): Oluşturulan reader’a bir isim atar.
- .resource(new FileSystemResource(“staging/billing-data.csv”)): Okunacak dosyanın kaynağını belirtir. Bu örnekte, dosyanın dosya sistemindeki bir konumdan (staging/billing-data.csv) alınacağını belirtir.
- .delimited(): Dosyanın bir ayraçla ayrılmış olduğunu belirtir.
- .names(“dataYear”, “dataMonth”, “accountId”, “phoneNumber”, “dataUsage”, “callDuration”, “smsCount”): Dosyadaki sütun adlarını belirtir. Her bir ad, BillingData sınıfındaki ilgili alanla eşleştirilir.
- .targetType(BillingData.class): Okunan verinin hangi türde olduğunu belirtir.
- .build(): Belirtilen özelliklere sahip FlatFileItemReader örneğini oluşturur ve döndürür.
ItemProcessor
Ögelerin yığın odaklı bir step’te işlenmesi, verilerin okunması ve yazılması arasında gerçekleşir. Bu kısım yığın odaklı işleme modelinin isteğe bağlı bir aşamasıdır. Spring Batch’te ögelerin işlenmesi, aşağıda tanımlanan ItemProcessor arayüzünün uygulanmasıyla yapılır. ItemProcessor arayüzü, “process” adı verilen tek bir metot sağlar. Verilerin işlenmesi geniş bir terim olduğundan ve verileri dönüştürmek, zenginleştirmek, doğrulamak veya verileri filtrelemek gibi farklı kullanım durumlarını kapsadığından metot ismi geneldir.
- Veri dönüştürme (Transforming Data):
- Okunan her ögeyi belirli bir iş mantığına göre dönüştürme işlemini gerçekleştirir.
- Veri filtreleme (Filtering Data):
- Belirli koşullara uyan ögeleri filtreleme işlemi gerçekleştirilir. Bu sayede, işlemek istenmeyen ögeleri işleme dahil etmeme yeteneğine sahiptir.
- Veri Doğrulama (Validating Data):
- Okunan verinin belirli kurallara uygun olup olmadığını doğrulama işlemini gerçekleştirir. Eğer veri belirli bir kurala uymuyorsa, bu veriyi işleme dahil etmeme veya uygun bir şekilde işleme alma yeteneğine sahiptir.
- Veri Zenginleştirme(Enriching Data):
- Var olan veriye ek bilgiler ekleyerek veya var olan bilgileri geliştirerek veriyi zenginleştirme işlemidir.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ValidatingItemProcessor implements ItemProcessor<Person, Person> {
private EmailService emailService;
public ValidatingItemProcessor(EmailService emailService) {
this.emailService = emailService;
}
public Person process(Person person) {
if (!this.emailService.isValid(person.getEmail()) {
throw new InvalidEmailException("Invalid email for " + person);
}
return person;
}
}
Yukarıda veri doğrulamaya yönelik bir örnek verilmiştir. Bu kodun temel amacı, her bir kişi ögesinin e-posta adresinin geçerli olup olmadığını kontrol etmektir. Bu, özel bir iş mantığı gerektiren bir geçerlilik kontrolüdür ve bu kontrolü sağlamak için EmailService kullanılmıştır. Eğer geçerlilik kontrolü başarısız olursa, bir istisna fırlatılır ve işleme devam edilemez. Eğer geçerlilik kontrolü başarılı olursa, kişi ögesi işlenmiş olarak geri döndürülür ve bu öge bir sonraki aşamada kullanılmak üzere hazır hale gelir.
ItemWriter
ItemWriter, ItemReader tarafından okunmuş verileri ya da ItemProccessor tarafından işlenmiş verileri hedef sistemde kalıcı hale getirmek amacıyla kullanılan bir arayüzdür. Spring Batch ile veri yazma işlemi aşağıdaki gibi tanımlanan ItemWriter arayüzü ile yapılır.
Yazma yöntemi bir yığın öge bekler. Her seferinde bir öge ile yapılan okuma ögelerinin aksine, ögelerin yazılması yığınlar halinde yapılır. Bunun nedeni toplu yazma işlemlerinin genellikle tek yazma işlemlerine göre daha verimli olmasıdır.
Spring Batch, dosyalar, veri tabanları ve mesaj aracıları gibi çeşitli hedeflere veri yazmak için ItemWriter arayüzünün çeşitli uygulamalarını sağlar.
1
2
3
4
5
6
7
8
9
@Bean
public JdbcBatchItemWriter<BillingData> billingDataTableWriter(DataSource dataSource) {
String sql = "insert into BILLING_DATA values (:dataYear, :dataMonth, :accountId, :phoneNumber, :dataUsage, :callDuration, :smsCount)";
return new JdbcBatchItemWriterBuilder<BillingData>()
.dataSource(dataSource)
.sql(sql)
.beanMapped()
.build();
}
Yukarıdaki kod parçasında JdbcBatchItemWriter’ı kullanarak, BillingData türündeki verileri BILLING_DATA adlı bir tabloya eklemek için kullanılır. Detaylı olarak bakacak olursak;
- JdbcBatchItemWriterBuilder sınıfı, JdbcBatchItemWriter‘ı kolayca yapılandırmak için kullanılır.
- .dataSource(dataSource): JdbcBatchItemWriter’ın veri tabanına erişim için kullanacağı kaynaktır.
- .sql(sql): Veri tabanına gönderilecek SQL sorgusunu belirtir.
- .beanMapped(): BillingData sınıfındaki alanlar(field) ile BILLING_DATA tablosundaki sütun isimleri otomatik olarak eşleştirmek için kullanılmıştır.
- .build(): Belirtilen özelliklere sahip JdbcBatchItemWriter örneğini oluşturur ve döndürür.
Hata Yönetimi
Büyük veri seti üzerinde çalışan bir job, okuma (read), işleme (process) ve yazma (write) step’lerinden bir kaçını veya hepsini içermektedir. Bu step’lerde gerek yazılımsal/sistemsel gerek verisel hatalarla karşılaşma olasılığı her zaman mevcuttur ve göz önünde bulundurulması gerekmektedir. Spring Batch bu gibi durumları kontrol etmemizi sağlayan özellikler sunmaktadır.
Retry
Spring Batch daha çok geçici hataları (ağ problemleri, veri tabanına erişememe vb.) kontrol etmek için yeniden deneme (retry) özelliği sunmaktadır. Okuma adımı hariç chunk-based yazma ve işleme adımlarında yeniden deneme özelliği kullanılmaktadır. Bu özelliğin kullanılabilmesi için ilgili adım oluşturulurken “faultTolerant” metodunun da çağrılması gerekmektedir. Böylece hata toleransı olan bir step oluşturulmaktadır.
Aşağıdaki gibi bir step oluşturulduğunda ve bu step’te CalculationException türünde bir hata alınırsa 100 kere yeniden deneme yapıldıktan sonra hala aynı hata alınırsa ilgili görev başarısız olacaktır. “retry” metoduna alınacak hata, “retryLimit” metoduna da kaç kere yeniden deneneceği bilgisi geçilmektedir.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
public Step adım(JobRepository jobRepository, JdbcTransactionManager transactionManager,
ItemReader<Stok> stokTablosunuOku,
ItemProcessor<Stock, Rapor> stoklariIsle,
ItemWriter<Rapor> stoklariDosyayaYaz) {
return new StepBuilder("raporOlusturma", jobRepository)
.<Stock, Rapor>chunk(100, transactionManager)
.reader(stokTablosunuOku)
.processor(stoklariIsle)
.writer(stoklariDosyayaYaz)
.faultTolerant()
.retry(CalculationException.class)
.retryLimit(100)
.build();
}
- RetryListener:
- Bu arayüz “open”, “close” , “onSuccess”, “onError” metotlarını içermektedir. Yeniden deneme yaparken hata alınması durumda geliştiriciler tarafında alınacak aksiyonları yazmak için kullanılmaktadır. Oluşturacağımız step’te “listener(CustomRetryListener.class)” metoduna bu arayüzü uygulayan sınıf geçilmektedir.

Şekil 6. RetryListener Arayüzü
- RetryPolicy:
- RetryPolicy arayüzü “open”, “close”, “canRetry”, “registerThrowable” isminde 4 metot bulundurmaktadır. Bu arayüz kullanılarak özel policy sınıfları oluşturulup, step oluşturma aşamasında “retryPolicy” metoduna geçilmektedir. Hâlihazırda maxAttemptsRetryPolicy, TimeoutRetryPolicy gibi birçok özel policy sınıfları bulunmaktadır.
Skip
Spring Batch, görev çalışırken alınacak spesifik hatalarda görevin başarısız olmadan bazı kayıtların atlanılmasına olanak sağlamaktadır.
- SkipPolicy:
- SkipPolicy fonksiyonel arayüzde “shouldSkip” isminde bir metot bulunmaktadır. Bu metodun iki parametresi vardır.
1
boolean shouldSkip(Throwable t, long skipCount)
İlk parametreye spesifik bir hata geçilmektedir. İkinci parametrede ise atlama sayısı verilmektedir. Böylece karşılaşabileceğimizi ön gördüğümüz hataları alacağımız step için, bu arayüzün metodunun bulunduğu bir sınıf oluşturulur, hata ile ilgili alınacak aksiyonları bu metot içerisinde gerçekleştirilerek “skipPolicy” metoduna bu sınıfı geçebiliriz. Ayrıca atlanılan veri sayısı skipCount’dan büyük olduğunda metot false dönerek job başarısız olacaktır.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class HataliVeriSkipper implements SkipPolicy {
private static final Logger logger = LoggerFactory.getLogger("hataliVeriLogger");
@Override
public boolean shouldSkip(Throwable exception, int skipCount) throws SkipLimitExceededException {
if (exception instanceof FlatFileParseException) {
FlatFileParseException hata = (FlatFileParseException) exception;
StringBuilder hataMesaji = new StringBuilder();
hataMesaji.append("Stok verileri alınırken hata oluştu. " + hata.getLineNumber()
+ " satırındaki veri hatalı. Hatalı veri: " + "\n");
hataMesaji.append(hata.getInput() + "\n");
logger.error("{}", hataMesaji.toString());
return true;
} else {
return false;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
public SkipPolicy hataliVeriSkipper() {
return new HataliVeriSkipper();
}
@Bean
public Step adım() {
return stepBuilderFactory.get("dosyadanVeriAlma").chunk(10000)
.reader(stokDosyasiOku())
.faultTolerant()
.skipPolicy(hataliVeriSkipper())
.processor(stoklariIsle())
.writer(stoklariTabloyaYaz())
.build();
}
- SkipListener:
- SkipListener arayüzünde “onSkipInRead”, “onSkipInWrite”, “onSkipInProcess” isimlerinde 3 metot bulunmaktadır. Okuma, işleme veya yazma adımlarından birinde alınacak spesifik hatalar için, bu arayüzün ilgili metodunun yer aldığı bir sınıf oluşturularak, metot içerisinde kayıt ile ilgili loglama vesaire gibi işlemler yapılır. Daha sonra kayıt atlanılarak bir sonrakine geçiş yapılır.
Aşağıdaki örnekte bir dosyada bulunan stok verileri ile SkipListener arayüzü kullanılarak bir sınıf oluşturulmuş ve kayıtlar okunurken FlatFileParseException türünde bir hata alındığında kayıtla ilgili bilgiler loglanmıştır.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class HataliVeriSkipListener implements SkipListener<Stok, Stok> {
private static final Logger logger = LoggerFactory.getLogger("hataliVeriLogger");
@Override
public void onSkipInRead(Throwable throwable) {
if (throwable instanceof FlatFileParseException exception) {
String hataliKayit = exception.getInput();
int satirNumarasi = exception.getLineNumber();
StringBuilder hataMesaji = new StringBuilder();
hataMesaji.append("Stok verileri alınırken hata oluştu. " + satirNumarasi
+ " satırındaki veri hatalı. Hatalı veri: " + "\n");
hataMesaji.append(hataliKayit + "\n");
logger.error("{}", hataMesaji.toString());
}
}
}
Daha sonra dosyadanVeriAlma isimli step oluşturulurken “skip” metoduna alınacak hata, “skipLimit” metoduna atlanılacak kayıt sayısı ve “listener” metoduna da yazılan listener sınıfı geçilir.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Bean
@StepScope
public HataliVeriSkipListener hataliVeriAtlama() {
return new HataliVeriSkipListener();
}
@Bean
public Step adım(JobRepository jobRepository, JdbcTransactionManager transactionManager,
ItemReader<Stok> stokDosyasiOku,
ItemWriter<Stok> stoklariTabloyaYaz,
HataliVeriSkipListener hataliVeriSkipListener) {
return new StepBuilder("dosyadanVeriAlma", jobRepository)
.<Stok, Stok>chunk(100, transactionManager)
.reader(stokDosyasiOku)
.writer(stoklariTabloyaYaz)
.faultTolerant()
.skip(FlatFileParseException.class)
.skipLimit(10)
.listener(hataliVeriSkipListener)
.build();
}
Repeat
Oluşturucağımız taskletlerin “execute” metodu RepeatStatus dönmektedir. RepeatStatus yinelenen işlemin bitip bitmediğini belirtmek için kullanılır. FINISHED VE CONTINUABLE olmak üzere iki değeri bulunmaktadır. FINISHED işlemin devam etmeyeceğini belirtir. CONTINUABLE ise işlemin tekrar yapılacağını belirtir. Büyük veri seti üzerinde çalışırken, tasklet içerisinde yazılan business logic’in ne kadar veri için tekrarlanabileceği belirlenebilmektedir.
Aşağıdaki örnekte 200 verinin olduğu bir yığın üzerinde dosya oluşturma işleminin bir kere yapılması sağlanmıştır.
1
2
3
4
5
6
7
8
9
10
11
12
public class FilePreparationTasklet implements Tasklet {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
JobParameters jobParameters = contribution.getStepExecution().getJobParameters();
String inputFile = jobParameters.getString("input.file");
Path source = Paths.get(inputFile);
Path target = Paths.get("files", source.toFile().getName());
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
return RepeatStatus.FINISHED;
}
}
Spring Batch ve Alternatif Teknolojileri
Spring çerçevesinin (framework) bir uzantısı olan Spring Batch, yığın işleme (batch processing) için kapsamlı bir araç seti sağlar.
Spring Batch, batch processing için bazı güçlü özelliklere sahiptir:
- Spring Ekosistemi ile Entegrasyon:
- Bağımlılık enjeksiyonu (Dependency Injection) ve işlem yönetimi(Transaction Management) gibi özelliklerden yararlanarak diğer Spring projeleriyle sorunsuz bir şekilde bütünleşir.
- Sağlamlık:
- Yeniden başlatılabilirlik, atlama (skip) ve yeniden deneme (retry) işlevleri aracılığıyla hataya dayanıklı toplu işleme sunar.
- Genişletilebilirlik:
- Çeşitli toplu işleme bileşenleri için özel uygulamalara ve uzantılara izin verir.
- İzleme ve Yönetim:
- Toplu işleri izlemek ve Spring Boot Actuator gibi araçlar aracılığıyla yürütmeyi yönetmek için destek sağlar.
Spring Batch Alternatifleri
Spring Batch’e alternatif olan bazı teknolojiler şu şekildedir:
JobRunr:
JobRunr, uygulamalara gömülebilen ve Java 8 lambda kullanarak arka plan işlerini planlamaya olanak tanıyan bir kütüphanedir. JobRunr başarısız işler için geri çekilme (back-off) politikasına sahip bir otomatik yeniden deneme (retry) özelliği içerir. Ayrıca tüm işleri izlemeye olanak tanıyan yerleşik bir kontrol paneli de vardır. JobRunr kendi kendini yönetir: Başarılı işler yapılandırılabilir bir sürenin ardından otomatik olarak silinir, dolayısıyla manuel olarak depolama temizliği yapılmasına gerek yoktur. Arka planda bazı uzun süreli (long-running) görevler çalıştırılmak istendiğinde Spring Batch veya Quartz kullanılabilir, ancak bu çerçeveler kullanıcıyı özel arayüzler oluşturmaya zorlar ve çok fazla yük getirirler. JobRunr çerçevesinin faydaları [2]:
- Kolay (Easy):
- Yeni bir çerçeve öğrenmeye gerek yoktur. Yalnızca Java 8 kullanılabilir.
- Basit (Simple):
- Özel arayüzler uygulamaya veya soyut sınıfları genişletmeye gerek olmadığından kullanıcıların kodu neredeyse JobRunr’dan bağımsızdır. Mevcut servisler hiçbir değişiklik yapmadan yeniden kullanılabilir.
- Hata Dayanıklı (Fault-Tolerant):
- Uygulamada kullanılan bir servisin çökmesi veya disklerin dolması gibi aksilikler olabilir. JobRunr bir istisnayla (exception) karşılaşırsa varsayılan olarak arka plan işini üstel bir geri çekilme politikasıyla yeniden planlar. Arka plan işi on kez başarısız olmaya devam ederse Başarısız (Failed) durumuna geçecektir. Başarısız olan iş daha sonra yeniden kuyruğa (requeue) alınabilir.
- Şeffaf (Transparent):
- JobRunr, işleri izlemeye olanak tanıyan yerleşik bir gösterge panosu içerir. Tüm arka plan işlerine genel bir bakış sunar ve her işin durumu ayrıntılı olarak gözlemlenebilir.
- Dağıtık (Distributed):
- Lambda veya arka plan işi kalıcı depolamaya serileştirildikçe (serialization), arka plan işlemi JVM sınırlarının üzerinden geçebilir ve bu, dağıtık işleme izin verir.
Easy-Batch:
Easy Batch, Java ile toplu işlemeyi basitleştirmeyi amaçlayan bir çerçevedir. Basit, tek görevli ETL (Çıkarma, Dönüştürme, Yükleme) işleri için özel olarak tasarlanmıştır. Batch uygulamaları yazmak çok sayıda basmakalıp (boilerplate) kod gerektirir: okuma, yazma, filtreleme, verileri ayrıştırma ve doğrulama, günlüğe kaydetme (logging), raporlama bunlardan birkaçıdır. Buradaki amaç, geliştiriciyi bu uğraştırıcı görevlerden kurtarmak ve yığın uygulamasının mantığına odaklanmaya olanak sağlamaktır.
Spring Batch büyük ölçekli işler için tasarlanmıştır. Bu tür işleri sıfırdan yeniden başlatmak verimli değildir. Bu nedenle, başarısızlık durumunda kaldığı yerden yeniden başlamak için varsayılan olarak iş durumunu sürdürmek mantıklıdır. Easy Batch, küçük ve basit ETL işlerini hedeflemektedir. Bu işler çoğu durumda bağımsız olacak şekilde tasarlanabilir. Bu tür işler herhangi bir sorun yaşanmadan başarısız olursa sıfırdan yeniden başlatılabilir. Bu durumlarda iş durumunu varsayılan olarak sürdürmemeye yönelik tasarım seçeneği anlamlıdır. Sonuç olarak bu iki framework karşılaştırıldığında, uygulamanız arıza durumunda yeniden deneme (retry on failure) veya uzaktan iletişim (remoting) gibi gelişmiş özellikler gerektiriyorsa Spring Batch tercih edilebilir. Tüm bu gelişmiş özelliklere ihtiyaç yoksa Easy Batch, toplu uygulama geliştirmelerini basitleştirmek için çok kullanışlı olabilir [3].
Diğer Alternatif Yığın İşleme Teknolojileri:
Spring Batch, JVM üzerinde geleneksel kurumsal (enterprise) yığın işlemleri gerçekleştirmek üzere tasarlanmıştır. Kurumsal yığın işlemlerde yaygın olarak kullanılan kalıpları uygulamak ve bunları JVM çerçevesinde uygun hale getirmek için kullanılır. Aşağıda belirtilen Spark gibi diğer teknolojiler ise büyük veri ve makine öğrenimi kullanım durumları için tasarlanmıştır.
- Apache Spark:
- Işık hızında bellek içi işlemesiyle bilinen Spark, toplu işleme, gerçek zamanlı akış, makine öğrenimi ve grafik işleme için birleşik bir motor sunmaktadır. Zengin ekosistemi ve çeşitli veri kaynaklarına yönelik desteği, onu çeşitli büyük veri işleme gereksinimlerine uygun hale getirmektedir. Spark, dağıtık bir ortamda belirli kullanım durumları için özel bir altyapı gerektirir. Spring Batch herhangi bir altyapıya dağıtılabilir. Spark ekosistemi büyük veri kullanım senaryoları için tasarlanmıştır. Bu nedenle, okuma ve yazma için sunduğu bileşenler de buna odaklanmıştır. Spring Batch ise bildirimsel girdi ve çıktı için eksiksiz bir bileşen paketi sağlar. Yeniden başlatma gibi durum yönetimi işlemleri Spark’ta Spring Batch’ten çok daha ağırdır.
- Apache Beam:
- Apache Beam, hem yığın hem de akış işleme için birleşik bir programlama modeli sağlar. Beam’in taşınabilirliğe ve birleşik bir modele odaklanması, onu farklı işleme arka uçlarında esneklik gerektiren senaryolar için önemli bir seçim haline getirmektedir.
- Apache Flink:
- Flink, yığın işlemeyi de destekleyen güçlü bir akış işleme çerçevesidir. Düşük gecikme süreli, yüksek verimli işlemeye verdiği önem, onu gerçek zamanlı analizler ve karmaşık olay işleme için uygun hale getirir. Flink’in gelişmiş pencereleme ve durum yönetimi yetenekleri, onu hem akışa hem de yığın işleme ihtiyaç duyan uygulamalar için sağlam bir seçim haline getirmektedir.
- AWS Data Pipeline and Google Cloud Dataflow:
- Bu bulut tabanlı hizmetler, sırasıyla AWS ve Google Cloud ortamlarında yığın işleme iş akışlarının düzenlenmesi ve yürütülmesi için yönetilen çözümler sunar. Ölçeklenebilirlik, güvenilirlik ve bulut hizmetleriyle entegrasyon sağlayarak yığın işlem hatlarının yönetimini kolaylaştırırlar.
Yukarıda değinildiği üzere Spark, Flink ve Beam gibi teknolojiler Spring Batch çerçevesinden farklı olarak büyük veri ve makine öğrenmesi kullanım durumları için tasarlanmıştır. Bu kullanım durumları, farklı zorlukları ve amaçları beraberinde getirdiğinden Spring Batch ile bu teknolojileri karşılaştırmak doğru olmayabilir. Bunun yanında Easy Batch çerçevesi ise Spring Batch çerçevesindeki gelişmiş özelliklere ihtiyaç olmayan yığın işleri için sunulan küçük ve basit bir çerçevedir. Spring Batch ve Easy Batch için yapılan bazı karşılaştırmalar Tablo 1’de gösterilmiştir [4].

Tablo 1. Spring Batch ve Easy Batch Karşılaştırması
Diğer bir karşılaştırma ise Spring Batch ve JobRunr çerçeveleri arasında yapılabilir. Bu iki çerçevenin bazı özellikleri Tablo 2’de karşılaştırılmıştır.

Tablo 2. Spring Batch ve JobRunr Karşılaştırması
Özetle yukarıda bahsedilen teknolojiler farklı kullanım durumlarına hitap etmektedir. Aralarındaki seçim, uygulamanızın özel gereksinimlerine ve mevcut teknoloji yığınına bağlıdır. JobRunr basitliğe vurgu yaparak eş zamansız (asynchronous) görev yürütme ve arka planda işlemeye odaklanır; Easy-Batch ise küçük ETL işlerini hedeflemektedir. Spring Batch, Spring ekosistemi içindeki kapsamlı bir toplu işleme aracıdır ve sağlam, kurumsal ölçekte bir çözüm sağlayan tek açık kaynak çerçevesidir.
Yukarıdaki diğer teknolojiler ile karşılaştırıldığında; verileri eski bir sistemden yenisine taşımak (data migration), veri tabanı teknolojileri arasında geçiş yapmak, ETL işlemleri, karmaşık iş kuralları işlemek ve çeşitli dosya formatlarından okuma ve yazma işlemleri için Spring Batch ideal bir teknolojidir [5].
Spring Scheduler vs Quartz
Yazının başında Spring Batch’in zamanlayıcı (scheduling) özelliğinin olmadığı, zamanlayıcılarla entegre çalışmak üzere tasarlandığından bahsedilmişti. Hangi aracın kullanılacağına karar verilmesine yardımcı olmak amacıyla, bu yazının final bölümünde, iki önemli zamanlayıcı aracı olan Spring Scheduler ve Quartz’ın karşılaştırması sunulacaktır.
Spring Scheduler ve Quartz, Java uygulamalarında batch processing dahil olmak üzere görevleri zamanlamak için popüler seçimlerdir. Her birinin güçlü yönleri ve uygun kullanım durumları vardır.
Spring Scheduler
- Spring Framework ile entegre (Integrated within Spring Framework):
- Spring Scheduler, Spring Framework’ün bir parçasıdır ve @Scheduled gibi açıklamaları (annotation) kullanarak görevleri planlamanın basit bir yolunu sunar.
- Kullanım Kolaylığı (Ease of Use):
- Özellikle Spring tabanlı bir uygulamadaki basit planlama ihtiyaçları için yapılandırması ve kullanımı kolaydır.
- Hafif (Lightweight):
- Hafiftir ve dış bağımlılıklar eklemeden temel planlama gereksinimlerine uygundur.
- Sınırlı Özellikler (Limited Features):
- Bununla birlikte, Spring Scheduler, özellikle karmaşık planlama senaryoları veya işin yürütülmesi üzerinde ayrıntılı kontrol ile uğraşırken, Quartz’ta bulunan bazı gelişmiş özelliklerden yoksun olabilir.
Quartz Scheduler
- Sağlam Planlama (Robust Scheduling):
- Quartz, Cron benzeri ifadeler, takvim tabanlı planlama ve daha fazlasıyla karmaşık planlama senaryolarını işleyebilen güçlü, zengin özelliklere sahip bir zamanlayıcıdır.
- İşin Kalıcılığı (Job Persistence):
- İşin kalıcılığını sağlar; bu, zamanlanmış işlerin uygulamanın yeniden başlatılmasından veya arızalarından kurtulabileceği anlamına gelir.
- Kümeleme ve Ölçeklenebilirlik (Clustering and Scalability):
- Quartz, kümelemeyi destekleyerek yüksek kullanılabilirlik ve ölçeklenebilirlik için zamanlayıcının birden fazla örneğini yönetmeniz gereken dağıtılmış sistemler için uygun hale getirir.
- Gelişmiş Özellikler (Advanced Features):
- Quartz, dinleyiciler, iş zincirleme, hassas planlama için cron ifadeleri ve iş yürütmeyi kontrol etmek için çeşitli yapılandırmalar gibi daha gelişmiş özellikler sunar.
Yukarıdaki maddelerin ışığında; amaç hızlı ve basit bir iş/görev planlama biçimi uygulamaksa Spring Scheduler ideal olacaktır. Öte yandan, JobPersistence desteğinin yanı sıra kümelemeye de ihtiyaç varsa Quartz daha iyi hizmet verebilir [6].
Sonuç
Bu yazıda, Spring Batch’in güçlü özellikleri ve esnek mimarisi üzerinde ayrıntılı bir inceleme gerçekleştirdik. Büyük veri işleme ihtiyaçlarına özel olarak tasarlanan bu çerçeve, işlemlerinizi paralel bir şekilde yönetme yeteneği, hata durumlarına karşı güçlü direnç, ve ölçeklenebilirlik gibi önemli avantajlar sunmaktadır. Bu noktada Spring Batch, veri işleme görevlerinizi düzenli ve güvenilir bir şekilde yönetmenizde size yardımcı olabilir.
Kaynaklar
[1] https://docs.spring.io/spring-batch/docs/current/reference/html/
[2] https://www.jobrunr.io/en/blog/2020-04-08-jobrunr-release/
[3] https://github.com/j-easy/easy-batch
[4] https://github.com/fmbenhassine/easy-batch-vs-spring-batch/issues/1
[5] https://docs.spring.io/spring-batch/docs/4.1.x/reference/html/spring-batch-intro.html#spring-batch-intro
[6] https://khalidsaleem.blogspot.com/2015/03/quartz-scheduler-vs-spring-scheduler.html
[7] https://www.javacodegeeks.com/spring-batch-tutorials
[8] https://spring.academy/courses/building-a-batch-application-with-spring-batch/lessons/introduction
[9] https://terasoluna-batch.github.io/guideline/5.0.0.RELEASE/en/Ch03_ChunkOrTasklet.html