JDBC ve Veritabanı Bağlantısı
Hemen hemen her uygulama bir noktada veri saklamak ve sorgulamak zorundadır. Kullanıcı bilgileri, siparişler, ürünler, loglar... Bunların hepsi veritabanında yaşar. Java'nın veritabanıyla konuşma yolu JDBC (Java Database Connectivity) üzerinden geçer.
Bu derste JDBC'nin ne olduğunu, veritabanına nasıl bağlanacağını, veri ekleme-okuma-güncelleme-silme (CRUD) işlemlerini, SQL injection'dan korunmayı, transaction yönetimini ve connection pooling'i sıfırdan öğreneceğiz.
1. JDBC Nedir?
JDBC, Java'nın veritabanlarıyla iletişim kurmasını sağlayan standart bir API'dir. java.sql paketinde yaşar ve Java'nın kendisiyle birlikte gelir — ekstra bir şey kurman gerekmez.
JDBC'nin güzelliği veritabanı bağımsız olmasıdır. Aynı Java koduyla MySQL, PostgreSQL, Oracle, SQLite veya başka herhangi bir ilişkisel veritabanına bağlanabilirsin. Tek değişen şey JDBC driver ve connection string.
🎯 Analoji — Evrensel Kumanda:
>
JDBC'yi bir evrensel TV kumandası gibi düşün. Kumandanın "aç", "kapat", "ses yükselt" tuşları her TV'de aynı işi yapar. Ama her TV markası için farklı bir "kod" girersin — Samsung için bir kod, LG için başka bir kod. JDBC de böyle: API (kumanda) aynı, ama her veritabanı için farklı bir driver (kod) kullanırsın.
JDBC Mimarisi
Java Uygulaması
↓
JDBC API (java.sql)
↓
JDBC Driver (veritabanına özel)
↓
Veritabanı (MySQL, PostgreSQL, SQLite...)JDBC driver, veritabanı üreticisi tarafından sağlanır. Maven veya Gradle ile projeye eklenir:
<!-- MySQL Driver -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<!-- PostgreSQL Driver -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.1</version>
</dependency>
<!-- H2 (gömülü, test için ideal) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
</dependency>2. Veritabanına Bağlanma — Connection
Her şey bir Connection (bağlantı) ile başlar. DriverManager.getConnection() metodu ile veritabanına bağlanırsın.
Connection String Formatı
Her veritabanının kendine özel bir URL formatı vardır:
jdbc:mysql://localhost:3306/mydb ← MySQL
jdbc:postgresql://localhost:5432/mydb ← PostgreSQL
jdbc:h2:mem:testdb ← H2 (in-memory)
jdbc:sqlite:database.db ← SQLite
jdbc:oracle:thin:@localhost:1521:orcl ← OracleGenel format: jdbc:<veritabanı>://<host>:<port>/<database_adı>
İlk Bağlantı
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
class Main {
public static void main(String[] args) {
String url = "jdbc:h2:mem:testdb";
String user = "sa";
String password = "";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
System.out.println("Bağlantı başarılı!");
System.out.println("Veritabanı: " + conn.getMetaData().getDatabaseProductName());
System.out.println("Versiyon: " + conn.getMetaData().getDatabaseProductVersion());
} catch (SQLException e) {
System.err.println("Bağlantı hatası: " + e.getMessage());
}
}
}Burada try-with-resources kullandık. Connection bir kaynak (resource) olduğu için işimiz bittiğinde mutlaka kapatılmalı. try-with-resources bunu otomatik yapar — hata olsa bile close() çağrılır.
⚠️ Dikkat: Connection'ı kapatmayı unutmak Java'daki en yaygın veritabanı hatalarından biridir. Kapatılmayan connection'lar birikir ve bir süre sonra veritabanı yeni bağlantı kabul edemez hale gelir. Her zaman
try-with-resourceskullan.
3. Statement ile SQL Çalıştırma
Bağlantıyı kurduktan sonra SQL komutları çalıştırmak için Statement kullanırız. İki tür Statement var:
Statement— Basit, sabit SQL'ler içinPreparedStatement— Parametreli SQL'ler için (bunu kullan!)
Statement (Basit Kullanım)
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement()) {
// Tablo oluştur
stmt.execute("""
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(150) UNIQUE,
age INT
)
""");
// Veri ekle
int affected = stmt.executeUpdate(
"INSERT INTO users (name, email, age) VALUES ('Ali', 'ali@test.com', 25)"
);
System.out.println(affected + " satır eklendi");
// Veri oku
var rs = stmt.executeQuery("SELECT * FROM users");
while (rs.next()) {
System.out.println(rs.getInt("id") + " - " +
rs.getString("name") + " - " +
rs.getString("email"));
}
}Üç farklı execute metodu:
execute(sql)— DDL (CREATE, ALTER, DROP) için. boolean dönerexecuteUpdate(sql)— DML (INSERT, UPDATE, DELETE) için. Etkilenen satır sayısı dönerexecuteQuery(sql)— SELECT için. ResultSet döner
4. PreparedStatement — SQL Injection'dan Korunma
Statement basit iş görür ama ciddi bir güvenlik açığı barındırır: SQL Injection.
SQL Injection Nedir?
Kullanıcıdan alınan veriyi doğrudan SQL'e yapıştırırsan kötü niyetli kullanıcılar SQL komutları enjekte edebilir:
// ❌ TEHLİKELİ — Asla böyle yapma!
String username = userInput; // kullanıcı: "'; DROP TABLE users; --"
String sql = "SELECT * FROM users WHERE name = '" + username + "'";
stmt.executeQuery(sql);
// Oluşan SQL: SELECT * FROM users WHERE name = ''; DROP TABLE users; --'
// Tüm tablo silindi!Bu, web güvenliğindeki en yaygın saldırı vektörlerinden biridir. Çözüm? PreparedStatement.
PreparedStatement ile Güvenli Sorgulama
PreparedStatement, SQL'i ve parametreleri ayrı ayrı veritabanına gönderir. Veritabanı önce SQL'in yapısını derler, sonra parametreleri değer olarak yerleştirir. Parametre ne olursa olsun SQL yapısını bozamaz.
// ✅ GÜVENLİ — Her zaman böyle yap
String sql = "SELECT * FROM users WHERE name = ? AND age > ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, username); // 1. parametre (?)
pstmt.setInt(2, 18); // 2. parametre (?)
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("name") + " - " + rs.getInt("age"));
}
}
}? işaretleri placeholder (yer tutucu). setString(), setInt(), setDouble() gibi tip-güvenli metodlarla değer atanır. Parametre indeksleri 1'den başlar (0'dan değil!).
⚠️ Dikkat: Kullanıcıdan gelen hiçbir veriyi doğrudan SQL string'ine yapıştırma. Her zaman PreparedStatement kullan. Bu sadece güvenlik değil, aynı zamanda veritabanı sorgu planı cache'lemesi sayesinde performans da sağlar.
PreparedStatement'ın Diğer Avantajları
Güvenlik: SQL Injection imkansız
Performans: Veritabanı sorguyu önceden derler, tekrar kullanımda hızlı
Tip güvenliği:
setInt(),setString()ile doğru tipi garanti edersinOkunabilirlik: Parametreler net, SQL yapısı temiz
5. ResultSet ile Veri Okuma
executeQuery() bir ResultSet döner. ResultSet, sorgu sonucunu satır satır dolaşmamızı sağlayan bir imleç (cursor) gibi çalışır.
String sql = "SELECT id, name, email, age FROM users WHERE age >= ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, 20);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) { // bir sonraki satıra ilerle
int id = rs.getInt("id"); // kolon adıyla
String name = rs.getString("name");
String email = rs.getString(3); // kolon indeksiyle (1-based)
int age = rs.getInt("age");
System.out.printf("ID: %d, Ad: %s, E-posta: %s, Yaş: %d%n",
id, name, email, age);
}
}
}ResultSet ipuçları:
rs.next()bir sonraki satıra ilerler. Satır varsatrue, yoksafalsedönerKolon değerlerini isme veya indekse göre alabilirsin (isim tercih et — daha okunabilir)
İndeksler 1'den başlar
rs.wasNull()ile son okunan değerin NULL olup olmadığını kontrol edebilirsinPrimitive tipler (int, double) için NULL değer 0 döner — dikkat!
NULL Kontrolü
int age = rs.getInt("age");
if (rs.wasNull()) {
System.out.println("Yaş bilgisi yok");
} else {
System.out.println("Yaş: " + age);
}
// Veya wrapper tipleri kullan
Integer ageObj = rs.getObject("age", Integer.class); // null olabilir6. CRUD İşlemleri — Tam Örnek
Şimdi tüm CRUD (Create, Read, Update, Delete) işlemlerini birleştiren pratik bir örnek yapalım:
import java.sql.*;
class UserDao {
private final String url;
private final String user;
private final String password;
UserDao(String url, String user, String password) {
this.url = url;
this.user = user;
this.password = password;
}
// CREATE
int createUser(String name, String email, int age) throws SQLException {
String sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql,
Statement.RETURN_GENERATED_KEYS)) {
pstmt.setString(1, name);
pstmt.setString(2, email);
pstmt.setInt(3, age);
pstmt.executeUpdate();
try (ResultSet keys = pstmt.getGeneratedKeys()) {
if (keys.next()) {
return keys.getInt(1); // oluşturulan ID
}
}
return -1;
}
}
// READ
void findByAge(int minAge) throws SQLException {
String sql = "SELECT * FROM users WHERE age >= ? ORDER BY name";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, minAge);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
System.out.printf("[%d] %s (%s) - %d yaşında%n",
rs.getInt("id"), rs.getString("name"),
rs.getString("email"), rs.getInt("age"));
}
}
}
}
// UPDATE
int updateEmail(int userId, String newEmail) throws SQLException {
String sql = "UPDATE users SET email = ? WHERE id = ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, newEmail);
pstmt.setInt(2, userId);
return pstmt.executeUpdate(); // etkilenen satır sayısı
}
}
// DELETE
int deleteUser(int userId) throws SQLException {
String sql = "DELETE FROM users WHERE id = ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, userId);
return pstmt.executeUpdate();
}
}
}Statement.RETURN_GENERATED_KEYS parametresi, INSERT sonrası otomatik oluşturulan ID'yi almamızı sağlar. Veritabanı yeni kaydın ID'sini döner ve biz getGeneratedKeys() ile okuruz.
7. Transaction Yönetimi
Bazı işlemler birden fazla SQL komutundan oluşur ve ya hepsi başarılı olmalı ya da hiçbiri. Örneğin banka havalesi: bir hesaptan para düş, diğerine ekle. İkisinden biri başarısız olursa para havada kalır!
Auto-Commit ve Manuel Transaction
JDBC varsayılan olarak auto-commit modundadır — her SQL komutu otomatik olarak commit edilir. Transaction kullanmak için auto-commit'i kapatman gerekir.
Connection conn = DriverManager.getConnection(url, user, password);
conn.setAutoCommit(false); // Transaction başlat
try {
// Gönderenin bakiyesinden düş
PreparedStatement withdraw = conn.prepareStatement(
"UPDATE accounts SET balance = balance - ? WHERE id = ?");
withdraw.setDouble(1, 500.0);
withdraw.setInt(2, fromAccountId);
int rows1 = withdraw.executeUpdate();
// Alıcının bakiyesine ekle
PreparedStatement deposit = conn.prepareStatement(
"UPDATE accounts SET balance = balance + ? WHERE id = ?");
deposit.setDouble(1, 500.0);
deposit.setInt(2, toAccountId);
int rows2 = deposit.executeUpdate();
if (rows1 == 0 || rows2 == 0) {
throw new SQLException("Hesap bulunamadı!");
}
conn.commit(); // Her şey başarılıysa onayla
System.out.println("Havale başarılı!");
} catch (SQLException e) {
conn.rollback(); // Hata varsa tüm değişiklikleri geri al
System.err.println("Havale başarısız, geri alındı: " + e.getMessage());
} finally {
conn.setAutoCommit(true); // Eski duruma dön
conn.close();
}Transaction kuralları:
setAutoCommit(false)— Transaction başlatırcommit()— Tüm değişiklikleri kalıcı yaparrollback()— Tüm değişiklikleri geri alırHata durumunda her zaman rollback yap
Savepoint
Uzun transaction'larda belirli bir noktaya kadar geri almak isteyebilirsin:
conn.setAutoCommit(false);
// İlk işlem
stmt.executeUpdate("INSERT INTO log (message) VALUES ('İşlem başladı')");
Savepoint sp = conn.setSavepoint("after_log");
try {
// Riskli işlem
stmt.executeUpdate("UPDATE inventory SET stock = stock - 1 WHERE id = 42");
} catch (SQLException e) {
conn.rollback(sp); // Sadece savepoint'ten sonrasını geri al
// log kaydı hâlâ duruyor
}
conn.commit();8. Batch İşlemler
Çok sayıda INSERT veya UPDATE yapmak gerektiğinde tek tek göndermek yerine batch (toplu) gönderim kullanmak performansı dramatik şekilde artırır.
String sql = "INSERT INTO products (name, price) VALUES (?, ?)";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
conn.setAutoCommit(false);
String[][] products = {
{"Laptop", "15999.99"}, {"Telefon", "8999.99"},
{"Tablet", "5499.99"}, {"Kulaklık", "1299.99"},
{"Mouse", "399.99"}
};
for (String[] product : products) {
pstmt.setString(1, product[0]);
pstmt.setDouble(2, Double.parseDouble(product[1]));
pstmt.addBatch(); // Kuyruğa ekle
}
int[] results = pstmt.executeBatch(); // Toplu gönder
conn.commit();
System.out.println(results.length + " ürün eklendi");
}1000 kayıt eklerken tek tek gönderim 10 saniye sürerken, batch ile 0.5 saniyede tamamlanabilir. Fark bu kadar büyük.
9. Connection Pooling — HikariCP
Her CRUD işlemi için yeni bir Connection açmak ve kapatmak çok pahalı bir operasyondur. Veritabanına TCP bağlantısı kurmak, kimlik doğrulaması yapmak, kaynakları ayırmak... Bunların hepsi zaman alır.
Connection pooling, önceden açılmış bağlantıları bir havuzda tutar. İhtiyaç olduğunda havuzdan alır, işin bitince havuza geri koyarsın. Bağlantı açma/kapama maliyeti ortadan kalkar.
HikariCP — En Hızlı Connection Pool
HikariCP, Java ekosisteminin en hızlı ve en yaygın connection pool kütüphanesidir. Spring Boot bile varsayılan olarak HikariCP kullanır.
Maven'a ekle:
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
</dependency>Kullanımı:
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
class DatabasePool {
private static final HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("secret");
// Pool ayarları
config.setMaximumPoolSize(10); // Max 10 bağlantı
config.setMinimumIdle(2); // Min 2 boşta bağlantı
config.setConnectionTimeout(30000); // 30 sn bağlantı timeout
config.setIdleTimeout(600000); // 10 dk boşta kalma timeout
config.setMaxLifetime(1800000); // 30 dk max yaşam süresi
dataSource = new HikariDataSource(config);
}
static Connection getConnection() throws SQLException {
return dataSource.getConnection(); // Havuzdan al
}
static void close() {
dataSource.close(); // Uygulama kapanırken
}
}Kullanımda hiçbir fark yok — sadece DriverManager yerine pool'dan alırsın:
// Eskisi: DriverManager.getConnection(url, user, password)
// Yenisi:
try (Connection conn = DatabasePool.getConnection();
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM users")) {
// ... normal JDBC kullanımı
// try-with-resources bitince bağlantı havuza geri döner (kapanmaz!)
}💡 İpucu:
try-with-resourcesile aldığın pooled connection kapanmaz, havuza geri döner. Bu yüzden kodu hiç değiştirmeden DriverManager'dan HikariCP'ye geçebilirsin.
Pool Boyutu Ne Olmalı?
Yaygın bir hata: "Daha fazla connection = daha hızlı" düşüncesi. Aslında tam tersi olabilir! HikariCP'nin yaratıcısı şu formülü önerir:
Pool size = (core_count * 2) + effective_spindle_countÇoğu uygulama için 5-10 connection yeterlidir. 100+ connection kurmak veritabanını boğar.
10. try-with-resources ve Kaynak Yönetimi
JDBC'de en kritik konu kaynak yönetimidir. Connection, Statement ve ResultSet hepsi kapatılması gereken kaynaklardır. Kapatılmazlarsa bellek sızıntısı (memory leak) ve connection sızıntısı oluşur.
Doğru Yol — try-with-resources
// ✅ Tüm kaynaklar otomatik kapatılır
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM users");
ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("name"));
}
} catch (SQLException e) {
e.printStackTrace();
}
// Burada conn, pstmt ve rs hepsi kapatılmış durumdaYanlış Yol — Manuel Kapatma
// ❌ Hata olursa kaynaklar açık kalabilir
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection(url, user, password);
pstmt = conn.prepareStatement("SELECT * FROM users");
rs = pstmt.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("name"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// Her birini ayrı try-catch'te kapatmalısın... Karmaşık ve hata yapmaya açık
if (rs != null) try { rs.close(); } catch (SQLException e) { }
if (pstmt != null) try { pstmt.close(); } catch (SQLException e) { }
if (conn != null) try { conn.close(); } catch (SQLException e) { }
}Gördüğün gibi, try-with-resources hem daha kısa hem daha güvenli. Modern Java'da JDBC kullanırken her zaman try-with-resources tercih et.
11. DAO Pattern — Veritabanı Kodunu Organize Etme
Gerçek projelerde veritabanı kodunu doğrudan iş mantığına (business logic) karıştırmazsın. DAO (Data Access Object) pattern'i ile veritabanı operasyonlarını ayrı bir katmanda toplarız.
// Veri modeli
record User(int id, String name, String email, int age) {}
// DAO arayüzü
interface UserDao {
User findById(int id);
List<User> findAll();
int save(User user);
boolean update(User user);
boolean delete(int id);
}
// JDBC implementasyonu
class JdbcUserDao implements UserDao {
private final DataSource dataSource;
JdbcUserDao(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public User findById(int id) {
String sql = "SELECT * FROM users WHERE id = ?";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return new User(
rs.getInt("id"),
rs.getString("name"),
rs.getString("email"),
rs.getInt("age")
);
}
}
} catch (SQLException e) {
throw new RuntimeException("Kullanıcı bulunamadı: " + id, e);
}
return null;
}
@Override
public List<User> findAll() {
String sql = "SELECT * FROM users ORDER BY id";
List<User> users = new ArrayList<>();
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
users.add(new User(
rs.getInt("id"),
rs.getString("name"),
rs.getString("email"),
rs.getInt("age")
));
}
} catch (SQLException e) {
throw new RuntimeException("Kullanıcılar listelenemedi", e);
}
return users;
}
// save, update, delete benzer şekilde...
}DAO pattern'in avantajları:
Veritabanı kodu tek bir yerde toplanır
İş mantığı veritabanından bağımsız olur
Test yazmak kolaylaşır (mock DAO kullanabilirsin)
Veritabanı değiştirmek istersen sadece DAO implementasyonunu değiştirirsin
12. Yaygın Hatalar ve Best Practice
1. Connection Sızıntısı
Her zaman try-with-resources kullan. Kapatılmayan connection'lar sunucuyu çökertir.
2. SQL Injection
Kullanıcı girdisini asla string birleştirme ile SQL'e koyma. PreparedStatement kullan.
3. SELECT * Kullanma
Sadece ihtiyacın olan kolonları seç. SELECT name, email FROM users — gereksiz veri transferini önler.
4. N+1 Sorgu Problemi
Bir listede her eleman için ayrı sorgu atmak yerine JOIN veya IN clause kullan:
// ❌ N+1 sorgu — yavaş
for (int orderId : orderIds) {
pstmt.setInt(1, orderId);
rs = pstmt.executeQuery(); // Her sipariş için ayrı sorgu
}
// ✅ Tek sorgu — hızlı
String placeholders = String.join(",", Collections.nCopies(orderIds.size(), "?"));
String sql = "SELECT * FROM orders WHERE id IN (" + placeholders + ")";5. Exception Yutma
catch (SQLException e) {} gibi boş catch blokları kullanma. En azından logla veya RuntimeException'a çevir.
13. JDBC'den Sonrası — ORM ve JPA
JDBC güçlü ama çok fazla tekrar eden kod (boilerplate) yazarsın. Her sorgu için Connection aç, PreparedStatement hazırla, ResultSet oku, objeye dönüştür... Bunun çözümü ORM (Object-Relational Mapping) araçlarıdır.
JPA (Java Persistence API) ve Hibernate gibi ORM framework'leri, Java nesnelerini doğrudan veritabanı tablolarına eşler. SQL yazmadan CRUD yapabilirsin:
// JPA ile — JDBC boilerplate yok
@Entity
class User {
@Id @GeneratedValue
private Long id;
private String name;
private String email;
}
// Kaydet
entityManager.persist(new User("Ali", "ali@test.com"));
// Bul
User user = entityManager.find(User.class, 1L);Ama JPA'yı anlamak için önce JDBC'yi bilmelisin. Çünkü JPA arka planda JDBC kullanır. JDBC'yi bilmeden JPA sorunlarını çözmek çok zor.
Özet
JDBC, Java'nın veritabanlarıyla iletişim kurmasını sağlayan standart API'dir.
java.sqlpaketinde yaşar ve veritabanı bağımsızdır — sadece driver ve connection string değişir.PreparedStatement her zaman tercih et. SQL Injection'ı önler, performans sağlar ve tip-güvenlidir. Kullanıcı girdisini asla string birleştirme ile SQL'e koyma.
Transaction yönetiminde
setAutoCommit(false)ile başla, başarılıysacommit(), hata varsarollback()yap. Banka havalesi gibi kritik işlemlerde olmazsa olmaz.Connection pooling (HikariCP) üretim ortamında zorunludur. Her işlem için yeni bağlantı açmak pahalıdır; pool bağlantıları yeniden kullanır.
try-with-resources ile Connection, Statement ve ResultSet'i her zaman otomatik kapat. Kaynak sızıntısı en yaygın JDBC hatasıdır.
DAO pattern ile veritabanı kodunu iş mantığından ayır. JDBC'yi öğrendikten sonra JPA/Hibernate'e geçiş çok daha kolay ve anlamlı olur.
AI Asistan
Sorularını yanıtlamaya hazır