JPA Annotasyonları ve Hibernate Annotasyonları: Derinlemesine Bir Karşılaştırma ve Uygulama Rehberi
Giriş
Günümüz kurumsal Java uygulamalarının bel kemiğini oluşturan veri kalıcılığı (persistence) katmanı, nesne yönelimli programlama dünyası ile ilişkisel veritabanları arasındaki “empedans uyumsuzluğunu” çözmek için Nesne-İlişkisel Eşleştirme (Object-Relational Mapping – ORM) araçlarına ihtiyaç duyar. ORM, geliştiricilerin veritabanı işlemlerini doğrudan SQL sorguları yazmak yerine Java nesneleri üzerinde gerçekleştirmesine olanak tanıyarak geliştirme sürecini hızlandırır, kod okunabilirliğini artırır ve bakım maliyetlerini düşürür. Bu alandaki en önemli standartlardan biri Java Persistence API (JPA), en popüler implementasyonlardan biri ise Hibernate’dir. Bu makale, JPA ve Hibernate arasındaki ilişkiyi, her iki tarafın sunduğu annotasyonları ayrıntılı bir şekilde inceleyecek, aralarındaki farkları ve hangi durumlarda hangi annotasyonların tercih edilmesi gerektiğini örneklerle açıklayacaktır.
JPA (Java Persistence API) Nedir?
JPA, Java EE (şimdiki Jakarta EE) ve Java SE uygulamalarında veri kalıcılığını yönetmek için bir standart sağlayan bir spesifikasyondur. Temel amacı, Java nesnelerinin ilişkisel bir veritabanına nasıl eşleneceğini, veritabanı işlemlerinin nasıl gerçekleştirileceğini ve sorguların nasıl yazılacağını tanımlamaktır. JPA bir API olmasına rağmen, kendisi bir ORM aracı değildir; bunun yerine, ORM araçlarının bu spesifikasyonu uygulamasını bekler. Bu sayede, geliştiriciler farklı JPA sağlayıcıları (Hibernate, EclipseLink, OpenJPA gibi) arasında kolayca geçiş yapabilir, uygulamalarının taşınabilirliğini artırabilirler.
JPA’nın kalbinde, veritabanı tablolarına eşlenen Java sınıfları olan “entity”ler bulunur. Entity’ler, @Entity annotasyonu ile işaretlenir ve genellikle birincil anahtarları (@Id) ve diğer sütunları (@Column) içerir. JPA, bir “Persistence Context” (Kalıcılık Bağlamı) aracılığıyla entity’lerin yaşam döngüsünü yönetir. EntityManager arayüzü, entity’leri kaydetme, güncelleme, silme ve sorgulama gibi işlemleri gerçekleştirmek için kullanılır.
JPA Annotasyonları
JPA, javax.persistence (veya Jakarta EE 9+ için jakarta.persistence) paketinde yer alan bir dizi annotasyon sunar. Bu annotasyonlar, Java sınıflarını ve alanlarını veritabanı tablolarına ve sütunlarına eşlemek için kullanılır.
Temel Entity Annotasyonları
Bu annotasyonlar, bir Java sınıfının nasıl bir veritabanı tablosuna eşleneceğini ve temel alanların nasıl davranacağını tanımlar.
* @Entity: Bir sınıfın bir JPA entity’si olduğunu ve kalıcı hale getirileceğini belirtir. Bu annotasyon, genellikle sınıfın veritabanındaki bir tabloya eşleneceği anlamına gelir.
import jakarta.persistence.Entity;
@Entity
public class Kullanici {
// ...
}
* @Table: Entity’nin eşleneceği veritabanı tablosunun adını ve diğer özelliklerini (schema, catalog, uniqueConstraints, indexes) belirtir. Eğer belirtilmezse, sınıf adını kullanır.
import jakarta.persistence.Table;
@Entity
@Table(name = "APP_KULLANICILAR", uniqueConstraints = @UniqueConstraint(columnNames = {"email"}))
public class Kullanici {
// ...
}
* @Id: Bir entity’nin birincil anahtarını belirten alanı işaretler. Her entity’nin bir @Id alanı olmalıdır.
import jakarta.persistence.Id;
@Id
private Long id;
* @GeneratedValue: Birincil anahtar değerinin nasıl oluşturulacağını belirtir. Stratejiler şunlardır:
* AUTO: Sağlayıcının uygun bir strateji seçmesini sağlar (varsayılan).
* IDENTITY: Veritabanının kimlik sütununu kullanır (örn: MySQL’de AUTO_INCREMENT).
* SEQUENCE: Veritabanı dizilerini kullanır.
* TABLE: Ayrı bir veritabanı tablosu kullanarak kimlik değerleri oluşturur.
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
* @Column: Bir alanın veritabanı sütunuyla nasıl eşleştiğini belirtir. name, nullable, unique, length, precision, scale gibi özellikler ayarlanabilir.
import jakarta.persistence.Column;
@Column(name = "kullanici_adi", nullable = false, unique = true, length = 50)
private String username;
* @Transient: Bir alanın kalıcı hale getirilmemesi gerektiğini belirtir. Bu alan veritabanında bir sütun olarak görünmez.
import jakarta.persistence.Transient;
@Transient
private String geciciVeri;
* @Temporal: java.util.Date veya java.util.Calendar türündeki alanların veritabanında nasıl saklanacağını belirtir (DATE, TIME, TIMESTAMP).
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
@Temporal(TemporalType.TIMESTAMP)
private Date olusturmaTarihi;
* @Enumerated: Enum türündeki alanların veritabanında nasıl saklanacağını belirtir (ORDINAL – sayısal indeks, STRING – enum adı). STRING genellikle daha güvenli ve okunabilirdir.
import jakarta.persistence.Enumerated;
import jakarta.persistence.EnumType;
public enum Rol { ADMIN, KULLANICI }
@Enumerated(EnumType.STRING)
private Rol rol;
İlişki (Relationship) Annotasyonları
JPA, entity’ler arasındaki ilişkileri modellemek için bir dizi annotasyon sunar:
* @OneToOne: İki entity arasında birebir ilişkiyi belirtir.
* @OneToMany: Bir entity’nin birden çok başka entity ile ilişkili olduğunu belirtir.
* @ManyToOne: Birden çok entity’nin tek bir başka entity ile ilişkili olduğunu belirtir.
* @ManyToMany: İki entity arasında çoktan çoğa ilişkiyi belirtir.
Bu ilişki annotasyonları genellikle fetch (yükleme stratejisi: LAZY veya EAGER) ve cascade (işlem yayılımı: ALL, PERSIST, MERGE, REMOVE, REFRESH, DETACH) gibi özniteliklere sahiptir.
* @JoinColumn: İlişkinin sahibi tarafında yabancı anahtar sütununu belirtir.
import jakarta.persistence.ManyToOne;
import jakarta.persistence.JoinColumn;
@ManyToOne
@JoinColumn(name = "kategori_id") // Urun tablosundaki yabancı anahtar sütunu
private Kategori kategori;
* @JoinTable: Çoktan çoğa ilişkilerde veya özel bir ara tabloya ihtiyaç duyulduğunda, bu ara tablonun adını ve birleştirme sütunlarını belirtir.
import jakarta.persistence.ManyToMany;
import jakarta.persistence.JoinTable;
import jakarta.persistence.JoinColumn;
@ManyToMany
@JoinTable(
name = "KULLANICI_GRUP",
joinColumns = @JoinColumn(name = "kullanici_id"),
inverseJoinColumns = @JoinColumn(name = "grup_id")
)
private Set gruplar = new HashSet<>();
* mappedBy: İlişkinin sahibi olmayan tarafında kullanılır ve ilişkinin hangi alandan yönetildiğini belirtir. Bu, çift yönlü ilişkilerde döngüsel bağımlılıkları önler.
import jakarta.persistence.OneToMany;
@OneToMany(mappedBy = "kategori", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set urunler = new HashSet<>();
Diğer Faydalı JPA Annotasyonları
* @Embedded, @Embeddable: Bir nesnenin, başka bir entity’nin bir bileşeni olarak depolanmasını sağlar. @Embeddable ile işaretlenmiş bir sınıf, @Embedded ile bir entity’nin içine gömülebilir.
import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded;
@Embeddable
public class Adres {
private String sokak;
private String sehir;
}
@Entity
public class Musteri {
@Embedded
private Adres adres;
}
* @Version: İyimser kilitlenme (optimistic locking) için kullanılır. Entity güncellendiğinde değeri otomatik olarak artırılan bir versiyon sütununu işaretler. Bu, eşzamanlı güncellemelerde veri tutarsızlığını önler.
import jakarta.persistence.Version;
@Version
private int version;
* @NamedQueries, @NamedQuery: Önceden tanımlanmış, parametreli sorguları entity üzerinde tanımlamak için kullanılır. Bu, sorguların merkezi bir yerde yönetilmesini ve yeniden kullanılabilirliğini artırır.
import jakarta.persistence.NamedQuery;
import jakarta.persistence.NamedQueries;
@Entity
@NamedQueries({
@NamedQuery(name = "Kullanici.findAll", query = "SELECT k FROM Kullanici k"),
@NamedQuery(name = "Kullanici.findByUsername", query = "SELECT k FROM Kullanici k WHERE k.username = :username")
})
public class Kullanici {
// ...
}
* @PrePersist, @PostPersist, @PreUpdate, @PostUpdate, @PreRemove, @PostRemove, @PostLoad: Entity yaşam döngüsü olaylarına tepki vermek için kullanılan geri çağırma (callback) annotasyonlarıdır. Örneğin, bir entity kaydedilmeden önce (@PrePersist) veya yüklendikten sonra (@PostLoad) belirli bir metodu çalıştırmak için kullanılırlar.
import jakarta.persistence.PrePersist;
import java.util.Date;
@Entity
public class Urun {
// ...
private Date olusturmaTarihi;
@PrePersist
protected void onCreate() {
olusturmaTarihi = new Date();
}
}
Hibernate Nedir?
Hibernate, Java için önde gelen açık kaynaklı bir ORM aracıdır ve aynı zamanda JPA spesifikasyonunun en yaygın kullanılan implementasyonudur. JPA’dan önce var olan Hibernate, JPA’nın tasarımını önemli ölçüde etkilemiştir. Hibernate, JPA’nın sunduğu temel işlevselliği sağlamanın yanı sıra, daha gelişmiş özellikler ve optimizasyonlar sunarak standart JPA’nın ötesine geçer.
Hibernate’in temel özellikleri arasında güçlü bir önbellekleme mekanizması (birinci ve ikinci seviye önbellek), zengin bir sorgu dili olan HQL (Hibernate Query Language), dinamik sorgular için Criteria API ve birçok performans optimizasyonu bulunur. Bir JPA sağlayıcısı olarak Hibernate, EntityManager arayüzünü ve JPA annotasyonlarını kullanarak entity’lerinizi yönetmenize olanak tanır. Ancak, Hibernate’in kendi özel annotasyon setini kullanarak JPA’nın kapsamadığı veya daha ince ayar gerektiren durumlar için ek yetenekler sunar.
Hibernate Annotasyonları
Hibernate, org.hibernate.annotations paketinde yer alan kendi özel annotasyon setini sunar. Bu annotasyonlar, standart JPA annotasyonlarının sağlamadığı veya daha derinlemesine kontrol gerektiren senaryolarda kullanılır. Hibernate annotasyonları, Hibernate’in gelişmiş özelliklerini doğrudan entity sınıflarınıza entegre etmenizi sağlar. Ancak, bu annotasyonları kullanmak uygulamanızı Hibernate’e bağımlı hale getirir ve taşınabilirliğini azaltır.
JPA Annotasyonlarına Ek Olarak Gelenler
* @Immutable: Bir entity’nin oluşturulduktan sonra değiştirilemez olduğunu belirtir. Bu, Hibernate’in önbellekleme ve oturum yönetimi optimizasyonlarını artırabilir.
import org.hibernate.annotations.Immutable;
@Entity
@Immutable
public class LogKaydi {
// ...
}
* @Cache, @CacheConcurrencyStrategy: İkinci seviye önbellekleme stratejilerini (örn: READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL) ve önbellek bölgesini yapılandırmak için kullanılır. JPA’nın doğrudan bir önbellek annotasyonu yoktur, ancak sağlayıcıya özel yapılandırma seçenekleri sunar.
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Urun {
// ...
}
* @BatchSize: İlişkili koleksiyonların veya entity’lerin toplu yüklenmesi (batch loading) için kullanılır. Bu, N+1 sorgu sorununu hafifletmeye yardımcı olabilir.
import org.hibernate.annotations.BatchSize;
@OneToMany(mappedBy = "kategori")
@BatchSize(size = 10) // 10 ürünü aynı anda yükle
private Set urunler = new HashSet<>();
* @Formula: Veritabanı tarafında hesaplanan bir değeri bir entity özelliğine eşlemek için kullanılır. SQL ifadeleriyle doğrudan çalışır.
import org.hibernate.annotations.Formula;
@Entity
public class Siparis {
// ...
private double miktar;
private double birimFiyat;
@Formula("miktar * birim_fiyat")
private double toplamTutar;
}
* @Type: Alanlar için özel Hibernate türlerini (custom types) belirtmek için kullanılır. Bu, JPA’nın desteklemediği karmaşık veri tiplerini eşlemek için faydalıdır.
import org.hibernate.annotations.Type;
@Type(JsonType.class) // Varsayımsal bir JSON türü
private MyCustomJsonType jsonVerisi;
* @Where: Bir koleksiyon veya entity sorgulanırken uygulanacak ek bir SQL WHERE koşulu belirtir. Genellikle “soft delete” (mantıksal silme) gibi durumlar için kullanılır.
import org.hibernate.annotations.Where;
@Entity
@Where(clause = "aktif = true")
public class Kullanici {
private boolean aktif;
// ...
}
* @FilterDef, @Filter: Dinamik filtreleme mekanizması sunar. Bir entity üzerinde tanımlanmış filtreleri çalışma zamanında etkinleştirip devre dışı bırakmaya olanak tanır.
import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.ParamDef;
@Entity
@FilterDef(name = "byBolge", parameters = @ParamDef(name = "bolgeId", type = Integer.class))
@Filter(name = "byBolge", condition = "bolge_id = :bolgeId")
public class Magaza {
private Integer bolgeId;
// ...
}
* @Fetch: İlişki yükleme stratejisi için JPA’nın fetch özniteliğinden daha fazla kontrol sağlar. FetchMode.JOIN (sol dış birleştirme), FetchMode.SELECT (ayrı bir sorgu) veya FetchMode.SUBSELECT (alt sorgu) gibi seçenekler sunar.
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
@OneToMany(mappedBy = "kategori")
@Fetch(FetchMode.SUBSELECT)
private Set urunler = new HashSet<>();
* @NaturalId: İş modelinde doğal birincil anahtar olarak kullanılan bir veya daha fazla alanı işaretler. Bu alanlar benzersizdir ve entity’yi tanımlamak için kullanılabilir, ancak veritabanı tarafından üretilen sentetik @Id ile farklıdır.
import org.hibernate.annotations.NaturalId;
@Entity
public class Ulke {
@Id
private Long id;
@NaturalId
@Column(unique = true)
private String kod; // Örneğin "TR", "US"
// ...
}
* @OptimisticLocking: JPA’nın @Version annotasyonundan daha fazla iyimser kilitlenme stratejisi sunar (VERSION, ALL, DIRTY, NONE).
* @LazyCollection, @LazyToOne: Koleksiyonların veya birebir ilişkilerin tembel yükleme davranışını daha ince ayarlamak için kullanılır. EXTRA gibi seçenekler sunarak performans optimizasyonlarına olanak tanır.
* @Index, @UniqueConstraint: JPA’nın @Table annotasyonu içinde uniqueConstraints ve indexes tanımlanabilse de, Hibernate kendi @Index ve @UniqueConstraint annotasyonlarını sunar. Bunlar genellikle daha esnek veya belirli durumlarda tercih edilebilir.
JPA ve Hibernate Annotasyonları Arasındaki İlişki ve Farklar
JPA ve Hibernate annotasyonları arasındaki ilişkiyi ve farkları anlamak, doğru aracı doğru senaryoda kullanmak için kritik öneme sahiptir.
İlişki
* JPA Bir Standart, Hibernate Bir Implementasyondur: En temel ilişki budur. Hibernate, JPA spesifikasyonunu uygulayan bir sağlayıcıdır. Bu nedenle, Hibernate’i kullanırken tüm standart JPA annotasyonlarını kullanabilirsiniz.
* JPA Annotasyonları Taşınabilirlik Sağlar: Yalnızca JPA annotasyonlarını kullanarak yazdığınız bir uygulama, Hibernate yerine EclipseLink veya OpenJPA gibi başka bir JPA sağlayıcısıyla çalışabilir. Bu, uygulamanızın gelecekteki değişikliklere karşı daha dirençli olmasını sağlar.
* Hibernate Annotasyonları JPA’yı Genişletir: Hibernate annotasyonları, JPA’nın sağlamadığı veya daha gelişmiş kontrol gerektiren özellikler için ek yetenekler sunar. Bunlar, Hibernate’in kendi iç mimarisine özgü optimizasyonlar ve özelliklerdir.
Farklar
* Standardizasyon vs. Satıcıya Özgülük: JPA annotasyonları standarttır ve tüm JPA sağlayıcıları tarafından desteklenmelidir. Hibernate annotasyonları ise Hibernate’e özgüdür ve başka bir JPA sağlayıcısında çalışmaz.
* Taşınabilirlik: JPA annotasyonları ile yazılan kod taşınabilirdir. Hibernate annotasyonları ile yazılan kod, Hibernate’e bağımlıdır.
* Kapsam: JPA, nesne-ilişkisel eşleştirmenin temelini ve en yaygın senaryolarını kapsar. Hibernate, bu temel üzerine inşa eder ve önbellekleme, toplu işlemler, özel türler, gelişmiş sorgulama gibi daha gelişmiş ve performans odaklı özellikler sunar.
* Paketler: JPA annotasyonları jakarta.persistence (veya eski Java EE sürümlerinde javax.persistence) paketinde yer alırken, Hibernate annotasyonları org.hibernate.annotations paketinde bulunur.
Örnek Farklılıklar:
* Benzersiz Kısıtlamalar ve İndeksler: JPA’da @Table(uniqueConstraints = ...) veya @Column(unique = true) kullanırsınız. Hibernate’de ise @UniqueConstraint ve @Index gibi daha esnek ve doğrudan veritabanı indekslerini tanımlayan annotasyonlar bulunur.
* Yükleme Stratejileri: JPA’nın ilişkiler için fetch = FetchType.LAZY veya EAGER özniteliği vardır. Hibernate, @Fetch(FetchMode.JOIN/SELECT/SUBSELECT) ile daha ayrıntılı kontrol sağlar.
* ID Üretimi: JPA’nın @GeneratedValue stratejileri varken, Hibernate’in @GenericGenerator gibi daha özelleştirilebilir jeneratörleri vardır.
* Önbellekleme: JPA’nın doğrudan bir önbellek annotasyonu yoktur, önbellek yapılandırması sağlayıcıya özel XML veya özellikler üzerinden yapılır. Hibernate, @Cache annotasyonu ile entity bazında önbellek stratejilerini doğrudan tanımlamanıza olanak tanır.
* Hesaplanmış Alanlar: JPA’da doğrudan bir karşılığı olmayan @Formula gibi annotasyonlar, Hibernate’in güçlü özelliklerindendir.
Ne Zaman Hangi Annotasyonu Kullanmalı?
Bu, her geliştiricinin karşılaştığı önemli bir sorudur. İşte genel bir kılavuz:
* Öncelik JPA’ya Verin: Uygulamanızın taşınabilirliğini en üst düzeyde tutmak için her zaman önce JPA standart annotasyonlarını kullanmaya çalışın. Bir özelliğe ihtiyacınız olduğunda, önce JPA’nın bunu nasıl ele aldığını araştırın. Bu, kodunuzu farklı bir JPA sağlayıcısına geçiş yapmanız gerektiğinde büyük ölçüde basitleştirir.
* Hibernate’in Gücünden Faydalanın: Eğer JPA’nın sunduğu özellikler yetersiz kalıyorsa veya belirli bir performans optimizasyonu ya da özel bir davranış gerekiyorsa, o zaman Hibernate’in özel annotasyonlarına başvurun. Örneğin:
* Gelişmiş ikinci seviye önbellekleme stratejileri gerektiğinde (@Cache).
* Veritabanı tarafında hesaplanan bir alanı eşlemek istediğinizde (@Formula).
* Koleksiyonların veya ilişkilerin yükleme davranışını ince ayarlamak istediğinizde (@BatchSize, @Fetch, @LazyCollection).
* Soft delete gibi durumlarda dinamik filtreleme gerektiğinde (@Where, @Filter).
* JPA’nın desteklemediği özel bir veri türünü eşlemek istediğinizde (@Type).
* Performans ve Özelleştirme: Performans kritik uygulamalarda veya veritabanı ile daha derin entegrasyon gerektiğinde, Hibernate annotasyonları vazgeçilmez olabilir. Ancak, bu kararın uygulamanın taşınabilirlik maliyetiyle dikkatlice tartılması gerekir.
* Ekosistem ve Topluluk: Hibernate, geniş bir topluluğa ve zengin belgelere sahiptir. Bu, özel durumlar için çözüm bulmayı kolaylaştırır ve karşılaşılan sorunlarda destek bulma olasılığını artırır.
Pratik Örnekler
Şimdi, JPA ve Hibernate annotasyonlarının birlikte veya ayrı ayrı nasıl kullanılabileceğini gösteren bazı pratik örneklere bakalım.
Basit Bir JPA Entity
Bir Kullanici entity’si tanımlayalım. Tamamen JPA annotasyonları kullanıyoruz.
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import java.util.Date;
@Entity
@Table(name = "KULLANICILAR", uniqueConstraints = {
@UniqueConstraint(columnNames = "email")
})
public class Kullanici {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "kullanici_adi", nullable = false, unique = true, length = 50)
private String username;
@Column(nullable = false, length = 100)
private String email;
@Column(name = "sifre", nullable = false, length = 255)
private String password;
@Column(name = "kayit_tarihi", updatable = false)
private Date registrationDate;
// Constructors, getters, setters
public Kullanici() {
this.registrationDate = new Date(); // Varsayılan değer
}
public Kullanici(String username, String email, String password) {
this();
this.username = username;
this.email = email;
this.password = password;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public Date getRegistrationDate() { return registrationDate; }
public void setRegistrationDate(Date registrationDate) { this.registrationDate = registrationDate; }
@Override
public String toString() {
return "Kullanici{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
'}';
}
}
Bu örnek, temel bir entity’nin nasıl tanımlandığını gösterir. id alanı birincil anahtar olarak işaretlenmiş ve veritabanı tarafından otomatik olarak artırılması için GenerationType.IDENTITY kullanılmıştır. username ve email alanları benzersiz ve boş olamaz.
İlişki Örneği (JPA)
Kategori ve Urun entity’leri arasında bire çok (OneToMany) ve çoktan bire (ManyToOne) ilişkiyi gösterelim.
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "KATEGORILER")
public class Kategori {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 50)
private String ad;
@OneToMany(mappedBy = "kategori", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set urunler = new HashSet<>();
// Constructors, getters, setters
public Kategori() {}
public Kategori(String ad) { this.ad = ad; }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getAd() { return ad; }
public void setAd(String ad) { this.ad = ad; }
public Set getUrunler() { return urunler; }
public void setUrunler(Set urunler) { this.urunler = urunler; }
public void addUrun(Urun urun) {
urunler.add(urun);
urun.setKategori(this);
}
}
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
@Entity
@Table(name = "URUNLER")
public class Urun {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String ad;
@Column(nullable = false)
private double fiyat;
@ManyToOne
@JoinColumn(name = "kategori_id", nullable = false)
private Kategori kategori;
// Constructors, getters, setters
public Urun() {}
public Urun(String ad, double fiyat, Kategori kategori) {
this.ad = ad;
this.fiyat = fiyat;
this.kategori = kategori;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getAd() { return ad; }
public void setAd(String ad) { this.ad = ad; }
public double getFiyat() { return fiyat; }
public void setFiyat(double fiyat) { this.fiyat = fiyat; }
public Kategori getKategori() { return kategori; }
public void setKategori(Kategori kategori) { this.kategori = kategori; }
}
Kategori sınıfında @OneToMany(mappedBy = "kategori") ilişkisi, Urun sınıfındaki kategori alanı tarafından yönetildiğini belirtir. Urun sınıfında ise @ManyToOne ve @JoinColumn ile yabancı anahtar tanımlanmıştır.
Hibernate Özel Annotasyon Kullanımı
Şimdi, Urun entity’mize Hibernate’e özgü bazı annotasyonlar ekleyerek daha gelişmiş özellikler kazandıralım.
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Formula;
import org.hibernate.annotations.Where;
@Entity
@Table(name = "URUNLER")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) // Urun entity'sini 2. seviye önbelleğe al
@Where(clause = "stok_durumu > 0") // Varsayılan olarak sadece stokta olan ürünleri getir
public class Urun {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String ad;
@Column(nullable = false)
private double fiyat;
@Column(name = "stok_durumu", nullable = false)
private int stokDurumu;
@Formula("fiyat * 1.18") // KDV dahil fiyatı hesapla (varsayımsal %18 KDV)
private double kdvliFiyat;
@ManyToOne
@JoinColumn(name = "kategori_id", nullable = false)
private Kategori kategori;
// Constructors, getters, setters
public Urun() {}
public Urun(String ad, double fiyat, int stokDurumu, Kategori kategori) {
this.ad = ad;
this.fiyat = fiyat;
this.stokDurumu = stokDurumu;
this.kategori = kategori;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getAd() { return ad; }
public void setAd(String ad) { this.ad = ad; }
public double getFiyat() { return fiyat; }
public void setFiyat(double fiyat) { this.fiyat = fiyat; }
public int getStokDurumu() { return stokDurumu; }
public void setStokDurumu(int stokDurumu) { this.stokDurumu = stokDurumu; }
public double getKdvliFiyat() { return kdvliFiyat; } // Setter olmamalı, çünkü veritabanı hesaplar
public Kategori getKategori() { return kategori; }
public void setKategori(Kategori kategori) { this.kategori = kategori; }
}
Bu örnekte:
* @Cache(usage = CacheConcurrencyStrategy.READ_WRITE): Urun entity’sinin ikinci seviye önbellekte READ_WRITE stratejisiyle önbelleğe alınmasını sağlar. Bu, sık okunan ama nadiren güncellenen veriler için performansı artırır.
* @Where(clause = "stok_durumu > 0"): Urun entity’si sorgulandığında, varsayılan olarak yalnızca stok_durumu 0’dan büyük olan ürünlerin getirilmesini sağlar. Bu, “soft delete” veya aktif/pasif kayıtları filtrelemek için yaygın bir kullanımdır.
@Formula("fiyat 1.18"): kdvliFiyat alanı, veritabanı tarafında fiyat * 1.18 SQL ifadesiyle hesaplanır. Bu, uygulamanın hesaplama yükünü azaltır ve veritabanının yeteneklerinden faydalanır.
Bu eklemeler, uygulamayı Hibernate’e bağımlı hale getirir ancak belirli performans ve işlevsellik gereksinimlerini karşılamak için güçlü araçlar sunar.
Sonuç
JPA ve Hibernate annotasyonları, modern Java uygulamalarında veri kalıcılığı katmanını inşa etmek için vazgeçilmez araçlardır. JPA, Java nesnelerinin ilişkisel veritabanlarına eşlenmesi için standart ve taşınabilir bir yol sunarken, Hibernate bu standardı uygulayan ve aynı zamanda standart ötesi, güçlü özellikler ve optimizasyonlar sağlayan lider bir implementasyondur.
Bir geliştirici olarak, öncelikle JPA annotasyonlarını kullanarak uygulamanızın taşınabilirliğini ve endüstri standartlarına uygunluğunu sağlamalısınız. Ancak, performans, özel veri türleri veya gelişmiş önbellekleme gibi belirli gereksinimler ortaya çıktığında, Hibernate’in özel annotasyonlarını kullanmaktan çekinmeyin. Bu annotasyonlar, JPA’nın kapsamadığı senaryolarda size ince ayar ve daha derinlemesine kontrol imkanı sunar.
Her iki annotasyon setini de iyi anlamak, hem sağlam ve sürdürülebilir hem de performanslı ve optimize edilmiş Java uygulamaları geliştirmek için anahtardır. Doğru dengeyi bulmak, uygulamanızın ihtiyaçlarına ve projenizin uzun vadeli hedeflerine bağlıdır. Gelecekte de JPA ve Hibernate’in, geliştiricilere daha iyi araçlar sunmak için birlikte evrilmeye devam edeceği açıktır.