Skip to content

6. Spring Data JPA Hibernate

6.1. Introduction

[spring-jdbc-04] projesi tarafından yönetilen [dbproduitscategories] veritabanını kullanacağız ve bu projede tanımlanan iki [IDao<Categorie>, IDao<Produit>] arayüzünü uygulayacağız. Bu bize birkaç şey sağlayacaktır:

  • uygulama kodlarını karşılaştırmak;
  • aynı test katmanını kullanmak;
  • iki uygulamanın performanslarını karşılaştırmak;
  • [JDBC] katmanı, 3.3. paragrafta incelenen [mysql-config-jdbc] projesi tarafından uygulanmaktadır;

Şimdi diğer katmanlara geçiyoruz.

6.2. Çalışma ortamının kurulması

STS ile, [<exemples>/spring-database-config/mysql/eclipse] [2] klasöründe bulunan [mysl-config-jpa-hibernate] [1] projesini içe aktarın:

Bu proje, projenin [Spring JPA Hibernate] katmanını yapılandırır. Her bir JPA uygulaması kendi yapılandırma projesine sahiptir.

Ardından, [<exemples>/spring-database-generic/spring-jpa] [2] klasöründe bulunan [spring-jpa-generic] [1] projesini içe aktarın:

Bu işlem tamamlandıktan sonra, [Package Explorer] içinde bulunan tüm projelerin Maven ortamını (Alt-F5) sıfırlayın:

 

Ardından, çalışma ortamını kontrol etmek için [spring-jpa-generic-JUnitTestDao-hibernate] adlı yürütme yapılandırmasını çalıştırın:

Bu yapılandırma, [JUnitTestDao] testini çalıştırır. Bu testin başarılı olması gerekir:

  

6.3. JPA katman yapılandırma projesi

  

Bu projenin amacı, aşağıdaki mimarideki JPA katmanını yapılandırmaktır:

6.3.1. Maven Yapılandırması

Proje bir Maven projesidir ve aşağıdaki [pom.xml] dosyası ile yapılandırılmıştır:


<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
    xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <modelVersion>4.0.0</modelVersion>
    <groupId>dvp.spring.database</groupId>
    <artifactId>generic-config-jpa</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>configuration mysql openjpa</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <!-- değişken bağımlılıklar ********************************************** -->
        <!-- JPA sağlayıcı -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
        </dependency>
        <!-- sabit bağımlılıklar ********************************************** -->
        <!-- Spring Data -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
        </dependency>
        <!-- Spring Context -->
        <!-- JDBC miras alınan yapılandırma -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>generic-config-jdbc</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-jdbc</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.7</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • 5-7. satırlar: Bu proje tarafından oluşturulan Maven artefaktı. Diğer uygulamaların yapılandırma projeleri (JPA (Eclipselink) ve OpenJpa) de aynı artefaktı kullanacaktır. Bu, herhangi bir anda bu projelerden yalnızca birinin aktif olabileceği anlamına gelir. Bu nedenle, [Package Explorer]'te hepsinin aynı anda bulunmasından kaçınılmalıdır. Sadece biri yeterlidir;
  • 10-14. satırlar: Proje için gerekli olan çoğu bağımlılığın sürümünü belirleyen üst Maven projesi;
  • 19-22. satırlar: Hibernate kütüphanesi;
  • 25-28. satırlar: Spring Data kütüphanesi;
  • satır 32-34: JPA katmanının yapılandırma projesi, diğer şeylerin yanı sıra kullanılan SGBD'in JDBC sürücüsünü ve kullanılacak veritabanının bilgilerini tanımlar;
  • 35-39. satırlar: JDBC katmanının yapılandırma taslağı, burada [Spring Data JPA] kütüphanesi ile değiştirilen [Spring JDBC] kütüphanesini içerir. Bu nedenle, bu kütüphanenin proje bağımlılıklarına dahil edilmemesi belirtilmiştir. Ancak, kütüphane kalırsa da bu durum herhangi bir hataya neden olmaz;

Sonuç olarak, projenin bağımlılıkları şunlardır:

  

6.3.2. Spring Yapılandırması

 

[ConfigJpa] sınıfı, Spring projesini yapılandırır:


package generic.jpa.config;

import javax.persistence.EntityManagerFactory;

import generic.jdbc.config.ConfigJdbc;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
@Import({ ConfigJdbc.class })
public class ConfigJpa {

    // JPA sağlayıcısı
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
        hibernateJpaVendorAdapter.setShowSql(false);
        hibernateJpaVendorAdapter.setDatabase(Database.MYSQL);
        hibernateJpaVendorAdapter.setGenerateDdl(true);
        return hibernateJpaVendorAdapter;
    }

    // JPA varlık paketleri
    public final static String[] ENTITIES_PACKAGES = { "generic.jpa.entities.dbproduitscategories" };

    // veri kaynağı
    @Bean
    public DataSource dataSource() {
        // TomcatJdbc veri kaynağı
        DataSource dataSource = new DataSource();
        // erişim yapılandırması JDBC
        dataSource.setDriverClassName(ConfigJdbc.DRIVER_CLASSNAME);
        dataSource.setUsername(ConfigJdbc.USER_DBPRODUITSCATEGORIES);
        dataSource.setPassword(ConfigJdbc.PASSWD_DBPRODUITSCATEGORIES);
        dataSource.setUrl(ConfigJdbc.URL_DBPRODUITSCATEGORIES);
        // başlangıçta açık olan bağlantılar
        dataSource.setInitialSize(5);
        // sonuç
        return dataSource;
    }

    // EntityManagerFactory
    @Bean
    public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(jpaVendorAdapter);
        factory.setPackagesToScan(ENTITIES_PACKAGES);
        factory.setDataSource(dataSource);
        factory.afterPropertiesSet();
        return factory.getObject();
    }

    // İşlem yöneticisi
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }

}
  • 18. satır: Sınıf, bir Spring yapılandırma sınıfıdır;
  • 19. satır: [mysql-config-jdbc] adlı Spring projesini yapılandırmak için kullanılan [ConfigJdbc] yapılandırma sınıfı tarafından tanımlanan bean'leri içe aktarır. Bunlar, jSON filtreleridir;
  • 23-30. satırlar: Kullanılan JPA uygulamasını tanımlar; burada Hibernate uygulamasıdır (25. satır);
  • 26. satır: Hibernate uygulaması tarafından yürütülen SQL işlemlerinin görüntülenip görüntülenmeyeceği belirlenebilir;
  • satır 27: Hibernate'e, bağlı olan SGBD'i belirtir. Bu yapılandırma önemlidir. Bu sayede Hibernate, SGBD ve MySQL'in SQL lehçesini, özel özellikleri de dahil olmak üzere kullanabilir. Ayrıca, bu ayar Hibernate’e hangi SQL türlerini ve SGBD nesnelerini kullanabileceğini bildirir. JPA uygulamasının belirli bir SGBD'e uyum sağlama yeteneği, ona SGBD arasında büyük bir taşınabilirlik sağlar;
  • 28. satır: Hibernate, bulduğu JPA varlıklarından hedef veritabanındaki tabloları oluşturabilir veya oluşturmayabilir. Bu oluşturma işlemi yalnızca tablolar mevcut değilse gerçekleşir. Tablolar zaten mevcutsa hiçbir işlem yapılmaz. Bu belgede kullanılan çeşitli veritabanlarının oluşturulmasına yönelik SQL komut dosyalarının nasıl oluşturulduğunu anlatırken, tabloları oluşturma özelliğini kullanacağız;
  • 33. satır: [dbproduitscategories] veritabanına ait JPA varlıklarının bulunduğu paket;
  • 36-49. satırlar: [dbproduitscategories] veritabanına bağlı [tomcat-jdbc] veri kaynağı;
  • 52-60. satırlar: [entityManagerFactory] adlı bean (adı bu şekilde olmalıdır), JPA kalıcılık bağlamını yöneten [EntityManager] nesnesini oluşturacak olan bean'dir. Tüm JPA işlemleri bu nesne üzerinden gerçekleştirilir. [Spring Data JPA]'in kullanılması nedeniyle bu nesneyi kendimiz asla kullanmayacağız. Ancak onu yapılandırmamız gerekiyor. Bu nesnenin aşağıdakileri bilmesi gerekir:
    • kullanılan JPA uygulaması (55. satır);
    • kullanılan veri kaynağı (57. satır);
    • bu kaynağa ait JPA varlıklarını (satır 56);
  • 58. satır: EntityManager'i bu bilgilerle başlatır;
  • satır 59: [entityManagerFactory] tekil nesnesini döndürür;
  • 63-68. satırlar: İşlem yöneticisini tanımlar. Adı [transactionManager] olmalıdır;
  • 65. satır: JPA adlı bir işlem yöneticisi oluşturulur;
  • satır 66: Bu işlem yöneticisi, [entityManagerFactory] bean'i aracılığıyla (satır 53 ve 57) satır 37'deki veri kaynağına bağlanır;

Yalnızca 23-30. satırlardaki bean, kullanılan JPA uygulamasına bağımlıdır. Diğer bean'ler ise buna dayanır.

6.3.3. [JPA] katmanındaki varlıklar

  

Hedef veritabanı, [dbproduitscategories] veritabanıdır ve bu veritabanında [CATEGORIES] ile [PRODUITS] adlı iki tablo bulunmaktadır. Bu veritabanında, web üzerinde dağıtılacak web hizmetinin güvenliğini sağlamak için kullanılacak üç adet [USERS, ROLES, USERS_ROLES] tablosu daha olduğunu görmüştük. Şimdilik bu tabloları göz ardı edeceğiz. Hatırlatmak gerekirse, [CATEGORIES] ve [PRODUITS] tablolarının yapısı şöyledir:

[PRODUITS] tablosu şu şekildedir:

  • [ID]: [2] tablosunun otomatik artan birincil anahtarı;
  • [NOM]: [4] ürününün benzersiz adı;
  • [PRIX]: ürünün fiyatı;
  • [DESCRIPTION]: ürün açıklaması;
  • [VERSIONING], ürünün sürüm numarasıdır. İlk sürümü 1 [3]'tir. Ürün her değiştirildiğinde, sürüm numarası tabloyu işleyen kod tarafından bir artırılır;
  • [CATEGORIE_ID]: Ürünün ait olduğu kategoriyi belirtmek için [CATEGORIES] tablosundaki yabancı anahtar;
  • [1-3]'te, [PRODUITS] tablosunun [CATEGORIE_ID] yabancı anahtarı. Bu, [CATEGORIES] ve [4-5] tablolarının [ID] sütununu hedefler;
  • bir kategori silindiğinde, ona bağlı tüm ürünler de silinir ([6]). Bu nokta, [dbproduitscategories] veritabanını kullanan [DAO] katmanının oluşturulmasında kullanıldığı için dikkat edilmesi gereken önemli bir husustur;

Kategorilere ait [CATEGORIES] tablosu şu şekildedir:

  • [ID]: otomatik artan birincil anahtar;
  • [VERSIONING]: kategorinin sürüm numarası;
  • [NOM]: kategorinin benzersiz adı;

Şimdi, JPA, [Produit] ve [Categorie] varlıklarını, [PRODUITS] ve [CATEGORIES] tablolarının görüntüleri olarak açıklayacağız.

  

6.3.3.1. [AbstractCoreEntity] arayüzü

[AbstractCoreEntity] arayüzü, JPA, [Categorie] ve [Produit] varlıkları tarafından uygulanır:


package generic.jpa.entities.dbproduitscategories;

public interface AbstractCoreEntity {

    // [id], [version], [entityType] alanlarının alıcı ve ayarlayıcıları
    public Long getId();

    public void setId(Long id);

    public Long getVersion();

    public void setVersion(Long version);

    public enum EntityType {
        PROXY, POJO
    }

    public EntityType getEntityType();

    public void setEntityType(EntityType entityType);

}

Bu arayüz, JPA adlı iki varlık tarafından uygulanmaktadır ve yalnızca bu varlıkların [id], [version] ve [entityType] alanlarını okumak/yazmak için kullanılan yöntemleri listelemek amacıyla kullanılır. [entityType] alanının işlevi daha sonra açıklanacaktır;

6.3.3.2. JPA ve [Produit] varlıkları

[Produit] sınıfı, [PRODUITS] tablosundaki bir satırla ilişkili JPA varlığıdır:

 

package generic.jpa.entities.dbproduitscategories;

import generic.jdbc.config.ConfigJdbc;
import generic.jpa.infrastructure.ProxyException;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;

import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
@Table(name = ConfigJdbc.TAB_PRODUITS)
@JsonFilter("jsonFilterProduit")
public class Produit implements AbstractCoreEntity {
    // özellikler
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = ConfigJdbc.TAB_JPA_ID)
    protected Long id;

    @Version
    @Column(name = ConfigJdbc.TAB_JPA_VERSIONING)
    protected Long version;

    @Transient
    protected EntityType entityType = EntityType.POJO;

    @Transient
    @JsonIgnore
    protected String simpleClassName = getClass().getSimpleName();

    // özellikler
    @Column(name = ConfigJdbc.TAB_PRODUITS_NOM, unique = true, length = 30, nullable = false)
    private String nom;

    @Column(name = ConfigJdbc.TAB_PRODUITS_CATEGORIE_ID, insertable = false, updatable = false, nullable = false)
    private Long idCategorie;

    @Column(name = ConfigJdbc.TAB_PRODUITS_PRIX, nullable = false)
    private double prix;

    @Column(name = ConfigJdbc.TAB_PRODUITS_DESCRIPTION, length = 100)
    private String description;

    // kategori
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = ConfigJdbc.TAB_PRODUITS_CATEGORIE_ID)
    private Categorie categorie;

    // üreticiler
    public Produit() {

    }

    public Produit(Long id, Long version, String nom, Long idCategorie, double prix, String description,
            Categorie categorie) {
        this.id = id;
        this.version = version;
        this.nom = nom;
        this.idCategorie = idCategorie;
        this.prix = prix;
        this.description = description;
        this.categorie = categorie;
    }

    // imza
    public String toString() {
        return String.format("[id=%s, version=%s, nom=%s, prix=10.2f, desc=%s, idCategorie=%s]", id, version, nom, prix,
                description, idCategorie);
    }

    // ------------------------------------------------------------
    // yeniden tanımlama [equals] ve [hashcode]
    @Override
    public int hashCode() {
        Long id = getId();
        return (id != null ? id.hashCode() : 0);
    }

    @Override
    public boolean equals(Object entity) {
        if (!(entity instanceof AbstractCoreEntity)) {
            return false;
        }
        String class1 = this.getClass().getName();
        String class2 = entity.getClass().getName();
        if (!class2.equals(class1)) {
            return false;
        }
        AbstractCoreEntity other = (AbstractCoreEntity) entity;
        Long id = getId();
        Long otherId = other.getId();
        return id != null && otherId != null && id.equals(otherId);
    }

    // alıcı ve ayarlayıcılar
...
    public void setCategorie(Categorie categorie) {
        // varlık türü
        if (entityType == EntityType.PROXY) {
            throw new ProxyException(1005, new RuntimeException(
                    "On ne peut changer la catégorie d'un produit de type [PROXY]"), simpleClassName);
        }
        this.categorie = categorie;
    }

}
  • 21. satır: [@Entity] açıklaması, [Produit] sınıfını [JPA] katmanı tarafından yönetilen bir varlık haline getirir. Ayrıca, varlığa [MonProduit] adını veren [@Entity(name="MonProduit")] yazılabilir. Bu bilgi bulunmadığında, varlığın adı sınıfın adıdır; bu örnekte [Produit]. Bu adlandırma, varlıklar arasında aynı adı taşıyan farklı paketlere ait iki sınıf olduğunda gerekli hale gelir;
  • 22. satır: [@Table(name = "PRODUITS")] açıklaması, [Produit] sınıfının, veritabanındaki [PRODUITS] tablosundaki bir satırın nesne görüntüsü olduğunu belirtir;
  • 23. satır: Varlığa uygulanacak jSON filtresinin adı. 58. satırdaki [categorie] özelliğinin her zaman mevcut olmadığını göreceğiz. Bu durumda, nesnenin jSON temsilinden bu özelliği hariç tutmamız gerekir. Bunun için bir filtreye ihtiyacımız var. Dolayısıyla, [jsonFilterCategorie] adlı filtrede, [categorie] özelliğini dahil etmek isteyip istemediğimizi belirteceğiz;
  • 26. satır: [@Id] açıklaması, açıklanan alanı 19. satırdaki tablonun birincil anahtarıyla ilişkili alan haline getirir;
  • 27. satır: [@GeneratedValue(strategy = GenerationType.IDENTITY)] etiketlemesi, [PRODUITS] tablosunda birincil anahtarın otomatik olarak oluşturulma şeklini belirler. Bunu belirleyen, [strategy] özniteliğidir. Farklı modlar vardır:

Image

[IDENTITY] stratejisi, tüm SGBD'ler için mevcut değildir. Test edilen altı SGBD arasında, bu strateji SGBD ve [MySQL 5, PostgreSQL 9.4, SQL Server 2014, DB2 Express-C10.5] için kullanılabilirdi. Diğer iki [Oracle Express 11g Release 2, Firebird 2.5.4] için ise [SEQUENCE] stratejisinin kullanılması gerekti. JPA uygulamaları arasında taşınabilirlik için, birincil anahtar oluşturma stratejisinin seçimini JPA uygulamasının takdirine bırakan [AUTO] stratejisi kullanılmamalıdır. Dolayısıyla, MySQL 5 ve [AUTO] stratejisi ile:

  • Hibernate, birincil anahtar için [AUTO_INCREMENT] moduna sahip [IDENTITY] stratejisini seçer;
  • EclipseLink, varsayılan olarak [SEQUENCE] adlı bir tablo oluşturan [TABLE] stratejisini seçer; birincil anahtarları elde etmek için bu tabloya sorgu gönderilmesi gerekir.

Sonuç olarak, bu iki JPA uygulaması tarafından yönetilen veritabanı yapısı aynı değildir. Veritabanı Hibernate tarafından oluşturulmuşsa, EclipseLink tarafından kullanılamaz ve bunun tersi de geçerlidir.

  • 28. satır: [@Column(name="ID"] anotasyonu, [id] alanıyla ilişkilendirilecek [PRODUITS] tablosundaki sütun adını belirler;
  • 29. satır: Birincil anahtar için [long] yerine [Long] türü kullanılır. Zira, [null] birincil anahtarları, JPA için özel bir anlam taşır. Bu nedenle burada basit bir tür yerine bir nesne türü kullanılması tercih edilir;
  • 31. satır: [@Version] açıklaması, [version] alanının bir sürümleme sütunuyla ilişkili olduğunu belirtir. JPA uygulaması, varlık her değiştirildiğinde bu sürüm numarasını artıracaktır. Bu numara, varlığın iki farklı kullanıcı tarafından aynı anda güncellenmesini önlemek için kullanılır: U1 ve U2 adlı iki kullanıcı, sürüm numarası V1 olan E varlığını okur. U1, E'yi değiştirir ve bu değişikliği veritabanına kaydeder: sürüm numarası bu durumda V1+1'e geçer. U2 da E'yi değiştirir ve bu değişikliği veritabanına kaydeder: veritabanındaki sürümden (V1+1) farklı bir sürüme (V1) sahip olduğu için bir istisna alır;
  • 36. satır: Varlığın türü. İki tür olacaktır: POJO ve PROXY. Varsayılan olarak, ürün örneği bir POJO (Plain Old Java Object) olacaktır. Bazı durumlarda, veritabanından getirilen [Produit] örnekleri [PROXY] türünde olacaktır. Bu durum, 56. satırdaki [fetch = FetchType.LAZY] özniteliği nedeniyle 58. satırdaki [Categorie categorie] özelliğinin bir kategoriyle başlatılmamış olması halinde ortaya çıkar. Bu durumda, test edilecek JPA uygulamaları birbirinden farklıdır:
    • [Hibernate, OpenJPA]: [PROXY] türündeki bir ürünün kategorisine erişilmeye çalışıldığında bir istisna oluşur. Hibernate, [LAZY] modunda elde edilen bir JPA örneğini ifade etmek için "proxy" terimini kullanır. Bu nedenle, bu tür bir varlığı tanımlamak için bu terimi kullandım;
    • [EclipseLink]: [PROXY] türündeki bir ürünün kategorisine erişildiğinde, veritabanında bu kategori aranır ve bir istisna oluşmaz;

Kullanılan JPA uygulamasından bağımsız bir test katmanı oluşturmak istediğim için, her bir varlığın türünü (POJO mi yoksa PROXY mi) bilmek istedim. Bu nedenle, JPA varlıklarına [entityType] alanını ekledim;

  • 35. satır: [@Transient] açıklaması, JPA uygulamasının bu alanı göz ardı etmesi gerektiğini belirtir. Nitekim bu alan, SGBD tablolarında mevcut değildir;
  • 40. satır: [Produit] sınıfı, sınıf adını gerektiren [ProxyException] türünde bir istisna oluşturur;
  • satır 38: daha önce olduğu gibi, JPA uygulamasının bu alanı göz ardı etmesi gerektiği belirtilmiştir;
  • satır 39: [@JsonIgnore] açıklaması, bir [Produit] örneğinin jSON serileştiricisi/deserileştiricisinin bu alanı göz ardı etmesi gerektiğini belirtir;
  • satır 43: [@Column] açıklaması, [nom] alanını [PRODUITS] tablosunun [NOM] sütunuyla ilişkilendirir. Alan, ilişkili sütunla aynı adı taşıyorsa (büyük/küçük harf duyarlı değildir), [@Column] etiketini atlayabilirsiniz. Bu durumda da durum böyledir. [unique = true, length = 30, nullable = false] öznitelikleri, yalnızca JPA uygulaması, [Produit] varlığından [CATEGORIES] tablosunu oluşturmak zorunda kaldığında kullanılır. Bunlar, SQL ve [UNIQUE, VARCHAR(30), NOT NULL] öznitelikleriyle çevrilir; bu öznitelikler, [NOM] sütununun en fazla 30 karakterden oluşmasını, tabloda benzersiz olmasını ve NULL değerini alamamasını sağlar;
  • 46-47. satırlar: [idCategorie] alanı, [CATEGORIE_ID] sütunuyla ilişkilidir. Bu alanın özelliklerine biraz ileride tekrar değineceğiz;
  • satır 49-50: [prix] alanı, [PRIX] sütunuyla ilişkilidir;
  • satır 52-53: [description] alanı, [DESCRIPTION] sütunuyla ilişkilendirilmiştir;
  • satır 56-58: ürün kategorisi;
  • 56. satır: [@ManyToOne] açıklaması, 57. satırdaki [@JoinColumn(name = "CATEGORIE_ID")] açıklamasının sütununun,[Produit] varlığının, 58. satırdaki varlıkla ilişkili [CATEGORIES] tablosundaki yabancı anahtarıdır. Bu açıklama, bir JPA varlığını açıklamalıdır. Dolayısıyla 58. satırdaki sınıf, bir JPA varlığı olmalıdır;
  • 56. satır: [fetch = FetchType.LAZY] açıklaması, [PRODUITS] tablosundan bir ürün getirildiğinde, ürünün kategorisinin (58. satır) hemen getirilmemesini (gecikmeli yükleme) talep eder. Kategori, [getCategorie] yöntemine ilk çağrı yapıldığında elde edilir. Bunun için, yürütme sırasında, JPA katmanı, başlangıçtaki [getCategorie] yöntemini (sadece categorie alanını döndüren) SGBD yöntemini çağırarak kategoriyi almak üzere zenginleştirir; bu, 'proxying' olarak adlandırılan bir tekniktir. JPA uygulamaları, yukarıda da belirttiğimiz gibi bu özelliğin uygulanışında farklılık gösterir. Bu öznitelik zorunlu değildir. Kullanılan JPA uygulaması bunu göz ardı etme hakkına sahiptir. [categorie] özelliğinin mevcut olabileceği veya olmayabileceği için 23. satırdaki jSON filtresini ekledik. [PRODUITS] tablosundaki [CATEGORIE_ID] birleştirme sütunu, ürün eklenirken veya güncellenirken otomatik olarak güncellenir. Bu sütun, [categorie.getId()] değerini alır; burada [categorie], 58. satırdaki alandır. JPA spesifikasyonu, bu birleştirme sütununun başka bir yolla güncellenememesini şart koşar. Ayrıca, 46. satırdaki [insertable = false, updatable = false] özniteliklerini de zorunlu kılar; bu öznitelikler sayesinde, [idCategorie] alanıyla ilişkili [CATEGORIE_ID] (yani birleştirme sütunu) [idCategorie] alanıyla ilişkili olarak [idCategorie] alanı tarafından değiştirilemez. Yalnızca [CATEGORIE_ID] sütununun [idCategorie] alanına aktarılması mümkün olacaktır;
  • satır 91-104: [Produit] varlıkları arasındaki eşleşme, bunların birincil anahtarları [id] arasındaki eşleşme olarak tanımlanmıştır;
  • satır 108-115: Test katmanımızı taşınabilir hale getirmek için, üç uygulamanın (JPA, [Hibernate, EclipseLink, OpenJpa]) [PROXY] varlıklarını tek tip bir şekilde yöneteceğiz. [Produit] türündeki bir [PROXY] türü için, [categorie] alanının değerinin değiştirilmesini engelleyeceğiz. [ProxyException] sınıfı şu şekildedir:
  

package generic.jpa.infrastructure;

import generic.jdbc.infrastructure.UncheckedException;

public class ProxyException extends UncheckedException {

    private static final long serialVersionUID = 7278276670314994574L;

    public ProxyException() {
    }

    public ProxyException(int code, Throwable e, String simpleClassName) {
        super(code, e, simpleClassName);
    }

}

Bu varlığın incelenmesini tamamlamak için, açıklamaların ve özniteliklerinin iki farklı durumda kullanıldığına dikkat edilmelidir:

  • veritabanı tablolarını oluşturmak için;
  • bu tabloları işlemek için. Bu durumda, JPA uygulaması, tabloları kendi oluşturduğu şekilde bulmayı bekler. Bu nedenle, önceki [Produit] varlığına herhangi bir [PRODUITS] tablosu eşleştirilemez. Bu tablonun, en azından (başka özellikleri de olabilir) kendi oluşturacağı [PRODUITS] tablosunun özelliklerine sahip olması gerekir. JPA ile çalışırken, en iyisi JPA'in tabloları oluşturmasına izin vereceğimiz boş bir veritabanından başlamaktır. Bu tablo oluşturma sürecini biraz sonra ele alacağız. SGBD ve MySQL için sağlanan SQL komut dosyası, JPA tarafından oluşturulan tablolardan oluşturulmuştur.

[Produit] varlığının tüm öznitelikleri, [PRODUITS] tablosunun oluşturulmasında kullanılır. Bu işlem tamamlandığında, [unique = true, length = 30, nullable = false] gibi oluşturma öznitelikleri, tabloların işlenmesinde artık kullanılmaz.

6.3.3.3. JPA varlığı [Categorie]

[Categorie] sınıfı, [CATEGORIES] tablosundaki bir satırla ilişkili bir JPA varlığıdır:

Kodu şöyledir:


package generic.jpa.entities.dbproduitscategories;

import generic.jdbc.config.ConfigJdbc;
import generic.jpa.infrastructure.ProxyException;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;

import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
@Table(name = ConfigJdbc.TAB_CATEGORIES)
@JsonFilter("jsonFilterCategorie")
public class Categorie implements AbstractCoreEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = ConfigJdbc.TAB_JPA_ID)
    protected Long id;

    @Version
    @Column(name = ConfigJdbc.TAB_JPA_VERSIONING)
    protected Long version;

    @Transient
    protected EntityType entityType = EntityType.POJO;

    @Transient
    @JsonIgnore
    protected String simpleClassName = getClass().getSimpleName();

    // özellikler
    @Column(name = ConfigJdbc.TAB_CATEGORIES_NOM, unique = true, length = 30, nullable = false)
    private String nom;

    // ilgili ürünler
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "categorie", cascade = { CascadeType.ALL })
    private List<Produit> produits;

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

    }

    public Categorie(Long id, Long version, String nom, List<Produit> produits) {
        this.id = id;
        this.version = version;
        this.nom = nom;
        this.produits = produits;
    }

    // imza
    public String toString() {
        return String.format("[id=%s, version=%s, nom=%s]", id, version, nom);
    }

    // yöntemler
    public void addProduit(Produit produit) {
        // varlık türü
        if (entityType == EntityType.PROXY) {
            throw new ProxyException(1004, new RuntimeException(
                    "On ne peut ajouter de produits à une catégorie de type [PROXY]"), simpleClassName);
        }
        // ürün ekleme
        if (produits == null) {
            produits = new ArrayList<Produit>();
        }
        if (produit != null) {
            // ürün ekleniyor
            produits.add(produit);
            // kategorisi belirleniyor
            produit.setCategorie(this);
            produit.setIdCategorie(this.id);
        }
    }

    // ------------------------------------------------------------
    // [equals] ve [hashcode]'in yeniden tanımlanması
    @Override
    public int hashCode() {
        Long id = getId();
        return (id != null ? id.hashCode() : 0);
    }

    @Override
    public boolean equals(Object entity) {
        if (!(entity instanceof AbstractCoreEntity)) {
            return false;
        }
        String class1 = this.getClass().getName();
        String class2 = entity.getClass().getName();
        if (!class2.equals(class1)) {
            return false;
        }
        AbstractCoreEntity other = (AbstractCoreEntity) entity;
        Long id = getId();
        Long otherId = other.getId();
        return id != null && otherId != null && id.equals(otherId);
    }

    // alıcı ve ayarlayıcılar
...
}
  • 24. satır: sınıf, JPA varlıktır;
  • 25. satır: [CATEGORIES] tablosuyla ilişkilidir;
  • 26. satır: [Categorie] varlığının jSON gösterimi, [jsonFilterCategorie] adlı filtre tarafından kontrol edilmektedir. Bu filtre, varlığın jSON temsiline yönelik herhangi bir istek gönderilmeden önce yapılandırılmalıdır. [jsonFilterCategorie] filtresi, [Categorie] varlığının jSON temsilinden 40. satırdaki [produits] alanını dahil etmek veya hariç tutmak için kullanılacaktır;
  • 29-32. satırlar: [id] alanı, [CATEGORIES] tablosunun [ID] birincil anahtarıyla ilişkilendirilmiştir. Seçilen oluşturma modu [IDENTITY] modudur; dolayısıyla MySQL için [AUTO_INCREMENT] modu geçerlidir;
  • 34-36. satırlar: [version] alanı, [CATEGORIES] tablosundaki [VERSIONING] sürümleme sütunuyla ilişkilidir;
  • satır 38-39: [Categorie] varlığının türü;
  • satır 41-43: [Categorie] sınıfının basit adı;
  • 46-47. satırlar: [nom] alanı, [CATEGORIES] tablosundaki [NOM] sütunuyla ilişkilidir. JPA ve [unique = true, length = 30, nullable=false] öznitelikleri bu alana atanır; böylece [CATEGORIES] tablosu oluşturulduğunda, [NOM] sütununa SQL ve [UNIQUE, VARCHAR(30), NOT NULL] öznitelikleri atansın;
  • 50-51. satırlar: kategoriye ait ürünler;
  • satır 50: [@OneToMany] notu, [Produit] varlığında karşılaştığımız [@ManyToOne] ilişkisinin ters ilişkisidir. [mappedBy = "categorie"] özniteliği, [@ManyToOne] ters ilişkisiyle etiketlenmiş [Produit] varlığının alanını belirtir. [cascade = { CascadeType.ALL }] özniteliği, bir @Entity [Categorie] üzerinde yapılan işlemlerin (persist, merge, remove) 51. satırdaki [produits]'lere kaskad olarak uygulanmasını gerektirir. [CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE] sabitleri kullanılarak kısmi zincirleme işlemleri belirtilebilir;
  • 50. satır: [fetch = FetchType.LAZY] özniteliği, [CATEGORIES] tablosundan bir kategori getirildiğinde, bu kategoriye ait ürünlerin hemen getirilmemesini sağlar. Ürünler, [getProduits] yöntemine ilk çağrıldığında getirilir. Bunun için, yürütme sırasında, JPA katmanı, başlangıçtaki [getProduits] yöntemini (sadece produits alanını döndüren) zenginleştirerek, kategorinin ürünlerini almak için SGBD yöntemini çağırır. Bu öznitelik zorunludur. JPA uygulaması bunu göz ardı edemez. [produits] özelliği başlatılabilir veya başlatılmayabilir; bu nedenle, 26. satırdaki jSON filtresini ekledik; bu filtre, bu özelliği isteyip istemediğimizi ve 39. satırdaki varlığın türünü belirtmemizi sağlayacaktır;
  • 71-88. satırlar: [addProduit] yöntemi, bir ürünü kategoriye eklemeye olanak tanır;
  • 73-76. satırlar: Farklı JPA uygulamaları arasında proxy yönetimini standartlaştırmak amacıyla, PROXY türündeki bir [Categorie] varlığına ürün eklenemeyeceğine karar verilmiştir;
  • satır 92-112: Aynı [id] birincil anahtarına sahip olan iki [Categorie] varlığı eşit kabul edilecektir;

6.3.4. [persistence.xml] dosyası

  

JPA uygulamaları, uygulamanın Classpath'inde bulunan [META-INF/persistence.xml] dosyasında, kullanılan JPA sağlayıcısının belirli özelliklerini ve kullanılacak JPA varlıklarını tanımlamalıdır. Yukarıda, bu dosya bir Eclipse projesinin Classpath'inin bir parçası olan [src/main/resources] klasörüne yerleştirilmiştir. JPA, Spring ile birlikte kullanıldığında, [persistence.xml] dosyasında bulunması gereken bazı bilgiler, Spring yapılandırma sınıflarında başka yerlere yerleştirilir. Bir Spring JPA uygulamasında, JPA dosyasını yöneten Spring'dir. Spring JPA Hibernate ile birlikte kullanıldığında, [persistence.xml] dosyası en basit haline indirgenebilir:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
    <persistence-unit name="dummy-persistence-unit" transaction-type="RESOURCE_LOCAL" />
</persistence>
  • 1-5. satırlar: Bir [persistence.xml] dosyası, <persistence> kök etiketine sahip olmalıdır. 2. satırdaki etiket öznitelikleri bu uygulamada kullanılmayacaktır;
  • bir kalıcılık dosyası, <persistence-unit> etiketiyle (4. satır) bir veya daha fazla kalıcılık birimi tanımlayabilir. Bir kalıcılık birimi, belirli bir veritabanına erişimi yönetir. Uygulama aynı anda iki veritabanını yönetiyorsa, iki kalıcılık birimine sahip olacaktır;
  • 4. satır: Bir kalıcılık biriminin adı [attribut name]'tir, [attribut transaction-type] işlem türünü destekler, özelliklere sahiptir ve kalıcılık birimi tarafından yönetilen veritabanındaki tablolara bağlı varlıkları tanımlar. Burada veritabanına erişimler [Spring JPA Hibernate] tarafından yönetileceğinden, bu son iki bilgi başka bir yere yerleştirilebilir. İki tür işlem vardır:
    • [RESOURCE_LOCAL]: İşlemler uygulamanın kendisi tarafından yönetilir. Burada durum böyledir; işlemleri Spring yönetecektir;
    • [JTA] (Java Transaction API): Uygulamayı çalıştıran EJB (Enterprise Java Bean) konteyneri, kodda bulunan Java anotasyonlarına göre işlemleri otomatik olarak yönetecektir. Burada bu yapılandırma söz konusu değildir;

Daha sonra göreceğiz ki, bu [persistence.xml] dosyasının içeriği, kullanılan JPA uygulamasına bağlıdır.

6.4. [spring-jpa-generic] projesi

Ne yapmak istediğimizi hatırlayalım. Aşağıdaki mimariyi uygulamak istiyoruz:

Bu mimaride, [DAO] katmanı, 4. bölümde incelediğimiz [IDao<Produit>, IDao<Categorie>] arayüzünü uygulayacaktır. Bu arayüzün iki uygulamasını karşılaştıracağız:

  • biri Spring ile oluşturulmuş olan JDBC;
  • diğeri ise Spring JPA ile oluşturulmuş olan;

Yukarıdaki mimaride:

  • [JDBC] katmanı, 3.3. paragrafta incelenen [mysql-config-jdbc] projesi tarafından uygulanmaktadır;
  • [JPA] katmanı, 6.3. paragrafta incelenen [mysql-config-jpa-hibernate] projesi tarafından uygulanmaktadır;

[spring-jpa-generic] projesi, [DAO] ve [Spring Data] katmanlarının uygulanmasını sağlar.

  

6.4.1. Maven Yapılandırması

[spring-jpa-generic] projesi, aşağıdaki [pom.xml] dosyası ile yapılandırılmış bir Maven projesidir:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>dvp.spring.database</groupId>
    <artifactId>spring-jpa-generic</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-jpa-generic</name>
    <description>démo spring data avec tables de catégories et de produits</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <!-- SGBD'in JPA yapılandırması -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>generic-config-jpa</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.7</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • 22-26. satırlar: Projenin tek bir bağımlılığı vardır; bu, uygulamanın [JPA] katmanını yapılandıran ve az önce incelediğimiz projeye olan bağımlılıktır. Bu, genel bir uygulamadır:
    • SGBD'e geçiş yapmak için [JDBC] katmanının yapılandırma projesini değiştiririz;
    • [JPA] katmanının yapılandırma projesini değiştirerek JPA uygulamasını değiştirebiliriz;

Sonuç olarak, bağımlılıklar şu şekildedir:

  

6.4.2. Spring Yapılandırması

  

[AppConfig] sınıfı, Spring projesini yapılandırır:


package spring.data.config;

import generic.jpa.config.ConfigJpa;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@EnableJpaRepositories(basePackages = { "spring.data.repositories" })
@Configuration
@ComponentScan(basePackages = { "spring.data.dao" })
@Import({ ConfigJpa.class })
public class AppConfig {

}
  • 11. satır: Sınıf, bir Spring yapılandırma sınıfıdır;
  • 10. satır: [@EnableJpaRepositories] anotasyonu, Spring Data'nın [CrudRepository] arayüzlerini içeren paketleri belirtmek için kullanılır. Bu, söz konusu paketleri diğer Spring bileşenlerine enjekte edilebilen Spring bileşenleri haline getirir;
  • 12. satır: [@ComponentScan] notu, Spring bileşenlerini aramak için [spring.data.dao] paketinin taranması gerektiğini belirtir. [DaoCategorie] ve [DaoProduit] bileşenleri bulunacaktır;
  • 13. satır: [ConfigJpa] yapılandırma sınıfına ait bean'ler içe aktarılır. Burada, kullanılan JPA uygulamasının bean'i (Hibernate, Eclipselink, OpenJpa), kullanılacak veri kaynağı, JPA işlemlerini yönetecek EntityManager ve işlem yöneticisi;

6.4.3. [Spring Data] katmanı

  

6.4.3.1. [CategoriesRepository] arayüzü

[CategoriesRepository] arayüzü, [CATEGORIES] tablosuna erişimi yönetir:


package spring.data.repositories;

import generic.jpa.entities.dbproduitscategories.Categorie;

import java.util.List;

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;

public interface CategoriesRepository extends CrudRepository<Categorie, Long> {

    // ürünlerini içeren kategori
    @Query("select c from Categorie c left join fetch c.produits where c.id=?1")
    public Categorie getLongCategorieById(Long id);

    @Query("select c from Categorie c left join fetch c.produits where c.nom=?1")
    public Categorie getLongCategorieByName(String nom);

    @Query("select c from Categorie c where c.nom in ?1")
    public List<Categorie> getShortCategoriesByName(Iterable<String> names);

    @Query("select c from Categorie c where c.id in ?1")
    public List<Categorie> getShortCategoriesById(Iterable<Long> ids);

    @Query("select distinct c from Categorie c left join fetch c.produits where c.id in ?1")
    public List<Categorie> getLongCategoriesById(List<Long> names);

    @Query("select distinct c from Categorie c left join fetch c.produits where c.nom in ?1")
    public List<Categorie> getLongCategoriesByName(List<String> names);

    @Query("select c from Categorie c")
    public List<Categorie> getAllShortCategories();

    @Query("select distinct c from Categorie c left join fetch c.produits")
    public List<Categorie> getAllLongCategories();

}
  • 10. satır: [CrudRepository] arayüzü, 5.1.3. paragrafında kullanılmış ve açıklanmıştır. Şunu hatırlatmak isteriz ki:
    • arayüzün 1. parametre türü, CRUD (findOne, findAll, kaydet, sil, deleteAll) erişimleri için yönetilen JPA varlığıdır;
    • arayüzün ikinci parametre türü, JPA varlığının birincil anahtarıdır; burada bir tamsayı olan [Long];

Arayüzün yöntemleri, JPQL (Java Persistence Query Language) sorguları ile uygulanır. Bu sorgu, JPA varlıklarını sorgular. Böyle bir sorguda:

  • tablolar, bunlara karşılık gelen JPA varlıklarıyla değiştirilir;
  • sütunlar, sorguda kullanılan JPA varlıklarının alanlarıyla değiştirilir;

31-32. satırları örnek olarak ele alalım: 32. satırdaki yöntem, veritabanındaki tüm kategorileri kısa biçimleriyle döndürür. Bu yöntem, 31. satırdaki JPQL (Java Persistence Query Language) sorgusu ile uygulanır ve bu sorgu, SQL sorgusuna oldukça benzemektedir. JPQL hakkında daha ayrıntılı bilgi için [ref2]'e bakabilirsiniz (bkz. paragraf 1.2).

[CategoriesRepository] arayüzünün yöntemleri şunlardır:

  • 13-14. satırlar: [getLongCategorieById] yöntemi, birincil anahtarı [id] ile referanslanan bir kategorinin uzun versiyonunu, yani ürünleri içeren kategoriyi döndürür. Hatırlanacağı üzere, [Categorie] varlığında, [produits] alanı [fetch = FetchType.LAZY] (gecikmeli yükleme) özniteliğine sahipti. JPQL sorgusunda, [fetch] anahtar kelimesi ile ürünlerin yüklenmesi zorlanmaktadır. Sorgudaki ?1 parametresi, yürütme sırasında 12. satırdaki yöntemin ilk parametresinin değeriyle, yani [Long id] parametresiyle değiştirilecektir;
  • 16-17. satırlar: [getLongCategorieByName] yöntemi, [nom] adıyla referans verilen bir kategorinin uzun sürümünü döndürür;
  • satır 19-20: [getShortCategoriesByName] yöntemi, adlarıyla referans verilen kategorilerin kısa sürümlerini döndürür. Bu kategorilerin [produits] alanı, null değildir. Bu alan, çağrıldığında kategorideki ürünleri döndürme görevini üstlenen bir proxy'nin (JPA uygulaması tarafından oluşturulan bir sınıf) referansını içerir. Bu proxy'nin JPA kalıcılık bağlamı dışında çağrılması bir istisna oluşturur (Hibernate ve OpenJpa, ancak EclipseLink değil). Bu nedenle, bir kategorinin kısa versiyonundaki [produits] alanını kullanmayacağız;
  • 22-23. satırlar: [getShortCategoriesById] yöntemi, birincil anahtarları [id] ile referanslanan kategorilerin kısa sürümlerini döndürür;
  • satır 25-26: [getLongCategoriesById] yöntemi, birincil anahtarları [id] ile referanslanan kategorilerin uzun versiyonlarını döndürür;
  • [28-29] satırları: [getLongCategoriesByName] yöntemi, adlarıyla referans verilen kategorilerin uzun sürümlerini döndürür;
  • satır 31-32: [getAllShortCategories] yöntemi, tüm kategorilerin kısa sürümlerini döndürür;
  • satır 34-35: [getAllLongCategories] yöntemi, tüm kategorilerin uzun versiyonlarını döndürür;

Not: JPA uygulamalarının tümü, JPQL ile aynı sözdizimini kabul etmez. Bu nedenle, aşağıdaki sözdizimi Hibernate ve EclipseLink tarafından kabul edilirken, OpenJpa tarafından kabul edilmez:


@Query("select c from Categorie c left join fetch c.produits p where c.nom=?1")

OpenJpa, yukarıdaki [p] takma adını kabul etmez.

6.4.3.2. [ProduitsRepository] arayüzü

[ProduitsRepository] arayüzü, [PRODUITS] tablosuna erişimi yönetir:


package spring.data.repositories;

import generic.jpa.entities.dbproduitscategories.Produit;

import java.util.List;

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional;

@Transactional()
public interface ProduitsRepository extends CrudRepository<Produit, Long> {

    // bir ürün ve kategorisi
    @Query("select p from Produit p left join fetch p.categorie where p.id=?1")
    public Produit getLongProduitById(Long id);

    @Query("select p from Produit p left join fetch p.categorie where p.nom=?1")
    public Produit getLongProduitByName(String nom);

    @Query("select p from Produit p where p.id in ?1")
    public List<Produit> getShortProduitsById(List<Long> ids);

    @Query("select p from Produit p where p.nom in ?1")
    public List<Produit> getShortProduitsByName(List<String> names);

    @Query("select distinct p from Produit p left join fetch p.categorie where p.id in ?1")
    public List<Produit> getLongProduitsById(List<Long> ids);

    @Query("select distinct p from Produit p left join fetch p.categorie where p.nom in ?1")
    public List<Produit> getLongProduitsByName(List<String> names);

    @Query("select distinct p from Produit p left join fetch p.categorie")
    public List<Produit> getAllLongProduits();

    @Query("select p from Produit p")
    public List<Produit> getAllShortProduits();
}
  • [15-16] satırları: [getLongProduitById] yöntemi, birincil anahtarı [id] ile tanımlanan bir ürünün uzun sürümünü, yani kategorisi ile birlikte döndürür. Hatırlanacağı üzere, [Produit] varlığında, [categorie] alanı [fetch = FetchType.LAZY] özniteliğine (gecikmeli yükleme) sahipti. JPQL sorgusunda, [fetch] anahtar sözcüğüyle kategorinin yüklenmesi zorlanıyor;
  • 18-19. satırlar: [getLongProduitByName] yöntemi, adıyla tanımlanan bir ürünün uzun sürümünü döndürür;
  • 21-22. satırlar: [getShortProduitsById] yöntemi, birincil anahtarı [id] ile tanımlanan ürünlerin kısa sürümünü döndürür. Bu kısa versiyonda, [categorie] alanı null değerine sahip değildir. Bu alan, JPA uygulaması tarafından oluşturulan bir proxy referansını içerir; bu proxy çağrıldığında ürünün kategorisini alır. Bu çağrı yalnızca JPA kalıcılık bağlamında yapılabilir. Başka bir yerde yapılması bir istisna oluşturur (Hibernate ve OpenJpa, ancak EclipseLink hariç). Dolayısıyla, [DAO] katmanında veya başka bir yerde, bir ürünün kısa versiyonunda [categorie] alanını kullanmayacağız. Ürünün kısa versiyonunda [idCategorie] alanı başlatılır. Bu alanın değeri, ürünün ait olduğu kategorinin birincil anahtarıdır. Bu, daha sonra [DaoCategorie. getShortCategoriesById(idCategorie)] yöntemi aracılığıyla [DAO] katmanından bu kategoriyi sorgulamayı mümkün kılar;
  • 24-25. satırlar: [getShortProduitsByName] yöntemi, adlarıyla tanımlanan ürünlerin kısa versiyonunu döndürür;
  • satır 27-28: [getLongProduitsById] yöntemi, birincil anahtarlarıyla tanımlanan ürünlerin uzun sürümünü döndürür;
  • satır 30-31: [getLongProduitsByName] yöntemi, adlarıyla tanımlanan ürünlerin uzun sürümünü döndürür;
  • satır 33-34: [getAllLongProduits] yöntemi, tüm ürünlerin uzun sürümünü döndürür;
  • satır 36-37: [getAllShortProduits] yöntemi, tüm ürünlerin kısa versiyonlarını döndürür;

Bu arayüzler, projenin yürütülmesi sırasında JPA uygulaması tarafından oluşturulan sınıflar aracılığıyla uygulanacaktır. Bu tür sınıflara [proxy] sınıfları denir. Varsayılan olarak, [CrudRepository] arayüzünün yöntemleri bir işlem içinde yürütülür. [ProduitsRepository, CategoriesRepository] arayüzlerinin [CrudRepository] sınıfını genişletmesi, bunların Spring bileşenleri olmasını sağlar. Bu nedenle, diğer Spring bileşenlerine enjekte edilebilirler.

6.4.4. [DAO] katmanı

  

6.4.4.1. [IDao<T>] arayüzü

[IDao<T>] arayüzü, Spring JDBC ile gerçekleştirilen [DAO] katmanının uygulamasında daha önce incelenen arayüzdür (bkz. paragraf 4.7);


package spring.data.dao;

import generic.jpa.entities.dbproduitscategories.AbstractCoreEntity;

import java.util.List;

public interface IDao<T extends AbstractCoreEntity> {

    // T varlıklarının tam listesi
    public List<T> getAllShortEntities();

    public List<T> getAllLongEntities();

    // belirli varlıklar - kısa sürüm
    public List<T> getShortEntitiesById(Iterable<Long> ids);

    public List<T> getShortEntitiesById(Long... ids);

    public List<T> getShortEntitiesByName(Iterable<String> names);

    public List<T> getShortEntitiesByName(String... names);

    // belirli varlıklar - uzun sürüm
    public List<T> getLongEntitiesById(Iterable<Long> ids);

    public List<T> getLongEntitiesById(Long... ids);

    public List<T> getLongEntitiesByName(Iterable<String> names);

    public List<T> getLongEntitiesByName(String... names);

    // birden fazla vitenin güncellenmesi
    public List<T> saveEntities(Iterable<T> entities);

    public List<T> saveEntities(@SuppressWarnings("unchecked") T... entities);

    // tüm varlıkların silinmesi
    public void deleteAllEntities();

    // birden fazla varlığın silinmesi
    public void deleteEntitiesById(Iterable<Long> ids);

    public void deleteEntitiesById(Long... ids);

    public void deleteEntitiesByName(Iterable<String> names);

    public void deleteEntitiesByName(String... names);

    public void deleteEntitiesByEntity(Iterable<T> entities);

    public void deleteEntitiesByEntity(@SuppressWarnings("unchecked") T... entities);
}

6.4.4.2. [AbstractDao] soyut sınıfı

  

Soyut sınıf [AbstractDao], [DAO] katmanını uygulayan sınıfların üst sınıfıdır:

  • [IDao<Produit>] arayüzünü uygulayan ve [PRODUITS] tablosuna erişimi yöneten [DaoProduit] sınıfı;
  • [IDao<Categorie>] arayüzünü uygulayan ve [CATEGORIES] tablosuna erişimi yöneten [DaoCategorie] sınıfı;

Kodu, 4.8. paragrafta açıklanan kodla aynıdır, ancak şu ayrıntı dışında: hiçbir yöntem, yöntemin bir işlem içinde yürütülmesini sağlayan [@Transactional] özniteliğine sahip değildir. Burada, Spring Data'nın [CrudRepository] arayüzlerinin varsayılan olarak bir işlem içinde yürütüldüğü gerçeğinden yararlanılmaktadır.

6.4.4.3. [DaoCategorie] sınıfı

  

[DaoCategorie] sınıfı, [IDao<Categorie>] arayüzünü şu şekilde uygular:


package spring.data.dao;

import generic.jpa.entities.dbproduitscategories.AbstractCoreEntity.EntityType;
import generic.jpa.entities.dbproduitscategories.Categorie;
import generic.jpa.entities.dbproduitscategories.Produit;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import spring.data.infrastructure.DaoException;
import spring.data.repositories.CategoriesRepository;
import spring.data.repositories.ProduitsRepository;

@Component
public class DaoCategorie extends AbstractDao<Categorie> {

    @Autowired
    private ProduitsRepository produitsRepository;

    @Autowired
    private CategoriesRepository categoriesRepository;

    @Override
    public List<Categorie> getAllShortEntities() {
        try {
            return setShortCategoriesType(categoriesRepository.getAllShortCategories());
        } catch (Exception e) {
            throw new DaoException(211, e, simpleClassName);
        }
    }

    private List<Categorie> setShortCategoriesType(List<Categorie> categories) {
        for (Categorie categorie : categories) {
            categorie.setEntityType(EntityType.PROXY);
        }
        return categories;
    }

    @Override
    public List<Categorie> getAllLongEntities() {
        try {
            return categoriesRepository.getAllLongCategories();
        } catch (Exception e) {
            throw new DaoException(202, e, simpleClassName);
        }
    }

    @Override
    public void deleteAllEntities() {
        try {
            categoriesRepository.deleteAll();
        } catch (Exception e) {
            throw new DaoException(208, e, simpleClassName);
        }
    }

    @Override
    protected List<Categorie> getShortEntitiesById(List<Long> ids) {
        try {
            return setShortCategoriesType(categoriesRepository.getShortCategoriesById(ids));
        } catch (Exception e) {
            throw new DaoException(203, e, simpleClassName);
        }
    }

    @Override
    protected List<Categorie> getShortEntitiesByName(List<String> names) {
        try {
            return setShortCategoriesType(categoriesRepository.getShortCategoriesByName(names));
        } catch (Exception e) {
            throw new DaoException(204, e, simpleClassName);
        }
    }

    @Override
    protected List<Categorie> getLongEntitiesById(List<Long> ids) {
        try {
            return categoriesRepository.getLongCategoriesById(ids);
        } catch (Exception e) {
            throw new DaoException(205, e, simpleClassName);
        }
    }

    @Override
    protected List<Categorie> getLongEntitiesByName(List<String> names) {
        try {
            return categoriesRepository.getLongCategoriesByName(names);
        } catch (Exception e) {
            throw new DaoException(206, e, simpleClassName);
        }
    }

    @Override
    protected List<Categorie> saveEntities(List<Categorie> categories) {
    ...
    }

    @Override
    protected void deleteEntitiesById(List<Long> ids) {
        try {
            categoriesRepository.delete(getShortEntitiesById(ids));
        } catch (Exception e) {
            throw new DaoException(209, e, simpleClassName);
        }
    }

    @Override
    protected void deleteEntitiesByName(List<String> names) {
        try {
            categoriesRepository.delete(getShortEntitiesByName(names));
        } catch (Exception e) {
            throw new DaoException(212, e, simpleClassName);
        }
    }

}
  • 17. satır: [@Component] açıklaması, [DaoCategorie] sınıfını bir Spring bileşeni haline getirir;
  • 18. satır: [DaoCategorie] sınıfı, [AbstractDao<Categorie>] sınıfını genişletir; bu sayede [IDao<Categorie>] arayüzünü uygular;
  • 20-24. satırlar: [CrudRepository] ve [Spring Data] arayüzlerine referans enjeksiyonu yapılır. Bu enjeksiyon, Spring nesnelerinin örneklendirilmesi sırasında, genellikle Spring projesinin yürütülmesinin başlangıcında gerçekleşir;
  • sınıfın tüm yöntemleri, işi [CrudRepository] arayüzlerindeki aynı isimli yöntemlere devreder;
  • entiteleri kısa versiyonlarına geri dönüştüren tüm yöntemler, entitenin türünü [EntityType.PROXY] olarak belirleyerek bunu belirtir (29., 63. ve 72. satırlar);

[saveEntities] yöntemi bir açıklama gerektirir:


@Override
    protected List<Categorie> saveEntities(List<Categorie> categories) {
        // eklenecek ürünler not edilir
        List<Produit> insertedProduits = new ArrayList<Produit>();
        for (Categorie categorie : categories) {
            EntityType categorieType = categorie.getEntityType();
            List<Produit> produits = null;
            if ((categorieType == EntityType.POJO) && (produits = categorie.getProduits()) != null) {
                for (Produit produit : produits) {
                    if (produit.getId() == null) {
                        insertedProduits.add(produit);
                    }
                    // bu fırsatı değerlendirerek (gerekirse) ürün --> kategori ilişkisini yeniden kurulur
                    produit.setCategorie(categorie);
                }
            }
        }
        // kategoriler / ürünler kalıcı hale getirilir
        try {
            categoriesRepository.save(categories);
        } catch (Exception e) {
            throw new DaoException(201, e, simpleClassName);
        }
        // Eklenen ürünlerin [idCategorie] alanı güncelleniyor
        for (Produit produit : insertedProduits) {
            produit.setIdCategorie(produit.getCategorie().getId());
        }
        // sonuç
        return categories;
    }
  • 2. satır: Parametre olarak geçirilen kategoriler, hem eklenecek ([id==null]) hem de değiştirilecek ([id!=null]) kategorilerdir;
  • 20. satır: Kategoriler, [categoriesRepository.save(entities)] yöntemi ile kalıcı hale getirilir. Testlerde, kalıcı hale getirilen ürünlerin (id==null) [idCategorie] alanının doldurulmadığı görülür. Bu sorunu gidermek için, 4-17. satırlarda eklenecek ürünler kaydedilir ve kalıcı hale getirildikten sonra [idCategorie] alanları doldurulur (25-27. satırlar);
  • 5-17. satırlar: Kategori listesi taranır;
  • 8-16. satırlar: her kategori için ürün listesi taranır. Burada bir zorluk vardır. [saveEntities] yöntemi, hem bir kategoriyi kalıcı hale getirmek hem de değiştirmek için kullanılır. İkinci durumda, kategori kısa versiyonuyla alınmış olabilir; dolayısıyla [produits] alanında bir proxy yöntem referansı bulunur. Bunu Hibernate ile kullanmak bir istisna oluşturur; çünkü kullanılan kategori, kategorilerin kısa sürümlerini getiren yöntemin işleminin sona ermesiyle kapatılan JPA kalıcılık bağlamında artık bulunmamaktadır. Bu durumda, 8. satırdaki [Categorie] varlığının [EntityType] alanı kullanılarak, kategorideki ürün listesine erişilip erişilemeyeceği belirlenir;
  • 14. satır: Ürün, kategorisine bağlanır. Normalde bu bağlantı zaten kurulmuş olmalıdır. Ancak bu ürünün nasıl oluşturulduğu ve kategorisine bağlanıp bağlanmadığı bilinmemektedir. Bu nedenle herhangi bir sorunu önlemek için ([Produit] varlığını yönetmek üzere, JPA'in, bağlı olduğu [Categorie] varlığına referans vermesi gerekir), bu bağlantıyı kendimiz kuruyoruz.

Bu kodu, Spring uygulaması JDBC’teki [DaoProduit] sınıfının koduyla karşılaştırdığımızda (bkz. paragraf 4.9) görüldüğü üzere, Spring Data kütüphanesi JPA, [DAO] katmanının yazılmasını büyük ölçüde kolaylaştırmaktadır.

6.4.4.4. [DaoProduit] sınıfı

  

[DaoProduit] sınıfı, [IDao<Produit>] arayüzünü şu şekilde uygular:


package spring.data.dao;

import generic.jpa.entities.dbproduitscategories.AbstractCoreEntity.EntityType;
import generic.jpa.entities.dbproduitscategories.Categorie;
import generic.jpa.entities.dbproduitscategories.Produit;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import spring.data.infrastructure.DaoException;
import spring.data.repositories.CategoriesRepository;
import spring.data.repositories.ProduitsRepository;

import com.google.common.collect.Lists;

@Component
public class DaoProduit extends AbstractDao<Produit> {
    @Autowired
    private ProduitsRepository produitsRepository;

    @Autowired
    private CategoriesRepository categoriesRepository;

    @Override
    public List<Produit> getAllShortEntities() {
        try {
            return setShortProduitsType(produitsRepository.getAllShortProduits());
        } catch (Exception e) {
            throw new DaoException(102, e, simpleClassName);
        }
    }

    private List<Produit> setShortProduitsType(List<Produit> produits) {
        for (Produit produit : produits) {
            produit.setEntityType(EntityType.PROXY);
        }
        return produits;
    }

    @Override
    public List<Produit> getAllLongEntities() {
        try {
            return produitsRepository.getAllLongProduits();
        } catch (Exception e) {
            throw new DaoException(117, e, simpleClassName);
        }
    }

    @Override
    public void deleteAllEntities() {
        try {
            produitsRepository.deleteAll();
        } catch (Exception e) {
            throw new DaoException(112, e, simpleClassName);
        }
    }

    @Override
    protected List<Produit> getShortEntitiesById(List<Long> ids) {
        try {
            return setShortProduitsType(produitsRepository.getShortProduitsById(ids));
        } catch (Exception e) {
            throw new DaoException(103, e, simpleClassName);
        }
    }

    @Override
    protected List<Produit> getShortEntitiesByName(List<String> names) {
        try {
            return setShortProduitsType(produitsRepository.getShortProduitsByName(names));
        } catch (Exception e) {
            throw new DaoException(104, e, simpleClassName);
        }
    }

    @Override
    protected List<Produit> getLongEntitiesById(List<Long> ids) {
        try {
            return linkLongProduitsToCategories(produitsRepository.getLongProduitsById(ids));
        } catch (Exception e) {
            throw new DaoException(105, e, simpleClassName);
        }
    }

    @Override
    protected List<Produit> getLongEntitiesByName(List<String> names) {
        try {
            return linkLongProduitsToCategories(produitsRepository.getLongProduitsByName(names));
        } catch (Exception e) {
            throw new DaoException(106, e, simpleClassName);
        }
    }

    private List<Produit> linkLongProduitsToCategories(List<Produit> produits) {
        for (Produit produit : produits) {
            Categorie categorie = produit.getCategorie();
            if (categorie != null) {
                produit.setCategorie(categorie);
                produit.setIdCategorie(categorie.getId());
            }
        }
        return produits;
    }

    @Override
    protected List<Produit> saveEntities(List<Produit> entities) {
        // bir ürün ile kategorisi arasındaki bağlantı (gerekirse) yeniden kurulur
        for (Produit produit : entities) {
            if (produit.getEntityType() == EntityType.POJO) {
                produit.setCategorie(new Categorie(produit.getIdCategorie(), 0L, null, null));
            }
        }
        // ürünler kalıcı hale getirilir
        try {
            return Lists.newArrayList(produitsRepository.save(entities));
        } catch (Exception e) {
            throw new DaoException(111, e, simpleClassName);
        }
    }

    @Override
    protected void deleteEntitiesById(List<Long> ids) {
        try {
            produitsRepository.delete(getShortEntitiesById(ids));
        } catch (Exception e) {
            throw new DaoException(113, e, simpleClassName);
        }
    }

    @Override
    protected void deleteEntitiesByName(List<String> names) {
        try {
            produitsRepository.delete(getShortEntitiesByName(names));
        } catch (Exception e) {
            throw new DaoException(118, e, simpleClassName);
        }
    }

}

Kod, [DaoCategorie] sınıfındakine benzerdir:

  • kategorilerin uzun sürümleri için yapılan testlerde, ürünlerin [idCategorie] alanının doldurulmadığı görülmüştür. 96-105. satırlardaki [linkLongProduitsToCategories] yöntemi bu sorunu giderir;
  • 108-121. satırlardaki [saveEntities] yöntemi, yeni ürünler ekler veya mevcut ürünleri değiştirir. JPA katmanı, her bir [Produit] varlığının bir [Categorie] varlığına bağlanmasını gerektirir. Kullanıcının bunu yapıp yapmadığı bilinmediğinden, 110-113. satırlarda bunu kendimiz gerçekleştiriyoruz. [Produit]'i, birincil anahtarı [Produit]'in [idCategorie] alanına eşit olan bir [Categorie] varlığına bağlamak yeterlidir. Testlerde, kategori sürümü olarak null girildiğinde bir hata oluştuğu görülmektedir. Bu nedenle buraya 0 değeri girilmiştir, ancak istenilen herhangi bir değer girilebilir. Birincil anahtar dışında, [Categorie] varlığının hiçbir alanı, JPA katmanında bir [Produit] varlığını eklemek veya değiştirmek için gerekli değildir;

6.4.5. Test katmanı

  

Yukarıdaki testler, Spring uygulaması JDBC'teki testlerle aynıdır. Gerekirse aşağıdaki sayfalara bakınız:

  • [JUnitTestCheckArguments]: 4.11.1. paragraf;
  • [JUnitTestDao]: 4.11.2. paragraf;
  • [JUnitTestPushTheLimits]: 4.11.3. paragraf;

Aşağıdaki yürütme yapılandırmalarını kullanıyoruz:

Çeşitli testlerde elde edilen sonuçlar şunlardır:

[1]'te, [JUnitTestPushTheLimits] testi, Spring Data JPA Hibernate uygulamasıyla ve [2]'te ise Spring JDBC uygulamasıyla gerçekleştirilmiştir. Görüldüğü üzere, Spring Data ile yapılan uygulama daha yüksek performans gösteriyor. Dolayısıyla ilk sonucumuza varıyoruz: Spring Data ile bir katman geliştirmek çok daha kolaydır, ancak bu, Spring ile yapılan bir uygulamaya göre daha düşük performans gösterir.

[JUnitTestProxies] testi, sahte bir JUnit testidir. Bu test, her bir JPA uygulamasının proxy’lere, yani varlıkların kısa sürümlerine karşı nasıl davrandığını göstermek amacıyla yapılmıştır:


package spring.data.tests;

import generic.jpa.entities.dbproduitscategories.Categorie;
import generic.jpa.entities.dbproduitscategories.Produit;

import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import spring.data.config.AppConfig;
import spring.data.dao.IDao;

import com.google.common.collect.Lists;

@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTestProxies {

    // katman [DAO]
    @Autowired
    private IDao<Produit> daoProduit;
    @Autowired
    private IDao<Categorie> daoCategorie;

    @Before
    public void clean() {
        // her testten önce veritabanı temizlenir
        log("Vidage de la base de données", 1);
        // [CATEGORIES] tablosu ve buna bağlı olarak [PRODUITS] tablosu boşaltılır
        daoCategorie.deleteAllEntities();
    }

    @Test
    public void doNothing() {
        System.out.println("doNothing");
    }

    private List<Categorie> fill(int nbCategories, int nbProduits) {
        // tablolar doldurulur
        List<Categorie> categories = new ArrayList<Categorie>();
        for (int i = 0; i < nbCategories; i++) {
            Categorie categorie = new Categorie(null, null, String.format("categorie[%d]", i), null);
            categorie.setProduits(new ArrayList<Produit>());
            for (int j = 0; j < nbProduits; j++) {
                Produit produit = new Produit(null, null, String.format("produit[%d,%d]", i, j), null,
                        100 * (1 + (double) (i * 10 + j) / 100), String.format("desc[%d,%d]", i, j), null);
                categorie.addProduit(produit);
            }
            categories.add(categorie);
        }
        // kategori eklenir - buna bağlı olarak ürünler de
        // eklenecek
        daoCategorie.saveEntities(categories);
        // sonuç
        return categories;
    }

    @Test
    public void getShortCategoriesByName1() {
        // doldurma
        fill(1, 1);
        // test
        log("getShortCategoriesByName1", 1);
        Categorie categorie = daoCategorie.getShortEntitiesByName(Lists.newArrayList("categorie[0]")).get(0);
        System.out.println(String.format("Catégorie de type : %s", categorie.getEntityType()));
        System.out.println("Catégorie :");
        try {
            System.out.println(categorie.getProduits().size());
        } catch (Exception e) {
            System.err.println(String.format("Exception : %s, Message : %s", e.getClass().getName(), e.getMessage()));
        }
    }

    @Test
    public void getShortProduitsByName1() {
        // doldurma
        fill(1, 1);
        // test
        log("getShortProduitsByName1", 1);
        Produit produit = daoProduit.getShortEntitiesByName(Lists.newArrayList("produit[0,0]")).get(0);
        System.out.println(String.format("Produit de type : %s", produit.getEntityType()));
        System.out.println("Nom de la catégorie du produit :");
        try {
            System.out.println(produit.getCategorie().getNom());
        } catch (Exception e) {
            System.err.println(String.format("Exception : %s, Message : %s", e.getClass().getName(), e.getMessage()));
        }
    }

    @Test
    public void getLongCategoriesByName1() {
        // doldurma
        fill(1, 1);
        // test
        log("getLongCategoriesByName1", 1);
        Categorie categorie = daoCategorie.getLongEntitiesByName(Lists.newArrayList("categorie[0]")).get(0);
        System.out.println(String.format("Catégorie de type : %s", categorie.getEntityType()));
        System.out.println("Catégorie :");
        try {
            System.out.println(categorie.getProduits().size());
        } catch (Exception e) {
            System.err.println(String.format("Exception : %s, Message : %s", e.getClass().getName(), e.getMessage()));
        }
    }

    @Test
    public void getLongProduitsByName1() {
        // doldurma
        fill(1, 1);
        // test
        log("getLongProduitsByName1", 1);
        Produit produit = daoProduit.getLongEntitiesByName(Lists.newArrayList("produit[0,0]")).get(0);
        System.out.println(String.format("Produit de type : %s", produit.getEntityType()));
        System.out.println("Nom de la catégorie du produit :");
        try {
            System.out.println(produit.getCategorie().getNom());
        } catch (Exception e) {
            System.err.println(String.format("Exception : %s, Message : %s", e.getClass().getName(), e.getMessage()));
        }
    }

    private void log(String message, int mode) {
        // mesaj görüntüleme
        String toPrint = null;
        switch (mode) {
        case 1:
            toPrint = String.format("%s --------------------------------", message);
            break;
        case 2:
            toPrint = String.format("-- %s", message);
            break;
        }
        System.out.println(toPrint);
    }

}

Elde edilen sonuçlar şunlardır:


Vidage de la base de données --------------------------------
doNothing
Vidage de la base de données --------------------------------
getShortCategoriesByName1 --------------------------------
Catégorie de type : PROXY
Catégorie :
Exception : org.hibernate.LazyInitializationException, Message : failed to lazily initialize a collection of role: generic.jpa.entities.dbproduitscategories.Categorie.produits, could not initialize proxy - no Session
Vidage de la base de données --------------------------------
getLongCategoriesByName1 --------------------------------
Catégorie de type : POJO
Catégorie :
1
Vidage de la base de données --------------------------------
getShortProduitsByName1 --------------------------------
Produit de type : PROXY
Nom de la catégorie du produit :
Exception : org.hibernate.LazyInitializationException, Message : could not initialize proxy - no Session
Vidage de la base de données --------------------------------
getLongProduitsByName1 --------------------------------
Produit de type : POJO
Nom de la catégorie du produit :
categorie[0]

Burada görüldüğü gibi, PROXY türündeki bir kategorinin [Categorie.produits] alanına ve PROXY türündeki bir ürünün [Produit.categorie] alanına erişildiğinde, her iki durumda da (7. ve 17. satırlar) [org.hibernate.LazyInitializationException] türünde bir istisna oluştuğu görülmektedir.