Bölüm 11: Veri Tabanı Programlama
Giriş
Öğrenme Çıktıları
Veri tabanının neden gerektiğini, dosyalardan farkını ve ilişkisel modeli (tablo, satır, sütun, birincil anahtar) açıklamak.
CREATE TABLE, INSERT, SELECT, UPDATE, DELETE komutlarını yazmak; WHERE, ORDER BY, LIMIT ile sorguyu daraltmak; JOIN ile tabloları birleştirmek, GROUP BY ile gruplamak.
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.
SQL enjeksiyonunu PreparedStatement ile önlemek; commit/rollback ile birden çok komutu tek bir işlem olarak yürütmek.
Veri Tabanı Kavramı
Veri Tabanı 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.
- Veri tabanı (database): düzenli biçimde saklanan ve kolayca sorgulanabilen veri topluluğu. Onu yöneten yazılıma Veri tabanı Yönetim Sistemi (VTYS, DBMS) denir.
- Benzetme: veri tabanı, kütüphanenin kataloglu arşivi gibidir; aranan kitap bütün raflar gezilmeden bulunur.
Veri Tabanı Kavramı
Dosya mı, Veri Tabanı 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 veri tabanları gerekir: verinin verimli işlenmesi için önemli özellikler sunarlar.
| Özellik | Düz dosya (txt, csv) | Veri tabanı |
|---|---|---|
| Arama | Dosya baştan sona okunur | İndeksle hızlı; tek satırlık SQL sorgusu |
| Güncelleme | Çoğu zaman tüm dosya yeniden yazılır | Yalnızca ilgili satır değişir |
| Eş zamanlı erişim | İki program aynı anda yazarsa veri bozulabilir | Kilitleme ve işlemlerle güvenli |
| Tutarlılık | Biçim denetimi tamamen programcıya kalır | Veri tipleri ve kısıtlar (NOT NULL, UNIQUE, CHECK) |
| Güvenlik | Dosya izni: ya hepsi ya hiçbiri | Kullanıcı, tablo ve alan düzeyinde yetki |
| Hata sonrası | Yarım kalan yazma dosyayı bozabilir | Yarım kalan işlem geri alınır |
Veri Tabanı Kavramı
Temel Özellikler
İndeksler sayesinde milyonlarca satır arasından aranan kayıt kısa sürede bulunur.
Yetki tablo, hatta sütun (alan) düzeyinde verilebilir: maaş sütununu yalnızca muhasebe birimi görebilir.
SQL bir standarttır; Java'da JDBC aynı arayüzle farklı veri tabanlarına erişir.
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 veri tabanları veri bütünlüğü kısıtları ve işlem (transaction) desteği sunar.
Veri Tabanı Kavramı
İlişkisel Model: Tablo, Satır, Sütun
- İlişkisel (relational) veri tabanı 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.
Veri Tabanı Kavramı
Veri Tabanı Türleri
Tablolar ve SQL. Oracle, SQL Server, MySQL, PostgreSQL. Kullanımın büyük bölümü (yaklaşık %77).
Belge, anahtar–değer, arama dizini gibi tablosuz modeller; belirli senaryolarda kullanılır: MongoDB, Elasticsearch (yaklaşık %23).
SQLite: programın içinde çalışan ilişkisel veri tabanı; veri paylaşımının az olduğu durumlarda yaygındır.
Örneklerde kurulumu en kolay olan SQLite kullanılır. SQL ve JDBC kodu diğer ilişkisel veri tabanlarında da neredeyse aynıdır.
SQLite
SQLite Nedir?
- Gömülü (embedded) bir ilişkisel veri tabanı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 veri tabanı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ü Veri Tabanı
| Sunucu tabanlı | SQLite | |
|---|---|---|
| Kurulum | Sunucu kurulur, kullanıcı ve şifre tanımlanır | Projeye tek bir jar eklenir |
| Veri | Sunucunun yönettiği birçok dosya | Tek bir .db dosyası |
| Eş zamanlı yazma | Çok kullanıcı | Aynı anda tek yazıcı |
SQLite
SQLite Sürücüsü ve DB Browser
Java'nın SQLite ile konuşmasını sağlayan jar dosyası (sqlite-jdbc-3.x.jar): github.com/xerial/sqlite-jdbc
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
- Jar dosyası indirilip projede bir
libklasörüne konur. - Projeye sağ tık → Build Path → Configure Build Path…
- Libraries sekmesi → Classpath → Add JARs… → jar seçilir → 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ı
| Komut | Görevi | Örnek | CRUD |
|---|---|---|---|
CREATE TABLE | Yeni tablo oluşturur | CREATE TABLE users (id INTEGER, name TEXT) | — |
INSERT INTO | Yeni kayıt ekler | INSERT INTO users(name) VALUES('Ali') | Create |
SELECT | Tabloyu sorgular | SELECT * FROM users | Read |
UPDATE | Var olan kayıtları günceller | UPDATE users SET name = 'Efe' WHERE id = 1 | Update |
DELETE | Kayıtları siler | DELETE FROM users WHERE id = 1 | Delete |
- SQL (Structured Query Language, yapılandırılmış sorgu dili): ilişkisel veri tabanlarının ortak dilidir.
- Anahtar sözcüklerde 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.WHEREkomutun 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 tipi | Java karşılığı |
|---|---|
INTEGER | int, long · getInt |
REAL | double · getDouble |
TEXT | String · getString |
BLOB | byte[] · getBytes |
NULL | değer yok |
SQL Temelleri
SQL Komutları Adım Adım
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)
Tablo oluşturuldu: üç sütun var, henüz hiç satır yok.
INSERT INTO users(name, email) VALUES('Ahmet Yılmaz', 'ahmet@example.com')
id verilmedi; SQLite 1 atadı. 1 satır etkilendi.
INSERT INTO users(name, email) VALUES('Ayşe Demir', 'ayse@example.com')
İkinci kayıt id = 2 aldı (en büyük id + 1).
UPDATE users SET name = 'Mehmet Akdeniz' WHERE id = 1
WHERE id = 1 yalnızca ilk satırı seçti; sadece name değişti, email aynı kaldı.
DELETE FROM users WHERE id = 1
id = 1 olan satır silindi. 1 satır etkilendi.
SELECT * FROM users
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
| Sorgu | Sonuç |
|---|---|
SELECT oyuncu, puan FROM skorlar | naz 81, ada 72, ece 58 |
SELECT oyuncu, puan FROM skorlar | can 95, efe 95, naz 81 |
SELECT oyuncu, puan FROM skorlar | ece 58, efe 95 |
SELECT COUNT(*), AVG(puan), MAX(puan) | 4 · 85.75 · 95 |
WHEREsatırları süzer (=,<>,<,>=,AND,OR,LIKE 'e%': “e ile başlayan”).ORDER BY … DESCbüyükten küçüğe sıralar; eşitlikte ikinci ölçüt (oyuncu) kullanılır.LIMIT 3ilk 3 satırı alır.
SQL Temelleri · Adım adım kod izleme
Bir SELECT Sorgusu Nasıl Çalışır?
String sql = "SELECT oyuncu, puan FROM skorlar "
+ "WHERE puan > 70 "
+ "ORDER BY oyuncu DESC LIMIT 2";
try (Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql)) {
while (rs.next()) {
System.out.println(rs.getString("oyuncu") + " "
+ rs.getInt("puan"));
}
}Sorgu tek bir metindir. skorlar tablosunda 5 satır var.
executeQuery sorguyu gönderir. Veri tabanı önce FROM: tablonun bütün satırları.
WHERE puan > 70: koşulu sağlamayan ece elenir; 4 satır kalır.
ORDER BY oyuncu DESC: adlar ters alfabetik sıraya dizilir (puana göre değil).
LIMIT 2: sıralanmış listenin ilk iki satırı sonuç kümesidir.
İmleç başlangıçta ilk satırın önündedir; next() onu 1. satıra taşır.
Sütunlar adlarıyla okunur ve yazdırılır.
İmleç 2. satıra geçer.
efe 95 yazılır.
Başka satır yok: next() false, döngü biter. try bitince rs ve st kapanır.
İlişkiler ve Gruplama
Tekrarı Önlemek: Normalleştirme
- Tek tabloda aynı bilgi (Ali'nin telefonu) her siparişte tekrarlanır: yer israfı ve tutarsızlık riski.
- Güncelleme anomalisi: telefon değişince her satır ayrı ayrı düzeltilmeli; biri unutulursa Ali'nin iki farklı telefonu olur.
- Ekleme anomalisi: henüz siparişi olmayan bir müşteri kaydedilemez.
- Silme anomalisi: Ece'nin tek siparişi silinince Ece'nin bilgisi de kaybolur.
- Normalleştirme (normalization): her bilgiyi tek bir yerde tutacak biçimde tabloları bölmek. Tablolar bir anahtarla (
musteri_id) bağlanır. - Birinci normal biçim: her hücrede tek değer.
'555-11, 555-33'gibi bir liste ayrı satırlara ya da ayrı bir tabloya taşınır.
İlişkiler ve Gruplama
Yabancı Anahtar ve İlişkiler
CREATE TABLE bolumler ( id INTEGER PRIMARY KEY, ad TEXT NOT NULL); CREATE TABLE ogrenciler ( id INTEGER PRIMARY KEY, ad TEXT NOT NULL, bolum_id INTEGER REFERENCES bolumler(id));
- Yabancı anahtar (foreign key): başka bir tablonun birincil anahtarını gösteren sütun. Bir bölümde çok öğrenci olabilir: bire çok (one-to-many) ilişki.
- Var olmayan bölüme öğrenci eklemek (
bolum_id = 9) ya da öğrencisi olan bölümü silmek reddedilir:FOREIGN KEY constraint failed. - SQLite bu denetimi varsayılan olarak yapmaz: her bağlantıda
stmt.execute("PRAGMA foreign_keys = ON")gerekir. Kapalıykenbolum_id = 9sessizce kabul edilir. - Çoka çok ilişki (öğrenci–ders) üçüncü bir ara tabloyla kurulur:
kayitlar(ogrenci_id, ders_id). - Java benzetmesi:
Ogrencinesnesindeki birBolumreferansı.
İlişkiler ve Gruplama
JOIN ile Tabloları Birleştirme
SELECT o.ad AS ogrenci, b.ad AS bolum FROM ogrenciler o JOIN bolumler b ON o.bolum_id = b.id ORDER BY o.id
| ogrenci | JOIN (INNER) | LEFT JOIN |
|---|---|---|
| Ada | Bilgisayar | Bilgisayar |
| Bora | Elektrik | Elektrik |
| Cem | Bilgisayar | Bilgisayar |
| Deniz | satır yok | NULL |
- JOIN (birleştirme): iki tablonun satırlarını
ONkoşuluna göre eşleyip tek bir sonuç tablosu üretir. ovebtablo takma adlarıdır. İki tabloda daadsütunu olduğu içino.ad,b.addiye ayrılır; yalnızadyazılırsa:ambiguous column name: ad.ASsonuç sütununa yeni ad verir: Java'dars.getString("bolum")ile okunur.- INNER JOIN (yalnız
JOIN): yalnızca eşi bulunan satırlar. BölümüNULLolan Deniz sonuçta yoktur. - LEFT JOIN: soldaki tablonun tüm satırları; eşi yoksa sağ taraf
NULL, Java'danull. ONunutulursa (FROM ogrenciler, bolumler) her satır her satırla eşleşir: 4 × 3 = 12 satırlık Kartezyen çarpım.
İlişkiler ve Gruplama · Adım adım kod izleme
JOIN Sorgusu Adım Adım
String sql = "SELECT o.ad AS ogrenci, b.ad AS bolum "
+ "FROM ogrenciler o JOIN bolumler b "
+ "ON o.bolum_id = b.id ORDER BY o.id";
try (ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
System.out.println(rs.getString("ogrenci") + " - "
+ rs.getString("bolum"));
}
}Sorgu iki tabloyu takma adlarla anar: o = ogrenciler, b = bolumler.
executeQuery: Ada'nın bolum_id'si 1; bolumler'de id = 1 bulunur → (Ada, Bilgisayar).
Bora → 2 → Elektrik.
Cem → 1: aynı bölüm satırı ikinci kez kullanılır.
Deniz'in bolum_id'si NULL: hiçbir id'ye eşit sayılmaz; satır sonuca girmez.
next() ilk satıra geçer; sütunlar AS ile verilen adlarla okunur.
İkinci satır yazılır.
Üçüncü satır yazılır.
Başka satır yok, döngü biter. LEFT JOIN yazılsaydı dördüncü satır Deniz - null olurdu.
İlişkiler ve Gruplama
Gruplama: GROUP BY ve HAVING
GROUP BYaynı değere sahip satırları bir grupta toplar; toplama fonksiyonları (COUNT,SUM,AVG,MIN,MAX) her grup için ayrı hesaplanır.WHEREgruplamadan önce satırları,HAVINGgruplamadan sonra grupları süzer.WHERE SUM(tutar) >= 150hatadır:misuse of aggregate: SUM().- Mantıksal çalışma sırası:
FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY→LIMIT.
Sorgu (SELECT musteri, COUNT(*), SUM(tutar) FROM siparisler …) | Sonuç |
|---|---|
GROUP BY musteri | Ali 3 200 · Can 1 200 · Ece 2 120 |
WHERE tutar >= 50 GROUP BY musteri | Ali 2 170 · Can 1 200 · Ece 1 80 |
GROUP BY musteri HAVING SUM(tutar) >= 150 | Ali 3 200 · Can 1 200 |
JOIN ile birlikte: SELECT b.ad, COUNT(o.id) FROM bolumler b LEFT JOIN ogrenciler o ON o.bolum_id = b.id GROUP BY b.id → Bilgisayar 2 · Elektrik 1 · Makine 0. COUNT(o.id) NULL'ları saymaz; COUNT(*) yazılsaydı öğrencisiz Makine 1 alırdı.
İlişkiler ve Gruplama · Adım adım kod izleme
GROUP BY Sorgusu Adım Adım
String sql = "SELECT musteri, COUNT(*) AS adet, "
+ "SUM(tutar) AS toplam FROM siparisler "
+ "GROUP BY musteri HAVING SUM(tutar) >= 150 "
+ "ORDER BY toplam DESC, musteri";
try (ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
System.out.println(rs.getString("musteri") + ": "
+ rs.getInt("adet") + " sipariş, "
+ rs.getInt("toplam") + " TL");
}
}siparisler tablosunda 6 satır var; sorgu tek bir metindir.
FROM ve GROUP BY musteri: satırlar müşteri adına göre üç gruba ayrılır.
Her grup için COUNT(*) ve SUM(tutar): Ali 120 + 50 + 30 = 200, Can 200, Ece 80 + 40 = 120.
HAVING SUM(tutar) >= 150: Ece'nin grubu elenir (120).
ORDER BY toplam DESC, musteri: Ali ile Can 200'de eşit; ikinci ölçüt ad, önce Ali.
next() ilk satıra geçer; takma adlar (adet, toplam) sütun adı olarak okunur.
İkinci satır yazılır.
Döngü biter. Sonuçta grup başına bir satır vardır; tek tek siparişler görünmez.
İlişkiler ve Gruplama
NULL: Değerin Olmaması
NULL“bilinmiyor / yok” demektir;0ya da boş metin ('') değildir.NULLile her karşılaştırma bilinmeyen sonuç verir:WHERE bolum_id = NULLhiçbir satırı seçmez. DoğrusuIS NULLya daIS NOT NULL.WHERE bolum_id <> 1de Deniz'i seçmez:NULL'un 1'den farklı olduğu bile bilinmez.NULL + 1sonucuNULL'dur.COALESCE(bolum_id, 0)NULLyerine 0 koyar.COUNT(*)satırları,COUNT(bolum_id)NULLolmayan değerleri sayar: 4 ve 3. Eşleşen satır yoksaAVGveSUMNULLdöndürür.
String sql = "SELECT ad, bolum_id FROM ogrenciler "
+ "WHERE id >= 3";
try (ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
int b = rs.getInt("bolum_id");
boolean bos = rs.wasNull();
System.out.println(rs.getString("ad") + " " + b
+ (bos ? " (NULL)" : ""));
}
}Cem 1 Deniz 0 (NULL)
getIntNULLiçin 0 döndürür; 0 mıNULLmı ayırt etmek için hemen ardındanwasNull()çağrılır.wasNull()son okunan sütuna bakar:getString("ad")'dan sonra çağrılsaydı adı sorardı ve(NULL)hiç yazılmazdı.getStringvegetObjectNULLiçinnulldöndürür.
İlişkiler ve Gruplama
İndeks: Aramayı Hızlandırmak
CREATE INDEX idx_ogr_ad ON ogrenciler(ad); EXPLAIN QUERY PLAN SELECT * FROM ogrenciler WHERE ad = 'Cem';
- İndeks (index): bir sütunun değerlerini sıralı tutan ek yapı (B-ağacı); kitabın sonundaki dizin gibi. Arama tüm tabloyu taramak yerine ilgili satıra gider.
- Birincil anahtar ve
UNIQUEsütunların indeksi kendiliğinden oluşur. - Bedeli: her
INSERT,UPDATE,DELETEindeksi de günceller ve indeks diskte yer kaplar. Sık aranan;WHERE,JOINveORDER BY'da kullanılan sütunlara konur. EXPLAIN QUERY PLAN(SQLite) sorgunun nasıl yürütüleceğini gösterir.
| Durum | EXPLAIN QUERY PLAN çıktısı |
|---|---|
| İndeks yokken | SCAN ogrenciler |
| İndeks varken | SEARCH ogrenciler USING INDEX idx_ogr_ad (ad=?) |
WHERE id = 3 | SEARCH … USING INTEGER PRIMARY KEY (rowid=?) |
WHERE ad LIKE '%em' | SCAN ogrenciler (baştaki joker indeksi kullanamaz) |
JDBC
JDBC (Java Database Connectivity)
- JDBC (Java Database Connectivity): Java'nın veri tabanlarına bağlanmak için sunduğu standart API'dir;
java.sqlpaketindedir ve JDK ile gelir. - Program yalnızca JDBC arayüzlerini kullanır; her veri tabanı üreticisi bu arayüzleri uygulayan bir sürücü (driver) sağlar.
- Veri tabanı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:
Connectionbir arayüzdür; SQLite'ta gerçek nesnenin sınıfıorg.sqlite.jdbc4.JDBC4Connection'dır.
JDBC
JDBC'nin Temel Türleri
| Tür | Görevi | Önemli metotlar |
|---|---|---|
DriverManager | Uygun sürücüyü bulur, bağlantı açar | getConnection(url), getConnection(url, kullanici, sifre) |
Connection | Veri tabanıyla açık bir oturum | createStatement(), prepareStatement(sql), commit(), rollback(), close() |
Statement | Sabit bir SQL metnini çalıştırır | execute, executeUpdate, executeQuery |
PreparedStatement | ? yer tutuculu, önceden hazırlanan SQL | setInt, setString, executeUpdate(), executeQuery() |
ResultSet | Sorgu sonucundaki satırlar ve bir imleç | next(), getInt("id"), getString("name") |
SQLException | Veri tabanı hatası; kontrol edilen istisna | getMessage(), 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.db | Tam yol ile dosya |
jdbc:sqlite::memory: | Bellekte geçici veri tabanı; bağlantı kapanınca silinir (denemeler için) |
jdbc:mysql://localhost:3306/testdb | MySQL sunucusu: makine, port, veri tabanı adı; ayrıca kullanıcı adı ve şifre |
- Biçim:
jdbc:<alt protokol>:<veri tabanı dosyası ya da 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ı
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, sonrastmt, en sonconn. - 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
// Veri tabanı 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()); }
Bağlantı başarılı!
getConnectiondosya yoksa oluşturur (boş veri tabanı).- Bağlantı kurulamazsa
nulldönmez,SQLExceptionfırlatır;if (conn != null)yalnızca ek bir güvencedir. SQLExceptionkontrol edilen istisnadır: yakalanmalı ya dathrowsile 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());
}Tablo oluşturuldu!
execute(sql)her tür SQL'i çalıştırır; sonuç kümesi üretirsetrue,CREATEiçinfalsedöner.IF NOT EXISTSsayesinde 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()) {
// Veri tabanına veri ekleniyor.
int sayi = stmt.executeUpdate(sql);
System.out.println("Veri eklendi! (" + sayi + " satır)");
} catch (SQLException e) {
System.out.println(e.getMessage());
}Veri eklendi! (1 satır)
executeUpdateetkilenen satır sayısını (int) döndürür.idverilmedi; SQLite1atadı.- 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()); }
ID: 1, Ad: Ahmet Yılmaz, E-posta: ahmet@example.com
executeQuerybirResultSetdöndürür.rs.next()imleci bir sonraki satıra taşır; satır varsatrue, yoksafalsedö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
Bağlantı açılır, sorgu çalışır. rs iki satırlık sonucu tutar; imleç ilk satırdan önce bekler.
rs.next() imleci 1. satıra taşır ve true döndürür: döngüye girilir.
Sütunlar o anki satırdan okunur.
Satır yazdırılır.
İmleç 2. satıra geçer; yine true.
Bu kez 2. satırın değerleri okunur.
İkinci satır yazdırılır.
Başka satır yok: rs.next() false döner, döngü biter.
Döngüden sonraki satır çalışır.
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()) { // Veri tabanı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()); }
Veri güncellendi! (1 satır)
- Öncesinde okuma:
ID: 1, Ad: Ahmet Yılmaz, …; sonrasında:ID: 1, Ad: Mehmet Akdeniz, … SETyalnızca yazılan sütunu değiştirir;emailaynı kalır.WHERE id = 1gü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()) { // Veri tabanındaki veri siliniyor. int sayi = stmt.executeUpdate(sql); System.out.println("Veri silindi! (" + sayi + " satır)"); } catch (SQLException e) { System.out.println(e.getMessage()); }
Veri silindi! (1 satır) Veri silindi! (0 satır)
- İkinci çalıştırmada silinecek satır kalmamıştır: hata oluşmaz,
executeUpdate0döner. - Bu yüzden mesajı dönen sayıya göre vermek daha doğrudur (Alıştırma 1).
- WHERE'siz
DELETE FROM userstüm satırları siler; tablonun kendisi kalır.
Temel İşlemler
execute, executeUpdate, executeQuery
| Metot | Döndürür | Kullanım |
|---|---|---|
execute | boolean | CREATE TABLE, DROP |
executeUpdate | int satır sayısı | INSERT, UPDATE, DELETE |
executeQuery | ResultSet | SELECT |
Yanlış metot hataya yol açar: executeQuery ile INSERT çalıştırmak → query does not return ResultSet.
> 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)
Temel İşlemler · Adım adım kod izleme
executeUpdate Ne Döndürür?
String[] adlar = {"zımba", "makas", "bant"};
int[] stok = {40, 0, 15};
PreparedStatement ek = conn.prepareStatement(
"INSERT INTO malzeme(ad, stok) VALUES(?, ?)");
for (int i = 0; i < adlar.length; i++) {
ek.setString(1, adlar[i]);
ek.setInt(2, stok[i]);
ek.executeUpdate();
}
int g = stmt.executeUpdate(
"UPDATE malzeme SET stok = stok + 10 WHERE stok < 20");
int s = stmt.executeUpdate("DELETE FROM malzeme WHERE stok > 100");
System.out.println(g + " güncellendi, " + s + " silindi");Ekleme komutu bir kez hazırlanır; döngüde yalnızca değerler değişecek.
İlk turda yer tutucular doldurulur.
executeUpdate 1 döndürür; SQLite id = 1 atar.
Aynı hazır komut yeni değerlerle yeniden çalışır.
Üçüncü ürün eklenir; i = 3 olunca döngü biter.
WHERE stok < 20 makas (0) ve bandı (15) seçer; ikisine 10 eklenir. Dönen değer etkilenen satır sayısı: 2.
Stoku 100'den büyük ürün yok: hata oluşmaz, yalnızca 0 döner.
Program bu sayılara bakarak "kayıt bulunamadı" gibi doğru mesajlar verebilir.
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();
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 · Adım adım kod izleme
SQL Enjeksiyonu Adım Adım
String girdi = "x' OR '1'='1";
String sql = "SELECT ad FROM kisiler WHERE ad = '"
+ girdi + "'";
int n = 0;
try (ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) n++;
}
System.out.println("Birleştirme: " + n + " satır");
PreparedStatement ps = conn.prepareStatement(
"SELECT ad FROM kisiler WHERE ad = ?");
ps.setString(1, girdi);
n = 0;
try (ResultSet rs = ps.executeQuery()) { while (rs.next()) n++; }
System.out.println("PreparedStatement: " + n + " satır");Kullanıcı ad yerine tırnak ve OR içeren bir metin yazdı. Tabloda 3 kişi var.
Birleştirme sonucu: kullanıcının tırnağı ad değerini kapattı, ardından gelen OR '1'='1' artık SQL'in parçası.
Veri tabanı koşulu her satırda değerlendirir: Ali için OR'un sağ tarafı her zaman doğru → satır seçilir.
Ayşe ve Can da seçilir: koşul hiçbir satırı elemez.
Döngü 3 kez döner. Bir giriş ekranında bu, şifresiz oturum açmak demektir.
Sonuç yazılır.
Bu kez SQL önce derlenir; ? yalnızca bir değerin yeri. Komutun yapısı artık değişemez.
Girdi bütünüyle tek bir metin değeri olarak bağlanır; içindeki tırnaklar sıradan karakterdir.
Hiçbir kişinin adı bu 12 karakterlik metne eşit değil: satır yok.
Aynı girdi, iki farklı sonuç. Kullanıcıdan gelen değer SQL'e her zaman ? ile verilir.
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.
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;maindethrowsile 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("---"); } }
1 Ahmet Akdeniz 35 2 Ayşe Demir 40 ---
- Sorguda değişken değer olmadığından düz
Statementyeterlidir. - Her
listeleçağrısı tablonun o anki hâlini okur; sonuna---yazar.
PreparedStatement
Kişi Programı: Adım Adım
kisiler.db yoksa oluşturulur; bağlantı ve stmt açılır.
Tablo oluşturulur; henüz boştur.
ekle: ?'ler doldurulur, satır eklenir, id = 1.
İkinci kişi id = 2 ile eklenir.
WHERE ad = ?: Ahmet'in soyadı ve yaşı değişir.
listele tüm satırları ve --- yazar.
Ahmet'in satırı silinir.
Tabloda yalnızca Ayşe kaldı.
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); } }
İleri JDBC
Otomatik Anahtar: getGeneratedKeys
String sql = "INSERT INTO ogrenciler(ad, bolum_id) "
+ "VALUES(?, ?)";
try (PreparedStatement ps = conn.prepareStatement(sql,
Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, "Ece");
ps.setInt(2, 3);
ps.executeUpdate();
try (ResultSet k = ps.getGeneratedKeys()) {
if (k.next()) {
System.out.println("Yeni id: " + k.getInt(1));
}
}
}Yeni id: 5
- Yeni satırın
id'sini veri tabanı belirler; program onugetGeneratedKeys()ile öğrenir. Örnek: yeni siparişin kalemlerini eklemek için siparişinid'si gerekir. prepareStatement'aStatement.RETURN_GENERATED_KEYSverilir; dönenResultSet'in tek sütunu yeni anahtardır.- Tabloda 4 öğrenci vardı; Ece
id = 5aldı. - Silinen kaydın numarası başka kayda geçmemeliyse (fatura, sipariş no)
AUTOINCREMENTkullanılır. MySQL'deki karşılığıAUTO_INCREMENT.
| 1, 2, 3 eklendi; 3 silindi; yeni satır → | id |
|---|---|
id INTEGER PRIMARY KEY | 3 (en büyük id + 1) |
id INTEGER PRIMARY KEY AUTOINCREMENT | 4 (kullanılmış numara verilmez) |
İleri JDBC
Satırdan Nesneye: DAO Deseni
class Ogrenci { private final int id; private final String ad; Ogrenci(int id, String ad) { this.id = id; this.ad = ad; } public String toString() { return "#" + id + " " + ad; } }
OgrenciDao metodu | SQL | Döndürür |
|---|---|---|
ekle(ad, bolum) | INSERT | yeni id |
bul(id) | SELECT … WHERE id = ? | Ogrenci ya da null |
bolumdekiler(bolum) | SELECT … WHERE bolum_id = ? | List<Ogrenci> |
sil(id) | DELETE | silindi mi (boolean) |
- Nesne yönelimli programda her satır bir nesneye dönüştürülür: tablo ≈ sınıf, satır ≈ nesne.
- DAO (Data Access Object, veri erişim nesnesi): bir tablonun bütün SQL'ini tek sınıfta toplar. Programın geri kalanı SQL görmez.
- Veri tabanı değişirse yalnızca DAO değişir. Bağlantı DAO'ya yapıcıyla verilir.
- Bu eşlemeyi JPA, Hibernate gibi ORM (nesne-ilişkisel eşleme) kütüphaneleri otomatik yapar.
İleri JDBC · Adım adım kod izleme
Satırdan Nesneye Adım Adım
static List<Ogrenci> bolumdekiler(Connection conn, int bolum) throws SQLException { List<Ogrenci> liste = new ArrayList<>(); String sql = "SELECT id, ad FROM ogrenciler " + "WHERE bolum_id = ? ORDER BY id"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, bolum); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { liste.add(new Ogrenci(rs.getInt("id"), rs.getString("ad"))); } } } return liste; } // main içinde: List<Ogrenci> liste = bolumdekiler(conn, 1); for (Ogrenci o : liste) System.out.println(o);
main metodu çağırır: bolum = 1. Bağlantı main'de açıktır.
Boş bir liste oluşturulur; sorgu ? ile hazırlanır.
? = 1 bağlanır, sorgu çalışır: bölüm 1'de Ada ve Cem var.
İlk satır: id = 1, ad = Ada okunur; new Ogrenci(1, "Ada") listeye eklenir.
İkinci satırdan ikinci nesne: Ogrenci #3 Cem.
next() false: try blokları biter, rs ve ps kapanır. Bağlantıyı onu açan main kapatır.
Metot List<Ogrenci> döndürür (2 öğe); çerçevesi yığından kalkar.
Her nesne toString() ile yazılır. main SQL'den habersizdir.
MySQL
Aynı Kod, Başka Veri Tabanı: MySQL
String url = "jdbc:mysql://localhost:3306/testdb"; String kullanici = "root"; String sifre = "gizli"; 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ı,
testdbveri tabanı oluşturulmalıdır. - Sürücü sınıfı
com.mysql.cj.jdbc.Driver'dır; eskicom.mysql.jdbc.Driveradı 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) veyaconn.rollback()(geri al).
Bölünmezlik: işlemin ya tamamı ya hiçbiri uygulanır.
Tutarlılık: işlem sonunda tüm kısıtlar (ör. bakiye ≥ 0) sağlanır.
Yalıtım: eş zamanlı işlemler birbirinin yarım hâlini görmez.
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); } }
300 TL aktarıldı. Geri alındı: 5000 TL Ali: 700 Ece: 800
CHECK (bakiye >= 0): bakiyeyi eksiye düşüren komutSQLExceptionfırlatır.- İkinci havalede Ece'ye 5000 TL yatırılmıştı;
rollbackbunu da geri aldı. yazdir,listelegibi birSELECTdöngüsüdür.
İşlemler
commit ve rollback: Adım Adım
Auto-commit kapatılır: bundan sonraki değişiklikler commit'e kadar geçicidir.
Ece'ye 300 TL yatırılır: 500 → 800.
Ali'den 300 TL çekilir: 1000 → 700. Kısıt sağlanıyor.
commit(): iki değişiklik birlikte kalıcı olur.
finally: auto-commit yeniden açılır; metot biter.
İkinci çağrı: Ece'ye 5000 TL yatırılır (geçici): 800 → 5800.
700 − 5000 < 0 olurdu: CHECK bozulur, SQLException fırlar.
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); } }
İşlemler
Toplu İşlem: addBatch ve executeBatch
conn.setAutoCommit(false); try (PreparedStatement ps = conn.prepareStatement( "INSERT INTO olcumler(deger) VALUES(?)")) { for (int i = 1; i <= 1000; i++) { ps.setInt(1, i); ps.addBatch(); } int[] sonuc = ps.executeBatch(); conn.commit(); System.out.println(sonuc.length + " komut, ilki " + sonuc[0] + " satır etkiledi"); } catch (SQLException e) { conn.rollback(); } conn.setAutoCommit(true);
1000 komut, ilki 1 satır etkiledi
addBatch()doldurulmuş komutu bir kuyruğa ekler;executeBatch()kuyruktakilerin hepsini gönderir ve her komutun etkilediği satır sayısınıint[]olarak döndürür.- Auto-commit açıkken her
INSERTayrı bir işlemdir ve diske ayrı ayrı yazılır. Disk üzerindeki bir.dbdosyasıyla yapılan bir denemede 1000 satır: tek tek yaklaşık 2,6 s, toplu ve tek işlemde yaklaşık 0,01 s (makineye göre değişir). - Hepsi tek işlem olduğu için biri başarısız olursa
rollback()tüm kuyruğun etkisini geri alır: ya hepsi ya hiçbiri. - Çok büyük yüklemelerde kuyruk parça parça gönderilir (ör. her 500
addBatch'te birexecuteBatch); bellek şişmez.
Özet
Özet
| Konu | Özü |
|---|---|
| Veri tabanı | Kalıcı, düzenli, sorgulanabilir veri; VTYS yönetir. Tablo, satır, sütun, birincil anahtar. |
| SQLite | Sunucusuz, gömülü, tek dosya. Adres: jdbc:sqlite:sample.db; sürücü jar'ı classpath'te olmalı. |
| SQL | CREATE TABLE, INSERT, SELECT, UPDATE, DELETE; WHERE, ORDER BY, LIMIT. |
| İlişkiler | Yabancı anahtar; JOIN … ON; GROUP BY / HAVING; NULL için IS NULL. |
| JDBC | DriverManager → Connection → Statement / PreparedStatement → ResultSet. |
| Çalıştırma | execute (tablo), executeUpdate → int, executeQuery → ResultSet; while (rs.next()). |
| Güvenlik | Kullanıcı girdisi her zaman ? ve setXxx ile; SQL metnine eklenmez. |
| İşlem | setAutoCommit(false), commit(), rollback(): ya hepsi ya hiçbiri (ACID). |
| Kaynaklar | try-with-resources: rs, stmt, conn ters sırada kapanır; SQLException kontrol edilen istisnadır. |
Özet
Sık Yapılan Hatalar
No suitable driver found for jdbc:sqlite:sample.dbunreported exception SQLException; must be caught or declared to be thrownno such table: usersVALUES(Ahmet, …):no such column: AhmetVALUES('Ali'nin'):near "nin": syntax errorexecuteQuery ile INSERT:query does not return ResultSetrs.getString(0); sıra 1'den başlar:column 0 out of bounds [1,3]Özet
Sık Yapılan Hatalar: İlişkiler, Gruplama, JDBC
= NULL ile karşılaştırmakWHERE bolum_id = NULL hata vermez, 0 satır döner. Doğrusu IS NULL.ON'suz birleştirmeFROM ogrenciler, bolumler: her satır her satırla eşleşir, 4 × 3 = 12 satır.ad var; tablo adı yazılmamış:ambiguous column name: adWHERE'de toplama fonksiyonuGrup koşulu HAVING'e yazılır:misuse of aggregate: SUM()PRAGMA foreign_keys = ON olmadan bolum_id = 9 sessizce kabul edilir.wasNull()'u geç çağırmakSon okunan sütuna bakar; ilgili getInt'in hemen ardından çağrılmalıdır.SELECT musteri, tutar … GROUP BY musteri: SQLite gruptaki herhangi bir satırın tutarını getirir; birçok veri tabanında hatadır.INSERTHer satır ayrı işlem: çok yavaş. setAutoCommit(false), addBatch, commit.Ö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, 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ı).UPDATEyalnızca 1 numaralı satırı değiştirdi.- Dosya kalıcıdır; ilk çalıştırmadan Ayşe (id 2) kalmıştı ve
IF NOT EXISTStabloyu 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
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 = ?ilePreparedStatement→ 0 satır.- Ali: 700, Ece: 5800. İkinci havalede yatırma hemen kalıcı oldu, çekme
CHECKyü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
- Silme programını değiştirin:
executeUpdate0 döndürürseKayıt bulunamadı.yazsın. ogrenciler(id, ad, vize, final)tablosunu oluşturun; Ada (60, 70), Bora (30, 50), Cem (80, 40) öğrencileriniPreparedStatementile bir döngüde ekleyin. Ortalaması (vize %40, final %60) 50 ve üzeri olanları büyükten küçüğe listeleyin.- Menülü bir program yazın: 1-Ekle, 2-Güncelle, 3-Sil, 4-Listele.
Scannerveswitchkullanın; güncellenecek/silinecek id yoksaKullanıcı bulunamadı.yazsın. - Bir oyunun skorlarını
skorlar(oyuncu, puan)tablosuna kaydedin ve en yüksek 10 skoru listeleyin.
Cevaplar / ipuçları
int sayi = stmt.executeUpdate(sql); if (sayi > 0) System.out.println("Veri silindi!"); else System.out.println("Kayıt bulunamadı.");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.nextInt()'ten sonranextLine()ile satır sonu temizlenir.UPDATE … WHERE id = ?veDELETE … WHERE id = ?içinexecuteUpdate'in döndürdüğü sayı 0 ise kullanıcı yoktur.SELECT oyuncu, puan FROM skorlar ORDER BY puan DESC LIMIT ?veps.setInt(1, 10). EklemedeINSERT 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.sqlpaketi (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.