← Kursa Dön
📄 Text · 35 min

Java ile CRUD İşlemleri

Giriş — Depocu ve Forklift

Bir depo düşünün. Raflar dolu, binlerce ürün var. Depocu her gün aynı işleri yapar: yeni ürün yerleştirir (index), mevcut ürünü bulup getirir (get), etiket günceller (update), bozuk ürünü çıkarır (delete). Arada bir de TIR gelir — yüzlerce kutuyu tek seferde indirmek gerekir (bulk). Tek tek taşımak saatler alır; forklift ile palet palet taşırsanız dakikalar.

Elasticsearch Java Client ile CRUD işlemleri tam olarak bu mantıkla çalışır. Bu derste önce tek tek CRUD'u öğreneceğiz, sonra forkliftimiz olan Bulk API'yi devreye sokacağız. Önceki derste kurduğumuz ElasticsearchClient nesnesini kullanacağız.


1. Model Tanımları — POJO ve Record

1.1 Java Record (Önerilen)

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.LocalDateTime;
import java.util.List;

@JsonIgnoreProperties(ignoreUnknown = true)
public record Product(
    @JsonProperty("name") String name,
    @JsonProperty("description") String description,
    @JsonProperty("category") String category,
    @JsonProperty("price") double price,
    @JsonProperty("stock") int stock,
    @JsonProperty("tags") List<String> tags,
    @JsonProperty("created_at") LocalDateTime createdAt
) {}

1.2 Klasik POJO

@JsonIgnoreProperties(ignoreUnknown = true)
public class Product {
    private String name;
    private String description;
    private String category;
    private double price;
    private int stock;
    private List<String> tags;

    @JsonProperty("created_at")
    private LocalDateTime createdAt;

    public Product() {} // Jackson için boş constructor zorunlu!

    public Product(String name, String description, String category,
                   double price, int stock, List<String> tags,
                   LocalDateTime createdAt) {
        this.name = name;
        this.description = description;
        this.category = category;
        this.price = price;
        this.stock = stock;
        this.tags = tags;
        this.createdAt = createdAt;
    }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public double getPrice() { return price; }
    public void setPrice(double price) { this.price = price; }
    public int getStock() { return stock; }
    public void setStock(int stock) { this.stock = stock; }
    // ... diğer getter/setter'lar
}

💡 İpucu: Java 16+ kullanıyorsanız Record tercih edin — daha az kod, immutable, doğal equals/hashCode/toString.

1.3 ObjectMapper Konfigürasyonu

LocalDateTime gibi Java 8+ tarih tiplerini Jackson'ın tanıması için JavaTimeModule zorunludur:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

ElasticsearchTransport transport = new RestClientTransport(
    restClient, new JacksonJsonpMapper(objectMapper)
);

⚠️ Dependency eklemeyi unutmayın:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.16.1</version>
</dependency>

2. Document Oluşturma (Index)

2.1 POJO ile Index

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.IndexResponse;
import co.elastic.clients.elasticsearch._types.Result;
import java.time.LocalDateTime;
import java.util.List;

public class IndexExample {

    public static void indexWithPojo(ElasticsearchClient client) throws Exception {
        Product product = new Product(
            "MacBook Pro 16",
            "Apple M3 Max çipli profesyonel dizüstü bilgisayar",
            "electronics", 74999.99, 50,
            List.of("apple", "laptop", "pro"),
            LocalDateTime.now()
        );

        IndexResponse response = client.index(i -> i
            .index("products")
            .id("prod-001")
            .document(product)
        );

        System.out.println("ID: " + response.id());
        System.out.println("Version: " + response.version());
        System.out.println("Result: " + response.result());

        if (response.result() == Result.Created) {
            System.out.println("Yeni document oluşturuldu");
        } else if (response.result() == Result.Updated) {
            System.out.println("Mevcut document güncellendi");
        }
    }
}

ID belirtmezseniz Elasticsearch otomatik üretir:

IndexResponse response = client.index(i -> i
    .index("products")
    .document(product)  // ID yok → otomatik üretilir
);
System.out.println("Auto ID: " + response.id()); // "k1xB4owBdRz2..."

2.2 JSON String ile Index

Elimizde POJO yerine ham JSON olduğunda:

import java.io.StringReader;

String jsonString = """
    {
        "name": "Sony WH-1000XM5",
        "description": "Gürültü engelleyici kulaklık",
        "category": "electronics",
        "price": 12999.99,
        "stock": 200,
        "tags": ["sony", "headphone"],
        "created_at": "2024-01-15T10:30:00"
    }
    """;

IndexResponse response = client.index(i -> i
    .index("products")
    .id("prod-003")
    .withJson(new StringReader(jsonString))
);

2.3 Sadece Yoksa Oluştur — op_type: create

import co.elastic.clients.elasticsearch._types.OpType;
import co.elastic.clients.elasticsearch._types.ElasticsearchException;

try {
    IndexResponse response = client.index(i -> i
        .index("products")
        .id("prod-001")
        .document(product)
        .opType(OpType.Create)  // Varsa 409 conflict fırlat
    );
} catch (ElasticsearchException e) {
    if (e.status() == 409) {
        System.out.println("Document zaten var, atlanıyor.");
    } else {
        throw e;
    }
}

3. Document Okuma (Get)

3.1 Basit Get

import co.elastic.clients.elasticsearch.core.GetResponse;

public class GetExample {

    public static void getById(ElasticsearchClient client) throws Exception {
        GetResponse<Product> response = client.get(g -> g
            .index("products")
            .id("prod-001"),
            Product.class
        );

        if (response.found()) {
            Product product = response.source();
            System.out.println("Ürün: " + product.getName());
            System.out.println("Fiyat: " + product.getPrice());
        } else {
            System.out.println("Document bulunamadı!");
        }
    }
}

3.2 Source Filtering — Sadece İstediğin Alanlar

GetResponse<Product> response = client.get(g -> g
    .index("products")
    .id("prod-001")
    .sourceIncludes("name", "price"),
    Product.class
);
// Sadece name ve price dolu, diğerleri null

3.3 Exists — Varlık Kontrolü

import co.elastic.clients.transport.endpoints.BooleanResponse;

BooleanResponse exists = client.exists(e -> e.index("products").id("prod-001"));
System.out.println(exists.value() ? "Mevcut" : "Bulunamadı");

3.4 Multi-Get — Toplu Okuma

import co.elastic.clients.elasticsearch.core.MgetResponse;
import co.elastic.clients.elasticsearch.core.mget.MultiGetResponseItem;

MgetResponse<Product> response = client.mget(m -> m
    .index("products")
    .ids("prod-001", "prod-002", "prod-003", "prod-999"),
    Product.class
);

for (MultiGetResponseItem<Product> item : response.docs()) {
    if (item.isResult()) {
        var doc = item.result();
        if (doc.found()) {
            System.out.println("✅ " + doc.id() + ": " + doc.source().getName());
        } else {
            System.out.println("❌ " + doc.id() + ": Bulunamadı");
        }
    }
}

⚠️ mget vs search: Belirli ID'leri biliyorsanız mget kullanın — doğrudan shard'a gider, aramadan çok daha hızlıdır.


4. Document Güncelleme (Update)

4.1 Partial Update

import co.elastic.clients.elasticsearch.core.UpdateResponse;
import java.util.Map;

public class UpdateExample {

    public static void partialUpdate(ElasticsearchClient client) throws Exception {
        UpdateResponse<Product> response = client.update(u -> u
            .index("products")
            .id("prod-001")
            .doc(Map.of("price", 69999.99, "stock", 45)),
            Product.class
        );

        System.out.println("Result: " + response.result());
        System.out.println("Version: " + response.version());
    }
}

4.2 Script ile Update

Mevcut değere bağlı güncelleme — örneğin stoktan düşme:

UpdateResponse<Product> response = client.update(u -> u
    .index("products")
    .id("prod-001")
    .script(s -> s
        .inline(i -> i
            .source("ctx._source.stock -= params.qty")
            .params("qty", co.elastic.clients.json.JsonData.of(5))
        )
    ),
    Product.class
);

Koşullu script — stok yeterliyse düş, yoksa noop:

String script = """
    if (ctx._source.stock >= params.qty) {
        ctx._source.stock -= params.qty;
    } else {
        ctx.op = 'noop';
    }
    """;

UpdateResponse<Product> response = client.update(u -> u
    .index("products")
    .id("prod-001")
    .script(s -> s.inline(i -> i
        .source(script)
        .params("qty", co.elastic.clients.json.JsonData.of(100))
    )),
    Product.class
);
// Stok yetersizse → Result.NoOp

4.3 Upsert — Yoksa Oluştur, Varsa Güncelle

Product newProduct = new Product("iPad Air M2", "M2 çipli tablet",
    "electronics", 29999.99, 80, List.of("apple", "tablet"), LocalDateTime.now());

UpdateResponse<Product> response = client.update(u -> u
    .index("products")
    .id("prod-010")
    .doc(newProduct)       // Varsa güncelle
    .upsert(newProduct),   // Yoksa oluştur
    Product.class
);

// Result.Created veya Result.Updated

4.4 Güncellenmiş Hali Geri Alma

UpdateResponse<Product> response = client.update(u -> u
    .index("products")
    .id("prod-001")
    .doc(Map.of("price", 64999.99))
    .source(s -> s.fetch(true)),  // Güncel halini döndür
    Product.class
);

Product updated = response.get().source();
System.out.println("Güncel fiyat: " + updated.getPrice());

5. Document Silme (Delete)

5.1 ID ile Silme

import co.elastic.clients.elasticsearch.core.DeleteResponse;

DeleteResponse response = client.delete(d -> d.index("products").id("prod-001"));

if (response.result() == Result.Deleted) {
    System.out.println("Silindi");
} else if (response.result() == Result.NotFound) {
    System.out.println("Zaten yoktu");
}

5.2 Delete by Query — Koşullu Toplu Silme

import co.elastic.clients.elasticsearch.core.DeleteByQueryResponse;

// Stoku 0 olan tüm ürünleri sil
DeleteByQueryResponse response = client.deleteByQuery(d -> d
    .index("products")
    .query(q -> q.term(t -> t.field("stock").value(0)))
);

System.out.println("Silinen: " + response.deleted());
System.out.println("Süre (ms): " + response.took());

Daha karmaşık koşul:

// 2023'ten önce oluşturulmuş VE stoku 0 olan ürünler
DeleteByQueryResponse response = client.deleteByQuery(d -> d
    .index("products")
    .query(q -> q.bool(b -> b
        .must(m -> m.range(r -> r.field("created_at")
            .lt(co.elastic.clients.json.JsonData.of("2023-01-01"))))
        .must(m -> m.term(t -> t.field("stock").value(0)))
    ))
    .refresh(true)
);

6. Bulk API — Toplu İşlemler

Production'da binlerce document'ı tek tek index'lemek yüzlerce HTTP isteği demek. Bulk API tüm işlemleri tek istekte birleştirir — bu dersin en kritik kısmı.

6.1 Basit Bulk Index

import co.elastic.clients.elasticsearch.core.BulkResponse;
import co.elastic.clients.elasticsearch.core.BulkRequest;
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;

public class BulkExample {

    public static void bulkIndex(ElasticsearchClient client) throws Exception {
        List<Product> products = List.of(
            new Product("iPhone 15 Pro", "A17 Pro", "electronics",
                64999.99, 100, List.of("apple", "phone"), LocalDateTime.now()),
            new Product("AirPods Pro 2", "ANC", "electronics",
                9999.99, 500, List.of("apple", "earbuds"), LocalDateTime.now()),
            new Product("Dell XPS 15", "i9", "electronics",
                52999.99, 35, List.of("dell", "laptop"), LocalDateTime.now())
        );

        BulkRequest.Builder bulkBuilder = new BulkRequest.Builder();
        for (int i = 0; i < products.size(); i++) {
            final int idx = i;
            final String id = "prod-" + (100 + i);
            bulkBuilder.operations(op -> op
                .index(ix -> ix.index("products").id(id)
                    .document(products.get(idx)))
            );
        }

        BulkResponse response = client.bulk(bulkBuilder.build());

        if (response.errors()) {
            for (BulkResponseItem item : response.items()) {
                if (item.error() != null) {
                    System.err.println("❌ " + item.id() + ": " + item.error().reason());
                }
            }
        } else {
            System.out.println("✅ " + response.items().size() + " document eklendi");
        }
    }
}

6.2 Karma Bulk — Index + Update + Delete

BulkResponse response = client.bulk(b -> b
    .operations(op -> op
        .index(idx -> idx.index("products").id("prod-200")
            .document(new Product("Yeni", "Açıklama", "electronics",
                9999.99, 10, List.of("new"), LocalDateTime.now())))
    )
    .operations(op -> op
        .update(upd -> upd.index("products").id("prod-001")
            .action(a -> a.doc(Map.of("price", 59999.99))))
    )
    .operations(op -> op
        .delete(del -> del.index("products").id("prod-old-001"))
    )
);

for (BulkResponseItem item : response.items()) {
    System.out.printf("  %s %s → %s%n",
        item.operationType(), item.id(), item.result());
}

6.3 Büyük Veri Seti — Chunk'lara Bölme

public class ChunkedBulkProcessor {
    private static final int CHUNK_SIZE = 1000;

    public static void bulkIndexChunked(ElasticsearchClient client,
                                         List<Product> allProducts) throws Exception {
        int success = 0, errors = 0;
        long start = System.currentTimeMillis();

        for (int i = 0; i < allProducts.size(); i += CHUNK_SIZE) {
            int end = Math.min(i + CHUNK_SIZE, allProducts.size());
            List<Product> chunk = allProducts.subList(i, end);

            BulkRequest.Builder builder = new BulkRequest.Builder();
            for (int j = 0; j < chunk.size(); j++) {
                final Product p = chunk.get(j);
                final String id = "prod-" + (i + j);
                builder.operations(op -> op
                    .index(idx -> idx.index("products").id(id).document(p)));
            }

            BulkResponse resp = client.bulk(builder.build());
            if (resp.errors()) {
                for (BulkResponseItem item : resp.items()) {
                    if (item.error() != null) errors++; else success++;
                }
            } else {
                success += chunk.size();
            }
            System.out.printf("İlerleme: %d/%d%n", end, allProducts.size());
        }
        System.out.printf("Bitti: %d OK, %d hata, %d ms%n",
            success, errors, System.currentTimeMillis() - start);
    }
}

6.4 BulkIngester — Otomatik Chunk Yönetimi

import co.elastic.clients.helpers.bulk.BulkIngester;
import co.elastic.clients.helpers.bulk.BulkListener;

BulkListener<Void> listener = new BulkListener<>() {
    @Override
    public void beforeBulk(long id, BulkRequest req, List<Void> ctx) {
        System.out.println("Bulk #" + id + " → " + req.operations().size() + " işlem");
    }
    @Override
    public void afterBulk(long id, BulkRequest req, List<Void> ctx, BulkResponse resp) {
        System.out.println("Bulk #" + id + (resp.errors() ? " ⚠️" : " ✅"));
    }
    @Override
    public void afterBulk(long id, BulkRequest req, List<Void> ctx, Throwable err) {
        System.err.println("Bulk #" + id + " BAŞARISIZ: " + err.getMessage());
    }
};

try (BulkIngester<Void> ingester = BulkIngester.of(b -> b
    .client(client)
    .maxOperations(500)
    .flushInterval(5, java.util.concurrent.TimeUnit.SECONDS)
    .listener(listener)
)) {
    for (int i = 0; i < products.size(); i++) {
        final String id = "prod-" + i;
        final Product p = products.get(i);
        ingester.add(op -> op
            .index(idx -> idx.index("products").id(id).document(p)), null);
    }
} // close() → kalan işlemler otomatik gönderilir

💡 BulkIngester thread-safe'tir, maxOperations'a ulaşınca veya flushInterval dolunca otomatik flush eder, close() çağrılınca kalanları gönderir.


7. Hata Yönetimi

7.1 Genel Hata Yakalama

import co.elastic.clients.elasticsearch._types.ElasticsearchException;
import co.elastic.clients.transport.TransportException;

public class ErrorHandling {

    public static void safeIndex(ElasticsearchClient client,
                                  String id, Product product) {
        try {
            IndexResponse response = client.index(i -> i
                .index("products").id(id).document(product));
            System.out.println("OK: " + response.id());

        } catch (ElasticsearchException e) {
            System.err.println("ES Hatası [" + e.status() + "]: " + e.error().reason());
            switch (e.status()) {
                case 400 -> System.err.println("→ Geçersiz istek (mapping hatası?)");
                case 404 -> System.err.println("→ Index bulunamadı");
                case 409 -> System.err.println("→ Version conflict");
                case 429 -> System.err.println("→ Rate limit — retry gerekli");
            }
        } catch (TransportException e) {
            System.err.println("Bağlantı hatası: " + e.getMessage());
        } catch (java.io.IOException e) {
            System.err.println("IO hatası: " + e.getMessage());
        }
    }
}

7.2 Retry (Exponential Backoff)

public class RetryHelper {
    public static <T> T withRetry(java.util.concurrent.Callable<T> op,
                                   int maxRetries, long delayMs) throws Exception {
        for (int attempt = 1; ; attempt++) {
            try {
                return op.call();
            } catch (Exception e) {
                if (attempt >= maxRetries) throw e;
                System.out.printf("Deneme %d/%d başarısız, %dms bekleniyor%n",
                    attempt, maxRetries, delayMs);
                Thread.sleep(delayMs);
                delayMs = Math.min(delayMs * 2, 30000);
            }
        }
    }
}

// Kullanım
IndexResponse resp = RetryHelper.withRetry(
    () -> client.index(i -> i.index("products").id("1").document(product)),
    3, 1000
);

8. Optimistic Concurrency Control

İki thread aynı document'ı güncellemeye çalışırsa _seq_no ve _primary_term ile sadece biri başarılı olur:

// 1. Oku — seq_no ve primary_term al
GetResponse<Product> getResp = client.get(g -> g
    .index("products").id("prod-001"), Product.class);

long seqNo = getResp.seqNo();
long primaryTerm = getResp.primaryTerm();

// 2. Koşullu güncelle
try {
    IndexResponse updateResp = client.index(i -> i
        .index("products")
        .id("prod-001")
        .document(getResp.source())
        .ifSeqNo(seqNo)
        .ifPrimaryTerm(primaryTerm)
    );
    System.out.println("OK, version: " + updateResp.version());
} catch (ElasticsearchException e) {
    if (e.status() == 409) {
        System.out.println("⚠️ Conflict! Başka biri güncellemiş → tekrar oku ve dene.");
    }
}

9. Bütünleşik Örnek — E-Ticaret Ürün Yönetimi

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch._types.Result;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

public class ECommerceProductManager {

    private final ElasticsearchClient client;
    private static final String INDEX = "products_v2";

    public ECommerceProductManager(ElasticsearchClient client) {
        this.client = client;
    }

    public void setupIndex() throws Exception {
        if (!client.indices().exists(e -> e.index(INDEX)).value()) {
            client.indices().create(c -> c.index(INDEX)
                .settings(s -> s.numberOfShards("3").numberOfReplicas("1"))
                .mappings(m -> m
                    .properties("name", p -> p.text(t -> t.analyzer("standard")))
                    .properties("category", p -> p.keyword(k -> k))
                    .properties("price", p -> p.float_(f -> f))
                    .properties("stock", p -> p.integer(i -> i))
                    .properties("tags", p -> p.keyword(k -> k))
                    .properties("created_at", p -> p.date(d ->
                        d.format("yyyy-MM-dd'T'HH:mm:ss")))
                )
            );
            System.out.println("✅ Index oluşturuldu");
        }
    }

    public void addProduct(String id, Product product) throws Exception {
        client.index(i -> i.index(INDEX).id(id).document(product));
    }

    public void addBulk(Map<String, Product> products) throws Exception {
        BulkRequest.Builder b = new BulkRequest.Builder();
        products.forEach((id, p) -> b.operations(op -> op
            .index(idx -> idx.index(INDEX).id(id).document(p))));
        BulkResponse resp = client.bulk(b.build());
        if (resp.errors()) System.out.println("⚠️ Bulk hatası var!");
    }

    public Product getProduct(String id) throws Exception {
        var resp = client.get(g -> g.index(INDEX).id(id), Product.class);
        return resp.found() ? resp.source() : null;
    }

    public void updatePrice(String id, double price) throws Exception {
        client.update(u -> u.index(INDEX).id(id)
            .doc(Map.of("price", price)), Product.class);
    }

    public void decreaseStock(String id, int qty) throws Exception {
        client.update(u -> u.index(INDEX).id(id)
            .script(s -> s.inline(i -> i
                .source("ctx._source.stock -= params.qty")
                .params("qty", co.elastic.clients.json.JsonData.of(qty))
            )), Product.class);
    }

    public boolean deleteProduct(String id) throws Exception {
        return client.delete(d -> d.index(INDEX).id(id))
            .result() == Result.Deleted;
    }

    public static void main(String[] args) throws Exception {
        ObjectMapper om = new ObjectMapper();
        om.registerModule(new JavaTimeModule());
        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        RestClient rest = RestClient.builder(
            new HttpHost("localhost", 9200, "http")).build();
        var transport = new RestClientTransport(rest, new JacksonJsonpMapper(om));
        var client = new ElasticsearchClient(transport);
        var mgr = new ECommerceProductManager(client);

        mgr.setupIndex();

        mgr.addProduct("p-001", new Product("MacBook Pro 16", "M3 Max",
            "electronics", 74999.99, 50, List.of("apple"), LocalDateTime.now()));

        mgr.addBulk(Map.of(
            "p-002", new Product("iPhone 15", "A17", "electronics",
                64999.99, 100, List.of("apple"), LocalDateTime.now()),
            "p-003", new Product("Sony XM5", "ANC", "electronics",
                12999.99, 200, List.of("sony"), LocalDateTime.now())
        ));

        client.indices().refresh(r -> r.index(INDEX));

        Product p = mgr.getProduct("p-001");
        System.out.println("Okunan: " + p.getName() + " — " + p.getPrice() + " TL");

        mgr.updatePrice("p-001", 69999.99);
        mgr.decreaseStock("p-002", 3);
        System.out.println("Silindi: " + mgr.deleteProduct("p-003"));

        transport.close();
        rest.close();
    }
}

10. Best Practices

✅ Yapın

UygulamaNeden
Bulk API kullanın (10+ document)Her HTTP isteğinin overhead'i var — toplu gönderin
Chunk size: 500-2000Çok büyük chunk bellek patlatır
@JsonIgnoreProperties(ignoreUnknown = true)Mapping değişse bile deserialize çalışır
op_type: create ile duplicate engeliAynı ID'nin üzerine yazmayı önler
Optimistic locking (if_seq_no)Race condition'ları önler

❌ Yapmayın

UygulamaNeden
Tek tek index'lemeyinHer biri ayrı HTTP roundtrip — çok yavaş
Bulk'ta 10MB'ı aşmayın5-15MB ideal, büyük request timeout alır
Bulk errors kontrolünü atlamayınerrors: true olabilir ama exception fırlatmaz
Hata yönetimi olmadan production'a çıkmayınAğ hataları, conflict'ler kaçınılmaz

11. Yaygın Hatalar

Hata 1: Jackson Boş Constructor Eksikliği

// ❌ No default constructor hatası
public class Product {
    private final String name;
    public Product(String name) { this.name = name; }
}

// ✅ Boş constructor ekle veya Record kullan
public class Product {
    private String name;
    public Product() {}
}

Hata 2: LocalDateTime Serialization

// ❌ Tarih array olarak serialize oluyor: [2024,1,15,10,30]
// ✅ JavaTimeModule + WRITE_DATES_AS_TIMESTAMPS kapalı
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

Hata 3: Bulk Response Kontrol Etmemek

// ❌ Bazı document'lar başarısız olmuş olabilir!
BulkResponse response = client.bulk(builder.build());
System.out.println("Bitti!");

// ✅ Errors flag'ini kontrol et
if (response.errors()) {
    response.items().stream()
        .filter(item -> item.error() != null)
        .forEach(item -> System.err.println(item.id() + " → " + item.error().reason()));
}

Özet

  • IndexRequest ile document oluşturma: POJO, JSON string ile — op_type: create duplicate'i önler

  • GetRequest ile tekil okuma, mget ile toplu okuma — source filtering ile sadece gerekli alanları çekin

  • UpdateRequest ile partial update, script update, upsert — koşullu güncelleme için Painless script

  • DeleteRequest ile tekil silme, DeleteByQuery ile koşullu toplu silme

  • Bulk API production'ın olmazsa olmazı — 500-2000'lik chunk'lar veya BulkIngester ile otomatik

  • Hata yönetimi zorunlu — ElasticsearchException status code'ları, retry pattern, bulk error collecting

  • Optimistic locking (if_seq_no, if_primary_term) ile concurrent update güvenliği

  • Jackson konfigürasyonu kritik: JavaTimeModule + @JsonIgnoreProperties eklemeyi unutmayın