Bu yazımda, kısaca Java'da kalıcılık API'sini tanıtmak ve ORM araçlarından en popüler olanı Hibernate ile ilgili giriş düzeyinde bir örnek paylaşmak istiyorum.
JPA (Java Persistence API), JCP (Java Community Process - Java teknolojileri için spesifikasyon belirleyen gurup) tarafından spec'leri belirlenmiş, kalıcılık/ORM araçlarının manifestosu olan bir API'dir. Her JCP spec'inin bir RI (Reference Implementation) vardır, JPA' nın da refrence implementation' u Oracle' ın Toplink' idir. Fakat yaygın kullanım olarak Hibernate tercih ediliyor.
Normalde JCP de işler şu sırayla işler;
- Community'nin ihtiyaçlarına göre spec'ler hazırlanır (JPA, JMS, JSF, JTA ...)
- Bu spec'lere uygun referans gerçekleştirimleri -RI- yapılır (JPA için Oracle Toplink'tir)
- Daha özel ihtiyaçlara göre farklı gerçekleştirimler yapılır (Hibernate, OpenJPA, IBatis ...)
Fakat ORM araçları için bu adımlar biraz ters işledi, şöyle ki; JPA spec'leri yokken Hibernate yoğun olarak kullanılıyordu. Bir anlamda işin bürokrasisi için JPA spec'leri Hibernate'den sonra hazırlandı.
Tüm spec'lerde olduğu gibi JPA'da tek başına kullanılamaz. Verilerinizi işlemek için gerçekleştirim API' lerine ihtiyaç duyarsınız. Aşağıda hibernate özelinde bunları görebilirsiniz.
Geliştireceğiniz projeye başlamadan önce classpath de bulunması gereken bazı jar dosyaları var.
Zorunlu olanlar:
hibernate3.jar
antlr-2.7.6.jar
common-collections-3.1.jar
dom4j-1.6.1.jar
javasist-3.9.0.GA.jar
jta-1.1.jar
cglib.jar
Seçeneğe bağlı zorunlu olanlar:
ejb3-persistence.jar (EJB kullanacaksanız)
javax.persistence-1.xx.x.xxxxxxxxxx.jar
Seçeneğe göre zorunlu olanlar:
mysql,
hsql,
oracle,
mssql driver larından birisinin jar dosyası
Annotation'lar ile setup yapılacaksa, seçimlik:
hibernate-annototaions.jar
Kolon geçerlilik denetimleri yapılacaksa, seçimlik:
hibernate-validator.jar
Gelişmiş arama yapısı kullanılacaksa, seçimlik:
hibernate-search.jar
lucene-core-x.y.z.jar
Hibernate ORM ayarları için iki seçenek sunar, xml ile ve annotation' lar ile. Ben, daha yeni olduğu için annotation' ları kullanacağım.
İlk önce, projemizin root klasöründe hibernate.cfg.xml isimli master setup dosyamız olmalı. Bu dosya içerisinde, veritabanı ve Hibernate için üst düzey seçenekler ve değerleri yer alır.
Kullanıcı adı, şifre, veritabanı bağlantı url'i, db sürücüsü, kullanılacak dialect, vs... gibi. Mysql e bağlanmak için basit olarak aşağıdaki gibi bir konfigurasyon dosyası kullanılabilir.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/deneme</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">12345</property>
<property name="hibernate.show_sql">false</property>
<property name="hibernate.hbm2ddl.auto">update</property>
</session-factory>
</hibernate-configuration>
Bu xml konfigurasyonunda kullanılan seçenekler ve değerleri ile ilgili daha ayrıntılı bilgiler başka bir yazının konusu. Unutmadan, konfigurasyonu sadece xml dosyası ile yapmak zorunda değilsiniz,
properties dosyası kullanılabilir veya kodlama yolunu da seçebilirsiniz ki bu müdaheleleri zorlaştırdığı için pek tercih edilmiyor.
Modellerin eşlenmesi, kolonların ve ilişkilerin belirlenmesi için annotation kullanacağız. Aşağıda iki model sınıfımız ve bu iki sınıfın nasıl ORM yapıldığını göreceğiz.
User modeli
@Entity
@Table(name="USER")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id @GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="ID")
private Long id;
@Column(name="NAME", length=30, nullable=false, unique=true)
private String name;
@Column(name="PASSWORD", length=32, nullable=false)
private String password;
@Column(name="REG_DATE")
@Temporal(TemporalType.TIMESTAMP)
private Date registrationDate = new Date();
@Column(name="IS_ADMIN")
private Boolean isAdmin = Boolean.FALSE;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Boolean getIsAdmin() {
return isAdmin;
}
public void setIsAdmin(Boolean isAdmin) {
this.isAdmin = isAdmin;
}
public Date getRegistrationDate() {
return registrationDate;
}
public void setRegistrationDate(Date registrationDate) {
this.registrationDate = registrationDate;
}
}
UserRole modeli
@Entity
@Table(name="USER_ROLE")
public class UserRole implements Serializable {
private static final long serialVersionUID = 1L;
@Id @GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="ID")
private Long id;
@Column(name="INDEX_COL")
private Integer indexCol;
@ManyToOne
@JoinColumn(name="USER_ID")
private User user;
@ManyToOne
@JoinColumn(name="ROLE_ID")
private Role role;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public Integer getIndexCol() {
return indexCol;
}
}
Model tanıtımlarımızda kullandığımız annotation' ları kısaca açıklayalım.
@Entity -> Model sınıf olduğunu belirtir (3 tane olan EJB lerden birisi budur, dğerleri ise Oturum ve Mesaj Güdümlü Bean'lardır)
@Table -> Tablo ve parentezlerden sonra da özelliklerini belirtir
@Id -> Modelin ID alanını belirtir
@GeneratedValue -> ID alanının farklılaştırılma seçeneğini belirtir, strategy=GenerationType.AUTO kullanarak otomatik artan bir ID alanına sahip oluruz
@Column -> Kolon ve parentezlerden sonra da özelliklerini belirtir
@Temporal -> Tarihin şeklini belirtir
@ManyToOne -> Tablolar arası ilişkilerden ÇokaBir'i belirtir, LookUp table yapısı
@JoinColumn -> İlişkinin hangi kolon üzerinden olacağını ve yapısını belirtir
Bu ve diğer annotation'lar başka bir yazının konusu. Burda dikkatinizi çekmek istediğim önemli bir nokta var. Kolon türleri hep Wrapper Class olarak belirlenmiş, primitif olarak belirlenemez miydi? Evet belirlenebilirdi fakat veritabanından dönen değerler her zaman dolu olmayabilir bazen NULL değeri elde ederiz. Bu durumda primitif değişkene NULL atamaya kalkmış oluruz ki bu hataya sebep olur. Bu yüzden kolon türlerini her zaman Wrapper sınıflardan seçin.
Tanımladığımız bu modellerin Hibernate bildirim için yine yaygın olarak aşağıdaki gibi bir yol izleyeceğiz.
Genel tercih olarak projemize DBUtils isimli bir sınıf ve içine de şu satırları ekleyelim.
//database oturum yöneticisi
private static SessionFactory sessionFactory = null;
//singleton patterni yapısında tasarlayalım ki, fazladan instance ın önüne geçelim,
//bu metodu projeniz startup olurken bir kere çağırmalısınız
public static void init() {
if (sessionFactory == null) {
try {
//oturum yöneticisine kullanacağımız model sınıflarımızı bildirerek konfigure ediyoruz
sessionFactory = new AnnotationConfiguration()
.addAnnotatedClass(User.class)
.addAnnotatedClass(UserRole.class)
.configure()
.buildSessionFactory(); //root klasördeki xml/properties dosyamıza otomatik olarak bakacak
//bağlantı işlemi başarılı olmuş mu, kontrol ediyoruz
try {
Transaction tx = getSession().beginTransaction();
if (tx.isActive()) tx.rollback();
System.out.println("Database connection successful.");
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("Factory not null!");
}
}
//oturum taleplerini tek bir noktadan idare edebilmek için bu sınıf içerisinde talepleri karşılıyoruz
public static Session getSession() {
Session session = null;
try {
//her talepte yeni bir oturum açılır, oturum ile connection u karıştırmamalıyız
session = sessionFactory.openSession();
} catch(Exception e) {
System.out.println("Session getting error!");
}
return session;
}
Bu işlemlerden sonra artık projemizin herhangi bir yerinden istediğimiz gibi CRUD (Create, Read, Update, Delete) işlemleri yapabiliriz.
Ekleme/Güncelleme
Session session = DBUtils.getSession();
User user = new User("Mustafa", "12345", false);
session.saveOrUpdate(user);
session.close();
Tekli Okuma
Session session = DBUtils.getSession();
User user = (User) session.load(User.class, 3L);
session.close();
Liste şeklinde okuma
Session session = DBUtils.getSession();
List userList = (List) session.createQuery("from User u").list();
session.close();
Silme
Session session = DBUtils.getSession();
User user = (User) session.load(User.class, 3L);
session.remove(user); //veya session.remove(session.load(User.class, 3L));
session.close();
Son olarak;
Hibernate'in hedefi sadece CRUD işlemleridir, daha fazlası için uygun değildir, örneğin büyük mitarda verileri transfer etme ve işleme gibi.