Veri Erişimi: Spring JDBC ve JdbcTemplate
Neredeyse her kurumsal uygulama bir veritabanıyla konuşur. Java bölümünde ham JDBC'yi öğrendin ve onun ne kadar "kazan kodu" (boilerplate) gerektirdiğini gördün: bağlantı aç, statement hazırla, ResultSet'i gez, kaynak…
Veri Erişimi: Spring JDBC ve JdbcTemplate
Neredeyse her kurumsal uygulama bir veritabanıyla konuşur. Java bölümünde ham JDBC'yi öğrendin
ve onun ne kadar "kazan kodu" (boilerplate) gerektirdiğini gördün: bağlantı aç, statement
hazırla, ResultSet'i gez, kaynakları kapat, SQLException'ı ele al... Hem de her sorguda.
Spring'in JdbcTemplate'i bu tekrarlayan yükü ortadan kaldırır; sen yalnızca SQL'i ve satır
eşlemesini yazarsın. Bu bölüm, Spring'in veri erişim felsefesinin giriş kapısıdır.
Sorun: ham JDBC boilerplate'i
Ham JDBC'de basit bir okuma bile try-with-resources, Connection, PreparedStatement,
ResultSet ve döngü gerektirir. Bu kod her sorguda tekrarlanır, asıl niyeti gölgeler ve kaynak
sızıntısına açıktır. Örnek 1 (./Ornek1.java) aynı sorguyu önce ham JDBC ile (uzun), sonra
JdbcTemplate ile (tek satır) yazarak farkı somut gösterir:
// Ham JDBC: bağlantı/statement/resultset + döngü + kapatma... // JdbcTemplate: tek satır jdbc.query("SELECT id, ad FROM urun", (rs, n) -> rs.getInt("id") + " " + rs.getString("ad"));
JdbcTemplate; bağlantı yönetimini, kaynak temizliğini ve istisna çevrimini senin yerine yapar.
DataSource ve JdbcTemplate
Her şey bir DataSource ile başlar — veritabanı bağlantılarının kaynağı. Üretimde bu, bir
bağlantı havuzudur (HikariCP); örneklerde basit bir DriverManagerDataSource kullanıyoruz:
var ds = new DriverManagerDataSource("jdbc:h2:mem:db", "sa", ""); ds.setDriverClassName("org.h2.Driver"); JdbcTemplate jdbc = new JdbcTemplate(ds);
Sık kullanılan metotlar:
update(sql, params...)— INSERT/UPDATE/DELETE (etkilenen satır sayısı).query(sql, rowMapper, params...)— birden çok satır →List<T>.queryForObject(sql, type, params...)— tek değer/satır.execute(sql)— DDL.
RowMapper ve DAO
RowMapper<T>, bir ResultSet satırını domain nesnesine çeviren küçük bir fonksiyondur; bir kez
yazıp her sorguda yeniden kullanırsın. Tüm veri erişimini bir DAO (veya repository) sınıfında
toplamak, iş mantığını SQL ayrıntısından ayırır:
private final RowMapper<Musteri> mapper = (rs, n) -> new Musteri(rs.getInt("id"), rs.getString("ad"), rs.getString("sehir")); List<Musteri> hepsi() { return jdbc.query("SELECT * FROM musteri", mapper); }
Örnek 2 (./Ornek2.java) tam bir MusteriDao yazar: ekleme, listeleme, id ile bulma, güncelleme.
Ayrıca NamedParameterJdbcTemplate ile ? yerine :ad gibi isimli parametreleri gösterir
— çok parametreli sorgularda okunabilirliği artırır.
Exception translation (istisna çevrimi)
Spring veri erişiminin sessiz ama önemli bir gücü: teknik, veritabanına özel SQLException'ları
(çoğu checked ve sürücüye göre kod/mesajı değişir) tutarlı, taşınabilir ve unchecked bir
hiyerarşiye — DataAccessException — çevirir:
DuplicateKeyException— benzersizlik/birincil anahtar çakışmasıDataIntegrityViolationException— kısıt ihlaliEmptyResultDataAccessException—queryForObjectsonuç bulamadı
try { jdbc.update("INSERT ... (id=1 zaten var)"); } catch (DuplicateKeyException e) { /* anlamlı, taşınabilir */ }
Örnek 3 (./Ornek3.java) çift anahtar ve boş sonuç senaryolarında bunu gösterir. Böylece iş
kodun hangi veritabanını kullandığından bağımsız ve temiz kalır. @Repository ile işaretli
bean'lerde bu çeviri otomatik devreye girer (bkz. stereotype bölümü).
JdbcTemplate'ten ORM'e
JdbcTemplate, SQL'i sen yazarken boilerplate'i alır — kontrol sende kalır. İhtiyaç büyüdükçe,
nesne-tablo eşlemesini (ORM) ve sorgu üretimini de otomatikleştiren Spring Data JPA / Hibernate
devreye girer; bunu Spring Boot bölümünde, gerçek entity'ler ve JpaRepository ile göreceğiz.
Ama altta yine bu JDBC temeli vardır.
Özet
Ham JDBC'nin boilerplate sorununu (Örnek 1), JdbcTemplate + RowMapper ile DAO yazmayı ve isimli parametreleri (Örnek 2) ve Spring'in istisna çevrimini (Örnek 3) öğrendik. Veri erişimi artık çok daha temiz. Sırada, bu veri işlemlerini güvenli ve bütünlüklü kılan kritik konu: transaction yönetimi.
▶ Kod Örnekleri(3)
Ornek1
ortam gerekir1// Ornek1: PROBLEM (ham JDBC boilerplate) → ÇÖZÜM (Spring JdbcTemplate).
2// Aynı sorguyu önce elle JDBC ile, sonra JdbcTemplate ile yazıp farkı görüyoruz.
3// Çalıştırma: portal Spring + H2 classpath'iyle çalıştırır.
4import org.springframework.jdbc.core.JdbcTemplate;
5import org.springframework.jdbc.datasource.DriverManagerDataSource;
6
7import javax.sql.DataSource;
8import java.sql.Connection;
9import java.sql.PreparedStatement;
10import java.sql.ResultSet;
11
12public class Ornek1 {
13
14 public static void main(String[] args) throws Exception {
15 // H2 bellek-içi veritabanı için bir DataSource (demo amaçlı basit sürücü kaynağı).
16 DriverManagerDataSource ds = new DriverManagerDataSource(
17 "jdbc:h2:mem:jt;DB_CLOSE_DELAY=-1", "sa", "");
18 ds.setDriverClassName("org.h2.Driver");
19
20 JdbcTemplate jdbc = new JdbcTemplate(ds);
21 // Şema + veri (JdbcTemplate.execute/update tek satır).
22 jdbc.execute("CREATE TABLE urun (id INT PRIMARY KEY, ad VARCHAR(50), fiyat DECIMAL(10,2))");
23 jdbc.update("INSERT INTO urun VALUES (?, ?, ?)", 1, "Klavye", 450);
24 jdbc.update("INSERT INTO urun VALUES (?, ?, ?)", 2, "Mouse", 250);
25
26 // --- PROBLEM: ham JDBC ile okuma (bağlantı aç/kapat, ResultSet, try-catch...) ---
27 System.out.println("Ham JDBC ile (uzun ve gürültülü):");
28 hamJdbcOku(ds);
29
30 // --- ÇÖZÜM: JdbcTemplate ile aynı iş (tek satır) ---
31 System.out.println("\nJdbcTemplate ile (tek satır):");
32 jdbc.query("SELECT id, ad, fiyat FROM urun ORDER BY id",
33 (rs, satirNo) -> " #" + rs.getInt("id") + " " + rs.getString("ad") + " - " + rs.getDouble("fiyat"))
34 .forEach(System.out::println);
35
36 // Tek değer okuma da çok kısa:
37 Integer adet = jdbc.queryForObject("SELECT COUNT(*) FROM urun", Integer.class);
38 System.out.println("\nToplam ürün: " + adet);
39
40 System.out.println("""
41
42 --- JdbcTemplate ne kazandırır? ---
43 - Bağlantı açma/kapatma, Statement/ResultSet yönetimi, kaynak temizliği -> OTOMATİK.
44 - try-catch-finally boilerplate'i ortadan kalkar.
45 - SQLException -> Spring'in DataAccessException'ına (unchecked) çevrilir.
46 Sen sadece SQL'i ve satır eşlemesini (RowMapper) yazarsın.""");
47 }
48
49 // Ham JDBC: her şeyi elle yönetmek zorundayız (kıyas için).
50 static void hamJdbcOku(DataSource ds) throws Exception {
51 try (Connection con = ds.getConnection();
52 PreparedStatement ps = con.prepareStatement("SELECT id, ad, fiyat FROM urun ORDER BY id");
53 ResultSet rs = ps.executeQuery()) {
54 while (rs.next()) {
55 System.out.println(" #" + rs.getInt("id") + " " + rs.getString("ad") + " - " + rs.getDouble("fiyat"));
56 }
57 }
58 }
59}Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek1.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.
Ornek2
ortam gerekir1// Ornek2: JdbcTemplate ile DAO — RowMapper, queryForObject, update ve NamedParameterJdbcTemplate.
2// Çalıştırma: portal Spring + H2 classpath'iyle çalıştırır.
3import org.springframework.jdbc.core.JdbcTemplate;
4import org.springframework.jdbc.core.RowMapper;
5import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
6import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
7import org.springframework.jdbc.datasource.DriverManagerDataSource;
8
9import java.util.List;
10import java.util.Optional;
11
12public class Ornek2 {
13
14 record Musteri(int id, String ad, String sehir) {}
15
16 public static void main(String[] args) {
17 DriverManagerDataSource ds = new DriverManagerDataSource(
18 "jdbc:h2:mem:dao;DB_CLOSE_DELAY=-1", "sa", "");
19 ds.setDriverClassName("org.h2.Driver");
20
21 MusteriDao dao = new MusteriDao(new JdbcTemplate(ds));
22 dao.tabloOlustur();
23 dao.ekle(new Musteri(1, "Ada", "İzmir"));
24 dao.ekle(new Musteri(2, "Burak", "Ankara"));
25 dao.ekle(new Musteri(3, "Ceren", "İzmir"));
26
27 System.out.println("Tüm müşteriler:");
28 dao.hepsi().forEach(m -> System.out.println(" " + m));
29
30 System.out.println("\nid=2: " + dao.bul(2).orElse(null));
31 System.out.println("İzmir'dekiler (named params): " + dao.sehirdekiler("İzmir").size() + " müşteri");
32
33 dao.sehirGuncelle(2, "İstanbul");
34 System.out.println("Güncelleme sonrası id=2: " + dao.bul(2).orElse(null));
35 }
36
37 // DAO: tüm veri erişimini JdbcTemplate ile tek sınıfta toplar.
38 static class MusteriDao {
39 private final JdbcTemplate jdbc;
40 private final NamedParameterJdbcTemplate namedJdbc;
41
42 // ResultSet satırını Musteri'ye çeviren yeniden kullanılabilir eşleyici.
43 private final RowMapper<Musteri> mapper =
44 (rs, n) -> new Musteri(rs.getInt("id"), rs.getString("ad"), rs.getString("sehir"));
45
46 MusteriDao(JdbcTemplate jdbc) {
47 this.jdbc = jdbc;
48 this.namedJdbc = new NamedParameterJdbcTemplate(jdbc);
49 }
50
51 void tabloOlustur() {
52 jdbc.execute("CREATE TABLE musteri (id INT PRIMARY KEY, ad VARCHAR(50), sehir VARCHAR(40))");
53 }
54
55 void ekle(Musteri m) {
56 jdbc.update("INSERT INTO musteri (id, ad, sehir) VALUES (?, ?, ?)", m.id(), m.ad(), m.sehir());
57 }
58
59 List<Musteri> hepsi() {
60 return jdbc.query("SELECT * FROM musteri ORDER BY id", mapper);
61 }
62
63 Optional<Musteri> bul(int id) {
64 var liste = jdbc.query("SELECT * FROM musteri WHERE id = ?", mapper, id);
65 return liste.stream().findFirst();
66 }
67
68 // İsimli parametreler: ? yerine :ad kullanır, okunaklılığı artırır.
69 List<Musteri> sehirdekiler(String sehir) {
70 var params = new MapSqlParameterSource("sehir", sehir);
71 return namedJdbc.query("SELECT * FROM musteri WHERE sehir = :sehir", params, mapper);
72 }
73
74 void sehirGuncelle(int id, String yeniSehir) {
75 jdbc.update("UPDATE musteri SET sehir = ? WHERE id = ?", yeniSehir, id);
76 }
77 }
78}Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek2.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.
Ornek3
ortam gerekir1// Ornek3: Exception translation — Spring, teknik SQLException'ları tutarlı DataAccessException'a çevirir.
2// Çalıştırma: portal Spring + H2 classpath'iyle çalıştırır.
3import org.springframework.dao.DataAccessException;
4import org.springframework.dao.DuplicateKeyException;
5import org.springframework.dao.EmptyResultDataAccessException;
6import org.springframework.jdbc.core.JdbcTemplate;
7import org.springframework.jdbc.datasource.DriverManagerDataSource;
8
9public class Ornek3 {
10
11 public static void main(String[] args) {
12 DriverManagerDataSource ds = new DriverManagerDataSource(
13 "jdbc:h2:mem:ex;DB_CLOSE_DELAY=-1", "sa", "");
14 ds.setDriverClassName("org.h2.Driver");
15 JdbcTemplate jdbc = new JdbcTemplate(ds);
16
17 jdbc.execute("CREATE TABLE kullanici (id INT PRIMARY KEY, eposta VARCHAR(80) UNIQUE)");
18 jdbc.update("INSERT INTO kullanici VALUES (1, 'ada@site.com')");
19
20 // --- Çift birincil anahtar -> DataIntegrityViolation/DuplicateKey ---
21 // Ham JDBC'de bu, sürücüye özel bir SQLException olurdu (kod/numara ezbere bilinir).
22 // Spring bunu ANLAMLI, taşınabilir bir istisnaya çevirir.
23 try {
24 jdbc.update("INSERT INTO kullanici VALUES (1, 'baska@site.com')"); // id=1 zaten var
25 } catch (DuplicateKeyException e) {
26 System.out.println("Yakalandı (DuplicateKeyException): birincil anahtar çakışması.");
27 } catch (DataAccessException e) {
28 System.out.println("Yakalandı (DataAccessException): " + e.getClass().getSimpleName());
29 }
30
31 // --- Sonuç bulunamadı -> EmptyResultDataAccessException ---
32 try {
33 jdbc.queryForObject("SELECT eposta FROM kullanici WHERE id = ?", String.class, 999);
34 } catch (EmptyResultDataAccessException e) {
35 System.out.println("Yakalandı (EmptyResultDataAccessException): kayıt yok.");
36 }
37
38 System.out.println("""
39
40 --- Exception translation neden değerli? ---
41 Ham JDBC'de hatalar 'checked' SQLException'dır ve veritabanına göre kod/mesaj DEĞİŞİR.
42 Spring bunları tek bir 'unchecked' DataAccessException hiyerarşisine çevirir:
43 DuplicateKeyException, DataIntegrityViolationException, EmptyResultDataAccessException...
44 Böylece iş kodun, hangi veritabanını kullandığından bağımsız ve temiz kalır.
45 (@Repository ile işaretli bean'lerde bu çeviri otomatik devreye girer.)""");
46 }
47}Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek3.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.