Hibernate ve JPA Anotasyonları: Kapsamlı Rehber (Temel ve İleri Seviye)
Java dünyasında kurumsal uygulama geliştirirken veritabanı işlemleriyle uğraşmak kaçınılmazdır. Hibernate, Java uygulamalarında veritabanı işlemlerini kolaylaştıran, bizi SQL karmaşasından kurtaran güçlü bir ORM (Object-Relational Mapping) aracıdır.
Bu rehberde sadece temel anotasyonları değil, performans yönetimi ve veri bütünlüğü için kritik olan ileri seviye konfigürasyonları da ele alacağız.
1. Temel Yapısal Anotasyonlar
@Entity
Bir sınıfın veritabanında kalıcı bir tabloya karşılık geldiğini belirtir. Hibernate'in bu sınıfı yönetebilmesi için zorunludur.
@Table
Varsayılan olarak sınıf adı tablo adı olur. Ancak @Table ile bunu özelleştirebilir, şema verebilir veya indeksler tanımlayabilirsiniz.
@Entity
@Table(
name = "users",
schema = "management",
uniqueConstraints = @UniqueConstraint(columnNames = {"email"})
)
public class User { ... }
2. Birincil Anahtar (Primary Key) Yönetimi
@Id ve @GeneratedValue
Her entity'nin bir kimliği olmalıdır. @GeneratedValue bu kimliğin nasıl üretileceğini belirler.
- IDENTITY: (MySQL, MSSQL) Veritabanındaki "Auto Increment" özelliğini kullanır. En sık kullanılan yöntemdir.
- SEQUENCE: (Oracle, PostgreSQL) Veritabanı dizilerini kullanır. Performans açısından batch insert işlemlerinde daha verimlidir.
- UUID: Dağıtık sistemlerde (Microservices) sıkça kullanılır. Benzersiz string id üretir.
3. İlişki Yönetimi ve Performans (Fetch Types)
Hibernate'in en güçlü ama en çok hata yapılan kısmı burasıdır. İlişkileri doğru yönetmek uygulamanızın performansını doğrudan etkiler.
Fetch Type: EAGER vs LAZY
@OneToMany ve @ManyToMany ilişkiler LAZY (Tembel), @ManyToOne ve @OneToOne ilişkiler EAGER (İstekli) yüklenir.
- FetchType.LAZY: Ana veriyi çektiğinizde ilişkili veriler gelmez. Ne zaman ki `getOrders()` çağırırsınız, o zaman ikinci bir sorgu atılır. Performans dostudur.
- FetchType.EAGER: Ana veriyi çekerken ilişkili verileri de "Join" atarak getirir. Gereksiz veri çekimine sebep olabilir.
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders;
Cascade Types (İşlem Yayılımı)
Bir entity üzerinde yapılan işlemin (save, delete), ilişkili olduğu çocuk entity'lere de uygulanmasını sağlar.
- CascadeType.PERSIST: Parent kaydedilince child da kaydedilir.
- CascadeType.REMOVE: Parent silinince child da silinir.
- CascadeType.ALL: Tüm işlemleri kapsar.
4. Kalıtım (Inheritance) Stratejileri
Nesne Yönelimli Programlamadaki (OOP) kalıtımın veritabanına nasıl yansıtılacağını @Inheritance belirler.
1. Single Table (Varsayılan)
Tüm sınıf hiyerarşisini tek bir tabloda tutar. Ayrım yapmak için "DTYPE" gibi bir kolon kullanır. Performansı en yüksek olandır.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "payment_type")
public class Payment { ... }
2. Joined
Her sınıf için ayrı tablo oluşturur ve bunları Primary Key ile joinler. Veri normalizasyonu için iyidir ancak performans maliyeti vardır.
3. MappedSuperclass (En Sık Kullanılan Pattern)
Veritabanında tablo oluşturmaz, ancak kendisini miras alan sınıflara kendi alanlarını (id, createdDate vb.) miras bırakır. Kod tekrarını önlemek için harikadır.
@MappedSuperclass
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "created_at")
private LocalDateTime createdAt;
}
@Entity
public class Product extends BaseEntity {
// id ve createdAt alanlarına otomatik sahip olur
private String name;
}
5. Varlık Yaşam Döngüsü (Lifecycle Callbacks)
Veri veritabanına gitmeden hemen önce veya geldikten hemen sonra araya girip işlem yapmamızı sağlar. Genellikle "Audit" (Kayıt tarihi, güncelleyen kullanıcı) işlemleri için kullanılır.
- @PrePersist: Kayıt (Insert) işleminden hemen önce çalışır.
- @PreUpdate: Güncelleme (Update) işleminden hemen önce çalışır.
@PrePersist
public void onCreate() {
this.createdAt = LocalDateTime.now();
this.isActive = true;
}
@PreUpdate
public void onUpdate() {
this.updatedAt = LocalDateTime.now();
}
6. Gelişmiş Özellikler
@Version (Optimistic Locking)
Aynı anda iki kişi aynı kaydı güncellemeye çalışırsa veri bütünlüğünü korur. Veritabanında bir versiyon numarası tutar. Eğer kaydederken versiyon değişmişse OptimisticLockException fırlatır.
@Transient
Sınıf içinde bir alan olsun ama veritabanına kaydedilmesin istiyorsanız kullanılır. Örneğin, veritabanında 'doğum tarihi' var ama siz entity içinde 'yaş' bilgisini hesaplayıp göstermek istiyorsunuz.
@Lob (Large Object)
Çok uzun metinler veya dosya verileri (byte array) saklamak için kullanılır.
@Data kullanırken dikkatli olun. toString() ve hashCode() metodları, ilişkili nesneler (OneToMany) arasında sonsuz döngüye girip "StackOverflowError" hatası verebilir. Bunun yerine @Getter, @Setter kullanmak veya @ToString.Exclude ile ilişkili alanları hariç tutmak daha güvenlidir.
Hibernate anotasyonları, Java ile veritabanı programlamanın temel taşıdır. Bu özellikleri doğru kullanmak, projenizin ölçeklenebilirliği ve bakım maliyeti üzerinde büyük bir etkiye sahiptir.
Kodla kalın!