24 Ağustos 2012 Cuma

Hibernate ve Foreign Key

Foreign Key Nedir
Foreign key kısaca Parent ve Child tabloların birbirlerine bağlanması amacını taşır.

Aşağıdaki şekilde grouptable bir Parent, story ise Child olarak kullanılmış. Her iki tabloyu birbirine bağlayan bir de foreign key var.




Bir başka benzer örneği de burada buldum.



Hibernate ve One-to-Many Kullanımı

Hibernate ile iki tablo arasındaki foreign key kullanılarak one-to-many ilişkisi kurulmak isteniyorsa önümüzde iki tane seçenek var.

  1. Çift yönlü (bidirectional) One-to-Many. 
  2. Tek yönlü (unidirectional) One-to-Many kullanımı.


One-To-Many İlişkisinin Çift Yönlü (Bidirectional) olması ve Foreign Key kullanması

Çift Yönlü Olması durumunu aşağıda açıklamaya çalıştım.


@Entity
public class Child{

private Long id;
private String childName;
private Parent parent;

     @ManyToOne
     public Parent getParent() {
         return parent;
     }
}
@Entity
public class Parent{

 private Long id;
 private String name;
 private String description;
 private List<Children> children;

 @Id @GeneratedValue
 public Long getId() {
  return id;
 }

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

 @OneToMany(cascade=CascadeType.ALL, mappedBy="parent")
 public List<Children> getChildren() {
  return children;
 } 
}
One-To-Many ve Foreign Key Sütun İsmi
Bu kodda mappedBy kullanımı şu anlama geliyor. Parent ve Child sınıfları ilişkililer ve ilişki için Child sınıfındaki parent alanı için için üretilen foreign key kullanılacak.

Benim verdiğimden farklı örnek bir kodu burada bulabilirsiniz.

Yukarıda verilen örnek kodda @JoinColumn kullanılmamış. Bu gösterimin kullanılmadığı durumda üretilen foreign key sütununun isminin nasıl üretildiği şöyle açıklanmış.
If no @JoinColumn is declared on the owner side, the defaults apply. A join column(s) will be created in the owner table and its name will be the concatenation of the name of the relationship in the owner side, _ (underscore), and the name of the primary key column(s) in the owned side.
Bu açıklamada dikkat edilmesi gereken bir nokta şu. Object Oriented dünyada "owning side" Parent iken, SQL dünyasında foreign key'i içeren taraf "owning side" oluyor (yani Child) . Bu durum burada daha iyi açıklanmış.

Yukarıdaki açıklamaya göre Child nesnesi "owning side" olduğu için @JoinColumn kullanılmazsa bizim üretilecek foreign key sütununun ismi parent_id olacaktı.

Böyle bir konfigürasyon ilk defa yaratılıp save() metodu çağrılırsa üretilen SQL şuna benzer bir şey oluyor.

Hibernate: insert into PARENT (x, y, z) values (?)
Hibernate: insert into CHILD (x, y, z, parent_id) values (?, ?, ?, ?, ?)
Hibernate: insert into CHILD (x, y, z, parent_id) values (?, ?, ?, ?, ?)

Bu şekilde oluşturulan ilşkilerde N+1 select problemi de çok rastlanılan bir durum. Bu konuyla ilgili Hibernate ve N+1 Select problemi başlıklı yazıyı da okuyabilirsiniz.

One-To-Many ve CascadeType (Ard arda İşlemler Seçenekleri)

One-To-Many ilişkilerde yeni parent ve child nesneleri kaydederken foreign key kısıtından dolayı hata almamak için asgari CascadeType.PERSIST seçeneğinin tanımlı olması lazım. Böylece yukarıdaki üretilen SQL örneklerinde de görülebileceği gibi önce parent nesnesi kaydedilir ve onun birincil anahtarı child nesnelerde foreign key olarak kullanılır.

One-To-Many İlişkisinin Tek Yönlü (Unidirectional) olması ve Foreign Key kullanması

Bu durumu açıklayan güzel bir örneği burada buldum. Bir başka örnek ise burada.

Parent sınıfa normalde eklenmesi gereken
mappedBy özelliği yerine bu sefer sadece @JoinColumn gösterimi ekleniyor.

@Entity
public class Child{

private Long id;
private String childName;
}
 
@Entity
public class Parent{

 private Long id;
 private String name;
 private String description;
 private List<Children> children;

 @Id @GeneratedValue
 public Long getId() {
  return id;
 }

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

 @OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="parent_id", referencedColumnName="id")
 public List<Children> getChildren() {
  return children;
 } 
  
Bu kullanım şeklinde dikkat edilmesi gereken nokta mappedBy kullanılmadığı için Parent sınfı "owning side"
haline geliyor ve kendi id sütununu Child tablosuna foreign key olarak yazıyor.

Bir diğer nokta ise bu kullanım şekli sadece JPA 2.0 ile destekleniyor.

Bir dikkat edilmesi gereken nokta ise bu kullanım şekil tavsiye edilmiyor.

Kullanılan Collection Sınıfları

Hibernate ile "one-to-many" ilişkisinı kullanırken dikkatimi çeken bir şey oldu.

Kod içinde List, Set, Map gibi collection interface'leri kullanılırken Hibernate bu interfacelerden türeyen kendi sınıflarını kullanıyor. Örneğin org.hibernate.collection.PersistentSet gibi.

15 Ağustos 2012 Çarşamba

Iterator İşlemleri

Iteratordeki Bazı Elemanları Atlama
Guava
Iterators.skip(Iterator<?> iterator,int numberToSkip) ile bu iş kolayca başarılıyor.

Java
Buradaki örnekten aldığım kod ile de bu işi gerçekletirmek mümkün.

8 Ağustos 2012 Çarşamba

JExcel ve Apache POI

JExcel ve RowsExceededException
JExcel API'si eski olduğu için büyük excel dosyaları üretirken 65536 satırdan fazla satır yaratılmaya çalışılırsa RowsExceededException exception'ı atıyor. WritableSheetImpl.java dosyasındaki bu satır hatanın sebebi.

Büyük dosyalar üretmek için http://poi.apache.org/spreadsheet/how-to.html adresinde örnek verildiği gibi SXSSFWorkbook sınıfını kullanmak lazım. Zaten bu sınıfın açıklamasında da "Streaming version of XSSFWorkbook implementing the "BigGridDemo" strategy. " yazıyor.


JExcel ve Yuvarlama Hatası
JExcel kullanırken Number tipi alanların değerlerini getContents() metodu ile String olarak alırken dikkatli olmak lazım. Çünkü jxl.read.biff.NumberRecord sınıfı kendi içinde aşağıdaki gibi bir DecimalFormat nesnesi tutuyor. Ancak bu nesne sadece 3 hane hassasiyete kadar doğru değerleri döndürüyor. Eğer girilen değer daha fazla hassasiyete sahipse sonuç yuvarlanarak veriliyor ki bu da farkında olmadan hatalara sebep olabilir.

Aşağıda Jexcel'in kismi sınıf hiyerarşisi var.

Understanding the Excel .xls Binary File Format başlıklı yazıda da Excel'in veriyi nasıl sakladığı anlatılıyor.

XLSX Formatı
XLSX formatı aslında zip dosya formatı ile aynı.Eğer xlsx dosyasını bir zip programı ile açarsak karşımıza aşağıdaki görüntü çıkar.
XSSFSheetXMLHandler sınıfı ile  xml dosyasını parse etmek imkanı var. Bu sınıfın en önemli özelliği her hücreyi String olarak okuyabilmemizi sağlaması.

Excel Hücre Tipleri
Bir hücre aşağıdaki gibi Number olarak görünüyor olsak bile
hücrenin tipi SST_STRING olabiliyor. Bu durumda da string olarak okunabiliyor.

Eğer hücreye format verilmişse
hücrenin tipi NUMBER oluyor. XML içindeki veri 0.22556399999999999 olsa bile


double'a parse edilirken 0.225564 şekline dönüyor. Daha sonra formatlama stringi #,##0.000000 olduğu için java.text.DecimalFormat sınıfı sayesinde Türkçe 0,225564 haline geliyor.


POI ve Number hücrenin sağa/sola dayalı olması
Excel üzerinde 16,466166 görüyoruz ancak xml'de 16.466166000000001 de yazılı.
Eğer hücre sağa dayalı ise xml aşağıdaki gibi
<c r="A1"><v>16.466166000000001</v></c> --> çıktı 16.466166000000001





Eğer hücre sola dayalı ise xml aşağıdaki gibi
<c r="A2" s="1"><v>16.466166000000001</v></c> --> çıktı :  16,466166 çünkü s=1 ile style olarak "general format" kullanıyor