← Kursa Dön
📄 Text · 20 min

Spring Batch ile Toplu Veri İşleme

Giriş — Gece Vardiyası

Bir fabrika düşün. Gündüz müşteriler geliyor, siparişler alınıyor, ürünler satılıyor. Ama fabrikanın bir de gece vardiyası var: gündüz biriken siparişleri paketleme, envanter sayımı, raporlama. Bu işler tek tek yapılmaz — toplu yapılır. Binlerce paketi tek tek değil, konveyör bantla paletler halinde işlersin.

İşte batch processing (toplu veri işleme) tam olarak bu gece vardiyasıdır. Gündüz uygulamanız canlı trafik alırken, batch işler trafiğin düştüğü saatlerde devreye girer: binlerce faturayı oluştur, milyonlarca kaydı raporla, CSV dosyalarını veritabanına aktar.

Batch processing'in karakteristik özellikleri:

  • Büyük hacim: Binlerce, milyonlarca kayıt işlenir

  • İnsan müdahalesi yok: Otomatik başlar, otomatik biter

  • Bölünebilir: Hata olursa kaldığı yerden devam edebilir

  • Zamanlanabilir: Belirli saatlerde, periyodik olarak çalışır

Spring Batch, bu tür işleri yazmak için Java dünyasının standart framework'üdür. Hata yönetimi, restart, chunk processing, parallelism... Tüm bu karmaşıklığı senin yerine yönetir.


Proje Kurulumu

Bu ders Wandbox'ta çalışmaz — local Spring Boot projesi gerekir. [start.spring.io](https://start.spring.io) adresinden Spring Batch, Spring Data JPA, H2 Database, Spring Web dependency'leriyle proje oluştur.

pom.xml:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-batch</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

application.yml:

spring:
  batch:
    jdbc:
      initialize-schema: always    # Batch metadata tablolarını otomatik oluştur
    job:
      enabled: false               # Uygulama başlarken otomatik çalışmasın
  datasource:
    url: jdbc:h2:mem:batchdb
    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

spring.batch.job.enabled=false önemli. Varsayılanda Spring Batch uygulama ayağa kalkarken tüm Job'ları otomatik çalıştırır. Production'da bunu kapatıp job'ları manuel veya schedule ile tetiklersin.


Spring Batch Mimarisi — Konveyör Bant

Spring Batch'in mimarisi fabrika analojisiyle birebir örtüşür:

Job (Gece Vardiyası Planı)
 └── Step 1: CSV'den oku → Dönüştür → DB'ye yaz
 └── Step 2: Rapor oluştur
 └── Step 3: E-posta gönder
BileşenFabrika KarşılığıAçıklama
JobGece vardiyası planıTüm batch işinin tanımı
Stepİş istasyonuJob içindeki her bir adım
ItemReaderHam madde deposuVeriyi kaynaktan okur
ItemProcessorİşleme hattıVeriyi dönüştürür, filtreler
ItemWriterPaketleme/sevkiyatİşlenmiş veriyi hedefe yazar
JobLauncherVardiya amiriJob'u başlatır
JobRepositoryKayıt defteriJob'ların durumunu takip eder

Chunk-Oriented Processing

Spring Batch verileri tek tek değil, chunk (yığın) halinde işler. 10.000 kayıt varsa ve chunk size 100 ise:

  1. Reader 100 kayıt okur → Processor 100 kaydı işler → Writer 100 kaydı yazar → commit

  2. Sonraki 100 kayda geçilir... (100 tekrar)

Bu yaklaşımın avantajları:

  • Bellek verimli: Tüm veri bir anda RAM'e yüklenmez

  • Transaction güvenliği: Hata olursa sadece son chunk geri alınır

  • Restart: Tamamlanan chunk'lar tekrar çalıştırılmaz


ItemReader — Veri Kaynakları

Reader, batch job'un girdi noktası. Önce bir entity tanımlayalım:

@Entity
@Table(name = "employees")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;
    private String lastName;
    private String email;
    private String department;
    private Double salary;

    public Employee() {}

    public Employee(String firstName, String lastName, String email,
                    String department, Double salary) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.department = department;
        this.salary = salary;
    }

    // Getter/Setter'lar
}

FlatFileItemReader — CSV Dosyaları

@Bean
public FlatFileItemReader<Employee> csvReader() {
    return new FlatFileItemReaderBuilder<Employee>()
        .name("employeeCsvReader")
        .resource(new ClassPathResource("employees.csv"))
        .linesToSkip(1)                     // İlk satır header, atla
        .delimited()                         // Virgülle ayrılmış
        .names("firstName", "lastName", "email", "department", "salary")
        .targetType(Employee.class)
        .build();
}

src/main/resources/employees.csv:

firstName,lastName,email,department,salary
Ahmet,Yılmaz,ahmet@example.com,Engineering,15000
Ayşe,Kaya,ayse@example.com,Marketing,12000
Mehmet,Demir,mehmet@example.com,Engineering,16000

JdbcCursorItemReader — Veritabanından Okuma

@Bean
public JdbcCursorItemReader<Employee> jdbcReader(DataSource dataSource) {
    return new JdbcCursorItemReaderBuilder<Employee>()
        .name("employeeJdbcReader")
        .dataSource(dataSource)
        .sql("SELECT first_name, last_name, email, department, salary " +
             "FROM employees WHERE processed = false")
        .rowMapper((rs, rowNum) -> new Employee(
            rs.getString("first_name"),
            rs.getString("last_name"),
            rs.getString("email"),
            rs.getString("department"),
            rs.getDouble("salary")
        ))
        .build();
}

Veritabanı cursor'ı açar ve satır satır okur. Tüm sonucu RAM'e çekmez — büyük veri setlerinde bellek dostu.

JpaPagingItemReader — JPA ile Sayfalı Okuma

@Bean
public JpaPagingItemReader<Employee> jpaReader(
        EntityManagerFactory entityManagerFactory) {
    return new JpaPagingItemReaderBuilder<Employee>()
        .name("employeeJpaReader")
        .entityManagerFactory(entityManagerFactory)
        .queryString("SELECT e FROM Employee e WHERE e.department = :dept")
        .parameterValues(Map.of("dept", "Engineering"))
        .pageSize(50)
        .build();
}

Her seferinde pageSize kadar kayıt getirir. Açık cursor tutmaz — her sayfa ayrı sorgu. Milyonlarca kayıt olan tablolarda daha güvenli.

💡 Hangi Reader Ne Zaman? Dosyadan → FlatFileItemReader. Küçük-orta tablo, hızlı → JdbcCursorItemReader. Büyük tablo, JPA entity var → JpaPagingItemReader. API'den okuma → Custom ItemReader yaz.


ItemProcessor — Dönüştür, Filtrele, Doğrula

Processor, Reader'dan gelen veriyi hedefe yazılmadan önce dönüştürür. null döndürürsen o kayıt filtrelenir — Writer'a ulaşmaz.

Dönüştürme ve Filtreleme

@Bean
public ItemProcessor<Employee, Employee> employeeProcessor() {
    return employee -> {
        // Email formatı kontrolü — geçersizse filtrele
        if (employee.getEmail() == null
                || !employee.getEmail().contains("@")) {
            return null;  // Bu kayıt atlanır
        }

        // %10 zam uygula
        employee.setSalary(employee.getSalary() * 1.10);

        // İsmi normalize et
        employee.setFirstName(employee.getFirstName().toUpperCase());
        employee.setLastName(employee.getLastName().toUpperCase());

        return employee;
    };
}

CompositeItemProcessor — Zincirleme

Birden fazla processor'ı sırayla çalıştırmak istersen:

@Bean
public CompositeItemProcessor<Employee, Employee> compositeProcessor() {
    CompositeItemProcessor<Employee, Employee> composite =
        new CompositeItemProcessor<>();
    composite.setDelegates(List.of(
        validationProcessor(),   // Önce doğrula
        filterProcessor(),       // Sonra filtrele
        salaryProcessor()        // En son dönüştür
    ));
    return composite;
}

ItemWriter — Hedefe Yazma

Writer, chunk halinde çalışır — write() metodu listeyle çağrılır.

JpaItemWriter

@Bean
public JpaItemWriter<Employee> jpaWriter(
        EntityManagerFactory entityManagerFactory) {
    JpaItemWriter<Employee> writer = new JpaItemWriter<>();
    writer.setEntityManagerFactory(entityManagerFactory);
    return writer;
}

Entity'yi veritabanına persist eder. @Id stratejisine göre INSERT veya UPDATE yapar.

JdbcBatchItemWriter

JPA overhead'i istemiyorsan, ham JDBC ile:

@Bean
public JdbcBatchItemWriter<Employee> jdbcWriter(DataSource dataSource) {
    return new JdbcBatchItemWriterBuilder<Employee>()
        .dataSource(dataSource)
        .sql("INSERT INTO employees (first_name, last_name, email, " +
             "department, salary) VALUES (:firstName, :lastName, " +
             ":email, :department, :salary)")
        .beanMapped()
        .build();
}

JDBC batch API'sini kullanır — JPA'ya göre daha hızlıdır çünkü entity lifecycle yönetimi yoktur.

FlatFileItemWriter — Dosyaya Yazma

@Bean
public FlatFileItemWriter<Employee> fileWriter() {
    return new FlatFileItemWriterBuilder<Employee>()
        .name("employeeFileWriter")
        .resource(new FileSystemResource("output/processed-employees.csv"))
        .headerCallback(writer -> writer.write(
            "firstName,lastName,email,department,salary"))
        .delimited()
        .delimiter(",")
        .names("firstName", "lastName", "email", "department", "salary")
        .build();
}

Job ve Step Tanımlama

Reader, Processor ve Writer'ı bir Step içinde birleştirip, Step'leri bir Job altında topluyoruz.

@Configuration
public class EmployeeBatchConfig {

    @Bean
    public Job employeeImportJob(JobRepository jobRepository,
                                  Step csvToDbStep,
                                  Step summaryStep) {
        return new JobBuilder("employeeImportJob", jobRepository)
            .start(csvToDbStep)
            .next(summaryStep)
            .build();
    }

    @Bean
    public Step csvToDbStep(JobRepository jobRepository,
                            PlatformTransactionManager transactionManager,
                            FlatFileItemReader<Employee> reader,
                            ItemProcessor<Employee, Employee> processor,
                            JpaItemWriter<Employee> writer) {
        return new StepBuilder("csvToDbStep", jobRepository)
            .<Employee, Employee>chunk(100, transactionManager)
            .reader(reader)
            .processor(processor)
            .writer(writer)
            .build();
    }

    @Bean
    public Step summaryStep(JobRepository jobRepository,
                            PlatformTransactionManager transactionManager) {
        return new StepBuilder("summaryStep", jobRepository)
            .tasklet((contribution, chunkContext) -> {
                System.out.println("=== Import Tamamlandı ===");
                return RepeatStatus.FINISHED;
            }, transactionManager)
            .build();
    }
}

İki tip Step var:

  • Chunk-oriented Step: Reader → Processor → Writer döngüsü. Büyük veri setleri için.

  • Tasklet Step: Tek seferlik iş. Dosya silme, rapor oluşturma, bildirim gönderme.

Chunk Size Optimizasyonu

Chunk SizeAvantajDezavantaj
Küçük (10-50)Az bellek, sık commitÇok transaction, yavaş
Orta (100-500)Dengeli performansGenel amaçlı iyi seçim
Büyük (1000+)Az transaction, hızlıÇok bellek, büyük rollback

100-500 arasında başla, profiling yaparak optimize et.


Job Parametreleri ve JobLauncher

Her Job çalıştırmasına parametre geçebilirsin. Parametreler Job instance'ını benzersiz kılar — aynı parametrelerle aynı Job tekrar çalıştırılamaz (restart hariç).

@RestController
@RequestMapping("/api/batch")
public class BatchController {

    private final JobLauncher jobLauncher;
    private final Job employeeImportJob;

    public BatchController(JobLauncher jobLauncher,
                           @Qualifier("employeeImportJob") Job job) {
        this.jobLauncher = jobLauncher;
        this.employeeImportJob = job;
    }

    @PostMapping("/run")
    public ResponseEntity<String> runJob(
            @RequestParam(defaultValue = "employees.csv") String fileName) {
        try {
            JobParameters params = new JobParametersBuilder()
                .addString("inputFile", fileName)
                .addLocalDateTime("runTime", LocalDateTime.now())
                .toJobParameters();

            JobExecution execution = jobLauncher.run(
                employeeImportJob, params);

            return ResponseEntity.ok(
                "Job başlatıldı. Status: " + execution.getStatus());
        } catch (Exception e) {
            return ResponseEntity.status(500)
                .body("Job başlatılamadı: " + e.getMessage());
        }
    }
}

Job Parametrelerine Step İçinden Erişim

@Bean
@StepScope  // Her Step çalışmasında yeni instance
public FlatFileItemReader<Employee> csvReader(
        @Value("#{jobParameters['inputFile']}") String inputFile) {
    return new FlatFileItemReaderBuilder<Employee>()
        .name("employeeCsvReader")
        .resource(new ClassPathResource(inputFile))
        .linesToSkip(1)
        .delimited()
        .names("firstName", "lastName", "email", "department", "salary")
        .targetType(Employee.class)
        .build();
}

@StepScope kritik. Bu olmadan #{jobParameters[...]} çalışmaz — bean uygulama başlangıcında oluşturulur, o anda henüz job parametresi yoktur. @StepScope ile bean, Step çalıştığında lazy oluşturulur.


Hata Yönetimi — Skip, Retry, Restart

Batch işlerde milyonlarca kayıt işlenirken bir satır bozuk CSV olabilir, veritabanı bir an erişilemez olabilir. Spring Batch üç temel hata stratejisi sunar.

Skip — Hatalı Kaydı Atla

@Bean
public Step faultTolerantStep(JobRepository jobRepository,
                              PlatformTransactionManager txManager,
                              FlatFileItemReader<Employee> reader,
                              ItemProcessor<Employee, Employee> processor,
                              JpaItemWriter<Employee> writer) {
    return new StepBuilder("faultTolerantStep", jobRepository)
        .<Employee, Employee>chunk(100, txManager)
        .reader(reader)
        .processor(processor)
        .writer(writer)
        .faultTolerant()
        .skip(FlatFileParseException.class)     // Parse hatalarını atla
        .skip(ValidationException.class)         // Validation hatalarını atla
        .skipLimit(50)                           // En fazla 50 kayıt atlanabilir
        .listener(new SkipListener<Employee, Employee>() {
            @Override
            public void onSkipInRead(Throwable t) {
                System.err.println("Okuma hatası, atlandı: "
                    + t.getMessage());
            }
            @Override
            public void onSkipInProcess(Employee item, Throwable t) {
                System.err.println("İşleme hatası: " + item.getEmail());
            }
            @Override
            public void onSkipInWrite(Employee item, Throwable t) {
                System.err.println("Yazma hatası: " + item.getEmail());
            }
        })
        .build();
}

skipLimit(50) demek "50 hataya kadar tolere et, 51. hatada job'u durdur." 10 milyon kayıtta 50 hata kabul edilebilir, 100 kayıtta 50 hata ciddi sorun.

Retry — Geçici Hatalarda Tekrar Dene

Veritabanı bağlantı hatası, network timeout gibi geçici hatalar için:

@Bean
public Step resilientStep(JobRepository jobRepository,
                          PlatformTransactionManager txManager,
                          ItemReader<Employee> reader,
                          ItemProcessor<Employee, Employee> processor,
                          ItemWriter<Employee> writer) {
    return new StepBuilder("resilientStep", jobRepository)
        .<Employee, Employee>chunk(100, txManager)
        .reader(reader)
        .processor(processor)
        .writer(writer)
        .faultTolerant()
        .retry(DeadlockLoserDataAccessException.class)
        .retry(ConnectTimeoutException.class)
        .retryLimit(3)                    // Max 3 deneme
        .skip(ValidationException.class)  // Validation'ı atla
        .skipLimit(100)
        .build();
}

Retry ve skip birlikte kullanılabilir. Retry başarısız olursa, o hata skip kurallarına düşer.

Restart — Kaldığı Yerden Devam

Spring Batch her chunk'ın commit noktasını JobRepository'ye kaydeder. Job dursa bile, aynı parametrelerle tekrar çalıştırıldığında kaldığı yerden devam eder. Bu restart yeteneği için JobRepository'nin kalıcı olması gerekir — H2 in-memory yerine gerçek bir veritabanı kullan.

⚠️ Production'da `initialize-schema: always` kullanma. Batch metadata tablolarını Flyway veya Liquibase migration'larıyla oluştur ve yönet.


Bütünleşik Proje: CSV'den Veritabanına Aktarım

Tüm parçaları bir araya getirelim. Senaryo: employees.csv dosyasını oku, doğrula, dönüştür, veritabanına yaz.

@Configuration
public class EmployeeBatchConfig {

    @Bean
    @StepScope
    public FlatFileItemReader<Employee> csvReader(
            @Value("#{jobParameters['inputFile'] ?: 'employees.csv'}")
            String inputFile) {
        return new FlatFileItemReaderBuilder<Employee>()
            .name("employeeCsvReader")
            .resource(new ClassPathResource(inputFile))
            .linesToSkip(1)
            .delimited()
            .names("firstName", "lastName", "email",
                   "department", "salary")
            .targetType(Employee.class)
            .build();
    }

    @Bean
    public ItemProcessor<Employee, Employee> employeeProcessor() {
        return employee -> {
            if (employee.getEmail() == null
                    || !employee.getEmail().contains("@")) {
                return null;  // Geçersiz email → filtrele
            }
            employee.setDepartment(
                employee.getDepartment().trim().toUpperCase());
            employee.setFirstName(capitalize(employee.getFirstName()));
            employee.setLastName(capitalize(employee.getLastName()));
            return employee;
        };
    }

    @Bean
    public JpaItemWriter<Employee> employeeWriter(
            EntityManagerFactory emf) {
        JpaItemWriter<Employee> writer = new JpaItemWriter<>();
        writer.setEntityManagerFactory(emf);
        return writer;
    }

    @Bean
    public Step importStep(JobRepository jobRepository,
                           PlatformTransactionManager txManager,
                           FlatFileItemReader<Employee> csvReader,
                           ItemProcessor<Employee, Employee> processor,
                           JpaItemWriter<Employee> writer) {
        return new StepBuilder("importStep", jobRepository)
            .<Employee, Employee>chunk(100, txManager)
            .reader(csvReader)
            .processor(processor)
            .writer(writer)
            .faultTolerant()
            .skip(FlatFileParseException.class)
            .skipLimit(10)
            .build();
    }

    @Bean
    public Step summaryStep(JobRepository jobRepository,
                            PlatformTransactionManager txManager,
                            EmployeeRepository repo) {
        return new StepBuilder("summaryStep", jobRepository)
            .tasklet((contribution, chunkContext) -> {
                System.out.println("=== Import Tamamlandı ===");
                System.out.println("Toplam kayıt: " + repo.count());
                return RepeatStatus.FINISHED;
            }, txManager)
            .build();
    }

    @Bean
    public Job employeeImportJob(JobRepository jobRepository,
                                  Step importStep,
                                  Step summaryStep) {
        return new JobBuilder("employeeImportJob", jobRepository)
            .start(importStep)
            .next(summaryStep)
            .build();
    }

    private String capitalize(String str) {
        if (str == null || str.isEmpty()) return str;
        return str.substring(0, 1).toUpperCase()
             + str.substring(1).toLowerCase();
    }
}

Test:

mvn spring-boot:run
curl -X POST "http://localhost:8080/api/batch/run?fileName=employees.csv"

Scheduling ile Batch Job Tetikleme

Gerçek dünyada batch job'lar zamanlama ile çalışır. Spring'in @Scheduled annotation'ı ile:

@Component
public class BatchScheduler {

    private final JobLauncher jobLauncher;
    private final Job employeeImportJob;

    public BatchScheduler(JobLauncher jobLauncher,
                          @Qualifier("employeeImportJob") Job job) {
        this.jobLauncher = jobLauncher;
        this.employeeImportJob = job;
    }

    @Scheduled(cron = "0 0 2 * * *")  // Her gece 02:00
    public void runNightlyImport() {
        try {
            JobParameters params = new JobParametersBuilder()
                .addLocalDateTime("scheduledAt", LocalDateTime.now())
                .toJobParameters();

            JobExecution execution = jobLauncher.run(
                employeeImportJob, params);
            System.out.println("Nightly import: " + execution.getStatus());
        } catch (Exception e) {
            System.err.println("Batch job başarısız: " + e.getMessage());
        }
    }
}

Main class'ta @EnableScheduling olmalı:

@SpringBootApplication
@EnableScheduling
public class BatchDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(BatchDemoApplication.class, args);
    }
}

Cron İfade Formatı (Spring)

saniye  dakika  saat  gün  ay  haftanın_günü

"0 0 2 * * *"       → Her gün 02:00
"0 0 2 * * MON-FRI" → Hafta içi 02:00
"0 0 */6 * * *"     → Her 6 saatte bir
"0 30 1 1 * *"      → Her ayın 1'i 01:30

İleri Konular

Koşullu Step Akışı

@Bean
public Job conditionalJob(JobRepository jobRepository,
                           Step validateStep,
                           Step importStep,
                           Step errorStep) {
    return new JobBuilder("conditionalJob", jobRepository)
        .start(validateStep)
            .on("FAILED").to(errorStep)
        .from(validateStep)
            .on("*").to(importStep)
        .end()
        .build();
}

Job Execution Listener

@Component
public class JobCompletionListener implements JobExecutionListener {

    @Override
    public void beforeJob(JobExecution execution) {
        System.out.println("Job başlıyor: "
            + execution.getJobInstance().getJobName());
    }

    @Override
    public void afterJob(JobExecution execution) {
        if (execution.getStatus() == BatchStatus.COMPLETED) {
            System.out.println("Job BAŞARILI!");
        } else if (execution.getStatus() == BatchStatus.FAILED) {
            System.err.println("Job BAŞARISIZ: "
                + execution.getAllFailureExceptions());
        }

        execution.getStepExecutions().forEach(step ->
            System.out.printf("Step '%s': Read=%d, Written=%d, " +
                "Skipped=%d%n",
                step.getStepName(), step.getReadCount(),
                step.getWriteCount(), step.getSkipCount())
        );
    }
}

Multi-threaded Step

@Bean
public Step parallelStep(JobRepository jobRepository,
                         PlatformTransactionManager txManager,
                         FlatFileItemReader<Employee> reader,
                         ItemProcessor<Employee, Employee> processor,
                         JpaItemWriter<Employee> writer) {
    return new StepBuilder("parallelStep", jobRepository)
        .<Employee, Employee>chunk(100, txManager)
        .reader(reader)
        .processor(processor)
        .writer(writer)
        .taskExecutor(new SimpleAsyncTaskExecutor())
        .throttleLimit(4)  // Max 4 thread
        .build();
}

⚠️ Multi-threaded step'te Reader thread-safe olmalı. FlatFileItemReader varsayılanda thread-safe değildir — SynchronizedItemStreamReader ile sarmalaman gerekir. JpaPagingItemReader ise thread-safe'dir.


Yaygın Hatalar

1. Job Parametreleri Benzersiz Olmalı

// ❌ Her seferinde aynı parametreler → Job tekrar çalışmaz
JobParameters params = new JobParametersBuilder()
    .addString("inputFile", "employees.csv")
    .toJobParameters();

// ✅ Her çalıştırmada benzersiz parametre ekle
JobParameters params = new JobParametersBuilder()
    .addString("inputFile", "employees.csv")
    .addLocalDateTime("runTime", LocalDateTime.now())
    .toJobParameters();

2. @StepScope Unutma

// ❌ jobParameters çalışmaz — bean başlangıçta oluşturulur
@Bean
public FlatFileItemReader<Employee> reader(
        @Value("#{jobParameters['file']}") String file) { }

// ✅ @StepScope ile lazy oluştur
@Bean
@StepScope
public FlatFileItemReader<Employee> reader(
        @Value("#{jobParameters['file']}") String file) { }

3. Transaction Sınırlarını Anla

Chunk size 100 ve 99. kayıtta hata olursa, 100 kaydın hepsi geri alınır. Çok büyük chunk size riskli olabilir — dengeyi bul.


Özet

  • Spring Batch, büyük hacimli veri işleme için standart Java framework'üdür — Job → Step → (Reader → Processor → Writer) mimarisiyle çalışır

  • Chunk-oriented processing, veriyi parça parça işler — bellek verimli, transaction güvenli, restart destekli

  • ItemReader (CSV, JDBC, JPA), ItemProcessor (dönüştürme, filtreleme) ve ItemWriter (DB, dosya) hazır bileşenleriyle çoğu senaryo karşılanır

  • Skip ve retry mekanizmaları hatalı kayıtları yönetir — milyonlarca kayıtta birkaç hata tüm job'u durdurmaz

  • Job parametreleri her çalıştırmayı benzersiz kılar, @StepScope ile runtime'da parametre injection yapılır

  • @Scheduled + JobLauncher kombinasyonuyla batch job'lar periyodik olarak otomatik tetiklenir