---
title: 'Doğrudan Sorgu ve Eşleyici'
source: 'https://academia.sh/tr/kurslar/veri-erisim-katmani/dogrudan-sorgu-ve-esleyici'
course: 'Veri Erişim Katmanı ve İş Mantığı'
language: tr
updated: '2026-08-17T18:06:50+00:00'
license: 'CC BY-SA 4.0'
---

# Doğrudan Sorgu ve Eşleyici

Aynı listenin iki yoldan kurulması: elle yazılan SQL ile bildirilen eşlemeden SQL üreten küçük bir eşleyici, üretilen sorguların sayılması ve iki yaklaşımın denetim–tekrar ödünleşimi.

Kimlik Doğrulama ve Yetkilendirme kursu bir soruyu kapattı: bir isteği kimin yapabileceği,
hangi kaynağa hangi rolle dokunabileceği artık karara bağlanmış durumda. Denetimden geçen
istek uygulamaya girer ve orada bekleyen ikinci soruyla karşılaşır. Çağrı veriye nasıl
inecek?

Bu kurs o inişi kurar. İlk adım en alttaki katmandır: uygulamadaki nesnelerle
veritabanındaki satırlar arasındaki çeviri. Çeviriyi elle yazmak da, bir eşleyiciye
(mapper) bırakmak da mümkündür. İkisi de aynı listeyi üretir; ürettikleri SQL ve
çalıştırdıkları sorgu sayısı aynı değildir. Bu ders farkı ölçer.

## Ortak Görev ve Şema

Kurs boyunca kullanılan şema, SQL Temelleri kursundaki kütüphane şemasıdır: şube, kitap,
üye ve ödünç bağıntıları. Aşağıdaki blok veritabanını sıfırdan kurar; bu dersin bütün
örnekleri bu dosya üzerinde çalışır.

```sh
rm -f kutuphane.db
sqlite3 kutuphane.db <<'SQL'
CREATE TABLE sube (sube_id INTEGER PRIMARY KEY, ad TEXT NOT NULL, sehir TEXT NOT NULL);
CREATE TABLE kitap (kitap_id INTEGER PRIMARY KEY, baslik TEXT NOT NULL, yazar TEXT NOT NULL,
                    basim_yili INTEGER, sube_id INTEGER REFERENCES sube(sube_id));
CREATE TABLE uye (uye_id INTEGER PRIMARY KEY, ad TEXT NOT NULL, soyad TEXT NOT NULL,
                  eposta TEXT, kayit_tarihi TEXT NOT NULL);
CREATE TABLE odunc (odunc_id INTEGER PRIMARY KEY, kitap_id INTEGER NOT NULL REFERENCES kitap(kitap_id),
                    uye_id INTEGER NOT NULL REFERENCES uye(uye_id), alis_tarihi TEXT NOT NULL,
                    iade_tarihi TEXT);
INSERT INTO sube VALUES (1,'Merkez','Ankara'),(2,'Bahçelievler','Ankara'),
  (3,'Kadıköy','İstanbul'),(4,'Konak','İzmir');
INSERT INTO kitap VALUES (1,'Körlük','José Saramago',1995,1),(2,'Tutunamayanlar','Oğuz Atay',1972,1),
  (3,'Kum Kitabı','Jorge Luis Borges',1975,2),(4,'Yaban','Yakup Kadri',1932,2),
  (5,'Sessiz Ev','Orhan Pamuk',1983,3),(6,'Anayurt Oteli','Yusuf Atılgan',NULL,3),
  (7,'Tehlikeli Oyunlar','Oğuz Atay',1973,NULL);
INSERT INTO uye VALUES (1,'Ayşe','Demir','ayse@ornek.test','2023-02-14'),
  (2,'Mehmet','Kaya','mehmet@ornek.test','2023-05-30'),(3,'Zeynep','Arslan',NULL,'2024-01-09'),
  (4,'Emre','Yıldız','emre@ornek.test','2024-03-22'),(5,'Selin','Aydın',NULL,'2024-11-05'),
  (6,'Burak','Şahin','burak@ornek.test','2025-01-18');
INSERT INTO odunc VALUES (1,1,1,'2025-01-10','2025-01-24'),(2,2,1,'2025-02-02','2025-02-20'),
  (3,1,2,'2025-02-11',NULL),(4,3,3,'2025-03-01','2025-03-15'),(5,4,3,'2025-03-18','2025-04-02'),
  (6,1,4,'2025-04-05','2025-04-19'),(7,5,4,'2025-04-21',NULL),(8,2,5,'2025-05-02','2025-05-30'),
  (9,7,1,'2025-05-14','2025-05-28'),(10,3,5,'2025-06-03',NULL),(11,6,2,'2025-06-11','2025-06-25'),
  (12,4,4,'2025-06-20','2025-07-04');
SQL
```

Görev şudur: iade edilmemiş ödünç kayıtlarını, kitabın başlığı ve üyenin adıyla birlikte
listele. Bu, ödünç servisinin en sık çağrılan okuma işidir; kurs boyunca aynı görev
farklı katmanlardan geçerek yeniden ele alınacak.

## Doğrudan Sorgu

İlk yaklaşımda sorgu elle yazılır. Veritabanı üç bağıntıyı tek adımda birleştirir, uygulama
gelen satırları kendi nesnesine çevirir.

```js
// dogrudan.mjs — acik odunc listesini tek sorguyla kurar
import { DatabaseSync } from "node:sqlite";

const db = new DatabaseSync("kutuphane.db");
let sorguSayisi = 0;

const sql = `SELECT o.odunc_id, o.alis_tarihi, k.baslik, u.ad, u.soyad
             FROM odunc o
             JOIN kitap k ON k.kitap_id = o.kitap_id
             JOIN uye  u ON u.uye_id  = o.uye_id
             WHERE o.iade_tarihi IS NULL
             ORDER BY o.odunc_id`;

sorguSayisi += 1;
const satirlar = db.prepare(sql).all();

// Satir -> nesne cevrimi elle yazilir.
const acikOdunc = satirlar.map((s) => ({
  id: s.odunc_id,
  alisTarihi: s.alis_tarihi,
  kitapBasligi: s.baslik,
  uyeAdi: `${s.ad} ${s.soyad}`,
}));

for (const o of acikOdunc) console.log(`${o.id}  ${o.alisTarihi}  ${o.kitapBasligi}  ${o.uyeAdi}`);
console.log("calistirilan sorgu sayisi:", sorguSayisi);
```

```sh
node dogrudan.mjs
```

```
3  2025-02-11  Körlük  Mehmet Kaya
7  2025-04-21  Sessiz Ev  Emre Yıldız
10  2025-06-03  Kum Kitabı  Selin Aydın
calistirilan sorgu sayisi: 1
```

Tek sorgu. Veritabanına giden istek sayısı bir, dönen satır sayısı üç, aktarılan sütun
sayısı beş. Bu yaklaşımın kazancı doğrudan görünür: sorgu metni gözle okunabilir, plan
üzerinde denetim tamdır, sonuç kümesinin biçimi yazan kişinin elindedir.

Bedeli de aynı yerdedir. `satirlar.map` çağrısındaki çevrim elle yazılmıştır. Ödünç
kaydına yeni bir sütun eklendiğinde hem SQL hem çevrim güncellenir. Uygulamada bu görevin
on beş varyantı varsa, çevrim on beş kez yazılır.

## Bildirilen Eşleme

İkinci yaklaşım çevrimi bir kez bildirir. Hangi tipin hangi tabloya, hangi alanın hangi
sütuna karşılık geldiği bir eşleme tablosunda durur; SQL o tablodan üretilir.

```js
// esleyici.mjs — bildirilen esleme tablosundan SQL ureten kucuk esleyici
import { DatabaseSync } from "node:sqlite";

const db = new DatabaseSync("kutuphane.db");
export const uretilen = [];

const esleme = {
  Odunc: {
    tablo: "odunc", anahtar: "odunc_id",
    alanlar: { id: "odunc_id", alisTarihi: "alis_tarihi", iadeTarihi: "iade_tarihi" },
    iliskiler: { kitap: { hedef: "Kitap", yerel: "kitap_id" },
                 uye:   { hedef: "Uye",   yerel: "uye_id" } },
  },
  Kitap: { tablo: "kitap", anahtar: "kitap_id",
           alanlar: { id: "kitap_id", baslik: "baslik", yazar: "yazar" }, iliskiler: {} },
  Uye:   { tablo: "uye", anahtar: "uye_id",
           alanlar: { id: "uye_id", ad: "ad", soyad: "soyad" }, iliskiler: {} },
};

const secimListesi = (tip) => [
  ...Object.values(esleme[tip].alanlar),
  ...Object.values(esleme[tip].iliskiler).map((i) => i.yerel),
].join(", ");

function calistir(sql, ...bagli) {
  uretilen.push(sql);
  return db.prepare(sql).all(...bagli);
}

function nesneyeCevir(tip, satir) {
  const m = esleme[tip];
  const nesne = {};
  for (const [ad, sutun] of Object.entries(m.alanlar)) nesne[ad] = satir[sutun];
  // Iliski, okundugu anda yuklenir: getter cagrildikca sorgu calisir.
  for (const [ad, iliski] of Object.entries(m.iliskiler)) {
    const kimlik = satir[iliski.yerel];
    Object.defineProperty(nesne, ad, { get: () => bul(iliski.hedef, kimlik) });
  }
  return nesne;
}

export function bul(tip, kimlik) {
  const m = esleme[tip];
  const satir = calistir(`SELECT ${secimListesi(tip)} FROM ${m.tablo} WHERE ${m.anahtar} = ?`, kimlik)[0];
  return satir ? nesneyeCevir(tip, satir) : null;
}

export function ara(tip, kosul) {
  const m = esleme[tip];
  const sql = `SELECT ${secimListesi(tip)} FROM ${m.tablo} WHERE ${kosul} ORDER BY ${m.anahtar}`;
  return calistir(sql).map((satir) => nesneyeCevir(tip, satir));
}
```

Eşleyicinin yaptığı iş üç maddede toplanır: eşlemeden seçim listesi kurar, sonucu nesneye
çevirir, ilişkileri okunduğu anda yükler. Son madde kritik olandır. `kitap` özelliği
gerçek bir değer değil, çağrıldığında sorgu çalıştıran bir okuyucudur. Bu davranışa
**tembel yükleme** (lazy loading) denir.

Çağıran taraf artık SQL görmez.

```js
// esleyici-kullan.mjs — ayni listeyi esleyiciyle kurar ve uretilen SQL'i sayar
import { ara, uretilen } from "./esleyici.mjs";

for (const o of ara("Odunc", "iade_tarihi IS NULL")) {
  console.log(`${o.id}  ${o.alisTarihi}  ${o.kitap.baslik}  ${o.uye.ad} ${o.uye.soyad}`);
}

console.log("calistirilan sorgu sayisi:", uretilen.length);
for (const sql of uretilen) console.log("  " + sql.replace(/\s+/g, " "));
```

```sh
node esleyici-kullan.mjs
```

```
3  2025-02-11  Körlük  Mehmet Kaya
7  2025-04-21  Sessiz Ev  Emre Yıldız
10  2025-06-03  Kum Kitabı  Selin Aydın
calistirilan sorgu sayisi: 10
  SELECT odunc_id, alis_tarihi, iade_tarihi, kitap_id, uye_id FROM odunc WHERE iade_tarihi IS NULL ORDER BY odunc_id
  SELECT kitap_id, baslik, yazar FROM kitap WHERE kitap_id = ?
  SELECT uye_id, ad, soyad FROM uye WHERE uye_id = ?
  SELECT uye_id, ad, soyad FROM uye WHERE uye_id = ?
  SELECT kitap_id, baslik, yazar FROM kitap WHERE kitap_id = ?
  SELECT uye_id, ad, soyad FROM uye WHERE uye_id = ?
  SELECT uye_id, ad, soyad FROM uye WHERE uye_id = ?
  SELECT kitap_id, baslik, yazar FROM kitap WHERE kitap_id = ?
  SELECT uye_id, ad, soyad FROM uye WHERE uye_id = ?
  SELECT uye_id, ad, soyad FROM uye WHERE uye_id = ?
```

Çıktı aynı, sorgu sayısı bir yerine on. Sayı iki kaynaktan gelir. Birincisi tembel
yüklemenin doğası: üç ödünç kaydının her biri için kitap ve üye ayrı ayrı okunur. İkincisi
daha sinsidir. Yazdırma satırında `o.uye.ad` ve `o.uye.soyad` iki ayrı okuma yapar; her
okuma okuyucuyu yeniden çalıştırır, dolayısıyla üye sorgusu ödünç başına iki kez döner.
Üçü birlikte 1 + 3 + 6 = 10 eder.

Bu, eşleyici kullanmanın merkezî riskidir. **Sorgu, kod okunurken görünmez.** Bir özellik
erişimi ile bir veritabanı gidiş-dönüşü arasındaki fark, yazan kişinin gözünden kalkmıştır.

## Kimlik Haritası

Sayının bir kısmı eşleyicinin kendi içinde kapatılabilir. Aynı tipten aynı kimliğe sahip
satır bir kez okunup saklanırsa, ikinci erişim sorgu üretmez. Yüklenmiş nesneleri kimliğine
göre tutan bu yapıya **kimlik haritası** (identity map) denir.

```js
// esleyici-kimlik.mjs — yuklenen nesneleri kimligine gore saklayan surum
import { DatabaseSync } from "node:sqlite";

const db = new DatabaseSync("kutuphane.db");
export const uretilen = [];
const kimlikHaritasi = new Map(); // tip:kimlik -> nesne

const esleme = {
  Odunc: {
    tablo: "odunc", anahtar: "odunc_id",
    alanlar: { id: "odunc_id", alisTarihi: "alis_tarihi", iadeTarihi: "iade_tarihi" },
    iliskiler: { kitap: { hedef: "Kitap", yerel: "kitap_id" },
                 uye:   { hedef: "Uye",   yerel: "uye_id" } },
  },
  Kitap: { tablo: "kitap", anahtar: "kitap_id",
           alanlar: { id: "kitap_id", baslik: "baslik", yazar: "yazar" }, iliskiler: {} },
  Uye:   { tablo: "uye", anahtar: "uye_id",
           alanlar: { id: "uye_id", ad: "ad", soyad: "soyad" }, iliskiler: {} },
};

const secimListesi = (tip) => [
  ...Object.values(esleme[tip].alanlar),
  ...Object.values(esleme[tip].iliskiler).map((i) => i.yerel),
].join(", ");

function calistir(sql, ...bagli) {
  uretilen.push(sql);
  return db.prepare(sql).all(...bagli);
}

function nesneyeCevir(tip, satir) {
  const m = esleme[tip];
  const nesne = {};
  for (const [ad, sutun] of Object.entries(m.alanlar)) nesne[ad] = satir[sutun];
  for (const [ad, iliski] of Object.entries(m.iliskiler)) {
    const kimlik = satir[iliski.yerel];
    Object.defineProperty(nesne, ad, { get: () => bul(iliski.hedef, kimlik) });
  }
  return nesne;
}

export function bul(tip, kimlik) {
  const kutu = `${tip}:${kimlik}`;
  if (kimlikHaritasi.has(kutu)) return kimlikHaritasi.get(kutu);
  const m = esleme[tip];
  const satir = calistir(`SELECT ${secimListesi(tip)} FROM ${m.tablo} WHERE ${m.anahtar} = ?`, kimlik)[0];
  const nesne = satir ? nesneyeCevir(tip, satir) : null;
  kimlikHaritasi.set(kutu, nesne);
  return nesne;
}

export function ara(tip, kosul) {
  const m = esleme[tip];
  const sql = `SELECT ${secimListesi(tip)} FROM ${m.tablo} WHERE ${kosul} ORDER BY ${m.anahtar}`;
  return calistir(sql).map((satir) => nesneyeCevir(tip, satir));
}
```

```js
// kimlik-kullan.mjs — kimlik haritali surumle ayni listeyi kurar
import { ara, uretilen } from "./esleyici-kimlik.mjs";

for (const o of ara("Odunc", "iade_tarihi IS NULL")) {
  console.log(`${o.id}  ${o.alisTarihi}  ${o.kitap.baslik}  ${o.uye.ad} ${o.uye.soyad}`);
}

console.log("calistirilan sorgu sayisi:", uretilen.length);
```

```sh
node kimlik-kullan.mjs
```

```
3  2025-02-11  Körlük  Mehmet Kaya
7  2025-04-21  Sessiz Ev  Emre Yıldız
10  2025-06-03  Kum Kitabı  Selin Aydın
calistirilan sorgu sayisi: 7
```

On sorgu yediye indi: aynı üyeye iki kez dokunmak artık bir kez okuyor. Kimlik haritası
ikinci bir güvence daha verir. Aynı satır için tek bir nesne döndüğünden, bir yerde
yapılan değişiklik başka bir yerde okunan kopyada da görünür; iki farklı kopyanın
birbirinin üzerine yazması olasılığı ortadan kalkar.

Yedi sayısı hâlâ birden büyüktür ve bu sayı listedeki kayıt sayısıyla birlikte artar.
Üç kayıtta yedi, üç yüz kayıtta yüzlerce sorgu demektir. Bu tabloya **N+1 sorgu sorunu**
denir; Başarım Sorunları konusunda ölçülüp giderilecek.

## Ödünleşim Nerede Duruyor

İki yaklaşım arasındaki seçim "hangisi daha iyi" sorusuna indirgenmez; ne tür maliyetin
düşürüldüğüne bakılır.

Doğrudan sorgu **denetimi** maksimize eder. Çalışacak SQL yazıldığı gibidir; hangi
sütunların döndüğü, birleştirmenin nasıl kurulduğu, planın neye benzeyeceği görünür.
Karmaşık raporlar, toplama işlevleri ve elle ayarlanmış birleştirmeler bu tarafta kalır.
Bedeli tekrardır: her okuma işi için çevrim kodu yeniden yazılır.

Eşleyici **tekrarı** düşürür. Eşleme bir kez bildirilir, onlarca okuma işi ondan üretilir;
şemaya sütun eklemek tek bir yerde değişiklik ister. Bedeli görünürlüğün kaybıdır: kaç
sorgu çalıştığı ancak sayılarak öğrenilir.

Pratikte iki yaklaşım aynı uygulamada yan yana durur. Ayrım işin türüne göre yapılır:
kayıt yaşam döngüsü ve tekil nesne okumaları eşleyiciye, çok tablolu raporlar ve toplu
işlemler doğrudan sorguya bırakılır. Kararın koşulu ölçülebilirliktir. Hangi yaklaşım
seçilirse seçilsin, üretilen SQL'i ve sorgu sayısını görebildiğin bir kanca bulunmalıdır;
bu dersteki `uretilen` dizisi o kancanın en küçük biçimidir.

## Özet

- Aynı okuma işi doğrudan yazılan SQL ile de, bildirilen eşlemeden SQL üreten bir
  eşleyiciyle de kurulabilir; iki yol aynı çıktıyı verir.
- Ölçüm farkı gösterdi: doğrudan sorgu 1, tembel yüklemeli eşleyici 10, kimlik haritası
  eklenmiş eşleyici 7 sorgu çalıştırdı.
- Tembel yükleme, özellik erişimini görünmez bir veritabanı gidiş-dönüşüne çevirir; aynı
  ilişkiye iki kez dokunmak iki sorgu üretir.
- Kimlik haritası aynı kimliğin tek nesneyle temsil edilmesini sağlar, yinelenen okumaları
  keser ve kopyaların birbirini ezmesini önler.
- Doğrudan sorgu denetimi, eşleyici tekrarı azaltır; seçim işin türüne göre yapılır ve her
  iki durumda da üretilen SQL sayılabilir olmalıdır.

## Sonraki Adım

Eşleyicinin ürettiği SQL beklenenden fazla sorgu çalıştırdı, ama asıl sorun sayıda değil.
Eşleme tablosunda `Odunc` tipi ile `odunc` bağıntısı bire bir örtüşüyordu; her alan bir
sütuna, her ilişki bir yabancı anahtara denk düştü. Gerçek alan modellerinde bu örtüşme
kurulmaz: kalıtım hiyerarşisinin tek bir tablo karşılığı yoktur, nesne kimliği ile birincil
anahtar aynı şey değildir, çift yönlü bir ilişkinin ilişkisel modelde tek bir sahibi
bulunur. Sonraki ders bu uyuşmazlıkların nereden çıktığını ve her birinin hangi eşleme
kararıyla kapatıldığını inceler.
