JPA Fetch Stratejileri ve Lazy Loading

JPA içerisinde nesneler arasındaki ilişkilerde verinin getirilmesi ile ilgili davranışlar kendi aralarında farklılık gösterebilmektedir. Bu özelliği sağlayan fetch stratejileridir.

Örnek verecek olursak OneToOne ve ManyToOne ilişkili nesneler asıl nesnenin çağırımıyla birlikte direkt olarak getirilirken, OneToMany ve ManyToMany ilişkili nesnelerde ise ilişkili nesnenin çağırımı ile bilgi çekilmesi sağlanmaktır. Fetch stratejilerinde OneToOne ve ManyToOne default olarak EAGER iken, ManyToMany ve OneToMany ise LAZY stratejisindedir.

Buna göre
LAZY : Tembel yükleme, asıl nesne çağırımı sonrasında lazy ilişkili nesnelerden bilgi getirilmez. Bilgiyi ancak ilişkili nesnenin çağırımı ile birlikte getirir. Burada get metodunun çağırımı ile proxy devreye girer ve ilgili sorguyu gerçekleştirir.
EAGER : Direkt yükleme, asıl nesne çağırımı ile birlikte ilişkili nesne bilgiside getirilir.

Buna örnek verecek olursak Document ve SubDocument isimli iki adet entity miz bulunuyor ve bu entityler kendi aralarinda aşağıdaki gibi ilişkili.

Fetch Stratejileri ve Lazy Loading

Fetch Stratejileri ve Lazy Loading

Bu ilişkinin kod tanımı aşağıdaki gibidir.

    @Entity
    public class Document implements Serializable {
     
     @Id @GeneratedValue
     private Long id;
     @OneToMany(mappedBy = "parentDocument")
     private List<SubDocument> subDocuments ;
     
    }
    @Entity
    public class SubDocument implements Serializable {
     
     @Id
     @GeneratedValue
     private Long id;
     
     @ManyToOne
     private Document parentDocument;
    }

Buna göre SubDocument tipinde bir nesne getirecek olursa Document nesnesi otomatik olarak yüklenecektir.
Bunu örnekleyecek olursak aşağıdaki örnekte SubDocument tipinde bir nesne getiriyoruz.

    SubDocument sub = entityManager.find(SubDocument.class, 1L);

Bu durumda yeni sorgumuz aşağıdaki gibi olacaktır.

    11:43:02,948 INFO  [STDOUT] Hibernate:
        SELECT
            subdocumen0_.id AS id28_1_,
            subdocumen0_.name AS name28_1_,
            subdocumen0_.parentDocument_id AS parentDo3_28_1_,
            document1_.id AS id29_0_,
            document1_.ad AS ad29_0_,
            document1_.version AS version29_0_
        FROM
            SubDocument subdocumen0_
        LEFT OUTER JOIN
            Document document1_
                ON subdocumen0_.parentDocument_id=document1_.id
        WHERE
            subdocumen0_.id=?

Bunun sebebi ilişkinin default olarak EAGER olması. Ancak bizim parentDocument a ihtiyacımız yoksa ne yapmalıyız ? Bu durumda ilişki belirtimini aşağıdaki örnekteki gibi LAZY olarak belirlememiz gerekiyor.

    @ManyToOne(fetch = FetchType.LAZY)
    private Document parentDocument;

Bu durumda yeni sorgumuz aşağıdaki gibi olacaktır.

    11:45:19,184 INFO  [STDOUT] Hibernate:
        SELECT
            subdocumen0_.id AS id32_0_,
            subdocumen0_.name AS name32_0_,
            subdocumen0_.parentDocument_id AS parentDo3_32_0_
        FROM
            SubDocument subdocumen0_
        WHERE
            subdocumen0_.id=?

Aradaki fark parentDocument bilgisinin çekilmemesidir.

Sorgularda Fetch Davranışları

Peki lazy olarak tanımladığımız ilişkiyi bir sorgu içerisinde kullanırsak ne olur ? Örneğin aşağıdaki gibi bir SubDocument listesi alıyoruz ve SubDocument -> Document ilişkisi EAGER durumda.

    entityManager.createQuery("from SubDocument").getResultList();

Bu durumda üretilen sorgu aşağıdaki gibidir.

    11:52:43,271 INFO  [STDOUT] Hibernate:
        SELECT
            subdocumen0_.id AS id36_,
            subdocumen0_.name AS name36_,
            subdocumen0_.parentDocument_id AS parentDo3_36_
        FROM
            SubDocument subdocumen0_
    11:52:43,274 INFO  [STDOUT] Hibernate:
        SELECT
            document0_.id AS id37_0_,
            document0_.ad AS ad37_0_,
            document0_.version AS version37_0_
        FROM
            Document document0_
        WHERE
            document0_.id=?
    11:52:43,276 INFO  [STDOUT] Hibernate:
        SELECT
            document0_.id AS id37_0_,
            document0_.ad AS ad37_0_,
            document0_.version AS version37_0_
        FROM
            Document document0_
        WHERE
            document0_.id=?

Peki buradaki sorguda EAGER stratejisi kullanmamıza karşın neden parentDocument lar ayrı sorgular ile getirildi ?
Bu aslında performansa bağlı bir durum. Sorgu içerisinde nesnelerin tekrar edilmemesi için ilişkili Document nesneleri ayrı olarak çekiliyor. Ancak her SubDocument için bu işlem tekrarlanmıyor. Burada 6 tane SubDocument olmasına karşın Document sorgu sayısı 2 dir.

Bunu detaylandıracak olursak subDocument içerisinde 1 id li bir document nesnesi var bu durumda bir sorgu oluşturuluyor ve bu document nesnesi cekiliyor. Ardından ikinci subDocument nesnesine geçiliyor ve buradaki document nesneside yine 1 numaralı id ye sahip. Bu durumda nesne entity context içerisinde managed durumda yer aldığı için tekrar veritabanına gidilmez yani yeni sorgu oluşturulmaz.

Kısacası 100 subDocument nesnenisi çektiniz ve bunlar sadece 2 Document nesnesi ile ilişkili. Bu durumda document için sadece 2 defa veritabanına gidiyor olacaktır.

Fetch Sorgular

Bir ilişkiyi lazy yaptınız ancak tek sorguda çekmek istiyorsanız duruma göre fetch sorgular kullanabilirsiniz. Örnek olarak aşağıdaki sorguda SubDocument ve Document lazy olmasına karşın aşağıdaki fetch query ile EAGER olarak çekiliyor.

    entityManager.createQuery("select sub from SubDocument sub join fetch sub.parentDocument").getResultList();

Burada oluşan sorgu aşağıdaki gibidir.

    13:23:18,693 INFO  [STDOUT] Hibernate:
        SELECT
            subdocumen0_.id AS id40_0_,
            document1_.id AS id41_1_,
            subdocumen0_.name AS name40_0_,
            subdocumen0_.parentDocument_id AS parentDo3_40_0_,
            document1_.ad AS ad41_1_,
            document1_.version AS version41_1_
        FROM
            SubDocument subdocumen0_
        INNER JOIN
            Document document1_
                ON subdocumen0_.parentDocument_id=document1_.id

Tam tersi olarak Document listesini alırken ilişkili olarak subDocument listesini almak istersek yine fetch join i aşağıdaki gibi bir yöntem ile kullanabiliriz.

    entityManager.createQuery("select doc Document doc join fetch doc.subDocuments").getResultList();

LazyInitializationException Hatası

Seam entity yaşam döngüsünü yönetsede bu hata zaman zaman karşımıza çıkabilir. Bunun nedeni lazy yüklenen nesneler bir proxy vasıtasıyla talep edildikleri anda bilgiyi getirir. Ancak bu arada entityManager kapanmışsa veya nesne ile bağı kalmamışsa sorgulama yapamayacağı icin LazyInitializationException hatası üretilir.

  1. Jul 29th, 2012 at 03:45 | #1

    Faydalı paylaşımlarından ötürü melih hocaya teşekkürler.
    Çok açıklayıcı olmuş.

  2. furkan kaynak
    Jul 31st, 2012 at 10:33 | #2

    peki hocam bu LazyInitializationException’ın çözümü nedir

  3. Aug 1st, 2012 at 12:30 | #3

    Entity tekrar managed duruma gecmeli bunu EntityManager terkar aktif hale getirerek yapabilirsiniz. Bir yontemde nesneyi sorgu olarak bir metoda gonderip sonclari almak olabilir.

  4. M.Ali K.
    Aug 14th, 2012 at 09:20 | #4

    Bu LazyInitializationException baya bela bir hata aslında. Örneğin sayfanız genellikle sorunsuz çalıştığı halde bazı zamanlarda (sunucu yük altındayken mesela) karşınıza yine de çıkabiliyor, yani programatik bişey olmasına da gerek yok. Seam’de bu hatanın bir de kardeşi var, sebepleri de zaman zaman birbirine yakın: Concurrent call to conversation!

  5. Aug 14th, 2012 at 10:36 | #5

    Belli durumlarda fetch query ler cekilebilir. Concurrent call conversation hatasi ayni conversation a birden fazla gonderimi sirasinda olabilir.

  6. M.Ali K.
    Aug 14th, 2012 at 11:54 | #6

    Melih Bey,
    Seam 3 ve java EE 6 hakkında ne düşünüyorsunuz. Seam 2′ye tam alışıyoruz derken artık bu bahsettiğim teknolojilerin gölgesinde kalacak gibi.
    Seam 2′deki bir çok fikir JAVA EE 6′da standart olmuş halde. Pure Java 6 mı yazmalı, Seam 3′e mi yönelmeli Seam ‘ye devam mı etmeli? Ne dersiniz?

  7. Aug 14th, 2012 at 16:37 | #7

    Seam dediginiz gibi Java EE 6 nin temellerini olusturdu. Ozellikle Web Beans yaklasimi ile CDI katmani Seam 3 ünde temeli oldu. Bu yuzden benim tavsiyem JBoss 7 üzerine kurulu bir seam 3 veya en azindan weld mimarisi olacaktir.

Leave a comment

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>