Türkiye'de ilk Mobil & PC Aynı Anda Metin2 Oyna. Triarchonline kalıcı ve uzun ömürlü yapısı ile 24 Temmuz'da açılıyor. | 1-99 Mobil Metin2 Triarch HEMEN TIKLA!
[NODE JS] Prisma ile İlişkisel Veri Modelleme
Veri modelleme, herhangi bir uygulamanın temelini oluşturan kritik bir süreçtir. Doğru veri modeli, uygulamanızın performansını, ölçeklenebilirliğini ve bakım kolaylığını doğrudan etkiler. Prisma Schema Language ile veri modellemesi yapmak, hem okunabilir hem de güçlü bir deneyim sunar. Şema dosyanız, veritabanınızın tek gerçek kaynağı (single source of truth) haline gelir.
İlişkisel veri modellemenin üç temel ilişki türü vardır: bire bir (1:1), bire çok (1:N) ve çoka çok (N:M). Prisma'da bu ilişkileri tanımlamak son derece kolay ve anlaşılır. Her ilişki, ilgili modellerin her ikisinde de tanımlanmalıdır — Prisma buna "ilişki alanları" der.
Bire bir ilişkide, bir kaydın diğer tablodan sadece bir karşılığı vardır. Örneğin her kullanıcının tek bir profili olması. Prisma'da bunu @relation dekoratörü ve @unique kısıtlamasıyla yabancı anahtar alanına uygulayarak tanımlarız. İsteğe bağlı (optional) ilişkiler için türün sonuna soru işareti (?) eklenir.
Bire çok ilişki en yaygın olanıdır. Bir kategorinin birden fazla ürünü olması, bir yazarın birden fazla makalesi olması gibi. Prisma'da "bir" tarafında model türü dizisi (örneğin Urun[]), "çok" tarafında ise tek model türü ve yabancı anahtar alanı tanımlanır.
Çoka çok ilişkiler iki şekilde tanımlanabilir: örtük (implicit) ve açık (explicit). Örtük ilişkide Prisma ara tabloyu otomatik oluşturur. Açık ilişkide ise ara tabloyu kendiniz tanımlarsınız ve bu tabloya ek alanlar ekleyebilirsiniz. Ara tabloya ek veri eklemeniz gerekiyorsa (örneğin sipariş kalemi tablosundaki adet ve birim fiyat) açık ilişki kullanmalısınız.
Kendine referans veren (self-referencing) ilişkiler de Prisma'da desteklenir. Bir çalışanın yöneticisinin de bir çalışan olması, bir kategorinin alt kategorilerinin olması gibi durumlar bu şekilde modellenir. Bu tür ilişkilerde ilişki adlarının açıkça belirtilmesi gerekir.
JavaScript:
// Prisma ile İlişkisel Veri Modelleme
// ============================================
// prisma/schema.prisma - Kapsamlı İlişki Modelleri
// ============================================
/*
// === E-Ticaret Veri Modeli ===
// Kullanıcı - Temel kullanıcı bilgileri
model Kullanici {
id Int @id @default(autoincrement())
email String @unique
ad String
soyad String
sifre String
rol KullaniciRol @default(MUSTERI)
aktif Boolean @default(true)
// Bire Bir: Kullanıcı - Profil
profil Profil?
// Bire Çok: Kullanıcı - Adresler
adresler Adres[]
// Bire Çok: Kullanıcı - Siparişler
siparisler Siparis[]
// Bire Çok: Kullanıcı - Değerlendirmeler
degerlendirmeler Degerlendirme[]
// Bire Çok: Kullanıcı - Favoriler
favoriler Favori[]
olusturmaTarihi DateTime @default(now())
guncellemeTarihi DateTime @updatedAt
@@map("kullanicilar")
}
// Profil - Bire bir ilişki
model Profil {
id Int @id @default(autoincrement())
biyografi String? @db.Text
avatarUrl String?
telefon String?
dogumTarihi DateTime?
cinsiyet Cinsiyet?
// İlişki tanımı
kullanici Kullanici @relation(fields: [kullaniciId], references: [id], onDelete: Cascade)
kullaniciId Int @unique
@@map("profiller")
}
// Adres - Bire çok ilişki
model Adres {
id Int @id @default(autoincrement())
baslik String // "Ev", "İş" gibi
adSoyad String
telefon String
il String
ilce String
mahalle String
adresDetay String @db.Text
postaKodu String?
varsayilan Boolean @default(false)
kullanici Kullanici @relation(fields: [kullaniciId], references: [id], onDelete: Cascade)
kullaniciId Int
siparisler Siparis[]
@@map("adresler")
}
// Kategori - Kendine referans (self-relation)
model Kategori {
id Int @id @default(autoincrement())
ad String
slug String @unique
aciklama String?
gorselUrl String?
sira Int @default(0)
aktif Boolean @default(true)
// Kendine referans: Üst-alt kategori ilişkisi
ustKategori Kategori? @relation("KategoriHiyerarsisi", fields: [ustKategoriId], references: [id])
ustKategoriId Int?
altKategoriler Kategori[] @relation("KategoriHiyerarsisi")
urunler Urun[]
@@map("kategoriler")
}
// Ürün
model Urun {
id Int @id @default(autoincrement())
ad String
slug String @unique
aciklama String @db.Text
fiyat Decimal @db.Decimal(10, 2)
indirimOrani Int @default(0)
stok Int @default(0)
sku String @unique
agirlik Float?
aktif Boolean @default(true)
// Bire Çok: Kategori - Ürün
kategori Kategori @relation(fields: [kategoriId], references: [id])
kategoriId Int
// Bire Çok: Ürün - Görseller
gorseller UrunGorsel[]
// Bire Çok: Ürün - Varyantlar
varyantlar UrunVaryant[]
// Çoka Çok: Ürün - Etiketler (açık ilişki)
etiketler UrunEtiket[]
// Sipariş kalemleri
siparisKalemleri SiparisKalemi[]
// Değerlendirmeler
degerlendirmeler Degerlendirme[]
// Favoriler
favoriler Favori[]
olusturmaTarihi DateTime @default(now())
guncellemeTarihi DateTime @updatedAt
@@map("urunler")
@@index([kategoriId])
@@index([fiyat])
}
// Ürün Görselleri
model UrunGorsel {
id Int @id @default(autoincrement())
url String
altMetin String?
sira Int @default(0)
anaGorsel Boolean @default(false)
urun Urun @relation(fields: [urunId], references: [id], onDelete: Cascade)
urunId Int
@@map("urun_gorseller")
}
// Ürün Varyantları
model UrunVaryant {
id Int @id @default(autoincrement())
ad String // "Kırmızı - L", "Mavi - M"
sku String @unique
fiyatFarki Decimal @default(0) @db.Decimal(10, 2)
stok Int @default(0)
renk String?
beden String?
aktif Boolean @default(true)
urun Urun @relation(fields: [urunId], references: [id], onDelete: Cascade)
urunId Int
@@map("urun_varyantlar")
}
// Etiketler
model Etiket {
id Int @id @default(autoincrement())
ad String @unique
slug String @unique
urunler UrunEtiket[]
@@map("etiketler")
}
// Ürün-Etiket ara tablosu (açık çoka çok)
model UrunEtiket {
urun Urun @relation(fields: [urunId], references: [id], onDelete: Cascade)
urunId Int
etiket Etiket @relation(fields: [etiketId], references: [id], onDelete: Cascade)
etiketId Int
@@id([urunId, etiketId])
@@map("urun_etiketler")
}
// Sipariş
model Siparis {
id Int @id @default(autoincrement())
siparisNo String @unique @default(cuid())
durum SiparisDurumu @default(BEKLEMEDE)
toplamTutar Decimal @db.Decimal(10, 2)
kargoUcreti Decimal @default(0) @db.Decimal(10, 2)
indirimTutari Decimal @default(0) @db.Decimal(10, 2)
not String?
musteri Kullanici @relation(fields: [musteriId], references: [id])
musteriId Int
teslimatAdresi Adres @relation(fields: [adresId], references: [id])
adresId Int
kalemler SiparisKalemi[]
siparisTarihi DateTime @default(now())
guncellemeTarihi DateTime @updatedAt
@@map("siparisler")
@@index([musteriId])
@@index([durum])
}
// Sipariş Kalemi - Açık çoka çok (Sipariş-Ürün arasında ek verili)
model SiparisKalemi {
id Int @id @default(autoincrement())
adet Int
birimFiyat Decimal @db.Decimal(10, 2)
indirimOrani Int @default(0)
siparis Siparis @relation(fields: [siparisId], references: [id], onDelete: Cascade)
siparisId Int
urun Urun @relation(fields: [urunId], references: [id])
urunId Int
@@map("siparis_kalemleri")
}
// Değerlendirme
model Degerlendirme {
id Int @id @default(autoincrement())
puan Int // 1-5
yorum String? @db.Text
onaylandi Boolean @default(false)
kullanici Kullanici @relation(fields: [kullaniciId], references: [id])
kullaniciId Int
urun Urun @relation(fields: [urunId], references: [id])
urunId Int
olusturmaTarihi DateTime @default(now())
@@unique([kullaniciId, urunId])
@@map("degerlendirmeler")
}
// Favori
model Favori {
kullanici Kullanici @relation(fields: [kullaniciId], references: [id], onDelete: Cascade)
kullaniciId Int
urun Urun @relation(fields: [urunId], references: [id], onDelete: Cascade)
urunId Int
eklenmeTarihi DateTime @default(now())
@@id([kullaniciId, urunId])
@@map("favoriler")
}
enum KullaniciRol {
MUSTERI
SATICI
ADMIN
}
enum Cinsiyet {
ERKEK
KADIN
BELIRTILMEMIS
}
enum SiparisDurumu {
BEKLEMEDE
ONAYLANDI
HAZIRLANIYOR
KARGODA
TESLIM_EDILDI
IPTAL_EDILDI
IADE_EDILDI
}
*/
// ============================================
// İlişkisel sorgulama örnekleri
// ============================================
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
// Derin ilişki sorgulama
async function urunDetayGetir(urunSlug) {
const urun = await prisma.urun.findUnique({
where: { slug: urunSlug },
include: {
kategori: {
include: {
ustKategori: true
}
},
gorseller: {
orderBy: { sira: 'asc' }
},
varyantlar: {
where: { aktif: true, stok: { gt: 0 } }
},
etiketler: {
include: { etiket: true }
},
degerlendirmeler: {
where: { onaylandi: true },
include: {
kullanici: {
select: { ad: true, soyad: true }
}
},
orderBy: { olusturmaTarihi: 'desc' },
take: 10
},
_count: {
select: {
degerlendirmeler: true,
favoriler: true
}
}
}
});
if (!urun) {
console.log('Ürün bulunamadı');
return null;
}
// Ortalama puan hesaplama
const puanOzeti = await prisma.degerlendirme.aggregate({
where: { urunId: urun.id, onaylandi: true },
_avg: { puan: true },
_count: { puan: true }
});
console.log(`Ürün: ${urun.ad}`);
console.log(`Kategori: ${urun.kategori.ad}`);
console.log(`Fiyat: ${urun.fiyat} TL`);
console.log(`Stok: ${urun.stok}`);
console.log(`Varyant sayısı: ${urun.varyantlar.length}`);
console.log(`Etiketler: ${urun.etiketler.map(e => e.etiket.ad).join(', ')}`);
console.log(`Ortalama puan: ${puanOzeti._avg.puan || 'Henüz yok'}`);
console.log(`Değerlendirme sayısı: ${puanOzeti._count.puan}`);
console.log(`Favori sayısı: ${urun._count.favoriler}`);
return urun;
}
// Sipariş oluşturma (Transaction ile)
async function siparisOlustur(musteriId, adresId, kalemler) {
return await prisma.$transaction(async (tx) => {
let toplamTutar = 0;
// Stok kontrolü ve toplam hesaplama
for (const kalem of kalemler) {
const urun = await tx.urun.findUnique({
where: { id: kalem.urunId }
});
if (!urun) throw new Error(`Ürün bulunamadı: ${kalem.urunId}`);
if (urun.stok < kalem.adet) {
throw new Error(`Yetersiz stok: ${urun.ad} (Mevcut: ${urun.stok}, İstenen: ${kalem.adet})`);
}
const birimFiyat = Number(urun.fiyat) * (1 - urun.indirimOrani / 100);
toplamTutar += birimFiyat * kalem.adet;
// Stok düşür
await tx.urun.update({
where: { id: urun.id },
data: { stok: { decrement: kalem.adet } }
});
}
// Kargo ücreti hesaplama
const kargoUcreti = toplamTutar >= 500 ? 0 : 29.99;
// Siparişi oluştur
const siparis = await tx.siparis.create({
data: {
musteriId,
adresId,
toplamTutar,
kargoUcreti,
kalemler: {
create: kalemler.map(k => ({
urunId: k.urunId,
adet: k.adet,
birimFiyat: k.birimFiyat
}))
}
},
include: {
kalemler: {
include: { urun: { select: { ad: true } } }
},
musteri: { select: { ad: true, soyad: true, email: true } }
}
});
console.log(`Sipariş oluşturuldu: ${siparis.siparisNo}`);
console.log(`Müşteri: ${siparis.musteri.ad} ${siparis.musteri.soyad}`);
console.log(`Toplam: ${toplamTutar + kargoUcreti} TL`);
siparis.kalemler.forEach(k => {
console.log(` - ${k.urun.ad} x${k.adet}`);
});
return siparis;
});
}
// Kategori ağacını getir (kendine referans)
async function kategoriAgaciGetir() {
const anaKategoriler = await prisma.kategori.findMany({
where: {
ustKategoriId: null,
aktif: true
},
include: {
altKategoriler: {
where: { aktif: true },
include: {
altKategoriler: {
where: { aktif: true }
},
_count: { select: { urunler: true } }
},
orderBy: { sira: 'asc' }
},
_count: { select: { urunler: true } }
},
orderBy: { sira: 'asc' }
});
console.log('
Kategori Ağacı:');
anaKategoriler.forEach(kat => {
console.log(`${kat.ad} (${kat._count.urunler} ürün)`);
kat.altKategoriler.forEach(alt => {
console.log(` └─ ${alt.ad} (${alt._count.urunler} ürün)`);
alt.altKategoriler.forEach(altAlt => {
console.log(` └─ ${altAlt.ad}`);
});
});
});
return anaKategoriler;
}
async function main() {
try {
await urunDetayGetir('kablosuz-kulaklik');
await kategoriAgaciGetir();
} catch (hata) {
console.error('Hata:', hata.message);
} finally {
await prisma.$disconnect();
}
}
main();
Prisma ile ilişkisel veri modelleme, şema dosyanızı veritabanınızın merkezi tanım dosyası haline getirir. Bire bir, bire çok ve çoka çok ilişkilerin yanı sıra kendine referans, bileşik anahtarlar ve kademeli silme gibi gelişmiş özellikler de desteklenir. İyi tasarlanmış bir veri modeli, uygulamanızın sağlam temeller üzerinde yükselmesini sağlar ve ilerideki değişikliklere uyum sağlamayı kolaylaştırır.
Emeğiniz ve katkınız için teşekkür ederiz!
Konunuz gerekli incelemelerden geçerek onaylandı. Topluluğumuza katkılarınızın devamını bekliyoruz.
İyi forumlar!
Konunuz gerekli incelemelerden geçerek onaylandı. Topluluğumuza katkılarınızın devamını bekliyoruz.
İyi forumlar!

Şu an konuyu görüntüleyenler (Toplam : 0, Üye: 0, Misafir: 0)
Benzer konular
- Cevaplar
- 0
- Görüntüleme
- 411
- Cevaplar
- 1
- Görüntüleme
- 549
