Eğitim Portalı/Spring Boot/Caching, Async ve Scheduling
Spring Boot03-spring-boot/05-caching-async-scheduling

Caching, Async ve Scheduling

Bir uygulamayı hızlı ve verimli kılmanın üç güçlü aracını ele alıyoruz: tekrarlayan pahalı işlemleri önbelleğe almak (@Cacheable), uzun süren işleri arka plana atmak (@Async) ve belirli aralıklarla otomatik görev çalı…

Caching, Async ve Scheduling

Bir uygulamayı hızlı ve verimli kılmanın üç güçlü aracını ele alıyoruz: tekrarlayan pahalı işlemleri önbelleğe almak (@Cacheable), uzun süren işleri arka plana atmak (@Async) ve belirli aralıklarla otomatik görev çalıştırmak (@Scheduled). Üçü de Spring'in AOP/proxy mekanizması üzerine kuruludur — yani bir anotasyon eklersin, gerisini Spring halleder.

@Cacheable — önbellekleme

Aynı veriyi tekrar tekrar hesaplamak (veritabanı sorgusu, dış API çağrısı, ağır hesap) israftır. @Cacheable, bir metodun sonucunu anahtarına göre saklar; aynı argümanla ikinci çağrıda metot hiç çalışmaz, sonuç doğrudan önbellekten döner:

@EnableCaching // altyapıyı aç
@Cacheable("urunler")
public String detayGetir(Long id) { /* pahalı işlem */ }

Örnek 1 (./Ornek1.java) aynı id ile ilk çağrının ~600 ms, ikinci çağrının ~0 ms sürdüğünü (önbellekten) canlı ölçer. Farklı id, anahtarı değiştirdiğinden tekrar hesaplanır.

Spring Boot, bir cache sağlayıcısı yoksa basit bir bellek-içi (ConcurrentMapCacheManager) önbellek yapılandırır. Üretimde Redis, Caffeine gibi sağlayıcılarla dağıtık/gelişmiş önbellek kullanılır. İlgili anotasyonlar: @CacheEvict (önbelleği temizle), @CachePut (güncelle), @Cacheable(condition=..., unless=...).

@Async — asenkron çalıştırma

Bir e-posta göndermek veya rapor üretmek için kullanıcıyı bekletmek istemezsin. @Async, bir metodu ayrı bir thread'de, çağrıyı bloklamadan çalıştırır:

@EnableAsync
@Async
public CompletableFuture<String> raporUret(int no) { /* uzun iş */ }

Metot çağrılır çağrılmaz hemen döner; iş arka planda sürer. CompletableFuture döndürerek sonucu sonra toplayabilirsin. Örnek 2 (./Ornek2.java) üç raporu aynı anda tetikler ve toplam sürenin ~500 ms (sıralı olsaydı ~1500 ms) olduğunu gösterir — işler paralel koştu.

Dikkat: @Async da proxy tabanlıdır; self-invocation (aynı sınıf içi çağrı) asenkron çalışmaz. Ayrıca üretimde kendi TaskExecutor (thread havuzu) yapılandırmanı tanımlamak iyidir (varsayılan havuz sınırlıdır).

@Scheduled — zamanlanmış görevler

Periyodik işler (gece yarısı rapor, her 5 dakikada sağlık kontrolü, eski kayıt temizliği) @Scheduled ile tanımlanır:

@EnableScheduling
@Scheduled(fixedRate = 1500)            // her 1.5 sn
public void periyodikGorev() { ... }

@Scheduled(cron = "0 0 3 * * *")        // her gün saat 03:00 (cron ifadesi)
public void geceBakimi() { ... }
  • fixedRate — sabit aralıkla (önceki bitişi beklemeden).
  • fixedDelay — önceki bitişten itibaren belirli gecikmeyle.
  • initialDelay — ilk çalışmadan önceki gecikme.
  • cron — takvim bazlı ifadeyle (saat/gün/ay).

Örnek 3 (./Ornek3.java) iki zamanlanmış görevi başlatır; uygulama açık kaldığı birkaç saniye boyunca tikler arka planda otomatik üretilir (bu örnekte çıktı, self-test'ten değil zamanlayıcıdan gelir).

Özet

@Cacheable ile pahalı işlemleri önbelleğe almayı (Örnek 1), @Async ile işleri arka plana atıp paralel çalıştırmayı (Örnek 2) ve @Scheduled ile periyodik görevleri (Örnek 3) gerçek, çalışan uygulamalarla gördük. Üçü de proxy tabanlıdır (self-invocation tuzağı geçerli). Bu araçlar, uygulamanın yanıt süresini ve verimliliğini ciddi biçimde iyileştirir. Sırada, uygulamayı izlemek ve üretime hazırlamak: Actuator ve üretim araçları.

Kod Örnekleri(3)

Ornek1

ortam gerekir
Ornek1.java
1// Ornek1: @Cacheable — pahalı bir işlemin sonucunu önbelleğe alıp tekrar tekrar hesaplamamak.
2// Çalıştırma: portal derleyip gömülü Tomcat ile başlatır.
3package com.egitim.springboot.cache;
4
5import org.springframework.boot.CommandLineRunner;
6import org.springframework.boot.SpringApplication;
7import org.springframework.boot.autoconfigure.SpringBootApplication;
8import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
9import org.springframework.cache.annotation.Cacheable;
10import org.springframework.cache.annotation.EnableCaching;
11import org.springframework.stereotype.Service;
12
13@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
14@EnableCaching // önbellekleme altyapısını açar (Boot basit bir bellek-içi cache yapılandırır)
15public class Ornek1 {
16
17    public static void main(String[] args) { SpringApplication.run(Ornek1.class, args); }
18
19    @Service
20    public static class UrunServisi {
21        // @Cacheable: aynı id ile ilk çağrıda metot çalışır ve sonuç "urunler" cache'ine yazılır;
22        // sonraki çağrılarda metot HİÇ çalışmaz, sonuç doğrudan cache'ten döner.
23        @Cacheable("urunler")
24        public String detayGetir(Long id) {
25            yavasIslem(); // pahalı işlemi (DB/ağ) taklit eder
26            return "Ürün-" + id + " detayları";
27        }
28
29        private void yavasIslem() {
30            try { Thread.sleep(600); } catch (InterruptedException ignored) {}
31        }
32    }
33
34    @org.springframework.context.annotation.Bean
35    CommandLineRunner selfTest(UrunServisi servis) {
36        return args -> {
37            System.out.println("\n================ @Cacheable ================");
38            System.out.println("1. çağrı (id=1): " + olcveCagir(servis, 1L));
39            System.out.println("2. çağrı (id=1): " + olcveCagir(servis, 1L) + "  <- cache'ten, anında!");
40            System.out.println("3. çağrı (id=2): " + olcveCagir(servis, 2L) + "  <- yeni id, tekrar hesaplandı");
41            System.out.println("4. çağrı (id=1): " + olcveCagir(servis, 1L) + "  <- yine cache'ten");
42            System.out.println("============================================");
43            System.out.println("@Cacheable, pahalı (DB/ağ/hesap) işlemleri tekrarlamamayı sağlar. Redis ile dağıtık da olabilir.");
44        };
45    }
46
47    static String olcveCagir(UrunServisi s, Long id) {
48        long t0 = System.currentTimeMillis();
49        String sonuc = s.detayGetir(id);
50        return "(" + (System.currentTimeMillis() - t0) + " ms) " + sonuc;
51    }
52}
canlı çalıştırma için ortam gerekir

Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek1.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.

Ornek2

ortam gerekir
Ornek2.java
1// Ornek2: @Async — uzun süren işleri ayrı thread'lerde, bloklamadan çalıştırmak.
2// Çalıştırma: portal derleyip gömülü Tomcat ile başlatır.
3package com.egitim.springboot.cache;
4
5import org.springframework.boot.CommandLineRunner;
6import org.springframework.boot.SpringApplication;
7import org.springframework.boot.autoconfigure.SpringBootApplication;
8import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
9import org.springframework.context.annotation.Bean;
10import org.springframework.scheduling.annotation.Async;
11import org.springframework.scheduling.annotation.EnableAsync;
12import org.springframework.stereotype.Service;
13
14import java.util.concurrent.CompletableFuture;
15
16@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
17@EnableAsync // @Async metotları ayrı thread havuzunda çalıştırır
18public class Ornek2 {
19
20    public static void main(String[] args) { SpringApplication.run(Ornek2.class, args); }
21
22    @Service
23    public static class RaporServisi {
24        // @Async: bu metot çağrıldığında HEMEN döner; iş arka planda (başka thread'de) çalışır.
25        // CompletableFuture döndürerek sonucu sonra toplayabiliriz.
26        @Async
27        public CompletableFuture<String> raporUret(int no) {
28            try { Thread.sleep(500); } catch (InterruptedException ignored) {} // ağır iş
29            String thread = Thread.currentThread().getName();
30            return CompletableFuture.completedFuture("Rapor-" + no + " (thread: " + thread + ")");
31        }
32    }
33
34    @Bean
35    CommandLineRunner selfTest(RaporServisi servis) {
36        return args -> {
37            System.out.println("\n================ @Async ================");
38            long t0 = System.currentTimeMillis();
39
40            // Üç raporu AYNI ANDA tetikle (her biri ~500 ms). Çağrılar hemen döner.
41            CompletableFuture<String> r1 = servis.raporUret(1);
42            CompletableFuture<String> r2 = servis.raporUret(2);
43            CompletableFuture<String> r3 = servis.raporUret(3);
44
45            // Hepsinin bitmesini bekle ve sonuçları yazdır.
46            CompletableFuture.allOf(r1, r2, r3).join();
47            System.out.println("  " + r1.get());
48            System.out.println("  " + r2.get());
49            System.out.println("  " + r3.get());
50
51            long sure = System.currentTimeMillis() - t0;
52            System.out.println("Toplam süre: ~" + sure + " ms (sıralı olsaydı ~1500 ms olurdu)");
53            System.out.println("========================================");
54            System.out.println("@Async: e-posta gönderme, rapor üretme gibi işleri arka plana atıp isteği bekletmemek için.");
55        };
56    }
57}
canlı çalıştırma için ortam gerekir

Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek2.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.

Ornek3

ortam gerekir
Ornek3.java
1// Ornek3: @Scheduled — belirli aralıklarla otomatik çalışan görevler (zamanlama).
2// Çalıştırma: portal derleyip gömülü Tomcat ile başlatır; birkaç saniye boyunca tetiklenir.
3package com.egitim.springboot.cache;
4
5import org.springframework.boot.SpringApplication;
6import org.springframework.boot.autoconfigure.SpringBootApplication;
7import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
8import org.springframework.scheduling.annotation.EnableScheduling;
9import org.springframework.scheduling.annotation.Scheduled;
10import org.springframework.stereotype.Component;
11
12import java.time.LocalTime;
13import java.time.format.DateTimeFormatter;
14import java.util.concurrent.atomic.AtomicInteger;
15
16@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
17@EnableScheduling // zamanlanmış görevleri etkinleştirir
18public class Ornek3 {
19
20    public static void main(String[] args) { SpringApplication.run(Ornek3.class, args); }
21
22    @Component
23    public static class ZamanlanmisGorevler {
24        private final AtomicInteger sayac = new AtomicInteger();
25        private static final DateTimeFormatter SAAT = DateTimeFormatter.ofPattern("HH:mm:ss");
26
27        // fixedRate: her 1500 ms'de bir çalışır (önceki bitişe bakmadan, sabit aralıkla).
28        @Scheduled(fixedRate = 1500)
29        public void periyodikGorev() {
30            System.out.println("  [zamanlanmış] tik #" + sayac.incrementAndGet()
31                    + " @ " + LocalTime.now().format(SAAT));
32        }
33
34        // initialDelay + fixedRate: ilk çalışma 500 ms gecikmeli başlar.
35        @Scheduled(initialDelay = 500, fixedRate = 2000)
36        public void digerGorev() {
37            System.out.println("  [zamanlanmış-2] yedekleme kontrolü @ " + LocalTime.now().format(SAAT));
38        }
39    }
40
41    // Not: Bu örnekte ana çıktı, arka planda çalışan @Scheduled metotlarından gelir.
42    // Uygulama açık kaldığı süre boyunca (birkaç saniye) görevler tetiklenir, sonra durdurulur.
43    @org.springframework.context.annotation.Bean
44    org.springframework.boot.CommandLineRunner aciklama() {
45        return args -> {
46            System.out.println("\n================ @Scheduled ================");
47            System.out.println("Zamanlanmış görevler başladı. Aşağıdaki tikler OTOMATİK, arka planda üretilir:");
48            System.out.println("(@Scheduled(fixedRate=1500) ve initialDelay'li ikinci görev)");
49            System.out.println("--------------------------------------------");
50        };
51    }
52}
canlı çalıştırma için ortam gerekir

Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek3.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.