Bölüm 10: Veri Tabanı

Java ile Nesne Yönelimli Programlama
Kalıcı depolamaİlişkisel modelSQLiteSQL komutları JDBCEkle, oku, güncelle, silPreparedStatementİşlemler (transaction)
Sercan KÜLCÜ · Giresun Üniversitesi · Elektrik-Elektronik Mühendisliği

Giriş

Öğrenme Çıktıları

Kavramlar

Veritabanının neden gerektiğini, dosyalardan farkını ve ilişkisel modeli (tablo, satır, sütun, birincil anahtar) açıklamak.

SQL

CREATE TABLE, INSERT, SELECT, UPDATE, DELETE komutlarını yazmak; WHERE, ORDER BY, LIMIT ile sorguyu daraltmak.

JDBC

Connection, Statement ve ResultSet ile Java'dan SQLite'a bağlanıp kayıt eklemek, okumak, güncellemek ve silmek; kaynakları try-with-resources ile kapatmak.

Güvenlik ve bütünlük

SQL enjeksiyonunu PreparedStatement ile önlemek; commit/rollback ile birden çok komutu tek bir işlem olarak yürütmek.

Veritabanı Kavramı

Veritabanı Neden Önemlidir?

  • Programdaki değişkenler ve nesneler RAM'de yaşar: program kapanınca kaybolur.
  • Kalıcı depolama (persistent storage): verinin uzun süre, program kapandıktan sonra da saklanması. Öğrenci notları, banka hesapları, oyun skorları hep kalıcı olmalıdır.
  • Veritabanı (database): düzenli biçimde saklanan ve kolayca sorgulanabilen veri topluluğu. Onu yöneten yazılıma Veritabanı Yönetim Sistemi (VTYS, DBMS) denir.
  • Benzetme: veritabanı, kütüphanenin kataloglu arşivi gibidir; aradığınız kitabı tüm rafları gezmeden bulursunuz.
RAM (bellek)ogrencinotlar[]toplam = 87kapanınca silinirDiskokul.dbkalıcıkaydet: INSERT, UPDATEgeri oku: SELECTProgram yeniden açılınca veri diskten okunur.

Veritabanı Kavramı

Dosya mı, Veritabanı mı?

Dosya sistemleri bazı uygulamalar için yeterli olabilir (ayar dosyası, günlük kaydı, kısa bir liste). Veri büyüyüp karmaşıklaştıkça veritabanları gerekir: verinin verimli işlenmesi için önemli özellikler sunarlar.

ÖzellikDüz dosya (txt, csv)Veritabanı
AramaDosya baştan sona okunurİndeksle hızlı; tek satırlık SQL sorgusu
GüncellemeÇoğu zaman tüm dosya yeniden yazılırYalnızca ilgili satır değişir
Eşzamanlı erişimİki program aynı anda yazarsa veri bozulabilirKilitleme ve işlemlerle güvenli
TutarlılıkBiçim denetimi tamamen programcıya kalırVeri tipleri ve kısıtlar (NOT NULL, UNIQUE, CHECK)
GüvenlikDosya izni: ya hepsi ya hiçbiriKullanıcı, tablo ve alan düzeyinde yetki
Hata sonrasıYarım kalan yazma dosyayı bozabilirYarım kalan işlem geri alınır

Veritabanı Kavramı

Temel Özellikler

Verilere hızlı erişim

İndeksler sayesinde milyonlarca satır arasından aranan kayıt kısa sürede bulunur.

Alan seviyesinde güvenlik

Yetki tablo, hatta sütun (alan) düzeyinde verilebilir: maaş sütununu yalnızca muhasebe birimi görebilir.

Platform bağımsız, standart API'ler

SQL bir standarttır; Java'da JDBC aynı arayüzle farklı veritabanlarına erişir.

Dosya yapısından bağımsızlık

Program verinin diskte nasıl tutulduğunu bilmez. Ne istediğini SQL ile söyler; nasıl bulunacağına VTYS karar verir.

Bunlara ek olarak veritabanları veri bütünlüğü kısıtları ve işlem (transaction) desteği sunar; bölümün sonunda göreceğiz.

Veritabanı Kavramı

İlişkisel Model: Tablo, Satır, Sütun

userstablo (table)idnameemail1Ahmet Yılmazahmet@example.com2Ayşe Demirayse@example.com3Can Kayacan@example.comsütun (alan, column)satır(kayıt, row)birincil anahtar (primary key): her satırda farklı
  • İlişkisel (relational) veritabanı veriyi tablolarda saklar.
  • Sütun: verinin bir özelliği; adı ve veri tipi vardır (name TEXT).
  • Satır: bir varlığın tüm bilgileri (bir kullanıcı).
  • Birincil anahtar: her satırı benzersiz tanımlar; iki satırın id'si aynı olamaz.
  • Java benzetmesi: tablo ≈ sınıf, sütun ≈ alan, satır ≈ nesne.

Veritabanı Kavramı

Veritabanı Türleri

İlişkisel

Tablolar ve SQL. Oracle, SQL Server, MySQL, PostgreSQL. Kullanımın büyük bölümü (yaklaşık %77).

NoSQL / arama

Belge, anahtar–değer, arama dizini gibi tablosuz modeller; belirli senaryolarda kullanılır: MongoDB, Elasticsearch (yaklaşık %23).

Gömülü ilişkisel

SQLite: programın içinde çalışan ilişkisel veritabanı; veri paylaşımının az olduğu durumlarda yaygındır.

İlişkisel%77NoSQL / arama%23yaklaşık kullanım payı

Bu bölümde, kurulumu en kolay olan SQLite'ı kullanacağız. Öğrendiğiniz SQL ve JDBC kodu diğer ilişkisel veritabanlarında da neredeyse aynıdır.

SQLite

SQLite Nedir?

  • Gömülü (embedded) bir ilişkisel veritabanıdır: programın içinde bir kütüphane olarak çalışır.
  • Sunucuya ihtiyaç duymaz (serverless): ayrı bir program, kurulum ya da yönetici gerekmez.
  • Kendine yeten bir yapıya sahiptir: tüm tablolar tek bir disk dosyasında saklanır (ör. sample.db).
  • Çapraz platform: aynı dosya Windows, Linux, macOS ve Android'de açılır.
  • Kolayca kurulur ve kullanılır; açık kaynaklı ve ücretsizdir.
  • Dünyada en yaygın kullanılan gömülü ilişkisel veritabanıdır.

Yaygın kullanım alanları

  • Mobil uygulamalar
  • Gömülü sistemler
  • Küçük ölçekli uygulamalar
  • Tarayıcı, e-posta istemcisi gibi masaüstü programları

Uygun olmadığı yer

  • Ağ üzerinden çok sayıda kullanıcının aynı anda yazdığı büyük sistemler

SQLite

Sunucu Tabanlı ve Gömülü Veritabanı

Sunucu tabanlı (MySQL, Oracle)Java programı1. süreçağport 3306VT sunucusu2. süreçveri dosyalarıkullanıcı adıve şifre gerekirGömülü (SQLite)tek süreçJava programıSQLite kütüphanesitest.dbtek dosyadosyayı doğrudan okur/yazar
Sunucu tabanlıSQLite
KurulumSunucu kurulur, kullanıcı ve şifre tanımlanırProjeye tek bir jar eklenir
VeriSunucunun yönettiği birçok dosyaTek bir .db dosyası
Eşzamanlı yazmaÇok kullanıcıAynı anda tek yazıcı

SQLite

SQLite Sürücüsü ve DB Browser

SQLite JDBC sürücüsü

Java'nın SQLite ile konuşmasını sağlayan jar dosyası (sqlite-jdbc-3.x.jar): github.com/xerial/sqlite-jdbc

DB Browser for SQLite

SQLite için popüler bir SQL istemcisi: .db dosyasını açar, tabloları görsel gösterir, SQL çalıştırır: sqlitebrowser.org/dl

Eclipse'te sürücüyü projeye eklemek

  1. Jar dosyasını indirip projede bir lib klasörüne koyun.
  2. Projeye sağ tık → Build Path → Configure Build Path…
  3. Libraries sekmesi → Classpath → Add JARs… → jar'ı seçin → Apply and Close.

Komut satırından

javac Baglan.java
java -cp ".;sqlite-jdbc-3.40.0.0.jar" Baglan

Linux ve macOS'ta ayırıcı ; yerine : olur. Derleme için sürücü gerekmez; java.sql paketi JDK ile gelir.

SQL Temelleri

Yaygın SQL Komutları

KomutGöreviÖrnekCRUD
CREATE TABLEYeni tablo oluştururCREATE TABLE users (id INTEGER, name TEXT)—
INSERT INTOYeni kayıt eklerINSERT INTO users(name) VALUES('Ali')Create
SELECTTabloyu sorgularSELECT * FROM usersRead
UPDATEVar olan kayıtları güncellerUPDATE users SET name = 'Efe' WHERE id = 1Update
DELETEKayıtları silerDELETE FROM users WHERE id = 1Delete
  • SQL (Structured Query Language, yapılandırılmış sorgu dili): ilişkisel veritabanlarının ortak dilidir.
  • Anahtar kelimelerde büyük/küçük harf fark etmez; okunabilirlik için BÜYÜK yazılır. Metin değerler tek tırnak içindedir: 'Ali'.
  • * “tüm sütunlar” demektir. WHERE komutun hangi satırlara uygulanacağını seçer.

SQL Temelleri

CREATE TABLE ve Veri Tipleri

CREATE TABLE IF NOT EXISTS uyeler (
  id    INTEGER PRIMARY KEY,   -- benzersiz
  name  TEXT NOT NULL,         -- boş olamaz
  email TEXT UNIQUE,           -- tekrar edemez
  yas   INTEGER CHECK (yas >= 0)
)
  • IF NOT EXISTS: tablo zaten varsa hata verme.
  • INTEGER PRIMARY KEY: değer verilmezse SQLite en büyük id + 1 atar.
  • Kısıt bozulursa komut reddedilir: NOT NULL constraint failed: uyeler.name, CHECK constraint failed: yas >= 0.
SQLite tipiJava karşılığı
INTEGERint, long · getInt
REALdouble · getDouble
TEXTString · getString
BLOBbyte[] · getBytes
NULLdeğer yok

SQL Temelleri

SQL Komutları Adım Adım

CREATE TABLE users (id INTEGER PRIMARY KEY,
                    name TEXT, email TEXT)
usersidnameemail(tablo boş)

Tablo oluşturuldu: üç sütun var, henüz hiç satır yok.

INSERT INTO users(name, email)
  VALUES('Ahmet Yılmaz', 'ahmet@example.com')
usersidnameemail1Ahmet Yılmazahmet@example.com

id verilmedi; SQLite 1 atadı. 1 satır etkilendi.

INSERT INTO users(name, email)
  VALUES('Ayşe Demir', 'ayse@example.com')
usersidnameemail1Ahmet Yılmazahmet@example.com2Ayşe Demirayse@example.com

İkinci kayıt id = 2 aldı (en büyük id + 1).

UPDATE users SET name = 'Mehmet Akdeniz'
  WHERE id = 1
usersidnameemail1Mehmet Akdenizahmet@example.com2Ayşe Demirayse@example.com

WHERE id = 1 yalnızca ilk satırı seçti; sadece name değişti, email aynı kaldı.

DELETE FROM users WHERE id = 1
usersidnameemail1Mehmet Akdenizahmet@example.com2Ayşe Demirayse@example.com

id = 1 olan satır silindi. 1 satır etkilendi.

SELECT * FROM users
usersidnameemail2Ayşe Demirayse@example.com

Sorgu tabloyu değiştirmez, yalnızca okur: sonuç tek satır (2, Ayşe Demir, ayse@example.com).

SQL Temelleri

SELECT: Süzme, Sıralama, Sınırlama

skorlaroyuncupuanada72can95ece58efe95naz81
SorguSonuç
SELECT oyuncu, puan FROM skorlar
WHERE puan < 90 ORDER BY puan DESC
naz 81, ada 72, ece 58
SELECT oyuncu, puan FROM skorlar
ORDER BY puan DESC, oyuncu LIMIT 3
can 95, efe 95, naz 81
SELECT oyuncu, puan FROM skorlar
WHERE oyuncu LIKE 'e%'
ece 58, efe 95
SELECT COUNT(*), AVG(puan), MAX(puan)
FROM skorlar WHERE puan >= 60
4 · 85.75 · 95
  • WHERE satırları süzer (=, <>, <, >=, AND, OR, LIKE 'e%': “e ile başlayan”).
  • ORDER BY … DESC büyükten küçüğe sıralar; eşitlikte ikinci ölçüt (oyuncu) kullanılır. LIMIT 3 ilk 3 satırı alır.

JDBC

JDBC (Java Database Connectivity)

  • JDBC (Java Database Connectivity): Java'nın veritabanlarına bağlanmak için sunduğu standart API'dir; java.sql paketindedir ve JDK ile gelir.
  • Program yalnızca JDBC arayüzlerini kullanır; her veritabanı üreticisi bu arayüzleri gerçekleyen bir sürücü (driver) sağlar.
  • Veritabanını değiştirmek için çoğu zaman yalnızca sürücü jar'ı ve bağlantı adresi değişir.
  • Çok biçimlilik iş başında: Connection bir arayüzdür; SQLite'ta gerçek nesnenin sınıfı org.sqlite.jdbc4.JDBC4Connection'dır.
Java programınızJDBC API (java.sql)Connection · Statement · ResultSetDriverManagerSQLite sürücüsüsqlite-jdbc.jarMySQL sürücüsümysql-connector-j.jarsample.dbMySQL sunucusu

JDBC

JDBC'nin Temel Türleri

TürGöreviÖnemli metotlar
DriverManagerUygun sürücüyü bulur, bağlantı açargetConnection(url), getConnection(url, kullanici, sifre)
ConnectionVeritabanıyla açık bir oturumcreateStatement(), prepareStatement(sql), commit(), rollback(), close()
StatementSabit bir SQL metnini çalıştırırexecute, executeUpdate, executeQuery
PreparedStatement? yer tutuculu, önceden hazırlanan SQLsetInt, setString, executeUpdate(), executeQuery()
ResultSetSorgu sonucundaki satırlar ve bir imleçnext(), getInt("id"), getString("name")
SQLExceptionVeritabanı hatası; kontrol edilen istisnagetMessage(), getErrorCode()

Hepsi java.sql paketindedir: dosyanın başına import java.sql.*; yazılır. Connection, Statement ve ResultSet kapatılması gereken kaynaklardır (AutoCloseable).

JDBC

Bağlantı Adresi (JDBC URL)

Bağlantı adresi (URL)Anlamı
jdbc:sqlite:sample.dbÇalışma dizinindeki sample.db; yoksa boş olarak oluşturulur
jdbc:sqlite:C:/veriler/okul.dbTam yol ile dosya
jdbc:sqlite::memory:Bellekte geçici veritabanı; bağlantı kapanınca silinir (denemeler için)
jdbc:mysql://localhost:3306/testdbMySQL sunucusu: makine, port, veritabanı adı; ayrıca kullanıcı adı ve şifre
  • Biçim: jdbc:<alt protokol>:<veritabanı_dosyanız veya adresi>. DriverManager, alt protokole bakarak sürücüyü seçer.
  • JDBC 4.0 (Java 6) ve sonrasında sürücü, classpath'teki jar'dan otomatik yüklenir. Eski kodlardaki Class.forName("org.sqlite.JDBC") satırı artık gerekmez.
  • Sürücü classpath'te yoksa: No suitable driver found for jdbc:sqlite:sample.db.

JDBC

Bir JDBC Programının Adımları

1. BağlangetConnection()2. Komut nesnesicreateStatement()3. ÇalıştırexecuteQuery()4. Sonucu işlers.next()5. Kapatclose()
try (Connection conn = DriverManager.getConnection(url); // 1
     Statement stmt = conn.createStatement();             // 2
     ResultSet rs = stmt.executeQuery(sql)) {             // 3
  while (rs.next()) {                                     // 4
    System.out.println(rs.getInt(1));
  }
} // 5: rs, stmt, conn ters sırada otomatik kapanır
  • Kaynaklar try-with-resources parantezinde açılır; blok bitince (hata olsa bile) ters sırada kapanır: önce rs, sonra stmt, en son conn.
  • Kapatılmayan bağlantılar dosyayı kilitli bırakabilir ve bellek sızıntısına yol açar.
  • getInt(1): sütun numaraları 1'den başlar.

Temel İşlemler

SQLite'a Bağlanma

// Veritabanı dosyasının yolu (bağlantı adresi)
String url = "jdbc:sqlite:sample.db";
try (Connection conn = DriverManager.getConnection(url)) {
  // Bağlantı başarılıysa mesaj yazdırılır.
  if (conn != null) {
    System.out.println("Bağlantı başarılı!");
  }
} catch (SQLException e) {
  // Bağlantı hatası olursa hata mesajı yazdırılır.
  System.out.println(e.getMessage());
}
Çıktı
Bağlantı başarılı!
  • getConnection dosya yoksa oluşturur (boş veritabanı).
  • Bağlantı kurulamazsa null dönmez, SQLException fırlatır; if (conn != null) yalnızca ek bir güvencedir.
  • SQLException kontrol edilen istisnadır: yakalanmalı ya da throws ile bildirilmelidir.

Temel İşlemler

Tablo Oluşturma

String url = "jdbc:sqlite:sample.db";
String sql = "CREATE TABLE IF NOT EXISTS users "
           + "(id INTEGER PRIMARY KEY, name TEXT, email TEXT)";
try (Connection conn = DriverManager.getConnection(url);
     Statement stmt = conn.createStatement()) {
  // Tablo oluşturuluyor.
  stmt.execute(sql);
  System.out.println("Tablo oluşturuldu!");
} catch (SQLException e) {
  System.out.println(e.getMessage());
}
Çıktı
Tablo oluşturuldu!
  • execute(sql) her tür SQL'i çalıştırır; sonuç kümesi üretirse true, CREATE için false döner.
  • IF NOT EXISTS sayesinde program ikinci kez çalışınca hata olmaz. Olmasaydı: table users already exists.
  • Birden çok kaynak tek try (…; …) içinde ; ile ayrılır.

Temel İşlemler

Veri Ekleme

String url = "jdbc:sqlite:sample.db";
String sql = "INSERT INTO users(name, email) "
           + "VALUES('Ahmet Yılmaz', 'ahmet@example.com')";
try (Connection conn = DriverManager.getConnection(url);
     Statement stmt = conn.createStatement()) {
  // Veritabanına veri ekleniyor.
  int sayi = stmt.executeUpdate(sql);
  System.out.println("Veri eklendi! (" + sayi + " satır)");
} catch (SQLException e) {
  System.out.println(e.getMessage());
}
Çıktı
Veri eklendi! (1 satır)
  • executeUpdate etkilenen satır sayısını (int) döndürür.
  • id verilmedi; SQLite 1 atadı.
  • Program her çalıştığında yeni bir satır eklenir (id 2, 3, …).
  • SQL içindeki metin değerler tek tırnak içindedir.

Temel İşlemler

Veri Okuma

String url = "jdbc:sqlite:sample.db";
String sql = "SELECT * FROM users";
try (Connection conn = DriverManager.getConnection(url);
     Statement stmt = conn.createStatement();
     ResultSet rs = stmt.executeQuery(sql)) {
  // Satırlar tek tek okunup yazdırılıyor.
  while (rs.next()) {
    System.out.println("ID: " + rs.getInt("id")
        + ", Ad: " + rs.getString("name")
        + ", E-posta: " + rs.getString("email"));
  }
} catch (SQLException e) {
  System.out.println(e.getMessage());
}
Çıktı
ID: 1, Ad: Ahmet Yılmaz, E-posta: ahmet@example.com
  • executeQuery bir ResultSet döndürür.
  • rs.next() imleci bir sonraki satıra taşır; satır varsa true, yoksa false döner.
  • getInt("id"), getString("name"): o anki satırın sütunlarını adıyla okur.

Temel İşlemler

ResultSet ile Okuma: Adım Adım

ResultSet rsidname1Ahmet Yılmaz2Ayşe Demirimleç: ilk satırdan öncers

Bağlantı açılır, sorgu çalışır. rs iki satırlık sonucu tutar; imleç ilk satırdan önce bekler.

ResultSet rsidname1Ahmet Yılmaz2Ayşe Demirimleç: 1. satırrs

rs.next() imleci 1. satıra taşır ve true döndürür: döngüye girilir.

ResultSet rsidname1Ahmet Yılmaz2Ayşe Demirimleç: 1. satırrs

Sütunlar o anki satırdan okunur.

ResultSet rsidname1Ahmet Yılmaz2Ayşe Demirimleç: 1. satırrs

Satır yazdırılır.

ResultSet rsidname1Ahmet Yılmaz2Ayşe Demirimleç: 2. satırrs

İmleç 2. satıra geçer; yine true.

ResultSet rsidname1Ahmet Yılmaz2Ayşe Demirimleç: 2. satırrs

Bu kez 2. satırın değerleri okunur.

ResultSet rsidname1Ahmet Yılmaz2Ayşe Demirimleç: 2. satırrs

İkinci satır yazdırılır.

ResultSet rsidname1Ahmet Yılmaz2Ayşe Demirimleç: son satırdan sonrars

Başka satır yok: rs.next() false döner, döngü biter.

ResultSet rsidname1Ahmet Yılmaz2Ayşe Demirimleç: son satırdan sonrars

Döngüden sonraki satır çalışır.

ResultSet rsidname1Ahmet Yılmaz2Ayşe Demirimleç: son satırdan sonrars

try bloğu biter: rs, stmt, conn bu sırayla otomatik kapanır.

String url = "jdbc:sqlite:okuma.db";
String sql = "SELECT * FROM users";
try (Connection conn = DriverManager.getConnection(url);
     Statement stmt = conn.createStatement();
     ResultSet rs = stmt.executeQuery(sql)) {
  while (rs.next()) {
    int id = rs.getInt("id");
    String ad = rs.getString("name");
    System.out.println(id + " - " + ad);
  }
  System.out.println("Okuma bitti.");
} catch (SQLException e) {
  System.out.println(e.getMessage());
}

Temel İşlemler

Veri Güncelleme

String url = "jdbc:sqlite:sample.db";
String sql = "UPDATE users SET name = 'Mehmet Akdeniz' WHERE id = 1";
try (Connection conn = DriverManager.getConnection(url);
     Statement stmt = conn.createStatement()) {
  // Veritabanındaki veri güncelleniyor.
  int sayi = stmt.executeUpdate(sql);
  System.out.println("Veri güncellendi! (" + sayi + " satır)");
} catch (SQLException e) {
  System.out.println(e.getMessage());
}
Çıktı
Veri güncellendi! (1 satır)
  • Öncesinde okuma: ID: 1, Ad: Ahmet Yılmaz, …; sonrasında: ID: 1, Ad: Mehmet Akdeniz, …
  • SET yalnızca yazılan sütunu değiştirir; email aynı kalır.
  • WHERE id = 1 güncellenecek satırı seçer. WHERE unutulursa tüm satırlar güncellenir.

Temel İşlemler

Veri Silme

String url = "jdbc:sqlite:sample.db";
String sql = "DELETE FROM users WHERE id = 1";
try (Connection conn = DriverManager.getConnection(url);
     Statement stmt = conn.createStatement()) {
  // Veritabanındaki veri siliniyor.
  int sayi = stmt.executeUpdate(sql);
  System.out.println("Veri silindi! (" + sayi + " satır)");
} catch (SQLException e) {
  System.out.println(e.getMessage());
}
Çıktı (program iki kez çalıştırılınca)
Veri silindi! (1 satır)
Veri silindi! (0 satır)
  • İkinci çalıştırmada silinecek satır kalmamıştır: hata oluşmaz, executeUpdate 0 döner.
  • Bu yüzden mesajı dönen sayıya göre vermek daha doğrudur (Alıştırma 1).
  • WHERE'siz DELETE FROM users tüm satırları siler; tablonun kendisi kalır.

Temel İşlemler

execute, executeUpdate, executeQuery

MetotDöndürürKullanım
executebooleanCREATE TABLE, DROP
executeUpdateint satır sayısıINSERT, UPDATE, DELETE
executeQueryResultSetSELECT

Yanlış metot hataya yol açar: executeQuery ile INSERT çalıştırmak → query does not return ResultSet.

Programlar sırayla çalıştırılınca
> java Baglan
Bağlantı başarılı!
> java TabloOlustur
Tablo oluşturuldu!
> java VeriEkle
Veri eklendi! (1 satır)
> java VeriOku
ID: 1, Ad: Ahmet Yılmaz, E-posta: ahmet@example.com
> java VeriGuncelle
Veri güncellendi! (1 satır)
> java VeriOku
ID: 1, Ad: Mehmet Akdeniz, E-posta: ahmet@example.com
> java VeriSil
Veri silindi! (1 satır)
> java VeriOku
> java VeriSil
Veri silindi! (0 satır)

PreparedStatement

SQL Enjeksiyonu (SQL Injection)

String girdi = "x' OR '1'='1";   // kullanıcının yazdığı "ad"

// TEHLİKELİ: girdi SQL metnine yapıştırılıyor
String sql = "SELECT ad FROM kisiler WHERE ad = '"
           + girdi + "'";
System.out.println(sql);
ResultSet rs1 = stmt.executeQuery(sql);

// GÜVENLİ: girdi yalnızca değer olarak gönderiliyor
PreparedStatement ps = conn.prepareStatement(
    "SELECT ad FROM kisiler WHERE ad = ?");
ps.setString(1, girdi);
ResultSet rs2 = ps.executeQuery();
Çıktı (tabloda 3 kişi var; satırlar sayıldı)
SELECT ad FROM kisiler WHERE ad = 'x' OR '1'='1'
Birleştirme: 3 satır
PreparedStatement: 0 satır
  • Kullanıcının yazdığı tırnak, SQL metnini değiştirdi: '1'='1' her zaman doğru, tüm satırlar döndü.
  • Giriş ekranında bu, şifresiz oturum açmak demektir.
  • ? ile gönderilen girdi yalnızca bir değerdir; SQL'in parçası olamaz.

PreparedStatement

PreparedStatement ve Yer Tutucular

static void ekle(Connection conn, String ad, String soyad,
                 int yas) throws SQLException {
  String sql = "INSERT INTO kisiler(ad, soyad, yas) "
             + "VALUES(?, ?, ?)";
  try (PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setString(1, ad);      // 1. soru işareti
    ps.setString(2, soyad);   // 2. soru işareti
    ps.setInt(3, yas);        // 3. soru işareti
    ps.executeUpdate();
  }
}

Neden?

  • Güvenli: SQL enjeksiyonunu önler.
  • Tırnak sorunu yok: "Ali'nin" sorunsuz kaydedilir.
  • Aynı komut farklı değerlerle tekrar kullanılır.
INSERT INTO kisiler(ad, soyad, yas) VALUES( ?, ?, ? )?1. yer tutucu?2. yer tutucu?3. yer tutucusetString(1, ad)"Ahmet"setString(2, soyad)"Yılmaz"setInt(3, yas)30

setXxx(sıra, değer) her ?'i doldurur; sıra 1'den başlar. Sonra executeUpdate() parametresiz çağrılır.

PreparedStatement

Kişi Programı: Güncelleme ve Silme Metotları

static void guncelle(Connection conn, String ad, String soyad,
                     int yas) throws SQLException {
  String sql = "UPDATE kisiler SET soyad = ?, yas = ? "
             + "WHERE ad = ?";
  try (PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setString(1, soyad);
    ps.setInt(2, yas);
    ps.setString(3, ad);
    ps.executeUpdate();
  }
}

static void sil(Connection conn, String ad) throws SQLException {
  String sql = "DELETE FROM kisiler WHERE ad = ?";
  try (PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setString(1, ad);
    ps.executeUpdate();
  }
}
  • Her metot Connection'ı parametre olarak alır: bağlantı bir kez açılır, birçok işte kullanılır.
  • ?'ler SQL'deki sıraya göre numaralanır: guncelle'de ad, 3. yer tutucudur.
  • throws SQLException: hata çağırana iletilir; main de throws ile bildirdiğinden hata olursa program yığın iziyle durur.

PreparedStatement

Kişi Programı: Listeleme Metodu

static void listele(Connection conn) throws SQLException {
  String sql = "SELECT id, ad, soyad, yas FROM kisiler";
  try (Statement st = conn.createStatement();
       ResultSet rs = st.executeQuery(sql)) {
    while (rs.next()) {
      System.out.println(rs.getInt("id") + " " + rs.getString("ad")
          + " " + rs.getString("soyad") + " " + rs.getInt("yas"));
    }
    System.out.println("---");
  }
}
Çıktı (ilk listele çağrısı)
1 Ahmet Akdeniz 35
2 Ayşe Demir 40
---
  • Sorguda değişken değer olmadığından düz Statement yeterlidir.
  • Her listele çağrısı tablonun o anki hâlini okur; sonuna --- yazar.

PreparedStatement

Kişi Programı: Adım Adım

kisileridadsoyadyas(tablo boş)

kisiler.db yoksa oluşturulur; bağlantı ve stmt açılır.

kisileridadsoyadyas(tablo boş)

Tablo oluşturulur; henüz boştur.

kisileridadsoyadyas1AhmetYılmaz30

ekle: ?'ler doldurulur, satır eklenir, id = 1.

kisileridadsoyadyas1AhmetYılmaz302AyşeDemir40

İkinci kişi id = 2 ile eklenir.

kisileridadsoyadyas1AhmetAkdeniz352AyşeDemir40

WHERE ad = ?: Ahmet'in soyadı ve yaşı değişir.

kisileridadsoyadyas1AhmetAkdeniz352AyşeDemir40

listele tüm satırları ve --- yazar.

kisileridadsoyadyas1AhmetAkdeniz352AyşeDemir40

Ahmet'in satırı silinir.

kisileridadsoyadyas2AyşeDemir40

Tabloda yalnızca Ayşe kaldı.

kisileridadsoyadyas2AyşeDemir40

Bağlantı kapanır; veri kisiler.db'de kalıcıdır.

public static void main(String[] args) throws SQLException {
  String url = "jdbc:sqlite:kisiler.db";
  try (Connection conn = DriverManager.getConnection(url);
       Statement stmt = conn.createStatement()) {
    stmt.execute("CREATE TABLE IF NOT EXISTS kisiler ("
        + "id INTEGER PRIMARY KEY, ad TEXT NOT NULL, "
        + "soyad TEXT NOT NULL, yas INTEGER NOT NULL)");
    ekle(conn, "Ahmet", "Yılmaz", 30);
    ekle(conn, "Ayşe", "Demir", 40);
    guncelle(conn, "Ahmet", "Akdeniz", 35);
    listele(conn);
    sil(conn, "Ahmet");
    listele(conn);
  }
}

MySQL

Aynı Kod, Başka Veritabanı: MySQL

String url = "jdbc:mysql://localhost:3306/testdb";
String kullanici = "root";
String sifre = "sifreniz";
String sql = "SELECT id, name, email FROM users";
try (Connection conn =
         DriverManager.getConnection(url, kullanici, sifre);
     Statement stmt = conn.createStatement();
     ResultSet rs = stmt.executeQuery(sql)) {
  while (rs.next()) {
    System.out.println(rs.getInt("id") + " "
        + rs.getString("name"));
  }
} catch (SQLException e) {
  System.out.println(e.getMessage());
}
  • JDBC kodu aynıdır; değişenler: adres, kullanıcı adı, şifre ve sürücü jar'ı (mysql-connector-j).
  • MySQL bir sunucudur: önce kurulup çalıştırılmalı, testdb veritabanı oluşturulmalıdır.
  • Sürücü sınıfı com.mysql.cj.jdbc.Driver'dır; eski com.mysql.jdbc.Driver adı kullanımdan kalkmıştır.
  • Gerçek projelerde şifre koda yazılmaz; ayar dosyasından veya ortam değişkeninden okunur.

İşlemler

İşlem (Transaction) ve ACID

  • Havale: Ece'nin hesabına 300 TL ekle ve Ali'nin hesabından 300 TL düş. İki UPDATE'ten biri olur diğeri olmazsa para yoktan var olur ya da kaybolur.
  • İşlem (transaction): tek bir iş birimi olarak yürütülen bir veya daha fazla SQL komutu: ya hepsi ya hiçbiri.
  • JDBC'de varsayılan auto-commit açıktır: her komut hemen kalıcı olur.
  • conn.setAutoCommit(false) → komutlar → conn.commit() (kalıcı yap) veya conn.rollback() (geri al).
Atomicity

Bölünmezlik: işlemin ya tamamı ya hiçbiri uygulanır.

Consistency

Tutarlılık: işlem sonunda tüm kısıtlar (ör. bakiye ≥ 0) sağlanır.

Isolation

Yalıtım: eşzamanlı işlemler birbirinin yarım hâlini görmez.

Durability

Kalıcılık: commit edilen veri, elektrik kesilse de kaybolmaz.

İşlemler

Havale Örneği: Kurulum ve Sonuç

static final String YATIR =
    "UPDATE hesaplar SET bakiye = bakiye + ? WHERE id = ?";
static final String CEK =
    "UPDATE hesaplar SET bakiye = bakiye - ? WHERE id = ?";

public static void main(String[] args) throws SQLException {
  String url = "jdbc:sqlite::memory:";  // bellekte, geçici
  try (Connection conn = DriverManager.getConnection(url);
       Statement st = conn.createStatement()) {
    st.execute("CREATE TABLE hesaplar (id INTEGER PRIMARY KEY, "
        + "sahip TEXT, bakiye INTEGER CHECK (bakiye >= 0))");
    st.execute("INSERT INTO hesaplar(sahip, bakiye) "
        + "VALUES ('Ali', 1000), ('Ece', 500)");
    havale(conn, 1, 2, 300);    // Ali → Ece 300 TL
    havale(conn, 1, 2, 5000);   // Ali'nin parası yetmez
    yazdir(conn);
  }
}
Çıktı
300 TL aktarıldı.
Geri alındı: 5000 TL
Ali: 700
Ece: 800
  • CHECK (bakiye >= 0): bakiyeyi eksiye düşüren komut SQLException fırlatır.
  • İkinci havalede Ece'ye 5000 TL yatırılmıştı; rollback bunu da geri aldı.
  • havale metodu sonraki slayttadır; yazdir, listele gibi bir SELECT döngüsüdür.

İşlemler

commit ve rollback: Adım Adım

işlem başladıidsahipbakiye1Ali10002Ece500

Auto-commit kapatılır: bundan sonraki değişiklikler commit'e kadar geçicidir.

geçici (commit yok)idsahipbakiye1Ali10002Ece800

Ece'ye 300 TL yatırılır: 500 → 800.

geçici (commit yok)idsahipbakiye1Ali7002Ece800

Ali'den 300 TL çekilir: 1000 → 700. Kısıt sağlanıyor.

kalıcı (commit edildi)idsahipbakiye1Ali7002Ece800

commit(): iki değişiklik birlikte kalıcı olur.

kalıcı durumidsahipbakiye1Ali7002Ece800

finally: auto-commit yeniden açılır; metot biter.

geçici (commit yok)idsahipbakiye1Ali7002Ece5800

İkinci çağrı: Ece'ye 5000 TL yatırılır (geçici): 800 → 5800.

geçici (commit yok)idsahipbakiye1Ali7002Ece5800

700 − 5000 < 0 olurdu: CHECK bozulur, SQLException fırlar.

rollback: geri alındıidsahipbakiye1Ali7002Ece800

rollback(): geçici yatırma da geri alınır. Para ne kayboldu ne çoğaldı.

static void havale(Connection conn, int kimden, int kime,
                   int tutar) throws SQLException {
  conn.setAutoCommit(false);         // işlem başlar
  try (PreparedStatement p1 = conn.prepareStatement(YATIR);
       PreparedStatement p2 = conn.prepareStatement(CEK)) {
    p1.setInt(1, tutar);  p1.setInt(2, kime);
    p1.executeUpdate();              // alıcıya yatır
    p2.setInt(1, tutar);  p2.setInt(2, kimden);
    p2.executeUpdate();              // göndericiden çek
    conn.commit();                   // kalıcı yap
    System.out.println(tutar + " TL aktarıldı.");
  } catch (SQLException e) {
    conn.rollback();                 // geri al
    System.out.println("Geri alındı: " + tutar + " TL");
  } finally {
    conn.setAutoCommit(true);
  }
}

Özet

Özet

KonuAna fikir
VeritabanıKalıcı, düzenli, sorgulanabilir veri; VTYS yönetir. Tablo, satır, sütun, birincil anahtar.
SQLiteSunucusuz, gömülü, tek dosya. Adres: jdbc:sqlite:sample.db; sürücü jar'ı classpath'te olmalı.
SQLCREATE TABLE, INSERT, SELECT, UPDATE, DELETE; WHERE, ORDER BY, LIMIT.
JDBCDriverManager → Connection → Statement / PreparedStatement → ResultSet.
Çalıştırmaexecute (tablo), executeUpdate → int, executeQuery → ResultSet; while (rs.next()).
GüvenlikKullanıcı girdisi her zaman ? ve setXxx ile; SQL metnine eklenmez.
İşlemsetAutoCommit(false), commit(), rollback(): ya hepsi ya hiçbiri (ACID).
Kaynaklartry-with-resources: rs, stmt, conn ters sırada kapanır; SQLException kontrol edilen istisnadır.

Özet

Sık Yapılan Hatalar

Sürücü classpath'te değilJar eklenmemiş:No suitable driver found for jdbc:sqlite:sample.db
SQLException'ı yakalamamaktry veya throws yok (derleme hatası):unreported exception SQLException; must be caught or declared to be thrown
Tablo yok veya adı yanlışTablo oluşturulmadan sorgu:no such table: users
Metni tırnaksız yazmakVALUES(Ahmet, …):no such column: Ahmet
Kesme işaretli metni birleştirmekVALUES('Ali'nin'):near "nin": syntax error
Yanlış execute metoduexecuteQuery ile INSERT:query does not return ResultSet
İndeksi 0'dan başlatmakrs.getString(0); sıra 1'den başlar:column 0 out of bounds [1,3]
WHERE'siz UPDATE / DELETEHata vermez; tüm satırları değiştirir ya da siler.

Özet

Örnek Problemler 1

Problem 1: SELECT hangi satırları döndürür?

CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO users(name) VALUES ('Ali');
INSERT INTO users(name) VALUES ('Can');
DELETE FROM users WHERE id = 2;
INSERT INTO users(name) VALUES ('Ece');
UPDATE users SET name = 'Efe' WHERE id = 1;
SELECT * FROM users;

Problem 2

Kişi programı (kisiler.db ile) ikinci kez çalıştırılıyor. Çıktının ilk bloğu (ilk ---'e kadar) ve son bloğu nedir?

Cevapları göster
  1. (1, Efe) ve (2, Ece). Can silinince en büyük id 1 kaldı; Ece en büyük id + 1 = 2 aldı (id yeniden kullanıldı). UPDATE yalnızca 1 numaralı satırı değiştirdi.
  2. Dosya kalıcıdır; ilk çalıştırmadan Ayşe (id 2) kalmıştı ve IF NOT EXISTS tabloyu silmez. Yeni kayıtlar id 3 (Ahmet) ve 4 (Ayşe) alır. İlk blok: 2 Ayşe Demir 40, 3 Ahmet Akdeniz 35, 4 Ayşe Demir 40. Son blok: 2 Ayşe Demir 40, 4 Ayşe Demir 40 (aynı kişi iki kez kayıtlı).

Özet

Örnek Problemler 2

Problem 3: Giriş kontrolü

String sql = "SELECT * FROM kisiler WHERE ad = '" + ad
           + "' AND sifre = '" + sifre + "'";
ResultSet rs = stmt.executeQuery(sql);
boolean girisBasarili = rs.next();

Tabloda 3 kişi var. Kullanıcı ad olarak ahmet, şifre olarak ' OR '1'='1 yazarsa oluşan SQL nedir, kaç satır döner, giriş başarılı olur mu?

Problem 4

havale metodundan setAutoCommit(false), commit() ve rollback() satırları çıkarılsaydı (her komut kendi başına kalıcı) program sonunda Ali ve Ece'nin bakiyesi ne olurdu?

Cevapları göster
  1. SELECT * FROM kisiler WHERE ad = 'ahmet' AND sifre = '' OR '1'='1'. AND, OR'dan önce uygulanır: (… AND …) OR '1'='1' her satırda doğru → 3 satır, rs.next() true: şifresiz giriş! Çözüm: WHERE ad = ? AND sifre = ? ile PreparedStatement → 0 satır.
  2. Ali: 700, Ece: 5800. İkinci havalede yatırma hemen kalıcı oldu, çekme CHECK yüzünden başarısız oldu ve geri alacak işlem yoktu: 5000 TL yoktan var oldu (program yine de “Geri alındı” yazar).

Özet

Alıştırmalar

  1. Silme programını değiştirin: executeUpdate 0 döndürürse Kayıt bulunamadı. yazsın.
  2. ogrenciler(id, ad, vize, final) tablosunu oluşturun; Ada (60, 70), Bora (30, 50), Cem (80, 40) öğrencilerini PreparedStatement ile bir döngüde ekleyin. Ortalaması (vize %40, final %60) 50 ve üzeri olanları büyükten küçüğe listeleyin.
  3. Menülü bir program yazın: 1-Ekle, 2-Güncelle, 3-Sil, 4-Listele. Scanner ve switch kullanın; güncellenecek/silinecek id yoksa Kullanıcı bulunamadı. yazsın.
  4. Bir oyunun skorlarını skorlar(oyuncu, puan) tablosuna kaydedin ve en yüksek 10 skoru listeleyin.
Cevaplar / ipuçları
  1. int sayi = stmt.executeUpdate(sql); if (sayi > 0) System.out.println("Veri silindi!"); else System.out.println("Kayıt bulunamadı.");
  2. SELECT ad, vize * 0.4 + final * 0.6 AS ort FROM ogrenciler WHERE vize * 0.4 + final * 0.6 >= 50 ORDER BY ort DESC · Çıktı: Ada 66.0, Cem 56.0 (Bora 42.0 elenir). getDouble("ort") ile okunur.
  3. nextInt()'ten sonra nextLine() ile satır sonunu temizleyin. UPDATE … WHERE id = ? ve DELETE … WHERE id = ? için executeUpdate'in döndürdüğü sayı 0 ise kullanıcı yoktur.
  4. SELECT oyuncu, puan FROM skorlar ORDER BY puan DESC LIMIT ? ve ps.setInt(1, 10). Eklemede INSERT INTO skorlar(oyuncu, puan) VALUES(?, ?).

Özet

Kaynaklar

  • Oracle, The Java Tutorials: JDBC Database Access (Establishing a Connection; Processing SQL Statements with JDBC; Using Prepared Statements; Using Transactions).
  • Oracle, Java SE 21 API Documentation: java.sql paketi (DriverManager, Connection, PreparedStatement, ResultSet).
  • SQLite belgeleri, sqlite.org: SQL As Understood By SQLite; Datatypes In SQLite; Appropriate Uses For SQLite.
  • Xerial, SQLite JDBC Driver, github.com/xerial/sqlite-jdbc.
  • P. Deitel ve H. Deitel, Java: How to Program, 11. baskı, Pearson (Accessing Databases with JDBC).
  • C. S. Horstmann, Core Java, Volume II: Advanced Features, 12. baskı, Pearson (Database Programming).
  • OWASP, SQL Injection Prevention Cheat Sheet.

Son

Bölüm 10: Veri Tabanı
Sonraki bölüm: Bölüm 11: Test ve Hata Ayıklama
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.