Skip to content

2. varlıkları JPA

2.1. Örnek 1 - Tek bir tablonun nesne temsili

2.1.1. [personne] tablosu

Bireyler hakkında bazı bilgileri depolamakla görevli tek bir tablo [personne] içeren bir veritabanını ele alalım:

 
ID
tablonun birincil anahtarı
VERSION
tablodaki satırın sürümü.
kişi her değiştirildiğinde, sürüm numarası bir artırılır.
NOM
kişinin adı
PRENOM
ad
DATENAISSANCE
doğum tarihi
MARIE
0 (bekar) veya 1 (evli) tamsayı
NBENFANTS
kişinin çocuk sayısı

2.1.2. [Personne] varlığı

Aşağıdaki çalışma ortamını ele alıyoruz:

JPA [5] katmanı, [7] veritabanının ilişkisel dünyası ile Java programları [4] tarafından işlenen nesne dünyası [3] arasında bir köprü kurmalıdır.231ZQX tarafından işlenen nesne dünyası arasında bir köprü kurmalıdır. Bu köprü, yapılandırma yoluyla kurulur ve bunu yapmanın iki yolu vardır:

  1. XML dosyaları kullanılarak. JDK 1.5 sürümünün ortaya çıkmasına kadar bu, neredeyse tek yöntemdi
  1. JDK 1.5 sürümünden itibaren Java anotasyonları kullanılarak

Bu belgede, neredeyse tamamen ikinci yöntemi kullanacağız.

Daha önce sunulan [personne] tablosunun [Personne] görüntüsü şu şekilde olabilir:


...

@SuppressWarnings("unused")
@Entity
@Table(name="Personne")
public class Personne implements Serializable{

    @Id
    @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @Column(name = "VERSION", nullable = false)
    @Version
    private int version;

    @Column(name = "NOM", length = 30, nullable = false, unique = true)
    private String nom;

    @Column(name = "PRENOM", length = 30, nullable = false)
    private String prenom;

    @Column(name = "DATENAISSANCE", nullable = false)
    @Temporal(TemporalType.DATE)
    private Date datenaissance;

    @Column(name = "MARIE", nullable = false)
    private boolean marie;

    @Column(name = "NBENFANTS", nullable = false)
    private int nbenfants;

    // yapıcılar
    public Personne() {
    }

    public Personne(String nom, String prenom, Date datenaissance, boolean marie,
            int nbenfants) {
        setNom(nom);
        setPrenom(prenom);
        setDatenaissance(datenaissance);
        setMarie(marie);
        setNbenfants(nbenfants);
    }

    // toString
    public String toString() {
...
    }

    // alıcı ve ayarlayıcılar
...
}

Yapılandırma, @Annotation Java anotasyonları kullanılarak yapılır. Java anotasyonları, derleme sırasında derleyici tarafından ya da yürütme sırasında özel araçlar tarafından kullanılır. Derleyiciye yönelik 3. satırdaki anotasyon dışında, buradaki tüm anotasyonlar kullanılan JPA uygulaması, Hibernate veya Toplink için tasarlanmıştır. Dolayısıyla bunlar yürütme sırasında kullanılacaktır. Bu anotasyonları yorumlayabilecek araçlar bulunmadığında, bu anotasyonlar göz ardı edilir. Böylece, yukarıdaki [Personne] sınıfı, JPA dışındaki bir bağlamda kullanılabilir.

T tablosuyla ilişkili bir C sınıfında JPA anotasyonlarının kullanımında iki durum ayırt edilmelidir:

  1. T tablosu zaten mevcutsa: JPA açıklamaları mevcut durumu yansıtmalıdır (sütunların adı ve tanımı, bütünlük kısıtlamaları, yabancı anahtarlar, birincil anahtarlar, ...)
  2. T tablosu mevcut değil ve C sınıfında bulunan anotasyonlara göre oluşturulacak.
  1. durum, yönetilmesi en kolay olanıdır. JPA anotasyonları yardımıyla, istediğimiz T tablosunun yapısını belirtiriz. 1. durum ise genellikle daha karmaşıktır. T tablosu, uzun zaman önce, herhangi bir JPA bağlamı dışında oluşturulmuş olabilir. Bu durumda, tablonun yapısı JPA'in ilişkisel/nesne köprüsüne uygun olmayabilir. Basitleştirmek için, C sınıfıyla ilişkili T tablosunun C sınıfının JPA anotasyonlarına göre oluşturulacağı 2. durumu ele alalım.

[Personne] sınıfındaki JPA anotasyonlarını inceleyelim:

  • 4. satır: @Entity anotasyonu, ilk ve zorunlu anotasyondur. Sınıfı tanımlayan satırın önüne yerleştirilir ve söz konusu sınıfın JPA kalıcılık katmanı tarafından yönetilmesi gerektiğini belirtir. Bu anotasyon yoksa, diğer tüm anotasyonlar göz ardı edilir.
  • 5. satır: @Table anotasyonu, sınıfın bir temsili olduğu veritabanı tablosunu belirtir. Ana argümanı, tablonun adını belirten name’dir. Bu argüman yoksa, tablo sınıfın adını alır; bu örnekte [Personne]. Dolayısıyla, örneğimizde @Table anotasyonu gereksizdir.
  • 8. satır: @Id anotasyonu, sınıftaki hangi alanın tablonun birincil anahtarının karşılığı olduğunu belirtmek için kullanılır. Bu anotasyon zorunludur. Burada, 11. satırdaki id alanının tablonun birincil anahtarının karşılığı olduğunu belirtir.
  • 9. satır: @Column anotasyonu, sınıfın bir alanı ile bu alanın karşılık geldiği tablodaki sütun arasında bağlantı kurmak için kullanılır. name özniteliği, tablodaki sütunun adını belirtir. Bu öznitelik belirtilmezse, sütun alanla aynı adı alır. Dolayısıyla örneğimizde name argümanı zorunlu değildi. nullable=false argümanı, alana bağlı sütunun NULL değerini alamayacağını ve dolayısıyla alanın mutlaka bir değere sahip olması gerektiğini belirtir.
  • 10. satır: @GeneratedValue açıklaması, birincil anahtarın SGBD tarafından otomatik olarak oluşturulduğunda nasıl oluşturulduğunu belirtir. Tüm örneklerimizde durum böyle olacaktır. Bu zorunlu değildir. Dolayısıyla, kişimizin birincil anahtar görevi gören bir öğrenci numarası olabilir ve bu numara SGBD tarafından oluşturulmayıp uygulama tarafından belirlenebilir. Bu durumda, @GeneratedValue açıklaması bulunmayacaktır. strategy argümanı, birincil anahtarın SGBD tarafından oluşturulduğunda nasıl oluşturulduğunu belirtir. SGBD'lerin hepsi birincil anahtar değerlerini oluşturmak için aynı tekniği kullanmaz. Örneğin:
Firebird
her ekleme işleminden önce çağrılan bir değer üreteci kullanır
SQL server
birincil anahtar alanı, Identity türü olarak tanımlanmıştır. Firebird'ün değer üretecine benzer bir sonuç elde edilir, ancak anahtar değeri satırın eklenmesinden sonra bilinir.
Oracle
SEQUENCE adlı bir nesne kullanılır; bu nesne de yine bir değer üreteci işlevi görür

JPA katmanı, değer üretecini oluşturmak için SGBD'lere göre farklı SQL komutları üretmelidir. Yapılandırma yoluyla, bu katmana yönetmesi gereken SGBD nesnesinin türü belirtilir. Böylece, bu SGBD nesnesinin birincil anahtar değerlerini oluşturmak için kullanılan olağan stratejinin ne olduğunu bilebilir. strategy = GenerationType.AUTO argümanı, JPA katmanına bu standart stratejiyi kullanması gerektiğini belirtir. Bu teknik, bu belgedeki tüm örneklerde kullanılan yedi SGBD için işe yaramıştır.

  • 14. satır: @Version açıklaması, tablonun aynı satırına eşzamanlı erişimleri yönetmek için kullanılan alanı belirtir.

[personne] tablosundaki aynı satıra eşzamanlı erişim sorununu anlamak için, bir web uygulamasının bir kişinin bilgilerinin güncellenmesine izin verdiğini varsayalım ve şu durumu inceleyelim:

T1 zamanında, bir kullanıcı U1, kişi P'nin düzenleme ekranına girer. Bu anda çocuk sayısı 0'dır. Bu sayıyı 1’e değiştirir ancak değişikliğini onaylamadan önce, U2 adlı bir kullanıcı aynı kişi P’nin düzenleme sayfasına girer. U1 henüz değişikliğini onaylamadığından, U2, ekranında çocuk sayısının 0 olduğunu görür. U2, kişi P'nin adını büyük harflerle yazar. Ardından U1 ve U2, bu sırayla değişikliklerini onaylar. U2’in yaptığı değişiklik geçerli olacaktır: veritabanında isim büyük harfe dönüştürülecek ve çocuk sayısı sıfır olarak kalacaktır; oysa U1, bu sayıyı 1 olarak değiştirdiğini sanmaktadır.

Kişi sürümü kavramı bu sorunu çözmemize yardımcı olur. Aynı kullanım örneğini ele alalım:

T1 zamanında, U1 adlı bir kullanıcı P adlı bir kişinin düzenleme ekranına girer. Bu anda çocuk sayısı 0’dır ve sürüm V1’tir. Kullanıcı çocuk sayısını 1’e değiştirir, ancak değişikliğini onaylamadan önce U2 adlı bir kullanıcı aynı kişi P’nin düzenleme sayfasına girer. U1 henüz değişikliğini onaylamadığından, U2, çocuk sayısını 0 ve sürümü V1 olarak görür. U2, P adlı kişinin adını büyük harflerle yazar. Ardından U1 ve U2, değişikliklerini bu sırayla onaylar. Bir değişikliği onaylamadan önce, P kişisini değiştiren kişinin, halihazırda kayıtlı olan P kişisiyle aynı sürüme sahip olup olmadığı kontrol edilir. Bu durum, U1 kullanıcısı için geçerlidir. Dolayısıyla, onun yaptığı değişiklik kabul edilir ve değiştirilen kişinin sürümü, kişide bir değişiklik yapıldığını belirtmek amacıyla V1'ten V2'e değiştirilir. U2'in yaptığı değişikliğin onaylanması sırasında, U2'in kişi P'nin V1 sürümüne sahip olduğu fark edilecektir; oysa şu anda bu kişinin sürümü V2'tir. Böylece U2 kullanıcıya, kendisinden önce birinin bu işlemi gerçekleştirdiği ve P kişisinin yeni sürümünden başlaması gerektiği söylenebilir. Kullanıcı bunu yapacak, artık bir çocuğu olan V2 sürümündeki P kişisini alacak, ismi büyük harflerle yazacak ve onaylayacaktır. Kayıtlı P kişisinin sürümü hâlâ V2 ise, yaptığı değişiklik kabul edilecektir. Sonuç olarak, U1 ve U2 tarafından yapılan değişiklikler dikkate alınacaktır; oysa sürüm kullanılmayan senaryoda, değişikliklerden biri kaybolmuş olacaktı.

İstemci uygulamasının [dao] katmanı, [Personne] sınıfının sürümünü kendisi yönetebilir. Bir P nesnesinde her değişiklik yapıldığında, bu nesnenin sürümü tabloda 1 artırılır. @Version anotasyonu, bu yönetimi JPA katmanına devretmeyi sağlar. İlgili alanın, örnekte olduğu gibi version olarak adlandırılmasına gerek yoktur. Herhangi bir isim verilebilir.

@Id ve @Version anotasyonlarına karşılık gelen alanlar, kalıcılık nedeniyle mevcut olan alanlardır. [Personne] sınıfının kalıcı hale getirilmesine gerek olmasaydı, bu alanlara ihtiyaç duyulmazdı. Dolayısıyla, bir nesnenin kalıcı hale getirilmesi gerekip gerekmediğine bağlı olarak temsilinin farklı olduğu görülmektedir.

  • 17. satır: Yine @Column anotasyonu, Personne sınıfının nom alanıyla ilişkili [personne] tablosundaki sütun hakkında bilgi vermek için kullanılmıştır. Burada iki yeni argüman bulunmaktadır:
    • unique=true, bir kişinin adının benzersiz olması gerektiğini belirtir. Bu, veritabanında [personne] tablosundaki NOM sütununa bir benzersizlik kısıtlamasının eklenmesiyle sonuçlanacaktır.
    • length=30, NOM sütununun karakter sayısını 30 olarak belirler. Bu, söz konusu sütunun türünün VARCHAR(30) olacağı anlamına gelir.
  • 24. satır: @Temporal ek açıklaması, tarih/saat türündeki bir sütuna/alana hangi SQL türünün atanacağını belirtmek için kullanılır. TemporalType.DATE türü, saat bilgisi içermeyen tek bir tarihi ifade eder. Diğer olası türler arasında, saati kodlamak için TemporalType.TIME ve tarih ile saati birlikte kodlamak için TemporalType.TIMESTAMP bulunur.

Şimdi [Personne] sınıfının kodunun geri kalanını inceleyelim:

  • 6. satır: Sınıf, Serializable arayüzünü uygular. Bir nesnenin sérialisation işlemi, onu bir bit dizisine dönüştürmektir. désérialisation ise bunun tersi işlemdir. Seri hale getirme / seri halinden çıkarma işlemleri, özellikle nesnelerin ağ üzerinden alışverişinin yapıldığı istemci / sunucu uygulamalarında kullanılır. İstemci veya sunucu uygulamaları, JVM tarafından şeffaf bir şekilde gerçekleştirilen bu işlemden haberdar değildir. Ancak bunun mümkün olabilmesi için, alışverişi yapılan nesnelerin sınıflarının Serializable anahtar sözcüğüyle "etiketlenmiş" olması gerekir.
  • 37. satır: Sınıfın bir oluşturucusu. id ve version alanlarının parametreler arasında yer almadığına dikkat edilmelidir. Aslında bu iki alan, uygulama tarafından değil, JPA katmanı tarafından yönetilir.
  • 51. satır ve sonrası: Sınıfın her bir alanının get ve set yöntemleri. JPA anotasyonlarının, alanların kendilerine değil, alanların get yöntemlerine yerleştirilebileceği unutulmamalıdır. Anotasyonların yerleştirildiği yer, JPA'in alanlara erişmek için kullanması gereken modu belirler:
    • eğer anotasyonlar alan düzeyinde yerleştirilirse, JPA alanlara doğrudan erişerek bunları okur veya yazar
    • eğer anotasyonlar get düzeyinde yer alıyorsa, JPA, alanlara okumak veya yazmak için get / set yöntemleri aracılığıyla erişecektir

Bir sınıftaki JPA anotasyonlarının konumunu belirleyen, @Id anotasyonunun konumudur. Alan düzeyinde yerleştirildiğinde alanlara doğrudan erişim, get düzeyinde yerleştirildiğinde ise get ve set aracılığıyla alanlara erişim belirtir. Diğer anotasyonlar da @Id anotasyonuyla aynı şekilde yerleştirilmelidir.

2.1.3. Testlerin Eclipse projesi

İlk denemelerimizi yukarıdaki [Personne] varlığıyla gerçekleştireceğiz. Bu denemeleri aşağıdaki mimariyle yürüteceğiz:

  • [7]: [Personne] varlığının anotasyonlarından ve [persistence.xml] adlı dosyada yapılan ek yapılandırmalardan oluşturulacak veritabanı
  • [5, 6]: Hibernate tarafından uygulanan bir JPA katmanı
  • [4]'te: [Personne] varlığı
  • [3]: konsol türü bir test programı

Çeşitli denemeler yapacağız:

  • bir Ant betiği ve Hibernate Tools aracını kullanarak BD şemasını oluşturmak
  • BD'i oluşturup birkaç veriyle başlatmak
  • BD'i çalıştırıp [personne] tablosu üzerinde dört temel işlemi gerçekleştirmek (ekleme, güncelleme, silme, sorgulama)

Gerekli araçlar şunlardır:

  • Eclipse ve 5.2. paragrafında açıklanan eklentileri.
  • <exemples>/hibernate/direct/personnes-entites klasöründe bulunan [hibernate-personnes-entites] projesi
  • Eklerde (5. paragraf ve sonrası) açıklanan çeşitli SGBD dosyaları.

Eclipse projesi şöyledir:

  • [1]: Eclipse proje klasörü
  • [2] dosyasında: Eclipse'e içe aktarılan proje (Dosya / İçe Aktar)
  • [3] dosyasında: testlerin konusu olan [Personne] nesnesi
  • [4]: test programları
  • [5]: [persistence.xml], JPA katmanının yapılandırma dosyasıdır
  • [6]'te: kullanılan kütüphaneler. Bunlar 1.5. paragrafında açıklanmıştır.
  • [8]: [Personne] varlığıyla ilişkili tabloyu oluşturmak için kullanılacak bir ant betiği
  • [9]: Kullanılan her bir SGBD için [persistence.xml] dosyaları
  • [10]: Kullanılan her bir SGBD için oluşturulan veritabanı şemaları

Bu öğeleri sırayla açıklayacağız.

2.1.4. [Personne] varlığı (2)

Daha önce [Personne] varlığı için yapılan açıklamaya küçük bir değişiklik getiriyor ve ek bilgi sunuyoruz:


package entites;

...

@SuppressWarnings({ "unused", "serial" })
@Entity
@Table(name="jpa01_personne")
public class Personne implements Serializable{

    @Id
    @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @Column(name = "VERSION", nullable = false)
    @Version
    private int version;

    @Column(name = "NOM", length = 30, nullable = false, unique = true)
    private String nom;

    @Column(name = "PRENOM", length = 30, nullable = false)
    private String prenom;

    @Column(name = "DATENAISSANCE", nullable = false)
    @Temporal(TemporalType.DATE)
    private Date datenaissance;

    @Column(name = "MARIE", nullable = false)
    private boolean marie;

    @Column(name = "NBENFANTS", nullable = false)
    private int nbenfants;

    // yapıcılar
    public Personne() {
    }

    public Personne(String nom, String prenom, Date datenaissance, boolean marie,
            int nbenfants) {
....
    }

    // toString
    public String toString() {
        return String.format("[%d,%d,%s,%s,%s,%s,%d]", getId(), getVersion(),
                getNom(), getPrenom(), new SimpleDateFormat("dd/MM/yyyy")
                        .format(getDatenaissance()), isMarie(), getNbenfants());
    }

    // alıcı ve ayarlayıcılar
...
}
  • 7. satır: [Personne] varlığıyla ilişkili tabloya [jpa01_personne] adını veriyoruz. Bu belgede, her zaman jpa olarak adlandırılan bir şemada çeşitli tablolar oluşturulacaktır. Bu öğreticinin sonunda, jpa şeması çok sayıda tablo içerecektir. Okuyucunun kolayca takip edebilmesi için, birbiriyle ilişkili tabloların hepsi aynı jpaxx_ önekini taşıyacaktır.
  • 45. satır: [toString] yöntemini kullanarak [Personne] nesnesini konsolda görüntülemek.

2.1.5. Veri erişim katmanının yapılandırılması

Yukarıdaki Eclipse projesinde, JPA katmanının yapılandırması [META-INF/persistence.xml] dosyası ile sağlanmaktadır:

Çalıştırma sırasında, uygulamanın classpath klasöründe [META-INF/persistence.xml] dosyası aranır. Eclipse projemizde, [/src] ve [1] klasörlerinde bulunan tüm içerikler, [/bin] ve [2] klasörlerine kopyalanır. Bu klasör, projenin classpath katmanının bir parçasıdır. Bu nedenle, JPA katmanı yapılandırıldığında [META-INF/persistence.xml] bulunacaktır.

Varsayılan olarak, Eclipse kaynak kodlarını projenin [/src] klasörüne değil, doğrudan projenin ana klasörüne yerleştirir. Tüm Eclipse projelerimiz, 5.2.1. paragrafında gösterildiği gibi, kaynak kodların [/src] klasöründe ve derlenmiş sınıfların [/bin] klasöründe bulunacak şekilde yapılandırılacaktır.

Projemizin [persistence.xml] dosyasında yapılan JPA katmanının yapılandırmasını inceleyelim:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
        <!--  sağlayıcı -->
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <properties>
            <!-- kalıcı sınıflar -->
            <property name="hibernate.archive.autodetection" value="class, hbm" />
            <!-- günlükler SQL
                <property name="hibernate.show_sql" value="true"/>
                <property name="hibernate.format_sql" value="true"/>
                <property name="use_sql_comments" value="true"/>
            -->
            <!-- bağlantı JDBC -->
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
            <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/jpa" />
            <property name="hibernate.connection.username" value="jpa" />
            <property name="hibernate.connection.password" value="jpa" />
            <!--  şemanın otomatik olarak oluşturulması -->
            <property name="hibernate.hbm2ddl.auto" value="create" />
            <!-- Lehçe -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
            <!--  özellikler DataSource c3p0 -->
            <property name="hibernate.c3p0.min_size" value="5" />
            <property name="hibernate.c3p0.max_size" value="20" />
            <property name="hibernate.c3p0.timeout" value="300" />
            <property name="hibernate.c3p0.max_statements" value="50" />
            <property name="hibernate.c3p0.idle_test_period" value="3000" />
        </properties>
    </persistence-unit>
</persistence>

Bu yapılandırmayı anlamak için, uygulamamızın veri erişim mimarisine tekrar göz atmamız gerekiyor:

  • [persistence.xml] dosyası, [4, 5, 6] katmanlarını yapılandıracaktır
  • [4]: JPA'in Hibernate uygulaması
  • [5]: Hibernate, bir bağlantı havuzu aracılığıyla veritabanına erişir. Bir bağlantı havuzu, SGBD ile açık olan bağlantıların bir rezervidir. Bir SGBD'e birden fazla kullanıcı erişirken, performans nedenleriyle aynı anda açık olan bağlantı sayısı N sınırını aşamaz. İyi yazılmış bir kod, SGBD ile en kısa sürede bir bağlantı açar: SQL komutlarını gönderir ve bağlantıyı kapatır. Veritabanıyla çalışması gerektiği her seferinde bunu tekrar tekrar yapar. Bir bağlantıyı açma/kapama maliyeti ihmal edilemez ve işte burada bağlantı havuzu devreye girer. Bu havuz, uygulamanın başlatılmasıyla birlikte N1 ile SGBD arasında N1 bağlantı açar. Uygulama, ihtiyaç duyduğunda açık bir bağlantı için bu havuza başvuracaktır. Uygulamanın bağlantıya artık ihtiyacı kalmadığında, tercihen mümkün olan en kısa sürede, bağlantı havuza iade edilecektir. Bağlantı kapatılmaz ve bir sonraki kullanıcı için kullanılabilir durumda kalır. Dolayısıyla, bağlantı havuzu, açık bağlantıların paylaşıldığı bir sistemdir.
  • [6]: SGBD tarafından kullanılan JDBC sürücüsü

Şimdi [persistence.xml] dosyasının yukarıdaki [4, 5, 6] katmanlarını nasıl yapılandırdığına bakalım:

  • 2. satır: XML dosyasının kök etiketi <persistence>'dir.
  • 3. satır: <persistence-unit>, bir kalıcılık birimini tanımlamak için kullanılır. Birden fazla kalıcılık birimi olabilir. Her birinin bir adı (name özniteliği) ve bir işlem türü (transaction-type özniteliği) vardır. Uygulama, kalıcılık birimine adıyla (burada jpa) erişecektir. RESOURCE_LOCAL işlem türü, uygulamanın SGBD ile işlemleri kendi başına yönettiğini gösterir. Burada da durum böyledir. Uygulama bir EJB3 kapsayıcısında çalıştığında, bu kapsayıcının işlem hizmetini kullanabilir. Bu durumda, transaction-type=JTA (Java Transaction API) olarak ayarlanacaktır. transaction-type özniteliği belirtilmediğinde varsayılan değer JTA'tir.
  • 5. satır: <provider> etiketi, uygulamanın kalıcılık katmanını başlatmasına olanak tanıyan [javax.persistence.spi.PersistenceProvider] arayüzünü uygulayan bir sınıfı tanımlamak için kullanılır. JPA / Hibernate uygulaması kullanıldığından, burada kullanılan sınıf bir Hibernate sınıfıdır.
  • 6. satır: <properties> etiketi, seçilen belirli provider'e özgü özellikleri tanımlar. Dolayısıyla Hibernate, Toplink, Kodo vb. seçildiğine göre farklı özellikler olacaktır. Aşağıdakiler Hibernate'e özgüdür.
  • 8. satır: Hibernate’ten, projedeki classpath’i taramasını ve @Entity anotasyonuna sahip sınıfları bulup yönetmesini ister. @Entity sınıfları, <persistence-unit> etiketinin hemen altında <class>nom_de_la_classe</class> etiketleriyle de tanımlanabilir. provider / Toplink örneğinde bunu yapacağız.
  • Burada yorum satırları olarak gösterilen 10-12. satırlar, Hibernate'in konsol günlüklerini yapılandırır:
  • 10. satır: Hibernate tarafından SGBD üzerinde verilen SQL komutlarının görüntülenip görüntülenmeyeceğini belirler. Bu, öğrenme aşamasında çok yararlıdır. İlişkisel/nesne köprüsü nedeniyle, uygulama kalıcı nesneler üzerinde çalışır ve bu nesnelere [persist, merge, remove] türünde işlemler uygular. Bu işlemler sırasında gerçekte hangi SQL komutlarının gönderildiğini bilmek oldukça ilginçtir. Bunları inceleyerek, kalıcı nesneler üzerinde bu tür bir işlem yapıldığında Hibernate'in hangi SQL komutlarını oluşturacağını yavaş yavaş tahmin etmeye başlarız ve ilişkisel/nesne köprüsü zihnimizde netleşmeye başlar.
  • 11. satır: Konsolda görüntülenen SQL komutları, okunmasını kolaylaştırmak için güzel bir şekilde biçimlendirilebilir
  • 12. satır: Görüntülenen SQL komutlarına ayrıca açıklamalar eklenecektir
  • 15-19. satırlar, JDBC katmanını (mimari içinde [6] katmanı) tanımlar:
  • 15. satır: JDBC sürücüsünün sınıfı SGBD'tir; burada MySQL5
  • 16. satır: Kullanılan veritabanının URL'si
  • 17. ve 18. satırlar: bağlantı kullanıcısı ve şifresi
  • Burada, eklerdeki 5.5. paragrafında açıklanan öğeleri kullanıyoruz. Okuyucunun MySQL5 ile ilgili bu bölümü okumasını öneririz.
  • 22. satır: Hibernate, karşısındaki SGBD'i bilmek zorundadır. Nitekim, tüm SGBD'ler özel SQL uzantılarına sahiptir; bu, birincil anahtar değerlerinin otomatik olarak oluşturulmasını yönetmenin kendine özgü bir yoludur, ... bu nedenle Hibernate, çalıştığı SGBD'i bilmek zorundadır; böylece ona, onun anlayabileceği SQL komutlarını gönderebilir. [MySQL5InnoDBDialect], işlemleri destekleyen InnoDB türündeki tablolara sahip SGBD MySQL5'i ifade eder.
  • 24-28. satırlar, c3p0 bağlantı havuzunu (mimari içinde [5] katmanı) yapılandırır:
  • 24. ve 25. satırlar: havuzdaki minimum (varsayılan 3) ve maksimum bağlantı sayısı (varsayılan 15). Varsayılan başlangıç bağlantı sayısı 3'tür.
  • 26. satır: İstemciden gelen bir bağlantı isteğinin bekleme süresinin milisaniye cinsinden maksimum süresi. Bu süre geçtikten sonra, c3p0 istemciye bir istisna döndürür.
  • 27. satır: BD'e erişmek için Hibernate, c3p0'ın önbelleğe alabileceği önceden hazırlanmış SQL komutlarını (PreparedStatement) kullanır. Bu, uygulamanın önbellekte zaten bulunan hazırlanmış bir SQL komutunu ikinci kez talep etmesi durumunda, bu komutun yeniden hazırlanmasına gerek kalmayacağı (bir SQL komutunun hazırlanması bir maliyete sahiptir) ve önbellekteki komutun kullanılacağı anlamına gelir. Burada, tüm bağlantılar dikkate alındığında önbelleğin içerebileceği hazırlanmış SQL emirlerinin maksimum sayısı belirtilir (hazırlanmış bir SQL emri bir bağlantıya aittir).
  • 28. satır: Bağlantıların geçerliliğinin milisaniye cinsinden kontrol sıklığı. Havuzdaki bir bağlantı çeşitli nedenlerle geçersiz hale gelebilir (JDBC sürücüsü, bağlantının çok uzun sürmesi nedeniyle bağlantıyı geçersiz kılar; JDBC sürücüsünde "hatalar" bulunur, ...).
  • 20. satır: Burada, kalıcılık biriminin başlatılması sırasında @Entity nesnelerinin veritabanı şemasının oluşturulmasını talep ediyoruz. Hibernate artık veritabanı tablolarını oluşturmak için SQL komutlarını göndermek üzere gerekli tüm araçlara sahiptir:
  • @Entity nesnelerinin yapılandırması, hangi tabloların oluşturulacağını belirlemesini sağlar
  • 15-18 ve 24-28. satırlar, SGBD ile bağlantı kurmasını sağlar
  • 22. satır, tabloları oluşturmak için hangi SQL lehçesinin kullanılacağını belirler

Böylece burada kullanılan [persistence.xml] dosyası, uygulamanın her yeni çalıştırılışında yeni bir veritabanı oluşturur. Tablolar, varsa silindikten (drop table) sonra yeniden oluşturulur (create table). Elbette bunun üretim ortamındaki bir veritabanında yapılmaması gerektiği unutulmamalıdır...

Testler, tabloların silinme/oluşturulma aşamasının başarısız olabileceğini göstermiştir. Bu durum özellikle, aynı test için JPA/Hibernate katmanından JPA/Toplink katmanına veya tersi yönde geçiş yapıldığında ortaya çıkmıştır. Aynı @Entity nesnelerinden yola çıkıldığında, her iki uygulama da tam olarak aynı tabloları, oluşturucuları, sıralamaları vb. üretmez ve bazen "drop / create" aşaması başarısız olur ve tabloları manuel olarak silmek zorunda kalınır. "Ekler" bölümünün 5. paragrafı ve sonrası, bu işi manuel olarak yapmak için kullanılabilecek uygulamaları açıklamaktadır. JPA/Hibernate uygulamasının, veritabanı içeriğinin bu ilk oluşturma aşamasında en verimli olduğu görülmüştür: çökme vakaları nadiren yaşanmıştır.

JPA / Hibernate katmanı tarafından kullanılan araçlar, 8. sayfadaki 1.5. paragrafta tanıtılan [jpa-hibernate] kütüphanesinde yer almaktadır. SGBD'e erişmek için gerekli olan JDBC sürücüleri, [jpa-divers] kütüphanesinde bulunmaktadır. Bu iki kütüphane, burada incelenen projenin classpath'ine eklenmiştir. Aşağıda bunların içeriğini hatırlatıyoruz:

2.1.6. Ant komut dosyası ile veritabanı oluşturma

Az önce gördüğümüz gibi, Hibernate, uygulamanın @Entity nesnelerinin veritabanı yapısını oluşturmak için araçlar sağlar. Hibernate şunları yapabilir:

  • Veritabanını oluşturan SQL komut dosyasını oluşturabilir. Bu durumda yalnızca [persistence.xml] dosyasındaki lehçe kullanılır.
  • [persistence.xml] dosyasında tanımlanan hedef veritabanında @Entity nesnelerinin şema tablolarını oluşturabilir. Bu durumda [persistence.xml] dosyasının tamamı kullanılır.

Veritabanı şemasını ve @Entity nesnelerinin tablosunu oluşturabilen bir Ant komut dosyası sunacağız. Bu komut dosyası bana ait değildir: [ref1] dosyasındaki benzer bir komut dosyasını temel almaktadır. Ant (Another Neat Tool), Java görevlerini toplu olarak çalıştıran bir araçtır. Ant komut dosyaları, bu konuda deneyimsiz kişiler için anlaşılması kolay değildir. Biz bunlardan sadece birini kullanacağız; şu anda üzerinde yorum yapacağımız komut dosyası şudur:

  • [1]: Bu öğreticinin örneklerinin dizin yapısı.
  • [2]: şu anda incelenmekte olan Eclipse projesinin [personnes-entites] klasörü
  • [3]: 1.5. paragrafta tanımlanan beş JAR kütüphanesini içeren <lib> klasörü.
  • [4]: İncelemeye alacağımız [ant-hibernate.xml] komut dosyasının görevlerinden biri için gerekli olan [hibernate-tools.jar] arşivi.
  • [5] içinde: Eclipse projesi ve [ant-hibernate.xml] komut dosyası
  • [6]'te: projenin [src] klasörü

[ant-hibernate.xml] [5] komut dosyası, <lib> [3] klasöründeki jar dosyalarını kullanacaktır, özellikle de [lib/hibernate] klasöründeki [hibernate-tools.jar] ve [4] arşivlerini kullanacaktır. Okuyucunun, [ant-hibernate.xml] betiğindeki [personnes-entites] ve [2] klasörlerinden [lib] klasörünü bulmak için şu yolu izlemesi gerektiğini görebilmesi amacıyla klasör yapısını yeniden oluşturduk: ../../../lib.

Şimdi [ant-hibernate.xml] komut dosyasını inceleyelim:


<project name="jpa-hibernate" default="compile" basedir=".">

    <!-- proje adı ve sürümü -->
    <property name="proj.name" value="jpa-hibernate" />
    <property name="proj.shortname" value="jpa-hibernate" />
    <property name="version" value="1.0" />

    <!-- Genel özellikler -->
    <property name="src.java.dir" value="src" />
    <property name="lib.dir" value="../../../lib" />
    <property name="build.dir" value="bin" />

    <!-- Projenin sınıf yolu -->
    <path id="project.classpath">
        <fileset dir="${lib.dir}">
            <include name="**/*.jar" />
        </fileset>
    </path>

    <!-- classpath'te bulunması gereken yapılandırma dosyaları-->
    <patternset id="conf">
        <include name="**/*.xml" />
        <include name="**/*.properties" />
    </patternset>

    <!-- Proje temizleme -->
    <target name="clean" description="Nettoyer le projet">
        <delete dir="${build.dir}" />
        <mkdir dir="${build.dir}" />
    </target>

    <!-- Proje derleme -->
<target name="compile" depends="clean">
        <javac srcdir="${src.java.dir}" destdir="${build.dir}" classpathref="project.classpath" />
    </target>

    <!-- Yapılandırma dosyalarını sınıf yoluna kopyalama -->
    <target name="copyconf">
        <mkdir dir="${build.dir}" />
        <copy todir="${build.dir}">
            <fileset dir="${src.java.dir}">
                <patternset refid="conf" />
            </fileset>
        </copy>
    </target>

    <!-- Hibernate Araçları -->
    <taskdef name="hibernatetool" classname="org.hibernate.tool.ant.HibernateToolTask" classpathref="project.classpath" />

    <!-- Veritabanının DDL dosyasını oluştur -->
    <target name="DDL" depends="compile, copyconf" description="Génération DDL base">

        <hibernatetool destdir="${basedir}">
            <classpath path="${build.dir}" />
            <!-- META-INF/persistence.xml'i kullan -->
            <jpaconfiguration />
            <!-- dışa aktarma -->
            <hbm2ddl drop="true" create="true" export="false" outputfilename="ddl/schema.sql" delimiter=";" format="true" />
        </hibernatetool>
    </target>

    <!-- Veritabanını oluştur -->
    <target name="BD" depends="compile, copyconf" description="Génération BD">

        <hibernatetool destdir="${basedir}">
            <classpath path="${build.dir}" />
            <!-- META-INF/persistence.xml kullan -->
            <jpaconfiguration />
            <!-- dışa aktarma -->
            <hbm2ddl drop="true" create="true" export="true" outputfilename="ddl/schema.sql" delimiter=";" format="true" />
        </hibernatetool>
    </target>
</project>
  • 1. satır: [ant] projesi "jpa-hibernate" olarak adlandırılmıştır. Bu proje, bir dizi görevi bir araya getirir; bunlardan biri varsayılan görevdir: burada "compile" adlı görev. Bir T görevini yürütmek için ant komut dosyası çağrılır. Görev belirtilmezse, varsayılan görev yürütülür. basedir="." ifadesi, komut dosyasında bulunan tüm göreceli yollar için başlangıç noktasının ant komut dosyasının bulunduğu klasör olduğunu belirtir; burada bu klasör <exemples>/hibernate/direct/personnes-entites'tir.
  • 3-11. satırlar: <property name="nomVariable" value="valeurVariable"/> etiketiyle komut dosyası değişkenlerini tanımlar. Değişken daha sonra komut dosyasında ${nomVariable} biçiminde kullanılabilir. Adlar isteğe bağlı olarak belirlenebilir. 9-11. satırlarda tanımlanan değişkenler üzerinde biraz duralım:
    • 9. satır: "src.java.dir" adlı bir değişken tanımlar (ad serbesttir); bu değişken, komut dosyasının ilerleyen kısımlarında Java kaynak kodlarını içeren klasörü belirtecektir. Değeri "src" olup, bu değer basedir özniteliği (1. satır) ile belirtilen klasöre göre göreceli bir yoldur. Dolayısıyla bu yol "./src" şeklindedir; burada ., <exemples>/hibernate/direct/personnes-entites klasörünü belirtir. Java kaynak kodları tam da <personnes-entites>/src klasöründe bulunur (yukarıdaki [6]'e bakınız).
    • 10. satır: "lib.dir" adlı bir değişken tanımlar; bu değişken, komut dosyasının ilerleyen kısımlarında, komut dosyasındaki Java görevlerinin ihtiyaç duyduğu jar dosyalarını içeren klasörü belirtecektir. Değeri ../../../lib, <exemples>/lib klasörünü belirtir (yukarıdaki [3]'e bakınız).
    • 11. satır: "build.dir" adlı bir değişken tanımlar; bu değişken, komut dosyasının ilerleyen kısımlarında .java kaynaklarının derlenmesi sonucu ortaya çıkan .class dosyalarının oluşturulacağı klasörü belirtecektir. "bin" değeri, <personnes-entites>/bin klasörünü belirtir. İncelediğimiz Eclipse projesinde <bin> klasörünün .class dosyalarının oluşturulduğu klasör olduğunu daha önce açıklamıştık. Ant da aynı şekilde çalışacaktır.
    • 14-18. satırlar: <path> etiketi, classpath görevlerinin kullanması gereken ant öğelerini tanımlamak için kullanılır. Burada, "project.classpath" yolu (ad serbesttir), <exemples>/lib klasör ağacındaki tüm .jar dosyalarını bir araya getirir.
    • 21-24. satırlar: <patternset> etiketi, bir dizi dosyayı isim kalıplarıyla belirtmek için kullanılır. Burada, "conf" olarak adlandırılan patternset, .xml veya .properties uzantısına sahip tüm dosyaları belirtir. Bu patternset ise <src> klasöründeki .xml ve .properties dosyalarını belirtmek için kullanılacaktır (persistence.xml, log4j.properties) (bkz. [6]) içindeki .xml ve .properties dosyalarını belirtmek için kullanılacak ve bunlar uygulamanın yapılandırma dosyalarıdır. Bazı görevlerin yürütülmesi sırasında, bu dosyaların projenin classpath dizininde yer alabilmesi için <bin> klasörüne kopyalanması gerekir. Bu durumda, bunları belirtmek için patternset conf kullanılacaktır.
    • 27-30. satırlar: <target> etiketi, komut dosyasındaki bir görevi belirtir. Bu, karşılaştığımız ilk görevdir. Bundan önceki tüm kısımlar, ant komut dosyasının çalışma ortamının yapılandırmasına aittir. Görev, clean olarak adlandırılmıştır. Bu görev iki aşamada yürütülür: <bin> klasörü silinir (28. satır) ve ardından yeniden oluşturulur (29. satır).
    • 33-35. satırlar: script'in varsayılan görevi olan compile görevi (1. satır). Bu görev, clean görevine bağlıdır (depends özniteliği). Bu, "compile" görevini çalıştırmadan önce ant'in "clean" görevini, yani c.a.d'i çalıştırması gerektiği anlamına gelir. <bin> klasörünü temizler. Burada "compile" görevinin amacı, <src> klasöründeki Java kaynak kodlarını derlemektir.
    • 34. satır: Java derleyicisinin üç parametreyle çağrılması:
      • srcdir: Java kaynak kodlarını içeren klasör; burada <src> klasörü
      • destdir: Oluşturulan .class dosyalarının kaydedileceği klasör; burada <bin> klasörü
      • classpathref: derleme için kullanılacak sınıf yolu; burada <lib> klasörünün dizin yapısındaki tüm jar dosyaları
  • (devamı)
    • 38-45. satırlar: <src> dosyasındaki tüm .xml ve .properties dosyalarını <bin> klasörüne kopyalamayı amaçlayan copyconf görevi.
    • 48. satır: <taskdef> etiketiyle bir görevin tanımlanması. Bu tür bir görev, komut dosyasında başka yerlerde yeniden kullanılmak üzere tasarlanmıştır. Bu, kodlamayı kolaylaştırır. Görev, komut dosyasının çeşitli yerlerinde kullanıldığından, <taskdef> etiketiyle bir kez tanımlanır ve daha sonra ihtiyaç duyulduğunda adı aracılığıyla yeniden kullanılır.
      • Görevin adı hibernatetool'tir (name özniteliği).
      • Sınıfı, classname özniteliği ile tanımlanır. Burada belirtilen sınıf, daha önce bahsettiğimiz [hibernate-tools.jar] arşivinde bulunur.
      • classpathref özniteliği, ant'e önceki sınıfı nerede arayacağını belirtir
  • (devam)
    • 51-60. satırlar, burada ilgilendiğimiz görevle, yani Eclipse projemizdeki @Entity nesnelerinin görüntü veritabanı şemasının oluşturulmasıyla ilgilidir.
      • 51. satır: Görevin adı DDL’tir (Data Definition Language’a benzer şekilde, bir veritabanındaki nesnelerin oluşturulmasıyla ilişkili SQL gibi). Bu görev, sırasıyla compile ve copyconf görevlerine bağlıdır. Dolayısıyla DDL görevi, sırasıyla clean, compile ve copyconf görevlerinin yürütülmesini tetikleyecektir. DDL görevi başladığında, <bin> klasörü .java kaynak kodlarının .class dosyalarını (özellikle @Entity nesnelerini) ve JPA / Hibernate katmanını yapılandıran [META-INF/persistence.xml] dosyasını içerir.
      • 53-59. satırlar: 48. satırda tanımlanan [hibernatetool] görevi çağrılır. Bu göreve, 48. satırda zaten tanımlanmış olanların yanı sıra birçok parametre aktarılır:
      • 53. satır: Görev tarafından üretilen sonuçların çıkış klasörü, geçerli klasör olacaktır.
      • 54. satır: Görevin classpath klasörü <bin> klasörü olacaktır.
      • 56. satır: [hibernatetool] görevine, çalışma ortamını nasıl öğrenebileceğini belirtir: <jpaconfiguration/> etiketi, göreve JPA ortamında olduğunu ve bu nedenle classpath klasöründe bulunan [META-INF/persistence.xml] dosyasını kullanması gerektiğini bildirir.
      • 58. satır, veritabanının oluşturulma koşullarını belirler: drop=true, tabloların oluşturulmasından önce SQL drop table komutlarının verilmesi gerektiğini belirtir; create=true, veritabanını oluşturma komutlarının bulunduğu SQL metin dosyasının oluşturulması gerektiğini belirtir; outputfilename ise bu SQL dosyasının adını belirtir - burada schema.sql, Eclipse projesinin <ddl> klasöründe yer alır; export=false, oluşturulan komutların SQL bir SGBD bağlantısında çalıştırılmaması gerektiğini belirtir. Bu nokta önemlidir: bu, görevi yürütmek için hedef SGBD'in başlatılmasına gerek olmadığı anlamına gelir. delimiter, oluşturulan şemada iki SQL komutunu ayıran karakteri belirler; format=true ise oluşturulan metne temel bir biçimlendirme uygulanmasını ister.
  • (devam)
    • 63-72. satırlar, BD adlı görevi tanımlar. Bu görev, önceki DDL göreviyle aynıdır; tek farkı, bu sefer veritabanını oluşturmasıdır (70. satırdaki export="true" ifadesi). Görev, [persistence.xml]'te bulunan bilgilerle SGBD üzerinde bir bağlantı açar, burada SQL şemasını uygular ve veritabanını oluşturur. Dolayısıyla, BD görevini çalıştırmak için SGBD görevinin başlatılmış olması gerekir.

2.1.7. nt DDL görevinin çalıştırılması

[ant-hibernate.xml] komut dosyasını çalıştırmak için öncelikle Eclipse içinde bazı ayarlamalar yapmamız gerekiyor.

  • [1]'te: [External Tools]'i seçin
  • [2]'te: yeni bir yapılandırma oluşturun ant
  • [3]'e: ant yapılandırmasına bir ad verin
  • [5]'e: ant komut dosyasını [4] düğmesi ile seçin
  • [6]: Değişiklikleri uygulayın
  • [7]'e: ant ve DDL yapılandırmaları oluşturuldu
  • [8]'te: JRE sekmesinde, kullanılacak JRE tanımlanır. [10] alanı normalde Eclipse tarafından kullanılan JRE ile önceden doldurulur. Dolayısıyla bu panelde normalde yapılacak bir şey yoktur. Bununla birlikte, ant komut dosyasının <javac> derleyicisini bulamadığı bir durumla karşılaştım. Bu derleyici, bir JRE (Java Runtime Environment) içinde değil, bir JDK (Java Development Kit) içinde yer almaktadır. Eclipse’in ant aracı, bu derleyiciyi JAVA_HOME ortam değişkeni aracılığıyla bulur (Başlat / Denetim Masası / Performans ve Bakım / Sistem / Gelişmiş sekmesi / Ortam Değişkenleri düğmesi) aracılığıyla [A] adıyla bu derleyiciyi bulur. Bu değişken tanımlanmamışsa, ant'in <javac> derleyicisini bulmasını sağlamak için [10]'e JRE yerine JDK değerini girebilirsiniz. Bu derleyici, JRE ve [B] ile aynı klasörde bulunur. [9] düğmesini kullanarak, mevcut JRE ve [C] dosyaları arasından JDK dosyasını bildireceğiz; böylece daha sonra [10] dosyasında bu dosyayı seçebileceğiz.
  • [12]'te: [Targets] sekmesinde, DDL görevini seçin. Böylece, ant olarak adlandırdığımız yapılandırma (DDL [7]), DDL [12] adlı görevin yürütülmesine karşılık gelecektir; bu görev, bildiğimiz üzere, uygulamanın @Entity nesnelerinin görüntü veritabanının DDL şemasını oluşturur.
  • [13]'e dönüştürülür: yapılandırma onaylanır
  • [14]'te: yapılandırma yürütülür

[console] görünümünde, ant görevinin yürütülmesine ait günlükler görüntülenir DDL:


Buildfile: C:\data\2006-2007\eclipse\dvp-jpa\hibernate\direct\personnes-entites\ant-hibernate.xml
clean:
   [delete] Deleting directory C:\data\2006-2007\eclipse\dvp-jpa\hibernate\direct\personnes-entites\bin
    [mkdir] Created dir: C:\data\2006-2007\eclipse\dvp-jpa\hibernate\direct\personnes-entites\bin
compile:
    [javac] Compiling 3 source files to C:\data\2006-2007\eclipse\dvp-jpa\hibernate\direct\personnes-entites\bin
copyconf:
     [copy] Copying 2 files to C:\data\2006-2007\eclipse\dvp-jpa\hibernate\direct\personnes-entites\bin
DDL:
[hibernatetool] Executing Hibernate Tool with a JPA Configuration
[hibernatetool] 1. task: hbm2ddl (Generates database schema)
[hibernatetool] drop table if exists jpa01_personne;
[hibernatetool] create table jpa01_personne (
[hibernatetool] ID integer not null auto_increment,
[hibernatetool] VERSION integer not null,
[hibernatetool] NOM varchar(30) not null unique,
[hibernatetool] PRENOM varchar(30) not null,
[hibernatetool] DATENAISSANCE date not null,
[hibernatetool] MARIE bit not null,
[hibernatetool] NBENFANTS integer not null,
[hibernatetool] primary key (ID)
[hibernatetool] ) ENGINE=InnoDB;
BUILD SUCCESSFUL
Total time: 5 seconds
  • DDL görevinin adının [hibernatetool] (satır 10) olduğu ve bu görevin clean (satır 2), compile (satır 5) ve copyconf (satır 7) görevlerine bağlı olduğunu hatırlayalım.
  • 10. satır: [hibernatetool] görevi, JPA yapılandırmasına ait [persistence.xml] dosyasını kullanır
  • 11. satır: [hbm2ddl] görevi, veritabanının DDL şemasını oluşturacaktır
  • 12-22. satırlar: veritabanının DDL şeması

Hatırlayacağınız üzere, [hbm2ddl] görevinden DDL şemasını belirli bir konumda oluşturmasını istemiştik:


<hbm2ddl drop="true" create="true" export="true" outputfilename="ddl/schema.sql" delimiter=";" format="true" />
  • 74. satır: şema, ddl/schema.sql dosyasında oluşturulmalıdır. Kontrol edelim:
  • [1] dosyasında: ddl/schema.sql dosyası mevcut (ağaç yapısını yenilemek için F5 komutunu çalıştırın)
  • [2] dosyasına bakalım: içeriği. Bu, MySQL5 veritabanının şemasıdır. JPA katmanının yapılandırma dosyası olan [persistence.xml] dosyası, aslında bir SGBD ve MySQL5 dosyasını belirtmişti (aşağıdaki 8. satır):


            <!-- bağlantı JDBC -->
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
...
            <!--  şemanın otomatik olarak oluşturulması -->
            <property name="hibernate.hbm2ddl.auto" value="create" />
            <!-- Lehçe -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
            <!--  özellikler DataSource c3p0 -->
...

Burada oluşturulan nesne/ilişkisel köprüyü, @Entity Personne nesnesinin yapılandırmasını ve oluşturulan DDL şemasını inceleyerek ele alalım:

Birkaç noktaya dikkat çekelim:

  • A1-B1: A1'te belirtilen tablo adı, B1'te kullanılan tablo adıyla aynıdır. B1'te, create'ten önce gelen drop'e dikkat edilmelidir.
  • A2-B2: birincil anahtarın oluşturulma şeklini gösterir. A2'te belirtilen AUTO modu, MySQL5'e özgü autoincrement özniteliğiyle sonuçlanmıştır. Birincil anahtarın oluşturulma modu genellikle SGBD'e özgüdür.
  • A3-B3: SQL bit türünü gösterir; bu, bir boolean Java türünü temsil etmek için MySQL5'e özgüdür.

Bu testi başka bir SGBD ile tekrar yapalım:

  • [conf] [1] klasörü, çeşitli SGBD türleri için [persistence.xml] dosyalarını içerir. Örneğin Oracle'a ait [2] dosyasını alıp, [META-INF] ve [3] klasörlerine önceki dosyanın yerine yerleştirin. İçeriği şu şekildedir:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
        <!--  sağlayıcı -->
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <properties>
            <!-- Kalıcı sınıflar -->
            <property name="hibernate.archive.autodetection" value="class, hbm" />
            <!-- günlükler SQL
                <property name="hibernate.show_sql" value="true"/>
                <property name="hibernate.format_sql" value="true"/>
                <property name="use_sql_comments" value="true"/>
            -->
            <!-- bağlantı JDBC -->
            <property name="hibernate.connection.driver_class" value="oracle.jdbc.OracleDriver" />
            <property name="hibernate.connection.url" value="jdbc:oracle:thin:@localhost:1521:xe" />
            <property name="hibernate.connection.username" value="jpa" />
            <property name="hibernate.connection.password" value="jpa" />
            <!--  şemanın otomatik olarak oluşturulması -->
            <property name="hibernate.hbm2ddl.auto" value="create" />
            <!-- Lehçe -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.OracleDialect" />
            <!--  özellikler DataSource c3p0 -->
            <property name="hibernate.c3p0.min_size" value="5" />
            <property name="hibernate.c3p0.max_size" value="20" />
            <property name="hibernate.c3p0.timeout" value="300" />
            <property name="hibernate.c3p0.max_statements" value="50" />
            <property name="hibernate.c3p0.idle_test_period" value="3000" />
        </properties>
    </persistence-unit>
</persistence>

Okuyucunun, özellikle JDBC yapılandırmasını anlamak için eklerdeki Oracle ile ilgili bölümü (5.7. paragraf) okumasını öneririz.

Burada asıl önemli olan sadece 25. satırdır: Hibernate’e, artık SGBD’in bir Oracle SGBD olduğu belirtilmektedir. DDL ant görevinin çalıştırılması, yukarıdaki [4] sonucunu verir. Oracle şemasının MySQL5 şemasından farklı olduğu dikkatinizi çekecektir. Bu, JPA'in en güçlü yanlarından biridir: geliştiricinin bu ayrıntılarla ilgilenmesine gerek kalmaz, bu da geliştirmelerinin taşınabilirliğini önemli ölçüde artırır.

2.1.8. 'in BD görevini yürütmesi

Hatırlanacağı üzere, ant adlı görev, BD adıyla anılır ve ant ile DDL görevleriyle aynı işlevi görür, ancak buna ek olarak veritabanını da oluşturur. Bu nedenle SGBD görevinin başlatılması gerekir. SGBD ve MySQL5 görevlerini ele alacağız; okuyucuyu, [conf/mysql5/persistence.xml] dosyasını [src/META-INF] klasörüne kopyalamaya davet ediyoruz. Görevin çalışmasını kontrol etmek için, SQL Explorer eklentisini (bkz. paragraf 5.2.6) kullanarak, BD jpa dosyasının durumunu, ant ve BD görevlerinin yürütülmesinden önce ve sonra kontrol edeceğiz.

Öncelikle, BD görevini çalıştırmak için yeni bir ant yapılandırması oluşturmamız gerekiyor. Okuyucunun, 2.1.7. paragrafında DDL yapılandırması için açıklanan adımları izlemesi önerilir. Yeni ant yapılandırmasının adı BD olacaktır:

  • [1]'e: DDL adlı önceki yapılandırma kopyalanır
  • [2] olarak: yeni yapılandırmaya BD adı verilir. Bu yapılandırma, veritabanını fiziksel olarak oluşturan ant, BD ve [3] görevlerini yürütür.
  • Bu işlem tamamlandıktan sonra, SGBD ve MySQL5 görevlerini başlatın (5.5. paragraf).

Şimdi, SQL Explorer eklentisini kullanarak SGBD tarafından yönetilen veritabanlarını inceleyeceğiz. Okuyucu, gerekirse önceden bu eklentiyi öğrenmelidir (bkz. paragraf 5.2.6).

  • [1]: SQL Explorer [Window / Open Perspective / Other] görünümünü açıyoruz
  • [2]: Gerekirse bir [mysql5-jpa] bağlantısı oluşturulur (bkz. paragraf 5.5.5, sayfa 252) ve açılır
  • [3]: jpa / jpa ile oturum açılır
  • [4]: MySQL5'e bağlanılır.
  • [5]'te: BD jpa'sında yalnızca bir tablo vardır: [articles]
  • [6]'te: ant BD görevinin yürütülmesi başlatılır. [SQL Explorer] perspektifinde olduğumuz için, görevin günlüklerini gösteren [Console] görünümünü göremiyoruz. Bu [Window / Show View / ...] görünümünü görüntüleyebilir veya Java perspektifi [Window / Open Perspective / ...]'e geri dönebiliriz.
  • [7]'te: BD görev tamamlandıktan sonra, gerekirse [SQL Explorer] perspektifine geri dönüp BD jpa ağacını yenileyebiliriz.
  • [8]'te: Oluşturulmuş olan [jpa01_personne] tablosu görülmektedir.

Okuyucunun, diğer SGBD dosyalarıyla birlikte bu BD dosyasını yeniden oluşturması istenir. İzlenecek prosedür şöyledir:

  • [conf/<sgbd>/persistence.xml] dosyasını, <sgbd>'nin test edilen SGBD olduğu [src/META-INF] klasörüne kopyalayın
  • Eklerdeki talimatları izleyerek <sgbd> dosyasını çalıştırın
  • SQL Explorer görünümünde, <sgbd>'ye bir bağlantı oluşturun. Bu işlem, eklerde her bir SGBD dosyası için de açıklanmıştır
  • önceki testleri tekrarlayın

Bu noktaya geldiğimizde, bir dizi kazanım elde ettik:

  • nesne-ilişkisel köprü kavramını daha iyi anlıyoruz. Burada bu köprü Hibernate tarafından gerçekleştirilmiştir. Daha sonra Toplink'i kullanacağız.
  • Bu nesne/ilişkisel köprünün iki yerde yapılandırıldığını biliyoruz:
  • @Entity nesnelerinde; burada nesnelerin alanları ile BD tablosundaki sütunlar arasındaki bağlantılar belirtilir
  • [META-INF/persistence.xml] dosyasında, burada JPA uygulamasına nesne/ilişkisel köprünün iki bileşeni hakkında bilgi verilir: @Entity nesneleri (nesne) ve veritabanı (ilişkisel).
  • DDL ve BD adlı iki Ant görevi oluşturduk; bu görevler, henüz herhangi bir Java kodu yazmadan önce önceki yapılandırmaya göre veritabanını oluşturmamızı sağlıyor.

Artık uygulamamızın JPA katmanı doğru şekilde yapılandırıldığına göre, API ve JPA katmanlarını Java kodu ile keşfetmeye başlayabiliriz.

2.1.9. : Bir uygulamanın kalıcılık bağlamı

Bir JPA istemcisinin çalışma ortamını biraz açıklayalım:

JPA [2] katmanının, bir nesne-ilişkisel köprü oluşturduğunu biliyoruz. Bu nesne / ilişkisel köprü kapsamında JPA katmanı tarafından yönetilen nesnelerin tümüne "kalıcılık bağlamı" denir. Kalıcılık bağlamındaki verilere erişmek için, bir JPA [1] istemcisi, JPA [2] katmanını kullanmalıdır:

  1. bir nesne oluşturabilir ve JPA katmanından bu nesneyi kalıcı hale getirmesini isteyebilir. Böylece nesne, kalıcılık bağlamının bir parçası olur.
  2. [JPA] katmanından mevcut bir kalıcı nesneye ait bir referans isteyebilir.
  3. JPA katmanından alınan kalıcı bir nesneyi değiştirebilir.
  4. JPA katmanından, kalıcılık bağlamından bir nesnenin silinmesini isteyebilir.

JPA katmanı, istemciye [EntityManager] adlı bir arayüz sunar; bu arayüz, adından da anlaşılacağı gibi, kalıcılık bağlamındaki @Entity nesnelerini yönetmeye olanak tanır. Aşağıda, bu arayüzün başlıca yöntemlerini sunuyoruz:

void persist(Object entity)
entity'i kalıcılık bağlamına yerleştirir
void remove(Object entity)
entity'i kalıcılık bağlamından çıkarır
<T> T merge(T entity)
kalıcılık bağlamı tarafından yönetilmeyen istemciden gelen entity nesnesini
aynı birincil anahtara sahip kalıcılık bağlamındaki entity nesnesiyle birleştirir.
Sonuç olarak, kalıcılık bağlamındaki entity nesnesi elde edilir.
<T> T find(Class<T> entityClass,
 Object primaryKey)
komutu, veritabanında aranan bir nesneyi
veritabanında birincil anahtarı aracılığıyla aranan bir nesneyi kalıcılık bağlamına yerleştirir. Nesnenin T türü,
JPA katmanının hangi tabloya sorgu yapması gerektiğini belirler.
Bu şekilde oluşturulan kalıcı nesne, istemciye döndürülür.
Query createQuery(String queryText)
, bir JPQL
(Java Persistence Query Language) sorgusundan bir Query nesnesi oluşturur. JPQL sorgusu,
SQL sorgusuna benzer, ancak tablolar yerine nesneler sorgulanır.
Query createNativeQuery(String queryText)
öncekine benzer bir yöntemdir; tek fark, queryText'in
SQL komutudur, JPQL değildir.
Query createNamedQuery(String name)
createQuery ile aynı yöntemdir, ancak JPQL ve queryText komutları
bir yapılandırma dosyasına taşınmış ve bir adla ilişkilendirilmiştir.
Yöntemin parametresi işte bu isimdir.

Bir EntityManager nesnesinin yaşam döngüsü, uygulamanın yaşam döngüsüyle mutlaka aynı olmak zorunda değildir. Bu nesnenin bir başlangıcı ve bir sonu vardır. Dolayısıyla, bir JPA istemcisi, farklı EntityManager nesneleriyle art arda çalışabilir. Bir EntityManager nesnesine bağlı kalıcılık bağlamı, bu nesneyle aynı yaşam döngüsüne sahiptir. İkisi birbirinden ayrılamaz. Bir EntityManager nesnesi kapatıldığında, kalıcılık bağlamı gerekirse veritabanıyla senkronize edilir ve ardından ortadan kalkar. Kalıcılık bağlamına yeniden sahip olmak için yeni bir EntityManager oluşturulması gerekir.

JPA istemcisi, aşağıdaki komutla bir EntityManager nesnesi ve dolayısıyla bir kalıcılık bağlamı oluşturabilir:


        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
  • javax.persistence.Persistence, EntityManager nesnelerinin fabrikasını (factory) elde etmeyi sağlayan statik bir sınıftır. Bu fabrika, belirli bir kalıcılık birimiyle ilişkilidir. Hatırlanacağı üzere, [META-INF/persistence.xml] yapılandırma dosyası, kalıcılık birimlerinin tanımlanmasına olanak tanır ve bu birimlerin birer adı vardır:

    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">

Yukarıda, kalıcılık biriminin adı jpa'tir. Bu birimle birlikte, kendisine özgü bir yapılandırma da gelir; özellikle de birlikte çalıştığı SGBD. [Persistence.createEntityManagerFactory("jpa")] komutu, jpa adlı kalıcılık birimiyle ilişkili kalıcılık bağlamlarını yönetmek üzere tasarlanmış EntityManager nesnelerini sağlayabilen EntityManagerFactory türünde bir nesne fabrikası oluşturur. Bir EntityManager nesnesinin ve dolayısıyla bir kalıcılık bağlamının elde edilmesi, EntityManagerFactory nesnesinden aşağıdaki şekilde gerçekleştirilir:

        EntityManager em = emf.createEntityManager();

[EntityManager] arayüzündeki aşağıdaki yöntemler, kalıcılık bağlamının yaşam döngüsünü yönetmeye olanak tanır:

void close()
kalıcılık bağlamı kapatılır. Kalıcılık bağlamının veritabanıyla senkronizasyonunu zorlar:
  • bağlamdaki bir nesne veritabanında mevcut değilse, bir işlemle veritabanına eklenir (SQL INSERT)
  • bağlamdaki bir nesne veritabanında mevcutsa ve okunduktan sonra değiştirilmişse, değişikliği kalıcı hale getirmek için SQL UPDATE işlemi gerçekleştirilir
  • bağlamdaki bir nesne, üzerinde gerçekleştirilen bir remove işlemi sonucunda "silinmiş" olarak işaretlenmişse, onu veritabanından silmek için SQL DELETE işlemi gerçekleştirilir.
void clear()
Kalıcılık bağlamındaki tüm nesneler silinir, ancak bağlam kapatılmaz.
void flush()
kalıcılık bağlamı, close() için açıklanan şekilde veritabanıyla senkronize edilir

JPA istemcisi, önceki [EntityManager].flush yöntemini kullanarak kalıcılık bağlamının veritabanıyla senkronizasyonunu zorlayabilir. Senkronizasyon açık veya örtük olabilir. İlk durumda, senkronizasyon yapmak istediğinde flush işlemlerini gerçekleştirmek istemli olarak istemciye aittir; aksi takdirde senkronizasyon, daha sonra belirteceğimiz belirli zamanlarda gerçekleştirilir. Senkronizasyon modu, [EntityManager] arayüzünün aşağıdaki yöntemleri tarafından yönetilir:

void setFlushMode(FlushModeType flushMode)
flushmode için iki olası değer vardır:
FlushModeType.AUTO (varsayılan): Senkronizasyon, veritabanına yapılan her SELECT isteğinden önce gerçekleşir.
FlushModeType.COMMIT: Senkronizasyon, veritabanındaki işlemlerin tamamlanmasının ardından gerçekleştirilir.
FlushModeType getFlushMode()
mevcut senkronizasyon modunu gösterir

Özetleyelim. Varsayılan mod olan FlushModeType.AUTO modunda, kalıcılık bağlamı aşağıdaki zamanlarda veritabanıyla senkronize edilir:

  1. veritabanındaki her SELECT işlemi öncesinde
  2. veritabanındaki bir işlemin sonunda
  3. kalıcılık bağlamında bir flush veya close işlemi sonrasında

FlushModeType.COMMIT modunda da durum aynıdır; ancak 1 numaralı işlem gerçekleşmez. JPA katmanıyla etkileşimin normal modu, işlem modudur. İstemci, bir işlem içinde kalıcılık bağlamı üzerinde çeşitli işlemler gerçekleştirir. Bu durumda, kalıcılık bağlamının veritabanıyla senkronize edildiği anlar, AUTO modunda yukarıdaki durum 1 ve 2’dir; COMMIT modunda ise yalnızca durum 2’dir.

Son olarak, Query arayüzündeki API'e değinelim; bu arayüz, kalıcılık bağlamında JPQL komutları göndermek veya verileri bulmak için doğrudan veritabanına SQL komutları göndermek için kullanılır. Query arayüzü şu şekildedir:

Yukarıdaki 1 ila 4 numaralı yöntemleri kullanmamız gerekecektir:

  • 1 - getResultList yöntemi, birden fazla nesne döndüren bir SELECT yöntemini yürütür. Bu nesneler, bir List nesnesi içinde elde edilir. Bu nesne bir arayüzdür. Bu arayüz, L listesindeki öğeleri aşağıdaki biçimde taramaya olanak tanıyan bir Iterator nesnesi sunar:

        Iterator iterator = L.iterator();
        while (iterator.hasNext()) {
             // listenin geçerli öğesini temsil eden iterator.next() nesnesini işleme
...
}

L listesi, bir for nesnesiyle de kullanılabilir:


        for (Object o : L) {
             // o nesnesini kullan
}
  • 2 - getSingleResult yöntemi, tek bir nesne döndüren bir JPQL / SQL / SELECT komutunu yürütür.
  • 3 - executeUpdate yöntemi, SQL güncelleme veya silme komutunu yürütür ve işlemden etkilenen satır sayısını döndürür.
  • 4 - setParameter(String, Object) yöntemi, parametreli bir JPQL komutundaki adlandırılmış bir parametreye değer atamaya olanak tanır
  • 5 - setParameter(int, Object) yöntemi, ancak parametre adıyla değil, JPQL komutundaki konumuyla belirtilir.

2.1.10. İlk JPA istemcisi

Projeye Java perspektifinden tekrar bakalım:

 

Artık bu proje hakkında, şu anda incelediğimiz [src/tests] klasörünün içeriği hariç, hemen hemen her şeyi biliyoruz. Klasör, JPA katmanının iki test programını içeriyor:

  • [InitDB.java], veritabanındaki [jpa01_personne] tablosuna birkaç satır ekleyen bir programdır. Bu programın kodu, bize JPA katmanının ilk öğelerini sağlayacaktır.
  • [Main.java], [jpa01_personne] tablosu üzerinde CRUD işlemlerini gerçekleştiren bir programdır. Bu programın kodunu incelemek, kalıcılık bağlamının temel kavramlarını ve bu bağlamdaki nesnelerin yaşam döngüsünü ele almamızı sağlayacaktır.

2.1.10.1. Kod

[InitDB.java] programının kodu şöyledir:


package tests;

import java.text.ParseException;
import java.text.SimpleDateFormat;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

import entites.Personne;

public class InitDB {
    // sabitler
    private final static String TABLE_NAME = "jpa01_personne";

    public static void main(String[] args) throws ParseException {
        // Kalıcılık birimi
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
        // kalıcılık biriminden bir EntityManagerFactory nesnesini al
        EntityManager em = emf.createEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // Kişiler tablosundaki öğeleri sil
        em.createNativeQuery("delete from " + TABLE_NAME).executeUpdate();
        // iki kişi oluştur
        Personne p1 = new Personne("Martin", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
        Personne p2 = new Personne("Durant", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
        // kişilerin kalıcılığı
        em.persist(p1);
        em.persist(p2);
        // kişileri görüntüleme
        System.out.println("[personnes]");
        for (Object p : em.createQuery("select p from Personne p order by p.nom asc").getResultList()) {
            System.out.println(p);
        }
        // işlemin sonu
        tx.commit();
        // EntityManager'in sonu
        em.close();
        // EntityManagerFactory'in sonu
        emf.close();
        // günlük
        System.out.println("terminé ...");
    }
}

Bu kodu, 2.1.9. paragrafında açıklananlar ışığında okumak gerekir.

  • 19. satır: jpa kalıcılık birimi (persistence.xml'te tanımlanmıştır) için bir EntityManagerFactory emf nesnesi talep edilmektedir. Bu işlem normalde bir uygulamanın ömrü boyunca yalnızca bir kez gerçekleştirilir.
  • 21. satır: Bir kalıcılık bağlamını yönetmek için bir EntityManager em nesnesi talep edilmektedir.
  • 23. satır: Bir işlemi yönetmek için bir Transaction nesnesi talep edilir. Burada, kalıcılık bağlamındaki işlemlerin bir işlem içinde gerçekleştirildiğini hatırlatmak isteriz. Bunun zorunlu olmadığını göreceğiz, ancak zorunlu tutulmadığında sorunlarla karşılaşılabilir. Uygulama bir EJB3 konteynerinde çalışıyorsa, kalıcılık bağlamı üzerindeki işlemler her zaman bir işlem içinde gerçekleştirilir.
  • 24. satır: işlem başlar
  • 26. satır: "jpa01_personne" (nativeQuery) tablosunda bir SQL delete komutu yürütülür. Bunu, tablodaki tüm içeriği boşaltmak ve böylece [InitDB] uygulamasının yürütme sonucunu daha iyi görebilmek için yapıyoruz
  • 28-29. satırlar: p1 ve p2 adlı iki Personne nesnesi oluşturulur. Bunlar normal nesnelerdir ve şimdilik kalıcılık bağlamıyla hiçbir ilgisi yoktur. Kalıcılık bağlamı açısından Hibernate, bu nesnelerin geçici (transient) durumda olduğunu belirtir; bu, kalıcılık bağlamı tarafından yönetilen kalıcı (persistent) nesnelerle karşılaştırmak içindir. Biz ise, henüz kalıcılık bağlamı tarafından yönetilmediklerini belirtmek için "kalıcı olmayan nesneler" (Fransızca olmayan bir ifade) ve kalıcılık bağlamı tarafından yönetilenler için "kalıcı nesneler" terimlerini kullanacağız. Üçüncü bir nesne kategorisi de vardır: daha önce kalıcı olan ancak kalıcılık bağlamı kapatılmış "ayrılmış (detached)" nesneler. İstemci bu tür nesnelere referanslar tutabilir; bu da, kalıcılık bağlamı kapatıldığında bu nesnelerin mutlaka yok edilmeyeceğini açıklar. Bu durumda, bu nesnelerin “ayrılmış” durumda olduğu söylenir. [EntityManager].merge işlemi, bu nesneleri yeni oluşturulan bir kalıcılık bağlamına yeniden bağlamaya olanak tanır.
  • 31-32. satırlar: p1 ve p2 kişileri, [EntityManager].persist işlemi ile kalıcılık bağlamına eklenir. Böylece kalıcı nesneler haline gelirler.
  • 35-37. satırlar: JPQL komutu "select p from Personne p order by p.nom asc" şeklinde yürütülür. Personne bir tablo değildir (tablonun adı jpa01_personne'tir), ancak tabloyla ilişkili @Entity nesnesidir. Burada, veritabanında bir SQL emri değil, kalıcılık bağlamında bir JPQL (Java Persistence Query Language) sorgusu söz konusudur. Bununla birlikte, jpa01_personne tablosunun yerini alan Personne nesnesi dışında, sözdizimleri aynıdır. Bir for döngüsü, select sorgusunun sonucu olan (kişi) listesini tarayarak her bir öğeyi konsolda görüntüler. Burada, 31-32. satırlarda kalıcılık bağlamına eklenen öğelerin tabloda gerçekten mevcut olup olmadığını doğrulamaya çalışıyoruz. Şeffaf bir şekilde, kalıcılık bağlamı ile veritabanı arasında bir senkronizasyon gerçekleşecektir. Nitekim, bir select sorgusu gönderilecek ve bunun senkronizasyonun yapıldığı durumlardan biri olduğu belirtilmişti. Dolayısıyla tam da bu anda, arka planda JPA / Hibernate, iki kişiyi jpa01_personne tablosuna ekleyecek olan SQL ve insert komutlarını gönderecektir. persist işlemi bunu yapmamıştı. Bu işlem, veritabanında herhangi bir etki yaratmadan nesneleri kalıcılık bağlamına ekler. Asıl işlemler senkronizasyonlar sırasında gerçekleşir; burada, veritabanındaki select işleminden hemen önce.
  • 39. satır: 24. satırda başlatılan işlem sonlandırılır. Yine bir senkronizasyon gerçekleşecektir. Son senkronizasyondan bu yana kalıcılık bağlamında herhangi bir değişiklik olmadığından burada hiçbir işlem yapılmayacaktır.
  • 41. satır: Kalıcılık bağlamı kapatılır.
  • 43. satır: EntityManager fabrikası kapatılır.

2.1.10.2. kodun yürütülmesi

  • SGBD'i çalıştır MySQL5
  • gerekirse conf/mysql5/persistence.xml dosyasını META-INF/persistence.xml dizinine yerleştirin
  • [InitDB] uygulamasını çalıştırın

Aşağıdaki sonuçlar elde edilir:

  • [1]'te: Java perspektifinde konsol ekranı. Beklenen sonuç elde edilir.
  • [2]'te: 2.1.8. paragrafında açıklandığı gibi SQL Explorer perspektifiyle [jpa01_personne] tablosunun içeriği kontrol edilir. İki nokta dikkat çekmektedir:
    • ID birincil anahtarı, bizim müdahale etmemize gerek kalmadan oluşturulmuştur
    • aynı durum sürüm numarası için de geçerlidir. İlk sürümün numarasının 0 olduğu görülmektedir.

Elimizde, JPA kültürünün ilk unsurları var. Bir tabloya veri eklemeyi başardık. Bu kazanımları temel alarak ikinci testi yazacağız, ancak önce günlüklerden bahsedelim.

2.1.11. Hibernate günlüklerini devreye almak

SQL katmanı / Hibernate tarafından veritabanına gönderilen JPA komutlarını öğrenmek mümkündür. JPA katmanının, SQL komutlarını kendi başına yazmış bir geliştirici kadar verimli olup olmadığını görmek için bu komutları incelemek ilginçtir.

JPA / Hibernate ile, SQL günlükleri [persistence.xml] dosyasında kontrol edilebilir:


            <!-- Kalıcı sınıflar -->
            <property name="hibernate.archive.autodetection" value="class, hbm" />
            <!-- günlükler SQL
                <property name="hibernate.show_sql" value="true"/>
                <property name="hibernate.format_sql" value="true"/>
                <property name="use_sql_comments" value="true"/>
            -->
            <!-- bağlantı JDBC -->
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />

  • 4-6. satırlar: SQL günlükleri şu anda etkinleştirilmemişti. 3. ve 7. satırlardaki yorum etiketini kaldırarak bunları etkinleştiriyoruz.

[InitDB] uygulamasını yeniden çalıştırıyoruz. Konsol çıktıları şu şekilde oluyor:

Hibernate: 
    delete 
    from
        jpa01_personne
Hibernate: 
    insert 
    into
        jpa01_personne
        (VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS) 
    values
        (?, ?, ?, ?, ?, ?)
Hibernate: 
    insert 
    into
        jpa01_personne
        (VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS) 
    values
        (?, ?, ?, ?, ?, ?)
[personnes]
Hibernate: 
    select
        personne0_.ID as ID0_,
        personne0_.VERSION as VERSION0_,
        personne0_.NOM as NOM0_,
        personne0_.PRENOM as PRENOM0_,
        personne0_.DATENAISSANCE as DATENAIS5_0_,
        personne0_.MARIE as MARIE0_,
        personne0_.NBENFANTS as NBENFANTS0_ 
    from
        jpa01_personne personne0_ 
    order by
        personne0_.NOM asc
[2,0,Durant,Sylvie,05/07/2001,false,0]
[1,0,Martin,Paul,31/01/2000,true,2]
terminé ...
  • 2-4. satırlar: Aşağıdaki komuttan kaynaklanan SQL delete komutu:

        // kişi tablosundaki öğeleri sil
        em.createNativeQuery("delete from " + TABLE_NAME).executeUpdate();
  • satır 5-18: Aşağıdaki komutlardan kaynaklanan SQL ve insert komutları:

        // Kişilerin kalıcılığı
        em.persist(p1);
        em.persist(p2);
  • 21-32. satırlar: Aşağıdaki komuttan gelen SQL select komutu:

        for (Object p : em.createQuery("select p from Personne p order by p.nom asc").getResultList()) 

Ara konsol çıktıları alınırsa, Java kodundaki bir I komutunun SQL günlüklerinin yazılmasının, I komutu yürütüldüğünde gerçekleştiği görülecektir. Bu, görüntülenen SQL komutunun o anda veritabanında yürütüldüğü anlamına gelmez. Aslında bu komut, kalıcılık bağlamının veritabanıyla bir sonraki senkronizasyonu sırasında yürütülmek üzere önbelleğe alınır.

[src/log4j.properties] dosyası aracılığıyla başka günlükler de elde edilebilir:

  • [1] dosyasında, [log4j.properties] dosyası, [log4j-1.2.13.jar] [2] adlı LOG4j (Logs for Java) adlı aracın [log4j-1.2.13.jar] ve [2] arşivleri tarafından işlenir. Eclipse projesinin [src] klasörüne yerleştirildiğinde, [log4j.properties] dosyasının [3] projesinin [bin] klasörüne otomatik olarak kopyalanacağını biliyoruz. Bu işlem tamamlandıktan sonra, dosya artık projenin classpath klasöründe bulunur ve [2] arşivi bu klasörden dosyayı alacaktır.

[log4j.properties] dosyası, Hibernate’in belirli günlüklerini kontrol etmemizi sağlar. Önceki çalıştırmalarda içeriği şu şekildeydi:


# Günlük mesajlarını stdout'a yönlendir
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

# Kök günlüğü seçeneği
log4j.rootLogger=ERROR, stdout

# Hibernate günlük kaydı seçenekleri (INFO yalnızca başlangıç mesajlarını gösterir)
#log4j.logger.org.hibernate=INFO

# JDBC bağlama parametresi çalışma zamanı argümanlarını günlüğe kaydet
#log4j.logger.org.hibernate.type=DEBUG

LOG4j hakkında ciddi bir şekilde bilgi edinmeye hiç zaman ayırmadığım için bu yapılandırma hakkında fazla yorum yapmayacağım.

  • 1-8. satırlar, karşılaştığım tüm log4j.properties dosyalarında yer almaktadır
  • 10-14. satırlar ise Hibernate örneklerindeki log4j.properties dosyalarında yer almaktadır.
  • 11. satır: Hibernate’in genel günlüklerini denetler. Satır yorumlanmış olduğundan, bu günlükler burada devre dışı bırakılmıştır. Birkaç farklı günlük seviyesi mevcuttur: INFO (Hibernate'in ne yaptığına dair genel bilgiler), WARN (Hibernate olası bir sorun hakkında uyarı verir), DEBUG (ayrıntılı günlükler). INFO seviyesi en az ayrıntılı, DEBUG modu ise en ayrıntılıdır. 11. satırı etkinleştirmek, özellikle uygulamanın başlatılması sırasında Hibernate'in ne yaptığını öğrenmenizi sağlar. Bu genellikle ilgi çekicidir.
  • 12. satır, etkinleştirildiğinde, parametreleri belirlenmiş SQL sorgularının yürütülmesi sırasında fiilen kullanılan argümanları öğrenmenizi sağlar.

Öncelikle 14. satırın yorum satırını kaldırarak başlayalım


# JDBC bağlama parametresi çalışma zamanı argümanlarını günlüğe kaydet
log4j.logger.org.hibernate.type=DEBUG

ve [InitDB]'i yeniden çalıştıralım. Bu değişikliğin getirdiği yeni günlükler şunlardır (kısmi görünüm):

Hibernate: 
    insert 
    into
        jpa01_personne
        (VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS) 
    values
        (?, ?, ?, ?, ?, ?)
07:20:03,843 DEBUG IntegerType:80 - binding '0' to parameter: 1
07:20:03,843 DEBUG StringType:80 - binding 'Durant' to parameter: 2
07:20:03,843 DEBUG StringType:80 - binding 'Sylvie' to parameter: 3
07:20:03,843 DEBUG DateType:80 - binding '05 juillet 2001' to parameter: 4
07:20:03,843 DEBUG BooleanType:80 - binding 'false' to parameter: 5
07:20:03,843 DEBUG IntegerType:80 - binding '0' to parameter: 6
  • 8-10. satırlar, [log4j.properties] komut dosyasının 14. satırının etkinleştirilmesiyle ortaya çıkan yeni günlükleridir. Bu satırlar, 2-7. satırlardaki parametreli sorgunun ? formal parametrelerine atanan 5 değeri gösterir. Böylece, VERSION sütununa 0 değerinin atanacağını görüyoruz (8. satır).

Şimdi [log4j.properties] dosyasının 11. satırını etkinleştirelim:

# Hibernate günlüğü seçenekleri (INFO yalnızca başlangıç mesajlarını gösterir)
log4j.logger.org.hibernate=INFO

ve [InitDB]'i yeniden çalıştıralım:

07:50:23,937  INFO Version:15 - Hibernate EntityManager 3.2.0.CR3
07:50:23,968  INFO Version:15 - Hibernate Annotations 3.2.0.CR3
07:50:23,984  INFO Environment:500 - Hibernate 3.2.0.cr5
07:50:23,984  INFO Environment:533 - hibernate.properties not found
07:50:23,984  INFO Environment:667 - Bytecode provider name : cglib
07:50:24,000  INFO Environment:584 - using JDK 1.4 java.sql.Timestamp handling
07:50:24,375  INFO AnnotationBinder:387 - Binding entity from annotated class: entites.Personne
07:50:24,421  INFO EntityBinder:340 - Bind entity entites.Personne on table jpa01_personne
07:50:24,609  INFO C3P0ConnectionProvider:50 - C3P0 using driver: com.mysql.jdbc.Driver at URL: jdbc:mysql://localhost:3306/jpa
07:50:24,609  INFO C3P0ConnectionProvider:51 - Connection properties: {user=jpa, password=****, autocommit=true, release_mode=auto}
07:50:24,609  INFO C3P0ConnectionProvider:54 - autocommit mode: true
07:50:25,296  INFO SettingsFactory:81 - RDBMS: MySQL, version: 5.0.37-community-nt
07:50:25,296  INFO SettingsFactory:82 - JDBC driver: MySQL-AB JDBC Driver, version: mysql-connector-java-3.1.9 ( $Date: 2005/05/19 15:52:23 $, $Revision: 1.1.2.2 $ )
07:50:25,312  INFO Dialect:141 - Using dialect: org.hibernate.dialect.MySQL5InnoDBDialect
07:50:25,312  INFO TransactionFactoryFactory:34 - Transaction strategy: org.hibernate.transaction.JDBCTransactionFactory
07:50:25,312  INFO TransactionManagerLookupFactory:33 - No TransactionManagerLookup configured (in JTA environment, use of read-write or transactional second-level cache is not recommended)
07:50:25,328  INFO SettingsFactory:134 - Automatic flush during beforeCompletion(): disabled
07:50:25,328  INFO SettingsFactory:138 - Automatic session close at end of transaction: disabled
07:50:25,328  INFO SettingsFactory:145 - JDBC batch size: 15
07:50:25,328  INFO SettingsFactory:148 - JDBC batch updates for versioned data: disabled
07:50:25,328  INFO SettingsFactory:153 - Scrollable result sets: enabled
07:50:25,328  INFO SettingsFactory:161 - JDBC3 getGeneratedKeys(): enabled
07:50:25,328  INFO SettingsFactory:169 - Connection release mode: auto
07:50:25,328  INFO SettingsFactory:193 - Maximum outer join fetch depth: 2
07:50:25,328  INFO SettingsFactory:196 - Default batch fetch size: 1
07:50:25,328  INFO SettingsFactory:200 - Generate SQL with comments: disabled
07:50:25,328  INFO SettingsFactory:204 - Order SQL updates by primary key: disabled
07:50:25,328  INFO SettingsFactory:369 - Query translator: org.hibernate.hql.ast.ASTQueryTranslatorFactory
07:50:25,328  INFO ASTQueryTranslatorFactory:24 - Using ASTQueryTranslatorFactory
07:50:25,328  INFO SettingsFactory:212 - Query language substitutions: {}
07:50:25,328  INFO SettingsFactory:217 - JPA-QL strict compliance: enabled
07:50:25,328  INFO SettingsFactory:222 - Second-level cache: enabled
07:50:25,328  INFO SettingsFactory:226 - Query cache: disabled
07:50:25,328  INFO SettingsFactory:356 - Cache provider: org.hibernate.cache.NoCacheProvider
07:50:25,328  INFO SettingsFactory:241 - Optimize cache for minimal puts: disabled
07:50:25,328  INFO SettingsFactory:250 - Structured second-level cache entries: disabled
07:50:25,343  INFO SettingsFactory:270 - Echoing all SQL to stdout
07:50:25,343  INFO SettingsFactory:277 - Statistics: disabled
07:50:25,343  INFO SettingsFactory:281 - Deleted entity synthetic identifier rollback: disabled
07:50:25,343  INFO SettingsFactory:296 - Default entity-mode: pojo
07:50:25,468  INFO SessionFactoryImpl:161 - building session factory
07:50:25,750  INFO SessionFactoryObjectFactory:82 - Not binding factory to JNDI, no JNDI name configured
07:50:25,765  INFO SchemaExport:154 - Running hbm2ddl schema export
07:50:25,765  INFO SchemaExport:179 - exporting generated schema to database
07:50:25,968  INFO SchemaExport:196 - schema export complete
Hibernate: 
    delete 
    from
        jpa01_personne
Hibernate: 
    ... 

Bu günlükleri inceleyince birçok ilginç bilgi elde ediliyor:

  • 7. satır: Hibernate, bulduğu bir @Entity sınıfının adını gösterir
  • 8. satır: [Personne] sınıfının [jpa01_personne] tablosuna bağlanacağını belirtir
  • 9. satır: Kullanılacak C3P0 bağlantı havuzu, JDBC sürücüsünün adı ve yönetilecek veritabanının URL'sini belirtir
  • 10. satır: JDBC bağlantısının diğer özelliklerini belirtir: sahip, commit türü, ...
  • 14. satır: SGBD ile iletişim kurmak için kullanılan lehçe
  • 15. satır: Kullanılan işlem türü. JDBCTransactionFactory, uygulamanın işlemlerini kendi başına yönettiğini gösterir. Uygulama, kendi işlem hizmetini sağlayan bir EJB3 kapsayıcısında çalışmaz.
  • Sonraki satırlar, daha önce karşılaşmadığımız Hibernate yapılandırma seçenekleriyle ilgilidir. Konuyla ilgilenen okuyucuların Hibernate belgelerini incelemeleri önerilir.
  • 37. satır: SQL komutları konsolda görüntülenecektir. Bu, [persistence.xml]'te talep edilmiştir:

            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.format_sql" value="true" />
            <property name="use_sql_comments" value="true" />
  • 43-45. satırlar: Veritabanı şeması SGBD ve c.a.d dosyalarına aktarılır. Veritabanı boşaltılır ve ardından yeniden oluşturulur. Bu mekanizma, [persistence.xml]'te yapılan yapılandırmadan kaynaklanmaktadır (aşağıdaki 4. satır):

            ...
            <property name="hibernate.connection.password" value="jpa" />
            <!--  şemanın otomatik olarak oluşturulması -->
            <property name="hibernate.hbm2ddl.auto" value="create" />
            <!-- Lehçe -->
            ...

Bir uygulama, anlaşılmayan bir Hibernate istisnası nedeniyle "çökerse", durumu daha net görebilmek için öncelikle [log4j.properties] içinde DEBUG modunda Hibernate günlüklerini etkinleştirmeliyiz:


# Kök günlüğü seçeneği
log4j.rootLogger=ERROR, stdout

# Hibernate günlüğü seçenekleri (INFO yalnızca başlangıç mesajlarını gösterir)
log4j.logger.org.hibernate=DEBUG

Bu belgenin devamında, konsol ekranının daha okunaklı olması için günlükler varsayılan olarak devre dışı bırakılmıştır.

2.1.12. 'i keşfetmek JPQL / HQL Hibernate konsolu ile

Not: Bu bölüm için Hibernate Tools eklentisi gereklidir (5.2.5. paragraf).

[InitDB] uygulama kodunda, bir JPQL sorgusu kullandık. JPQL (Java Persistence Query Language), kalıcılık bağlamına sorgu göndermek için kullanılan bir dildir. Karşılaştığımız sorgu şuydu:

select p from Personne p order by p.nom asc

Bu sorgu, @Entity [Personne] ile ilişkili tablodaki tüm öğeleri seçiyor ve bunları ada göre artan sırada döndürüyordu. Yukarıdaki sorguda, p.nom, [Personne] sınıfının p örneğinin ad alanıdır. Dolayısıyla bir JPQL sorgusu, doğrudan veritabanındaki tablolar üzerinde değil, kalıcılık bağlamındaki @Entity nesneleri üzerinde çalışır. JPA katmanı ise bu JPQL sorgusunu, birlikte çalıştığı SGBD sınıfına uygun bir SQL sorgusuna çevirecektir. Dolayısıyla, bir JPA / Hibernate uygulaması, SGBD ve MySQL5 ile bağlantılıysa, önceki JPQL sorgusu aşağıdaki SQL sorgusuna dönüştürülür:

select
  personne0_.ID as ID0_,
  personne0_.VERSION as VERSION0_,
  personne0_.NOM as NOM0_,
  personne0_.PRENOM as PRENOM0_,
  personne0_.DATENAISSANCE as DATENAIS5_0_,
  personne0_.MARIE as MARIE0_,
  personne0_.NBENFANTS as NBENFANTS0_ 
 from
  jpa01_personne personne0_ 
 order by
  personne0_.NOM asc

JPA katmanı, doğru SQL siparişini oluşturmak için @Entity nesnesi [Personne]'in yapılandırmasını kullandı. Burada uygulanan, nesne-ilişkisel köprüdür.

[Hibernate Tools] eklentisi (5.2.5. paragraf), "Hibernate Konsolu" adlı bir araç sunar; bu araç,

  • JPQL komutlarını veya HQL (Hibernate Sorgu Dili) üst kümesini kalıcılık bağlamında
  • sonuçları almak
  • veritabanında yürütülen SQL komutunun eşdeğerini öğrenmek

Hibernate konsolu, JPQL dilini öğrenmek ve JPQL / SQL köprüsüne aşina olmak için çok değerli bir araçtır. JPA'in, Hibernate veya Toplink gibi ORM araçlarından büyük ölçüde esinlendiği bilinmektedir. JPQL, Hibernate'in HQL diline çok yakındır ancak tüm özelliklerini içermemektedir. Hibernate konsolunda, konsolda normal şekilde yürütülecek ancak JPQL dilinin bir parçası olmayan ve dolayısıyla bir JPA istemcisinde kullanılamayacak HQL komutları verilebilir. Böyle bir durum olduğunda bunu belirteceğiz.

Mevcut Eclipse projemiz için bir Hibernate konsolu oluşturalım:

  • [1]: [Hibernate Console] perspektifine geçiyoruz (Window / Open Perspective / Other)
  • [2]: [Hibernate Configuration] penceresinde
  • [4] düğmesini kullanarak, Hibernate yapılandırmasının oluşturulacağı Java projesini seçiyoruz. Projenin adı [3]'te görüntülenir.
  • [5]'te, bu yapılandırmaya istediğimiz adı veriyoruz. Burada [3]'i kullandık.
  • [6]'te, JPA yapılandırmasını kullandığımızı belirtiriz; böylece araç, [META-INF/persistence.xml] dosyasını işlemesi gerektiğini bilir
  • [7]'te: bu [META-INF/persistence.xml] dosyasında jpa adlı kalıcılık biriminin kullanılması gerektiğini belirtiyoruz.
  • [8] dosyasında yapılandırmayı onaylıyoruz.

Sonraki adımda, SGBD dosyasının çalıştırılması gerekir. Burada söz konusu olan dosya MySQL5'tir.

  • [1]'te: Oluşturulan yapılandırma üç dallı bir ağaç yapısı sergiler
  • [2]'te: [Configuration] dalı, konsolun kendini yapılandırmak için kullandığı nesneleri listeler: burada @Entity Personne.
  • [3]'te: Session Factory, EntityManager'teki JPA kavramına yakın bir Hibernate kavramıdır. [Configuration] dalındaki nesneler sayesinde nesne-ilişkisel köprüyü kurar. [3]'te kalıcılık bağlamının nesneleri sunulmaktadır; burada yine @Entity Personne yer almaktadır.
  • [4]'te: [persistence.xml]'te bulunan yapılandırma aracılığıyla erişilen veritabanı yer almaktadır. Burada [jpa01_personne] tablosu bulunur.
  • [1]'te, HQL adlı bir düzenleyici oluşturulur
  • HQL düzenleyicisinde,
    • [2]'te, birden fazla varsa kullanılacak Hibernate yapılandırmasını seçin
    • [3]'te, çalıştırmak istediğimiz JPQL komutunu yazıyoruz
    • [4]'te, komutu çalıştırın
  • [5]'te, sorgunun sonuçları [Hibernate Query Result] penceresinde görüntülenir. Burada iki sorunla karşılaşılabilir:
    • hiçbir sonuç alınmaz (hiçbir satır). Hibernate konsolu, [persistence.xml]'in içeriğini kullanarak SGBD ile bir bağlantı kurmuştur. Ancak bu yapılandırmada veritabanını boşaltmayı belirten bir özellik vardır:

            <property name="hibernate.hbm2ddl.auto" value="create" />

Bu nedenle, yukarıdaki JPQL komutunu tekrar çalıştırmadan önce [InitDB] uygulamasını yeniden çalıştırmanız gerekir.

  • (devam)
    • [Hibernate Query Result] penceresi görünmüyor. Bu pencereyi [Window / Show View / ...] komutuyla çağırıyoruz

[Hibernate Dynamic SQL preview] penceresi (aşağıda [1] olarak gösterilmiştir), şu anda yazmakta olduğumuz JPQL komutunu yürütmek için çalıştırılacak olan SQL isteğini görmemizi sağlar. JPQL komutunun sözdizimi doğru olduğunda, bu pencerede ilgili SQL komutu görünür:

  • [2]'e geldiğimizde, önceki HQL komutunu sileriz
  • [3] komutunda, yeni bir komut çalıştırılır
  • [4] komutunda sonuç
  • [5]'te, temel alınarak yürütülen SQL komutu

HQL düzenleyicisi, HQL komutlarının yazılmasına yardımcı olur:

  • [1]'te: Düzenleyici, p'in bir Personne nesnesi olduğunu anladığında, yazarken bize p alanlarını önerebilir.
  • [2] örneğinde: HQL komutu hatalıdır. where p.marie=true yazılmalıdır.
  • [3] örneğinde: hata, [SQL Preview] penceresinde bildirilir.

Okuyucuyu, veritabanında başka HQL / JPQL komutları vermeye davet ediyoruz.

2.1.13. İkinci bir istemci: JPA

Projeye Java açısından tekrar bakalım:

 
  • [InitDB.java], veritabanındaki [jpa01_personne] tablosuna birkaç satır ekleyen bir programdır. Bu programın kodunu incelememiz, API ve JPA ile ilgili ilk bilgileri edinmemizi sağladı.
  • [Main.java], [jpa01_personne] tablosu üzerinde CRUD işlemlerini gerçekleştiren bir programdır. Bu programın kodunu incelemek, kalıcılık bağlamının temel kavramlarını ve bu bağlamdaki nesnelerin yaşam döngüsünü yeniden gözden geçirmemizi sağlayacaktır.

2.1.13.1. Kodun yapısı

[Main.java], her biri JPA'in belirli bir yönünü göstermeyi amaçlayan bir dizi testi birbirine bağlayacaktır:

 

[main] yöntemi

  • , test1'ten test11'e kadar olan yöntemleri sırayla çağırır. Bu yöntemlerin her birinin kodunu ayrı ayrı sunacağız.
  • Ayrıca, özel yardımcı yöntemler kullanır: clean, dump, log, getEntityManager, getNewEntityManager.

main yöntemini ve yardımcı yöntemler olarak adlandırılan yöntemleri aşağıda sunuyoruz:


package tests;

...
import entites.Personne;

@SuppressWarnings("unchecked")
public class Main {

    // sabitler
    private final static String TABLE_NAME = "jpa01_personne";

    // Kalıcılık bağlamı
    private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
    private static EntityManager em = null;

    // Paylaşılan nesneler
    private static Personne p1, p2, newp1;

    public static void main(String[] args) throws Exception {
        // veritabanı temizleme
        log("clean");clean();

        // tablo dökümü
        dump();

        // test1
        log("test1");test1();

...
        // test11
        log("test11");test11();

        // kalıcılık bağlamı sonu
        if (em.isOpen())
            em.close();

        // EntityManagerFactory kapatma
        emf.close();
    }

    // mevcut EntityManager'i al
    private static EntityManager getEntityManager() {
        if (em == null || !em.isOpen()) {
            em = emf.createEntityManager();
        }
        return em;
    }

    // yeni bir EntityManager almak
    private static EntityManager getNewEntityManager() {
        if (em != null && em.isOpen()) {
            em.close();
        }
        em = emf.createEntityManager();
        return em;
    }

    // tablo içeriğini görüntüle
    private static void dump() {
        // geçerli kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // kişileri görüntüle
        System.out.println("[personnes]");
        for (Object p : em.createQuery("select p from Personne p order by p.nom asc").getResultList()) {
            System.out.println(p);
        }
        // işlem sonu
        tx.commit();
    }

    // BD'i sıfırla
    private static void clean() {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // PERSONNES tablosundaki öğeleri sil
        em.createNativeQuery("delete from " + TABLE_NAME).executeUpdate();
        // işlem sonu
        tx.commit();
    }

    // günlükler
    private static void log(String message) {
        System.out.println("main : ----------- " + message);
    }

    // nesnelerin oluşturulması
    public static void test1() throws ParseException {
...
    }

    // bağlamdaki bir nesneyi değiştir
    public static void test2() {
...
    }

    // nesneleri isteme
    public static void test3() {
...
    }

    // kalıcılık bağlamına ait bir nesneyi silme
    public static void test4() {
....
    }

    // ayırma, yeniden bağlama ve değiştirme
    public static void test5() {
...
    }

    // kalıcılık bağlamına ait olmayan bir nesneyi silme
    public static void test6() {
...
    }

    // kalıcılık bağlamına ait olmayan bir nesneyi değiştirmek
    public static void test7() {
...
    }

    // bir nesneyi kalıcılık bağlamına yeniden ekleme
    public static void test8() {
...
    }

    // bir select sorgusu senkronizasyona neden olur
    // veritabanı ile kalıcılık bağlamı arasında senkronizasyon
    public static void test9() {
....
    }

    // sürüm kontrolü (iyimser kilitleme)
    public static void test10() {
...
    }

    // bir işlemin geri alınması
    public static void test11() throws ParseException {
...
    }

}
  • 13. satır: [persistence.xml]'te tanımlanan jpa kalıcılık biriminden oluşturulan EntityManagerFactory emf nesnesi. Bu nesne, uygulama boyunca çeşitli kalıcılık bağlamları oluşturmamızı sağlayacaktır.
  • 14. satır: EntityManager adlı bir kalıcılık bağlamı henüz başlatılmadı
  • 17. satır: Testler tarafından paylaşılan üç [Personne] nesnesi
  • 21. satır: jpa01_personne tablosu boşaltıldıktan sonra 24. satırda görüntülenerek tablonun boş olduğundan emin olundu.
  • satır 27-31: testlerin sırayla yürütülmesi
  • satır 34-35: açık olması durumunda em kalıcılık bağlamının kapatılması.
  • 38. satır: EntityManagerFactory emf nesnesinin kapatılması.
  • 42-47. satırlar: [getEntityManager] yöntemi, EntityManager'i (veya kalıcılık bağlamını) mevcut hale getirir; eğer yoksa yeni bir tane oluşturur (43-44. satırlar).
  • satır 50-56: [getNewEntityManager] yöntemi, bir kalıcılık bağlamını yeni hale getirir. Daha önce mevcut olan bir bağlam varsa, bu bağlam kapatılır (satır 51-52)
  • satır 59-72: [dump] yöntemi, [jpa01_personne] tablosunun içeriğini görüntüler. Bu kod, [InitDB]'te daha önce karşımıza çıkmıştı.
  • 75-85. satırlar: [clean] yöntemi, [jpa01_personne] tablosunu boşaltır. Bu kod, [InitDB]'te de daha önce karşımıza çıkmıştı.
  • 88-90. satırlar: [log] yöntemi, kendisine parametre olarak geçirilen mesajı konsolda görüntüler, böylece mesajın fark edilmesini sağlar.

Şimdi testleri incelemeye geçebiliriz.

2.1.13.2. Test 1

test1 kodunu aşağıdaki gibidir:


// nesnelerin oluşturulması
    public static void test1() throws ParseException {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // kişi oluşturma
        p1 = new Personne("Martin", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
        p2 = new Personne("Durant", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
        // işlemin başlatılması
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // kişilerin kalıcılığı
        em.persist(p1);
        em.persist(p2);
        // işlemin sonu
        tx.commit();
        // tablo görüntüleniyor
        dump();

}

Bu kod, [InitDB]'te daha önce karşımıza çıkmıştı: iki kişi oluşturur ve bunları kalıcılık bağlamına yerleştirir.

  • 4. satır: geçerli kalıcılık bağlamı sorgulanır
  • 6-7. satırlar: iki kişi oluşturulur
  • 9-15. satırlar: iki kişi, bir işlem içinde kalıcılık bağlamına yerleştirilir.
  • 15. satır: İşlemin tamamlanması nedeniyle, kalıcılık bağlamı veritabanıyla senkronize edilir. İki kişi, [jpa01_personne] tablosuna eklenecektir.
  • 17. satır: Tablo görüntülenir

Bu ilk testin konsol çıktısı şöyledir:

main : ----------- test1
[personnes]
[2,0,Durant,Sylvie,05/07/2001,false,0]
[1,0,Martin,Paul,31/01/2000,true,2]

2.1.13.3. Test 2

Test 2'nin kodu şöyledir:


// bağlamdaki bir nesneyi değiştirme
    public static void test2() {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // işlemin başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // p1'in çocuk sayısını artırma
        p1.setNbenfants(p1.getNbenfants() + 1);
        // medeni durumu değiştiriliyor
        p1.setMarie(false);
        // p1 nesnesi otomatik olarak kaydedilir (kirli kontrol)
        // bir sonraki senkronizasyonda (commit veya select)
        // işlem sonu
        tx.commit();
        // yeni tablo görüntülenir
        dump();
    }
  • Test 2'nin amacı, kalıcılık bağlamındaki bir nesneyi değiştirmek ve ardından tablonun içeriğini görüntüleyerek değişikliğin gerçekleşip gerçekleşmediğini kontrol etmektir
  • 4. satır: mevcut kalıcılık bağlamı alınır
  • 6-7. satırlar: İşlemler bir işlem içinde gerçekleştirilecektir
  • 9. ve 11. satırlar: p1 kişisinin çocuk sayısı ve medeni durumu değiştirilir
  • 15. satır: İşlem sona erer, dolayısıyla kalıcılık bağlamı veritabanıyla senkronize edilir
  • 17. satır: tablo görüntülenir

Test 2'nin konsol çıktısı şöyledir:

1
2
3
4
5
6
7
8
main : ----------- test1
[personnes]
[2,0,Durant,Sylvie,05/07/2001,false,0]
[1,0,Martin,Paul,31/01/2000,true,2]
main : ----------- test2
[personnes]
[2,0,Durant,Sylvie,05/07/2001,false,0]
[1,1,Martin,Paul,31/01/2000,false,3]
  • 4. satır: Değişiklikten önceki p1 kişisi
  • satır 8: değişiklik sonrası p1 kişisi. Versiyon numarasının 1'e yükseldiği dikkat çekmektedir. Bu numara, satır her güncellendiğinde 1 artar.

2.1.13.4. Test 3

Test 3'ün kodu şöyledir:


    // nesneleri sorgula
    public static void test3() {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // p1 kişisi isteniyor
        Personne p1b = em.find(Personne.class, p1.getId());
        // p1 zaten kalıcılık bağlamında olduğu için veritabanına erişim gerçekleşmedi
        // p1b ve p1 aynı referanslardır
        System.out.format("p1==p1b ? %s%n", p1 == p1b);
        // var olmayan bir nesneye erişim denemesi, 1 işaretçiyi null yapar
        Personne px = em.find(Personne.class, -4);
        System.out.format("px==null ? %s%n", px == null);
        // işlem sonu
        tx.commit();
}
  • Test 3, veritabanından bir nesneyi alıp kalıcılık bağlamına yerleştirmeye olanak tanıyan [EntityManager.find] yöntemini ele almaktadır. Artık, olağandışı bir şekilde kullanılmadığı sürece, tüm testlerde gerçekleşen işlemi açıklamayacağız.
  • 9. satır: Kalıcılık bağlamından, p1 kişisiyle aynı birincil anahtara sahip kişiyi talep ediyoruz. İki durum söz konusudur:
    • p1 zaten kalıcılık bağlamında bulunuyor. Burada durum budur. Bu durumda veritabanına erişim yapılmaz. find yöntemi, yalnızca kalıcı nesneye bir referans döndürür.
    • p1, kalıcılık bağlamında değilse, verilen birincil anahtar aracılığıyla veritabanına erişim yapılır. Alınan satır kalıcılık bağlamına eklenir ve find, bu yeni kalıcı nesnenin referansını döndürür.
  • 12. satır: find'in, p1 nesnesinin referansını zaten bağlamda döndürdüğü kontrol edilir
  • 14. satır: Ne kalıcılık bağlamında ne de veritabanında bulunan bir nesne talep edilir. find yöntemi, null işaretçisini döndürür. Bu durum 15. satırda doğrulanır.

Test 3'ün konsol çıktısı şöyledir:

1
2
3
main : ----------- test3
p1==p1b ? true
px==null ? true

2.1.13.5. Test 4

Test 4'ün kodu şöyledir:


    // kalıcılık bağlamına ait bir nesneyi silme
    public static void test4() {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // kalıcı nesne p2 siliniyor
        em.remove(p2);
        // işlemin sonu
        tx.commit();
        // yeni tablo görüntüleniyor
        dump();
}
  • Test 4, kalıcılık bağlamından ve dolayısıyla veritabanından bir öğeyi silmeye olanak tanıyan [EntityManager.remove] yöntemini ele almaktadır.
  • 9. satır: p2 kişisi kalıcılık bağlamından kaldırılır
  • 11. satır: bağlamın veritabanıyla senkronizasyonu
  • 13. satır: Tablonun görüntülenmesi. Normalde, p2 artık orada olmamalıdır.

Test 4'ün konsol çıktısı şöyledir:

main : ----------- test1
[personnes]
[2,0,Durant,Sylvie,05/07/2001,false,0]
[1,0,Martin,Paul,31/01/2000,true,2]
main : ----------- test2
[personnes]
[2,0,Durant,Sylvie,05/07/2001,false,0]
[1,1,Martin,Paul,31/01/2000,false,3]
main : ----------- test3
p1==p1b ? true
px==null ? true
main : ----------- test4
[personnes]
[1,1,Martin,Paul,31/01/2000,false,3]
  • 3. satır: test1'teki p2 kişisi
  • 12-14. satırlar: test4'in sonunda artık mevcut değildir.

2.1.13.6. Test 5

Test 5'in kodu şöyledir:


// ayırma, yeniden ekleme ve değiştirme
    public static void test5() {
        // yeni kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // p1 ayrıldı
        Personne oldp1=p1;
        // p1'i yeni bağlama yeniden bağlama
        p1 = em.find(Personne.class, p1.getId());
        // doğrulama
        System.out.format("p1==oldp1 ? %s%n", p1 == oldp1);        
        // işlem sonu
        tx.commit();
        // p1'in alt öğe sayısı artırılıyor
        p1.setNbenfants(p1.getNbenfants() + 1);
        // yeni tablo görüntüleniyor
        dump();
    }
  • Test 5, birbirini izleyen birkaç kalıcılık bağlamı boyunca kalıcı nesnelerin ömrünü inceler. Şimdiye kadar, farklı testler boyunca her zaman aynı kalıcılık bağlamını kullanmıştık.
  • 4. satır: Yeni bir kalıcılık bağlamı talep edilir. [getNewEntityManager] yöntemi, önceki bağlamı kapatır ve yeni bir tane açar. Bunun sonucu olarak, uygulama tarafından tutulan p1 ve p2 nesneleri artık kalıcı durumda değildir. Bu nesneler, kapatılan bir bağlama aitti. Bunların "ayrılmış" durumda olduğu söylenir. Yeni kalıcılık bağlamına ait değillerdir.
  • 6-7. satırlar: İşlemin başlangıcı. Burada, işlem alışılmadık bir şekilde kullanılacaktır.
  • 9. satır: Artık ayrılmış olan p1 nesnesinin adresi kaydedilir.
  • 11. satır: Kalıcılık bağlamından p1 kişisi (p1'in birincil anahtarına sahip olan) istenir. Bağlam yeni olduğu için p1 kişisi burada bulunmaz. Dolayısıyla veritabanına erişim gerçekleşecektir. Geri getirilen nesne yeni bağlama yerleştirilecektir.
  • 13. satır: Bağlamdaki kalıcı nesne p1'in, eski ayrılmış nesne p1 olan oldp1 nesnesinden farklı olduğu kontrol edilir.
  • 15. satır: İşlem sona erer
  • 17. satır: İşlem dışında, yeni kalıcı nesne p1 değiştirilir. Bu durumda ne olur? Bunu öğrenmek istiyoruz.
  • 19. satır: Tablonun görüntülenmesi istenir. dump yöntemi tarafından oluşturulan select nesnesi nedeniyle, kalıcılık bağlamının veritabanıyla senkronizasyonu otomatik olarak gerçekleştirilir.

Test 5’in konsol çıktısı şöyledir:

1
2
3
4
5
6
7
main : ----------- test4
[personnes]
[1,1,Martin,Paul,31/01/2000,false,3]
main : ----------- test5
p1==oldp1 ? false
[personnes]
[1,2,Martin,Paul,31/01/2000,false,4]
  • 5. satır: find yöntemi veritabanına erişim gerçekleştirmiştir; aksi takdirde iki işaretçi eşit olurdu
  • 7. ve 3. satırlar: p1'in çocuk sayısı 1 artmıştır. Dolayısıyla, işlem dışında yapılan değişiklik dikkate alınmıştır. Bu durum, aslında kullanılan SGBD yöntemine bağlıdır. Bir SGBD'te, bir SQL komutu her zaman bir işlem içinde yürütülür. JPA istemcisi kendisi açık bir işlem başlatmazsa, SGBD bir örtük işlem başlatır. İki yaygın durum söz konusudur:
    • 1 - Her bir SQL emri, emirden önce açılan ve emirden sonra kapatılan bir işlemin konusu olur. Buna otomatik onay (autocommit) modu denir. Dolayısıyla her şey, sanki JPA istemcisi her bir SQL emri için ayrı bir işlem yapıyormuş gibi gerçekleşir.
    • 2 - SGBD, otomatik onay modunda değildir ve JPA müşterisinin bir işlem dışında gönderdiği ilk SQL emriyle örtük bir işlem başlatır ve bu işlemin kapatılmasını müşteriye bırakır. JPA istemcisi tarafından gönderilen tüm SQL emirleri bu noktada örtük işlemin bir parçası haline gelir. Bu işlem, çeşitli olaylar sonucunda sona erebilir: istemcinin bağlantıyı kapatması, yeni bir işlem başlatması vb.

Bu durum, SGBD'in yapılandırmasına bağlıdır. Dolayısıyla, taşınabilir olmayan bir kodla karşı karşıyayız. Biraz ileride, işlem içermeyen bir kod göstereceğiz ve tüm SGBD'lerin bu koda karşı aynı davranış sergilemediğini göreceğiz. Bu nedenle, işlemler dışında çalışmanın bir programlama hatası olduğunu kabul edeceğiz.

  • 7. satır: Sürüm numarasının 2’ye yükseldiğine dikkat edelim.

2.1.13.7. Test 6

Test 6 kodunu aşağıda görebilirsiniz:


// kalıcılık bağlamına ait olmayan bir nesneyi silme
    public static void test6() {
        // yeni kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlem başlatılır
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // yeni bağlamda yer almayan p1 siliniyor
        try {
            em.remove(p1);
            // işlem sonu
            tx.commit();
        } catch (RuntimeException e1) {
            System.out.format("Erreur à la suppression de p1 : [%s,%s]%n", e1.getClass().getName(), e1.getMessage());
            // işlem geri alınır
            try {
                if (tx.isActive())
                    tx.rollback();
            } catch (RuntimeException e2) {
                System.out.format("Erreur au rollback [%s,%s]%n", e2.getClass().getName(), e2.getMessage());
            }
        }
        // yeni tablo görüntüleniyor
        dump();
    }
  • Test 6, kalıcılık bağlamına ait olmayan bir nesneyi silmeye çalışır.
  • 4. satır: Yeni bir kalıcılık bağlamı talep edilir. Dolayısıyla eski bağlam kapatılır ve içindeki nesneler bağsız hale gelir. Önceki Test 5'teki p1 nesnesi de bu durumdadır.
  • 6-7. satırlar: İşlemin başlangıcı.
  • 10. satır: Bağlantısı kesilmiş p1 nesnesi siliniyor. Bunun bir istisna oluşturacağı bilindiğinden, işlem bir try/catch bloğu içine alınmıştır.
  • 12. satır: commit işlemi gerçekleşmeyecektir.
  • 16-21. satırlar: Bir işlem, commit (işlemin tüm işlemleri onaylanır) veya rollback (işlemin tüm işlemleri iptal edilir) ile sonlanmalıdır. Bir istisna oluştu, bu nedenle işlem için rollback komutu veriyoruz. İşlemin tek işlemi başarısız olduğu için geri alınacak bir şey yok, ancak rollback komutu işlemi sonlandırır. [EntityTransaction].rollback işlemini ilk kez kullanıyoruz. Bunu ilk örneklerden itibaren yapmalıydık. Kodu basit tutmak için bunu yapmadık. Yine de okuyucu, işlemdeki rollback durumunun kodda her zaman öngörülmesi gerektiğini akılda tutmalıdır.
  • 24. satır: tablo görüntülenir. Normalde, tabloda bir değişiklik olmamış olmalıdır.

Test 6'nın konsol çıktısı şöyledir:

1
2
3
4
5
6
7
8
main : ----------- test5
p1==oldp1 ? false
[personnes]
[1,2,Martin,Paul,31/01/2000,false,4]
main : ----------- test6
Erreur à la suppression de p1 : [java.lang.IllegalArgumentException,Removing a detached instance entites.Personne#1]
[personnes]
[1,2,Martin,Paul,31/01/2000,false,4]
  • 6. satır: p1'in silinmesi başarısız oldu. İstisna mesajı, bağlanmamış ve dolayısıyla bağlamın bir parçası olmayan bir nesneyi silmeye çalışıldığını açıklıyor. Bu mümkün değildir.
  • 8. satır: p1 hala orada.

2.1.13.8. Test 7

test7 kodunun içeriği şöyledir:


// kalıcılık bağlamına ait olmayan bir nesne değiştiriliyor
    public static void test7() {
        // yeni kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlem başlatılıyor
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // yeni bağlamda yer almayan p1'in alt öğe sayısı artırılıyor
        p1.setNbenfants(p1.getNbenfants() + 1);
        // işlem sonu
        tx.commit();
        // yeni tablo görüntüleniyor - değişmemiş olmalı
        dump();
    }
  • Test 7, kalıcılık bağlamına ait olmayan bir nesneyi değiştirmeye çalışır ve bunun veritabanı üzerinde ne gibi bir etkisi olduğunu inceler. Bunun hiçbir etkisi olmayacağı tahmin edilebilir. Test sonuçları da bunu göstermektedir.
  • 4. satır: Yeni bir kalıcılık bağlamı talep ediliyor. Dolayısıyla, içinde kalıcı nesneler bulunmayan yeni bir bağlamımız var.
  • 6-7. satırlar: İşlemin başlangıcı.
  • 9. satır: Ayrılmış p1 nesnesi değiştirilir. Bu işlem, em kalıcılık bağlamını içermez. Dolayısıyla bir istisna veya benzeri bir durum beklenmemelidir. Bu, bir POJO üzerinde yapılan temel bir işlemdir.
  • 11. satır: Commit işlemi, bağlamın veritabanıyla senkronize edilmesini sağlar. Bu bağlam boştur. Dolayısıyla veritabanında herhangi bir değişiklik olmaz.
  • 24. satır: Tablo görüntülenir. Normalde, tabloda herhangi bir değişiklik olmamış olmalıdır.

Test 7'nin konsol çıktısı şöyledir:

1
2
3
4
5
6
7
main : ----------- test6
Erreur à la suppression de p1 : [java.lang.IllegalArgumentException,Removing a detached instance entites.Personne#1]
[personnes]
[1,2,Martin,Paul,31/01/2000,false,4]
main : ----------- test7
[personnes]
[1,2,Martin,Paul,31/01/2000,false,4]
  • 7. satır: p1 kişisi veritabanında değişmemiştir. Bir sonraki test için, bellekte çocuk sayısının artık 5 olduğunu unutmayalım.

2.1.13.9. Test 8

Test 8'in kodu şöyledir:


    // bir nesneyi kalıcılık bağlamına yeniden bağlama
    public static void test8() {
        // yeni kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlemin başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // ayrılmış p1 nesnesi yeni bağlama yeniden ekleniyor
        newp1 = em.merge(p1);
        // artık bağlamın parçası olan newp1'dir, p1 değil
        // işlem sonu
        tx.commit();
        // yeni tablo görüntüleniyor - p1'in çocuk sayısı değişmiş olmalı
        dump();
}
  • Test 8, kalıcı bağlamdan ayrılmış bir nesneyi tekrar kalıcı bağlama ekler.
  • 4. satır: Yeni bir kalıcılık bağlamı talep edilir. Dolayısıyla, içinde kalıcı nesneler bulunmayan yeni bir bağlamımız vardır.
  • 6-7. satırlar: İşlemin başlangıcı.
  • 9. satır: Ayrılmış p1 nesnesi kalıcılık bağlamına yeniden eklenir. Birleştirme işlemi birkaç durumu içerebilir:
    • Durum 1: Kalıcılık bağlamında, ayrılmış nesne p1 ile aynı birincil anahtara sahip ps1 adlı bir kalıcı nesne bulunmaktadır. p1'in içeriği ps1'e kopyalanır ve merge, ps1'e referans verir.
    • Durum 2: Kalıcılık bağlamında, ayrılmış p1 nesnesiyle aynı birincil anahtara sahip ps1 adlı kalıcı bir nesne mevcut değildir. Bu durumda, aranan nesnenin veritabanında mevcut olup olmadığı öğrenmek için veritabanı sorgulanır. Eğer varsa, bu nesne kalıcılık bağlamına alınır, ps1 kalıcı nesnesi haline gelir ve önceki durum 1'e geri dönülür.
    • Durum 3: Ne kalıcılık bağlamında ne de veritabanında, ayrılmış nesne p1 ile aynı birincil anahtara sahip bir nesne mevcut değildir. Bu durumda yeni bir nesne [Personne] (new) oluşturulur ve ardından kalıcılık bağlamına eklenir. Ardından tekrar durum 1’e dönülür.
    • Sonuç olarak: Ayrılmış nesne p1, ayrılmış durumda kalır. merge işlemi, merge nesnesinden türetilen kalıcı nesne ps1’e bir referans (burada newp1) döndürür. İstemci uygulaması artık ayrılmış nesne p1 ile değil, kalıcı nesne ps1 ile çalışmalıdır.
    • merge için programlanan SQL siparişi açısından 1. ve 3. durumlar arasında bir fark olduğu dikkat çekmektedir: 1. ve 2. durumlarda bu, UPDATE emridir; oysa 3. durumda ise INSERT emridir.
  • 12. satır: Commit işlemi, bağlamın veritabanıyla senkronize edilmesini sağlar. Bu bağlam artık boş değildir. İçinde newp1 nesnesi bulunur. Bu nesne veritabanında kalıcı olarak kaydedilecektir.
  • 24. satır: Doğrulamak için tablo görüntülenir.

Test 8'in konsol çıktısı şöyledir:

main : ----------- test6
Erreur à la suppression de p1 : [java.lang.IllegalArgumentException,Removing a detached instance entites.Personne#1]
[personnes]
[1,2,Martin,Paul,31/01/2000,false,4]
main : ----------- test7
[personnes]
[1,2,Martin,Paul,31/01/2000,false,4]
main : ----------- test8
[personnes]
[1,3,Martin,Paul,31/01/2000,false,5]
  • p1'in çocuk sayısı Test 6'da (satır 4) 4 idi, ardından Test 7'de 5'e yükseltilmişti ancak veritabanına kaydedilmemişti (satır 7). merge'ten sonra, newp1 veritabanına kaydedildi: 10. satırda, 5 çocuk olduğu görülüyor.
  • 10. satır: newp1'in sürüm numarası 3'e yükseltildi.

2.1.13.10. Test 9

Test9 kodunu aşağıdaki gibidir:


// Bir SELECT sorgusu senkronizasyona neden olur
    // veritabanı ile kalıcılık bağlamı arasında
    public static void test9() {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // newp1'in çocuk sayısı artırılıyor
        newp1.setNbenfants(newp1.getNbenfants() + 1);
        // Kişiler ekranı - newp1'in çocuk sayısı değişmiş olmalı
        System.out.println("[personnes]");
        for (Object p : em.createQuery("select p from Personne p order by p.nom asc").getResultList()) {
            System.out.println(p);
        }
        // işlem sonu
        tx.commit();
    }
  • Test 9, bir select işleminden önce otomatik olarak gerçekleşen bağlam senkronizasyon mekanizmasını göstermeyi amaçlamaktadır.
  • 5. satır: Kalıcılık bağlamı değiştirilmez. Dolayısıyla newp1 bu bağlamın içindedir.
  • 7-8. satırlar: İşlemin başlangıcı.
  • 10. satır: Kalıcı nesne newp1'in alt öğe sayısı 1 artırılır (5 -> 6).
  • 12-15. satırlar: Bir SELECT sorgusuyla tablo görüntülenir. select çalıştırılmadan önce bağlam veritabanıyla senkronize edilecektir.
  • 17. satır: işlemin sonu

Senkronizasyonu görmek için, Hibernate günlüklerinin görüntülenmesini DEBUG (log4j.properties) modunda başlatıyoruz:


# Kök günlüğü seçeneği
log4j.rootLogger=ERROR, stdout

# Hibernate günlüğü seçenekleri (INFO yalnızca başlangıç mesajlarını gösterir)
log4j.logger.org.hibernate=DEBUG

Test 9'un konsol çıktısı şöyledir:

main : ----------- test9
14:27:27,250 DEBUG JDBCTransaction:54 - begin
14:27:27,250 DEBUG ConnectionManager:415 - opening JDBC connection
14:27:27,250 DEBUG JDBCTransaction:59 - current autocommit status: true
14:27:27,250 DEBUG JDBCTransaction:62 - disabling autocommit
14:27:27,250 DEBUG JDBCContext:210 - after transaction begin
[personnes]
14:27:27,250 DEBUG QueryPlanCache:76 - located HQL query plan in cache (select p from Personne p order by p.nom asc)
14:27:27,250 DEBUG AbstractFlushingEventListener:58 - flushing session
...
14:27:27,250 DEBUG AbstractEntityPersister:3116 - entites.Personne.nbenfants is dirty
14:27:27,250 DEBUG DefaultFlushEntityEventListener:229 - Updating entity: [entites.Personne#1]
14:27:27,250 DEBUG Versioning:27 - Incrementing: 3 to 4
...
14:27:27,250 DEBUG AbstractFlushingEventListener:85 - Flushed: 0 insertions, 1 updates, 0 deletions to 1 objects
...
14:27:27,250 DEBUG ConnectionManager:463 - registering flush begin
14:27:27,250 DEBUG AbstractEntityPersister:2274 - Updating entity: [entites.Personne#1]
14:27:27,265 DEBUG AbstractEntityPersister:2276 - Existing version: 3 -> New version: 4
14:27:27,265 DEBUG AbstractBatcher:358 - about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
14:27:27,265 DEBUG SQL:393 - update jpa01_personne set VERSION=?, NOM=?, PRENOM=?, DATENAISSANCE=?, MARIE=?, NBENFANTS=? where ID=? and VERSION=?
14:27:27,265 DEBUG AbstractBatcher:476 - preparing statement
14:27:27,265 DEBUG AbstractEntityPersister:1927 - Dehydrating entity: [entites.Personne#1]
14:27:27,265 DEBUG IntegerType:80 - binding '4' to parameter: 1
14:27:27,265 DEBUG StringType:80 - binding 'Martin' to parameter: 2
14:27:27,265 DEBUG StringType:80 - binding 'Paul' to parameter: 3
14:27:27,265 DEBUG DateType:80 - binding '31 janvier 2000' to parameter: 4
14:27:27,265 DEBUG BooleanType:80 - binding 'false' to parameter: 5
14:27:27,265 DEBUG IntegerType:80 - binding '6' to parameter: 6
14:27:27,265 DEBUG IntegerType:80 - binding '1' to parameter: 7
14:27:27,265 DEBUG IntegerType:80 - binding '3' to parameter: 8
14:27:27,265 DEBUG AbstractBatcher:366 - about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
14:27:27,265 DEBUG AbstractBatcher:525 - closing statement
14:27:27,265 DEBUG ConnectionManager:472 - registering flush end
14:27:27,265 DEBUG HQLQueryPlan:150 - find: select p from Personne p order by p.nom asc
14:27:27,265 DEBUG QueryParameters:277 - named parameters: {}
14:27:27,265 DEBUG AbstractBatcher:358 - about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
14:27:27,265 DEBUG SQL:393 - select personne0_.ID as ID0_, personne0_.VERSION as VERSION0_, personne0_.NOM as NOM0_, personne0_.PRENOM as PRENOM0_, personne0_.DATENAISSANCE as DATENAIS5_0_, personne0_.MARIE as MARIE0_, personne0_.NBENFANTS as NBENFANTS0_ from jpa01_personne personne0_ order by personne0_.NOM asc
...
14:27:27,265 DEBUG Loader:1164 - result row: EntityKey[entites.Personne#1]
...
14:27:27,265 DEBUG Loader:839 - total objects hydrated: 0
14:27:27,265 DEBUG StatefulPersistenceContext:748 - initializing non-lazy collections
[1,4,Martin,Paul,31/01/2000,false,6]
14:27:27,265 DEBUG JDBCTransaction:103 - commit
14:27:27,265 DEBUG SessionImpl:337 - automatically flushing session
...
14:27:27,265 DEBUG AbstractFlushingEventListener:91 - Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
...
14:27:27,296 DEBUG JDBCTransaction:116 - committed JDBC Connection
...
  • 1. satır: Test 9 başlıyor
  • 2-6. satırlar: Jdbc işlemi başlıyor. SGBD'in autocommit modu devre dışı bırakılmıştır (5. satır)
  • 7. satır: Java kodunun 12. satırı tarafından tetiklenen çıktı. Java kodunun sonraki satırları bir select komutunu tetikleyecek ve dolayısıyla kalıcılık bağlamının veritabanıyla senkronize edilmesini sağlayacaktır.
  • 8. satır: Vermek istediğimiz JPQL komutu zaten verilmiştir. Hibernate bunu "hazırlanmış sorgular" önbelleğinde bulur.
  • 9. satır: Hibernate, kalıcılık bağlamını temizleyeceğini bildirir
  • 11-12. satırlar: Hibernate (Hb), Personne#1 varlığının (birincil anahtarı 1 olan) değiştirildiğini (kirli olduğunu) tespit eder.
  • 12-13. satırlar: Hb, bu öğeyi güncellediğini ve sürüm numarasını 3'ten 4'e yükselttiğini bildirir.
  • 15. satır: Bağlam senkronizasyonu 0 ekleme, 1 güncelleme (update), 0 silme (delete) işlemine neden olacaktır
  • 17-34. satırlar: bağlam senkronizasyonu (flush). Dikkat edilmesi gerekenler: sürüm numarasının artırılması (satır 19), hazırlanan SQL güncelleme emri (satır 21), update emrinin parametre değerleri (satır 24-31).
  • satır 35: select komutu başlıyor
  • 38. satır: Yürütülecek olan SQL emri
  • 40. satır: select yalnızca bir satır döndürür
  • satır 42: Hb, select komutunun veritabanından getirdiği Personne#1 varlığının zaten kalıcılık bağlamında mevcut olduğunu fark eder. Bu durumda, veritabanından alınan satırı bağlama kopyalamaz; bu işleme "hidrasyon" adını verir.
  • 43. satır: select tarafından getirilen nesnelerin, aynı zamanda yüklenmesi gereken bağımlılıkları (genellikle yabancı anahtarlar) olup olmadığını kontrol eder (tembel olmayan koleksiyonlar). Burada böyle bir bağımlılık yoktur.
  • 44. satır: Java kodu tarafından tetiklenen görüntüleme
  • 45. satır: Java kodu tarafından talep edilen Jdbc işleminin sonu
  • 46. satır: commit sırasında gerçekleşen bağlamın otomatik senkronizasyonu başlar.
  • 48. satır: Hb, bağlamın bir önceki senkronizasyondan bu yana değişmediğini tespit eder.
  • satır 50: commit işlemi sona erer.

Yine, DEBUG modundaki Hibernate günlükleri, Hibernate'in tam olarak ne yaptığını anlamak için çok yararlıdır.

2.1.13.11. Test 10

Test10 kodunu aşağıdaki gibidir:


// sürüm denetimi (iyimser kilitleme)
    public static void test10() {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // newp1 sürümünü doğrudan veritabanında artır (yerel sorgu)
        em.createNativeQuery(String.format("update %s set VERSION=VERSION+1 WHERE ID=%d", TABLE_NAME, newp1.getId())).executeUpdate();
        // işlemin sonu
        tx.commit();
        // yeni işlemin başlangıcı
        tx = em.getTransaction();
        tx.begin();
        // newp1'in çocuk sayısını artırıyoruz
        newp1.setNbenfants(newp1.getNbenfants() + 1);
        // işlem sonu - newp1 artık doğru sürüme sahip olmadığı için işlem başarısız olmalıdır
        try {
            tx.commit();
        } catch (RuntimeException e1) {
            System.out.format("Erreur lors de la mise à jour de newp1 [%s,%s,%s,%s]%n", e1.getClass().getName(), e1.getMessage(), e1.getCause().getClass().getName(), e1.getCause().getMessage());
            // işlem geri alınır
            try {
                if (tx.isActive())
                    tx.rollback();
            } catch (RuntimeException e2) {
                System.out.format("Erreur au rollback [%s,%s]%n", e2.getClass().getName(), e2.getMessage());
            }
        }
        // artık güncel olmayan bağlam kapatılıyor
        em.close();
        // tablonun dökümü - p1'in sürümü değişmiş olmalı
        dump();
    }
  • Test 10, @Entity Personne sınıfının version alanının sağladığı mekanizmayı göstermeyi amaçlamaktadır; bu sınıf, JPA @Version özniteliğine sahiptir. Bu anotasyonun, veritabanında @Version anotasyonuyla ilişkili sütunun değerinin, ait olduğu satırda yapılan her update işleminde artırılmasını sağladığını açıklamıştık. Optimistic locking (iyimser kilitleme) olarak da adlandırılan bu mekanizma, veritabanındaki bir O nesnesini değiştirmek isteyen istemcinin, bu nesnenin en son sürümüne sahip olmasını zorunlu kılar. Eğer istemci en son sürüme sahip değilse, bu durum nesnenin istemci tarafından alındığından beri değiştirildiğini gösterir ve istemciye bu durum bildirilmelidir.
  • 4. satır: Kalıcılık bağlamı değiştirilmez. Dolayısıyla newp1 bu bağlamın içindedir.
  • 6-7. satırlar: Bir işlemin başlangıcı.
  • 9. satır: newp1 nesnesinin sürümü, doğrudan veritabanında 1 artırılır (4 -> 5). nativeQuery türündeki sorgular, kalıcılık bağlamını atlayarak doğrudan veritabanına erişir. Sonuç olarak, kalıcı nesne newp1 ile veritabanındaki görüntüsünün sürümleri artık aynı değildir.
  • 10. satır: ilk işlemin sonu
  • 13-14. satırlar: ikinci işlemin başlangıcı
  • 16. satır: newp1 kalıcı nesnesinin alt nesne sayısı 1 artar (6 -> 7).
  • 19. satır: işlemin sonu. Dolayısıyla bir senkronizasyon gerçekleşir. Bu, veritabanındaki newp1 nesnesinin alt nesne sayısının güncellenmesine neden olacaktır. Bu işlem başarısız olacaktır çünkü kalıcı nesne newp1'in sürümü 4 iken, veritabanında güncellenecek nesnenin sürümü 5'tir. Bir istisna atılacaktır; bu da koddaki try / catch yapısını haklı kılar.
  • 21. satır: İstisna ve nedeni görüntülenir.
  • 25. satır: İşlemin geri alınması
  • 33. satır: Tablonun görüntülenmesi: Veritabanında newp1 nesnesinin sürümünün 5 olduğu görülmelidir.

Test 10’un konsol çıktısı şöyledir:

1
2
3
4
5
6
7
main : ----------- test9
[personnes]
[1,4,Martin,Paul,31/01/2000,false,6]
main : ----------- test10
Erreur lors de la mise à jour de newp1 [javax.persistence.RollbackException,Error while commiting the transaction,org.hibernate.StaleObjectStateException,Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [entites.Personne#1]]
[personnes]
[1,5,Martin,Paul,31/01/2000,false,6]
  • 5. satır: Commit işlemi bir istisna oluşturuyor. İstisna türü [javax.persistence.RollbackException]. İlgili hata mesajı belirsizdir. Bu istisnanın (Exception.getCause) nedenine bakıldığında, doğru sürüme sahip olmadan veritabanındaki bir satırı değiştirmeye çalışılması nedeniyle bir Hibernate istisnası oluştuğu görülür.
  • 7. satır: Veritabanındaki newp1 sürümünün, nativeQuery tarafından 5'e güncellendiği görülüyor.

2.1.13.12. Test 11

Test11 kodunu aşağıdaki gibidir:


// bir işlemin geri alınması
    public static void test11() throws ParseException {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = null;
        try {
            tx = em.getTransaction();
            tx.begin();
            // p1'i veritabanından alarak bağlama bağlanır
            p1 = em.find(Personne.class, p1.getId());
            // p1'in çocuk sayısı artırılıyor
            p1.setNbenfants(p1.getNbenfants() + 1);
            // kişilerin görüntülenmesi - p1'in çocuk sayısı değişmiş olmalı
            System.out.println("[personnes]");
            for (Object p : em.createQuery("select p from Personne p order by p.nom asc").getResultList()) {
                System.out.println(p);
            }
            // Aynı ada sahip 2 kişi oluşturuldu; bu, DDL tarafından yasaklanmıştır
            Personne p3 = new Personne("X", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
            Personne p4 = new Personne("X", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
            // kişilerin kalıcılığı
            em.persist(p3);
            em.persist(p4);
            // işlem sonu
            tx.commit();
        } catch (RuntimeException e1) {
            // Bir sorun yaşandı
            System.out.format("Erreur dans transaction [%s,%s,%s,%s,%s,%s]%n", e1.getClass().getName(), e1.getMessage(),
                    e1.getCause().getClass().getName(), e1.getCause().getMessage(), e1.getCause().getCause().getClass().getName(), e1.getCause().getCause()
                            .getMessage());
            try {
                if (tx.isActive())
                    tx.rollback();
            } catch (RuntimeException e2) {
                System.out.format("Erreur au rollback [%s]%n", e2.getMessage());
            }
            // mevcut bağlamdan çıkılıyor
            em.clear();
        }
        // dump - geri alma nedeniyle tablo değişmemiş olmalı
        dump();
    }
  • Test 11, bir işlemin rollback mekanizmasını incelemektedir. Bir işlem ya tamamen başarılı olur ya da tamamen başarısız olur: İçerdiği SQL işlemleri ya hepsi başarıyla yürütülür (commit) ya da bunlardan biri başarısız olursa hepsi iptal edilir (rollback).
  • 4. satır: Aynı kalıcılık bağlamıyla devam edilir. Okuyucu, önceki testin çökmesi sonucunda bağlamın kapatıldığını hatırlayabilir. Bu durumda, [getEntityManager] yepyeni, yani boş bir bağlam sağlar.
  • 7-27. satırlar: Karşılaşılacak sorunları kontrol etmek için tek bir try / catch bloğu
  • 8-9. satırlar: Birden fazla SQL işlemini içerecek bir işlemin başlangıcı
  • 11. satır: p1 veritabanında aranır ve bağlama eklenir
  • 13. satır: p1'in çocuk sayısı artırılır (6 -> 7)
  • 15-18. satırlar: Veritabanının içeriği görüntülenir; bu, bağlamın senkronize edilmesini zorlar. Veritabanında, p1'in çocuk sayısı 7'ye yükselecek ve bu durum konsol çıktısında da teyit edilmelidir.
  • 20-21. satırlar: Aynı ada sahip 2 kişi, p3 ve p4 oluşturulur. Ancak @Entity Personne'nin nom alanı unique=true özniteliğine sahiptir; bu da [jpa01_personne] tablosundaki NOM sütununda bir tekil olma kısıtlaması oluşturmuştur.
  • 23-24. satırlar: p3 ve p4 kişileri kalıcılık bağlamına eklenir.
  • 26. satır: İşlem onaylanır. Ardından, ilk senkronizasyonun select sırasında gerçekleşmiş olması nedeniyle, bağlamın ikinci bir senkronizasyonu yapılır. JPA, p3 ve p4 kişileri için SQL ve insert olmak üzere iki emir gönderecektir. p3 eklenecektir. p4 için, SGBD bir istisna oluşturacaktır, çünkü p4, p3 ile aynı ada sahiptir. Dolayısıyla p4 eklenmez ve Jdbc sürücüsü müşteriye bir istisna bildirir.
  • 27. satır: istisna işlenir
  • 29-31. satırlar: istisnayı ve bizi bu noktaya getiren istisna zincirindeki önceki iki nedeni görüntülüyoruz.
  • 34. satır: Şu anda aktif olan işlemin geri alınması gerçekleştirilir. Bu işlem, Java kodunun 9. satırında başlamıştır. Bundan sonra, p1'in çocuk sayısını değiştirmek için update işlemi ve ardından p3 kişisi için insert işlemi gerçekleştirilmiştir. Tüm bunlar geri alma işlemiyle iptal edilecektir.
  • 39. satır: Kalıcılık bağlamı boşaltılır
  • 42. satır: [jpa01_personne] tablosu görüntülenir. p1'in hala 6 çocuğa sahip olduğu ve ne p3 ne de p4'in tabloda bulunmadığı kontrol edilmelidir.

Test 11'in konsol çıktısı şöyledir:


main : ----------- test11
[personnes]
[1,6,Martin,Paul,31/01/2000,false,7]
14:50:30,312 ERROR JDBCExceptionReporter:72 - Duplicate entry 'X' for key 2
Erreur dans transaction [javax.persistence.EntityExistsException,org.hibernate.exception.ConstraintViolationException: could not insert: [entites.Personne],org.hibernate.exception.ConstraintViolationException,could not insert: [entites.Personne],java.sql.SQLException,Duplicate entry 'X' for key 2]
[personnes]
[1,5,Martin,Paul,31/01/2000,false,6]
  • 3. satır: Veritabanında p1'in çocuk sayısı 6'dan 7'ye çıkmış, p1'in sürümü 6'ya yükselmiştir.
  • 4. satır: İşlemin commit edilmesi sırasında yakalanan istisna. Dikkatli okunduğunda, nedenin X (ad) adlı yinelenen bir anahtar olduğu görülür. Bu hataya, p4'in eklenmesi neden olmuştur; oysa daha önce eklenmiş olan p3'in adı da X'tir.
  • 7. satır: Geri alma işleminden sonra tablonun durumu. p1, sürüm 5’e ve 6 çocuk sayısına geri döndü; p3 ve p4 ise eklenmedi.

2.1.13.13. Test 12

Test12 kodunu aşağıdaki gibidir:


    // aynı işlemi işlemler olmadan tekrar yapıyoruz
    // SGBD ile daha önce elde ettiğimizle aynı sonucu alıyoruz: FIREBIRD, ORACLE XE, POSTGRES, MYSQL5
    // ve SQLSERVER ile tablo boş kalır. Bağlantı, programın
    // programın yeniden çalıştırılmasını engelleyen bir durumda bırakılır. Bu durumda sunucuyu yeniden başlatmak gerekir.
    // Aynı durum SGBD Derby için de geçerlidir
    // HSQL ilk kişiyi ekler - geri alma işlemi yoktur

    public static void test12() throws ParseException {
        // p1 yeniden bağlanır
        p1 = em.find(Personne.class, p1.getId());
        // p1'in çocuk sayısını artırıyoruz
        p1.setNbenfants(p1.getNbenfants() + 1);
        // kişilerin görüntülenmesi - p1'in çocuk sayısı değişmiş olmalı
        System.out.println("[personnes]");
        for (Object p : em.createQuery("select p from Personne p order by p.nom asc").getResultList()) {
            System.out.println(p);
        }
        // Aynı isimde 2 kişi oluşturuldu; bu, DDL tarafından yasaklanmıştır
        Personne p3 = new Personne("X", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
        Personne p4 = new Personne("X", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
        // Kişilerin kalıcılığı
        em.persist(p3);
        em.persist(p4);
        // em bağlamının BD ile senkronizasyonuna neden olacak döküm
        try {
            dump();
        } catch (RuntimeException e3) {
            System.out.format("Erreur dans dump [%s,%s,%s,%s]%n", e3.getClass().getName(), e3.getMessage(), e3.getCause().getClass().getName(), e3
                    .getCause().getMessage());
        }
        // mevcut bağlam kapatılıyor
        em.close();
        // döküm
        dump();
}
  • Test 12, Test 11 ile aynı işlemi gerçekleştirir, ancak işlem dışında yapılır. Bu durumda ne olacağını görmek istiyoruz.
  • 1-6. satırlar: çeşitli SGBD kodlarıyla yapılan test sonuçlarını gösterir:
  • Bazı SGBD değerlerinde (Firebird, Oracle, MySQL5, Postgres) Test 11 ile aynı sonuç elde edilir. Bu da, söz konusu SGBD'lerin, hataya neden olan emre kadar alınan tüm SQL emirlerini kapsayan bir işlemi kendileri başlattığını ve bir rollback'i kendileri başlattığını düşündürüyor.
  • Diğer SGBD (SQL Sunucusu, Apache Derby) ile birlikte, uygulamanın ve/veya SGBD'in çökmesi yaşanıyor.
  • SGBD ve HSQLDB ile birlikte, SGBD tarafından açılan işlemin autocommit modunda olduğu görülüyor: p1'in alt öğe sayısındaki değişiklik ve p3'in eklenmesi kalıcı hale getirilmiştir. Yalnızca p4'in eklenmesi başarısız olmaktadır.

Dolayısıyla, sonuç SGBD'e bağlıdır ve bu da uygulamanın taşınabilir olmamasına neden olur. Kalıcılık bağlamındaki işlemlerin her zaman bir işlem içinde gerçekleştirilmesi gerektiği unutulmamalıdır.

2.1.14. SGBD'e geçme

Şu anki projemizin test mimarisine geri dönelim:

[3] istemci uygulaması, yalnızca JPA ve [5] arayüzlerini görür. Ne bu arayüzlerin gerçek uygulamasını ne de hedef SGBD'i görür. Bu nedenle, [3] istemcisinde herhangi bir değişiklik yapmadan bu iki zincir öğesini değiştirebilmeliyiz. Şu anda SGBD'i değiştirerek bunu denemeye çalışıyoruz. Şimdiye kadar MySQL5'i kullanmıştık. Eklerde (5. paragraf) açıklanan altı tane daha sunuyoruz; umarız bunların arasında okuyucunun en sevdiği SGBD da vardır.

Her halükarda, Eclipse projesinde yapılması gereken değişiklik basittir (aşağıya bakınız): JPA katmanının yapılandırma dosyası olan persistence.xml [1] dosyasını, projenin conf [2] klasöründeki dosyalardan biriyle değiştirin. Bu JDBC ve SGBD sürücüleri, [jpa-divers], [3] ve [4] kütüphanelerinde zaten mevcuttur.

2.1.14.1. Oracle 10g Express

Oracle 10g Express, Ekler bölümündeki 5.7. paragrafta tanıtılmıştır. Oracle'ın persistence.xml dosyası şöyledir:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
        <!--  sağlayıcı -->
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <properties>
            <!-- Kalıcı sınıflar -->
            <property name="hibernate.archive.autodetection" value="class, hbm" />
            <!-- günlükler SQL
                <property name="hibernate.show_sql" value="true"/>
                <property name="hibernate.format_sql" value="true"/>
                <property name="use_sql_comments" value="true"/>
            -->
            <!-- bağlantı JDBC -->
            <property name="hibernate.connection.driver_class" value="oracle.jdbc.OracleDriver" />
            <property name="hibernate.connection.url" value="jdbc:oracle:thin:@localhost:1521:xe" />
            <property name="hibernate.connection.username" value="jpa" />
            <property name="hibernate.connection.password" value="jpa" />
            <!--  şemanın otomatik olarak oluşturulması -->
            <property name="hibernate.hbm2ddl.auto" value="create" />
            <!-- Lehçe -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.OracleDialect" />
            <!--  özellikler DataSource c3p0 -->
            <property name="hibernate.c3p0.min_size" value="5" />
            <property name="hibernate.c3p0.max_size" value="20" />
            <property name="hibernate.c3p0.timeout" value="300" />
            <property name="hibernate.c3p0.max_statements" value="50" />
            <property name="hibernate.c3p0.idle_test_period" value="3000" />
        </properties>
    </persistence-unit>
</persistence>

Bu yapılandırma, SGBD ve MySQL5 için yapılan yapılandırmayla aynıdır; ancak aşağıdaki ayrıntılar farklıdır:

  • 15-18. satırlar, JDBC bağlantısını veritabanıyla yapılandırır
  • 22. satır: Kullanılacak SQL lehçesini belirler

İleride verilecek örneklerde, yalnızca değişen satırları belirteceğiz. Yapılandırma hakkında açıklama için, kullanılan SGBD'e ayrılmış ek bölümüne bakınız. Burada, her seferinde JDBC bağlantısının kullanımına ilişkin bir örnek, [SQL Explorer] eklentisi bağlamında verilmektedir. Ek'teki bilgilerle okuyucu, 2.1.10.2 numaralı paragrafta yapılan [InitDB] uygulamasının sonucunu doğrulama işlemini tekrarlayabilir.

Yukarıda belirtilen paragrafta anlatıldığı gibi ilerliyoruz:

  • SGBD Oracle'ı başlatın
  • conf/oracle/persistence.xml dosyasını META-INF/persistence.xml klasörüne yerleştirin
  • [InitDB] uygulamasını çalıştırın

Konsolda şu sonuçlar görüntülenir:

Bundan sonra, her zaman aynı olan bu ekran görüntüsünü bir daha göstermeyeceğiz. Daha ilginç olanı, SQL Explorer'ın JDBC ile SGBD arasındaki bağlantıyı incelemesidir. 2.1.8. paragrafında açıklanan adımları takip edeceğiz.

  • [1]'te: Oracle ile bağlantı
  • [2]'e: [InitDB] komutunun çalıştırılmasından sonra ortaya çıkan bağlantı ağacı
  • [3] dosyasında: [jpa01_personne] tablosunun yapısı
  • [4]'te: içeriği.

Bu işlem tamamlandıktan sonra, okuyucunun [Main] uygulamasını çalıştırması ve ardından SGBD'i durdurması istenir.

2.1.14.2. PostgreSQL 8.2

PostgreSQL 8.2, Ekler bölümünün 5.6. paragrafında sunulmuştur. persistence.xml dosyası şöyledir:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
...
            <!-- bağlantı JDBC -->
            <property name="hibernate.connection.driver_class" value="org.postgresql.Driver" />
            <property name="hibernate.connection.url" value="jdbc:postgresql:jpa" />
            <property name="hibernate.connection.username" value="jpa" />
            <property name="hibernate.connection.password" value="jpa" />
...
            <!-- Lehçe -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
...
    </persistence-unit>
</persistence>

[InitDB] dosyasını çalıştırmak için:

  • SGBD dosyasını çalıştırın PostgreSQL
  • conf/postgres/persistence.xml dosyasını META-INF/persistence.xml klasörüne yerleştirin
  • [InitDB] uygulamasını çalıştırın

SQL Explorer'da, JDBC ile SGBD arasındaki bağlantı şu şekildedir:

  • [1]'te: PostgreSQL ile bağlantı
  • [2]'te: [InitDB]'in çalıştırılmasından sonra bağlantının ağaç yapısı
  • [3]'te: [jpa01_personne] tablosunun yapısı
  • [4]: içeriği.

Bu işlem tamamlandıktan sonra, okuyucunun [Main] uygulamasını çalıştırması ve ardından SGBD'i durdurması istenir

2.1.14.3. SQL Server Express 2005

SQL Server Express 2005, Ekler bölümünde 5.8. paragrafta, sayfa 270'de sunulmuştur. persistence.xml dosyası şöyledir:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
...
            <!-- bağlantı JDBC -->
            <property name="hibernate.connection.driver_class" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
            <property name="hibernate.connection.url" value="jdbc:sqlserver://localhost\\SQLEXPRESS:1433;databaseName=jpa" />
            <property name="hibernate.connection.username" value="jpa" />
            <property name="hibernate.connection.password" value="jpa" />
...
            <!-- Lehçe -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.SQLServerDialect" />
...
    </persistence-unit>
</persistence>

[InitDB] dosyasını çalıştırmak için:

  • SGBD SQL Server'ı çalıştırın
  • conf/sqlserver/persistence.xml dosyasını META-INF/persistence.xml klasörüne yerleştirin
  • [InitDB] uygulamasını çalıştır

SQL bağlantısının, JDBC ile SGBD arasındaki Explorer görünümü şu şekildedir:

  • [1]'te: SQL Sunucusu ile bağlantı
  • [2]'te: [InitDB]'in çalıştırılmasından sonra bağlantının ağaç yapısı
  • [3]: [jpa01_personne] tablosunun yapısı
  • [4]: içeriği.

Bu işlem tamamlandıktan sonra, okuyucunun [Main] uygulamasını çalıştırması ve ardından SGBD'i durdurması istenir

2.1.14.4. Firebird 2.0

Firebird 2.0, Ekler bölümünün 5.4. paragrafında tanıtılmıştır. persistence.xml dosyası şu şekildedir:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
...
            <!-- bağlantı JDBC -->
            <property name="hibernate.connection.driver_class" value="org.firebirdsql.jdbc.FBDriver" />
            <property name="hibernate.connection.url" value="jdbc:firebirdsql:localhost/3050:C:\data\2006-2007\eclipse\dvp-jpa\annexes\firebird\jpa.fdb" />
            <property name="hibernate.connection.username" value="sysdba" />
            <property name="hibernate.connection.password" value="masterkey" />
...
            <!-- Lehçe -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.FirebirdDialect" />
...
    </persistence-unit>
</persistence>

[InitDB] dosyasını çalıştırmak için:

  • SGBD Firebird dosyasını çalıştırın
  • conf/firebird/persistence.xml dosyasını META-INF/persistence.xml dizinine yerleştirin
  • [InitDB] uygulamasını çalıştırın

SQL Explorer'da, JDBC ile SGBD arasındaki bağlantı şu şekildedir:

  • [1]'te: Firebird ile bağlantı
  • [2]'te: [InitDB] komutunun çalıştırılmasından sonra ortaya çıkan bağlantı ağacı
  • [3] dosyasında: [jpa01_personne] tablosunun yapısı
  • [4]: içeriği.

Bu işlem tamamlandıktan sonra, okuyucunun [Main] uygulamasını çalıştırması ve ardından SGBD'i durdurması istenir.

2.1.14.5. Apache Derby

Apache Derby, Ekler bölümünün 5.10. paragrafında tanıtılmıştır. persistence.xml dosyası şu şekildedir:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
...
            <!-- bağlantı JDBC -->
            <property name="hibernate.connection.driver_class" value="org.apache.derby.jdbc.ClientDriver" />
            <property name="hibernate.connection.url" value="jdbc:derby://localhost:1527//data/2006-2007/eclipse/dvp-jpa/annexes/derby/jpa;create=true" />
            <property name="hibernate.connection.username" value="jpa" />
            <property name="hibernate.connection.password" value="jpa" />
...
            <!-- Lehçe -->
...
    </persistence-unit>
</persistence>

[InitDB]'i çalıştırmak için:

  • SGBD Apache Derby'yi çalıştırın
  • conf/derby/persistence.xml dosyasını META-INF/persistence.xml klasörüne yerleştirin
  • [InitDB] uygulamasını çalıştırın

SQL Explorer'da, JDBC ile SGBD arasındaki bağlantı şu şekildedir:

  • [1]'te: Apache Derby ile bağlantı
  • [2]'te: [InitDB]'in çalıştırılmasından sonra ortaya çıkan bağlantı ağacı. JPA / Hibernate tarafından, ID birincil anahtarının ardışık değerlerini otomatik olarak oluşturmak üzere oluşturulan [HIBERNATE_UNIQUE_KEY] tablosuna dikkat edilmelidir. Bu mekanizmanın genellikle özel bir mekanizma olduğunu daha önce belirtmiştik. Burada bu durum açıkça görülüyor. JPA sayesinde, geliştiricinin SGBD ile ilgili bu ayrıntılara girmesi gerekmez.
  • [3]: [jpa01_personne] tablosunun yapısı
  • [4]'te: içeriği.

Bu işlem tamamlandıktan sonra, okuyucunun [Main] uygulamasını çalıştırması ve ardından SGBD'i durdurması istenir.

2.1.14.6. HSQLDB

HSQLDB, Ekler bölümünün 5.9. paragrafında sunulmuştur. persistence.xml dosyası şöyledir:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
...
            <!-- bağlantı JDBC -->
            <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver" />
            <property name="hibernate.connection.url" value="jdbc:hsqldb:hsql://localhost" />
            <property name="hibernate.connection.username" value="sa" />
            <!-- 
                <property name="hibernate.connection.password" value="" />
            -->
...
            <!-- Lehçe -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
...
        </properties>
    </persistence-unit>
</persistence>

[InitDB] dosyasını çalıştırmak için:

  • SGBD dosyasını çalıştırın HSQL
  • conf/hsql/persistence.xml dosyasını META-INF/persistence.xml klasörüne yerleştirin
  • [InitDB] uygulamasını çalıştırın

SQL Explorer'da, JDBC ile SGBD arasındaki bağlantı şu şekildedir:

  • [1]'te: HSQL ile bağlantı
  • [2]'te: [InitDB]'in çalıştırılmasından sonra bağlantının ağaç yapısı.
  • [3]'te: [jpa01_personne] tablosunun yapısı
  • [4]: içeriği.

Bu işlem tamamlandıktan sonra, okuyucunun [Main] uygulamasını çalıştırması ve ardından SGBD'i durdurması istenir.

2.1.15. JPA uygulamasını değiştirmek

Şu anki projemizin test mimarisine geri dönelim:

Önceki çalışmada, SGBD ve [7]'i, [3] istemci kodunda hiçbir değişiklik yapmadan değiştirebildiğimizi göstermiştik. Şimdi JPA ve [6] uygulamalarını değiştiriyoruz ve bunun, [3] istemci kodu için şeffaf bir şekilde gerçekleştiğini bir kez daha gösteriyoruz. TopLink ve [http://www.oracle.com/technology/products/ias/toplink/jpa/index.html] uygulamalarını ele alalım:

2.1.15.1. Eclipse projesi

JPA uygulamasındaki değişiklik nedeniyle, mevcut projeyi bozmamak için yeni bir Eclipse projesi oluşturuyoruz. Zira yeni proje, Hibernate'inkilerle çakışabilecek kalıcılık kütüphaneleri kullanıyor:

  • [1]'te: [<exemples>/toplink/direct/personnes-entites] klasörü Eclipse projesini içerir. Bunu içe aktarın.
  • [2]'e: içe aktarılan [toplink-personnes-entites] projesi. Bu proje, [hibernate-personne-entites] projesiyle (kopyalanarak elde edildiği için) iki ayrıntı dışında aynıdır:
    • [META-INF/persistence.xml] dosyası artık [3] / Toplink katmanını yapılandırıyor
    • [jpa-hibernate] kütüphanesi, [jpa-toplink], [4] ve [5] kütüphaneleriyle değiştirilmiştir (bkz. paragraf 1.5).
  • [6]'te: [conf] klasörü, her bir SGBD dosyası için bir [persistence.xml] dosyası sürümü içerir.
  • [7]'te: [ddl] klasörü, veritabanı şemasını oluşturmaya yarayan SQL komut dosyalarını içerecektir.

JPA katmanının [META-INF/persistence.xml] dosyası tarafından yapılandırıldığını biliyoruz. Bu dosya artık bir JPA / Toplink uygulamasını yapılandırmaktadır. JPA katmanı için, SGBD ve MySQL5 ile arayüzlenen içeriği şu şekildedir:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
        <!--  sağlayıcı -->
        <provider>oracle.toplink.essentials.PersistenceProvider</provider>
        <!-- kalıcı sınıflar -->
        <class>entites.Personne</class>
        <!-- kalıcılık biriminin özellikleri -->
        <properties>
            <!-- bağlantı JDBC -->
            <property name="toplink.jdbc.driver" value="com.mysql.jdbc.Driver" />
            <property name="toplink.jdbc.url" value="jdbc:mysql://localhost:3306/jpa" />
            <property name="toplink.jdbc.user" value="jpa" />
            <property name="toplink.jdbc.password" value="jpa" />
            <property name="toplink.jdbc.read-connections.max" value="3" />
            <property name="toplink.jdbc.read-connections.min" value="1" />
            <property name="toplink.jdbc.write-connections.max" value="5" />
            <property name="toplink.jdbc.write-connections.min" value="2" />
            <!-- SGBD -->
            <property name="toplink.target-database" value="MySQL4" />
            <!--  uygulama sunucusu -->
            <property name="toplink.target-server" value="None" />
            <!--  şema oluşturma -->
            <property name="toplink.ddl-generation" value="drop-and-create-tables" />
            <property name="toplink.application-location" value="ddl/mysql5" />
            <property name="toplink.create-ddl-jdbc-file-name" value="create.sql" />
            <property name="toplink.drop-ddl-jdbc-file-name" value="drop.sql" />
            <property name="toplink.ddl-generation.output-mode" value="both" />
            <!-- günlükler -->
            <property name="toplink.logging.level" value="OFF" />
        </properties>
    </persistence-unit>
</persistence>
  • 3. satır: değişmemiştir
  • 5. satır: sağlayıcı artık Toplink'tir. Burada adı geçen sınıf, [jpa-toplink] kütüphanesinde (aşağıda [1] olarak gösterilmiştir) bulunacaktır:
  • 7. satır: <class> etiketi, projedeki tüm @Entity sınıflarını belirtmek için kullanılır; burada sadece Personne sınıfı belirtilmiştir. Hibernate'de, bu sınıfları tek tek belirtmemizi gerektirmeyen bir yapılandırma seçeneği vardı. Projedeki classpath dosyasını tarayarak @Entity sınıflarını bulurdu.
  • 9. satır: <properties> etiketi, kullanılan JPA uygulamasına özgü özellikleri tanımlar; burada Toplink kullanılmaktadır.
  • 11-14. satırlar: SGBD ve MySQL5 ile JDBC bağlantısının yapılandırılması
  • 15-18. satırlar: Toplink tarafından yerel olarak yönetilen JDBC bağlantı havuzunun yapılandırması:
  • 15. ve 16. satırlar: Okuma bağlantı havuzundaki maksimum ve minimum bağlantı sayısı. Varsayılan (2,2)
  • satır 17, 18: yazma bağlantı havuzundaki maksimum ve minimum bağlantı sayısı. Varsayılan (10,2)
  • 20. satır: Hedef SGBD. Kullanılabilir SGBD listesi, [oracle.toplink.essentials.platform.database] paketinde mevcuttur (yukarıdaki [2]'e bakınız). SGBD ve MySQL5, [2] listesinde bulunmadığından, MySQL4 seçilmiştir. Toplink, Hibernate’e kıyasla biraz daha az SGBD’i desteklemektedir. Dolayısıyla, örneklerimizde kullanılan yedi SGBD’ten Firebird desteklenmemektedir. Listede Oracle de bulunmamaktadır. Aslında bu, başka bir pakette yer almaktadır (yukarıdaki [3]). Bu iki pakette, hedef SGBD, <Sgbd>Platform.class sınıfıyla belirtiliyorsa, etiket şu şekilde yazılacaktır:

            <property name="toplink.target-database" value="<Sgbd>" />
  • 22. satır: Uygulama bir uygulama sunucusunda çalışıyorsa, uygulama sunucusunu belirler. Mevcut olası değerler (None, OC4J_10_1_3, SunAS9). Varsayılan (None).
  • 24-28. satırlar: JPA katmanı başlatıldığında, 11-14. satırlardaki Jdbc bağlantısıyla tanımlanan veritabanını temizlemesi istenir. Böylece boş bir veritabanından başlanacaktır.
    • 24. satır: Toplink’ten, veritabanı şemasındaki tablolar için önce drop, ardından create komutlarını çalıştırması istenir
    • 25. satır: Toplink’ten, drop ve create işlemlerine ait SQL komut dosyalarını oluşturması istenecektir. application-location, bu komut dosyalarının oluşturulacağı klasörü belirler. Varsayılan: (geçerli klasör).
    • 26. satır: create. işlemlerine ait SQL komut dosyasının adı. Varsayılan: createDDL.jdbc.
    • 27. satır: drop. işlemlerine ait SQL komut dosyasının adı. Varsayılan: dropDDL.jdbc.
    • 28. satır: şema oluşturma modu (Varsayılan: both):
      • both: komut dosyaları ve veritabanı
      • database: Yalnızca veritabanı
      • sql-script: Yalnızca komut dosyaları
  • satır 30: Toplink günlükleri devre dışı bırakılır (OFF). Kullanılabilir farklı oturum açma seviyeleri şunlardır: OFF, SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST. Varsayılan: INFO.

Toplink ile kullanılabilen <property> etiketlerinin kapsamlı bir tanımını görmek için [http://www.oracle.com/technology/products/ias/toplink/JPA/essentials/toplink-jpa-extensions.html] URL'sine bakınız.

2.1.15.3. [InitDB] Testi

Yapılacak başka bir şey yok. İlk testi [InitDB]'i çalıştırmaya hazırız:

  • SGBD'i başlatın, burada MySQL5
  • [InitDB]'i çalıştırın
  • [1]'te: konsol ekranı. JPA / Hibernate ile daha önce elde edilen sonuçları burada da görüyoruz.
  • [3]'te: [SQL Explorer] perspektifini açıp ardından [mysql5-jpa] bağlantısını açıyoruz
  • [4]'te: jpa veritabanının dizin yapısı. [InitDB]'in çalıştırılmasıyla iki tablo oluşturulduğunu görüyoruz: beklenen [jpa01_personne] tablosu ve beklenmeyen [sequence] tablosu.
  • [5]'te: [jpa01_personne] tablosunun yapısı ve [6]'te içeriği
  • [7] dosyasında: [sequence] tablosunun yapısı ve [8] dosyasında ise içeriği.

[persistence.xml] yapılandırma dosyası, DDL komut dosyalarının oluşturulmasını talep ediyordu:


            <!--  şema oluşturma -->
            <property name="toplink.ddl-generation" value="drop-and-create-tables" />
            <property name="toplink.application-location" value="ddl/mysql5" />
            <property name="toplink.create-ddl-jdbc-file-name" value="create.sql" />
            <property name="toplink.drop-ddl-jdbc-file-name" value="drop.sql" />
<property name="toplink.ddl-generation.output-mode" value="both" />

[ddl/mysql5] klasöründe neler oluşturulduğuna bir göz atalım:

 

create.sql


CREATE TABLE jpa01_personne (ID INTEGER NOT NULL, PRENOM VARCHAR(30) NOT NULL, DATENAISSANCE DATE NOT NULL, NOM VARCHAR(30) UNIQUE NOT NULL, MARIE TINYINT(1) default 0 NOT NULL, VERSION INTEGER NOT NULL, NBENFANTS INTEGER NOT NULL, PRIMARY KEY (ID))
CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 1)
  • 1. satır: [jpa01_personne] tablosundaki DDL. Toplink'in ID birincil anahtarı için autoincrement özniteliğini kullanmadığı görülmektedir. Bu nedenle, satır ekleme işlemleri sırasında bu anahtarın otomatik olarak artışı gerçekleşmemektedir.
  • 2. satır: [sequence] tablosundaki DDL. Adından anlaşıldığı üzere, Toplink bu tabloyu ID birincil anahtarının değerlerini oluşturmak için kullanıyor gibi görünüyor.
  • 3. satır: [SEQUENCE] tablosuna tek bir satır eklenmesi

drop.sql


DROP TABLE jpa01_personne
DELETE FROM SEQUENCE WHERE SEQ_NAME = 'SEQ_GEN'
  • 1. satır: [jpa01_personne] tablosunun silinmesi
  • 2. satır: [SEQUENCE] tablosundan belirli bir satırın silinmesi. Tablonun kendisi veya içerebileceği diğer satırlar silinmez.

[SEQUENCE] tablosunun rolü hakkında daha fazla bilgi edinmek için, [persistence.xml] içinde, FINE düzeyinde Toplink günlüklerini etkinleştirin; bu düzey, Toplink tarafından verilen SQL komutlarını izler:


            <!-- günlükler -->
<property name="toplink.logging.level" value="FINE" />

InitDB komutunu yeniden çalıştırıyoruz. Aşağıda, konsol ekranının yalnızca bir kısmını gösterdik:


...
[TopLink Config]: 2007.05.28 12:07:52.796--ServerSession(12910198)--Bağlantı(30708295)--İş Parçacığı(Thread[main,5,main])--Bağlandı: jdbc:mysql://localhost:3306/jpa
    User: jpa@localhost
    Database: MySQL  Version: 5.0.37-community-nt
    Driver: MySQL-AB JDBC Driver  Version: mysql-connector-java-3.1.9 ( $Date: 2005/05/19 15:52:23 $, $Revision: 1.1.2.2 $ )
...
[TopLink Fine]: 2007.05.28 12:07:53.093--ServerSession(12910198)--Bağlantı(19255406)--İş Parçacığı(Thread[main,5,main])--DROP TABLE jpa01_personne
[TopLink Fine]: 2007.05.28 12:07:53.265--ServerSession(12910198)--Bağlantı(30708295)--İş Parçacığı(Thread[main,5,main])--CREATE TABLE jpa01_personne (ID INTEGER NOT NULL, PRENOM VARCHAR(30) NOT NULL, DATENAISSANCE DATE NOT NULL, NOM VARCHAR(30) UNIQUE NOT NULL, MARIE TINYINT(1) varsayılan 0 NOT NULL, VERSION INTEGER NOT NULL, NBENFANTS INTEGER NOT NULL, PRIMARY KEY (ID))
[TopLink Fine]: 2007.05.28 12:07:53.468--ServerSession(12910198)--Bağlantı(19255406)--İş Parçacığı(Thread[main,5,main])--CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
[TopLink Warning]: 2007.05.28 12:07:53.468--ServerSession(12910198)--İş Parçacığı(Thread[main,5,main])--İstisna [TOPLINK-4002] (Oracle TopLink Essentials - 2.0 (Derleme b41-beta2 (30.03.2007))): oracle.toplink.essentials.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: Table 'sequence' already exists
Error Code: 1050
Call: CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
Query: DataModifyQuery()
[TopLink Fine]: 2007.05.28 12:07:53.468--ServerSession(12910198)--Bağlantı(30708295)--İş Parçacığı(Thread[main,5,main])--DELETE FROM SEQUENCE WHERE SEQ_NAME = 'SEQ_GEN'
[TopLink Fine]: 2007.05.28 12:07:53.609--ServerSession(12910198)--Bağlantı(19255406)--İş Parçacığı(Thread[main,5,main])--SELECT * FROM SEQUENCE WHERE SEQ_NAME = 'SEQ_GEN'
[TopLink Fine]: 2007.05.28 12:07:53.609--ServerSession(12910198)--Bağlantı(30708295)--İş Parçacığı(Thread[main,5,main])--INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) değerleri ('SEQ_GEN', 1)
[TopLink Fine]: 2007.05.28 12:07:53.734--ClientSession(15308417)--Bağlantı(14069849)--İş Parçacığı(Thread[main,5,main])--jpa01_personne'ten sil
[TopLink Fine]: 2007.05.28 12:07:53.750--ClientSession(15308417)--Bağlantı(14069849)--İş parçacığı(Thread[main,5,main])--UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?
    bind => [50, SEQ_GEN]
[TopLink Fine]: 2007.05.28 12:07:53.750--ClientSession(15308417)--Bağlantı(14069849)--İş Parçacığı(Thread[main,5,main])--SELECT SEQ_COUNT FROM SEQUENCE WHERE SEQ_NAME = ?
    bind => [SEQ_GEN]
[personnes]
[TopLink Fine]: 2007.05.28 12:07:53.906--ClientSession(15308417)--Bağlantı(14069849)--İş Parçacığı(Thread[main,5,main])--INSERT INTO jpa01_personne (ID, PRENOM, DATENAISSANCE, NOM, MARIE, VERSION, NBENFANTS) VALUES (?, ?, ?, ?, ?, ?, ?)
    bind => [3, Sylvie, 2001-07-05, Durant, false, 1, 0]
[TopLink Fine]: 2007.05.28 12:07:53.921--ClientSession(15308417)--Bağlantı(14069849)--İş Parçacığı(Thread[main,5,main])--INSERT INTO jpa01_personne (ID, PRENOM, DATENAISSANCE, NOM, MARIE, VERSION, NBENFANTS) VALUES (?, ?, ?, ?, ?, ?, ?)
    bind => [2, Paul, 2000-01-31, Martin, true, 1, 2]
[TopLink Fine]: 2007.05.28 12:07:53.937--ClientSession(15308417)--Bağlantı(14069849)--İş Parçacığı(Thread[main,5,main])--SELECT ID, PRENOM, DATENAISSANCE, NOM, MARIE, VERSION, NBENFANTS FROM jpa01_personne ORDER BY NOM ASC
[3,1,Durant,Sylvie,05/07/2001,false,0]
[2,1,Martin,Paul,31/01/2000,true,2]
[TopLink Config]: 2007.05.28 12:07:54.062--ServerSession(12910198)--Bağlantı(30708295)--İş Parçacığı(Thread[main,5,main])--bağlantı kesildi
[TopLink Info]: 2007.05.28 12:07:54.062--ServerSession(12910198)--İş Parçacığı(Thread[main,5,main])--file:/C:/data/2006-2007/eclipse/dvp-jpa/toplink/direct/personnes-entites/bin/-jpa oturum kapatma başarılı
...
terminé ...
  • 2-5. satırlar: SGBD'e, parametreleriyle birlikte bir bağlantı. Aslında, günlükler Toplink'in SGBD ile 3 bağlantı kurduğunu gösteriyor. Bu sayının, Jdbc bağlantı havuzu için kullanılan yapılandırma değerlerinden biriyle ilişkili olup olmadığına bakmak gerekir:

            <property name="toplink.jdbc.read-connections.max" value="3" />
            <property name="toplink.jdbc.read-connections.min" value="1" />
            <property name="toplink.jdbc.write-connections.max" value="5" />
<property name="toplink.jdbc.write-connections.min" value="2" />
  • 7. satır: [jpa01_personne] tablosunun silinmesi. Bu normaldir, çünkü [persistence.xml] dosyası jpa veritabanının temizlenmesini talep etmektedir.
  • 8. satır: [jpa01_personne] tablosunun oluşturulması. ID birincil anahtarının autoincrement özniteliğine sahip olmadığı görülmektedir.
  • 9. satır: [SEQUENCE] tablosunun oluşturulması; bu tablo, önceki çalıştırma sırasında oluşturulmuş olduğundan zaten mevcuttur.
  • 10-13. satırlar: Toplink, [SEQUENCE] tablosunun oluşturulmasında hata olduğunu bildiriyor.
  • satır 15-18: Toplink, [SEQUENCE] tablosunu temizler. Bu temizleme işleminin ardından, [SEQUENCE] tablosunda ('SEQ_NAME', 'SEQ_COUNT') değerlerini içeren bir satır (SEQ_NAME, SEQ_COUNT) bulunur.
  • 18. satır: [jpa01_personne] tablosu boşaltılır.
  • 19-20. satırlar: Toplink, [SEQUENCE] tablosundaki SEQ_NAME='SEQ_GEN' olan tek satırı, ('SEQ_GEN', 1) değerinden ('SEQ_GEN', 51) değerine
  • 21. satır: Toplink, [SEQUENCE] tablosundaki ('SEQ_GEN', 51) satırından 51 değerini alır.
  • 24-27. satırlar: Toplink, [jpa01_personne] tablosuna 'Martin' ve 'Durant' adlı iki kişiyi ekler. Burada bir gizem vardır: bu iki satırın birincil anahtarlarına 2 ve 3 değerleri atanır, ancak bu değerlerin nasıl elde edildiği bilinmemektedir. 21. satırda elde edilen SEQ_COUNT (51) değerinin bir amaca hizmet edip etmediği bilinmemektedir. Satırların sürüm değerinin 1 olduğu, oysa Hibernate'in 0'dan başladığı dikkate alınmalıdır.
  • 28. satır: Toplink, SELECT tablosundaki tüm satırları almak için [jpa01_personne] tablosunu kullanır
  • 29-30. satırlar: Java istemcisi tarafından görüntülenen satırlar
  • satır 31-32: Toplink bir bağlantıyı kapatır. Başlangıçta açılan her bağlantı için bu işlemi tekrarlayacaktır.

Sonuç olarak, [SEQUENCE] tablosunun rolünü tam olarak bilmiyoruz, ancak yine de ID birincil anahtar değerlerinin oluşturulmasında bir rol oynadığı görülüyor. En ayrıntılı günlük düzeyini (FINEST) incelediğimizde, [SEQUENCE] tablosunun rolü hakkında biraz daha bilgi ediniyoruz.


            <!-- günlükleri -->
            <property name="toplink.logging.level" value="FINEST" />

Aşağıda, bu iki kişinin tabloya eklenmesiyle ilgili günlükleri sakladık. Birincil anahtar değerlerinin oluşturulma mekanizması burada görülebilir:

[TopLink Finest]: 2007.05.28 03:05:04.046--ClientSession(30617157)--İş parçacığı(Thread[main,5,main])--ValueReadQuery() sorgusu çalıştırıldı
[TopLink Fine]: 2007.05.28 03:05:04.046--ClientSession(30617157)--Bağlantı(13301441)--İş parçacığı(Thread[main,5,main])--SELECT SEQ_COUNT FROM SEQUENCE WHERE SEQ_NAME = ?
    bind => [SEQ_GEN]
[TopLink Finest]: 2007.05.28 03:05:04.062--ClientSession(30617157)--Bağlantı(13301441)--İş Parçacığı(Thread[main,5,main])--SEQ_GEN için yerel sıralama ön tahsisi: nesneler: 50, ilk: 2, son: 51
[TopLink Finest]: 2007.05.28 03:05:04.062--UnitOfWork(19864560)--İş parçacığı (Thread[main,5,main])--nesneye sıra atama (2 -> [null,0,Martin,Paul,31/01/2000,true,2])
[TopLink Finest]: 2007.05.28 03:05:04.062--UnitOfWork(19864560)--İş parçacığı(Thread[main,5,main])--Sorguyu çalıştır DoesExistQuery()
[TopLink Finest]: 2007.05.28 03:05:04.062--UnitOfWork(19864560)--İş parçacığı(Thread[main,5,main])--PERSIST işlemi şu adreste çağrıldı: [null,0,Durant,Sylvie,05/07/2001,false,0].
[TopLink Finest]: 2007.05.28 03:05:04.062--UnitOfWork(19864560)--İş parçacığı(Thread[main,5,main])--nesneye sıra atama (3 -> [null,0,Durant,Sylvie,05/07/2001,false,0])
[personnes]
[TopLink Finest]: 2007.05.28 03:05:04.203--UnitOfWork(19864560)--İş parçacığı(Thread[main,5,main])--Sorguyu çalıştır InsertObjectQuery([3,0,Durant,Sylvie,05/07/2001,false,0])
[TopLink Finest]: 2007.05.28 03:05:04.203--UnitOfWork(19864560)--İş parçacığı(Thread[main,5,main])--Dönüş satırını atama DatabaseRecord(
    jpa01_personne.VERSION => 1)
[TopLink Fine]: 2007.05.28 03:05:04.203--ClientSession(30617157)--Bağlantı(13301441)--İş parçacığı(Thread[main,5,main])--INSERT INTO jpa01_personne (ID, PRENOM, DATENAISSANCE, NOM, MARIE, VERSION, NBENFANTS) VALUES (?, ?, ?, ?, ?, ?, ?)
    bind => [3, Sylvie, 2001-07-05, Durant, false, 1, 0]
[TopLink Finest]: 2007.05.28 03:05:04.203--UnitOfWork(19864560)--Konu(Konu[main,5,main])--Sorguyu çalıştır InsertObjectQuery([2,0,Martin,Paul,31/01/2000,true,2])
[TopLink Finest]: 2007.05.28 03:05:04.203--UnitOfWork(19864560)--İş parçacığı(Thread[main,5,main])--Dönüş satırını atama DatabaseRecord(
    jpa01_personne.VERSION => 1)
[TopLink Fine]: 2007.05.28 03:05:04.203--ClientSession(30617157)--Bağlantı(13301441)--İş parçacığı(Thread[main,5,main])--INSERT INTO jpa01_personne (ID, PRENOM, DATENAISSANCE, NOM, MARIE, VERSION, NBENFANTS) VALUES (?, ?, ?, ?, ?, ?, ?)
bind => [2, Paul, 2000-01-31, Martin, true, 1, 2]
  • 4. satır: [SEQUENCE] tablosunun 2. satırından alınan 51 sayısının, birincil anahtar için bir değer aralığı sınırlamak amacıyla kullanıldığını görüyoruz: [2,51]
  • 5. satır: İlk kişiye birincil anahtar olarak 2 değeri atanır
  • 8. satır: İkinci kişiye birincil anahtar olarak 3 değeri atanır
  • 12. satır: ilk kişinin sürüm yönetimini gösterir
  • satır 17: ikinci kişi için de aynısı geçerlidir

[FINEST] günlük seviyesi, Toplink tarafından gönderilen işlemlerin sınırlarını da gösterir. Bu günlüklerin incelenmesi, Toplink'in ne yaptığını ortaya koyar ve nesne/ilişkisel köprüyü anlamak için harika bir yoldur.

Yukarıdakilerden çıkarılacak sonuç şudur:

  • Farklı JPA uygulamaları, farklı veritabanı şemaları oluşturacaktır. Bu örnekte, Hibernate ve Toplink aynı şemaları oluşturmamıştır.
  • Toplink’in FINE, FINER ve FINEST günlük düzeyleri, Toplink’in tam olarak ne yaptığını netleştirmek istendiğinde kullanılmalıdır.

2.1.15.4. [Main] Testi

Şimdi [Main] testini çalıştırıyoruz:

  • [1]'te: 11 numaralı test hariç tüm testler başarılı [2]
  • [3]'te: 376. satır, istisnanın meydana geldiği kod satırı

İstisnaya neden olan kod şöyledir:


} catch (RuntimeException e1) {
            // bir sorun yaşandı
            System.out.format("Erreur dans transaction [%s,%s,%s,%s,%s,%s]%n", e1.getClass().getName(), e1.getMessage(),
                    e1.getCause().getClass().getName(), e1.getCause().getMessage(), e1.getCause().getCause().getClass().getName(), e1.getCause().getCause()
                            .getMessage());
            try {
            ...
  • [3] satırı: istisnanın oluştuğu satır. NullPointerException değerine rastlanıyor; bu da 4. ve 5. satırlardaki getCause yöntemlerinden birinin bir null işaretçisi döndürdüğünü düşündürüyor. [e1.getCause().getCause()] gibi bir ifade, istisna zincirinin 3 [e1.getCause().getCause(), e1.getCause(), e1] öğesi içerdiğini varsayar. Eğer sadece iki öğe varsa, ilk ifade bir istisna oluşturacaktır.

İstisna dizisindeki son iki istisnayı gösterecek şekilde önceki kodu değiştiriyoruz:


        } catch (RuntimeException e1) {
            // bir sorun yaşadık
            System.out.format("Erreur dans transaction [%s,%s,%s,%s,]%n", e1.getClass().getName(), e1.getMessage(),
                    e1.getCause().getClass().getName(), e1.getCause().getMessage());
            try {
...

Çalıştırıldığında şu sonuç elde edilir:


...
[personnes]
[2,5,Martin,Paul,31/01/2000,false,6]
main : ----------- test11
[personnes]
Erreur dans transaction [javax.persistence.OptimisticLockException,Exception [TOPLINK-5006] (Oracle TopLink Essentials - 2.0 (Build b41-beta2 (03/30/2007))): oracle.toplink.essentials.exceptions.OptimisticLockException
Exception Description: The object [[2,6,Martin,Paul,31/01/2000,false,7]] cannot be updated because it has changed or been deleted since it was last read. 
Class> entites.Personne Primary Key> [2],oracle.toplink.essentials.exceptions.OptimisticLockException,
Exception Description: The object [[2,6,Martin,Paul,31/01/2000,false,7]] cannot be updated because it has changed or been deleted since it was last read. 
Class> entites.Personne Primary Key> [2],]
[personnes]
[2,5,Martin,Paul,31/01/2000,false,6]

Bu sefer 11 numaralı test başarılı oldu. İstisna mesajları (6-10. satırlar), Java kodu tarafından talep edildi (yukarıdaki kodun 3. satırı). Hatırlanacağı üzere, 11 numaralı test, aynı işlem içinde birden fazla SQL işlemini sıralıyordu; bunlardan biri başarısız oluyordu ve işlemin geri alınmasına neden oluyordu. Test öncesinde (satır 3) ve sonrasında (satır 12) [jpa01_personne] tablosunun durumları tamamen aynıdır; bu da geri almanın gerçekleştiğini göstermektedir.

Burada önemli bir noktaya dikkat çekmek gerekir: JPA / Hibernate ve JPA / Toplink uygulamaları %100 birbirinin yerine kullanılamaz. Bu örnekte, NullPointerException hatasını önlemek için JPA istemci kodunu değiştirmemiz gerekiyor. Bu soruna daha sonra ve yine bir istisna kapsamında tekrar rastlayacağız.

Şu anki projemizin test mimarisine geri dönelim:

Daha önce, [7]'te kullanılan SGBD, MySQL5 idi. Oracle ile SGBD'e nasıl geçileceğini göstereceğiz. Her halükarda, Eclipse projesinde yapılacak değişiklik basittir (aşağıya bakınız): JPA katmanının yapılandırma dosyası olan persistence.xml [1] dosyasını, projenin conf ([2] ve [3]) ile değiştirin.

2.1.16.1. Oracle 10g Express

Oracle 10g Express, Ekler bölümündeki 5.7. paragrafında tanıtılmıştır. Oracle'ın Toplink için persistence.xml dosyası şöyledir:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
        <!--  sağlayıcı -->
        <provider>oracle.toplink.essentials.PersistenceProvider</provider>
        <!-- kalıcı sınıflar -->
        <class>entites.Personne</class>
        <!-- kalıcılık biriminin özellikleri -->
        <properties>
            <!-- bağlantı JDBC -->
            <property name="toplink.jdbc.driver" value="oracle.jdbc.OracleDriver" />
            <property name="toplink.jdbc.url" value="jdbc:oracle:thin:@localhost:1521:xe" />
            <property name="toplink.jdbc.user" value="jpa" />
            <property name="toplink.jdbc.password" value="jpa" />
            <property name="toplink.jdbc.read-connections.max" value="3" />
            <property name="toplink.jdbc.read-connections.min" value="1" />
            <property name="toplink.jdbc.write-connections.max" value="5" />
            <property name="toplink.jdbc.write-connections.min" value="2" />
            <!-- SGBD -->
            <property name="toplink.target-database" value="Oracle" />
            <!--  uygulama sunucusu -->
            <property name="toplink.target-server" value="None" />
            <!--  şema oluşturma -->
            <property name="toplink.ddl-generation" value="drop-and-create-tables" />
            <property name="toplink.application-location" value="ddl/oracle" />
            <property name="toplink.create-ddl-jdbc-file-name" value="create.sql" />
            <property name="toplink.drop-ddl-jdbc-file-name" value="drop.sql" />
            <property name="toplink.ddl-generation.output-mode" value="both" />
            <!-- günlükler -->
            <property name="toplink.logging.level" value="OFF" />
        </properties>
    </persistence-unit>
</persistence>

Bu yapılandırma, SGBD ve MySQL5 için yapılan yapılandırmayla aynıdır; ancak aşağıdaki ayrıntılar farklıdır:

  • 11-14. satırlar, JDBC bağlantısını veritabanıyla yapılandırır
  • 20. satır: Hedef SGBD'i belirler
  • 25. satır: DDL'in SQL komut dosyalarını oluşturacağı klasörü belirler

[InitDB] testini çalıştırmak için:

  • Oracle SGBD'i çalıştırın
  • conf/oracle/persistence.xml dosyasını META-INF/persistence.xml klasörüne yerleştirin
  • [InitDB] uygulamasını çalıştırın

Konsolda ve [SQL Explorer] perspektifinde aşağıdaki sonuçlar elde edilir:

  • [1]: konsol ekranı
  • [2]: [oracle-jpa] bağlantısı, SQL Explorer'da
  • [3]: jpa veritabanı
  • [4]: InitDB, MySQL5'te olduğu gibi iki tablo oluşturdu: JPA01_PERSONNE ve SEQUENCE. Bazen [4]'te [BIN*] tabloları görünür. Bunlar silinmiş tablolara karşılık gelir. Bu durumu görmek için [InitDB]'i yeniden çalıştırmak yeterlidir. JPA katmanının başlatma aşaması, jpa veritabanının temizlenmesini içerir ve bu işlem sırasında [JPA01_PERSONNE] tablosu silinir:

[A]'te, [BIN] tablosu ortaya çıkar. Oracle, drop işlemine tabi tutulmuş bir tabloyu kalıcı olarak silmez, ancak onu [Recycle Bin] çöp kutusuna yerleştirir. Bu çöp kutusu, 5.7.4. paragrafında açıklanan SQL Developer aracı ile [B] olarak görüntülenebilir. [B]'te, çöp kutusunda bulunan [JPA01_PERSONNE] tablosunu silebilirsiniz. Bu işlem, [C] çöp kutusunu boşaltır. SQL Explorer'da tabloları yenilediğinizde (sağ tıklama / Refresh), BIN tablosunun artık orada olmadığını görürsünüz ([D]).

  • [5, 6]: [JPA01_PERSONNE] tablosunun yapısı ve içeriği
  • [7, 8]: [SEQUENCE] tablosunun yapısı ve içeriği

İşte bu kadar! Okuyucu şimdi Oracle üzerinde [Main] uygulamasını çalıştırmaya davet ediliyor.

2.1.16.2. Diğer SGBD'ler

Diğer SGBD'ler hakkında fazla bir şey göstermeyeceğiz. Oracle için izlenen prosedürü aynen uygulamak yeterlidir. Aşağıdaki noktalara dikkat edilmelidir:

  • Hangi SGBD olursa olsun, Toplink, ID tablosunun birincil anahtar değerlerini oluşturmak için her zaman aynı tekniği kullanır: yukarıda ayrıntılı olarak açıklanan [SEQUENCE] tablosunu kullanır.
  • Toplink, Firebird'ün SGBD'ini tanımaz. Bu tür durumlar için genel bir veritabanı mevcuttur:
                <property name="toplink.target-database" value="Auto" />

[Auto] adlı bu genel veritabanıyla, Firebird üzerinde yapılan testler SQL sözdizimi hataları nedeniyle başarısız olmaktadır. Toplink, birincil anahtar için ID türünü kullanır; ancak bu tür (SQL ve Number(10)), Firebird tarafından tanınmamaktadır. Bu durumda, Firebird ile aynı türlere (bu örnekte) sahip bir SGBD seçilmelidir. Apache Derby'de durum böyledir:


            <!-- bağlantı JDBC -->
            <property name="toplink.jdbc.driver" value="org.firebirdsql.jdbc.FBDriver" />
...
            <!-- SGBD -->
            <!-- 
            TopLink ne reconnaît pas Firebird pour l'instant (05/07). Derby convient pour remplacer.
            -->
            <property name="toplink.target-database" value="Derby" />
...
  • Toplink, SGBD ve HSQLDB için veritabanının orijinal şemasını oluşturamaz. Yani şu yönerge:

            <!--  şema oluşturma -->
<property name="toplink.ddl-generation" value="drop-and-create-tables" />

komutu, HSQLDB için başarısız oluyor. Bunun nedeni, [jpa01_personne] tablosunun oluşturulmasında bir sözdizimi hatasıdır:


[TopLink Fine]: 2007.05.29 09:44:18.515--ServerSession(12910198)--Bağlantı(29775659)--İş Parçacığı(Thread[main,5,main])--DROP TABLE jpa01_personne
[TopLink Fine]: 2007.05.29 09:44:18.531--ServerSession(12910198)--Bağlantı(29775659)--İş Parçacığı(Thread[main,5,main])--CREATE TABLE jpa01_personne (ID INTEGER NOT NULL, PRENOM VARCHAR(30) NOT NULL, DATENAISSANCE DATE NOT NULL, NOM VARCHAR(30) UNIQUE NOT NULL, MARIE TINYINT NOT NULL, VERSION INTEGER NOT NULL, NBENFANTS INTEGER NOT NULL, PRIMARY KEY (ID))
[TopLink Warning]: 2007.05.29 09:44:18.531--ServerSession(12910198)--İş Parçacığı(Thread[main,5,main])--İstisna [TOPLINK-4002] (Oracle TopLink Essentials - 2.0 (Derleme b41-beta2 (30.03.2007))): oracle.toplink.essentials.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: Unexpected token: UNIQUE in statement [CREATE TABLE jpa01_personne (ID INTEGER NOT NULL, PRENOM VARCHAR(30) NOT NULL, DATENAISSANCE DATE NOT NULL, NOM VARCHAR(30) UNIQUE]
  1. satır, sözdizimi NOM VARCHAR(30) UNIQUE NOT NULL sözdizimi, HSQL tarafından kabul edilmiyor. Hibernate şu sözdizimini kullanmıştı: NOM VARCHAR(30) NOT NULL, UNIQUE(NOM).

Genel olarak, Hibernate, bu belgedeki testlerin yapıldığı SGBD'leri tanımada Toplink'ten daha etkili olmuştur.

2.1.17. Sonuç

@Entity [Personne] üzerine yapılan inceleme burada sona ermektedir. Kavramsal açıdan pek fazla bir şey yapılmadı: nesne-ilişkisel köprüyü en basit durumda inceledik: bir @Entity nesnesi <--> bir tablo. Ancak bu inceleme, belge boyunca kullanacağımız araçları tanıtmamızı sağladı. Bu sayede, bundan sonra inceleyeceğimiz diğer nesne-ilişkisel köprü durumlarını biraz daha hızlı ele alabileceğiz:

  • Önceki @Entity [Personne]'e, [Adresse] sınıfıyla modellenen adresse alanını ekleyeceğiz. Veritabanı tarafında ise iki olası uygulama göreceğiz. [Personne] ve [Adresse] nesneleri,
  • tek bir [personne] tablosu oluşturur; bu tablo,
  • iki tablo olan [personne] ve [adresse]'i içeren tek bir [personne] tablosu oluşturur.
  • bir-çok ilişkisi örneği: [article] tablosu, bir yabancı anahtar aracılığıyla [categorie] tablosuna bağlanır
  • Çok-çok ilişkisi örneği: [personne] ve [activite] tabloları, [personne_activite] birleştirme tablosu aracılığıyla birbirine bağlanmıştır.

2.2. Örnek 2: Dahil etme yoluyla tek-tek ilişki

2.2.1. Veritabanı şeması

 
1
2

    drop table if exists jpa02_personne;

    create table jpa02_personne (
        id bigint not null auto_increment,
        version integer not null,
        nom varchar(30) not null unique,
        prenom varchar(30) not null,
        datenaissance date not null,
        marie bit not null,
        nbenfants integer not null,
        adr1 varchar(30) not null,
        adr2 varchar(30),
        adr3 varchar(30),
        codePostal varchar(5) not null,
        ville varchar(20) not null,
        cedex varchar(3),
        pays varchar(20) not null,
        primary key (id)
) ENGINE=InnoDB;

  • [1]'te: veritabanı (Azurri Clay eklentisi)
  • [2]: Hibernate tarafından MySQL5 için oluşturulan DDL

[jpa02_personne] tablosu, daha önce incelenen [jpa01_personne] tablosuna bir adres eklenmiş halidir (DDL'in 12-18. satırları).

2.2.2. Veritabanını temsil eden @Entity nesneleri

Bir kişinin adresi, aşağıdaki [Adresse] sınıfı ile temsil edilecektir:


package entites;

...
@SuppressWarnings("serial")
@Embeddable
public class Adresse implements Serializable {

    // alanlar
    @Column(length = 30, nullable = false)
    private String adr1;

    @Column(length = 30)
    private String adr2;

    @Column(length = 30)
    private String adr3;

    @Column(length = 5, nullable = false)
    private String codePostal;

    @Column(length = 20, nullable = false)
    private String ville;

    @Column(length = 3)
    private String cedex;

    @Column(length = 20, nullable = false)
    private String pays;

    // yapıcılar
    public Adresse() {

    }

    public Adresse(String adr1, String adr2, String adr3, String codePostal, String ville, String cedex, String pays) {
...
    }

    // getter ve setter'lar
...

    // toString
    public String toString() {
        return String.format("A[%s,%s,%s,%s,%s,%s,%s]", getAdr1(), getAdr2(), getAdr3(), getCodePostal(), getVille(), getCedex(), getPays());
    }
}
  • En önemli yenilik, 5. satırdaki @Embeddable anotasyonundadır. [Adresse] sınıfı bir tablo oluşturmak için tasarlanmadığından, @Entity anotasyonuna sahip değildir. @Embeddable anotasyonu, sınıfın bir @Entity nesnesine ve dolayısıyla bu nesneyle ilişkili tabloya entegre edileceğini belirtir. Bu nedenle, veritabanı şemasında [Adresse] sınıfı ayrı bir tablo olarak değil, @Entity [Personne] ile ilişkili tablonun bir parçası olarak görünür.

@Entity [Personne], önceki sürümüne kıyasla çok az değişiklik göstermektedir: sadece adresse alanı eklenmiştir:


package entites;

...
@Entity
@Table(name = "jpa02_hb_personne")
public class Personne implements Serializable{

    @Id
    @Column(nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    @Version
    private int version;

    @Column(length = 30, nullable = false, unique = true)
    private String nom;

    @Column(length = 30, nullable = false)
    private String prenom;

    @Column(nullable = false)
    @Temporal(TemporalType.DATE)
    private Date datenaissance;

    @Column(nullable = false)
    private boolean marie;

    @Column(nullable = false)
    private int nbenfants;

    @Embedded
    private Adresse adresse;

    // yapıcılar
    public Personne() {
    }
...
}
  • değişiklik 33-34. satırlarda yapılmıştır. [Personne] nesnesi artık Adresse türünde bir adresse alanına sahiptir. Bu, POJO için geçerlidir. @Embedded ek açıklaması, nesne-ilişkisel köprü için kullanılır. Bu, [Adresse adresse] alanının [Personne] nesnesiyle aynı tabloda kapsüllenmesi gerektiğini belirtir.

2.2.3. Test ortamı

Daha önce incelediğimiz testlere çok benzer testler yapacağız. Bu testler aşağıdaki bağlamda gerçekleştirilecektir:

Kullanılan uygulama JPA / Hibernate [6]'tir. Testlerin Eclipse projesi şöyledir:

[1] adlı Eclipse projesi, önceki projeden yalnızca [2] adlı Java kodlarıyla farklılık göstermektedir. Ortam (kütüphaneler – persistence.xml – veritabanı sistemi – yapılandırma ve DDL klasörleri – Ant komut dosyası), özellikle 2.1.5. paragrafında daha önce incelenen ortamdır. Bu durum, gelecekteki Hibernate projeleri için de geçerli olacak ve istisnai durumlar haricinde bu ortama tekrar değinmeyeceğiz. Özellikle, farklı SGBD'ler için JPA/Hibernate katmanını yapılandıran persistence.xml dosyaları, daha önce incelenen ve <conf> klasöründe bulunan dosyalardır.

İzlenecek prosedürler konusunda herhangi bir şüphe duyulursa, okuyucunun önceki çalışmada izlenen prosedürlere tekrar göz atması önerilir.

Eclipse projesi, örnekler klasöründe ([4]) [3] adıyla bulunmaktadır. Bu projeyi içe aktaracağız.

2.2.4. Veritabanından DDL dosyasının oluşturulması

2.1.7 numaralı paragrafta verilen talimatları izleyerek, SGBD MySQL5 için elde edilen DDL dosyası şöyledir:


    drop table if exists jpa02_hb_personne;

    create table jpa02_hb_personne (
        id bigint not null auto_increment,
        version integer not null,
        nom varchar(30) not null unique,
        prenom varchar(30) not null,
        datenaissance date not null,
        marie bit not null,
        nbenfants integer not null,
        adr1 varchar(30) not null,
        adr2 varchar(30),
        adr3 varchar(30),
        codePostal varchar(5) not null,
        ville varchar(20) not null,
        cedex varchar(3),
        pays varchar(20) not null,
        primary key (id)
) ENGINE=InnoDB;

Hibernate, kişinin adresinin @Entity Personne ile ilişkili tabloya eklenmesi gerektiğini doğru bir şekilde tanımıştır (11-17. satırlar).

2.2.5. InitDB

[InitDB] kodunun içeriği şöyledir:


package tests;
...

public class InitDB {

    // sabitler
    private final static String TABLE_NAME = "jpa02_hb_personne";

    public static void main(String[] args) throws ParseException {

        // kalıcılık bağlamı
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
        EntityManager em = null;
        // önceki EntityManagerFactory'ten bir EntityManager alınır
        em = emf.createEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // istek
        Query sql1;
        // PERSONNE tablosundaki öğeleri sil
        sql1 = em.createNativeQuery("delete from " + TABLE_NAME);
        sql1.executeUpdate();
        // kişi oluşturma
        Personne p1 = new Personne("Martin", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
        Personne p2 = new Personne("Durant", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
        // adreslerin oluşturulması
        Adresse a1 = new Adresse("8 rue Boileau", null, null, "49000", "Angers", null, "France");
        Adresse a2 = new Adresse("Apt 100", "Les Mimosas", "15 av Foch", "49002", "Angers", "03", "France");
        // kişi <--> adres ilişkilendirmeleri
        p1.setAdresse(a1);
        p2.setAdresse(a2);
        // kişilerin kalıcılığı
        em.persist(p1);
        em.persist(p2);
        // kişilerin görüntülenmesi
        System.out.println("[personnes]");
        for (Object p : em.createQuery("select p from Personne p order by p.nom asc").getResultList()) {
            System.out.println(p);
        }
        // işlemin sonu
        tx.commit();
        // EntityManager'in sonu
        em.close();
        // EntityManagerFactory'in sonu
        emf.close();
        // günlük
        System.out.println("terminé...");

    }
}

Bu kodda yeni bir şey yok. Her şey daha önce karşımıza çıkmıştı. [InitDB]'in MySQL5 ile birlikte çalıştırılması şu sonuçları verir:

  • [1]: konsol çıktısı
  • [2]: [jpa02_hb_personne] tablosu, SQL Explorer görünümünde
  • [3] ve [4]: yapısı ve içeriği.

2.2.6. Ana Sayfa

[Main] sınıfı şu şekildedir:


package tests;

...
import entites.Adresse;
import entites.Personne;

@SuppressWarnings( { "unused", "unchecked" })
public class Main {

    // sabitler
    private final static String TABLE_NAME = "jpa02_hb_personne";

    // Kalıcılık bağlamı
    private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");

    private static EntityManager em = null;

    // paylaşılan nesneler
    private static Personne p1, p2, newp1;

    private static Adresse a1, a2, a3, a4, newa1, newa4;

    public static void main(String[] args) throws Exception {
        // EntityManager, EntityManagerFactory'ten alınır
        em = emf.createEntityManager();

        // veritabanı temizliği
        log("clean");clean();

        // tablo dökümü
        dumpPersonne();

        // test1
        log("test1"); test1();

        // test2
        log("test2"); test2();

        // test3
        log("test3"); test3();

        // test4
        log("test4"); test4();

        // test5
        log("test5");test5();

        // kalıcılık bağlamı sonu
        if (em != null && em.isOpen())
            em.close();

        // EntityManagerFactory kapatma
        emf.close();
    }

    // mevcut EntityManager'i al
    private static EntityManager getEntityManager() {
...
    }

    // yeni bir EntityManager almak
    private static EntityManager getNewEntityManager() {
...
    }

    // Kişi tablosunun içeriğini görüntüle
    private static void dumpPersonne() {
...
    }

    // BD'i sıfırla
    private static void clean() {
    ...
    }

    // günlükler
    private static void log(String message) {
...
    }

    // nesne oluşturma
    public static void test1() throws ParseException {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // kişi oluşturma
        p1 = new Personne("Martin", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
        p2 = new Personne("Durant", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
        // adreslerin oluşturulması
        a1 = new Adresse("8 rue Boileau", null, null, "49000", "Angers", null, "France");
        a2 = new Adresse("Apt 100", "Les Mimosas", "15 av Foch", "49002", "Angers", "03", "France");
        // kişi <--> adres ilişkilendirmeleri
        p1.setAdresse(a1);
        p2.setAdresse(a2);
        // işlemin başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // kişilerin kalıcılığı
        em.persist(p1);
        em.persist(p2);
        // işlemin sonu
        tx.commit();
        // döküm
        dumpPersonne();
    }

    // bağlamdaki bir nesneyi değiştirme
    public static void test2() {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // işlemin başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // p1'in çocuk sayısını artırma
        p1.setNbenfants(p1.getNbenfants() + 1);
        // medeni durumu değiştiriliyor
        p1.setMarie(false);
        // p1 nesnesi otomatik olarak kaydedilir (kirli kontrol)
        // bir sonraki senkronizasyonda (commit veya select)
        // işlem sonu
        tx.commit();
        // yeni tablo görüntülenir
        dumpPersonne();
    }

    // kalıcılık bağlamına ait bir nesneyi sil
    public static void test4() {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // p2'ye bağlı nesne siliniyor
        em.remove(p2);
        // işlemin sonu
        tx.commit();
        // yeni tablo görüntüleniyor
        dumpPersonne();
    }

    // ayırma, yeniden ekleme ve değiştirme
    public static void test5() {
        // yeni kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // p1'i yeni bağlama yeniden bağlama
        p1 = em.find(Personne.class, p1.getId());
        // işlem sonu
        tx.commit();
        // p1'in adresi değiştiriliyor
        p1.getAdresse().setVille("Paris");
        // yeni tablo görüntüleniyor
        dumpPersonne();
    }

}

Yine, daha önce görülmemiş hiçbir şey yok. Konsol çıktısı şu şekildedir:

main : ----------- clean
[personnes]
main : ----------- test1
[personnes]
P[2,0,Durant,Sylvie,05/07/2001,false,0,A[Apt 100,Les Mimosas,15 av Foch,49002,Angers,03,France]]
P[1,0,Martin,Paul,31/01/2000,true,2,A[8 rue Boileau,null,null,49000,Angers,null,France]]
main : ----------- test2
[personnes]
P[2,0,Durant,Sylvie,05/07/2001,false,0,A[Apt 100,Les Mimosas,15 av Foch,49002,Angers,03,France]]
P[1,1,Martin,Paul,31/01/2000,false,3,A[8 rue Boileau,null,null,49000,Angers,null,France]]
main : ----------- test4
[personnes]
P[1,1,Martin,Paul,31/01/2000,false,3,A[8 rue Boileau,null,null,49000,Angers,null,France]]
main : ----------- test5
[personnes]
P[1,2,Martin,Paul,31/01/2000,false,3,A[8 rue Boileau,null,null,49000,Paris,null,France]]

Okuyucunun sonuçlarla kod arasında bağlantı kurması istenir.

Şimdi bir JPA / Toplink uygulaması kullanıyoruz:

Yeni Eclipse test projesi şöyledir:

Java kodları, önceki Hibernate projesindekilerle aynıdır. Ortam (kütüphaneler – persistence.xml – veritabanı sistemi – conf ve ddl klasörleri – ant komut dosyası), 2.1.15.2 numaralı paragrafta daha önce incelenen ortamdır. Gelecekteki Toplink projeleri için de durum aynı olacak ve istisnai durumlar haricinde bu ortama tekrar değinmeyeceğiz. Özellikle, farklı SGBD'ler için JPA/Toplink katmanını yapılandıran persistence.xml dosyaları, daha önce incelenen ve <conf> klasöründe bulunan dosyalardır.

İzlenecek prosedürler konusunda herhangi bir şüphe duyulursa, okuyucunun önceki çalışmada izlenen prosedürlere tekrar göz atması önerilir.

Eclipse projesi, [3] adıyla [4] örnekler klasöründe bulunmaktadır. Bu projeyi içe aktaracağız.

[InitDB] dosyasının SGBD ve MySQL5 dosyalarıyla birlikte çalıştırılması şu sonuçları verir:

  • [1]: konsol çıktısı
  • [2]: [jpa02_tl_personne] ve [SEQENCE] tabloları, SQL Explorer görünümünde
  • [3] ve [4]: [jpa02_tl_personne]'in yapısı ve içeriği.

ddl/mysql5 dizininde oluşturulan SQL komut dosyaları şunlardır:

create.sql


CREATE TABLE jpa02_tl_personne (ID BIGINT NOT NULL, PRENOM VARCHAR(30) NOT NULL, DATENAISSANCE DATE NOT NULL, VERSION INTEGER NOT NULL, MARIE TINYINT(1) default 0 NOT NULL, NBENFANTS INTEGER NOT NULL, NOM VARCHAR(30) UNIQUE NOT NULL, CODEPOSTAL VARCHAR(5) NOT NULL, ADR1 VARCHAR(30) NOT NULL, VILLE VARCHAR(20) NOT NULL, ADR3 VARCHAR(30), CEDEX VARCHAR(3), ADR2 VARCHAR(30), PAYS VARCHAR(20) NOT NULL, PRIMARY KEY (ID))
CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 1)

drop.sql


DROP TABLE jpa02_tl_personne
DELETE FROM SEQUENCE WHERE SEQ_NAME = 'SEQ_GEN'

2.3. Örnek 3: Yabancı anahtar aracılığıyla bire-bir ilişki

2.3.1. 'taki veritabanı şeması

1
2

    alter table jpa03_hb_personne 
        drop 
        foreign key FKFBBBFDD05FE379D0;

    drop table if exists jpa03_hb_adresse;

    drop table if exists jpa03_hb_personne;

    create table jpa03_hb_adresse (
        id bigint not null auto_increment,
        version integer not null,
        adr1 varchar(30) not null,
        adr2 varchar(30),
        adr3 varchar(30),
        codePostal varchar(5) not null,
        ville varchar(20) not null,
        cedex varchar(3),
        pays varchar(20) not null,
        primary key (id)
    ) ENGINE=InnoDB;

    create table jpa03_hb_personne (
        id bigint not null auto_increment,
        version integer not null,
        nom varchar(30) not null unique,
        prenom varchar(30) not null,
        datenaissance date not null,
        marie bit not null,
        nbenfants integer not null,
        adresse_id bigint not null unique,
        primary key (id)
    ) ENGINE=InnoDB;

    alter table jpa03_hb_personne 
        add index FKFBBBFDD05FE379D0 (adresse_id), 
        add constraint FKFBBBFDD05FE379D0 
        foreign key (adresse_id) 
references jpa03_hb_adresse (id);
  • [1]'e: veritabanı. Bu sefer, kişinin adresi kendisine ait olan [adresse] tablosuna eklenmiştir. [personne] tablosu, bir yabancı anahtar aracılığıyla bu tabloya bağlanmıştır.
  • [2]: Hibernate tarafından MySQL5 için oluşturulan DDL:
    • 9-20. satırlar: @Entity nesnesi haline gelen [Adresse] sınıfına bağlanacak olan [adresse] tablosu.
    • 10. satır: [adresse] tablosunun birincil anahtarı
    • 30. satır: Tam bir adres yerine, artık [personne] tablosunda bu adresin [adresse_id] kimliği bulunur.
    • 34-38. satırlar: kişi (adresse_id), adres (id) üzerinde yabancı anahtardır.

2.3.2. Veritabanını temsil eden @Entity nesneleri

Adresi olan bir kişi artık aşağıdaki [Personne] sınıfıyla temsil edilmektedir:


package entites;
...
@Entity
@Table(name = "jpa03_hb_personne")
public class Personne implements Serializable{

    @Id
    @Column(nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    @Version
    private int version;

    @Column(length = 30, nullable = false, unique = true)
    private String nom;

    @Column(length = 30, nullable = false)
    private String prenom;

    @Column(nullable = false)
    @Temporal(TemporalType.DATE)
    private Date datenaissance;

    @Column(nullable = false)
    private boolean marie;

    @Column(nullable = false)
    private int nbenfants;

    @OneToOne(cascade = CascadeType.ALL, fetch=FetchType.LAZY)
    @JoinColumn(name = "adresse_id", unique = true, nullable = false)
    private Adresse adresse;
...
}
  • 32-34. satırlar: kişinin adresi
    • 32. satır: @OneToOne anotasyonu, bire bir ilişkisini belirtir: bir kişinin en az ve en fazla bir adresi vardır. cascade = CascadeType.ALL özniteliği, @Entity [Personne] üzerinde gerçekleştirilen her türlü işlemin (persist, merge, remove) @Entity [Adresse] üzerinde de zincirleme olarak uygulanması gerektiği anlamına gelir. em kalıcılık bağlamı açısından bu, şu anlama gelir: p bir kişi ise ve a adresi varsa:
      • açık bir em.persist(p) işlemi, örtük bir em.persist(a) işlemine yol açacaktır
      • açık bir em.merge(p) işlemi, örtük bir em.merge(a) işlemini tetikleyecektir
      • açık bir em.remove(p) işlemi, örtük bir em.remove(a) işlemine yol açar

Deneyimler, bu örtük zincirlemelerin her derde deva olmadığını göstermektedir. Geliştirici, sonunda bunların ne işe yaradığını unutur. Kodda açık işlemler tercih edilebilir. Farklı zincirleme türleri mevcuttur. @OneToOne ek açıklaması şu şekilde yazılabilirdi:


//@OneToOne(cascade = CascadeType.ALL, fetch=FetchType.LAZY)
@OneToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE}, fetch=FetchType.LAZY)

cascade özniteliği, burada istenen kaskad türlerini belirten bir sabitler dizisi değerini kabul eder.

fetch=FetchType.LAZY özniteliği, Hibernate'ten bağımlılığı son anda yüklemesini ister. Kalıcılık bağlamına bir kişi listesi eklerken, mutlaka adreslerini de eklemek istemeyebiliriz. Örneğin, bu adresi yalnızca bir kullanıcının web arayüzü aracılığıyla seçtiği belirli bir kişi için isteyebiliriz. fetch=FetchType.EAGER özniteliği ise bağımlılıkların hemen yüklenmesini ister.

  • (devam)
    • 33. satır: @JoinColumn anotasyonu, @Entity [Personne] tablosunun @Entity [Adresse] tablosundaki yabancı anahtarını tanımlar. name özniteliği, yabancı anahtar olarak kullanılan sütunun adını tanımlar. unique=true özniteliği, bire-bir ilişkisini zorunlu kılar: [adresse_id] sütununda aynı değer iki kez bulunamaz. nullable=false özniteliği, bir kişinin mutlaka bir adrese sahip olmasını zorunlu kılar.

Bir kişinin adresi artık aşağıdaki @Entity [Adresse] ile temsil edilmektedir:


package entites;

...
@Entity
@Table(name = "jpa03_hb_adresse")
public class Adresse implements Serializable {

    // alanlar
    @Id
    @Column(nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    @Version
    private int version;

    @Column(length = 30, nullable = false)
    private String adr1;

    @Column(length = 30)
    private String adr2;

    @Column(length = 30)
    private String adr3;

    @Column(length = 5, nullable = false)
    private String codePostal;

    @Column(length = 20, nullable = false)
    private String ville;

    @Column(length = 3)
    private String cedex;

    @Column(length = 20, nullable = false)
    private String pays;

    @OneToOne(mappedBy = "adresse", fetch=FetchType.LAZY)
    private Personne personne;

    // yapıcılar
    public Adresse() {

    }
...
}
  • 4. satır: [Adresse] sınıfı bir @Entity nesnesi haline gelir. Dolayısıyla, veritabanında bir tablonun konusu olacaktır.
  • 9-12. satırlar: Tüm @Entity nesneleri gibi, [Adresse]'in de bir birincil anahtarı vardır. Bu anahtara Id adı verilmiştir ve @Entity [Personne]'in Id birincil anahtarındaki standart anotasyonlarla aynı anotasyonlara sahiptir.
  • 39-40. satırlar: @Entity [Personne] ile bire-bir ilişki. Burada birkaç ince nokta vardır:
    • öncelikle personne alanı zorunlu değildir. Bu alan, bir adresten yola çıkarak o adrese sahip tek kişiye ulaşmamızı sağlar. Eğer bu kolaylığı istemeseydik, personne alanı mevcut olmazdı ve her şey yine de çalışırdı.
    • [Personne] ve [Adresse] varlıklarını birbirine bağlayan bire-bir ilişki, @Entity [Personne] içinde zaten yapılandırılmıştır:

    @OneToOne(cascade = CascadeType.ALL, fetch=FetchType.LAZY)
    @JoinColumn(name = "adresse_id", unique = true, nullable = false)
private Adresse adresse;

İki bire-bir yapılandırmanın birbiriyle çakışmaması için, biri principale, diğeri ise inverse olarak kabul edilir. Nesne-ilişkisel köprü tarafından yönetilen ilişki, principale olarak adlandırılan ilişkidir. Diğer ilişki olan inverse ise doğrudan yönetilmez; bu ilişki, principale ilişkisi aracılığıyla dolaylı olarak yönetilir. @Entity [Adresse] içinde:


@OneToOne(mappedBy = "adresse", fetch=FetchType.LAZY)
private Personne personne;

yukarıdaki bire-bir ilişkiyi oluşturan mappedBy özniteliğidir; inverse ilişkisi ise, @Entity [Personne]'in adresse alanı tarafından tanımlanan principale tek-tek ilişkisidir.

2.3.3. Eclipse / Hibernate 1 projesi

Burada kullanılan JPA uygulaması Hibernate'e aittir. Testler için kullanılan Eclipse projesi şöyledir:

Proje, [3] örnekler klasöründe ([4]) bulunmaktadır. Bunu içe aktaracağız.

2.3.4. Veritabanından DDL dosyasının oluşturulması

2.1.7 numaralı paragrafta yer alan talimatları izleyerek, SGBD ve MySQL5 için elde edilen DDL dosyası, bu paragrafın başında gösterilen dosyadır.

2.3.5. InitDB

[InitDB] kodunun değeri şöyledir:


package tests;
...
import entites.Adresse;
import entites.Personne;

public class InitDB {

    // sabitler
    private final static String TABLE_PERSONNE = "jpa03_hb_personne";

    private final static String TABLE_ADRESSE = "jpa03_hb_adresse";

    public static void main(String[] args) throws ParseException {
        // Kalıcılık bağlamı
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
        EntityManager em = null;
        // önceki EntityManagerFactory'ten elde edilir
        em = emf.createEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // istek
        Query sql1;
        // PERSONNE tablosundaki öğeleri sil
        sql1 = em.createNativeQuery("delete from " + TABLE_PERSONNE);
        sql1.executeUpdate();
        // ADRESSE tablosundaki öğeleri sil
        sql1 = em.createNativeQuery("delete from " + TABLE_ADRESSE);
        sql1.executeUpdate();
        // kişi oluşturma
        Personne p1 = new Personne("Martin", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
        Personne p2 = new Personne("Durant", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
        // adreslerin oluşturulması
        Adresse a1 = new Adresse("8 rue Boileau", null, null, "49000", "Angers", null, "France");
        Adresse a2 = new Adresse("Apt 100", "Les Mimosas", "15 av Foch", "49002", "Angers", "03", "France");
        Adresse a3 = new Adresse("x", "x", "x", "x", "x", "x", "x");
        Adresse a4 = new Adresse("y", "y", "y", "y", "y", "y", "y");
        // kişi <--> adres ilişkilendirmeleri
        p1.setAdresse(a1);
        a1.setPersonne(p1);
        p2.setAdresse(a2);
        a2.setPersonne(p2);
        // kişilerin ve adreslerinin basamaklı olarak kalıcılığı
        em.persist(p1);
        em.persist(p2);
        // ve kişilerle ilişkili olmayan a3 ve a4 adresleri
        em.persist(a3);
        em.persist(a4);
        // kişilerin görüntülenmesi
        System.out.println("[personnes]");
        for (Object p : em.createQuery("select p from Personne p order by p.nom asc").getResultList()) {
            System.out.println(p);
        }
        // adreslerin görüntülenmesi
        System.out.println("[adresses]");
        for (Object a : em.createQuery("select a from Adresse a").getResultList()) {
            System.out.println(a);
        }

        // işlemin sonu
        tx.commit();
        // son EntityManager
        em.close();
        // EntityManagerFactory sonu
        emf.close();
        // günlük
        System.out.println("terminé...");

    }
}

Sadece daha önce incelenenlere kıyasla yeni bir ilgi konusu olan hususları ele alacağız:

  • 31-32. satırlar: iki kişi oluşturulur
  • 34-37. satırlar: dört adres oluşturulur
  • 39-42. satırlar: kişiler (p1, p2) adreslerle (a1, a2) ilişkilendirilir. (a3, a4) adresleri yetimdir. Hiçbir kişi bu adreslere atıfta bulunmaz. DDL bunu mümkün kılar. Bir kişinin mutlaka bir adresi olsa da, bunun tersi geçerli değildir.
  • 44-45. satırlar: (p1, p2) kişileri kalıcı hale getiriyoruz. Bir kişiyi adresiyle ilişkilendiren bire-bir ilişkiye cascade = CascadeType.ALL özniteliği eklediğimiz için, bu iki kişinin adresleri (a1, a2) de persist işlemine tabi tutulmalıdır. Doğrulamak istediğimiz şey budur. Yetim adresler (a3, a4) için ise işlemleri açıkça yapmak zorundayız (satır 47-48).
  • 51-53. satırlar: kişi tablosunun görüntülenmesi
  • 56-57. satırlar: adres tablosunun görüntülenmesi

[InitDB]'in MySQL5 ile birlikte çalıştırılması şu sonuçları verir:

  • [1]: konsol çıktısı
  • [2]: [jpa03_hb_*] tabloları, SQL Explorer görünümünde
  • [3]: Kişiler tablosu
  • [4]: adres tablosu. Hepsi burada mevcut. Ayrıca, [adresse_id] tablosundaki sütunun [3] tablosundaki sütun ile [4] tablosundaki [id] sütunu arasındaki bağlantıya (yabancı anahtar) dikkat edilmelidir.

2.3.6. Ana Sayfa

[Main] sınıfı, inceleyeceğimiz altı testi birbirine bağlar.

2.3.6.1. Test1

Bu test şu şekildedir:


// nesne oluşturma
    public static void test1() throws ParseException {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // kişi oluşturma
        p1 = new Personne("Martin", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
        p2 = new Personne("Durant", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
        // adreslerin oluşturulması
        a1 = new Adresse("8 rue Boileau", null, null, "49000", "Angers", null, "France");
        a2 = new Adresse("Apt 100", "Les Mimosas", "15 av Foch", "49002", "Angers", "03", "France");
        a3 = new Adresse("x", "x", "x", "x", "x", "x", "x");
        a4 = new Adresse("y", "y", "y", "y", "y", "y", "y");
        // kişi <--> adres ilişkilendirmeleri
        p1.setAdresse(a1);
        a1.setPersonne(p1);
        p2.setAdresse(a2);
        a2.setPersonne(p2);
        // işlemin başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // kişilerin kalıcılığı
        em.persist(p1);
        em.persist(p2);
        // ve kişilerle ilişkili olmayan a3 ve a4 adresleri
        em.persist(a3);
        em.persist(a4);
        // işlemin sonu
        tx.commit();
        // tablolar görüntülenir
        dumpPersonne();
        dumpAdresse();
    }

Bu kod, [InitDB]'ten alınmıştır. Sonucu şöyledir:

1
2
3
4
5
6
7
8
9
main : ----------- test1
[personnes]
P[2,0,Durant,Sylvie,05/07/2001,false,0,2]
P[1,0,Martin,Paul,31/01/2000,true,2,1]
[adresses]
A[1,0,8 rue Boileau,null,null,49000,Angers,null,France]
A[2,0,Apt 100,Les Mimosas,15 av Foch,49002,Angers,03,France]
A[3,0,x,x,x,x,x,x,x]
A[4,0,y,y,y,y,y,y,y]

Her iki tablo da doldurulmuştur.

2.3.6.2. Test2

Bu test şöyledir:


    // bağlamdaki bir nesneyi değiştirme
    public static void test2() {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // p1'in çocuk sayısını artırma
        p1.setNbenfants(p1.getNbenfants() + 1);
        // medeni durumu değiştiriliyor
        p1.setMarie(false);
        // p1 nesnesi otomatik olarak kaydedilir (kirli kontrol)
        // bir sonraki senkronizasyonda (commit veya select)
        // işlem sonu
        tx.commit();
        // yeni tablo görüntülenir
        dumpPersonne();
}

Sonuç şu şekildedir:

1
2
3
4
main : ----------- test2
[personnes]
P[2,0,Durant,Sylvie,05/07/2001,false,0,2]
P[1,1,Martin,Paul,31/01/2000,false,3,1]
  • 4. satır: p1 adlı kişinin çocuk sayısı 1 arttı ve sürümü 0'dan 1'e yükseldi

2.3.6.3. Test4

Bu test şu şekildedir:


    // kalıcılık bağlamına ait bir nesneyi sil
    public static void test4() {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // p2'ye bağlı nesne siliniyor
        em.remove(p2);
        // işlemin sonu
        tx.commit();
        // yeni tablolar görüntüleniyor
        dumpPersonne();
        dumpAdresse();
}
  • 9. satır: p2 kişisi silinir. Bu kişinin a2 adresiyle basamaklı bir ilişkisi vardır. Dolayısıyla a2 adresi de silinmelidir.

Test 4'ün sonucu şöyledir:

main : ----------- test1
[personnes]
P[2,0,Durant,Sylvie,05/07/2001,false,0,2]
P[1,0,Martin,Paul,31/01/2000,true,2,1]
[adresses]
A[1,0,8 rue Boileau,null,null,49000,Angers,null,France]
A[2,0,Apt 100,Les Mimosas,15 av Foch,49002,Angers,03,France]
A[3,0,x,x,x,x,x,x,x]
A[4,0,y,y,y,y,y,y,y]
main : ----------- test2
[personnes]
P[2,0,Durant,Sylvie,05/07/2001,false,0,2]
P[1,1,Martin,Paul,31/01/2000,false,3,1]
main : ----------- test4
[personnes]
P[1,1,Martin,Paul,31/01/2000,false,3,1]
[adresses]
A[1,0,8 rue Boileau,null,null,49000,Angers,null,France]
A[3,0,x,x,x,x,x,x,x]
A[4,0,y,y,y,y,y,y,y]
  • Test 1'in 3. satırında yer alan p2 kişisi, Test 4'te artık mevcut değildir
  • Aynı durum, test 1'in 7. satırında yer alan ve test 4'te bulunmayan a2 adresi için de geçerlidir.

2.3.6.4. Test 5

Bu test şu şekildedir:


// ayırma, yeniden ekleme ve değiştirme
    public static void test5() {
        // yeni kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // p1'i yeni bağlama yeniden bağlama
        p1 = em.find(Personne.class, p1.getId());
        // p1'in adresi değiştiriliyor
        p1.getAdresse().setVille("Paris");
        // işlem sonu
        tx.commit();
        // yeni tablolar görüntüleniyor
        dumpPersonne();
        dumpAdresse();
    }
  • 4. satır: Yeni bir kalıcılık bağlamı var, dolayısıyla boş.
  • 9. satır: p1 kişisini bu bağlama ekliyoruz. p1, bağlamda bulunmadığı için veritabanında aranır. p1’e (adresine) bağlı öğeler ise, şu kod yazıldığı için veritabanından getirilmez:

    @OneToOne(..., fetch=FetchType.LAZY)

Bu, "lazy loading" veya "tam zamanında yükleme" kavramıdır: kalıcı bir nesnenin bağımlılıkları, yalnızca ihtiyaç duyulduğunda belleğe getirilir.

  • 11. satır: p1 adresinin şehir alanı değiştirilir. getAdresse nedeniyle ve p1 adresi kalıcılık bağlamında henüz bulunmuyorsa, veritabanından okunarak buraya getirilecektir.
  • 13. satır: İşlem onaylanır; bu, kalıcılık bağlamının veritabanıyla senkronize edilmesini sağlar. Kalıcılık bağlamı, p1 kişisinin adresinin değiştirildiğini tespit eder ve bunu kaydeder.

test5'in çalıştırılması aşağıdaki sonuçları verir:

main : ----------- test4
[personnes]
P[1,1,Martin,Paul,31/01/2000,false,3,1]
[adresses]
A[1,0,8 rue Boileau,null,null,49000,Angers,null,France]
A[3,0,x,x,x,x,x,x,x]
A[4,0,y,y,y,y,y,y,y]
main : ----------- test5
[personnes]
P[1,1,Martin,Paul,31/01/2000,false,3,1]
[adresses]
A[1,1,8 rue Boileau,null,null,49000,Paris,null,France]
A[3,0,x,x,x,x,x,x,x]
A[4,0,y,y,y,y,y,y,y]
  • p1 kişisinin (satır 3 test4, satır 10 test5) şehri, Angers'ten (satır 5 test4) Paris'e (satır 12 test5) değişmiştir.

2.3.6.5. Test6

Bu test şu şekildedir:


// Adres nesnesini silme
    public static void test6() {
        EntityTransaction tx = null;
        // yeni kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlem başlangıcı
        tx = em.getTransaction();
        tx.begin();
        // a3 adresini yeni bağlama yeniden ekleniyor
        a3 = em.find(Adresse.class, a3.getId());
        System.out.println(a3);
        // siliniyor
        em.remove(a3);
        // işlem sonu
        tx.commit();
        // Adres tablosu dökümü
        dumpAdresse();
    }
  • 5. satır: Yeni bir kalıcılık bağlamındayız, dolayısıyla boş.
  • satır 10: a3 adresini kalıcılık bağlamına ekliyoruz
  • 13. satır: Bu adres siliniyor. Bu, yetim bir adrestir (hiçbir kişiyle bağlantılı değildir). Dolayısıyla silinmesi mümkündür.

Yürütme sonucu şöyledir:

main : ----------- test5
[personnes]
P[1,1,Martin,Paul,31/01/2000,false,3,1]
[adresses]
A[1,1,8 rue Boileau,null,null,49000,Paris,null,France]
A[3,0,x,x,x,x,x,x,x]
A[4,0,y,y,y,y,y,y,y]
main : ----------- test6
A[3,0,x,x,x,x,x,x,x]
[adresses]
A[1,1,8 rue Boileau,null,null,49000,Paris,null,France]
A[4,0,y,y,y,y,y,y,y]
  • Test 5'teki a3 adresi (6. satır), Test 6'daki adresler listesinden (11-12. satırlar) kaybolmuştur

2.3.6.6. Test 7

Bu test şu şekildedir:


// geri alma
    public static void test7() {
        EntityTransaction tx = null;
        try {
            // yeni kalıcılık bağlamı
            EntityManager em = getNewEntityManager();
            // işlem başlangıcı
            tx = em.getTransaction();
            tx.begin();
            // a1 adresi yeni bağlama yeniden ekleniyor
            newa1 = em.find(Adresse.class, a1.getId());
            // a4 adresi yeni bağlama yeniden ekleniyor
            newa4 = em.find(Adresse.class, a4.getId());
            // bunları silmeye çalışılıyor - bir kişiye bağlı bir adres silinemeyeceği için bir istisna atılması gerekir; newa1 adresi de bu durumdadır
            em.remove(newa4);
            em.remove(newa1);
            // işlem sonu
            tx.commit();
        } catch (RuntimeException e1) {
            // bir sorun yaşandı
            System.out.format("Erreur dans transaction [%s%n%s%n%s%n%s]%n", e1.getClass().getName(), e1.getMessage(), e1.getCause(), e1.getCause()
                    .getCause());
            try {
                if (tx.isActive())
                    tx.rollback();
            } catch (RuntimeException e2) {
                System.out.format("Erreur au rollback [%s]%n", e2.getMessage());
            }
            // mevcut bağlamdan çıkılıyor
            em.clear();
        }
        // dump - geri alma işlemi nedeniyle Adres tablosunda değişiklik olmamış olmalı
        dumpAdresse();
    }
  • test7: Bir işlemin geri alınması test ediliyor
    • 6. satır: Yeni, dolayısıyla boş bir kalıcılık bağlamındayız.
    • 11. satır: a1 adresini, newa1 referansı altında kalıcılık bağlamına ekliyoruz
    • 13. satır: a4 adresini, newa4 referansı altında kalıcılık bağlamına ekliyoruz
    • 15-16. satırlar: newa1 ve newa4 adresleri silinir. newa1, p1 kişisinin adresidir ve bu nedenle veritabanında p1, yabancı anahtar aracılığıyla newa1 referansına bağlanmaktadır. Dolayısıyla, newa1'in silinmesi başarısız olacak ve işlemin commit'i sırasında kalıcılık bağlamının senkronizasyonu sırasında bir istisna tetiklenecektir (18. satır). Bu işlem bir rollback'e maruz kalacak (25. satır) ve dolayısıyla işlemin her iki işlemi de iptal edilecektir. Dolayısıyla, yasal olarak silinebilecek olan newa4 adresinin silinmediğini görmemiz gerekir.

Yürütme işlemi şu sonucu verir:


main : ----------- test6
A[3,0,x,x,x,x,x,x,x]
[adresses]
A[1,1,8 rue Boileau,null,null,49000,Paris,null,France]
A[4,0,y,y,y,y,y,y,y]
main : ----------- test7
Erreur dans transaction [javax.persistence.RollbackException
Error while commiting the transaction
org.hibernate.ObjectDeletedException: deleted entity passed to persist: [entites.Adresse#<null>]
null]
[adresses]
A[1,1,8 rue Boileau,null,null,49000,Paris,null,France]
A[4,0,y,y,y,y,y,y,y]
  • Test 7'nin adres tablosu (satır 12-13), Test 6'nınkiyle (satır 4-5) aynıdır. Geri alma işlemi gerçekleşmiş gibi görünüyor. Bununla birlikte, satır 9'daki hata mesajı bir muamma ve daha ayrıntılı incelenmesi gerekiyor. Görünüşe göre meydana gelen istisna, beklenen istisna değil. Durumu daha net görebilmek için log4j.properties'teki Hibernate günlüklerini DEBUG moduna geçirmek gerekiyor:

# Kök günlüğü seçeneği
log4j.rootLogger=ERROR, stdout

# Hibernate günlüğü seçenekleri (INFO yalnızca başlangıç mesajlarını gösterir)
log4j.logger.org.hibernate=DEBUG

Burada görüldüğü üzere, a1 adresi kalıcılık bağlamına yerleştirildiğinde, Hibernate muhtemelen @Entity [Adresse]'in bire bir ilişkisi nedeniyle p1 kişisini de buraya yerleştirmiştir:


    @OneToOne(mappedBy = "adresse", fetch=FetchType.LAZY)
private Personne personne;

Burada "LazyLoading" istenmiş olmasına rağmen, [Personne] bağımlılığı hemen yüklenmiştir. Bu durum, muhtemelen fetch=FetchType.LAZY özniteliğinin burada bir anlam ifade etmediğini göstermektedir. Daha sonra, işlemin commit edildiğinde Hibernate'in a1 ve a4 adreslerinin silinmesini hazırladığını, ancak aynı zamanda p1 kişisinin de kaydedilmesini hazırladığını görüyoruz. İşte istisna bu noktada ortaya çıkıyor: p1 kişisinin adresi üzerinde bir kaskad olduğu için, Hibernate, az önce silinmiş olan a1 adresini de kalıcı hale getirmek istiyor. İstisnayı tetikleyen Hibernate'dir, Jdbc sürücüsü değil. Bu nedenle, yukarıdaki 9. satırdaki mesaj görüntülenir. Ayrıca, 25. satırdaki rollback komutunun, işlem etkin olmaktan çıktığı için hiçbir zaman yürütülmediğini görebiliriz. Dolayısıyla, 24. satırdaki test, rollback komutunun yürütülmesini engeller.

Dolayısıyla istenen hedefe ulaşılamadı: bir geri alma işlemi göstermek. Aslında veritabanında hiçbir SQL komutu verilmemiştir. Birkaç noktayı hatırlamakta fayda var:

  • ORM komutunun ne yaptığını anlamak için ayrıntılı günlüklerin etkinleştirilmesinin önemi
  • Bir ORM, geliştiricinin işini kolaylaştırabileceği gibi, geliştiricinin bilmesi gereken davranışları gizleyerek işini zorlaştırabilir de. Burada, bir @Entity'nin bağımlılıklarının yüklenme şekli söz konusudur.

2.3.7. Eclipse / Hibernate 2 Projesi

@Entity nesnelerinin yapılandırmasını biraz değiştirmek için Eclipse / Hibernate projesini kopyalayıp yapıştırıyoruz:

Proje, [3] adıyla örnekler klasöründe ([4]) bulunmaktadır. Bunu içe aktaracağız.

Yalnızca @Entity [Adresse]'i, @Entity [Personne] ile teke tek ters ilişki kurmayacak şekilde değiştiriyoruz:


package entites;
...
@Entity
@Table(name = "jpa04_hb_adresse")
public class Adresse implements Serializable {

    // alanlar
    @Id
    @Column(nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    @Version
    private int version;

    @Column(length = 30, nullable = false)
    private String adr1;

    ...

    @Column(length = 20, nullable = false)
    private String pays;

//     @OneToOne(mappedBy = "adres", fetch=FetchType.LAZY)
//     private Kişi kişi;

    // yapıcılar
    public Adresse() {

    }
  • 25-26. satırlar: @OneToOne ters ilişkisi kaldırılır. Ters ilişkinin hiçbir zaman zorunlu olmadığını iyi anlamak gerekir. Yalnızca ana ilişki zorunludur. Ters ilişki, kolaylık sağlamak amacıyla kullanılabilir. Burada, bir adresin sahibini basit bir şekilde elde etmemizi sağlıyordu. Bir ters ilişki her zaman bir JPQL sorgusuyla değiştirilebilir. Aşağıdaki örnekte bunu göstereceğiz.

Test programları aynen alınmıştır. Bizi ilgilendiren tek test, bire bir ters ilişkinin işleyişini gördüğümüz 7 numaralı testtir. Ayrıca, Adres -> Kişi ters ilişkisi olmadan da belirli bir adrese sahip kişiyi nasıl bulabileceğimizi göstermek için 8 numaralı bir test ekliyoruz.

Test 7'de herhangi bir değişiklik yapılmamıştır. Bu testin çalıştırılması artık aşağıdaki sonuçları vermektedir (günlükler devre dışı bırakılmıştır):


main : ----------- test6
A[3,0,x,x,x,x,x,x,x]
[adresses]
A[1,1,8 rue Boileau,null,null,49000,Paris,null,France]
A[4,0,y,y,y,y,y,y,y]
main : ----------- test7
Erreur dans transaction [javax.persistence.RollbackException
Error while commiting the transaction
org.hibernate.exception.ConstraintViolationException: could not delete: [entites.Adresse#1]
java.sql.SQLException: Cannot delete or update a parent row: a foreign key constraint fails (`jpa/jpa04_hb_personne`, CONSTRAINT `FKEA3F04515FE379D0` FOREIGN KEY (`adresse_id`) REFERENCES `jpa04_hb_adresse` (`id`))]
[adresses]
A[1,1,8 rue Boileau,null,null,49000,Paris,null,France]
A[4,0,y,y,y,y,y,y,y]
  • Bu sefer beklenen istisna ortaya çıkıyor: [adresse] tablosunda, [personne] tablosundaki bir satırın yabancı anahtarı tarafından referans verilen bir satırı silmeye çalıştığımız için Jdbc sürücüsü tarafından atılan istisna. [10] satırı, hatanın nedenini açıkça ortaya koyuyor.
  • Geri alma işlemi başarıyla gerçekleştirildi: Test 7'nin sonunda, [adresse] tablosu (satır 12-13), Test 6'nın sonunda (satır 4-5) elde ettiğimiz tablo ile aynıdır.

Bu durum, önceki Eclipse projesindeki 7. testten ne farkı var? Neden burada, önceki testte karşılaşamadığımız bir Jdbc istisnası var? Çünkü @Entity [Adresse] artık @Entity [Personne] ile bire-bir ters ilişkiye sahip olmadığı için, Hibernate tarafından ayrı olarak yönetiliyor. newa1 adresi kalıcılık bağlamına alındığında, Hibernate bu adrese sahip olan p1 kişisini de aynı bağlama almadı. Dolayısıyla, newa1 ve newa4 adreslerinin silinmesi, bağlamda Personne varlıkları bulunmadan gerçekleştirilmiştir.

Peki, newa1 adresinden, bu adrese sahip p1 kişisine nasıl ulaşılabilir? Bu meşru bir sorudur. Aşağıdaki 8 numaralı test bu soruya cevap vermektedir:


// bir-bir ters ilişki
    // bir sorgu ile gerçekleştirilen JPQL
    public static void test8() {
        EntityTransaction tx = null;
        // yeni kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlem başlangıcı
        tx = em.getTransaction();
        tx.begin();
        // a1 adresi yeni bağlama yeniden eklenir
        newa1 = em.find(Adresse.class, a1.getId());
        // bu adresin sahibini alınıyor
        Personne p1 = (Personne) em.createQuery("select p from Personne p join p.adresse a where a.id=:adresseId").setParameter("adresseId", newa1.getId())
                .getSingleResult();
        // bunlar görüntülenir
        System.out.println("adresse=" + newa1);
        System.out.println("personne=" + p1);
        // işlem sonu
        tx.commit();
    }
  • 6. satır: yeni boş kalıcılık bağlamı
  • 8-9. satırlar: işlemin başlangıcı
  • 11. satır: a1 adresi kalıcılık bağlamına eklenir ve newa1 tarafından referanslanır.
  • 13. satır: newa1 adresine sahip p1 kişisi, JPQL sorgusu ile alınır. [Personne] ve [Adresse]'in bir yabancı anahtar ilişkisi ile birbirine bağlı olduğu bilinmektedir. [Personne] sınıfında, bu ilişkiyi somutlaştıran, @OneToOne etiketine sahip [adresse] alanıdır. JPQL'teki "select p from Personne p join p.adresse a" yazımı, [personne] ve [adresse] tabloları arasında bir birleştirme gerçekleştirir. Hibernate konsolunda oluşturulan eşdeğer SQL (bkz. 2.1.12. paragrafındaki örnekler) şu şekildedir:
SQL #0 tür: entites.Personne
-----------------
select
  personne0_.id as id1_,
  personne0_.version as version1_,
  personne0_.nom as nom1_,
  personne0_.prenom as prenom1_,
  personne0_.datenaissance as datenais5_1_,
  personne0_.marie as marie1_,
  personne0_.nbenfants as nbenfants1_,
  personne0_.adresse_id as adresse8_1_ 
 from
  jpa04_hb_personne personne0_ 
 inner join
  jpa04_hb_adresse adresse1_ 
on personne0_.adresse_id=adresse1_.id

İki tablonun birleştirilmesi açıkça görülüyor. Artık her kişi kendi adresiyle ilişkilendirilmiş durumda. Sadece newa1 adresiyle ilgilendiğimizi belirtmek gerekiyor. Sorgu şu şekilde olur: "select p from Personne p join p.adresse a where a.id=:adresseId". p ve a takma adlarının kullanıldığına dikkat edilmelidir. JPQL sorguları takma adlarını yoğun bir şekilde kullanır. Böylece, "from Personne p join p.adresse a" ifadesi, bir kişinin p takma adıyla, adresinin (p.adresse) ise a takma adıyla temsil edilmesini sağlar. "where a.id=:adresseId" kısıtlama işlemi, istenen satırları yalnızca adresi a olan ve :adresseId değerine sahip olan kişilere sınırlar. :adresseId bir parametre olarak adlandırılır ve JPQL komutu, parametreli bir JPQL komutudur. Yürütme sırasında bu parametreye bir değer atanmalıdır. Bu,

Query setParameter(String nomParamètre, Object valeurParamètre)

yöntemi, adıyla tanımlanan bir parametreye değer atamayı sağlar. setParameter'in, Query nesnesini döndürdüğü, tıpkı createQuery yöntemi gibi, unutulmamalıdır. Bu sayede, [em.createQuery(...).setParameter(...).getSingleResult(...)] yöntem çağrılarını ardışık olarak gerçekleştirebiliriz; zira [setParameter, getSingleResult] yöntemleri, Query arayüzüne ait yöntemlerdir. [getSingleResult] yöntemi, yalnızca tek bir sonuç döndüren Select sorguları için kullanılır. Burada durum budur.

  • 16-17. satırlar: Doğrulama amacıyla newa1 adresi ve bu adrese sahip olan p1 kişisi görüntülenir.

Elde edilen sonuç şöyledir:

1
2
3
main : ----------- test8
adresse=A[1,1,8 rue Boileau,null,null,49000,Paris,null,France]
personne=P[1,1,Martin,Paul,31/01/2000,false,3,1]

Bu sonuç doğrudur. Bu örnekten çıkarılacak ders, @entity [Adresse] ile @entity [Personne] arasındaki bire bir ters ilişkinin zorunlu olmadığıdır. Deneyim, bu ilişkinin kaldırılmasının kodun daha öngörülebilir bir davranış sergilemesine yol açtığını göstermiştir. Bu durum sıklıkla görülür.

2.3.8. Hibernate Konsolu

Önceki 8. testte, Personne ve Adresse varlıklarını birleştirmek için JPQL komutu kullanılmıştı. SQL diline benzer olsalar da, Hibernate’in JPQL, JPA veya HQL dilleri öğrenme gerektirir ve Hibernate konsolu bu amaç için mükemmeldir. 2.1.12 numaralı paragrafta tek bir tabloyu işlemek için bu konsolu daha önce kullanmıştık. Burada, yabancı anahtar ilişkisiyle birbirine bağlı iki tabloyu işlemek için bunu tekrar yapacağız.

Mevcut Eclipse projemiz için bir Hibernate konsolu oluşturalım:

  • [1]: [Hibernate Console] perspektifine geçiyoruz (Window / Open Perspective / Other)
  • [2]: [4] düğmesini kullanarak yeni bir yapılandırma oluşturuyoruz
  • [4] düğmesini kullanarak, Hibernate yapılandırmasının oluşturulacağı Java projesini seçiyoruz. Projenin adı [3]'te görüntülenir.
  • [5]'te bu yapılandırmaya istediğimiz adı veriyoruz. Burada Java projesinin adını kullandık.
  • [6]'te, JPA yapılandırmasını kullandığımızı belirtiriz; böylece araç, [META-INF/persistence.xml] dosyasını kullanması gerektiğini bilir
  • [7] dosyasında: [META-INF/persistence.xml] dosyasında, jpa adlı kalıcılık biriminin kullanılması gerektiğini belirtiriz.
  • [8] dosyasında yapılandırmayı onaylıyoruz.

Sonraki adımda, SGBD dosyasının çalıştırılması gerekir. Burada söz konusu olan dosya MySQL5'tir.

  • [1]'te: Oluşturulan yapılandırma üç dallı bir ağaç yapısı sergiler
  • [2]'te: [Configuration] dalı, konsolun kendini yapılandırmak için kullandığı nesneleri listeler: burada @Entity Personne ve Adresse.
  • [3]'te: Session Factory, EntityManager'teki JPA kavramına yakın bir Hibernate kavramıdır. [Configuration] dalındaki nesneler sayesinde nesne/ilişkisel köprüyü gerçekleştirir. [3]'te kalıcılık bağlamındaki nesneler sunulmaktadır; burada yine @Entity Personne ve Adresse yer almaktadır.
  • [4]'te: [persistence.xml]'te bulunan yapılandırma aracılığıyla erişilen veritabanı. Burada, mevcut Eclipse projemiz tarafından oluşturulan [jpa04_hb_*] tabloları yer almaktadır.
  • [1]'te, HQL adlı bir düzenleyici oluşturulur
  • HQL düzenleyicisinde,
    • [2]'te, birden fazla varsa (burada olduğu gibi) kullanılacak Hibernate yapılandırmasını seçiyoruz
    • [3]'te, çalıştırmak istediğimiz JPQL komutunu giriyoruz; burada Test 8'in JPQL komutu
    • [4]'te, komutu çalıştırın
    • [5]'te, [Hibernate Query Result] penceresinde sorgunun sonuçlarını alırız.
    • [6]'te, [Hibernate Dynamic SQL preview] penceresi, çalıştırılan SQL sorgusunu görüntülememizi sağlar.

Aynı sonuca ulaşmanın başka bir yolu:

  • [1]'te: JPQL komutu, Personne ve Adresse varlıklarının birleştirilmesini gerçekleştirir. [ref1] bu biçime "theta birleştirme" adını vermektedir.
  • [2]'te: SQL'in eşdeğeri
  • [3]'te: sonuç

Yalnızca Hibernate tarafından kabul edilen üçüncü bir biçim (HQL):

  • [1]: HQL komutu. JPQL, p.adresse.id biçimini kabul etmez. Yalnızca tek bir dolaylı düzey kabul eder.
  • [2]'te: SQL'in karşılığı. Burada, tablolar arası birleştirme işleminin önlendiği görülmektedir.
  • [3]'te: sonuç

İşte diğer örnekler:

  • [1]: adresleriyle birlikte kişilerin listesi
  • [2]: SQL'in karşılığı.
  • [3]: sonuç
  • [1]'te: adreslerin listesi; varsa sahipleriyle birlikte, yoksa hiçbir şey olmadan (sağ dış birleştirme: Personne ile ilişkisi olmayan satırları sağlayacak olan Adresse varlığı, join anahtar sözcüğünün sağındadır).
  • [2]'te: SQL'in karşılığı.
  • [3]'te: sonuç

Sadece Personne varlığının Adresse varlığıyla bir ilişkisi olduğu dikkatinizi çekecektir. Adresse varlığında personne adlı ters bire bir ilişki kaldırıldığından beri bunun tersi artık geçerli değildir. Bu ters ilişki mevcut olsaydı, şöyle yazılabilirdi:

  • [1]'te: adreslerin listesi, varsa sahipleriyle birlikte, yoksa sahip olmadan (sol dış birleştirme: Personne ile ilişkisi olmayan satırları sağlayacak olan Adresse varlığı, join anahtar kelimesinin solundadır).
  • [2]'te: SQL'in karşılığı.
  • [3]'te: sonuç

Okuyucuyu, Hibernate konsolu ile JPQL dilini denemeye şiddetle davet ediyoruz.

Şimdi bir JPA / Toplink uygulaması kullanıyoruz:

Yeni Eclipse test projesi şu şekildedir:

Java kodları, önceki Hibernate projesindekilerle aynıdır. Ortam (kütüphaneler – persistence.xml – veritabanı – conf ve ddl klasörleri – ant komut dosyası), 2.1.15.2 numaralı paragrafta incelenen ortamdır. Eclipse projesi, [4] örnek klasöründe [3] adıyla bulunmaktadır. Bu projeyi içe aktaracağız.

<persistence.xml> dosyasında, bildirilen varlıklar kısmında bir değişiklik yapılmıştır:


    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
        <!--  sağlayıcı -->
        <provider>oracle.toplink.essentials.PersistenceProvider</provider>
        <!-- kalıcı sınıflar -->
        <class>entites.Personne</class>
        <class>entites.Adresse</class>
        <!-- kalıcılık biriminin özellikleri -->
...
  • 5. ve 6. satırlar: yönetilen iki varlık

[InitDB] dosyasının SGBD ve MySQL5 dosyalarıyla birlikte çalıştırılması şu sonuçları verir:

[1]'te, konsol çıktısı; [2]'te, oluşturulan iki [jpa04_tl] tablosu; [3]'te ise oluşturulan SQL komut dosyaları. İçerikleri şu şekildedir:

create.sql


CREATE TABLE jpa04_tl_personne (ID BIGINT NOT NULL, PRENOM VARCHAR(30) NOT NULL, DATENAISSANCE DATE NOT NULL, VERSION INTEGER NOT NULL, MARIE TINYINT(1) default 0 NOT NULL, NBENFANTS INTEGER NOT NULL, NOM VARCHAR(30) UNIQUE NOT NULL, adresse_id BIGINT UNIQUE NOT NULL, PRIMARY KEY (ID))
CREATE TABLE jpa04_tl_adresse (ID BIGINT NOT NULL, ADR3 VARCHAR(30), CODEPOSTAL VARCHAR(5) NOT NULL, ADR1 VARCHAR(30) NOT NULL, VILLE VARCHAR(20) NOT NULL, VERSION INTEGER NOT NULL, CEDEX VARCHAR(3), ADR2 VARCHAR(30), PAYS VARCHAR(20) NOT NULL, PRIMARY KEY (ID))
ALTER TABLE jpa04_tl_personne ADD CONSTRAINT FK_jpa04_tl_personne_adresse_id FOREIGN KEY (adresse_id) REFERENCES jpa04_tl_adresse (ID)
CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 1)

drop.sql


ALTER TABLE jpa04_tl_personne DROP FOREIGN KEY FK_jpa04_tl_personne_adresse_id
DROP TABLE jpa04_tl_personne
DROP TABLE jpa04_tl_adresse
DELETE FROM SEQUENCE WHERE SEQ_NAME = 'SEQ_GEN'

2.4. Örnek 4: Bir-çok ilişkisi

2.4.1. 'in veritabanı şeması

1
2

    alter table jpa06_article 
        drop 
        foreign key FKFFBDD9D8ECCE8750;

    drop table if exists jpa06_article;

    drop table if exists jpa06_categorie;

    create table jpa06_article (
        id bigint not null auto_increment,
        version integer not null,
        nom varchar(30),
        categorie_id bigint not null,
        primary key (id)
    ) ENGINE=InnoDB;

    create table jpa06_categorie (
        id bigint not null auto_increment,
        version integer not null,
        nom varchar(30),
        primary key (id)
    ) ENGINE=InnoDB;

    alter table jpa06_article 
        add index FKFFBDD9D8ECCE8750 (categorie_id), 
        add constraint FKFFBDD9D8ECCE8750 
        foreign key (categorie_id) 
references jpa06_categorie (id);
  • [1] veritabanında ve [2]'te, DDL (MySQL5)

Bir ürün A(id, sürüm, ad) tam olarak bir kategoriye C(id, sürüm, ad) aittir. Bir kategori C, 0, 1 veya birden fazla ürün içerebilir. Burada bir-çok ilişki (Kategori -> Ürün) ve tersi çok-bir ilişkisi (Ürün -> Kategori) vardır. Bu ilişki, [article] tablosunun [categorie] tablosuna sahip olduğu yabancı anahtarla somutlaştırılır (DDL tablosunun 24-28. satırları).

2.4.2. Veritabanını temsil eden @Entity nesneleri

Bir ürün, aşağıdaki @Entity [Article] ile temsil edilir:


package entites;

...
@Entity
@Table(name="jpa05_hb_article")
public class Article implements Serializable {

    // alanlar
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @SuppressWarnings("unused")
    @Version
    private int version;

    @Column(length = 30)
    private String nom;

    // ana ilişki: Article (çok) -> Category (tek)
    // Article'da bir yabancı anahtar (categorie_id) ile uygulanır
    // 1 Makale mutlaka 1 Kategoriye sahiptir (nullable=false)
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "categorie_id", nullable = false)
    private Categorie categorie;

    // yapıcılar
    public Article() {
    }

    // alıcı ve ayarlayıcılar
    ...
    // toString
    public String toString() {
        return String.format("Article[%d,%d,%s,%d]", id, version, nom, categorie.getId());
    }

}
  • 9-11. satırlar: @Entity'nin birincil anahtarı
  • 13-15. satırlar: ürünün sürüm numarası
  • 17-18. satırlar: ürün adı
  • 20-25. satırlar: @Entity Article ile @Entity Categorie'i birbirine bağlayan çok-bir ilişkisi:
    • 23. satır: ManyToOne anotasyonu. "Many" (Çok), içinde bulunduğumuz @Entity Article'e, "One" (Tek) ise @Entity Categorie'e (satır 25) atıfta bulunur. Bir kategori (One), birden fazla ürün (Many) içerebilir.
    • 24. satır: ManyToOne anotasyonu, [article] tablosundaki yabancı anahtar sütununu tanımlar. Bu sütunun adı (name) categorie_id olacak ve her satırda bu sütunda bir değer bulunmalıdır (nullable=false).
    • 25. satır: Ürünün ait olduğu kategori. Bir ürün kalıcılık bağlamına alındığında, kategorisinin hemen buraya eklenmemesi istenir (fetch=FetchType.LAZY, 23. satır). Bu talebin bir anlamı olup olmadığı bilinmemektedir. Göreceğiz.

Bir kategori, aşağıdaki @Entity [Categorie] ile temsil edilir:


package entites;
...
@Entity
@Table(name="jpa05_hb_categorie")
public class Categorie implements Serializable {

    // alanlar
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @SuppressWarnings("unused")
    @Version
    private int version;

    @Column(length = 30)
    private String nom;

    // Kategori (tek) -> Makale (çok) ters ilişkisi (Makale (çok) -> Kategori (tek) ilişkisinin tersi)
    // Kategori -> Makaleler ekleme kaskadı
    // Kategori güncelleme -> Makaleler güncelleme
    // Kategori -> Makaleler silme kaskadı
    @OneToMany(mappedBy = "categorie", cascade = { CascadeType.ALL })
    private Set<Article> articles = new HashSet<Article>();

    // yapıcılar
    public Categorie() {
    }

    // alıcı ve ayarlayıcılar
...
    // toString
    public String toString() {
        return String.format("Categorie[%d,%d,%s]", id, version, nom);
    }

    // Kategori <--> Makale arasında çift yönlü ilişki
    public void addArticle(Article article) {
        // ürün, kategorinin ürün koleksiyonuna eklenir
        articles.add(article);
        // ürün kategorisi değiştirilir
        article.setCategorie(this);
    }
}
  • satır 8-11: @Entity'nin birincil anahtarı
  • 12-14. satırlar: sürümü
  • 16-17. satırlar: kategorinin adı
  • 19-24. satırlar: kategorideki makalelerin kümesi
    • 23. satır: @OneToMany anotasyonu, bir-çok ilişkisini belirtir. "One", içinde bulunduğumuz @Entity [Categorie]'i, "Many" ise 24. satırdaki [Article] türünü ifade eder: bir (One) kategori, birden fazla (Many) makaleye sahiptir.
    • 23. satır: Bu anotasyon, @Entity Article'in categorie alanına yerleştirilen ManyToOne anotasyonunun tersi (mappedBy) niteliğindedir: mappedBy=categorie. @Entity Article'in categorie alanına yerleştirilen ManyToOne ilişkisi ana ilişkidir. Bu ilişki zorunludur. Bu ilişki, @Entity Article ile @Entity Categorie'i birbirine bağlayan yabancı anahtar ilişkisini somutlaştırır. @Entity Categorie'in articles alanına yerleştirilen OneToMany ilişkisi ise ters ilişkidir. Bu ilişki zorunlu değildir. Bu, bir kategorideki öğeleri almak için bir kolaylık sağlar. Bu kolaylık olmasaydı, bu öğeler JPQL sorgusuyla alınırdı.
    • 23. satır: cascadeType.ALL, bir @Entity Categorie üzerinde yapılan işlemlerin (persist, merge, remove) bu @Entity'nin öğelerine de yayılmasını sağlar.
    • 24. satır: Bir kategorideki öğeler, Set<Article> türünde bir nesneye yerleştirilecektir. Set türü, yinelenen öğeleri kabul etmez. Dolayısıyla, aynı öğeyi Set<Article> nesnesine iki kez ekleyemeyiz. “Aynı öğe” ne anlama gelir? a öğesinin b öğesiyle aynı olduğunu belirtmek için Java, a.equals(b) ifadesini kullanır. Tüm sınıfların üst sınıfı olan Object sınıfında, a==b ise a.equals(b) ifadesi doğrudur; c.a.d. a ve b nesneleri aynı bellek konumuna sahipse. a ve b öğelerinin aynı ada sahip olması durumunda bunların aynı olduğunu belirtmek isteyebiliriz. Bu durumda, geliştirici [Article] sınıfında iki yöntemi yeniden tanımlamalıdır:
      • equals: Her iki öğenin adı aynıysa true değerini döndürmelidir
      • hashCode: equals yöntemi tarafından eşit kabul edilen iki [Article] nesnesi için aynı tamsayı değerini döndürmelidir. Burada değer, öğenin adından oluşturulacaktır. hashCode tarafından döndürülen değer herhangi bir tamsayı olabilir. Bu değer, özellikle sözlükler (Hashtable) olmak üzere çeşitli nesne kapsayıcılarında kullanılır.

OneToMany ilişkisi, Many öğesini depolamak için Set dışında başka türler de kullanabilir; örneğin List nesneleri. Bu belgede bu durumlara değinmeyeceğiz. Okuyucu bunları [ref1]'te bulabilir.

  • 38. satır: [addArticle] yöntemi, bir kategoriye bir makale eklememizi sağlar. Bu yöntem, [Categorie] ile [Article]'i birbirine bağlayan OneToMany ilişkisinin her iki ucunu da güncellemeyi sağlar.

2.4.3. Eclipse / Hibernate 1 projesi

Burada kullanılan JPA uygulaması, Hibernate'e aittir. Testler için kullanılan Eclipse projesi şöyledir:

Proje, [3] adlı örnekler klasöründe ([4]) bulunmaktadır. Bu projeyi içe aktaracağız.

2.4.4. Veritabanından DDL dosyasının oluşturulması

2.1.7 numaralı paragrafta verilen talimatları izleyerek, SGBD ve MySQL5 için elde edilen DDL dosyası, bu örneğin başında, 2.4.1 numaralı paragrafta gösterilen dosyadır.

2.4.5. InitDB

[InitDB] kodunun içeriği şöyledir:


package tests;

...
public class InitDB {

    // sabitler
    private final static String TABLE_ARTICLE = "jpa05_hb_article";

    private final static String TABLE_CATEGORIE = "jpa05_hb_categorie";

    public static void main(String[] args) {
        // Kalıcılık bağlamı
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
        EntityManager em = null;
        // önceki EntityManagerFactory'ten bir EntityManager alınır
        em = emf.createEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // istek
        Query sql1;
        // ARTICLE tablosundaki öğeleri sil
        sql1 = em.createNativeQuery("delete from " + TABLE_ARTICLE);
        sql1.executeUpdate();
        // CATEGORIE tablosundaki öğeleri sil
        sql1 = em.createNativeQuery("delete from " + TABLE_CATEGORIE);
        sql1.executeUpdate();
        // üç kategori oluştur
        Categorie categorieA = new Categorie();
        categorieA.setNom("A");
        Categorie categorieB = new Categorie();
        categorieB.setNom("B");
        Categorie categorieC = new Categorie();
        categorieC.setNom("C");
        // 3 makale oluştur
        Article articleA1 = new Article();
        articleA1.setNom("A1");
        Article articleA2 = new Article();
        articleA2.setNom("A2");
        Article articleB1 = new Article();
        articleB1.setNom("B1");
        // bunları kategorilerine bağla
        categorieA.addArticle(articleA1);
        categorieA.addArticle(articleA2);
        categorieB.addArticle(articleB1);
        // kategorileri kalıcı hale getirin ve makaleleri zincirleme (ekleme) yoluyla ekleyin
        em.persist(categorieA);
        em.persist(categorieB);
        em.persist(categorieC);
        // kategorileri görüntüle
        System.out.println("[categories]");
        for (Object p : em.createQuery("select c from Categorie c order by c.nom asc").getResultList()) {
            System.out.println(p);
        }
        // ürünleri görüntüle
        System.out.println("[articles]");
        for (Object p : em.createQuery("select a from Article a order by a.nom asc").getResultList()) {
            System.out.println(p);
        }
        // işlemi sonlandır
        tx.commit();
        // son EntityManager
        em.close();
        // son EntityMangerFactory
        emf.close();
        // günlük
        System.out.println("terminé...");

    }
}
  • 22-27. satırlar: [article] ve [categorie] tabloları boşaltılır. Yabancı anahtara sahip olan tablodan başlamak zorunda olduğumuzu unutmayalım. Eğer [categorie] tablosundan başlasaydık, [article] tablosundaki satırların referans verdiği kategorileri silmiş olurduk ve bu durum SGBD tarafından reddedilirdi.
  • 29-34. satırlar: A, B, C olmak üzere üç kategori oluşturulur
  • satır 36-41: A1, A2, B1 adlı üç ürün oluşturulur (harf, kategoriyi belirtir)
  • 43-45. satırlar: 3 ürün, ilgili kategorilerine yerleştirilir
  • 47-49. satırlar: 3 kategori kalıcılık bağlamına eklenir. Kategori -> Ürün hiyerarşisi nedeniyle, bu kategorilere ait ürünler de buraya yerleştirilecektir. Böylece oluşturulan tüm nesneler artık kalıcılık bağlamındadır.
  • 50-59. satırlar: Kategorilerin ve öğelerin listesini almak için kalıcılık bağlamı sorgulanır. Bunun, bağlamın veritabanıyla senkronize edilmesine yol açacağı bilinmektedir. İşte bu anda kategoriler ve öğeler ilgili tablolarına kaydedilecektir.

[InitDB] komutunun MySQL5 ile birlikte çalıştırılması şu sonuçları verir:

  • [1]: konsol çıktısı
  • [2]: [jpa05_hb_*] tabloları, SQL Explorer görünümünde
  • [3]: kategoriler tablosu
  • [4]: Ürün tablosu. [categorie_id]'in [4]'teki [id] ile [3]'teki bağlantısına dikkat edilmelidir (yabancı anahtar).

2.4.6. Ana Sayfa

[Main] sınıfı, veritabanını başlatmak için [InitDB] kodunu kullanan 1 ve 2 numaralı testler hariç, gözden geçirdiğimiz bir dizi testi içerir.

2.4.6.1. Test3

Bu test şöyledir:


    // belirli bir öğeyi ara
    public static void test3() {
        // yeni kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlem
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // kategori yükleme
        Categorie categorie = em.find(Categorie.class, categorieA.getId());
        // kategori ve ilgili ürünlerin görüntülenmesi
        System.out.format("Articles de la catégorie %s :%n", categorie);
        for (Article a : categorie.getArticles()) {
            System.out.println(a);
        }
        // işlemi sonlandır
        tx.commit();
}
  • 4. satır: yeni ve dolayısıyla boş bir kalıcılık bağlamı var
  • 6-7. satırlar: işlem başlangıcı
  • 9. satır: A kategorisi veritabanından kalıcılık bağlamına aktarılır
  • 11. satır: A kategorisi görüntülenir
  • 12-14. satırlar: A kategorisindeki ürünler görüntüleniyor. Burada, @Entity Categorie'in ürünlerini gösteren ters ilişki OneToMany'in önemi ortaya çıkıyor. Bu ilişkinin varlığı sayesinde, A kategorisindeki ürünleri sorgulamak için JPQL sorgusu yapmamıza gerek kalmaz. Bu ürünleri elde etmek için, articles alanının get yöntemini kullanırız.

Sonuçlar şu şekildedir:

main : ----------- test1
[categories]
Categorie[1,0,A]
Categorie[2,0,B]
Categorie[3,0,C]
[articles]
Article[1,0,A1,1]
Article[2,0,A2,1]
Article[3,0,B1,2]
main : ----------- test2
3 categorie(s) trouvée(s) :
A
B
C
3 article(s) trouvé(s) :
A1
A2
B1
main : ----------- test3
Articles de la catégorie Categorie[1,0,A] :
Article[2,0,A2,1]
Article[1,0,A1,1]
  • 20. satır: A kategorisi
  • 21-22. satırlar: A kategorisindeki iki madde

2.4.6.2. Test4

Bu test şöyledir:


    // ürün silme
    @SuppressWarnings("unchecked")
    public static void test4() {
        // yeni kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlem
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // ürün yükleme A1
        Article newarticle1 = em.find(Article.class, articleA1.getId());
        // A1 ürününün silinmesi (şu anda yüklü kategori yok)
        em.remove(newarticle1);
        // toplink: ürün kategorisinden kaldırılmalıdır, aksi takdirde test6 çöker
        // hibernate: buna gerek yok
        newarticle1.getCategorie().getArticles().remove(newarticle1);
        // işlem sonu
        tx.commit();
        // makalelerin dökümü
        dumpArticles();
}
  • Test 4, A1 maddesini siler
  • 5. satır: Yeni ve boş bir bağlamdan başlıyoruz
  • 10. satır: A1 ürünü kalıcılık bağlamına eklenir. Burada newarticle1 referans numarasıyla anılacaktır.
  • 12. satır: bağlamdan silinir
  • 15. satır: A, B ve C kategorileri ile A1, A2 ve B1 öğeleri, artık kalıcı olmasalar da yine de bellekte bulunmaktadır. Bunlar sadece kalıcılık bağlamından ayrılmıştır. A kategorisinin ürünlerinden biri olan A1 ürünü, bu kategoriden kaldırılır. Bu işlem, daha sonra A kategorisinin kalıcılık bağlamına yeniden eklenmesini mümkün kılacaktır. Aksi takdirde, A kategorisi, içinden bir tanesi silinmiş olan bir ürün grubuyla birlikte bağlanacaktır. Bu durum Hibernate için bir sorun teşkil etmese de Toplink'i çökertir.
  • 19. satır: A1 öğesinin kaybolduğunu doğrulamak için tüm öğeleri görüntülüyoruz.

Sonuçlar şu şekildedir:

1
2
3
4
main : ----------- test4
[articles]
Article[2,0,A2,1]
Article[3,0,B1,2]

A1 öğesi gerçekten silinmiştir.

2.4.6.3. Test5

Bu test şu şekildedir:


// 1 ürünün değiştirilmesi
    public static void test5() {
        // yeni kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlem
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // articleA2'in değiştirilmesi
        articleA2.setNom(articleA2.getNom() + "-");
        // articleA2, kalıcılık bağlamına geri yerleştirildi
        em.merge(articleA2);
        // işlem sonu
        tx.commit();
        // ürünlerin dökümü
        dumpArticles();
    }
  • Test 5, A2 ürününün adını değiştirir
  • 4. satır: yeni ve boş bir bağlamdan başlıyoruz
  • 9. satır: Ayrılmış öğenin adı A2 değiştirilir ve "A2-" olur.
  • 11. satır: Ayrılmış öğe A2, kalıcılık bağlamına yeniden eklenir. A2'in her zaman ayrılmış bir nesne olarak kaldığına dikkat edilmelidir. Artık kalıcılık bağlamının bir parçası olan nesne em.merge (articleA2) 'dir. Bu nesne, burada alışılageldiği gibi bir değişkende saklanmamıştır. Dolayısıyla erişilemez durumdadır.
  • 13. satır: Kalıcılık bağlamının veritabanıyla senkronizasyonu. A2 öğesi veritabanında değiştirilecek ve sürüm numarası N'den N+1'e yükseltilecektir. Ayrılmış bellek sürümü articleA2 artık geçerli değildir. A kategorisini temsil eden ayrılmış nesne için de durum aynıdır, çünkü bu nesne, ürünleri arasında articleA2'i içermektedir.
  • 15. satır: A2 öğesinin adındaki değişikliği doğrulamak için tüm öğeler görüntülenir

Sonuçlar şöyledir:

1
2
3
4
main : ----------- test5
[articles]
Article[2,1,A2-,1]
Article[3,0,B1,2]

A2 adlı ürünün adı gerçekten değiştirilmiştir.

2.4.6.4. Test6

Bu test şu şekildedir:


// 1 kategorinin ve ürünlerinin değiştirilmesi
    public static void test6() {
        // yeni kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlem
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // kategori yükleme
        categorieA = em.find(Categorie.class, categorieA.getId());
        // A kategorisindeki ürünlerin listesi
        for (Article a : categorieA.getArticles()) {
            a.setNom(a.getNom() + "-");
        }
        // kategori adının değiştirilmesi
        categorieA.setNom(categorieA.getNom() + "-");
        // işlemin sonu
        tx.commit();
        // kategorilerin ve ürünlerin dökümü
        dumpCategories();
        dumpArticles();
}
  • Test 6, A kategorisinin ve tüm ürünlerinin adını değiştirir
  • 4. satır: yeni ve boş bir bağlamdan başlıyoruz
  • 9. satır: Veritabanından A kategorisi aranır. Ayrılmış nesne merge için categorieA işlemi yapılmaz, çünkü bu nesnenin artık kullanılmayan A2 makalesine bir referansı olduğu bilinmektedir. Dolayısıyla sıfırdan başlanır.
  • 11-12. satırlar: A kategorisindeki tüm ürünlerin adını değiştiriyoruz. Yine getArticles yöntemi aracılığıyla OneToMany ters ilişkisini kullanıyoruz.
  • 15. satır: Kategorinin adı da değiştirilir
  • 17. satır: İşlemin sonu. Bağlam ile veritabanı arasında senkronizasyon yapılır. Bağlamda değiştirilen tüm nesneler veritabanında güncellenecektir.
  • 21-22. satırlar: Doğrulama amacıyla ürünler ve kategoriler görüntülenir

Sonuçlar şu şekildedir:

1
2
3
4
5
6
7
8
main : ----------- test6
[categories]
Categorie[1,2,A-]
Categorie[2,0,B]
Categorie[3,0,C]
[articles]
Article[2,2,A2--,1]
Article[3,0,B1,2]

A2 ürününün adı ve A kategorisi bir kez daha değiştirilmiştir.

2.4.6.5. Test7

Bu test şu şekildedir:


// bir kategorinin silinmesi
    public static void test7() {
        // yeni kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlem
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // catégorieB kalıcılığı ve ilişkili ürünleri zincirleme (birleştirme)
        Categorie mergedcategorieB = em.merge(categorieB);
        // kategori silme ve buna bağlı öğeleri zincirleme (delete)
        em.remove(mergedcategorieB);
        // işlemin sonu
        tx.commit();
        // kategorilerin ve ürünlerin dökümü
        dumpCategories();
        dumpArticles();
    }
  • Test 7, B kategorisini ve buna bağlı olarak bu kategorideki ürünleri siler
  • 4. satır: yeni ve boş bir bağlamdan başlıyoruz
  • 9. satır: B kategorisi, kalıcılık bağlamından ayrılmış bir nesne olarak bellekte mevcuttur. Bu kategoriyi (merge) kalıcılık bağlamına yeniden entegre ediyoruz. Buna bağlı olarak, bu kategorinin makaleleri (B1 makalesi) bir merge işlemine tabi tutulacak ve böylece kalıcılık bağlamına yeniden eklenecektir.
  • 11. satır: Artık B kategorisi bağlamda yer aldığına göre, onu silebiliriz (remove). Zincirleme olarak, bu kategorinin makaleleri de bir remove işlemine tabi tutulacaktır. Bu işlemin mümkün olmasının nedeni, 9. satırdaki merge işleminin makaleleri kalıcılık bağlamına yeniden dahil etmiş olmasıdır.
  • 13. satır: İşlemin sonu. Bağlam senkronize edilecek. remove işlemine tabi tutulmuş bağlam nesneleri veritabanından silinecek.
  • 15-16. satırlar: Doğrulama amacıyla ürünler ve kategoriler görüntülenir

Sonuçlar şu şekildedir:

1
2
3
4
5
6
main : ----------- test7
[categories]
Categorie[1,2,A-]
Categorie[3,0,C]
[articles]
Article[1,2,A2--,1]

B kategorisi ve B1 ürünü gerçekten silinmiştir.

2.4.6.6. Test8

Bu test şu şekildedir:


// istekler
    @SuppressWarnings("unchecked")
    public static void test8() {
        // yeni kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlem
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // A kategorisindeki ürünlerin listesi
        List articles = em
                .createQuery(
                        "select a from Categorie c join c.articles a where c.nom like 'A%' order by a.nom asc")
                .getResultList();
        // ürün görüntüleme sayısı
        System.out.println("Articles de la catégorie A");
        for (Object a : articles) {
            System.out.println(a);
        }
        // işlem sonu
        tx.commit();
    }
  • Test 7, ters ilişkiyi kullanmadan bir kategorideki ürünleri nasıl alabileceğimizi gösterir. Bu da, ters ilişkinin zorunlu olmadığını ortaya koyar.
  • 4. satır: Yeni ve boş bir bağlamdan başlıyoruz
  • 10. satır: Adı A ile başlayan bir kategorideki tüm makaleleri isteyen JPQL sorgusu
  • 15-17. satırlar: sorgu sonucunun görüntülenmesi.

Sonuçlar şu şekildedir:

1
2
3
main : ----------- test8
Articles de la catégorie A
Article[2,2,A2--,1]

2.4.7. Eclipse / Hibernate 2 Projesi

Eclipse / Hibernate projesini kopyalayıp yapıştırıyoruz; böylece @ManyToOne (ana) ve @Entity [Article]'in @OneToMany (ters) ilişkisi etrafında oluşturduğumuz ana ilişki / ters ilişki kavramıyla ilgili bir noktayı açıklığa kavuşturmak için Eclipse / Hibernate projesini kopyalayıp yapıştırıyoruz. Bu son ilişkinin diğerine ters olarak tanımlanmaması durumunda, veritabanı için oluşturulan şemanın daha önce oluşturulan şemadan tamamen farklı olacağını göstermek istiyoruz.

[1]'te yeni Eclipse projesi. [2] dosyasında Java kodları, [3] dosyasında ise veritabanının SQL şemasını oluşturacak olan ant komut dosyası bulunmaktadır. Proje, örnekler klasöründe [4] olarak bulunmaktadır. Bunu içe aktaracağız.

Yalnızca @Entity [Categorie]'i, @[Article] Entity'si arasındaki ilişkinin, @ManyToOne Entity'sinin @[Article] Entity'si ile @[Categorie] Entity'si arasındaki ilişkiye göre ters ilişki olarak tanımlanmaması için:


...
@Entity
@Table(name="jpa05_hb_categorie")
public class Categorie implements Serializable {

    // alanlar
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @SuppressWarnings("unused")
    @Version
    private int version;

    @Column(length = 30)
    private String nom;

    // OneToMany ilişkisi tersine çevrilemez (mappedby yok) Kategori (tek) -> Ürün (çok)
    // bir birleştirme tablosu (Categorie_Article) ile uygulanır; böylece bir kategoriden
    // bu kategorideki makalelere ulaşılabilmesi için
    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
    private Set<Article> articles = new HashSet<Article>();

    // üreticiler
...
  • 18-22. satırlar: 21. satırdaki @OneToMany ilişkisi sayesinde belirli bir kategorideki makaleleri bulma imkânını yine de korumak istiyoruz. Ancak, başka bir yerde, başka bir @Entity içinde tanımlanmış bir ana ilişkinin tersi olan bir ilişki oluşturan mappedBy özniteliğinin etkisini bilmek istiyoruz. Burada, mappedBy kaldırılmıştır.

SGBD ve MySQL5 öznitelikleriyle ant-DLL görevini (bkz. 2.1.7. paragraf) çalıştırıyoruz. Elde edilen şema şöyledir:

Aşağıdaki noktalara dikkat edilmelidir:

  • [categorie_article] ve [1] adlı yeni bir tablo oluşturulmuştur. Bu tablo daha önce mevcut değildi.
  • Bu tablo, [categorie], [2] ile [article], [3] tabloları arasında bir birleştirme tablosudur. a1 ve a2 adlı nesneler c1 kategorisine aitse, birleştirme tablosunda şu satırlar bulunur:
[c1,a1]
[c1,a2]

burada c1, a1, a2, ilgili nesnelerin birincil anahtarlarıdır.

  • [categorie_article] [1] birleştirme tablosu, bir Categorie c nesnesinden c'e ait Article a nesnelerini bulabilmek için Hibernate tarafından oluşturulmuştur. Bu tablonun oluşturulmasına neden olan ilişki @OneToMany'tir. @Entity Article'ın ana ilişkisi olan @ManyToOne'in tersi olarak tanımlanmadığı için, Hibernate, c kategorisine ait makaleleri almak için bu ana ilişkiyi kullanabileceğini bilmiyordu. Dolayısıyla başka bir yol buldu.
  • Bu örnek sayesinde, principale ve inverse ilişkileri kavramlarını daha iyi anlayabiliriz. Birisi (ters ilişki), diğerinin (ana ilişki) özelliklerini kullanır.

Bu veritabanındaki SQL şeması, MySQL5 için şu şekildedir:


    alter table jpa05_hb_categorie_jpa06_hb_article 
        drop 
        foreign key FK79D4BA1D26D17756;

    alter table jpa05_hb_categorie_jpa06_hb_article 
        drop 
        foreign key FK79D4BA1D424C61C9;

    alter table jpa06_hb_article 
        drop 
        foreign key FK4547168FECCE8750;

    drop table if exists jpa05_hb_categorie;

    drop table if exists jpa05_hb_categorie_jpa06_hb_article;

    drop table if exists jpa06_hb_article;

    create table jpa05_hb_categorie (
        id bigint not null auto_increment,
        version integer not null,
        nom varchar(30),
        primary key (id)
    ) ENGINE=InnoDB;

    create table jpa05_hb_categorie_jpa06_hb_article (
        jpa05_hb_categorie_id bigint not null,
        articles_id bigint not null,
        primary key (jpa05_hb_categorie_id, articles_id),
        unique (articles_id)
    ) ENGINE=InnoDB;

    create table jpa06_hb_article (
        id bigint not null auto_increment,
        version integer not null,
        nom varchar(30),
        categorie_id bigint not null,
        primary key (id)
    ) ENGINE=InnoDB;

    alter table jpa05_hb_categorie_jpa06_hb_article 
        add index FK79D4BA1D26D17756 (jpa05_hb_categorie_id), 
        add constraint FK79D4BA1D26D17756 
        foreign key (jpa05_hb_categorie_id) 
        references jpa05_hb_categorie (id);

    alter table jpa05_hb_categorie_jpa06_hb_article 
        add index FK79D4BA1D424C61C9 (articles_id), 
        add constraint FK79D4BA1D424C61C9 
        foreign key (articles_id) 
        references jpa06_hb_article (id);

    alter table jpa06_hb_article 
        add index FK4547168FECCE8750 (categorie_id), 
        add constraint FK4547168FECCE8750 
        foreign key (categorie_id) 
references jpa05_hb_categorie (id);
  • 19-24. satırlar, [categorie] tablosunun oluşturulması ve 33-39. satırlar, [article] tablosunun oluşturulması. Bunların önceki örnektekilerle aynı olduğu dikkatinizi çekecektir.
  • 26-31. satırlar: @Entity Categorie'in tersi olmayan @OneToMany ilişkisi nedeniyle [categorie_article] birleştirme tablosunun oluşturulması. Bu tablonun satırları [c,a] türündedir; burada c, c kategorisinin birincil anahtarıdır ve a,c kategorisine ait a ürününün birincil anahtarıdır. Bu birleştirme tablosunun birincil anahtarı, birleştirilmiş iki birincil anahtar [c,a]'ten oluşur (satır 29).
  • satır 41-45: [categorie_article] tablosundan [categorie] tablosuna yönelik yabancı anahtar kısıtlaması
  • satır 47-51: [categorie_article] tablosundan [article] tablosuna yabancı anahtar kısıtlaması
  • 53-57. satırlar: [article] tablosundan [categorie] tablosuna yönelik yabancı anahtar kısıtlaması

Okuyucunun [InitDB] ve [Main] testlerini çalıştırması önerilir. Bu testler, daha önce elde edilen sonuçlarla aynı sonuçları vermektedir. Ancak veritabanı şeması gereksiz yere karmaşıktır ve performans, önceki sürüme kıyasla düşecektir. Yeni yapılandırmanın, aynı şeyi temsil etmek için iki bağımsız ilişkiye sahip olmaktan kaynaklanan ek çatışmalara yol açıp açmadığını görmek için, ters/ana ilişki konusunu muhtemelen daha derinlemesine incelemek gerekir: [article] tablosunun [categorie] tablosuyla olan çok-bir ilişkisi.

Şimdi bir JPA / Toplink uygulaması kullanıyoruz:

Toplink ile Eclipse projesi, Hibernate ile Eclipse projesinin (sürüm 1) bir kopyasıdır:

Java kodları, önceki Hibernate - sürüm 1 - projesindekilerle aynıdır. Ortam (kütüphaneler – persistence.xml – veritabanı – conf, ddl klasörleri – ant komut dosyası), 2.1.15.2 paragrafında incelenen ortamdır. Eclipse projesi, örnekler klasöründe ([4]) [3] adıyla bulunmaktadır. Bu projeyi içe aktaracağız.

<persistence.xml> [2] dosyası, bildirilen varlıklar kısmında bir noktada değiştirilmiştir:


        ...
        <!-- kalıcı sınıflar -->
        <class>entites.Categorie</class>
        <class>entites.Article</class>
...
  • 3. ve 4. satırlar: yönetilen iki varlık

[InitDB]'in SGBD ve MySQL5 ile çalıştırılması aşağıdaki sonuçları verir:

[1]'te, konsol çıktısı; [2]'te, oluşturulan iki [jpa05_tl] tablosu; [3]'te ise oluşturulan SQL komut dosyaları bulunur. İçerikleri şöyledir:

create.sql


CREATE TABLE jpa05_tl_article (ID BIGINT NOT NULL, VERSION INTEGER, NOM VARCHAR(30), categorie_id BIGINT NOT NULL, PRIMARY KEY (ID))
CREATE TABLE jpa05_tl_categorie (ID BIGINT NOT NULL, VERSION INTEGER, NOM VARCHAR(30), PRIMARY KEY (ID))
ALTER TABLE jpa05_tl_article ADD CONSTRAINT FK_jpa05_tl_article_categorie_id FOREIGN KEY (categorie_id) REFERENCES jpa05_tl_categorie (ID)
CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 1)

drop.sql


ALTER TABLE jpa05_tl_article DROP FOREIGN KEY FK_jpa05_tl_article_categorie_id
DROP TABLE jpa05_tl_article
DROP TABLE jpa05_tl_categorie
DELETE FROM SEQUENCE WHERE SEQ_NAME = 'SEQ_GEN'

[Main] komut dosyasının çalıştırılması hatasız gerçekleşir.

Bu Eclipse projesi, bir önceki projenin kopyalanmasıyla oluşturulmuştur. Hibernate ile oluşturulduğu için, @Entity Categorie nesnesinin @OneToMany ilişkisinden mappedBy özniteliği kaldırılır.


@Entity
@Table(name = "jpa06_tl_categorie")
public class Categorie implements Serializable {

    // alanlar
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Version
    private int version;

    @Column(length = 30)
    private String nom;

    // OneToMany tersi olmayan ilişki (mappedby yok) Kategori (one) ->
    // Makale (çok)
    // bir birleştirme tablosu ile uygulanır Categorie_Article böylece
    // bir kategoriden
    // bir kategoriden
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private Set<Article> articles = new HashSet<Article>();

MySQL5 için oluşturulan SQL şeması şu şekildedir:

create.sql


CREATE TABLE jpa06_tl_categorie (ID BIGINT NOT NULL, VERSION INTEGER, NOM VARCHAR(30), PRIMARY KEY (ID))
CREATE TABLE jpa06_tl_categorie_jpa06_tl_article (Categorie_ID BIGINT NOT NULL, articles_ID BIGINT NOT NULL, PRIMARY KEY (Categorie_ID, articles_ID))
CREATE TABLE jpa06_tl_article (ID BIGINT NOT NULL, VERSION INTEGER, NOM VARCHAR(30), categorie_id BIGINT NOT NULL, PRIMARY KEY (ID))
ALTER TABLE jpa06_tl_categorie_jpa06_tl_article ADD CONSTRAINT FK_jpa06_tl_categorie_jpa06_tl_article_articles_ID FOREIGN KEY (articles_ID) REFERENCES jpa06_tl_article (ID)
ALTER TABLE jpa06_tl_categorie_jpa06_tl_article ADD CONSTRAINT jpa06_tl_categorie_jpa06_tl_article_Categorie_ID FOREIGN KEY (Categorie_ID) REFERENCES jpa06_tl_categorie (ID)
ALTER TABLE jpa06_tl_article ADD CONSTRAINT FK_jpa06_tl_article_categorie_id FOREIGN KEY (categorie_id) REFERENCES jpa06_tl_categorie (ID)
CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 1)
  • 2. satır: önceki ters olmayan @OneToMany ilişkisini somutlaştıran birleştirme tablosu.

[InitDB]'in çalıştırılması hatasız gerçekleşir, ancak [Main]'in çalıştırılması 7. testte çöker ve aşağıdaki günlükleri (FINEST) üretir:

main : ----------- test7
[TopLink Finer]: 2007.06.01 01:41:48.734--ServerSession(15290002)--Konu(Konu[main,5,main])--müşteri kazanıldı
[TopLink Finest]: 2007.06.01 01:41:48.734--UnitOfWork(26285048)--İş parçacığı(Thread[main,5,main])--Klonu referanslarla birleştir Kategori[5,1,B]
[TopLink Finest]: 2007.06.01 01:41:48.734--UnitOfWork(26285048)--Konu(Konu[main,5,main])--Mevcut nesneyi kaydet Article[6,1,B1]
[TopLink Finest]: 2007.06.01 01:41:48.734--UnitOfWork(26285048)--Konu(Konu[main,5,main])--Mevcut nesneyi kaydet: Categorie[5,1,B]
[TopLink Finest]: 2007.06.01 01:41:48.734--UnitOfWork(26285048)--İş parçacığı(Thread[main,5,main])--Silme işlemi şu nesne üzerinde gerçekleştirildi: Categorie[5,1,B]
[TopLink Finest]: 2007.06.01 01:41:48.734--UnitOfWork(26285048)--Konu(Konu[main,5,main])--Şu öğe üzerinde kaldırma işlemi gerçekleştirildi: Article[6,1,B1]
[TopLink Finer]: 2007.06.01 01:41:48.750--UnitOfWork(26285048)--İş Parçacığı(Thread[main,5,main])--iş birimi taahhüdünün başlangıcı
[TopLink Finer]: 2007.06.01 01:41:48.750--ClientSession(15014700)--Bağlantı(6330655)--İş Parçacığı(Thread[main,5,main])--işlem başlangıcı
[TopLink Finest]: 2007.06.01 01:41:48.750--UnitOfWork(26285048)--İş Parçacığı(Thread[main,5,main])--Sorguyu çalıştır DeleteObjectQuery(Article[6,1,B1])
[TopLink Fine]: 2007.06.01 01:41:48.750--ClientSession(15014700)--Bağlantı(6330655)--İş parçacığı(Thread[main,5,main])--DELETE FROM jpa06_tl_article WHERE ((ID = ?) AND (VERSION = ?))
    bind => [6, 1]
[TopLink Warning]: 2007.06.01 01:41:48.750--UnitOfWork(26285048)--İş Parçacığı(Thread[main,5,main])--Yerel İstisna Yığını: 
Exception [TOPLINK-4002] (Oracle TopLink Essentials - 2.0 (Build b41-beta2 (03/30/2007))): oracle.toplink.essentials.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`jpa/jpa06_tl_categorie_jpa06_tl_article`, CONSTRAINT `FK_jpa06_tl_categorie_jpa06_tl_article_articles_ID` FOREIGN KEY (`articles_ID`) REFERENCES `jpa06_tl_article` (`ID`))
Error Code: 1451
Call: DELETE FROM jpa06_tl_article WHERE ((ID = ?) AND (VERSION = ?))
    bind => [6, 1]
  • 3. satır: B kategorisinde merge
  • 4. satır: Bağımlı öğe B1 bağlama yerleştirildi
  • 5. satır: B kategorisi için de aynısı geçerlidir
  • 6. satır: B kategorisi için remove
  • 7. satır: remove, B1 ürününe eklenir (basamaklı olarak)
  • 8. satır: İşlemin commit kodu Java kodu tarafından talep edilir
  • 9. satır: Bir işlem başlatılıyor – yani görünüşe göre daha önce başlamamıştı.
  • 10. satır: B1 ürünü, [article] tablosundaki DELETE işlemi tarafından silinecek. Sorun işte burada. [categorie_article] birleştirme tablosu, [article] tablosundaki B1 satırına bir referans içeriyor. [article] tablosundaki B1'in silinmesi, bir yabancı anahtar kısıtlamasını ihlal edecektir.
  • 13. satır ve sonrası: istisna oluşuyor

Sonuç olarak ne söylenebilir?

  • Yine, Hibernate ve Toplink arasında bir taşınabilirlik sorunu var: Hibernate bu testi başarıyla geçmişti
  • Toplink, iki ilişkinin aslında birbirinin tersi olduğu durumlarda, birinin ana, diğerinin ters ilişki olarak tanımlanmamasını doğru şekilde desteklemiyor. Bunu kabul edebiliriz çünkü bu durum aslında bir yapılandırma hatasıdır. Örneğimizde, [article] tablosunun [categorie_article] birleştirme tablosuyla hiçbir ilişkisi yoktur. Dolayısıyla, [article] tablosu üzerinde bir işlem yapıldığında Toplink’in [categorie_article] tablosuyla çalışmaya çalışmaması doğal görünmektedir.

2.5. Örnek 5: Açık bir birleştirme tablosu içeren çok-çok ilişkisi

2.5.1. Veritabanı şeması

  • ile [1] tablosu arasında, MySQL5

[personne], [2] ve [adresse], [3] tablolarını zaten biliyoruz. Bunlar 2.3.1. paragrafında incelenmiştir. Kişinin adresinin ayrı bir tablo olan [adresse] ve [3]'te yer aldığı versiyonu ele alıyoruz. [personne] tablosunda, bir kişiyi adresiyle ilişkilendiren ilişki, bir yabancı anahtar kısıtlamasıyla ifade edilmektedir.

Bir kişi çeşitli faaliyetlerde bulunur. Bu faaliyetler [activite] ve [4] tablolarında yer alır. Bir kişi birden fazla faaliyette bulunabilir ve bir faaliyet birden fazla kişi tarafından gerçekleştirilebilir. Dolayısıyla, [personne] ve [activite] tablolarını birbirine bağlayan bir çok-çok ilişkisi vardır. Bu ilişki, [personne_activite] ve [5] birleştirme tablosu ile somutlaştırılır.

2.5.2. Veritabanını temsil eden @Entity nesneleri

Yukarıdaki tablolar aşağıdaki @Entity'lerle temsil edilecektir:

  • @Entity Personne, [personne] tablosunu temsil edecektir
  • @Entity Adresse, [adresse] tablosunu temsil edecektir
  • @Entity Activite, [activite] tablosunu temsil edecektir
  • @Entity PersonneActivite, [personne_activite] tablosunu temsil edecektir

Bu varlıklar arasındaki ilişkiler şunlardır:

  • Personne varlığı ile Adresse varlığı arasında bir-bir ilişkisi vardır: p numaralı bir kişinin a numaralı bir adresi vardır. Yabancı anahtara sahip olan Personne varlığı ana ilişkiye, Adresse varlığı ise ters ilişkiye sahip olacaktır.
  • Bir çok-çok ilişkisi, Personne ve Activite varlıklarını birbirine bağlar: bir kişi birden fazla faaliyete sahiptir ve bir faaliyet birden fazla kişi tarafından yürütülür. Bu ilişki, her iki varlıkta da @ManyToMany etiketiyle doğrudan oluşturulabilir; bu durumda varlıkların biri diğerinin tersi olarak tanımlanır. Bu çözüm daha sonra ele alınacaktır. Burada, çok-çok ilişkisini iki adet bir-çok ilişkisi aracılığıyla oluşturuyoruz:
    • Personne varlığını PersonneActivite varlığına bağlayan bir "bir-çok" ilişkisi: [personne] tablosundaki bir satır (One), [personne_activite] tablosundaki birden fazla (Many) satır tarafından referans alınır. Yabancı anahtarı barındıran [personne_activite] tablosu, @ManyToOne ana ilişkisini barındıracak ve Personne varlığı ise @OneToMany ters ilişkisini barındıracaktır.
    • Activite varlığını PersonneActivite varlığına bağlayan bir-çok ilişkisi: [activite] tablosundaki bir satır (One), [personne_activite] tablosundaki birden fazla (Many) satır tarafından referans alınır. Yabancı anahtarı barındıran [personne_activite] tablosu, @ManyToOne ana ilişkisini, Activite varlığı ise @OneToMany ters ilişkisini barındıracaktır.

@Entity Personne şu şekildedir:


@Entity
@Table(name = "jpa07_hb_personne")
public class Personne implements Serializable {

    @Id
    @Column(nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    @Version
    private int version;

    @Column(length = 30, nullable = false, unique = true)
    private String nom;

    @Column(length = 30, nullable = false)
    private String prenom;

    @Column(nullable = false)
    @Temporal(TemporalType.DATE)
    private Date datenaissance;

    @Column(nullable = false)
    private boolean marie;

    @Column(nullable = false)
    private int nbenfants;

    // ana ilişki Kişi (one) -> Adres (one)
    // Kişi (adresse_id) yabancı anahtarı tarafından uygulanır -> Adres
    // Kişi -> Adres ekleme kaskadı
    // Kişi güncelleme -> Adres güncelleme
    // Kişi silme işlemi, Adres'e de yansıtılır
    // bir Kişi'nin 1 Adres'i olmalıdır (nullable=false)
    // 1 Adres sadece 1 kişiye aittir (unique=true)
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "adresse_id", unique = true, nullable = false)
    private Adresse adresse;

    // Kişi ilişkisi (one) -> PersonneActivite (many)
    // mevcut ilişkinin tersi: PersonneActivite (çok) -> Kişi (tek)
    // Kişi silme -> PersonneActivite silme kaskadı
    @OneToMany(mappedBy = "personne", cascade = { CascadeType.REMOVE })
    private Set<PersonneActivite> activites = new HashSet<PersonneActivite>();

    // yapıcılar

Bu @Entity zaten bilinmektedir. Biz sadece diğer varlıklarla olan ilişkilerini açıklayacağız:

  • 30-39. satırlar: @OneToOne ile @Entity Adresse arasında bir bire bir ilişki, bu ilişki, [personne] tablosunun [adresse] tablosuna sahip olacağı [adresse_id] yabancı anahtarı (satır 38) ile somutlaştırılmıştır.
  • 41-45. satırlar: @OneToMany ile @Entity PersonneActivite arasında bir "bir-çok" ilişkisi. Bir kişi (One), @Entity PersonneActivite ile temsil edilen [personne_activite] birleştirme tablosundaki birden fazla (Many) satır tarafından referans alınır. Bu PersonneActivite nesneleri, Set<PersonneActivite> türüne yerleştirilecektir; burada PersonneActivite, yakında tanımlayacağımız bir türdür.
  • 44. satır: Burada tanımlanan bir-çok ilişkisi, @Entity PersonneActivite'in personne alanında tanımlanan ana ilişkinin ters ilişkisidir (anahtar kelime: mappedBy). Silme işlemleri için Kişi -> Etkinlik yönünde bir kaskad vardır: p numaralı bir kişinin silinmesi, p.activites kümesinde bulunan PersonneActivite türündeki kalıcı öğelerin silinmesine yol açacaktır.

@Entity Adresse şu şekildedir:


@Entity
@Table(name = "jpa07_hb_adresse")
public class Adresse implements Serializable {

    // alanlar
    @Id
    @Column(nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Column(nullable = false)
    @Version
    private int version;

    @Column(length = 30, nullable = false)
    private String adr1;
    @Column(length = 30)
    private String adr2;
    @Column(length = 30)
    private String adr3;
    @Column(length = 5, nullable = false)
    private String codePostal;
    @Column(length = 20, nullable = false)
    private String ville;
    @Column(length = 3)
    private String cedex;
    @Column(length = 20, nullable = false)
    private String pays;
    @OneToOne(mappedBy = "adresse")
    private Personne personne;

  • 28-29. satırlar: @OneToOne ilişkisi, @OneToOne ilişkisinin tersi olup, @Entity Personne'i tanımlar (Personne'in 37-38. satırları).

@Entity Activite şu şekildedir


@Entity
@Table(name = "jpa07_hb_activite")
public class Activite implements Serializable {

    // alanlar
    @Id
    @Column(nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    @Version
    private int version;

    @Column(length = 30, nullable = false, unique = true)
    private String nom;

    // ilişki Etkinlik (tek) -> PersonneActivite (çok)
    // mevcut PersonneActivite (çok) -> Aktivite (tek) ilişkisinin tersi
    // Aktivite silme -> PersonneActivite silme kaskadı
    @OneToMany(mappedBy = "activite", cascade = { CascadeType.REMOVE })
    private Set<PersonneActivite> personnes = new HashSet<PersonneActivite>();

  • 6-9. satırlar: etkinliğin birincil anahtarı
  • 11-13. satırlar: faaliyetin sürüm numarası
  • satır 15-16: etkinliğin adı
  • 18-22. satırlar: @Entity Activite ile @Entity PersonneActivite'i birbirine bağlayan bir-çok ilişkisi: bir aktivite (One), @Entity PersonneActivite ile temsil edilen [personne_activite] birleştirme tablosundaki birden fazla (Many) satır tarafından referans alınır. Bu PersonneActivite nesneleri, bir Set<PersonneActivite> türüne yerleştirilecektir.
  • 22. satır: Burada tanımlanan bir-çok ilişkisi, @Entity PersonneActivite'teki activite alanında tanımlanan ana ilişkinin ters ilişkisidir (anahtar kelime: mappedBy). Silme işlemlerinde Activite -> PersonneActivite yönünde bir kaskad vardır: [activite] tablosununsilinmesi, a.personnes kümesinde bulunan PersonneActivite türündeki kalıcı öğelerin bulunduğu [personne_activite] birleştirme tablosunun silinmesine yol açacaktır.

@Entity PersonneActivite şu şekildedir:


@Entity
// birleştirme tablosu
@Table(name = "jpa07_hb_personne_activite")
public class PersonneActivite {

    @Embeddable
    public static class Id implements Serializable {
        // bileşik anahtarın bileşenleri
        // bir Kişiye işaret eder
        @Column(name = "PERSONNE_ID")
        private Long personneId;

        // bir Etkinliğe işaret eder
        @Column(name = "ACTIVITE_ID")
        private Long activiteId;

        // oluşturucular
...

        // getter ve setter'lar
...
        // toString
        public String toString() {
            return String.format("[%d,%d]", getPersonneId(), getActiviteId());
        }
    }

    // Personne_Activite sınıfının alanları
    // bileşik anahtar
    @EmbeddedId
    private Id id = new Id();

    // ana ilişki PersonneActivite (çok) -> Kişi (tek)
    // yabancı anahtar tarafından uygulanır: personneId (PersonneActivite (çok) -> Kişi (tek)
    // personneId aynı zamanda bileşik birincil anahtarın bir elemanıdır
    // JPA bu yabancı anahtarı yönetmemelidir (insertable = false, updatable = false), çünkü bu işlem uygulama tarafından kendi oluşturucusunda gerçekleştirilir
    @ManyToOne
    @JoinColumn(name = "PERSONNE_ID", insertable = false, updatable = false)
    private Personne personne;

    // ana ilişki PersonneActivite -> Aktivite
    // Yabancı anahtar tarafından uygulanır: activiteId (PersonneActivite (çok) -> Aktivite (tek)
    // activiteId aynı zamanda bileşik birincil anahtarın bir elemanıdır
    // JPA, bu yabancı anahtarı yönetmemelidir (insertable = false, updatable = false), çünkü bu işlem uygulama tarafından kendi oluşturucusunda gerçekleştirilir
    @ManyToOne()
    @JoinColumn(name = "ACTIVITE_ID", insertable = false, updatable = false)
    private Activite activite;

    // yapıcılar
    public PersonneActivite() {

    }

    public PersonneActivite(Personne p, Activite a) {
        // Yabancı anahtarlar uygulama tarafından belirlenir
        getId().setPersonneId(p.getId());
        getId().setActiviteId(a.getId());
        // iki yönlü ilişkiler
        this.setPersonne(p);
        this.setActivite(a);
        p.getActivites().add(this);
        a.getPersonnes().add(this);
    }

    // alıcı ve ayarlayıcılar
...
    // toString
    public String toString() {
        return String.format("[%s,%s,%s]", getId(), getPersonne().getNom(), getActivite().getNom());
    }
}

Bu sınıf, öncekilerden daha karmaşıktır.

  • [personne_activite] tablosunda, [p,a] biçiminde satırlar bulunur; burada p bir kişinin birincil anahtarı, a ise bir etkinliğin birincil anahtarıdır. Her tablonun bir birincil anahtarı olmalıdır ve [personne_activite] da bu kuraldan muaf değildir. Şimdiye kadar, SGBD tarafından dinamik olarak oluşturulan birincil anahtarlar tanımlamıştık. Bunu burada da yapabiliriz. Başka bir teknik kullanacağız; bu teknikte, uygulamanın kendisi bir tablonun birincil anahtar değerlerini tanımlar. Burada bir [p1,a1] satırı, p1 adlı bir kişinin a1 adlı faaliyeti gerçekleştirdiğini belirtir. Aynı satırı tabloda ikinci kez bulamayız. Dolayısıyla (p,a) çifti birincil anahtar olmak için uygun bir adaydır. Buna bileşik birincil anahtar denir.
  • 30-31. satırlar: bileşik birincil anahtar. @EmbeddedId notu (genellikle @Id olurdu), bir kişinin Adresse alanına uygulanan @Embedded notasyonuna benzerdir. İkinci durumda bu, Adresse alanının harici bir sınıfa ait olduğu, ancak kişi ile aynı tabloya eklenmesi gerektiği anlamına geliyordu. Burada anlam aynıdır; ancak birincil anahtarla ilgili olduğunu belirtmek için notasyon @EmbeddedId şeklinde olur.
  • 31. satır: id birincil anahtarını temsil eden boş bir nesne, [PersonneActivite] nesnesinin oluşturulmasıyla birlikte oluşturulur. Birincil anahtarı temsil eden sınıf, 7-26. satırlarda, [PersonneActivite] sınıfının içindeki statik ve genel bir sınıf olarak tanımlanmıştır. Bu sınıfın genel ve statik olması, Hibernate tarafından zorunlu kılınmaktadır. public static ifadesini private, ile değiştirirsek bir istisna oluşur ve ilgili hata mesajında Hibernate'in new PersonneActivite$Id komutunu çalıştırmaya çalıştığı görülür. Bu nedenle, Id sınıfının hem statik hem de public olması gerekir.
  • 6. satır: Birincil anahtarın Id sınıfı @Embeddable olarak tanımlanmıştır. Hatırlayacağımız üzere, 31. satırdaki id birincil anahtarı @EmbeddedId olarak tanımlanmıştı. Dolayısıyla, buna karşılık gelen sınıfın @Embeddable anotasyonuna sahip olması gerekir.
  • [personne_activite] tablosunun birincil anahtarının (p,a) çiftinden oluştuğunu belirtmiştik; burada p bir kişinin birincil anahtarı, a ise bir faaliyetin birincil anahtarıdır. Bileşik anahtarın iki elemanı (p,a), 11. satırda (personneId) ve 15. satırda (activiteId) bulunur. Bu iki alanla ilişkili sütunların adları şunlardır: kişi için PERSONNE_ID, faaliyet için ACTIVITE_ID.
  • 31. satır: Birincil anahtar, iki sütunuyla birlikte tanımlanmıştır (PERSONNE_ID, ACTIVITE_ID). [personne_activite] tablosunda başka sütun bulunmamaktadır. Geriye, şu anda tanımladığımız @Entity PersonneActivite ile ilişkisel şemadaki diğer @Entity'ler arasında var olan ilişkileri tanımlamak kalıyor. Bu ilişkiler, [personne_activite] tablosunun diğer tablolarla olan yabancı anahtar kısıtlamalarını yansıtmaktadır.
  • 33-39. satırlar: [personne_activite] tablosunun [personne] tablosu üzerindeki yabancı anahtarını tanımlar
  • 37. satır: İlişki türü @ManyToOne'tir: [personne] tablosundaki bir satır (One), [personne_activite] tablosundaki birden fazla (Many) satır tarafından referans alınır.
  • 38. satır: Yabancı anahtar sütununa bir ad verilir. Yabancı anahtarın "kişi" bileşeni için verilen adın aynısı kullanılır (10. satır). insertable=false, updatable=false öznitelikleri, Hibernate'in yabancı anahtarı yönetmesini engellemek için eklenmiştir. Bu yabancı anahtar, aslında uygulama tarafından hesaplanan bir birincil anahtarın bileşenidir ve Hibernate'in bu sürece müdahale etmemesi gerekir.
  • 41-47. satırlar: [personne_activite] tablosunun [activite] tablosuna sahip olduğu yabancı anahtarı tanımlar. Açıklamalar, daha önce verilenlerle aynıdır.
  • 54-63. satırlar: Bir p kişisi ve bir a etkinliğinden yola çıkarak bir PersonneActivite nesnesinin oluşturulması. Hatırlanacağı üzere, bir PersonneActivite nesnesinin oluşturulmasında, 31. satırdaki id birincil anahtarı boş bir Id nesnesine işaret ediyordu. 56-57. satırlar, Id nesnesinin her bir alanına (personneId, activiteId) bir değer atamaktadır. Bu değerler, sırasıyla p kişisinin ve a etkinliğinin birincil anahtarlarıdır ve oluşturucuya parametre olarak aktarılmıştır. Dolayısıyla, id birincil anahtarı (31. satır) artık bir değere sahiptir.
  • 59. satır: 39. satırdaki personne alanı, p değerini alır
  • 60. satır: 47. satırdaki activite alanı, a değerini alır
  • [PersonneActivite] nesnesi artık oluşturulmuş ve başlatılmıştır. @Entity Personne (satır 61) ve Activite (satır 62) ile az önce oluşturulan @Entity PersonneActivite arasındaki ters ilişkiler güncellenir.

Veritabanı varlıklarının tanımını tamamladık. Karmaşık ancak ne yazık ki sık karşılaşılan bir durumla karşı karşıyayız. JPA katmanının, bu karmaşıklığın bir kısmını gizleyen başka bir yapılandırma seçeneği olduğunu göreceğiz: birleştirme tablosu örtük hale gelir ve JPA katmanı tarafından oluşturulur ve yönetilir. Burada, en karmaşık olan ancak ilişkisel şemanın gelişmesine olanak tanıyan çözümü seçtik. Bu çözüm, birleştirme tablosuna sütun eklenmesine izin verir; oysa birleştirme tablosunun açık bir @Entity olmadığı yapılandırmada bu mümkün değildir. [ref1], şu anda incelediğimiz çözümü önermektedir. Bu çözümün geliştirilmesini sağlayan bilgiler [ref1]'te bulunmuştur.

2.5.3. Eclipse / Hibernate Projesi

Burada kullanılan JPA uygulaması, Hibernate'e aittir. Testler için kullanılan Eclipse projesi şöyledir:

 

Image

[1] dosyasında Eclipse projesi, [2] dosyasında ise Java kodları bulunmaktadır. Proje, [3] dosyasında, [4] adlı örnekler klasöründe yer almaktadır. Bu projeyi içe aktaracağız.

2.5.4. Veritabanından DDL dosyasının oluşturulması

2.1.7 numaralı paragrafta verilen talimatları izleyerek, SGBD ve MySQL5 için elde edilen DDL dosyası şu şekildedir:


alter table jpa07_hb_personne 
        drop 
        foreign key FKB5C817D45FE379D0;

    alter table jpa07_hb_personne_activite 
        drop 
        foreign key FKD3E49B06CD852024;

    alter table jpa07_hb_personne_activite 
        drop 
        foreign key FKD3E49B0668C7A284;

    drop table if exists jpa07_hb_activite;

    drop table if exists jpa07_hb_adresse;

    drop table if exists jpa07_hb_personne;

    drop table if exists jpa07_hb_personne_activite;

    create table jpa07_hb_activite (
        id bigint not null auto_increment,
        version integer not null,
        nom varchar(30) not null unique,
        primary key (id)
    ) ENGINE=InnoDB;

    create table jpa07_hb_adresse (
        id bigint not null auto_increment,
        version integer not null,
        adr1 varchar(30) not null,
        adr2 varchar(30),
        adr3 varchar(30),
        codePostal varchar(5) not null,
        ville varchar(20) not null,
        cedex varchar(3),
        pays varchar(20) not null,
        primary key (id)
    ) ENGINE=InnoDB;

    create table jpa07_hb_personne (
        id bigint not null auto_increment,
        version integer not null,
        nom varchar(30) not null unique,
        prenom varchar(30) not null,
        datenaissance date not null,
        marie bit not null,
        nbenfants integer not null,
        adresse_id bigint not null unique,
        primary key (id)
    ) ENGINE=InnoDB;

    create table jpa07_hb_personne_activite (
        PERSONNE_ID bigint not null,
        ACTIVITE_ID bigint not null,
        primary key (PERSONNE_ID, ACTIVITE_ID)
    ) ENGINE=InnoDB;

    alter table jpa07_hb_personne 
        add index FKB5C817D45FE379D0 (adresse_id), 
        add constraint FKB5C817D45FE379D0 
        foreign key (adresse_id) 
        references jpa07_hb_adresse (id);

    alter table jpa07_hb_personne_activite 
        add index FKD3E49B06CD852024 (ACTIVITE_ID), 
        add constraint FKD3E49B06CD852024 
        foreign key (ACTIVITE_ID) 
        references jpa07_hb_activite (id);

    alter table jpa07_hb_personne_activite 
        add index FKD3E49B0668C7A284 (PERSONNE_ID), 
        add constraint FKD3E49B0668C7A284 
        foreign key (PERSONNE_ID) 
        references jpa07_hb_personne (id);
  • 21-26. satırlar: [activite] tablosu
  • 28-39. satırlar: [adresse] tablosu
  • 41-51. satırlar: [personne] tablosu
  • 53-57. satırlar: [personne_activite] birleştirme tablosu. Bileşik anahtara (56. satır) dikkat edin
  • 59-63. satırlar: [personne] tablosundan [adresse] tablosuna yabancı anahtar
  • satır 65-69: [personne_activite] tablosundan [activite] tablosuna yabancı anahtar
  • satır 71-75: [personne_activite] tablosundan [personne] tablosuna yabancı anahtar

2.5.5. InitDB

[InitDB] kodunun içeriği şöyledir:


package tests;

...
public class InitDB {

    // sabitler
    private final static String TABLE_PERSONNE_ACTIVITE = "jpa07_hb_personne_activite";

    private final static String TABLE_PERSONNE = "jpa07_hb_personne";

    private final static String TABLE_ACTIVITE = "jpa07_hb_activite";

    private final static String TABLE_ADRESSE = "jpa07_hb_adresse";

    public static void main(String[] args) throws ParseException {
        // Kalıcılık bağlamı
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
        EntityManager em = null;
        // EntityManager, EntityManagerFactory'ten alınır
        // önceki
        em = emf.createEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // istek
        Query sql1;
        // PERSONNE_ACTIVITE tablosundaki öğeleri sil
        sql1 = em.createNativeQuery("delete from " + TABLE_PERSONNE_ACTIVITE);
        sql1.executeUpdate();
        // PERSONNE tablosundaki öğeleri sil
        sql1 = em.createNativeQuery("delete from " + TABLE_PERSONNE);
        sql1.executeUpdate();
        // ACTIVITE tablosundaki öğeleri sil
        sql1 = em.createNativeQuery("delete from " + TABLE_ACTIVITE);
        sql1.executeUpdate();
        // ADRESSE tablosundaki öğeleri sil
        sql1 = em.createNativeQuery("delete from " + TABLE_ADRESSE);
        sql1.executeUpdate();
        // faaliyetlerin oluşturulması
        Activite act1 = new Activite();
        act1.setNom("act1");
        Activite act2 = new Activite();
        act2.setNom("act2");
        Activite act3 = new Activite();
        act3.setNom("act3");
        // faaliyetlerin kalıcılığı
        em.persist(act1);
        em.persist(act2);
        em.persist(act3);
        // kişilerin oluşturulması
        Personne p1 = new Personne("p1", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
        Personne p2 = new Personne("p2", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
        Personne p3 = new Personne("p3", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
        // adreslerin oluşturulması
        Adresse adr1 = new Adresse("adr1", null, null, "49000", "Angers", null, "France");
        Adresse adr2 = new Adresse("adr2", "Les Mimosas", "15 av Foch", "49002", "Angers", "03", "France");
        Adresse adr3 = new Adresse("adr3", "x", "x", "x", "x", "x", "x");
        Adresse adr4 = new Adresse("adr4", "y", "y", "y", "y", "y", "y");
        // kişi <--> adres ilişkilendirmeleri
        p1.setAdresse(adr1);
        adr1.setPersonne(p1);
        p2.setAdresse(adr2);
        adr2.setPersonne(p2);
        p3.setAdresse(adr3);
        adr3.setPersonne(p3);
        // kişilerin ve dolayısıyla ilişkili adreslerin kalıcılığı
        em.persist(p1);
        em.persist(p2);
        em.persist(p3);
        // bir kişiyle ilişkilendirilmemiş a4 adresinin kalıcılığı
        em.persist(adr4);
        // kişilerin görüntülenmesi
        System.out.println("[personnes]");
        for (Object p : em.createQuery("select p from Personne p order by p.nom asc").getResultList()) {
            System.out.println(p);
        }
        // adreslerin görüntülenmesi
        System.out.println("[adresses]");
        for (Object a : em.createQuery("select a from Adresse a").getResultList()) {
            System.out.println(a);
        }
        System.out.println("[activites]");
        for (Object a : em.createQuery("select a from Activite a").getResultList()) {
            System.out.println(a);
        }
        // kişi <--> etkinlik ilişkilendirmeleri
        PersonneActivite p1act1 = new PersonneActivite(p1, act1);
        PersonneActivite p1act2 = new PersonneActivite(p1, act2);
        PersonneActivite p2act1 = new PersonneActivite(p2, act1);
        PersonneActivite p2act3 = new PersonneActivite(p2, act3);
        // kişi <--> etkinlik ilişkilendirmelerinin kalıcılığı
        em.persist(p1act1);
        em.persist(p1act2);
        em.persist(p2act1);
        em.persist(p2act3);
        // kişilerin görüntülenmesi
        System.out.println("[personnes]");
        for (Object p : em.createQuery("select p from Personne p order by p.nom asc").getResultList()) {
            System.out.println(p);
        }
        // adreslerin görüntülenmesi
        System.out.println("[adresses]");
        for (Object a : em.createQuery("select a from Adresse a").getResultList()) {
            System.out.println(a);
        }
        System.out.println("[activites]");
        for (Object a : em.createQuery("select a from Activite a").getResultList()) {
            System.out.println(a);
        }
        System.out.println("[personnes/activites]");
        for (Object pa : em.createQuery("select pa from PersonneActivite pa").getResultList()) {
            System.out.println(pa);
        }
        // işlemin sonu
        tx.commit();
        // EntityManager'in sonu
        em.close();
        // EntityManagerFactory sonu
        emf.close();
        // günlük
        System.out.println("terminé...");

    }
}
  • 27-38. satırlar: [personne_activite], [personne], [adresse] ve [activite] tabloları boşaltılır. Yabancı anahtarları olan tablolardan başlamak zorunda olduğumuzu unutmayalım.
  • 40-45. satırlar: act1, act2 ve act3 adlı üç etkinlik oluşturulur
  • 47-49. satırlar: Bu faaliyetler kalıcılık bağlamına eklenir.
  • 51-53. satırlar: p1, p2 ve p3 adlı üç kişi oluşturulur.
  • 55-58. satırlar: adr1 ile adr4 arasında dört adres oluşturulur.
  • 60-65. satırlar: adri adresleri, pi kişilerine eşleştirilir. Kişi <-> Adres ilişkisi çift yönlü olduğu için her seferinde iki işlem yapılır.
  • 67-69. satırlar: p1 ile p3 arasındaki kişiler kalıcılık bağlamına alınır. Kişi -> Adres kaskadı nedeniyle, adr1 ile adr3 arasındaki adresler için de aynı durum geçerli olacaktır.
  • 71. satır: Herhangi bir kişiyle ilişkilendirilmemiş 4. adres olan adr4, açıkça kalıcılık bağlamına eklenir.
  • 73-85. satırlar: [Personne], [Adresse] ve [Activite] türündeki varlıkların listesini almak için kalıcılık bağlamı sorgulanır. Bu sorguların bağlamın veritabanıyla senkronize edilmesine yol açacağı bilinmektedir: oluşturulan varlıklar veritabanına eklenecek ve birincil anahtarlarını alacaktır. Bundan sonraki adımlar için bunu anlamak önemlidir.
  • 87-90. satırlar: 4 adet Kişi <-> Etkinlik ilişkisi oluşturulur. İsimleri, hangi kişinin hangi etkinlikle ilişkili olduğunu gösterir. Hatırlanacağı üzere, PersonneActivite varlık türünün birincil anahtarı, bir kişinin birincil anahtarı ile bir etkinliğin birincil anahtarından oluşan bir bileşik anahtardır. Dolayısıyla, Personne ve Activite varlıklarının bir önceki senkronizasyon sırasında birincil anahtarlarını almış olmaları sayesinde bu işlem mümkün olmaktadır.
  • 92-95. satırlar: Bu 4 ilişki, kalıcılık bağlamına eklenir.
  • 87-86. satırlar: [Personne], [Adresse], [Activite] ve [PersonneActivite] türündeki varlıkların listesini almak için kalıcılık bağlamına sorgu gönderilir. Bu sorguların bağlamın veritabanıyla senkronize edilmesine neden olacağı bilinmektedir: oluşturulan PersonneActivite varlıklar veritabanına eklenecektir.

[InitDB] komutunun MySQL5 ile birlikte çalıştırılması sonucunda konsolda şu çıktı görüntülenir:

[personnes]
P[1,0,p1,Paul,31/01/2000,true,2,1]
P[2,0,p2,Sylvie,05/07/2001,false,0,2]
P[3,0,p3,Sylvie,05/07/2001,false,0,3]
[adresses]
A[1,adr1,null,null,49000,Angers,null,France]
A[2,adr2,Les Mimosas,15 av Foch,49002,Angers,03,France]
A[3,adr3,x,x,x,x,x,x]
A[4,adr4,y,y,y,y,y,y]
[activites]
Ac[1,0,act1]
Ac[2,0,act2]
Ac[3,0,act3]
[personnes]
P[1,1,p1,Paul,31/01/2000,true,2,1]
P[2,1,p2,Sylvie,05/07/2001,false,0,2]
P[3,0,p3,Sylvie,05/07/2001,false,0,3]
[adresses]
A[1,adr1,null,null,49000,Angers,null,France]
A[2,adr2,Les Mimosas,15 av Foch,49002,Angers,03,France]
A[3,adr3,x,x,x,x,x,x]
A[4,adr4,y,y,y,y,y,y]
[activites]
Ac[1,1,act1]
Ac[2,1,act2]
Ac[3,1,act3]
[personnes/activites]
[[1,1],p1,act1]
[[2,1],p2,act1]
[[1,2],p1,act2]
[[2,3],p2,act3]
terminé...

15-16. satırlarda p1 ve p2 kişilerin sürüm numaralarının 1 olması ve 24-26. satırlarda üç faaliyet için de durumun aynı olması şaşırtıcı gelebilir. Bunu anlamaya çalışalım.

2-4. satırlarda kişilerin sürüm numaraları 0'dır ve 11-13. satırlarda faaliyetlerin sürüm numaraları 0'dır. Yukarıdaki çıktılar, Kişi <-> Faaliyet ilişkilerinin oluşturulmasından önce gerçekleşir. Java kodunun 87-90. satırlarında, p1 ve p2 kişileri ile act1, act2, act3 arasında ilişkiler oluşturulur. Bu ilişkiler, @Entity PersonneActivite'in oluşturucusu aracılığıyla gerçekleştirilir (bkz. paragraf 2.5.2). Bu oluşturucunun koduna bakıldığında, bir kişi p bir faaliyet a ile ilişkilendirildiğinde:

  • a faaliyeti, p.activites kümesine eklenir
  • p kişisi, a.personnes kümesine eklenir

Dolayısıyla, new PersonneActivite(p,a) yazıldığında, p kişisi ve a faaliyeti bellekte bir değişiklik geçirir. [InitDB]'in 97-113. satırlarında, kalıcılık bağlamı veritabanıyla senkronize edilir, JPA / Hibernate, kalıcı öğeler p1, p2, act1, act2 ve act3'in değiştirildiğini tespit eder. Bu değişiklikler veritabanında yapılmalıdır. Bu değişiklikler aslında [personne_activite] birleştirme tablosuna kaydedilmiştir, ancak JPA / Hibernate yine de değiştirilen her bir kalıcı öğenin sürüm numarasını artırmaktadır.

SQL Explorer görünümünde sonuçlar şu şekildedir:

  • [2]: tablolar [jpa07_hb_*]
  • [3]: kişi tablosu
  • [4]: adres tablosu.
  • [5]: faaliyetler tablosu
  • [6]: kişi <-> etkinlik birleştirme tablosu

2.5.6. Ana Sayfa

[Main] sınıfı, veritabanını başlatmak için [InitDB]'in kodunu kullanan 1 numaralı test hariç, gözden geçirdiğimiz testleri sıralar.

2.5.6.1. Test2

Bu test şu şekildedir:


// Kişi p1'in silinmesi
    public static void test2() {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // p1 üzerindeki bağımlılıkların kaldırılması: Hibernate için gerekli değildir ancak
        // TopLink için zorunludur
        act1.getPersonnes().remove(p1act1);
        act2.getPersonnes().remove(p1act2);
        // p1 kişisinin kaldırılması
        em.remove(p1);
        // işlem sonu
        tx.commit();
        // yeni tablolar görüntüleniyor
        dumpPersonne();
        dumpActivite();
        dumpAdresse();
        dumpPersonne_Activite();
    }
  • 4. satır: test1 kalıcılık bağlamı kullanılır; burada p1 kişisi, bağlamın bir nesnesidir.
  • 13. satır: p1 kişisi silinmiştir. Aşağıdaki öznitelik nedeniyle:
    • cascadeType.ALL'in Adresse üzerindeki etkisi nedeniyle, p1 kişisinin adresi
    • cascadeType.REMOVE'in PersonneActivite üzerindeki etkisi nedeniyle, p1 kişisinin faaliyetleri silinecektir.
  • 10-11. satırlar: 13. satırda silinecek olan p1 kişisine diğer varlıkların bağımlılıkları silinir. act1 ve act2 faaliyetleri p1 kişisi tarafından gerçekleştirilmektedir. Bağlantılar, kodu aşağıdaki gibi olan PersonneActivite varlığının oluşturucusu tarafından oluşturulmuştur:

    public PersonneActivite(Personne p, Activite a) {
        // yabancı anahtarlar uygulama tarafından belirlenir
        getId().setPersonneId(p.getId());
        getId().setActiviteId(a.getId());
        // iki yönlü ilişkiler
        setPersonne(p);
        setActivite(a);
        p.getActivites().add(this);
        a.getPersonnes().add(this);
}

9. satırda, a faaliyeti, personnes kümesinde PersonneActivite türünde ek bir öğe alır. Bu öğe, p,a türündedir ve p adlı kişinin a faaliyetini yürüttüğünü belirtir. test1'te, [Main]'ten iki bağlantı (p1,act1) ve (p1,act2) bu şekilde oluşturulmuştur. test2'in 10. ve 11. satırları bu bağımlılıkları kaldırır. Hibernate'in, p1 kişisi üzerindeki bu bağımlılıkların kaldırılmasına gerek kalmadan çalıştığı, ancak Toplink'in çalışmadığı unutulmamalıdır.

  • 17-20. satırlar: tüm tablolar görüntülenir

Sonuçlar şu şekildedir:

main : ----------- test1
[personnes]
P[1,1,p1,Paul,31/01/2000,true,2,1]
P[2,1,p2,Sylvie,05/07/2001,false,0,2]
P[3,0,p3,Sylvie,05/07/2001,false,0,3]
[activites]
Ac[1,1,act1]
Ac[2,1,act2]
Ac[3,1,act3]
[adresses]
A[1,adr1,null,null,49000,Angers,null,France]
A[2,adr2,Les Mimosas,15 av Foch,49002,Angers,03,France]
A[3,adr3,x,x,x,x,x,x]
A[4,adr4,y,y,y,y,y,y]
[personnes/activites]
[[1,1],p1,act1]
[[2,1],p2,act1]
[[1,2],p1,act2]
[[2,3],p2,act3]
main : ----------- test2
[personnes]
P[2,1,p2,Sylvie,05/07/2001,false,0,2]
P[3,0,p3,Sylvie,05/07/2001,false,0,3]
[activites]
Ac[1,1,act1]
Ac[2,1,act2]
Ac[3,1,act3]
[adresses]
A[2,adr2,Les Mimosas,15 av Foch,49002,Angers,03,France]
A[3,adr3,x,x,x,x,x,x]
A[4,adr4,y,y,y,y,y,y]
[personnes/activites]
[[2,1],p2,act1]
[[2,3],p2,act3]
  • test1'te (satır 3) bulunan p1 kişisi, test2'in (satır 22-23) sonunda artık mevcut değildir
  • test1'te bulunan p1 kişisinin adr1 adresi (satır 11) test2 (satır 29-31) işleminden sonra artık mevcut değildir
  • test1'te bulunan p1 kişisinin faaliyetleri (p1,act1) (satır 16) ve (p1,act2) (satır 18), p1 kişisine ait olup, test1'te yer alırlar, ancak test2'in tamamlanmasının ardından artık mevcut değiller (satır 33-34)

2.5.6.2. Test3

Bu test şu şekildedir:


// act1 etkinliğinin silinmesi
    public static void test3() {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // act1 üzerindeki bağımlılıkların kaldırılması: Hibernate için gerekli değildir ancak
        // TopLink için vazgeçilmez
        p2.getActivites().remove(p2act1);
        // act1 etkinliğinin kaldırılması
        em.remove(act1);
        // işlem sonu
        tx.commit();
        // yeni tablolar görüntüleniyor
        dumpPersonne();
        dumpActivite();
        dumpAdresse();
        dumpPersonne_Activite();
    }
  • 4. satır: test2'in kalıcılık bağlamı kullanılır
  • 12. satır: act1 etkinliği silinir. Aşağıdaki öznitelik nedeniyle:
    • cascadeType.REMOVE'in PersonneActivite üzerindeki etkisi nedeniyle, [personne_activite] tablosundaki (p, act1) satırları silinecektir.
  • 10. satır: act1'i kalıcılık bağlamının dışına çıkarmadan önce, diğer varlıkların bu kalıcı nesneye sahip olabileceği bağımlılıklar silinir. Önceki testte p1 kişisi silindikten sonra, yalnızca p2 kişisi act1 faaliyetini yürütmektedir.
  • 13-16. satırlar: Tüm tablolar görüntülenir

Sonuçlar şöyledir:

main : ----------- test2
[personnes]
P[2,1,p2,Sylvie,05/07/2001,false,0,2]
P[3,0,p3,Sylvie,05/07/2001,false,0,3]
[activites]
Ac[1,1,act1]
Ac[2,1,act2]
Ac[3,1,act3]
[adresses]
A[2,adr2,Les Mimosas,15 av Foch,49002,Angers,03,France]
A[3,adr3,x,x,x,x,x,x]
A[4,adr4,y,y,y,y,y,y]
[personnes/activites]
[[2,1],p2,act1]
[[2,3],p2,act3]
main : ----------- test3
[personnes]
P[2,1,p2,Sylvie,05/07/2001,false,0,2]
P[3,0,p3,Sylvie,05/07/2001,false,0,3]
[activites]
Ac[2,1,act2]
Ac[3,1,act3]
[adresses]
A[2,adr2,Les Mimosas,15 av Foch,49002,Angers,03,France]
A[3,adr3,x,x,x,x,x,x]
A[4,adr4,y,y,y,y,y,y]
[personnes/activites]
[[2,3],p2,act3]
  • test2'te, act1 faaliyeti mevcuttur (6. satır). test3'te ise artık mevcut değildir (21-22. satırlar)
  • test2 dosyasında, (p2,act1)) bağlantısı mevcuttur (14. satır). test3 dosyasında ise artık mevcut değildir (28. satır)

2.5.6.3. Test4

Bu test şu şekildedir:


// bir kişinin faaliyetlerinin alınması
    public static void test4() {
        // kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlemin başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // p2 kişisini al
        p2 = em.find(Personne.class, p2.getId());
        System.out.format("1 - Activités de la personne p2 (JPQL) :%n");
        // faaliyetleri taranıyor
        for (Object pa : em.createQuery("select a.nom from Activite a join a.personnes pa where pa.personne.nom='p2'").getResultList()) {
            System.out.println(pa);
        }
        // p2'nin ters ilişkisi üzerinden geçiliyor
        p2 = em.find(Personne.class, p2.getId());
        System.out.format("2 - Activités de la personne p2 (relation inverse) :%n");
        // faaliyetleri taranıyor
        for (PersonneActivite pa : p2.getActivites()) {
            System.out.println(pa.getActivite().getNom());
        }
        // işlem sonu
        tx.commit();
    }
  • Test 4, p2 adlı kişinin faaliyetlerini görüntüler.
  • 4. satır: yeni ve boş bir bağlamdan başlıyoruz
  • 12-14. satırlar: p2 adlı kişinin gerçekleştirdiği faaliyetlerin adları, JPQL sorgusu kullanılarak görüntülenir.
    • Activite (a) / PersonneActivite (pa) birleştirme işlemi gerçekleştirilir (join a.personnes)
    • bu birleştirmenin satırlarında (a,pa), p2 kişisi için (pa.personne.nom='p2') faaliyet adı (a.nom) görüntülenir.
  • 16-21. satırlar: Daha önce yapılanın aynısı yapılır, ancak bu sefer p2 kişisinin OneToMany – p2.activites ilişkisi kullanılır. JPQL sorgusu, JPA tarafından oluşturulacaktır. Burada ters ilişki OneToMany'in faydası ortaya çıkmaktadır: bu ilişki, JPQL sorgusunun yapılmasını önler.

Sonuçlar şu şekildedir:

1
2
3
4
5
main : ----------- test4
1 - Activités de la personne p2 (JPQL) :
act3
2 - Activités de la personne p2 (relation inverse) :
act3

2.5.6.4. Test5

Bu test şöyledir:


// belirli bir faaliyette bulunan kişileri alıyoruz
    public static void test5() {
        // kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlemin başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        System.out.format("1 - Personnes pratiquant l'activité act3 (JPQL) :%n");
        // p2'nin faaliyetleri isteniyor
        for (Object pa : em.createQuery("select p.nom from Personne p join p.activites pa where pa.activite.nom='act3'").getResultList()) {
            System.out.println(pa);
        }
        // act3'ün ters ilişkisi üzerinden geçiliyor
        System.out.format("2 - Personnes pratiquant l'activité act3 (relation inverse) :%n");
        act3 = em.find(Activite.class, act3.getId());
        for (PersonneActivite pa : act3.getPersonnes()) {
            System.out.println(pa.getPersonne().getNom());
        }
        // işlem sonu
        tx.commit();
    }
  • Test 6, act3 etkinliğini gerçekleştiren kişileri gösterir. Yöntem, Test 6'dakine benzerdir. İki kod arasındaki bağlantıyı kurmayı okuyucuya bırakıyoruz.

Sonuçlar şöyledir:

1
2
3
4
5
main : ----------- test5
1 - Personnes pratiquant l'activité act3 (JPQL) :
p2
2 - Personnes pratiquant l'activité act3 (relation inverse) :
p2

Test 4 ve 5'in amacı, ters ilişkinin hiçbir zaman zorunlu olmadığını ve her zaman JPQL sorgusuyla değiştirilebileceğini bir kez daha göstermekti.

Şimdi bir JPA / Toplink uygulaması kullanıyoruz:

Toplink içeren Eclipse projesi, Hibernate içeren Eclipse projesinin bir kopyasıdır:

Java kodları, birazdan değineceğimiz birkaç ayrıntı dışında önceki Hibernate projesininkilerle aynıdır. Ortam (kütüphaneler – persistence.xml – veritabanı – conf ve ddl klasörleri – ant komut dosyası), 2.1.15.2 paragrafında incelenen ortamdır. Eclipse projesi, örnekler klasöründe ([4]) [3] adıyla bulunmaktadır. Bu projeyi içe aktaracağız.

<persistence.xml> [2] dosyası, bildirilen varlıklar kısmında bir noktada değiştirilmiştir:


        <!-- kalıcı sınıflar -->
        <class>entites.Activite</class>
        <class>entites.Adresse</class>
        <class>entites.Personne</class>
<class>entites.PersonneActivite</class>
  • 2-5. satırlar: yönetilen dört varlık

[InitDB]'in SGBD ve MySQL5 ile çalıştırılması aşağıdaki sonuçları verir:

[1]'te, konsol çıktısı; [2]'te, oluşturulan [jpa07_tl] tabloları; [3]'te ise oluşturulan SQL komut dosyaları bulunur. İçerikleri şöyledir:

create.sql


CREATE TABLE jpa07_tl_activite (ID BIGINT NOT NULL, VERSION INTEGER NOT NULL, NOM VARCHAR(30) UNIQUE NOT NULL, PRIMARY KEY (ID))
CREATE TABLE jpa07_tl_adresse (ID BIGINT NOT NULL, ADR3 VARCHAR(30), CODEPOSTAL VARCHAR(5) NOT NULL, VERSION INTEGER NOT NULL, VILLE VARCHAR(20) NOT NULL, ADR2 VARCHAR(30), CEDEX VARCHAR(3), ADR1 VARCHAR(30) NOT NULL, PAYS VARCHAR(20) NOT NULL, PRIMARY KEY (ID))
CREATE TABLE jpa07_tl_personne_activite (PERSONNE_ID BIGINT NOT NULL, ACTIVITE_ID BIGINT NOT NULL, PRIMARY KEY (PERSONNE_ID, ACTIVITE_ID))
CREATE TABLE jpa07_tl_personne (ID BIGINT NOT NULL, DATENAISSANCE DATE NOT NULL, MARIE TINYINT(1) default 0 NOT NULL, NOM VARCHAR(30) UNIQUE NOT NULL, NBENFANTS INTEGER NOT NULL, VERSION INTEGER NOT NULL, PRENOM VARCHAR(30) NOT NULL, adresse_id BIGINT UNIQUE NOT NULL, PRIMARY KEY (ID))
ALTER TABLE jpa07_tl_personne_activite ADD CONSTRAINT FK_jpa07_tl_personne_activite_ACTIVITE_ID FOREIGN KEY (ACTIVITE_ID) REFERENCES jpa07_tl_activite (ID)
ALTER TABLE jpa07_tl_personne_activite ADD CONSTRAINT FK_jpa07_tl_personne_activite_PERSONNE_ID FOREIGN KEY (PERSONNE_ID) REFERENCES jpa07_tl_personne (ID)
ALTER TABLE jpa07_tl_personne ADD CONSTRAINT FK_jpa07_tl_personne_adresse_id FOREIGN KEY (adresse_id) REFERENCES jpa07_tl_adresse (ID)
CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 1)

[InitDB] ve [Main] komutlarının çalıştırılması hatasız bir şekilde gerçekleşir.

2.6. Örnek 6: örtük birleştirme tablosu içeren çok-çok ilişkisi

Örnek 4'ü tekrar ele alıyoruz, ancak bu sefer JPA katmanı tarafından kendiliğinden oluşturulan örtük bir birleştirme tablosu ile işliyoruz.

2.6.1. Veritabanı şeması

  • [1]'te, MySQL5 veritabanı - [2]'te: [personne] tablosu – [3]'e; ilişkili [adresse] tablosu – [4]'e; faaliyetler tablosu [activite] – [5]: kişiler ile faaliyetler arasında bağlantı kuran birleştirme tablosu [personne_activite].

2.6.2. Veritabanını temsil eden @Entity nesneleri

Yukarıdaki tablolar aşağıdaki @Entity'lerle temsil edilecektir:

  • @Entity Personne, [personne] tablosunu temsil edecektir
  • @Entity Adresse, [adresse] tablosunu temsil edecektir
  • @Entity Activite, [activite] tablosunu temsil edecektir
  • [personne_activite] tablosu artık bir @Entity ile temsil edilmez

Bu varlıklar arasındaki ilişkiler şunlardır:

  • Personne varlığı ile Adresse varlığı arasında bir bire bir ilişki vardır: p numaralı bir kişinin a numaralı bir adresi vardır. Yabancı anahtara sahip olan Personne varlığı ana ilişkiye, Adresse varlığı ise ters ilişkiye sahip olacaktır.
  • Bir çok-çok ilişkisi, Personne ve Activite varlıklarını birbirine bağlar: bir kişinin birden fazla faaliyeti vardır ve bir faaliyet birden fazla kişi tarafından yürütülür. Bu ilişki, her iki varlıkta da @ManyToMany anotasyonu ile somutlaştırılacaktır; bunlardan biri diğerinin tersi olarak tanımlanacaktır.

@Entity Personne şu şekildedir:


@Entity
@Table(name = "jpa08_hb_personne")
public class Personne implements Serializable {

    @Id
    @Column(nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    // SQL Server toplink: @GeneratedValue (strateji = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    @Version
    private int version;

    @Column(length = 30, nullable = false, unique = true)
    private String nom;

    @Column(length = 30, nullable = false)
    private String prenom;

    @Column(nullable = false)
    @Temporal(TemporalType.DATE)
    private Date datenaissance;

    @Column(nullable = false)
    private boolean marie;

    @Column(nullable = false)
    private int nbenfants;

    // ana ilişki Kişi (one) -> Adres (one)
    // Kişi (adresse_id) -> Adres yabancı anahtarı ile uygulanır
    // Kişi -> Adres ekleme kaskadı
    // Kişi güncelleme -> Adres güncelleme
    // Kişi silme -> Adres silme
    // bir Kişi'nin 1 Adres'i olmalıdır (nullable=false)
    // 1 Adres sadece 1 kişiye aittir (unique=true)
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "adresse_id", unique = true, nullable = false)
    private Adresse adresse;

    // Kişi (çok) -> Etkinlik (çok) ilişkisi, personne_activite birleştirme tablosu aracılığıyla
    // personne_activite(PERSONNE_ID), Kişi(id) üzerinde yabancı anahtardır
    // personne_activite(ACTIVITE_ID), Etkinlik (id) üzerinde bir anahtardır
    // cascade=CascadeType.PERSIST : 1 kişinin kalıcılığı, faaliyetlerinin de kalıcılığını beraberinde getirir
    @ManyToMany(cascade={CascadeType.PERSIST})
    @JoinTable(name="jpa08_hb_personne_activite",joinColumns = @JoinColumn(name = "PERSONNE_ID"), inverseJoinColumns = @JoinColumn(name = "ACTIVITE_ID"))
    private Set<Activite> activites = new HashSet<Activite>();

    // oluşturucular
    public Personne() {
    }

Sadece 46-48. satırlardaki, @Entity Personne'i @Entity Activite'e bağlayan @ManyToMany ilişkisini açıklayacağız:

  • 48. satır: Bir kişinin faaliyetleri vardır. activites alanı bunları temsil edecektir. Önceki sürümde, activites kümesindeki öğelerin türü PersonneActivite idi. Burada ise Activite'tir. Böylece bir kişinin faaliyetlerine doğrudan erişilebiliyor; oysa önceki sürümde PersonneActivite ara varlığı üzerinden geçmek gerekiyordu.
  • 46. satır: İncelediğimiz @Entity Personne ile 48. satırdaki activites kümesindeki @Entity Activite'i birbirine bağlayan ilişki, çok-çoklu türdendir (ManyToMany):
    • bir kişi (One), birden fazla faaliyette (Many) bulunur
    • bir aktivite (One), birden fazla kişi (Many) tarafından gerçekleştirilir
    • sonuç olarak @Entity Personne ve Activite, bir ManyToMany ilişkisi ile birbirine bağlanır. OneToOne ilişkisinde olduğu gibi, bu ilişkide de varlıklar arasında simetri vardır. Ana ilişkiyi hangi @Entity'nin, ters ilişkiyi ise hangisinin taşıyacağını serbestçe seçebiliriz. Burada, @Entity Personne'in ana ilişkiyi taşıyacağına karar veriyoruz.
    • Önceki örnekte gördüğümüz gibi, @ManyToMany ilişkisi bir birleştirme tablosu gerektirir. Daha önce bunu bir @Entity kullanarak tanımlamıştık, ancak buradaki birleştirme tablosu 47. satırdaki @JoinTable anotasyonu kullanılarak tanımlanmıştır.
      • name özniteliği, tabloya bir ad verir.
      • Birleştirme tablosu, birleştirdiği tablolardaki yabancı anahtarlardan oluşur. Burada iki yabancı anahtar vardır: biri [personne] tablosunda, diğeri ise [activite] tablosundadır. Bu yabancı anahtar sütunları, joinColumns ve inverseJoinColumns öznitelikleriyle tanımlanmıştır.
      • joinColumns özniteliğinin @JoinColumn ek açıklaması, @ManyToMany ana ilişkisini barındıran @Entity tablosundaki yabancı anahtarı tanımlar; buradaki tablo [personne]'tir. Bu yabancı anahtar sütununun adı PERSONNE_ID olacaktır.
      • inverseJoinColumns özniteliğinin @JoinColumn anotasyonu, ters ilişkiyi barındıran @Entity'nin tablosundaki yabancı anahtarı tanımlar (@ManyToMany; burada [activite] tablosu). Bu yabancı anahtar sütununun adı ACTIVITE_ID olacaktır.

@Entity Adresse şu şekildedir:


@Entity
@Table(name = "jpa07_hb_adresse")
public class Adresse implements Serializable {

    // alanlar
    @Id
    @Column(nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Column(nullable = false)
    @Version
    private int version;

    @Column(length = 30, nullable = false)
    private String adr1;
    @Column(length = 30)
    private String adr2;
    @Column(length = 30)
    private String adr3;
    @Column(length = 5, nullable = false)
    private String codePostal;
    @Column(length = 20, nullable = false)
    private String ville;
    @Column(length = 3)
    private String cedex;
    @Column(length = 20, nullable = false)
    private String pays;
    @OneToOne(mappedBy = "adresse")
    private Personne personne;

  • 28-29. satırlar: @OneToOne ilişkisi, @OneToOne ilişkisinin tersidir ve @Entity Personne'in adresini gösterir (Personne'in 37-38. satırları).

@Entity Activite şu şekildedir


@Entity
@Table(name = "jpa08_hb_activite")
public class Activite implements Serializable {

    // tarlalar
    @Id()
    @Column(nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    // toplink sqlserver : @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    @Version
    private int version;

    @Column(length = 30, nullable = false, unique = true)
    private String nom;

    // ters ilişki Etkinlik -> Kişi
    @ManyToMany(mappedBy = "activites")
    private Set<Personne> personnes = new HashSet<Personne>();
...
  • 20-21. satırlar: @Entity Activite ile @Entity Personne'i birbirine bağlayan çok-çok ilişkisi. Bu ilişki, @Entity Personne'te zaten tanımlanmıştır. Dolayısıyla burada, bu ilişkinin @ManyToMany ilişkisinin tersi (mappedBy) olduğunu belirtmekle yetiniyoruz; söz konusu ilişki, @Personne varlıkta bulunan "activites" (mappedBy = "activites") alanındaki @ManyToMany ilişkisinin tersi (mappedBy) olduğunu belirtmekle yetineceğiz.
  • Ters bir ilişkinin her zaman isteğe bağlı olduğunu hatırlayalım. Burada, mevcut etkinliği gerçekleştiren kişileri elde etmek için bunu kullanıyoruz. Bu kişileri elde etmemizi sağlayacak olan Set<Personne> personnes kümesidir. @Entity Activite'in Personne bağımlılıklarının yükleme modu belirtilmemiştir. Önceki örnekte de bunu belirtmemiştik. Varsayılan olarak bu mod fetch=FetchType.LAZY'tir.

Veritabanı varlıklarının tanımını tamamladık. Bu işlem, [personne_activite] birleştirme tablosunun açık bir tablo olarak tanımlandığı duruma kıyasla daha basitti. Bu daha basit çözüm, zamanla bazı dezavantajlar ortaya çıkarabilir: birleştirme tablosuna sütun eklemeye izin vermez. Oysa yeni ihtiyaçları karşılamak için bu gerekli olabilir; örneğin, [personne_activite] tablosuna kişinin etkinliğe kayıt tarihini gösteren bir sütun eklemek gibi.

2.6.3. Eclipse / Hibernate Projesi

Burada kullanılan JPA uygulaması, Hibernate'e aittir. Testler için kullanılan Eclipse projesi şöyledir:

[1] dosyasında Eclipse projesi, [2] dosyasında ise Java kodları bulunmaktadır. Proje, [4] adlı örnekler klasörünün içindeki [3] dosyasında yer almaktadır. Bu dosyayı içe aktaracağız.

2.6.4. Veritabanından DDL dosyasının oluşturulması

2.1.7 numaralı paragrafta verilen talimatları izleyerek, SGBD ve MySQL5 için elde edilen DDL dosyası şu şekildedir:


alter table jpa08_hb_personne 
        drop 
        foreign key FKA44B1E555FE379D0;

    alter table jpa08_hb_personne_activite 
        drop 
        foreign key FK5A6A55A5CD852024;

    alter table jpa08_hb_personne_activite 
        drop 
        foreign key FK5A6A55A568C7A284;

    drop table if exists jpa08_hb_activite;

    drop table if exists jpa08_hb_adresse;

    drop table if exists jpa08_hb_personne;

    drop table if exists jpa08_hb_personne_activite;

    create table jpa08_hb_activite (
        id bigint not null auto_increment,
        version integer not null,
        nom varchar(30) not null unique,
        primary key (id)
    ) ENGINE=InnoDB;

    create table jpa08_hb_adresse (
        id bigint not null auto_increment,
        version integer not null,
        adr1 varchar(30) not null,
        adr2 varchar(30),
        adr3 varchar(30),
        codePostal varchar(5) not null,
        ville varchar(20) not null,
        cedex varchar(3),
        pays varchar(20) not null,
        primary key (id)
    ) ENGINE=InnoDB;

    create table jpa08_hb_personne (
        id bigint not null auto_increment,
        version integer not null,
        nom varchar(30) not null unique,
        prenom varchar(30) not null,
        datenaissance date not null,
        marie bit not null,
        nbenfants integer not null,
        adresse_id bigint not null unique,
        primary key (id)
    ) ENGINE=InnoDB;

    create table jpa08_hb_personne_activite (
        PERSONNE_ID bigint not null,
        ACTIVITE_ID bigint not null,
        primary key (PERSONNE_ID, ACTIVITE_ID)
    ) ENGINE=InnoDB;

    alter table jpa08_hb_personne 
        add index FKA44B1E555FE379D0 (adresse_id), 
        add constraint FKA44B1E555FE379D0 
        foreign key (adresse_id) 
        references jpa08_hb_adresse (id);

    alter table jpa08_hb_personne_activite 
        add index FK5A6A55A5CD852024 (ACTIVITE_ID), 
        add constraint FK5A6A55A5CD852024 
        foreign key (ACTIVITE_ID) 
        references jpa08_hb_activite (id);

    alter table jpa08_hb_personne_activite 
        add index FK5A6A55A568C7A284 (PERSONNE_ID), 
        add constraint FK5A6A55A568C7A284 
        foreign key (PERSONNE_ID) 
        references jpa08_hb_personne (id);

Bu DDL, açık birleştirme tablosuyla elde edilenle benzerdir ve daha önce sunulan şemaya karşılık gelir:

2.6.5. InitDB

Önceki sürümüyle aynı olan ve aynı sonuçları veren [InitDB] sınıfı hakkında fazla yorum yapmayacağız. Sadece, Personne <-> Activite birleşimini gösteren aşağıdaki koda biraz daha yakından bakalım:


        // kişiler/faaliyetlerin görüntülenmesi
        System.out.println("[personnes/activites]");
        Iterator iterator = em.createQuery("select p.id,a.id from Personne p join p.activites a").getResultList().iterator();
        while (iterator.hasNext()) {
            Object[] row = (Object[]) iterator.next();
            System.out.format("[%d,%d]%n", (Long) row[0], (Long) row[1]);
}
  • 3. satır: Birleştirme işlemini gerçekleştiren JPQL komutu. select komutunun sonucu, birleştirme tablosu aracılığıyla birbirine bağlı olan Personne ve Activite varlıklarının kimliklerini döndürür. select tarafından döndürülen liste, Long türünde iki nesne içeren satırlardan oluşur. Bu listeyi taramak için 3. satır, listeden bir Iterator nesnesi ister.
  • 4-7. satırlar: Önceki Iterator türündeki nesne kullanılarak liste taranır.
    • 5. satır: Listenin her bir öğesi, select'in bir satır sonucunu içeren bir dizidir.
    • 6. satır: select'in mevcut sonuç satırındaki öğeleri, uygun tip değişikliklerini yaparak alınır.

[InitDB]'in sonucu şöyledir:

[personnes]
P[1,0,p1,Paul,31/01/2000,true,2,1]
P[2,0,p2,Sylvie,05/07/2001,false,0,2]
P[3,0,p3,Sylvie,05/07/2001,false,0,3]
[adresses]
A[1,adr1,null,null,49000,Angers,null,France]
A[2,adr2,Les Mimosas,15 av Foch,49002,Angers,03,France]
A[3,adr3,x,x,x,x,x,x]
A[4,adr4,y,y,y,y,y,y]
[activites]
Ac[1,1,act1]
Ac[2,1,act2]
Ac[3,1,act3]
[personnes/activites]
[1,1]
[1,2]
[2,1]
[2,3]
terminé...

2.6.6. Ana Sayfa

[Main] sınıfı bir dizi test içerir; bunlardan bazılarını inceleyeceğiz.

2.6.6.1. Test3

Bu test şöyledir:


// act1 etkinliğinin silinmesi
    public static void test3() {
        // kalıcılık bağlamı
        EntityManager em = getEntityManager();
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // p2'den act1 etkinliğinin silinmesi
        p2.getActivites().remove(act1);
        // act1'i kalıcılık bağlamından çıkar
        em.remove(act1);
        // işlem sonu
        tx.commit();
        // yeni tablolar görüntüleniyor
        dumpPersonne();
        dumpActivite();
        dumpAdresse();
        dumpPersonne_Activite();
    }
  • 11. satır: act1 etkinliği kalıcılık bağlamından kaldırılır
  • 9. satır: act1 etkinliği, bağlamda kalan tek kişi olan p2'in etkinlikleri arasında yer alır. 9. satır, act1 faaliyetini p2 kişisinin faaliyetlerinden çıkarır. Bunu, kalıcılık bağlamının tutarlılığını korumak için yapıyoruz; çünkü bu bağlamı sonraki işlemler için saklıyoruz.

Sonuçlar şu şekildedir:

main : ----------- test1
[personnes]
P[1,0,p1,Paul,31/01/2000,true,2,1]
P[2,0,p2,Sylvie,05/07/2001,false,0,2]
P[3,0,p3,Sylvie,05/07/2001,false,0,3]
[activites]
Ac[1,0,act1]
Ac[2,0,act2]
Ac[3,0,act3]
[adresses]
A[1,adr1,null,null,49000,Angers,null,France]
A[2,adr2,Les Mimosas,15 av Foch,49002,Angers,03,France]
A[3,adr3,x,x,x,x,x,x]
A[4,adr4,y,y,y,y,y,y]
[personnes/activites]
[1,1]
[1,2]
[2,1]
[2,3]
main : ----------- test2
[personnes]
P[2,0,p2,Sylvie,05/07/2001,false,0,2]
P[3,0,p3,Sylvie,05/07/2001,false,0,3]
[activites]
Ac[1,0,act1]
Ac[2,0,act2]
Ac[3,0,act3]
[adresses]
A[2,adr2,Les Mimosas,15 av Foch,49002,Angers,03,France]
A[3,adr3,x,x,x,x,x,x]
A[4,adr4,y,y,y,y,y,y]
[personnes/activites]
[2,1]
[2,3]
main : ----------- test3
[personnes]
P[2,1,p2,Sylvie,05/07/2001,false,0,2]
P[3,0,p3,Sylvie,05/07/2001,false,0,3]
[activites]
Ac[2,0,act2]
Ac[3,0,act3]
[adresses]
A[2,adr2,Les Mimosas,15 av Foch,49002,Angers,03,France]
A[3,adr3,x,x,x,x,x,x]
A[4,adr4,y,y,y,y,y,y]
[personnes/activites]
[2,3]
  • act1 faaliyetinin test2'teki 26. satırı, test3 faaliyetlerinden (40-41. satırlar) kaybolmuştur
  • p2 numaralı kişi, test2'te act1 faaliyetine sahipti (satır 33). test3'in tamamlanmasının ardından, bu faaliyete artık sahip değil (satır 47)

2.6.6.2. Test6

Bu test şu şekildedir:


// bir kişinin faaliyetlerinde değişiklik
    public static void test6() {
        // kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlemin başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // p2 kişisi alınır
        p2 = em.find(Personne.class, p2.getId());
        // act2 faaliyetini alıyoruz
        act2 = em.find(Activite.class, act2.getId());
        // p2 artık sadece act2 faaliyetini yürütmektedir
        p2.getActivites().clear();
        p2.getActivites().add(act2);
        // işlem sonu
        tx.commit();
        // yeni tablolar görüntüleniyor
        dumpPersonne();
        dumpActivite();
        dumpPersonne_Activite();
    }
  • 4. satır: yeni ve boş bir kalıcılık bağlamı kullanılır
  • 9. satır: p2 kişisi veritabanından kalıcılık bağlamına aktarılır
  • 11. satır: act2 etkinliği veritabanından kalıcılık bağlamına aktarılır
  • 13. satır: p2 (act3) kişisinin faaliyetleri veritabanından bağlama aktarılır (fetchType.LAZY). Bu yüklemeyi tetikleyen [getActivites] çağrısıdır. p2'in faaliyetleri silinir. Bu, faaliyetlerin gerçek anlamda silinmesi (remove) değil, p2 kişisinin durumundaki bir değişikliktir. Artık herhangi bir faaliyette bulunmamaktadır.
  • 14. satır: p2 kişisine act2 faaliyeti eklenir. Sonuç olarak, p2 kişisinin tüm yeni faaliyetleri {act2} kümesidir.
  • 16. satır: İşlemin sonu. Senkronizasyon, bağlamdaki nesneleri (p2, act2, act3) gözden geçirecek ve p2'in durumunun değiştiğini tespit edecektir. Bu değişikliği veritabanına yansıtan SQL emirleri yürütülecektir.
  • 18-20. satırlar: tüm tablolar görüntülenir

Sonuçlar şöyledir:

main : ----------- test4
1 - Activités de la personne p2 (JPQL) :
act3
2 - Activités de la personne p2 (relation principale) :
act3
main : ----------- test5
1 - Personnes pratiquant l'activité act3 (JPQL) :
p2
2 - Personnes pratiquant l'activité act3 (relation inverse) :
p2
main : ----------- test6
[personnes]
P[2,2,p2,Sylvie,05/07/2001,false,0,2]
P[3,0,p3,Sylvie,05/07/2001,false,0,3]
[activites]
Ac[2,0,act2]
Ac[3,0,act3]
[personnes/activites]
[2,2]
  • 4. testin sonunda, p2 adlı kişi act3 faaliyetini yürütüyordu (satır 3).
  • Test 6'nın sonunda (satır 19), p2 adlı kişi artık act3 faaliyetini (satır 3) yürütmüyor ve act2 faaliyetini yürütüyor.

Artık JPA / Toplink uygulamasını kullanıyoruz:

Toplink ile Eclipse projesi, Hibernate ile Eclipse projesinin bir kopyasıdır:

<persistence.xml> [2] dosyası, bildirilen varlıklar kısmında bir noktada değiştirilmiştir:


        <!--  sağlayıcı -->
        <provider>oracle.toplink.essentials.PersistenceProvider</provider>
        <!-- kalıcı sınıflar -->
        <class>entites.Activite</class>
        <class>entites.Adresse</class>
        <class>entites.Personne</class>
...
  • 4-6. satırlar: yönetilen varlıklar

[InitDB] komutunun SGBD ve MySQL5 dosyalarıyla çalıştırılması aşağıdaki sonuçları verir:

[1]'te, konsol çıktısı, [2]'te oluşturulan tablolar, [jpa07_tl]'te ve [3]'te oluşturulan komut dosyaları. İçerikleri şöyledir:

create.sql


CREATE TABLE jpa08_tl_personne_activite (PERSONNE_ID BIGINT NOT NULL, ACTIVITE_ID BIGINT NOT NULL, PRIMARY KEY (PERSONNE_ID, ACTIVITE_ID))
CREATE TABLE jpa08_tl_activite (ID BIGINT NOT NULL, VERSION INTEGER NOT NULL, NOM VARCHAR(30) UNIQUE NOT NULL, PRIMARY KEY (ID))
CREATE TABLE jpa08_tl_personne (ID BIGINT NOT NULL, DATENAISSANCE DATE NOT NULL, MARIE TINYINT(1) default 0 NOT NULL, NOM VARCHAR(30) UNIQUE NOT NULL, NBENFANTS INTEGER NOT NULL, VERSION INTEGER NOT NULL, PRENOM VARCHAR(30) NOT NULL, adresse_id BIGINT UNIQUE NOT NULL, PRIMARY KEY (ID))
CREATE TABLE jpa08_tl_adresse (ID BIGINT NOT NULL, ADR3 VARCHAR(30), CODEPOSTAL VARCHAR(5) NOT NULL, VERSION INTEGER NOT NULL, VILLE VARCHAR(20) NOT NULL, ADR2 VARCHAR(30), CEDEX VARCHAR(3), ADR1 VARCHAR(30) NOT NULL, PAYS VARCHAR(20) NOT NULL, PRIMARY KEY (ID))
ALTER TABLE jpa08_tl_personne_activite ADD CONSTRAINT FK_jpa08_tl_personne_activite_ACTIVITE_ID FOREIGN KEY (ACTIVITE_ID) REFERENCES jpa08_tl_activite (ID)
ALTER TABLE jpa08_tl_personne_activite ADD CONSTRAINT FK_jpa08_tl_personne_activite_PERSONNE_ID FOREIGN KEY (PERSONNE_ID) REFERENCES jpa08_tl_personne (ID)
ALTER TABLE jpa08_tl_personne ADD CONSTRAINT FK_jpa08_tl_personne_adresse_id FOREIGN KEY (adresse_id) REFERENCES jpa08_tl_adresse (ID)
CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 1)

[InitDB] ve [Main] komutlarının çalıştırılması hatasız bir şekilde gerçekleşir.

2.6.8. Eclipse / Hibernate 2 projesi

Önceki projeden kopyalayarak bir Eclipse projesi oluşturuyoruz:

[1] dosyasında Eclipse projesi, [2] dosyasında ise Java kodları bulunmaktadır. Proje, [4] adlı örnekler klasöründeki [3] dosyasında yer almaktadır. Bu dosyayı içe aktaracağız.

Personne ile Activité arasındaki ilişkiyi şu şekilde değiştirelim:

Kişi


    // Kişi (çok) -> Etkinlik (çok) ilişkisi, bir birleştirme tablosu aracılığıyla personne_activite
    // personne_activite(PERSONNE_ID), Kişi(id) üzerinde yabancı anahtardır
    // personne_activite(ACTIVITE_ID), Etkinlik(id) üzerinde bir anahtardır
    // etkinlikler üzerinde daha fazla zincirleme
    // @ManyToMany(cascade={CascadeType.PERSIST})
    @ManyToMany()
    @JoinTable(name = "jpa09_hb_personne_activite", joinColumns = @JoinColumn(name = "PERSONNE_ID"), inverseJoinColumns = @JoinColumn(name = "ACTIVITE_ID"))
private Set<Activite> activites = new HashSet<Activite>();
  • 6. satır: @ManyToMany ana ilişkisinde artık Kişi -> Etkinlik kalıcılık zinciri bulunmamaktadır (eski sürümün 5. satırına bakınız)

Etkinlik


    // Kişi ile ters ilişki artık yok
    // @ManyToMany(mappedBy = "aktiviteler")
// private Set<Kişi> kişiler = new HashSet<Kişi>();
  • 2-3. satırlar: @ManyToMany Etkinlik -> Kişi ters ilişkisi kaldırılmıştır

Silinen özniteliklerin (kaskad ve ters ilişki) zorunlu olmadığını göstermeye çalışıyoruz. Bu yeni yapılandırmanın getirdiği ilk değişiklik [InitDB]'te yer almaktadır:


        // kişiler <--> etkinlikler ilişkileri
        p1.getActivites().add(act1);
        p1.getActivites().add(act2);
        p2.getActivites().add(act1);
        p2.getActivites().add(act3);
        // faaliyetlerin kalıcılığı
        em.persist(act1);
        em.persist(act2);
        em.persist(act3);
        // kişilerin kalıcılığı
        em.persist(p1);
        em.persist(p2);
        em.persist(p3);
        // ve bir kişiyle ilişkili olmayan a4 adresi
em.persist(adr4);
  • 7-9. satırlar: act1 ile act3 faaliyetlerini kalıcılık bağlamına açıkça eklemek zorundayız. Kişi kalıcılık zinciri -> Aktivite kalıcılık zinciri mevcutken, 11-13. satırlar hem p1 ile p3 arasındaki kişileri hem de bu kişilerin act1 ile act3 arasındaki faaliyetlerini kalıcı hale getiriyordu.

İkinci bir değişiklik [Main]'te görülebilir:


    // belirli bir faaliyette bulunan kişilerin geri kazanımı
    public static void test5() {
        // kalıcılık bağlamı
        EntityManager em = getNewEntityManager();
        // işlemin başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        System.out.format("1 - Personnes pratiquant l'activité act3 (JPQL) :%n");
        // p2'nin faaliyetleri isteniyor
        for (Object pa : em.createQuery("select p.nom from Personne p join p.activites a where a.nom='act3'").getResultList()) {
            System.out.println(pa);
        }
        // işlemin sonu
        tx.commit();
}
  • 9-12. satırlar: JPQL sorgusu, act3 faaliyetini yürüten kişileri
  • önceki sürümde, aynı sonuç artık kaldırılmış olan Activite -> Personne ters ilişkisi yoluyla da elde edilmişti:

        // act3'ün ters ilişkisi üzerinden geçiliyor
        System.out.format("2 - Personnes pratiquant l'activité act3 (relation inverse) :%n");
        act3 = em.find(Activite.class, act3.getId());
        for (Personne p : act3.getPersonnes()) {
            System.out.println(p.getNom());
}

Önceki Eclipse / Toplink projesini kopyalayarak yeni bir Eclipse projesi oluşturuyoruz:

[1] dosyasında Eclipse projesi, [2] dosyasında ise Java kodları bulunmaktadır. Proje, [3] dosyasında, [4] örnek klasöründe yer almaktadır. Bunu içe aktaracağız.

Java kodları, Hibernate sürümündekilerle aynıdır.

2.7. Örnek 7: Adlandırılmış sorguları kullanma

2. paragrafta başladığımız JPA varlıklarına ilişkin bu uzun sunumu, bir yapılandırma dosyasında dışsallaştırılmış JPQL sorgularının kullanımını gösteren son bir örnekle sonlandırıyoruz. Bu örnek, aşağıdaki kaynaktan alınmıştır:

[ref2]: Mark Fisher'ın "Spring 2.0'da JPA ile Başlangıç" başlıklı yazısı, adresi

[http://blog.springframework.com/markf/archives/2006/05/30/getting-started-with-jpa-in-spring-20/] adresinde yer almaktadır.

2.7.1. Örnek veritabanı

Veritabanı şöyledir:

  • [1]'te: isimleri ve adresleri ile birlikte restoranların listesi
  • [2]: Restoran adresleri tablosu; sadece sokak numarası ve sokak adı ile sınırlıdır. restaurant ve adresse tabloları arasında bire bir ilişki vardır: bir restoranın tek bir adresi vardır.
  • [3]: Yemeklerin adlarını ve yemeğin vejetaryen olup olmadığını belirten doğru/yanlış göstergesini içeren bir yemek tablosu
  • [4]'te: restoranlar / yemekler birleştirme tablosu: bir restoran birden fazla yemek sunar ve aynı yemek birden fazla restoran tarafından servis edilebilir. restaurant ve plat tabloları arasında çok-çoklu bir ilişki vardır.

2.7.2. Veritabanını temsil eden @Entity nesneleri

Yukarıdaki tablolar aşağıdaki @Entity'lerle temsil edilecektir:

  • @Entity Restaurant, [restaurant] tablosunu temsil edecektir
  • @Entity Adresse, [adresse] tablosunu temsil edecektir
  • @Entity Plat, [plat] tablosunu temsil edecektir

Bu varlıklar arasındaki ilişkiler şunlardır:

  • Restaurant varlığı ile Adresse varlığı arasında bir bire bir ilişki vardır: r restoranının a adresi vardır. Yabancı anahtara sahip olan Restaurant varlığı ana ilişkiye sahip olacaktır. Adresse varlığının ters ilişkisi olmayacaktır.
  • Restaurant ve Plat varlıklarını birbirine bağlayan çok-çoklu bir ilişki vardır: bir restoran birden fazla yemek sunar ve aynı yemek birden fazla restoran tarafından servis edilebilir. Bu ilişki, Restaurant varlığında @ManyToMany notu ile gösterilecektir. Plat varlığının ters ilişkisi olmayacaktır.

@Entity Restaurant şu şekildedir:


package entites;

...
@Entity
@Table(name = "jpa10_hb_restaurant")
public class Restaurant implements java.io.Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(unique = true, length = 30, nullable = false)
    private String nom;

    @OneToOne(cascade = CascadeType.ALL)
    private Adresse adresse;

    @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    @JoinTable(name = "jpa10_hb_restaurant_plat", inverseJoinColumns = @JoinColumn(name = "plat_id"))
    private Set<Plat> plats = new HashSet<Plat>();

    // oluşturucular
    public Restaurant() {

    }

    public Restaurant(String name, Adresse address, Set<Plat> entrees) {
...
    }

    // alıcı ve ayarlayıcılar
...

    // toString
    public String toString() {
        String signature = "R[" + getNom() + "," + getAdresse();
        for (Plat e : getPlats()) {
            signature += "," + e;
        }
        return signature + "]";
    }
}
  • 17. satır: Restaurant varlığı ile Adresse varlığı arasındaki bire-bir ilişki. Bir restoran üzerindeki tüm kalıcılık işlemleri, restoranın adresine de yansıtılır.
  • 20. satır: @Entity Restaurant'i, 22. satırdaki plats kümesinden @Entity Plat ile bağlayan ilişki, çok-çok ilişkisidir (ManyToMany):
    • bir restoran (One) birden fazla yemek (Many) sunar
    • bir yemek (One), birden fazla restoran (Many) tarafından servis edilebilir
    • sonuç olarak @Entity Restaurant ve Plat, ManyToMany ilişkisi ile birbirine bağlıdır. @Entity Restaurant'in ana ilişkiye sahip olacağına ve @Entity Plat'in ters ilişkiye sahip olmayacağına karar veriyoruz.
    • @ManyToMany ilişkisi bir birleştirme tablosu gerektirir. Bu tablo, 47. satırdaki @JoinTable anotasyonu kullanılarak tanımlanır.
      • name özniteliği, tabloya bir ad verir.
      • Birleştirme tablosu, birleştirdiği tablolardaki yabancı anahtarlardan oluşur. Burada iki yabancı anahtar vardır: biri [restaurant] tablosunda, diğeri [plat] tablosunda. Bu yabancı anahtar sütunları, joinColumns ve inverseJoinColumns öznitelikleriyle tanımlanmıştır.
      • joinColumns özniteliği, @ManyToMany ana ilişkisini barındıran @Entity tablosundaki yabancı anahtarı tanımlar; burada bu tablo [restaurant]'tir. joinColumns özniteliği burada yoktur. JPA'in bu durumda bir varsayılan değeri vardır: [table]_[clé_primaire_de_table]; burada bu değer [jpa10_hb_restaurant_id]'tir.
      • inverseJoinColumns özniteliğinin @JoinColumn açıklaması, ters ilişkiyi @ManyToMany olarak barındıran @Entity tablosundaki yabancı anahtarı tanımlar; burada bu tablo [plat]'tir. Bu yabancı anahtar sütununun adı plat_id olacaktır.

@Entity Adresse şu şekildedir:


package entites;

...
@Entity
@Table(name="jpa10_hb_adresse")
public class Adresse implements java.io.Serializable {
  
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;
  
  @Column(name = "NUMERO_RUE")
  private int numeroRue;
  
  @Column(name = "NOM_RUE", length=30, nullable=false)
  private String nomRue;
  
  // getter ve setter'lar
 ...
 
  // yapıcılar
  public Adresse(int streetNumber, String streetName){
...
  }
  
  public Adresse(){
    
  }
  
  // toString
  public String toString(){
    return "A["+getNumeroRue()+","+getNomRue()+"]";
  }
}
  • @Entity Adresse, diğer varlıklarla doğrudan ilişkisi olmayan bir varlıktır. Bu varlık, yalnızca Restaurant varlığı aracılığıyla kalıcı hale getirilebilir.
  • Bir adres, bir sokak adı (16. satır) ve sokak numarası (13. satır) ile tanımlanır.

@Entity Plat şu şekildedir


package entites;
...
@Entity
@Table(name="jpa10_hb_plat")
public class Plat implements java.io.Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(unique=true, length=50, nullable=false)
    private String nom;

    private boolean vegetarien;

    // yapıcılar
    public Plat() {

    }

    public Plat(String name, boolean vegetarian) {
...
    }

    // getter ve setter'lar
...

    // toString
    public String toString() {
        return "E[" + getNom() + "," + isVegetarien() + "]";
    }

}
  • @Entity Plat, diğer varlıklarla doğrudan ilişkisi olmayan bir varlıktır. Bu varlık, yalnızca Restaurant varlığı aracılığıyla kalıcı hale getirilebilir.
  • Bir yemek, bir ad (12. satır) ve vejetaryen olup olmadığı (14. satır) ile tanımlanır.

2.7.3. Eclipse / Hibernate projesi

Burada kullanılan JPA uygulaması, Hibernate'e aittir. Testler için kullanılan Eclipse projesi şöyledir:

[1] dosyasında Eclipse projesi, [2] dosyasında Java kodları ve JPA dosyasında katman yapılandırması bulunmaktadır. Daha önce hiç karşılaşılmamış bir [orm.xml] dosyasının varlığı dikkat çekmektedir. Proje, [4] adlı örnekler klasöründe [3] dosyası olarak bulunmaktadır. Bu dosyayı içe aktaracağız.

2.7.4. Veritabanından DDL dosyasının oluşturulması

2.1.7 numaralı paragrafta verilen talimatları izleyerek, SGBD ve MySQL5 dosyaları için elde edilen DDL dosyası şu şekildedir:


alter table jpa10_hb_restaurant 
        drop 
        foreign key FK3E8E4F5D5FE379D0;

    alter table jpa10_hb_restaurant_plat 
        drop 
        foreign key FK1D2D06D11F0F78A4;

    alter table jpa10_hb_restaurant_plat 
        drop 
        foreign key FK1D2D06D1AFAC3E44;

    drop table if exists jpa10_hb_adresse;

    drop table if exists jpa10_hb_plat;

    drop table if exists jpa10_hb_restaurant;

    drop table if exists jpa10_hb_restaurant_plat;

    create table jpa10_hb_adresse (
        id bigint not null auto_increment,
        NUMERO_RUE integer,
        NOM_RUE varchar(30) not null,
        primary key (id)
    ) ENGINE=InnoDB;

    create table jpa10_hb_plat (
        id bigint not null auto_increment,
        nom varchar(50) not null unique,
        vegetarien bit not null,
        primary key (id)
    ) ENGINE=InnoDB;

    create table jpa10_hb_restaurant (
        id bigint not null auto_increment,
        nom varchar(30) not null unique,
        adresse_id bigint,
        primary key (id)
    ) ENGINE=InnoDB;

    create table jpa10_hb_restaurant_plat (
        jpa10_hb_restaurant_id bigint not null,
        plat_id bigint not null,
        primary key (jpa10_hb_restaurant_id, plat_id)
    ) ENGINE=InnoDB;

    alter table jpa10_hb_restaurant 
        add index FK3E8E4F5D5FE379D0 (adresse_id), 
        add constraint FK3E8E4F5D5FE379D0 
        foreign key (adresse_id) 
        references jpa10_hb_adresse (id);

    alter table jpa10_hb_restaurant_plat 
        add index FK1D2D06D11F0F78A4 (plat_id), 
        add constraint FK1D2D06D11F0F78A4 
        foreign key (plat_id) 
        references jpa10_hb_plat (id);

    alter table jpa10_hb_restaurant_plat 
        add index FK1D2D06D1AFAC3E44 (jpa10_hb_restaurant_id), 
        add constraint FK1D2D06D1AFAC3E44 
        foreign key (jpa10_hb_restaurant_id) 
        references jpa10_hb_restaurant (id);
  • 21-26. satırlar: [adresse] tablosu
  • 28-33. satırlar: [plat] tablosu
  • 35-40. satırlar: [restaurant] tablosu
  • satır 42-46: birleştirme tablosu [restaurant_plat]. Bileşik anahtara (satır 45) dikkat edin
  • satır 48-52: [restaurant] tablosundan [adresse] tablosuna yabancı anahtar
  • satır 54-58: [restaurant_plat] tablosundan [plat] tablosuna giden yabancı anahtar
  • satır 60-64: [restaurant_plat] tablosundan [restaurant] tablosuna yabancı anahtar

Bu DDL, daha önce sunulan şemaya karşılık gelir:

SQL Explorer görünümünde veritabanı şu şekilde görünür:

  • [1]'te: veritabanındaki 4 tablo
  • [2]: adresler
  • [3]: yemekler
  • [4]: restoranlar. [adresse_id], [2]'teki adreslere referans verir.
  • [5]'te: [restaurant,plat] birleştirme tablosu. [jpa10_hb_restaurant_id], [4]'teki restoranlara ve [plat_id], [3]'teki yemeklere atıfta bulunur. Dolayısıyla [1,1], "Burger Barn" restoranının "CheeseBurger" adlı yemeği sunduğu anlamına gelir.

Yukarıdaki verileri elde etmek için Eclipse projesindeki [QueryDB] programı çalıştırıldı.

2.7.5. Hibernate konsolu ile JPQL sorguları

Önceki Eclipse projesine bağlı bir Hibernate konsolu oluşturuyoruz. Özellikle 2.1.12. paragrafında iki kez açıklanan adımları takip edeceğiz.

  • [1] ve [2]'te: Hibernate konsolunun yapılandırılması
  • [3]'te: bir sorgu JPQL ve [4]'te sonuç.
  • [5]: eşdeğer SQL emri

Şimdi bir dizi JPQL sorgusu sunuyoruz. Okuyucunun bunları çalıştırması ve Hibernate'in bunu yürütmek için oluşturduğu SQL sırasını keşfetmesi önerilir.

Tüm restoranları ve menülerindeki yemekleri almak:

En az bir vejetaryen yemek sunan restoranları almak:

Sadece vejetaryen yemekler sunan restoranların adlarını bulmak:

Hamburger servis eden restoranları bulmak:

2.7.6. QueryDB

Şimdi Eclipse projesinin [QueryDB] programına odaklanacağız; bu program:

  • veritabanını doldurur
  • üzerine bir dizi JPQL sorgusu gönderir. Bu sorgular, Eclipse projesinin [META-INF/orm.xml] dosyasına kaydedilir:

[orm.xml] dosyası, Java anotasyonları yerine JPA katmanını yapılandırmak için kullanılabilir. Bu, JPA katmanının yapılandırılmasında esneklik sağlar. Java kodlarını yeniden derlemeye gerek kalmadan bu yapılandırma değiştirilebilir. Java anotasyonları ve [orm.xml] dosyası olmak üzere her iki yöntem de aynı anda kullanılabilir. JPA yapılandırması önce Java anotasyonlarıyla, ardından [orm.xml] dosyasıyla yapılır. Dolayısıyla, yeniden derleme yapmadan bir Java anotasyonu ile yapılmış bir yapılandırmayı değiştirmek istersek, bu yapılandırmayı [orm.xml] dosyasına eklemek yeterlidir. Son sözü bu dosya söyleyecektir.

Örneğimizde, [orm.xml] dosyası, JPQL sorgu metinlerini kaydetmek için kullanılır. İçeriği şöyledir:


<?xml version="1.0" encoding="UTF-8" ?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" version="1.0">
    <description>Restaurants</description>
    <named-query name="supprimer le contenu de la table restaurant">
        <query>delete from Restaurant</query>
    </named-query>
    <named-query name="supprimer le contenu de la table plat">
        <query>delete from Plat</query>
    </named-query>
    <named-query name="obtenir tous les restaurants">
        <query>select r from Restaurant r order by r.nom asc</query>
    </named-query>
    <named-query name="obtenir toutes les adresses">
        <query>select a from Adresse a order by a.nomRue asc</query>
    </named-query>
    <named-query name="obtenir tous les plats">
        <query>select p from Plat p order by p.nom asc</query>
    </named-query>
    <named-query name="obtenir tous les restaurants avec leurs plats">
        <query>select r.nom,p.nom from Restaurant r join r.plats p</query>
    </named-query>
    <named-query name="obtenir les restaurants ayant au moins un plat vegetarien">
        <query>select distinct r from Restaurant r join r.plats p where p.vegetarien=true</query>
    </named-query>
    <named-query name="obtenir les restaurants avec uniquement des plats vegetariens">
        <query>
            select distinct r1.nom from Restaurant r1 where not exists (select p1 from Restaurant r2 join r2.plats p1 where r2.id=r1.id and
            p1.vegetarien=false)
        </query>
    </named-query>
    <named-query name="obtenir les restaurants d'une certaine rue">
        <query>select r from Restaurant r where r.adresse.nomRue=:nomRue</query>
    </named-query>
    <named-query name="obtenir les restaurants qui servent des burgers">
        <query>select r.nom,r.adresse.numeroRue, r.adresse.nomRue, p.nom from Restaurant r join r.plats p where p.nom like '%burger'</query>
    </named-query>
    <named-query name="obtenir les plats du restaurant untel">
        <query>select p.nom from Restaurant r join r.plats p where r.nom=:nomRestaurant</query>
    </named-query>
</entity-mappings>
  • [orm.xml] dosyasının kökü <entity-mappings>'tir (2. satır).
  • 5-7. satırlar: Adlandırılmış JPQL sorguları, <named-query name= "... ">metin</namedquery> etiketleri ile belirtilir.
    • Etiketin name özniteliği, sorgunun adıdır.
    • Etiketin texte içeriği, sorgunun metnidir.

QueryDB, önceki sorguları çalıştıracaktır. Kodu şöyledir:


package tests;

...
public class QueryDB {

    // kalıcılık bağlamı
    private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");

    private static EntityManager em = emf.createEntityManager();

    public static void main(String[] args) {
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // tablodaki öğeleri silme [restaurant]
        em.createNamedQuery("supprimer le contenu de la table restaurant").executeUpdate();
        // [plat] tablosundaki öğeleri sil
        em.createNamedQuery("supprimer le contenu de la table plat").executeUpdate();
        // Address nesnelerinin oluşturulması
        Adresse adr1 = new Adresse(10, "Main Street");
        Adresse adr2 = new Adresse(20, "Main Street");
        Adresse adr3 = new Adresse(123, "Dover Street");
        // Entree nesnelerinin oluşturulması
        Plat ent1 = new Plat("Hamburger", false);
        Plat ent2 = new Plat("Cheeseburger", false);
        Plat ent3 = new Plat("Tofu Stir Fry", true);
        Plat ent4 = new Plat("Vegetable Soup", true);
        // Restaurant nesnelerinin oluşturulması
        Restaurant restaurant1 = new Restaurant();
        restaurant1.setNom("Burger Barn");
        restaurant1.setAdresse(adr1);
        restaurant1.getPlats().add(ent1);
        restaurant1.getPlats().add(ent2);
        Restaurant restaurant2 = new Restaurant();
        restaurant2.setNom("Veggie Village");
        restaurant2.setAdresse(adr2);
        restaurant2.getPlats().add(ent3);
        restaurant2.getPlats().add(ent4);
        Restaurant restaurant3 = new Restaurant();
        restaurant3.setNom("Dover Diner");
        restaurant3.setAdresse(adr3);
        restaurant3.getPlats().add(ent1);
        restaurant3.getPlats().add(ent2);
        restaurant3.getPlats().add(ent4);
        // Restaurant nesnelerinin (ve diğer nesnelerin zincirleme olarak) kalıcılığı
        em.persist(restaurant1);
        em.persist(restaurant2);
        em.persist(restaurant3);
        // işlemin sonlandırılması
        tx.commit();
        // veritabanı dökümü
        dumpDataBase();
        // son EntityManager
        em.close();
        // son EntityManagerFactory
        emf.close();
    }

    // veritabanı içeriğini görüntüleme
    @SuppressWarnings("unchecked")
    private static void dumpDataBase() {
        // test2
        log("données de la base");
        // işlem başlangıcı
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // restoranların görüntülenmesi
        log("[restaurants]");
        for (Object restaurant : em.createNamedQuery("obtenir tous les restaurants").getResultList()) {
            System.out.println(restaurant);
        }
        // adres görüntülemeleri
        log("[adresses]");
        for (Object adresse : em.createNamedQuery("obtenir toutes les adresses").getResultList()) {
            System.out.println(adresse);
        }
        // yemek görüntülemeleri
        log("[plats]");
        for (Object plat : em.createNamedQuery("obtenir tous les plats").getResultList()) {
            System.out.println(plat);
        }
        // restoranlar <--> yemekler arası bağlantı görüntülemeleri
        log("[restaurants/plats]");
        Iterator record = em.createNamedQuery("obtenir tous les restaurants avec leurs plats").getResultList().iterator();
        while (record.hasNext()) {
            Object[] currentRecord = (Object[]) record.next();
            System.out.format("[%s,%s]%n", currentRecord[0], currentRecord[1]);
        }
        log("[Liste des restaurants avec au moins un plat végétarien]");
        for (Object r : em.createNamedQuery("obtenir les restaurants ayant au moins un plat vegetarien").getResultList()) {
            System.out.println(r);
        }
        // sorgu
        log("[Liste des restaurants avec seulement des plats végétariens]");
        for (Object r : em.createNamedQuery("obtenir les restaurants avec uniquement des plats vegetariens").getResultList()) {
            System.out.println(r);
        }
        // sorgu
        log("[Liste des restaurants dans Dover Street]");
        for (Object r : em.createNamedQuery("obtenir les restaurants d'une certaine rue").setParameter("nomRue", "Dover Street").getResultList()) {
            System.out.println(r);
        }
        // sorgu
        log("[Liste des restaurants ayant un plat de type burger]");
        record = em.createNamedQuery("obtenir les restaurants qui servent des burgers").getResultList().iterator();
        while (record.hasNext()) {
            Object[] currentRecord = (Object[]) record.next();
            System.out.format("[%s,%d,%s,%s]%n", currentRecord[0], currentRecord[1], currentRecord[2], currentRecord[3]);
        }
        // sorgu
        log("[Plats de Veggie Village]");
        for (Object r : em.createNamedQuery("obtenir les plats du restaurant untel").setParameter("nomRestaurant", "Veggie Village").getResultList()) {
            System.out.println(r);
        }
        // işlem sonu
        tx.commit();
    }

    // günlükler
    private static void log(String message) {
        System.out.println(" -----------" + message);
    }

}

[QueryDB]'in yürütülmesinin sonucu şöyledir:

-----------données de la base
 -----------[restaurants]
R[Burger Barn,A[10,Main Street],E[Cheeseburger,false],E[Hamburger,false]]
R[Dover Diner,A[123,Dover Street],E[Cheeseburger,false],E[Hamburger,false],E[Vegetable Soup,true]]
R[Veggie Village,A[20,Main Street],E[Tofu Stir Fry,true],E[Vegetable Soup,true]]
 -----------[adresses]
A[123,Dover Street]
A[10,Main Street]
A[20,Main Street]
 -----------[plats]
E[Cheeseburger,false]
E[Hamburger,false]
E[Tofu Stir Fry,true]
E[Vegetable Soup,true]
 -----------[restaurants/plats]
[Burger Barn,Cheeseburger]
[Burger Barn,Hamburger]
[Dover Diner,Cheeseburger]
[Dover Diner,Hamburger]
[Dover Diner,Vegetable Soup]
[Veggie Village,Tofu Stir Fry]
[Veggie Village,Vegetable Soup]
 -----------[Liste des restaurants avec au moins un plat végétarien]
R[Veggie Village,A[20,Main Street],E[Tofu Stir Fry,true],E[Vegetable Soup,true]]
R[Dover Diner,A[123,Dover Street],E[Cheeseburger,false],E[Hamburger,false],E[Vegetable Soup,true]]
 -----------[Liste des restaurants avec seulement des plats végétariens]
Veggie Village
 -----------[Liste des restaurants dans Dover Street]
R[Dover Diner,A[123,Dover Street],E[Cheeseburger,false],E[Hamburger,false],E[Vegetable Soup,true]]
 -----------[Liste des restaurants ayant un plat de type burger]
[Burger Barn,10,Main Street,Cheeseburger]
[Burger Barn,10,Main Street,Hamburger]
[Dover Diner,123,Dover Street,Cheeseburger]
[Dover Diner,123,Dover Street,Hamburger]
 -----------[Plats de Veggie Village]
Tofu Stir Fry
Vegetable Soup

Kod ile sonuçlar arasındaki bağlantıyı kurmayı okuyucuya bırakıyoruz. Bunun için, okuyucuya Hibernate konsolunda JPQL sorgularını çalıştırmasını ve buna ait SQL kodunu incelemesini öneririz.

İlgilenen okuyucular, bu öğreticiyle birlikte indirilebilen örnekler arasında Toplink ile uygulanmış önceki projeyi bulabilirler:

Toplink ile Eclipse projesi, Hibernate ile Eclipse projesinin bir kopyasıdır:

<persistence.xml> [2] dosyası, yönetilen varlıklarını tanımlar:


        <!--  sağlayıcı -->
        <provider>oracle.toplink.essentials.PersistenceProvider</provider>
            <!-- kalıcı sınıflar -->
        <class>entites.Restaurant</class>
        <class>entites.Adresse</class>
        <class>entites.Plat</class>

...
  • 4-6. satırlar: yönetilen varlıklar

JPQL dosyasında kaydedilen ve [orm.xml] dosyasında bulunan sorgular, Toplink tarafından doğru şekilde yürütülür. Bunun için, önceki projede HQL (Hibernate Query Language) sorgularının kullanılmamasına özen gösterilmişti; zira bu dil, aslında JPQL'in bir üst kümesidir ve bazı sözdizimi kuralları JPQL tarafından kabul edilmez.

2.8. Conclusion

JPA varlıklarına ilişkin incelememizi burada sonlandırıyoruz. Bu uzun bir süreçti ve yine de (ileri düzey geliştiriciler için) önemli bazı konular ele alınmadı. Yine, bu eğitimde kullanılan gibi bir referans kitabını okumanız tavsiye edilir:

[ref1]: Java Persistence with Hibernate, Christian Bauer ve Gavin King tarafından yazılmış, Manning yayınevi.