İçeriğe geç
academia.sh

Ders 02 / 21

Nesne–İlişki Uyumsuzluğu

Nesne modeli ile ilişkisel modelin örtüşmediği dört nokta: kimlik, kalıtım, ilişki yönü ve tanecik. Her uyumsuzluğun eşleme kararıyla nasıl kapatıldığı ölçülerek gösterilir.

İçindekiler

Önceki ders küçük bir eşleyici kurdu ve orada Odunc tipi ile odunc bağıntısı bire bir örtüştü: her alan bir sütun, her ilişki bir yabancı anahtardı. Bu örtüşme kolay bir örnekten gelir, kuraldan değil.

Nesne modeli ile ilişkisel model iki ayrı kuramın ürünüdür. Nesne modelinde kimlik başvurudur, kalıtım vardır, bağlar iki yönlüdür ve bir nesnenin içine başka nesneler gömülebilir. İlişkisel modelde kimlik anahtar değeridir, kalıtım yoktur, bağ yabancı anahtarla tek yönlü kurulur ve tablo düz bir demet listesidir. Bu iki modelin örtüşmeyen noktalarına nesne–ilişki uyumsuzluğu (object–relational impedance mismatch) denir. Uyumsuzluk bir hata değil, kapatılması gereken bir açıklıktır. Ders dört açıklığı sırayla ölçer.

Aşağıdaki blok bu dersin ilk bölümünde kullanılan veritabanını kurar.

rm -f kutuphane.db
sqlite3 kutuphane.db <<'SQL'
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 kitap (kitap_id INTEGER PRIMARY KEY, baslik TEXT NOT NULL, yazar TEXT NOT NULL,
                    basim_yili INTEGER);
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 uye VALUES (1,'Ayşe','Demir','[email protected]','2023-02-14'),
  (2,'Mehmet','Kaya','[email protected]','2023-05-30'),(3,'Zeynep','Arslan',NULL,'2024-01-09');
INSERT INTO kitap VALUES (1,'Körlük','José Saramago',1995),(6,'Anayurt Oteli','Yusuf Atılgan',NULL);
INSERT INTO odunc VALUES (3,1,2,'2025-02-11',NULL),(11,6,2,'2025-06-11','2025-06-25');
SQL

Kimlik Uyumsuzluğu

Bir bağıntıda iki satırın “aynı” olması birincil anahtarlarının eşit olması demektir. Nesne dünyasında ise iki tür eşitlik vardır: aynı başvuru olmak ve aynı değeri taşımak. Eşleyici satırı nesneye çevirirken bu iki eşitlik ayrışır.

// kimlik-farki.mjs — ayni satirin iki kez okunmasi iki ayri nesne uretir
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync("kutuphane.db");

const oku = (id) => db.prepare("SELECT uye_id, ad, soyad FROM uye WHERE uye_id = ?").get(id);

const a = oku(2);
const b = oku(2);

console.log("ayni satir mi (birincil anahtar):", a.uye_id === b.uye_id);
console.log("ayni nesne mi (basvuru):        ", a === b);

a.ad = "Mehmet Ali";
console.log("a.ad:", a.ad, "| b.ad:", b.ad);

const harita = new Map();
const okuHaritali = (id) => {
  if (!harita.has(id)) harita.set(id, oku(id));
  return harita.get(id);
};
const c = okuHaritali(2), d = okuHaritali(2);
c.ad = "Mehmet Ali";
console.log("kimlik haritasiyla ayni nesne mi:", c === d, "| d.ad:", d.ad);
node kimlik-farki.mjs
ayni satir mi (birincil anahtar): true
ayni nesne mi (basvuru):         false
a.ad: Mehmet Ali | b.ad: Mehmet
kimlik haritasiyla ayni nesne mi: true | d.ad: Mehmet Ali

İki okuma aynı satırı getirdi ama iki ayrı nesne üretti. Birinde yapılan değişiklik ötekinde görünmüyor; iki nesne de aynı satıra yazılırsa biri diğerini eziyor. Çıktının son satırı çözümü gösteriyor: önceki derste kurulan kimlik haritası, bir isteğin ömrü boyunca her satırı tek nesneyle temsil eder ve iki eşitliği yeniden çakıştırır.

Bunun ikinci bir sonucu vardır. Veritabanına henüz yazılmamış bir nesnenin birincil anahtarı yoktur. Anahtarı kimliğin tek ölçütü yaparsan, kaydedilmemiş iki nesne birbirine eşit görünür. Bu yüzden eşleme katmanında yeni nesneler ya uygulama tarafından üretilen bir kimlikle doğar ya da anahtar atanana kadar yalnız başvuru eşitliğiyle karşılaştırılır.

Kalıtım Uyumsuzluğu

Kütüphane hem basılı hem sesli eserler tutmaya başlarsa nesne modelinde bir üst tip ve iki alt tip belirir: ortak alanlar üstte, özel alanlar altta. İlişkisel modelde alt tip diye bir şey yoktur. Hiyerarşi üç yoldan tabloya indirilebilir.

rm -f kalitim.db
sqlite3 kalitim.db <<'SQL'
-- 1. Tek tablo: butun alt turler tek baginti, ayirt edici sutunla
CREATE TABLE tek_tablo_eser (
  eser_id INTEGER PRIMARY KEY, tur TEXT NOT NULL, baslik TEXT NOT NULL, yazar TEXT NOT NULL,
  sayfa_sayisi INTEGER, sure_dakika INTEGER, seslendiren TEXT);
INSERT INTO tek_tablo_eser VALUES
  (1,'basili','Körlük','José Saramago',352,NULL,NULL),
  (2,'basili','Tutunamayanlar','Oğuz Atay',724,NULL,NULL),
  (3,'sesli','Kum Kitabı','Jorge Luis Borges',NULL,214,'Deniz Ak'),
  (4,'sesli','Yaban','Yakup Kadri',NULL,398,'Ece Gür');

-- 2. Somut tur basina tablo: ortak sutunlar her tabloda tekrarlanir
CREATE TABLE somut_basili (eser_id INTEGER PRIMARY KEY, baslik TEXT NOT NULL,
  yazar TEXT NOT NULL, sayfa_sayisi INTEGER NOT NULL);
CREATE TABLE somut_sesli (eser_id INTEGER PRIMARY KEY, baslik TEXT NOT NULL,
  yazar TEXT NOT NULL, sure_dakika INTEGER NOT NULL, seslendiren TEXT NOT NULL);
INSERT INTO somut_basili VALUES (1,'Körlük','José Saramago',352),(2,'Tutunamayanlar','Oğuz Atay',724);
INSERT INTO somut_sesli VALUES (3,'Kum Kitabı','Jorge Luis Borges',214,'Deniz Ak'),
  (4,'Yaban','Yakup Kadri',398,'Ece Gür');

-- 3. Tur basina tablo: ortak alanlar ustte, ozel alanlar alt bagintida
CREATE TABLE eser (eser_id INTEGER PRIMARY KEY, tur TEXT NOT NULL,
  baslik TEXT NOT NULL, yazar TEXT NOT NULL);
CREATE TABLE eser_basili (eser_id INTEGER PRIMARY KEY REFERENCES eser(eser_id),
  sayfa_sayisi INTEGER NOT NULL);
CREATE TABLE eser_sesli (eser_id INTEGER PRIMARY KEY REFERENCES eser(eser_id),
  sure_dakika INTEGER NOT NULL, seslendiren TEXT NOT NULL);
INSERT INTO eser VALUES (1,'basili','Körlük','José Saramago'),(2,'basili','Tutunamayanlar','Oğuz Atay'),
  (3,'sesli','Kum Kitabı','Jorge Luis Borges'),(4,'sesli','Yaban','Yakup Kadri');
INSERT INTO eser_basili VALUES (1,352),(2,724);
INSERT INTO eser_sesli VALUES (3,214,'Deniz Ak'),(4,398,'Ece Gür');
SQL

Üçü de aynı dört eseri saklıyor. Fark, “bütün eserleri getir” isteğinin maliyetinde görünür.

// kalitim-olc.mjs — uc esleme stratejisinde sorgu ve bos deger sayisi
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync("kalitim.db");

function olc(ad, sorgular) {
  let bosDeger = 0, satir = 0;
  for (const s of sorgular) {
    for (const r of db.prepare(s).all()) {
      satir += 1;
      bosDeger += Object.values(r).filter((v) => v === null).length;
    }
  }
  console.log(`${ad.padEnd(22)} sorgu=${sorgular.length}  satir=${satir}  bos_deger=${bosDeger}`);
}

olc("tek tablo", ["SELECT * FROM tek_tablo_eser"]);
olc("somut tur basina", ["SELECT * FROM somut_basili", "SELECT * FROM somut_sesli"]);
olc("tur basina (birlesik)", [
  `SELECT e.eser_id, e.tur, e.baslik, e.yazar, b.sayfa_sayisi, s.sure_dakika, s.seslendiren
   FROM eser e LEFT JOIN eser_basili b ON b.eser_id = e.eser_id
               LEFT JOIN eser_sesli  s ON s.eser_id = e.eser_id`,
]);
node kalitim-olc.mjs
tek tablo              sorgu=1  satir=4  bos_deger=6
somut tur basina       sorgu=2  satir=4  bos_deger=0
tur basina (birlesik)  sorgu=1  satir=4  bos_deger=6

Sayılar üç stratejinin karakterini veriyor. Tek tablo tek sorguyla okur ama boş değer üretir; alt tipe özgü her sütun, öteki alt tipin satırlarında NULL kalır. Alt tipe özgü alanlar NOT NULL yapılamaz, çünkü kısıt bütün satırlara uygular. Somut tür başına tablo hiç boş değer üretmez ve NOT NULL kısıtına izin verir; bedeli sorgu sayısıdır ve üst tip üzerinden sorulan her soru birleşim gerektirir. Tür başına tablo hiyerarşiyi şemaya birebir yansıtır; okumada dış birleştirme, yazmada iki tabloya ekleme ister.

Somut tür başına tablo bir kısıt daha kaybettirir: hiyerarşinin tamamına başvuran bir yabancı anahtar kurulamaz.

sqlite3 kalitim.db <<'SQL'
PRAGMA foreign_keys = ON;
CREATE TABLE rezervasyon (rez_id INTEGER PRIMARY KEY,
  eser_id INTEGER NOT NULL REFERENCES somut_basili(eser_id), uye_id INTEGER NOT NULL);
INSERT INTO rezervasyon VALUES (1, 1, 3);
INSERT INTO rezervasyon VALUES (2, 3, 3);
SQL
Runtime error near line 5: FOREIGN KEY constraint failed (19)

Basılı esere yapılan rezervasyon geçti, sesli esere yapılan reddedildi. Yabancı anahtar tek bir bağıntıyı gösterebilir; hiyerarşi iki bağıntıya bölündüğünde başvuru bütünlüğü uygulamaya devredilir. Üst tipe başvuru gerekiyorsa tür başına tablo stratejisi seçilir, çünkü orada eser bağıntısı bütün hiyerarşiyi temsil eder.

İlişki Yönü Uyumsuzluğu

Nesne grafiğinde bir bağ tek yönlüdür; iki yön isteniyorsa iki ayrı başvuru tutulur ve ikisi elle tutarlı kılınır. İlişkisel modelde tek bir yabancı anahtar sütunu her iki yönü de yanıtlar.

// yon-farki.mjs — nesne tarafinda iki yon, iliskisel tarafta tek yabanci anahtar
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync("kutuphane.db");

// Nesne grafigi: iki yonlu bag. Iki taraf da elle tutulur.
const uye = { id: 2, ad: "Mehmet", oduncler: [] };
const odunc = { id: 99, kitapId: 6, uye: null };

odunc.uye = uye;                       // yalniz bir yon kuruldu
console.log("odunc.uye.id:", odunc.uye.id);
console.log("uye.oduncler.length:", uye.oduncler.length);

uye.oduncler.push(odunc);              // ikinci yon elle eklenir
console.log("ikinci yon kurulduktan sonra:", uye.oduncler.length);

// Iliskisel taraf: tek sutun iki yonu de tasir.
db.exec("CREATE TEMP TABLE gecici AS SELECT * FROM odunc");
db.prepare("INSERT INTO gecici VALUES (?,?,?,?,NULL)").run(99, 6, 2, "2025-07-01");
const ileri = db.prepare("SELECT uye_id FROM gecici WHERE odunc_id = 99").get();
const geri = db.prepare("SELECT odunc_id FROM gecici WHERE uye_id = 2 ORDER BY odunc_id").all();
console.log("ileri yon (odunc -> uye):", ileri.uye_id);
console.log("geri yon (uye -> odunc):", geri.map((r) => r.odunc_id).join(", "));
node yon-farki.mjs
odunc.uye.id: 2
uye.oduncler.length: 0
ikinci yon kurulduktan sonra: 1
ileri yon (odunc -> uye): 2
geri yon (uye -> odunc): 3, 11, 99

Nesne tarafında bir yön kurulduğunda öteki yön boş kaldı; ilişkisel tarafta tek satır her iki soruyu da yanıtladı. Bu farkın eşleme kararı ilişkinin sahibini seçmektir: hangi tarafın yazması yabancı anahtarı değiştirir, öteki taraf yalnız okur. Sahip seçilmezse iki taraftan gelen iki güncelleme birbiriyle çelişir ve hangisinin diske indiği çağrı sırasına bağlı kalır.

Çoktan çoğa ilişkilerde uyumsuzluk daha da belirgindir. Nesne tarafında iki listeyle kurulan bağın ilişkisel karşılığı yoktur; SQL Temelleri kursunda tanıtılan bağlantı bağıntısı zorunludur. Bağlantı bağıntısının kendi öznitelikleri varsa (ödünç tarihi gibi) bu bağıntı artık bir bağ değil, kendi başına bir varlıktır ve nesne modeline de varlık olarak yansımalıdır.

Tanecik Uyumsuzluğu

Nesne modelinde her kavram ayrı bir tip olabilir. Üyenin adresi Adres tipiyle temsil edilirse, bu tipin kimliği yoktur; iki adres aynı alanları taşıyorsa aynıdır. Kimliği olmayan, yalnız değeriyle tanımlanan bu tiplere değer nesnesi (value object) denir.

İlişkisel modelde her tablonun bir birincil anahtarı vardır. Değer nesnesine tablo verirsen ona olmayan bir kimlik uydurmuş olursun; vermezsen alanlarını sahip bağıntının içine gömersin. İkinci karar gömme (embedding) adını alır ve genellikle doğru olandır: uye bağıntısı adres_il, adres_ilce, adres_posta_kodu sütunlarını taşır, Adres tipi eşleyici tarafından bu üç sütundan kurulur.

Karar ölçütü paylaşımdır. Değer nesnesi birden çok sahip tarafından paylaşılıyor ve tek noktadan güncellenmesi gerekiyorsa gömme yetmez; o zaman kavram aslında bir varlıktır ve kimliği hak eder. Kütüphane örneğinde adres üyeye özeldir, gömülür; şube ise kitap ve personel tarafından paylaşılır, kendi bağıntısında durur.

Özet

  • Nesne–ilişki uyumsuzluğu kimlik, kalıtım, ilişki yönü ve tanecik olmak üzere dört noktada ortaya çıkar; her biri ayrı bir eşleme kararıyla kapatılır.
  • Aynı satırın iki kez okunması iki ayrı nesne üretti; kimlik haritası satır ile nesne arasındaki tekliği geri kurdu.
  • Kalıtımın üç stratejisi ölçüldü: tek tablo 1 sorgu ve 6 boş değer, somut tür başına tablo 2 sorgu ve 0 boş değer, tür başına tablo 1 sorgu ve 6 boş değer üretti.
  • Somut tür başına tablo stratejisinde hiyerarşinin tamamına başvuran yabancı anahtar kurulamadı; başvuru bütünlüğü uygulamaya devredildi.
  • İlişki yönü uyumsuzluğu sahibin seçilmesiyle, tanecik uyumsuzluğu değer nesnesinin sahip bağıntıya gömülmesiyle kapatılır.

Sonraki Adım

Uyumsuzluğu kapatan kararlar şimdiye kadar çağıran kodun içine dağıldı: hangi stratejinin seçildiği, hangi ilişkinin sahibi olduğu, hangi alanın gömüldüğü bilgisi sorguyu yazan her yerde tekrarlanıyor. Bu bilgiyi tek bir yerde toplayan, çağıran tarafa yalnız alan kavramlarıyla konuşan bir arayüz bırakan yapıya depo kalıbı denir. Sonraki ders bu arayüzü kurar ve kalıcılık ayrıntısının gerçekten gizlendiğini, deponun sahte bir uygulamasıyla iş mantığını veritabanı olmadan sınayarak gösterir.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat