Eğitim Portalı/Java/JDBC ile Veritabanı Bağlantısı
Java01-java/17-jdbc

JDBC ile Veritabanı Bağlantısı

Gerçek uygulamalar verilerini kalıcı olarak saklamak ister; işte burada veritabanları devreye girer. JDBC (Java Database Connectivity), Java'nın ilişkisel veritabanlarıyla konuşmak için sunduğu standart API'dir. Sprin…

JDBC ile Veritabanı Bağlantısı

Gerçek uygulamalar verilerini kalıcı olarak saklamak ister; işte burada veritabanları devreye girer. JDBC (Java Database Connectivity), Java'nın ilişkisel veritabanlarıyla konuşmak için sunduğu standart API'dir. Spring Data JPA gibi yüksek seviyeli araçlar bile altta JDBC kullanır; bu yüzden temelini anlamak, üst katmanların "ne yaptığını gerçekten anlamak" demektir. Bu bölümde temelden başlayıp DAO deseni, otomatik anahtarlar, transaction ve ilişkili tablolara kadar gerçek dünyada kullandığın her şeyi göreceğiz.

Örnekler H2 in-memory veritabanını kullanır; kurulum gerekmez, her çalıştırma temiz bir veritabanıyla başlar. Portaldaki "Çalıştır" özelliği H2 sürücüsünü otomatik classpath'e ekler; kendi bilgisayarında java -cp h2-*.jar Ornek1.java ile çalıştırabilirsin.

JDBC mimarisi

JDBC birkaç temel arayüz etrafında döner:

  • DriverManager — JDBC URL'ine uygun sürücüyü bulup bağlantı açar.
  • Connection — veritabanıyla açık oturum; işin bitince kapatılmalıdır.
  • Statement / PreparedStatement — SQL komutlarını çalıştırır.
  • ResultSetSELECT sonucunu satır satır gezdiğin imleç.

JDBC URL hedefi belirtir: jdbc:h2:mem:demo, jdbc:postgresql://localhost/mydb, jdbc:mysql://.... Çağrı türleri: execute (DDL), executeUpdate (INSERT/UPDATE/DELETE, etkilenen satır sayısını döndürür), executeQuery (SELECT, ResultSet döndürür).

Örnek 1 (./Ornek1.java) gerçekçi bir müşteri tablosu üzerinde bağlanmayı, tablo oluşturmayı, parametreli ekleme, filtreli sorgu ve toplama (COUNT/AVG) sorgusunu gösterir. Tüm kaynaklar try-with-resources ile otomatik kapatılır — veritabanı kodunda en sık hata kaynak sızıntısıdır.

PreparedStatement: güvenli ve parametreli

Kullanıcı verisini doğrudan SQL metnine yapıştırmak ("... WHERE ad = '" + ad + "'") hem hatalı hem de SQL injection açığına davetiyedir. Doğru yol her zaman PreparedStatement'tır: değerleri ? yer tutucularıyla, tipli biçimde yerleştirir, böylece hem güvenli hem de verimli olur:

PreparedStatement ps = con.prepareStatement("INSERT INTO musteri (ad, sehir) VALUES (?, ?)");
ps.setString(1, ad);
ps.setString(2, sehir);
ps.executeUpdate();

DAO deseni: veri erişimini düzenlemek

Gerçek projelerde SQL'i kodun her yerine serpiştirmeyiz; her varlık için veri erişimini tek bir sınıfta toplarız: DAO (Data Access Object) veya repository. Bu, iş mantığını SQL ayrıntısından ayırır ve test edilebilirliği artırır.

Örnek 2 (./Ornek2.java) bir UrunDAO yazar: ekle, bul, hepsi, fiyatGuncelle, sil — yani tam bir CRUD. İki önemli teknik içerir:

  • Generated keys: Veritabanının ürettiği otomatik id'yi geri almak:
    PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
    ps.executeUpdate();
    ResultSet keys = ps.getGeneratedKeys();
    int id = keys.next() ? keys.getInt(1) : -1;
  • Row mapping: Bir ResultSet satırını domain nesnesine (burada bir record) çevirmek.

Bu desen, Spring Data JPA'nın JpaRepository'sinin elle yazılmış halidir; ileride Spring'in bu tekrarlayan kodu nasıl ortadan kaldırdığını daha iyi takdir edeceksin.

Transaction, ilişkiler ve batch

Bazı işlemler bölünmez bir bütün olmalıdır. Bir sipariş; başlığı (siparis) ve kalemleri (siparis_kalem) ile birlikte ya tamamen kaydedilmeli ya da hiç. Ortada bir hata olursa sipariş yarım kalmamalı. Transaction bu "ya hepsi ya hiçbiri" garantisini verir:

con.setAutoCommit(false);
try {
    // birden çok ilişkili INSERT...
    con.commit();    // hepsi başarılı -> kalıcı
} catch (Exception e) {
    con.rollback();  // bir hata -> tümünü geri al
}

Örnek 3 (./Ornek3.java) bunu uçtan uca gösterir:

  • İlişkili tablolar: Bir siparişin başlığı ve kalemleri ayrı tablolarda; kalemler siparis_id ile bağlanır ve JOIN ile birlikte sorgulanır.
  • Toplu ekleme (batch): Çok sayıda kalemi tek tek değil, addBatch() + executeBatch() ile toplu göndermek çok daha verimlidir.
  • Rollback: Geçersiz bir kalem (adet=0) iş kuralını ihlal edince tüm sipariş geri alınır; veritabanında hiçbir iz kalmaz.

Bağlantı havuzu (connection pooling)

Her istekte yeni bir Connection açıp kapatmak pahalıdır. Gerçek uygulamalar bir bağlantı havuzu (HikariCP gibi) kullanır: hazır bağlantıları havuzda tutar, isteyene ödünç verir, geri alır. Spring Boot bunu otomatik yapılandırır; sen sadece DataSource'u kullanırsın.

JDBC'den ileriye

JDBC güçlüdür ama tekrarlayan "kazan kodu" (boilerplate) çoktur: aç/kapat, ResultSet'i nesneye çevir, exception ele al. Spring bu yükü önce JdbcTemplate ile, sonra Spring Data JPA ile büyük ölçüde ortadan kaldırır — ama hepsinin altında işte bu JDBC vardır.

Özet

JDBC mimarisini ve temel CRUD'u; PreparedStatement ile güvenli sorguları; DAO deseni, generated keys ve row mapping ile düzenli veri erişimini; transaction, ilişkili tablolar ve batch ile gerçek dünya senaryolarını gördük. Bağlantı havuzuna değindik. Bu, Java'nın veriyle buluştuğu sağlam zemindir. Sırada, projelerini ve bağımlılıklarını yöneten araçlar: Maven ve Gradle.

Kod Örnekleri(3)

Ornek1

ortam gerekir
Ornek1.java
1// Ornek1: JDBC temelleri — gerçekçi bir müşteri tablosu üzerinde bağlan, oluştur, ekle, sorgula.
2// H2 in-memory veritabanı kullanılır (kurulum gerekmez). Portaldaki "Çalıştır" H2'yi
3// otomatik classpath'e ekler; kendi bilgisayarında: java -cp h2-*.jar Ornek1.java
4import java.sql.Connection;
5import java.sql.DriverManager;
6import java.sql.PreparedStatement;
7import java.sql.ResultSet;
8import java.sql.SQLException;
9import java.sql.Statement;
10
11public class Ornek1 {
12
13    static final String URL = "jdbc:h2:mem:magaza;DB_CLOSE_DELAY=-1";
14
15    public static void main(String[] args) throws SQLException {
16        // try-with-resources: Connection blok bitince otomatik kapanır.
17        try (Connection con = DriverManager.getConnection(URL, "sa", "")) {
18            System.out.println("Bağlanıldı: " + con.getMetaData().getDatabaseProductName()
19                    + " " + con.getMetaData().getDatabaseProductVersion());
20
21            // DDL: şema oluştur.
22            try (Statement st = con.createStatement()) {
23                st.execute("""
24                    CREATE TABLE musteri (
25                        id      INT AUTO_INCREMENT PRIMARY KEY,
26                        ad      VARCHAR(60) NOT NULL,
27                        sehir   VARCHAR(40),
28                        bakiye  DECIMAL(10,2) DEFAULT 0
29                    )
30                    """);
31            }
32            System.out.println("Tablo oluşturuldu.\n");
33
34            // PreparedStatement ile parametreli ekleme (güvenli yol).
35            String ekle = "INSERT INTO musteri (ad, sehir, bakiye) VALUES (?, ?, ?)";
36            try (PreparedStatement ps = con.prepareStatement(ekle)) {
37                ekle(ps, "Ada Yılmaz", "İzmir", 1500);
38                ekle(ps, "Burak Demir", "Ankara", 300);
39                ekle(ps, "Ceren Kaya", "İzmir", 2750);
40                ekle(ps, "Deniz Ak", "İstanbul", 50);
41            }
42            System.out.println("4 müşteri eklendi.");
43
44            // Filtreli sorgu: bakiyesi 1000'den fazla olanlar.
45            System.out.println("\nBakiyesi 1000+ olan müşteriler:");
46            String sorgu = "SELECT id, ad, sehir, bakiye FROM musteri WHERE bakiye > ? ORDER BY bakiye DESC";
47            try (PreparedStatement ps = con.prepareStatement(sorgu)) {
48                ps.setDouble(1, 1000);
49                try (ResultSet rs = ps.executeQuery()) {
50                    while (rs.next()) {
51                        System.out.printf("  #%d %-12s %-10s %,.2f TL%n",
52                                rs.getInt("id"), rs.getString("ad"), rs.getString("sehir"), rs.getDouble("bakiye"));
53                    }
54                }
55            }
56
57            // Toplama (aggregate) sorgusu.
58            try (Statement st = con.createStatement();
59                 ResultSet rs = st.executeQuery("SELECT COUNT(*) adet, AVG(bakiye) ort FROM musteri")) {
60                if (rs.next()) {
61                    System.out.printf("%nToplam %d müşteri, ortalama bakiye %,.2f TL%n",
62                            rs.getInt("adet"), rs.getDouble("ort"));
63                }
64            }
65        }
66    }
67
68    static void ekle(PreparedStatement ps, String ad, String sehir, double bakiye) throws SQLException {
69        ps.setString(1, ad);
70        ps.setString(2, sehir);
71        ps.setDouble(3, bakiye);
72        ps.executeUpdate();
73    }
74}
canlı çalıştırma için ortam gerekir

Bu örnek canlı veritabanı bağlantısı (JDBC sürücüsü) gerektirir; tek başına java Ornek1.java ile çalışmaz. Çalışma mantığı yukarıdaki anlatım ve kodda açıklanmıştır.

Ornek2

ortam gerekir
Ornek2.java
1// Ornek2: DAO (Data Access Object) deseni — veri erişimini temiz bir sınıfta toplamak.
2// Spring Data JPA'nın Repository'lerinin elle yazılmış hali. Tam bir CRUD + generated keys.
3// Çalıştırma: java -cp h2-*.jar Ornek2.java  (portalda otomatik)
4import java.sql.Connection;
5import java.sql.DriverManager;
6import java.sql.PreparedStatement;
7import java.sql.ResultSet;
8import java.sql.SQLException;
9import java.sql.Statement;
10import java.util.ArrayList;
11import java.util.List;
12import java.util.Optional;
13
14public class Ornek2 {
15
16    // Domain nesnesi (immutable record).
17    record Urun(Integer id, String ad, double fiyat, int stok) {}
18
19    public static void main(String[] args) throws SQLException {
20        try (Connection con = DriverManager.getConnection("jdbc:h2:mem:dao;DB_CLOSE_DELAY=-1", "sa", "")) {
21            UrunDAO dao = new UrunDAO(con);
22            dao.tabloOlustur();
23
24            // CREATE — ekleme generated key (otomatik id) döndürür.
25            int id1 = dao.ekle(new Urun(null, "Klavye", 450, 20));
26            int id2 = dao.ekle(new Urun(null, "Mouse", 250, 35));
27            dao.ekle(new Urun(null, "Monitör", 3200, 8));
28            System.out.println("Eklendi. İlk iki id: " + id1 + ", " + id2);
29
30            // READ
31            System.out.println("\nTüm ürünler:");
32            dao.hepsi().forEach(u -> System.out.println("  " + u));
33
34            System.out.println("\nid=" + id2 + " ürün: " + dao.bul(id2).orElse(null));
35            System.out.println("id=999 ürün     : " + dao.bul(999).map(Object::toString).orElse("(bulunamadı)"));
36
37            // UPDATE — fiyat güncelle
38            dao.fiyatGuncelle(id1, 399);
39            System.out.println("\nGüncel Klavye: " + dao.bul(id1).orElse(null));
40
41            // DELETE
42            dao.sil(id2);
43            System.out.println("\nSilme sonrası ürün sayısı: " + dao.hepsi().size());
44        }
45    }
46
47    // DAO: belirli bir nesne için tüm SQL erişimini tek yerde toplar.
48    static class UrunDAO {
49        private final Connection con;
50        UrunDAO(Connection con) { this.con = con; }
51
52        void tabloOlustur() throws SQLException {
53            try (Statement st = con.createStatement()) {
54                st.execute("CREATE TABLE urun (id INT AUTO_INCREMENT PRIMARY KEY, ad VARCHAR(80), fiyat DECIMAL(10,2), stok INT)");
55            }
56        }
57
58        // RETURN_GENERATED_KEYS: veritabanının ürettiği id'yi geri alırız.
59        int ekle(Urun u) throws SQLException {
60            String sql = "INSERT INTO urun (ad, fiyat, stok) VALUES (?, ?, ?)";
61            try (PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
62                ps.setString(1, u.ad());
63                ps.setDouble(2, u.fiyat());
64                ps.setInt(3, u.stok());
65                ps.executeUpdate();
66                try (ResultSet keys = ps.getGeneratedKeys()) {
67                    keys.next();
68                    return keys.getInt(1);
69                }
70            }
71        }
72
73        Optional<Urun> bul(int id) throws SQLException {
74            try (PreparedStatement ps = con.prepareStatement("SELECT * FROM urun WHERE id = ?")) {
75                ps.setInt(1, id);
76                try (ResultSet rs = ps.executeQuery()) {
77                    return rs.next() ? Optional.of(map(rs)) : Optional.empty();
78                }
79            }
80        }
81
82        List<Urun> hepsi() throws SQLException {
83            List<Urun> liste = new ArrayList<>();
84            try (Statement st = con.createStatement();
85                 ResultSet rs = st.executeQuery("SELECT * FROM urun ORDER BY id")) {
86                while (rs.next()) liste.add(map(rs));
87            }
88            return liste;
89        }
90
91        void fiyatGuncelle(int id, double yeniFiyat) throws SQLException {
92            try (PreparedStatement ps = con.prepareStatement("UPDATE urun SET fiyat = ? WHERE id = ?")) {
93                ps.setDouble(1, yeniFiyat);
94                ps.setInt(2, id);
95                ps.executeUpdate();
96            }
97        }
98
99        void sil(int id) throws SQLException {
100            try (PreparedStatement ps = con.prepareStatement("DELETE FROM urun WHERE id = ?")) {
101                ps.setInt(1, id);
102                ps.executeUpdate();
103            }
104        }
105
106        // ResultSet satırını domain nesnesine çevirir (row mapping).
107        private Urun map(ResultSet rs) throws SQLException {
108            return new Urun(rs.getInt("id"), rs.getString("ad"), rs.getDouble("fiyat"), rs.getInt("stok"));
109        }
110    }
111}
canlı çalıştırma için ortam gerekir

Bu örnek canlı veritabanı bağlantısı (JDBC sürücüsü) gerektirir; tek başına java Ornek2.java ile çalışmaz. Çalışma mantığı yukarıdaki anlatım ve kodda açıklanmıştır.

Ornek3

ortam gerekir
Ornek3.java
1// Ornek3: Transaction + ilişkili tablolar + toplu ekleme (batch).
2// Gerçek senaryo: bir sipariş ve kalemleri TEK bir transaction'da kaydedilir;
3// bir hata olursa HER ŞEY geri alınır (sipariş yarım kalmaz).
4// Çalıştırma: java -cp h2-*.jar Ornek3.java  (portalda otomatik)
5import java.sql.Connection;
6import java.sql.DriverManager;
7import java.sql.PreparedStatement;
8import java.sql.ResultSet;
9import java.sql.SQLException;
10import java.sql.Statement;
11import java.util.List;
12
13public class Ornek3 {
14
15    record Kalem(String urun, int adet, double birimFiyat) {}
16
17    static final String URL = "jdbc:h2:mem:siparis;DB_CLOSE_DELAY=-1";
18
19    public static void main(String[] args) throws SQLException {
20        try (Connection con = DriverManager.getConnection(URL, "sa", "")) {
21            semaOlustur(con);
22
23            // 1) Başarılı sipariş: sipariş başlığı + kalemler tek transaction'da.
24            int siparisId = siparisOlustur(con, "Ada", List.of(
25                    new Kalem("Klavye", 2, 450),
26                    new Kalem("Mouse", 1, 250),
27                    new Kalem("Mousepad", 3, 80)
28            ));
29            System.out.println("Sipariş #" + siparisId + " başarıyla oluşturuldu (commit).");
30            siparisYazdir(con, siparisId);
31
32            // 2) Hatalı sipariş: bir kalemde geçersiz adet -> tüm sipariş geri alınır (rollback).
33            System.out.println("\n--- Geçersiz sipariş deneniyor (adet=0) ---");
34            try {
35                siparisOlustur(con, "Burak", List.of(
36                        new Kalem("Monitör", 1, 3200),
37                        new Kalem("Kablo", 0, 50)   // geçersiz: adet 0
38                ));
39            } catch (RuntimeException e) {
40                System.out.println("Hata: " + e.getMessage() + " -> rollback yapıldı.");
41            }
42
43            // Rollback çalıştıysa Burak'ın siparişi HİÇ oluşmamış olmalı.
44            try (Statement st = con.createStatement();
45                 ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM siparis")) {
46                rs.next();
47                System.out.println("Toplam sipariş sayısı: " + rs.getInt(1) + " (Burak'ınki kaydedilmedi)");
48            }
49        }
50    }
51
52    static void semaOlustur(Connection con) throws SQLException {
53        try (Statement st = con.createStatement()) {
54            st.execute("CREATE TABLE siparis (id INT AUTO_INCREMENT PRIMARY KEY, musteri VARCHAR(40), toplam DECIMAL(10,2))");
55            st.execute("""
56                CREATE TABLE siparis_kalem (
57                    id          INT AUTO_INCREMENT PRIMARY KEY,
58                    siparis_id  INT,
59                    urun        VARCHAR(60),
60                    adet        INT,
61                    tutar       DECIMAL(10,2)
62                )
63                """);
64        }
65    }
66
67    // Sipariş + kalemleri TEK transaction'da kaydeder; hata olursa tümünü geri alır.
68    static int siparisOlustur(Connection con, String musteri, List<Kalem> kalemler) throws SQLException {
69        con.setAutoCommit(false); // transaction başlat
70        try {
71            // İş kuralı: adet pozitif olmalı. Değilse transaction'ı iptal et.
72            for (Kalem k : kalemler) {
73                if (k.adet() <= 0) throw new RuntimeException("geçersiz adet: " + k.urun());
74            }
75
76            double toplam = kalemler.stream().mapToDouble(k -> k.adet() * k.birimFiyat()).sum();
77
78            // Sipariş başlığını ekle, üretilen id'yi al.
79            int siparisId;
80            try (PreparedStatement ps = con.prepareStatement(
81                    "INSERT INTO siparis (musteri, toplam) VALUES (?, ?)", Statement.RETURN_GENERATED_KEYS)) {
82                ps.setString(1, musteri);
83                ps.setDouble(2, toplam);
84                ps.executeUpdate();
85                try (ResultSet keys = ps.getGeneratedKeys()) {
86                    keys.next();
87                    siparisId = keys.getInt(1);
88                }
89            }
90
91            // Kalemleri TOPLU (batch) ekle — tek seferde, daha verimli.
92            try (PreparedStatement ps = con.prepareStatement(
93                    "INSERT INTO siparis_kalem (siparis_id, urun, adet, tutar) VALUES (?, ?, ?, ?)")) {
94                for (Kalem k : kalemler) {
95                    ps.setInt(1, siparisId);
96                    ps.setString(2, k.urun());
97                    ps.setInt(3, k.adet());
98                    ps.setDouble(4, k.adet() * k.birimFiyat());
99                    ps.addBatch();   // sıraya ekle
100                }
101                ps.executeBatch();   // hepsini tek seferde gönder
102            }
103
104            con.commit();            // her şey başarılı -> kalıcı yap
105            return siparisId;
106        } catch (RuntimeException | SQLException e) {
107            con.rollback();          // herhangi bir hata -> hiçbir şey kaydedilmesin
108            throw new RuntimeException(e.getMessage());
109        } finally {
110            con.setAutoCommit(true);
111        }
112    }
113
114    // JOIN ile siparişi ve kalemlerini birlikte yazdırır.
115    static void siparisYazdir(Connection con, int siparisId) throws SQLException {
116        String sql = """
117            SELECT s.musteri, s.toplam, k.urun, k.adet, k.tutar
118            FROM siparis s JOIN siparis_kalem k ON k.siparis_id = s.id
119            WHERE s.id = ?
120            """;
121        try (PreparedStatement ps = con.prepareStatement(sql)) {
122            ps.setInt(1, siparisId);
123            try (ResultSet rs = ps.executeQuery()) {
124                boolean baslik = false;
125                while (rs.next()) {
126                    if (!baslik) {
127                        System.out.printf("  Müşteri: %s | Sipariş toplamı: %,.2f TL%n",
128                                rs.getString("musteri"), rs.getDouble("toplam"));
129                        baslik = true;
130                    }
131                    System.out.printf("    - %-10s x%d = %,.2f TL%n",
132                            rs.getString("urun"), rs.getInt("adet"), rs.getDouble("tutar"));
133                }
134            }
135        }
136    }
137}
canlı çalıştırma için ortam gerekir

Bu örnek canlı veritabanı bağlantısı (JDBC sürücüsü) gerektirir; tek başına java Ornek3.java ile çalışmaz. Çalışma mantığı yukarıdaki anlatım ve kodda açıklanmıştır.