26 Ocak 2010 Salı

Hibernate konfigurasyonu

Yeni geliştirmek istediğiniz projede Seam, Spring... gibi genel kabül görmüş framework'lerden birisini kullanmak yerine kendi araç setinizi seçmek isterseniz ve projenizde veritabanı işlemleri için ORM (Object Relation Mapping) araçlarını kullanmak isterseniz neler yapmalısınız?

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.

20 Ocak 2010 Çarşamba

Hibernate'de equals metodu

Hibernate entity lerinde class ların equals metodlarını ezmeleri neden önemli? Kısaca; "equals metodu ezmezseniz, List ve Map yapıları üzerindeki entitylerden var olduğunu bildiklerinize ulaşamazsınız!" denilebilir.

Örneklersek; Arac isimli bir modelimiz olsun.

Arac sınıfımız
public class Arac implements Serializable {

private Long id;
private String plaka;

public Arac() {

}

public Arac(Long id, String plaka) {
super();
this.id = id;
this.plaka = plaka;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getPlaka() {
return plaka;
}

public void setPlaka(String plaka) {
this.plaka = plaka;
}

}

Arac tablosu verileri
ID  PLAKA
--- -----------
1 34-AB-034
2 34-BC-035
3 34-CD-036
4 34-DE-037
5 34-EF-038
6 34-FG-039
7 34-GH-040

Projemizde, tüm araçları içeren bir List/Map kullandığımızı düşünelim.

List<Arac> aracList = new ArrayList<Arac>();

//hibernate ile çektiğimiz araçları listeye dolduralım

//listeden aradığımız (ve var olduğunu kesin olarak bildiğimiz) aracı istiyoruz
Arac bulunan = aracList.get(new Arac(1L, "34-AB-034"));

Normalde üst kısımda aradığımız aracı bulurduk fakat hibernate kanalı ile elde ettiğimiz modeller için hashcode, her talepte yeniden oluşturuluyor! Tüm nesnelerin temeli olan Object sınıfındaki equals metodu eşitlik karşılaştırmasında hashcode değerine baktığı için equals değeri bu durumda false dönüyor. List ve Map yapılarında bir nesne aranırken equals metodundan faydalanılıyor. Eğer aranan ile bakılanın equals metodları true dönerse aranılan bulunmuş oluyor ve döngü sonlandırılıyor aksi halde aranılan bulunamıyor.

Özetlersek; List ve Map gibi veri yapıları içerisine hibernate kanalı ile doldurduğumuz nesnelerin equals metodunu uygun bir biçimde ezmezsek, List/Map üzerinde yapacağımız aramalarda aradığımızı bulamayabiliriz. En uygun ezme yolunun, List/Map e dolduracağımız entity nin ID alanı kıyaslaması olduğunu düşünüyorum.

Örnek:
 public boolean equals(Object obj) {
if (obj != null && obj instanceof Arac) {
if (((Arac) obj).getId().equals(this.id)) {
return true;
}
}
return super.equals(obj);
}

Equals metodunu ezmek yerine hashcode metodu da ezilebilirdi. Hash algoritmaları yazmak equals metodunu ezmekten daha zahmetlidir. hashcode metodunu ezme seçeneğine, gerçekten daha verimli ve lazım olduğunu düşündüğünüz durumlarda bakın derim.

15 Ocak 2010 Cuma

Crontab periyod eşitsizliği

Posix sistemlerde yaygın olarak kullanılan ve oldukça da başarılı olan Crontab hizmeti, belirlenen (ya da beklenen) periyodlar için tam da beklendiği gibi davranmayabiliyor. Vereceğimiz periyod bölünmeye dayalı olacaksa bölmek istediğimiz hanenin üst limiti önemli oluyor. Bildiğimiz gibi bu değerler; saniye ve dakika haneleri için 60, saat hanesi için 24 gibi (aslında bu değerler yerine 0 kabul ediyoruz), aynı şekilde hafta ve ay değerlerinin de üst limitlerine göre bölümleme yapmamız önemli oluyor.

Atlamadan eklemek isterim ki, posix sistem crontablarında saniye hanesi yok! Java' da geliştirilmiş olan Quartz projesinde saniye eklenmiş durumda. Bu yüzden 5 haneli crontablar yerine 6 haneli olanlarını örnekleyeceğim.

Üst limitler neden önemli? Bunun için bir iki örneğe bakmamız daha iyi olacaktır.

Aşağıda verilen örneklerde periyodların tamamı eşit zaman aralıkları ile TETİKLENECEKTİR.

Her 10 dakikada bir:
* 0/10 * * * ?

Her 3 saatte bir:
* * 0/3 * * ?

Aşağıda verilen örneklerde ise periyodların tamamı eşit zaman aralıkları ile TETİKLENMEYECEKTİR.

Her 7 dakikada bir:
* 0/7 * * * ?

Her 40 saniyede bir: (bu 40. ve 0. saniyelerde yani 20 ve 40 sn aralıklarla tetiklenecek)
0/40 * * * * ?

Crontab hanelerini bölmek istediğimiz değer, ilgili hanenin makisimum değerini kalansız böldüğünde aralıklar eşit oluyor. Yine örnek verecek olursak; Dakika/Saniye hanesini 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30 gibi sayılar maksimum değeri olan 60 ı tam bölecek ve bu şekilde verilen bir periyod eşit zamanlarda tetiklenecektir.

Üst limitlerde saniye ve dakika için 60, saat içinse 24 olmadığını biliyorum, fakat crontab davranışında son tetikleme 0 içinde yapılıyor bu yüzden saniye ve dakika için 60 ve saat içinde 24 olarak değerlendirebiliriz.

Yukarıda belirttiğim gibi; Java için geliştirilmiş olan Quartz API, posix lerdeki crontab mantığını gerçeklediği için benzer durumla karşılaşacaksınız, şaşırmayın.

Son bir küçük uyarı; bölümleme yaparken
0/10 -> her 10. da bir demek (10. da bir kez tetiklenir)
*/10 -> 10. nun her birinde demek oluyor (10. da defalarca kez tetiklenir)

14 Ocak 2010 Perşembe

MVC vs MVP

Uzun zamandır kullanımda olup ve birçok projenin de anaçatısı durumunda olan MVC (Model-View-Controller) desenine rakip olarak sahneye çıkan ve Google tarafından da ateşli bir biçimde savunulan MVP (Model-View-Presenter) deseni ile ilgili bir mücadele yaşanıyor forumlarda.

Google tarafından hazırlanan sunumlara baktığımızda MVP için;
  • GWT dünyasında testleri kolaylaştırdığını
  • View ile Controller bağlantısının olmadığını
  • Daha rahat kod geliştirildiğini
Yine aynı sunumlarda MVC için ise;
  • GWT dünyasında testleri zorlaştırdığını
  • View ile Controller bağlantısının olduğunu (Katı Bağ oluşturuyor diye)
  • 1980 lerden kalma olduğunu
söylüyorlar.

Google'ın değerlendirmeleri pek insaflıymış gibi gelmedi bana. Evet GWT framework'ünde testleri kolaylaştırdığı doğru olabilir fakat MVC' de View ile Controller' un doğrudan bağlantısı olmak zorunda değil, bu tarz bir kullanım sadece programcının tercihi olabilir. Ayrıca bir teknolojinin eski olması onun kötü olduğu anlamına gelseydi bugün kullandığımız ilişkisel veritabanlarının en berbat teknolojiler olaması gerekirdi zira 1970 li yıllarda geliştirilmişti. Benzer şekilde C dili ve HTML de çok eski zamanlara ait olmalarına rağmen günümüzde yaygın olarak kullanılmakta.

MVP desenindeki Presenter, Controller ile hemen hemen aynı yapıya sahip, tek fark; MVP'de bulunan EventBus sınıfı. Bu sınıf MVP katmanları arasında haberleşmeden sorumlu. Model, View'i, View'de Presenter'ı tanımıyor. Soyutlama açısından hoş bir yaklaşım. MVP sınıflarındaki tüm olaylar EventBus tarafından handle edilidiği için dispatch da bu sınıfın denetiminde oluyor. Özetle, kimse kimseyi tanımıyor ama EventBus herkesi tanıyor ve haberleştiriyor. Bunun tek dezavantajı ise fazladan bir sınıfımızın olması.

MVP'deki EventBus işlevini MVC' deki Controller sınıfı pekala yapabilir. MVC'nin gerçekleştirimine göre, View ve Model sınıfları birbirlerini tanımayabilir ve bütün denetim Controller' da olabilir. Zaten adından da anlaşıldığı gibi Controller sınıfı herşeyi kontrol eden ve iş mantıklarının yer aldığı sınıflar olmalıdır.

Bence, kullanılan dilin imkanları ve programcının tercihlerine göre MVP olsun MVC olsun çok işe yarayabilecekleri gibi bir çok sıkıntının kaynağı da olabilirler. MVC nin en büyük avantajı, genel kabul görmüş olması ve defalarca kendisini ispat etmiş olmasıdır. MVP henüz yeni olduğu için kabul zamanına ihtiyacı var. Ayrıca MVP'nin tercih edilebilmesi için daha net kazanımlar sunması lazım. Controller'a ait bir kısım görevleri ayrı bir sınıfta ele almak ve Model ile View arası iletişim için olay temelli haberleşme yapmak çok cazip bir kazanım gibi durmuyor.

13 Ocak 2010 Çarşamba

Selam Dünya!

Herhangi bir dile başlayan bir programcının ilk denediği, HelloWorld örneği ile blog yayınıma başlamak istiyorum.
public class HelloWorld {

public static void main(String[] args) {
System.out.println("Selam dünya...");
}

}

Ciddi anlamda zaman darlığı yaşamadığım sürece, bu blogda java, java teknolojileri ve kendime dair bilgileri paylaşacağım. Şimdiden belirtmek isterim ki; burada yazacağım içerikler iddia değildir, sadece ben öyle bildiğim için öyle yazdığım şeylerdir.

Yazdıklarımın birilerinin işine yaraması ve yapılacak eleştirilerin yapıcı olması umuduyla Selam Dünya...