Skip to content

11. [Cours]: Spring Data ile ilişkisel veritabanı yönetimi

Anahtar kelimeler: çok katmanlı mimari, Spring, bağımlılık enjeksiyonu, API JPA (Java Persistence API), Spring Data.

[DAO] katmanını, Spring ekosisteminin bir dalı olan TD ile [Spring Data] kullanarak uygulayacağız. [Spring Data], JPA (Java Persistence API) katmanına dayanır; bu katman, [DAO] katmanının SQL komutları yerine nesneleri işleyebilmesini sağlar. Sonuç olarak, [DAO] katmanı bir veritabanıyla iletişim kurduğunun farkında değildir. Yalnızca [Spring Data] katmanının arayüzünü tanır.

Öncelikle iki örnek üzerinden [Spring Data]'i inceleyeceğiz.

11.1. Support

  • [1]'te, [support / chap-11] klasörü üç Eclipse projesi içerir;
  • [2] dosyasında ise bu bölümdeki örnek veritabanını oluşturmaya yarayan SQL komut dosyası;

11.2. Örnek 1

Spring web sitesinde, Spring'e başlamak için çok sayıda öğretici bulunmaktadır ([http://spring.io/guides]). Spring Data'yı tanıtmak için bunlardan birini kullanacağız. Bunun için Spring Tool Suite'i (STS) kullanacağız.

  • [1]'te, [spring.io/guides]'teki öğreticilerden birini içe aktarıyoruz;
  • [2]'te, Spring Data ile bir veritabanına nasıl erişileceğini gösteren [Accessing Data Jpa] öğreticisini seçiyoruz;
  • [3]'te, Maven tarafından yapılandırılmış bir proje seçilir;
  • [4]'te, öğretici iki şekilde sunulabilir: öğreticiyi takip ederek doldurulacak boş bir sürüm olan [initial] veya öğreticinin son hali olan [complete]. Biz ikincisini seçiyoruz;
  • [5]'te, öğreticiyi bir tarayıcıda görüntülemeyi seçebiliriz;
  • [6]'te ise nihai projeyi görebiliriz.

11.2.1. Projenin Maven yapılandırması

Projenin Maven bağımlılıkları [pom.xml] dosyasında yapılandırılmıştır:


    <groupId>org.springframework</groupId>
    <artifactId>gs-accessing-data-jpa</artifactId>
    <version>0.1.0</version>

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

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <!-- her şey için UTF-8 kullan -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <start-class>hello.Application</start-class>
</properties>
  • 5-9. satırlar: bir üst Maven projesini tanımlar. Projenin bağımlılıklarının büyük kısmını bu proje tanımlar. Bu bağımlılıklar yeterli olabilir; bu durumda başka bağımlılık eklenmez, ya da yeterli olmayabilir; bu durumda eksik bağımlılıklar eklenir;
  • 12-15. satırlar: [spring-boot-starter-data-jpa]'e bir bağımlılık tanımlar. Bu artefakt, Spring Data sınıflarını içerir;
  • satır 16-19: bellek içi veritabanları oluşturmaya ve yönetmeye olanak tanıyan SGBD ve H2'e bir bağımlılık tanımlar.

Bu bağımlılıkların getirdiği sınıflara bir göz atalım:

Bunların sayısı oldukça fazla:

  • bazıları Spring ekosistemine aittir (spring ile başlayanlar);
  • diğerleri ise Hibernate ekosistemine aittir (hibernate, jboss); burada JPA uygulamasını kullanıyoruz;
  • bazıları test kütüphaneleridir (junit, hamcrest);
  • bazıları ise günlük kütüphaneleridir (log4j, logback, slf4j);

Hepsini saklayacağız. Üretim ortamındaki bir uygulama için ise sadece gerekli olanları saklamak gerekir.

[pom.xml] dosyasının 26. satırında şu satır yer almaktadır:


<start-class>hello.Application</start-class>

Bu satır, aşağıdaki satırlarla bağlantılıdır:


<build>
        <plugins>
            <plugin> 
                <artifactId>maven-compiler-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

6-9. satırlarda, [spring-boot-maven-plugin] eklentisi uygulamanın çalıştırılabilir jar dosyasını oluşturmaya olanak tanır. [pom.xml] dosyasının 26. satırı ise bu jar dosyasının çalıştırılabilir sınıfını belirtir.

11.2.2. [JPA] katmanı

Veritabanına erişim, [JPA] katmanı ve Java Persistence API aracılığıyla gerçekleştirilir:

  

Uygulama basittir ve [Customer] istemcilerini yönetir. [Customer] sınıfı, [JPA] katmanının bir parçasıdır ve şu şekildedir:


package hello;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String firstName;
    private String lastName;

    protected Customer() {
    }

    public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName);
    }

}

Bir müşterinin kimliği [id], adı [firstName] ve soyadı [lastName]'tir. Her [Customer] örneği, veritabanındaki bir tablonun bir satırını temsil eder.

  • 8. satır: JPA anotasyonu, [Customer] örneklerinin kalıcılığının (Oluşturma, Okuma, Güncelleme, Silme) JPA uygulaması tarafından yönetileceğini belirtir. Maven bağımlılıklarına göre, JPA / Hibernate uygulamasının kullanıldığı görülmektedir;
  • 11-12. satırlar: JPA anotasyonları, [id] alanını [Customer] tablosunun birincil anahtarıyla ilişkilendirir. 12. satır, JPA uygulamasının, kullanılan SGBD'e özgü birincil anahtar oluşturma yöntemini (burada H2) kullanacağını belirtir;

JPA ile ilgili başka bir açıklama yoktur. Bu durumda varsayılan değerler kullanılacaktır:

  • [Customer] tablosu, sınıfın adını taşıyacaktır, yani [Customer];
  • bu tablonun sütunları, sınıfın alanlarının adlarını taşıyacaktır: [id, firstName, lastName]; tablonun sütun adlarında büyük/küçük harf ayrımı yapılmadığını unutmayın;

Kullanılan JPA uygulamasının hiçbir aşamada adının belirtilmediğine dikkat edilmelidir.

11.2.3. [Spring Data] katmanı

[CustomerRepository] sınıfı, [Customer] tablosuna erişim katmanını uygular. Kod şöyledir:

  

package hello;

import java.util.List;

import org.springframework.data.repository.CrudRepository;

public interface CustomerRepository extends CrudRepository<Customer, Long> {

    List<Customer> findByLastName(String lastName);
}

Dolayısıyla bu bir sınıf değil, bir arayüzdür (7. satır). Spring Data'ya ait [CrudRepository] arayüzünü genişletir (5. satır). Bu arayüz iki türle tanımlanmıştır: ilki, yönetilen öğelerin türü; burada [Customer] türüdür; ikincisi ise yönetilen öğelerin birincil anahtarının türü; burada [Long] türüdür. [CrudRepository] arayüzü şöyledir:


package org.springframework.data.repository;

import java.io.Serializable;

@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {

    <S extends T> S save(S entity);

    <S extends T> Iterable<S> save(Iterable<S> entities);

    T findOne(ID id);

    boolean exists(ID id);

    Iterable<T> findAll();

    Iterable<T> findAll(Iterable<ID> ids);

    long count();

    void delete(ID id);

    void delete(T entity);

    void delete(Iterable<? extends T> entities);

    void deleteAll();
}

Bu arayüz, JPA T türü üzerinde gerçekleştirilebilecek CRUD işlemlerini (Oluştur – Oku – Güncelle – Sil) tanımlar:

  • 8. satır: save yöntemi, bir T varlığının veritabanında kalıcı hale getirilmesini sağlar. Bu yöntem, varlığı SGBD tarafından verilen birincil anahtarla kalıcı hale getirir. Ayrıca, id birincil anahtarıyla tanımlanan bir T varlığının güncellenmesini de sağlar. Hangi işlemin gerçekleştirileceği, id birincil anahtarının değerine göre belirlenir: bu değer null ise kalıcılık işlemi gerçekleştirilir, aksi takdirde güncelleme işlemi gerçekleştirilir;
  • 10. satır: Aynı işlem, ancak bu kez bir varlık listesi için;
  • 12. satır: findOne yöntemi, birincil anahtarı id ile tanımlanan bir T varlığını bulmaya olanak tanır;
  • 22. satır: delete yöntemi, birincil anahtarı id ile tanımlanan bir T varlığını silmeye yarar;
  • satır 24-28: [delete] yönteminin varyantları;
  • 16. satır: [findAll] yöntemi, kalıcı T varlıklarının tümünü bulmaya olanak tanır;
  • 18. satır: Aynı işlem, ancak kimlik listesi aktarılan varlıklarla sınırlıdır;

[CustomerRepository] arayüzüne geri dönelim:


package hello;

import java.util.List;

import org.springframework.data.repository.CrudRepository;

public interface CustomerRepository extends CrudRepository<Customer, Long> {

    List<Customer> findByLastName(String lastName);
}
  • 9. satır, [Customer]'i [lastName] adıyla bulmaya olanak tanır;

[DAO] katmanı ile ilgili bilgiler bu kadar. Önceki arayüzün bir uygulama sınıfı yoktur. Bu sınıf, yürütme sırasında [Spring Data] tarafından oluşturulur. [CrudRepository] arayüzünün yöntemleri otomatik olarak uygulanır. [CustomerRepository] arayüzüne eklenen yöntemler için ise durum değişebilir. [Customer] tanımına geri dönelim:


private long id;
private String firstName;
private String lastName;
  1. satırdaki yöntem, [Customer]'in [lastName] (3. satır) alanına referans verdiği için [Spring Data] tarafından otomatik olarak uygulanır. Spring Data, uygulanacak arayüzde bir [findBySomething] yöntemi ile karşılaştığında, bunu aşağıdaki JPQL (Java Persistence Query Language) sorgusu ile uygular:
select t from T t where t.something=:value

Bu nedenle, T türünün [something] adlı bir alanı olması gerekir. Böylece

List<Customer> findByLastName(String lastName);

yöntemi, aşağıdakine benzer bir kodla uygulanacaktır:

return [em].createQuery("select c from Customer c where  c.lastName=:value").setParameter("value",lastName).getResultList()

burada [em], JPA kalıcılık bağlamını ifade eder. Bu, ancak [Customer] sınıfında [lastName] adlı bir alan varsa mümkündür; ki bu durum söz konusudur.

Sonuç olarak, basit durumlarda Spring Data, [DAO] katmanını basit bir arayüzle uygulamamıza olanak tanır.

11.2.4. [console] katmanı

  

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


package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application implements CommandLineRunner {

    @Autowired
    CustomerRepository repository;
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }

    @Override
    public void run(String... strings) throws Exception {
        // birkaç müşteriyi kaydet
        repository.save(new Customer("Jack", "Bauer"));
        repository.save(new Customer("Chloe", "O'Brian"));
        repository.save(new Customer("Kim", "Bauer"));
        repository.save(new Customer("David", "Palmer"));
        repository.save(new Customer("Michelle", "Dessler"));

        // tüm müşterileri getir
        System.out.println("Customers found with findAll():");
        System.out.println("-------------------------------");
        for (Customer customer : repository.findAll()) {
            System.out.println(customer);
        }
        System.out.println();

        // ID ile tek bir müşteriyi getir
        Customer customer = repository.findOne(1L);
        System.out.println("Customer found with findOne(1L):");
        System.out.println("--------------------------------");
        System.out.println(customer);
        System.out.println();

        // müşterileri soyadına göre getir
        System.out.println("Customer found with findByLastName('Bauer'):");
        System.out.println("--------------------------------------------");
        for (Customer bauer : repository.findByLastName("Bauer")) {
            System.out.println(bauer);
        }
    }

}
  • 9. satır: Sınıf, [CommandLineRunner] arayüzünü uygular; bu arayüz, [Spring Boot] (4. satır) arayüzüdür. Bu arayüzün tek bir yöntemi vardır, o da 19. satırdaki yöntemdir;
  • 8. satır: @SpringBootApplication, birkaç [Spring Boot] anotasyonunu bir araya getiren bir anotasyondur:
    • @Configuration: sınıfın bir yapılandırma sınıfı olduğunu belirtir;
    • @EnableAutoConfiguration: [Spring Boot]'ten, çeşitli özelliklere, özellikle de projenin Classpath içeriğine bağlı olarak belirli sayıda bean'i kendisi oluşturmasını ister. Hibernate kütüphaneleri Classpath'te bulunduğu için, [entityManagerFactory] bean'i Hibernate ile uygulanacaktır. SGBD kütüphanesi Classpath'te bulunduğu için, H2 bean'i H2 ile uygulanacaktır. [dataSource] bean'inde kullanıcı adı ve şifre de tanımlanmalıdır. Burada Spring Boot, H2'in varsayılan yöneticisini kullanacaktır; bu, şifresizdir. [spring-tx] kütüphanesi Classpath'te bulunduğu için Spring'in işlem yöneticisi kullanılacaktır;
    • @EnableWebMvc: Classpath'te [spring-mvc] kütüphanesi varsa. Bu durumda, web uygulaması için otomatik bir yapılandırma yapılır;
    • @ComponentScan: Spring'e diğer bean'leri, yapılandırmaları ve hizmetleri nerede arayacağını bildirir. Burada bunlar varsayılan olarak etiketli sınıfın bulunduğu pakette, yani [hello] paketinde aranır. Böylece [Customer] ve [CustomerRepository] sınıfları bulunacaktır. İlki [@Entity] anotasyonuna sahip olduğu için Hibernate tarafından yönetilecek bir varlık olarak kataloglanacaktır. İkincisi ise [CrudRepository] arayüzünü genişlettiği için bir Spring bean'i olarak kaydedilecektir;
  • 11-12. satırlar: [CustomerRepository] bean'i ana sınıfın koduna enjekte edilir;
  • 15. satır: Spring Boot projesindeki [SpringApplication] sınıfının [run] statik yöntemi yürütülür. Bu yöntemin parametresi, [Configuration] veya [EnableAutoConfiguration] anotasyonuna sahip sınıftır. Daha önce açıklanan tüm süreç bu aşamada gerçekleşir. Sonuç, bir Spring uygulama bağlamıdır, yani Spring tarafından yönetilen bir dizi bean;

Aşağıdaki işlemler, yalnızca [CustomerRepository] arayüzünü uygulayan bean'in yöntemlerini kullanır. Konsol çıktıları şöyledir:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.2.RELEASE)

2015-03-10 15:35:43.661  INFO 5784 --- [           main] hello.Application                        : Starting Application on Gportpers3 with PID 5784 (started by ST in C:\Users\Serge Tahé\Documents\workspace-sts-3.6.3.RELEASE\gs-accessing-data-jpa-complete)
2015-03-10 15:35:43.708  INFO 5784 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5d11346a: startup date [Tue Mar 10 15:35:43 CET 2015]; root of context hierarchy
2015-03-10 15:35:45.230  INFO 5784 --- [           main] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
2015-03-10 15:35:45.254  INFO 5784 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
    name: default
    ...]
2015-03-10 15:35:45.331  INFO 5784 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {4.3.8.Final}
2015-03-10 15:35:45.332  INFO 5784 --- [           main] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2015-03-10 15:35:45.334  INFO 5784 --- [           main] org.hibernate.cfg.Environment            : HHH000021: Bytecode provider name : javassist
2015-03-10 15:35:45.651  INFO 5784 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {4.0.5.Final}
2015-03-10 15:35:45.754  INFO 5784 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2015-03-10 15:35:45.877  INFO 5784 --- [           main] o.h.h.i.ast.ASTQueryTranslatorFactory    : HHH000397: Using ASTQueryTranslatorFactory
2015-03-10 15:35:46.154  INFO 5784 --- [           main] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000227: Running hbm2ddl schema export
2015-03-10 15:35:46.169  INFO 5784 --- [           main] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000230: Schema export complete
2015-03-10 15:35:46.779  INFO 5784 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
Customers found with findAll():
-------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=2, firstName='Chloe', lastName='O'Brian']
Customer[id=3, firstName='Kim', lastName='Bauer']
Customer[id=4, firstName='David', lastName='Palmer']
Customer[id=5, firstName='Michelle', lastName='Dessler']

Customer found with findOne(1L):
--------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']

Customer found with findByLastName('Bauer'):
--------------------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=3, firstName='Kim', lastName='Bauer']
2015-03-10 15:35:47.040  INFO 5784 --- [           main] hello.Application                        : Started Application in 3.623 seconds (JVM running for 4.324)
2015-03-10 15:35:47.042  INFO 5784 --- [       Thread-1] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@5d11346a: startup date [Tue Mar 10 15:35:43 CET 2015]; root of context hierarchy
2015-03-10 15:35:47.044  INFO 5784 --- [       Thread-1] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
2015-03-10 15:35:47.046  INFO 5784 --- [       Thread-1] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2015-03-10 15:35:47.047  INFO 5784 --- [       Thread-1] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000227: Running hbm2ddl schema export
2015-03-10 15:35:47.051  INFO 5784 --- [       Thread-1] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000230: Schema export complete
  • 1-8. satırlar: Spring Boot projesinin logosu;
  • 9. satır: [hello.Application] sınıfı çalıştırılır;
  • 10. satır: [AnnotationConfigApplicationContext], Spring'in [ApplicationContext] arayüzünü uygulayan bir sınıftır. Bu, bir bean konteyneridir;
  • satır 11: [entityManagerFactory] bean'i, bir Spring sınıfı olan [LocalContainerEntityManagerFactory] sınıfı ile uygulanır;
  • 12. satır: [Hibernate] ortaya çıkıyor. Seçilen, JPA adlı bu uygulamadır;
  • 19. satır: SGBD ile birlikte kullanılacak Hibernate lehçesi, SQL varyantıdır. Burada [H2Dialect] lehçesi, Hibernate'in SGBD ve H2 ile çalışacağını göstermektedir;
  • 21-22. satırlar: veritabanı oluşturulur. [CUSTOMER] tablosu oluşturulur. Bu, Hibernate'in JPA tanımlarından tabloları oluşturacak şekilde yapılandırıldığı anlamına gelir; burada [Customer] sınıfının JPA tanımı kullanılır;
  • 26-30. satırlar: arayüzün [findAll] yönteminin sonucu;
  • 34. satır: arayüzün [findOne] yönteminin sonucu;
  • 38-39. satırlar: [findByLastName] yönteminin sonuçları;
  • 41. satır ve sonrası: Spring bağlamının kapatılmasına ait günlükler.

11.2.5. Spring Data projesinin manuel yapılandırması

Önceki projeyi [gs-accessing-data-jpa-02] projesinde kopyalıyoruz:

  

Bu yeni projede, Spring Boot tarafından yapılan otomatik yapılandırmaya güvenmeyeceğiz. Yapılandırmayı manuel olarak gerçekleştireceğiz. Varsayılan yapılandırmalar bize uymuyorsa bu yararlı olabilir.

Öncelikle, [pom.xml] dosyasında gerekli bağımlılıkları açıkça belirteceğiz:


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

    <groupId>org.springframework</groupId>
    <artifactId>gs-accessing-data-jpa-02</artifactId>
    <version>0.1.0</version>

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

    <dependencies>
        <!-- Spring Data -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
        </dependency>
        <!-- Hibernate -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
        </dependency>
        <!-- H2 Veritabanı -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
        <!-- Tomcat JDBC -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <!-- her şey için UTF-8 kullanın -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-releases</id>
            <name>Spring Releases</name>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
        <repository>
            <id>org.jboss.repository.releases</id>
            <name>JBoss Maven Release Repository</name>
            <url>https://repository.jboss.org/nexus/content/repositories/releases</url>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-releases</id>
            <name>Spring Releases</name>
            <url>https://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>

</project>
  • 10-14. satırlar: Tanımladığı kütüphaneleri kullanacağımız üst Maven projesi;
  • 18-21. satırlar: Veritabanına erişmek için kullanılan Spring Data;
  • 23-26. satırlar: JPA spesifikasyonunun Hibernate uygulaması;
  • 28-31. satırlar: SGBD ve H2;
  • 33-36. satırlar: Veritabanları genellikle, bağlantıların tekrar tekrar açılıp kapatılmasını önleyen açık bağlantı havuzlarıyla kullanılır. Burada kullanılan uygulama [tomcat-jdbc]’tir;

Yeni projede, [Customer] varlığı ve [CustomerRepository] arayüzü değişmez. [Application] sınıfını değiştireceğiz; bu sınıf iki sınıfa bölünecektir:

  • [Config], yapılandırma sınıfı olacak;
  • [Main], bu da çalıştırılabilir sınıf olacaktır;
  

Çalıştırılabilir sınıf [Application] artık şu şekildedir:


package console;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import repositories.CustomerRepository;
import config.AppConfig;
import entities.Customer;

public class Application {
    public static void main(String[] args) {
        // Spring bağlamı oluşturma
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        CustomerRepository repository = context.getBean(CustomerRepository.class);

        // birkaç müşteriyi kaydetme
        repository.save(new Customer("Jack", "Bauer"));
        repository.save(new Customer("Chloe", "O'Brian"));
        repository.save(new Customer("Kim", "Bauer"));
        repository.save(new Customer("David", "Palmer"));
        repository.save(new Customer("Michelle", "Dessler"));

        ...

        // bağlamın kapatılması
        context.close();
    }
}
  • 9. satır: [Application] sınıfında artık yapılandırma açıklamaları bulunmamaktadır;
  • 3-7. satırlar: [Spring Boot] paketlerinin artık içe aktarılmadığına dikkat edin;
  • 12. satır: Spring bean'leri oluşturulur. Bu şekilde oluşturulan bean'lerin referansını içeren Spring bağlamı elde edilir;
  • 13. satır: [CustomerRepository] türündeki bean'e bir referans talep edilir;

Projeyi yapılandıran [Config] sınıfı aşağıdaki gibidir:


package config;

import javax.persistence.EntityManagerFactory;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
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;
import org.springframework.transaction.annotation.EnableTransactionManagement;

//@EnableTransactionManagement
@EnableJpaRepositories(basePackages = { "repositories" })
@Configuration
// @ComponentScan(basePackages={"package1","package2"})
public class AppConfig {

    // veritabanı H2
    @Bean
    public DataSource dataSource() {
        // veri kaynağı TomcatJdbc
        DataSource dataSource = new DataSource();
        // erişim yapılandırması JDBC
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:./demo");
        dataSource.setUsername("sa");
        dataSource.setPassword("");
        // başlangıçta açık bir bağlantı
        dataSource.setInitialSize(1);
        // sonuç
        return dataSource;
    }

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

    // EntityManagerFactory
    @Bean
    public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(jpaVendorAdapter);
        factory.setPackagesToScan("entities");
        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;
    }

}
  • 17. satır: [@EnableTransactionManagement] anotasyonu, [CrudRepository] arayüzlerinin yöntemlerinin bir işlem içinde gerçekleştirilmesi gerektiğini belirtir. Bu, varsayılan olarak geçerli olduğu için yorum satırına alınmıştır;
  • 18. satır: [@EnableJpaRepositories] anotasyonu, Spring Data [CrudRepository] arayüzlerinin bulunduğu klasörleri belirtmeye yarar. Bu arayüzler Spring bileşenlerine dönüşecek ve Spring bağlamında kullanılabilir hale gelecektir;
  • 19. satır: [@Configuration] anotasyonu, [Config] sınıfını bir Spring yapılandırma sınıfı haline getirir;
  • 20. satır: [@ComponentScan] anotasyonu, Spring bileşenlerinin aranacağı klasörlerin listelenmesini sağlar. Spring bileşenleri, @Service, @Component, @Controller gibi Spring anotasyonlarıyla etiketlenmiş sınıflardır. Burada, [AppConfig] sınıfı içinde tanımlananlar dışında başka bileşen bulunmadığından, anotasyon yorum satırına alınmıştır;
  • 24-37. satırlar: veri kaynağını, yani H2 veritabanını tanımlar. 25. satırdaki @Bean anotasyonu, bu yöntemle oluşturulan nesneyi Spring tarafından yönetilen bir bileşen haline getirir. Yöntemin adı burada herhangi bir şey olabilir. Ancak, 51. satırdaki EntityManagerFactory yoksa ve otomatik yapılandırma ile tanımlanmışsa, bu yöntemin adı [dataSource] olmalıdır;
  • 30. satır: Veritabanının adı [demo] olacak ve proje klasöründe oluşturulacaktır;
  • satır 40-47: Kullanılan JPA uygulamasını tanımlar; burada bir Hibernate uygulamasıdır. Yöntemin adı burada herhangi bir şey olabilir;
  • satır 43: SQL için günlük kaydı yok;
  • satır 44: veritabanı mevcut değilse oluşturulacaktır;
  • satır 50-58: JPA'in kalıcılığını yönetecek olan EntityManagerFactory'i tanımlar. Yöntemin adı mutlaka [entityManagerFactory] olmalıdır;
  • satır 51: Yöntem, daha önce tanımlanan iki bean'in türünde iki parametre alır. Bu bean'ler daha sonra oluşturulur ve Spring tarafından yöntemin parametreleri olarak enjekte edilir;
  • satır 53: Kullanılacak JPA uygulaması belirlenir;
  • satır 54: JPA varlıklarının bulunacağı klasörleri belirler;
  • satır 55: yönetilecek veri kaynağını belirler;
  • satır 61-66: işlem yöneticisi. Yöntemin adı mutlaka [transactionManager] olmalıdır. Bu yöntem, satır 51-58'deki bean'i parametre olarak alır;
  • 64. satır: işlem yöneticisi, EntityManagerFactory ile ilişkilendirilir;

Yukarıdaki yöntemler herhangi bir sırayla tanımlanabilir.

Projenin çalıştırılması aynı sonuçları verir. Proje klasöründe, H2 adlı yeni bir veritabanı dosyası belirir:

  

11.2.6. Çalıştırılabilir arşiv oluşturma

Projenin çalıştırılabilir arşivini oluşturmak için şu adımları uygulayabilirsiniz:

  • [1]'te: bir yürütme yapılandırması oluşturulur;
  • [2]'te: [Java Application] türünde
  • [3]: çalıştırılacak projeyi belirtir (Browse düğmesini kullanın);
  • [4]'te: yürütülecek sınıfı belirtir;
  • [5]: çalıştırma yapılandırmasının adı – herhangi bir isim olabilir;
  • [6]'te: proje dışa aktarılır;
  • [7]'te: çalıştırılabilir bir arşiv JAR biçiminde;
  • [8]: Oluşturulacak yürütülebilir dosyanın yolunu ve adını belirtir;
  • [9]'te: [5]'te oluşturulan çalıştırma yapılandırmasının adı;
  • [10]'te, oluşturulan arşiv;

Bu işlem tamamlandıktan sonra, yürütülebilir arşivin bulunduğu klasörde bir konsol açılır:

.....\dist>dir
12/06/2014  09:11        15 104 869 gs-accessing-data-jpa-02.jar

Arşiv şu şekilde çalıştırılır:


.....\dist>java -jar gs-accessing-data-jpa-02.jar

Konsolda elde edilen sonuçlar şunlardır:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder daha fazla ayrıntı için.
mars 10, 2015 5:27:20 PM org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation
INFO: HHH000204: Processing PersistenceUnitInfo [
    name: default
    ...]
mars 10, 2015 5:27:20 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.3.8.Final}
mars 10, 2015 5:27:20 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
mars 10, 2015 5:27:20 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
mars 10, 2015 5:27:22 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.5.Final}
mars 10, 2015 5:27:22 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
mars 10, 2015 5:27:22 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000228: Running hbm2ddl schema update
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000102: Fetching database metadata
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000396: Updating schema
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Customer
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Customer
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Customer
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000232: Schema update complete
Customers found with findAll():
-------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=2, firstName='Chloe', lastName='O'Brian']
Customer[id=3, firstName='Kim', lastName='Bauer']
Customer[id=4, firstName='David', lastName='Palmer']
Customer[id=5, firstName='Michelle', lastName='Dessler']

Customer found with findOne(1L):
--------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']

Customer found with findByLastName('Bauer'):
--------------------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=3, firstName='Kim', lastName='Bauer']

11.3. Örnek 2

11.3.1. Giriş

API JDBC'i tanıtmak için kullandığımız ürün tablosu örneğini tekrar ele alacağız ve aşağıdaki mimariyi oluşturacağız:

[dbintrospringjpa] veritabanında [PRODUITS] ve [CATEGORIES] adlı iki tablo bulunmaktadır. [CATEGORIES] tablosu şu şekildedir:

 
  • [ID]: AUTO_INCREMENT modunda birincil anahtar;
  • [VERSION]: kaydın sürüm numarası;
  • [NOM]: kategori adı - benzersiz;

[PRODUITS] tablosu aşağıdaki gibidir:

 
  • [ID]: AUTO_INCREMENT modunda birincil anahtar;
  • [VERSION]: kaydın sürüm numarası;
  • [NOM]: ürün adı - benzersiz;
  • [ID_CATEGORIE]: kategorisi numarası - [CATEGORIES.ID] alanındaki yabancı anahtar;
  • [PRIX]: fiyatı;
  • [DESCRIPTION]: ürün açıklaması;

Yapılması gereken iş: Destekteki SQL ve [dbintrospringdata.sql] komut dosyalarını kullanarak [dbintrospringdata] veritabanını oluşturun:


11.3.2. Maven projesinin oluşturulması

Bir Spring Data proje iskeleti oluşturmak için aşağıdaki adımları uygulayabilirsiniz:

  • [1]'te yeni bir proje oluşturulur;
  • [2]'te: [Spring Starter Project] türünde;
  • oluşturulan proje bir Maven projesi olacaktır. [3]'te, proje grubunun adını belirtin;
  • [4]'te: projenin derlenmesi sırasında oluşturulacak artefaktın (burada bir jar dosyası) adını belirtin;
  • [5]'te: projenin Eclipse adı – herhangi bir ad olabilir ([4] ile aynı olması gerekmez);
  • [7]'te: [JPA] katmanına sahip ve SGBD ile MySQL'i içeren bir proje oluşturulacağı belirtilir. Böyle bir proje için gerekli bağımlılıklar daha sonra [pom.xml] dosyasına eklenecektir;
  • [8] dosyasına, proje klasörünün adını girin;
  • [9] dosyasına, sihirbazı tamamlayın;
  • [10]: oluşturulan proje;

[pom.xml] dosyası, bir proje için gerekli bağımlılıkları içerir JPA:


<?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>istia.st.springdata</groupId>
    <artifactId>intro-spring-data-01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>intro-spring-data-01</name>
    <description>démo spring data avec table de produits</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.2.RELEASE</version>
        <relativePath/> <!-- depo'dan üst öğeyi ara -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>demo.IntroSpringData01Application</start-class>
        <java.version>1.7</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • 14-19. satırlar: üst Maven projesi – çok sayıda kütüphaneyi sürümleriyle birlikte tanımlar – bu kütüphaneler, sürümleri belirtilmeden Maven bağımlılıkları olarak kullanılır;
  • 28-31. satırlar: JPA için gerekli bağımlılık – [Spring Data]'i içerecektir;
  • satır 32-36: MySQL'in JDBC sürücüsüne olan bağımlılığı;
  • satır 37-41: Spring ile entegre JUnit testleri için gerekli bağımlılıklar;

Çalıştırılabilir sınıf [Application] hiçbir işlem yapmaz ancak önceden yapılandırılmıştır:


package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class IntroSpringData01Application {

    public static void main(String[] args) {
        SpringApplication.run(IntroSpringData01Application.class, args);
    }
}
  • [@SpringBootApplication] anotasyonu, sınıfı projenin otomatik yapılandırma sınıfı haline getirir;

[ApplicationTests] test sınıfı hiçbir işlem yapmaz, ancak önceden yapılandırılmıştır:


package demo;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = IntroSpringData01Application.class)
public class IntroSpringData01ApplicationTests {

    @Test
    public void contextLoads() {
    }

}
  • 9. satır: [@SpringApplicationConfiguration] anotasyonu, [Application] yapılandırma dosyasının kullanılmasını sağlar. Böylece test sınıfı, bu dosyada tanımlanacak tüm bean'lerden yararlanacaktır;
  • 8. satır: [@RunWith] anotasyonu, Spring'in JUnit ile entegrasyonunu sağlar: sınıf, bir JUnit testi olarak çalıştırılabilecektir. [@RunWith], JUnit (4. satır) anotasyonudur; [SpringJUnit4ClassRunner] sınıfı ise bir Spring sınıfıdır (6. satır);

Artık JPA adlı bir uygulama iskeletine sahip olduğumuza göre, bunu tamamlayarak ürün veritabanıyla ilişkili kalıcılık katmanı projesini yazabiliriz.

11.3.3. Eclipse projesi

Önceki projeyi şu şekilde geliştiriyoruz:

  
  • [AppConfig.java]: Spring projesinin yapılandırma sınıfı;
  • [Main.java]: projenin çalıştırılabilir sınıfı;
  • [IDao.java]: [DAO] katmanının arayüzü;
  • [Dao.java]: [DAO] katmanının uygulama sınıfı;
  • [AbstractEntity.java]: [Produit] ve [Categorie] sınıflarının üst sınıfı;
  • [Produit.java]: veritabanındaki [PRODUITS] tablosunun bir satırıyla ilişkili sınıf;
  • [Categorie.java]: veritabanındaki [CATEGORIES] tablosunun bir satırıyla ilişkili sınıf;
  • [ProduitsRepository]: [PRODUITS] tablosuna erişim sağlayan Spring Data arayüzü;
  • [CategoriesRepository]: [CATEGORIES] tablosuna erişim sağlayan Spring Data arayüzü;
  • [pom.xml]: Maven projesinin yapılandırma dosyası;

Bu proje aşağıdaki mimariyi uygulamaktadır:

[DAO] katmanı, yalnızca [Spring Data] tarafından uygulanan katmanı görür.

11.3.4. Maven Yapılandırması

Maven projesinin [pom.xml] dosyası şöyledir:


<?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>istia.st.springdata</groupId>
    <artifactId>intro-spring-data-01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>intro-spring-data-01</name>
    <description>démo spring data avec table de produits</description>

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

    <dependencies>
        <!-- Spring Data -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
        </dependency>
        <!-- Hibernate -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
        </dependency>
        <!-- MySQL Veritabanı -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- Tomcat JDBC -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
        </dependency>
        <!-- kütüphane jSON -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <!-- Google Guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>16.0.1</version>
        </dependency>
        <!-- Spring Boot Testi -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- günlük kütüphanesi -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</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>

Bu yapılandırma, 11.2.5. paragrafında kullanılan ve açıklanan yapılandırmadır. Buna aşağıdaki kütüphaneleri ekliyoruz:

  • 42-49. satırlar: [Produit] sınıfının [toString] yöntemi tarafından kullanılan jSON kütüphanesi;
  • 51-55. satırlar: öğe koleksiyonlarını yönetmek için yardımcı yöntemler sağlayan [Google Guava] kütüphanesi. Bu kütüphane, [DAO] katmanını uygulayan [Dao] sınıfı tarafından kullanılacaktır;
  • satır 56-67: testler için gerekli kütüphaneler JUnit;
  • 69-72. satırlar: bir log kütüphanesi;
  • 81-86. satırlar: proje için gerekli Maven eklentileri;

11.3.5. [JPA] katmanındaki varlıklar

Katman

[DAO]

Katman

[console]

Katman

[JPA]

Sürücü

[JDBC]

Katman

[Spring Data]

Spring 4

SGBD

  

11.3.5.1. [AbstractEntity] sınıfı

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


package spring.data.entities;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

@MappedSuperclass
public abstract class AbstractEntity {
    // özellikler
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    protected Long id;
    @Version
    @Column(name = "VERSION")
    protected Long version;

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

    }

    public AbstractEntity(Long id, Long version) {
        this.id = id;
        this.version = version;
    }

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

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

    // imza jSON
    public String toString() {
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper.writeValueAsString(this);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }
    
    // alıcı ve ayarlayıcılar
....
}

Bu sınıfın amacı, JPA varlıklarına bir üst sınıf sağlamak ve [id, version] (satır 19, 22) özelliklerini tek bir yerde kapsülleyerek [Produit] ve [Categorie] varlıklarına bir ana sınıf sağlamaktır. Bu özellikler, tabloların [ID, VERSION] sütunlarıyla ilişkilidir (satır 18, 21).

  • 13. satır: [@MappedSuperclass] açıklaması, sınıfın JPA varlıklarının üst sınıfı olduğunu belirtir;
  • 16. satır: [@Id] açıklaması, [id] alanının (başka bir adı da olabilir) bir tablonun birincil anahtarıyla ilişkili olduğunu belirtir;
  • 17. satır: [@GeneratedValue(strategy=GenerationType.IDENTITY)] açıklaması, birincil anahtarların oluşturulma şeklini belirler. [GenerationType.IDENTITY] modu, MySQL ile birlikte [AUTO_INCREMENT] modunu kullanacaktır. Başka bir SGBD ile bu mod, farklı bir yöntem kullanır. Bunun avantajı, geliştiricinin bu konuyla ilgilenmesine gerek kalmaması ve kullanılan SGBD ne olursa olsun kodunun geçerliliğini korumasıdır;
  • 18. satır: [@Column] açıklaması, alanla ilişkili sütunu belirtir. Bu açıklama mevcut olmadığında, JPA, sütunun alanla aynı ada sahip olduğunu varsayar. Burada durum böyledir. Dolayısıyla bu açıklamayı eklememek de mümkündü;
  • satır 20: [@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;
  • 35-52. satırlar: [hashCode] ve [equals] yöntemlerinin yeniden tanımlanması. Varsayılan olarak, [obj1.equals(obj2)], [obj1==obj2] doğruysa, yani ob1 ve obj2 iki eşit işaretçi ise true değerini alır. İşaretçilerin kendileri yerine işaret ettikleri nesneleri karşılaştırmak istenirse, [equals] ve [hashCode] yöntemlerini yeniden tanımlamak gerekir. Bu yöntem, [equals] yönteminin eşit olduğunu belirten iki nesne için aynı değeri döndürmelidir;
  • 42-51. satırlar: [AbstractEntity] türünde veya türevleri olan iki nesne, [id] birincil anahtarları eşitse eşit kabul edilir;
  • satır 35-38: [hashCode] yöntemi, aynı olan ve dolayısıyla aynı [id] birincil anahtarına sahip iki [AbstractEntity] nesnesi için gerçekten de aynı değeri döndürür;
  • satır 55-63: [toString] yöntemi, [this] nesnesinin jSon dizesini döndürür. Bu nesne bir alt sınıfı işaret ediyorsa, bu yöntem alt sınıfın jSON dizesini döndürür. Bu sayede alt sınıflarda [toString] yöntemini oluşturmamıza gerek kalmaz;

11.3.5.2. JPA [Produit] varlığı

[Produit] sınıfı, [PRODUITS] tablosundaki bir satıra bağlı bir JPA varlığıdır:

 

package spring.data.entities;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import com.fasterxml.jackson.annotation.JsonFilter;

@Entity
@Table(name = "PRODUITS")
@JsonFilter("jsonFilterProduit")
public class Produit extends AbstractEntity {

    // özellikler
    @Column(name = "NOM")
    private String nom;

    @Column(name = "CATEGORIE_ID", insertable = false, updatable = false)    
    private Long idCategorie;

    @Column(name = "PRIX")
    private double prix;

    @Column(name = "DESCRIPTION")
    private String description;

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

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

    }

    public Produit(String nom, double prix, String description) {
        this.nom = nom;
        this.prix = prix;
        this.description = description;
    }

    // getter ve setter'lar
...
}
  • 12. satır: [@Entity] açıklaması, [Produit] sınıfını [JPA] katmanı tarafından yönetilen bir varlık haline getirir;
  • 13. 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;
  • satır 14: varlık üzerine uygulanacak jSON adlı filtre. 13. 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 belirleyeceğiz;
  • 18. satır: [@Column] açıklaması, [nom] alanını [PRODUITS] tablosunun [NOM] sütunuyla ilişkilendirir. Alan, ilişkili sütunla aynı adı taşıyorsa, [@Column] açıklaması atlanabilir. Bu durumda da öyle olur;
  • 31-33. satırlar: ürün kategorisi;
  • 31. satır: [@ManyToOne] açıklaması, 32. satırdaki [@JoinColumn(name = "CATEGORIE_ID")] açıklamasının sütununun, 33. satırdaki varlığa ait [CATEGORIES] tablosundaki[Produit] varlığının, 33. 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 33. satırdaki sınıf, bir JPA varlığı olmalıdır;
  • 31. satır: [fetch = FetchType.LAZY] açıklaması, [PRODUITS] tablosundan bir ürün getirildiğinde, ürünün kategorisinin (33. satır) hemen getirilmemesini (gecikmeli yükleme) talep eder. Kategori, [getCategorie] yöntemine ilk çağrı yapıldığında alınır. Bu öznitelik zorunlu değildir. Kullanılan JPA uygulaması bu özniteliği göz ardı etme hakkına sahiptir. [categorie] özelliğinin mevcut olabileceği veya olmayabileceği için 14. satırdaki jSON filtresini ekledik. Mevcut JPA uygulamaları (Hibernate, Eclipselink, OpenJPA) bu anotasyonu aynı şekilde yönetmez. Hibernate, kategoriyi almak için SGBD'e bir çağrı yaparak, başlangıçtaki [getCategorie] yöntemini (sadece categorie alanını döndüren) zenginleştirir. Bunun mümkün olabilmesi için, ürünü almak üzere başlangıçta kullanılan SGBD bağlantısının hâlâ açık olması gerekir; aksi takdirde bir istisna oluşur.

11.3.5.3. JPA ve [Categorie] varlıkları

[Categorie] sınıfı, [CATEGORIES] tablosundaki bir satıra bağlı bir JPA varlığıdır:

 

Kodu şöyledir:


package spring.data.entities;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import com.fasterxml.jackson.annotation.JsonFilter;

@Entity
@Table(name = "CATEGORIES")
@JsonFilter("jsonFilterCategorie")
public class Categorie extends AbstractEntity {

    // özellikler
    @Column(name = "NOM")
    private String nom;

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

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

    }

    public Categorie(String nom) {
        this.nom = nom;
    }

    // yöntemler
    public void addProduit(Produit produit) {
        // ürünü ekliyoruz
        produits.add(produit);
        // kategorisi belirlenir
        produit.setCategorie(this);
    }

    // getter ve setter'lar
...
}
  • 21-22. satırlar: kategorinin adı;
  • 25-26. satırlar: bu kategorideki ürünler;
  • 25. satır: [@OneToMany] notu, [Produit] varlığında karşılaştığımız [@ManyToOne] ilişkisinin ters ilişkisidir. [mappedBy = "categorie"] özniteliği, [@ManyToOne] ters ilişkisiyle etiketlenen [Produit] varlığının alanını belirtir. [cascade = { CascadeType.ALL }] özniteliği, bir @Entity [Categorie] üzerinde yapılan işlemlerin (persist, merge, remove) 26. satırdaki [produits]'lere kaskad olarak uygulanmasını gerektirir. [CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE] sabitleri kullanılarak kısmi zincirleme işlemler belirtilebilir;
  • 25. satır: [fetch = FetchType.LAZY] özniteliği, [CATEGORIES] tablosundan bir kategori getirildiğinde, bu kategorinin ürünlerinin hemen getirilmemesini talep eder. Ürünler, [getProduits] yöntemine ilk çağrıldığında getirilecektir. Mevcut JPA uygulamaları (Hibernate, Eclipselink, OpenJPA) bu anotasyonu aynı şekilde yönetmemektedir. Hibernate, başlangıçtaki [getProduits] yöntemini (sadece produits alanını döndüren) zenginleştirerek, kategorideki ürünleri almak için SGBD yöntemini çağırır. Bunun mümkün olabilmesi için, kategoriyi almak üzere başlangıçta kullanılan SGBD bağlantısının hâlâ açık olması gerekir. Bu özellik zorunludur. JPA uygulaması bunu göz ardı edemez. [produits] özelliği başlatılmış olabilir veya olmayabilir; bu nedenle, 17. satırdaki jSON filtresini ekledik; bu filtre, bu özelliği isteyip istemediğimizi belirtmemizi sağlayacaktır;
  • 26. satır: [Set] türü bir arayüzdür. [HashSet] türü ise bu arayüzü uygulayan bir sınıftır. Bu sınıf, ensemble adlı bir eleman koleksiyonunu uygular. Bir küme, iki özdeş nesne içerebilir. Burada nesneler [Produit] türündedir. Dolayısıyla, küme içinde iki özdeş nesne bulunamaz. Üst sınıf olan [AbstractEntity]'in [equals] yöntemi, aynı birincil anahtara sahip iki ürünün aynı olduğu şeklinde yeniden tanımlandığından, [produits] alanı aynı birincil anahtara sahip iki ürünü içerebilir;
  • satır 38-43: [addProduit] yöntemi, bir ürünü kategoriye eklemeye olanak tanır;

11.3.6. [Spring Data] katmanı

Katman

[DAO]

Katman

[console]

Katman

[JPA]

Sürücü

[JDBC]

Katman

[Spring Data]

Yay 4

SGBD

  

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


package spring.data.repositories;

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

import spring.data.entities.Categorie;

public interface CategoriesRepository extends CrudRepository<Categorie, Long> {

    // ürünleriyle birlikte kategori
    @Query("select c from Categorie c left join fetch c.produits p where c.id=?1")
    public Categorie getCategorieByIdWithProduits(Long id);

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

    // adıyla belirtilen, ürünleri olmayan bir kategori
    public Categorie findByNom(String nom);
}
  • 8. satır: [CrudRepository] arayüzü, 11.2.3. paragrafında kullanılmış ve açıklanmıştır. Şunu hatırlatmak isteriz ki:
    • arayüzün ilk türü, CRUD (findOne, findAll, kaydet, sil, deleteAll) erişimleri için yönetilen JPA varlığıdır;
    • ikinci tür ise JPA varlığının birincil anahtarıdır; burada bir tamsayı olan [Long];
  • 12. satır: 12. satırdaki yöntem, 11. satırdaki JPQL (Java Persistence Query Language) sorgusu 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;
  • 11. satır: JPQL sorgusu, bir kategoriyi ürünleriyle birlikte döndürür. [Categorie] varlığında, [produits] alanının [fetch = FetchType.LAZY] (gecikmeli yükleme) özelliğine sahip olduğunu hatırlayalım. JPQL sorgusunda, [fetch] anahtar sözcüğüyle ü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;
  • 14-15. satırlar: adıyla tanımlanan bir kategori için benzer bir yöntem;
  • 18. satır: [findByNom] yöntemi, [Category] türünün bir [nom] alanı olduğu için [Spring Data] tarafından otomatik olarak uygulanacaktır;

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


package spring.data.repositories;

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

import spring.data.entities.Produit;

public interface ProduitsRepository extends CrudRepository<Produit, Long> {

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

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

    // adıyla belirtilen, kategorisi olmayan bir ürün
    public Produit findByNom(String nom);
}

Açıklamalar, [CategoriesRepository] arayüzü için verilenlerle aynıdır.

Bu arayüzler, projenin yürütülmesi sırasında [Spring Data] tarafından oluşturulan sınıflar tarafından uygulanacaktır. Bu tür sınıflara [proxy] denir. Varsayılan olarak, uygulama sınıfının yöntemleri bir işlem içinde yürütülür. Bu arayüzlerin [CrudRepository] sınıfını genişletmesi, onları Spring bileşenleri haline getirir.

11.3.7. [DAO] katmanı

Katman

[DAO]

Katman

[console]

Katman

[JPA]

Sürücü

[JDBC]

Yatak

[Spring Data]

Yay 4

SGBD

  

[DAO] katmanının [IDao] arayüzü şöyledir:


package spring.data.dao;

import java.util.List;

import spring.data.entities.Categorie;
import spring.data.entities.Produit;

public interface IDao {

    // ürün listesinin eklenmesi
    public List<Produit> addProduits(List<Produit> produits);

    // tüm ürünlerin silinmesi
    public void deleteAllProduits();

    // bir ürün listesinin güncellenmesi
    public List<Produit> updateProduits(List<Produit> produits);

    // tüm ürünlerin alınması
    public List<Produit> getAllProduits();

    // kategori listesi ekleme
    public List<Categorie> addCategories(List<Categorie> categories);

    // tüm kategorilerin silinmesi
    public void deleteAllCategories();

    // kategori listesinin güncellenmesi
    public List<Categorie> updateCategories(List<Categorie> categories);

    // tüm kategorileri alma
    public List<Categorie> getAllCategories();

    // belirli bir ürünü, kategorisi olsun ya da olmasın
    public Produit getProduitByIdWithoutCategorie(Long idProduit);

    public Produit getProduitByIdWithCategorie(Long idProduit);

    public Produit getProduitByNameWithCategorie(String nom);

    public Produit getProduitByNameWithoutCategorie(String nom);

    // belirli bir kategoriyi, ürünleriyle birlikte veya ürünsüz olarak alma
    public Categorie getCategorieByIdWithoutProduits(Long idCategorie);

    public Categorie getCategorieByIdWithProduits(Long idCategorie);

    public Categorie getCategorieByNameWithProduits(String nom);

    public Categorie getCategorieByNameWithoutProduits(String nom);
}

Burada, giriş parametreleri olarak aldığı nesneleri değiştiren her yöntemin, bunları sonuç olarak geri vermesi gerektiği kuralı benimsenmiştir. Bu kuralın nedeni 4.2. paragrafta açıklanmıştır: bu kural, bir katmanın ve müşterisinin iki ayrı JVM içinde bulunmasına ve dolayısıyla istemci/sunucu olarak çalışmasına olanak tanır.

Bu arayüzün [Dao] uygulaması şu şekildedir:


package spring.data.dao;

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

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

import com.google.common.collect.Lists;

import spring.data.entities.Categorie;
import spring.data.entities.Produit;
import spring.data.repositories.CategoriesRepository;
import spring.data.repositories.ProduitsRepository;

@Component
public class Dao implements IDao {

    @Autowired
    private ProduitsRepository produitsRepository;

    @Autowired
    private CategoriesRepository categoriesRepository;

    @Override
    public List<Produit> addProduits(List<Produit> produits) {
        try {
            return Lists.newArrayList(produitsRepository.save(produits));
        } catch (Exception e) {
            throw new DaoException(101, getMessagesForException(e));
        }
    }

    @Override
    public void deleteAllProduits() {
        try {
            produitsRepository.deleteAll();
        } catch (Exception e) {
            throw new DaoException(102, getMessagesForException(e));
        }
    }

    @Override
    public List<Produit> updateProduits(List<Produit> produits) {
        try {
            return Lists.newArrayList(produitsRepository.save(produits));
        } catch (Exception e) {
            throw new DaoException(103, getMessagesForException(e));
        }
    }

    @Override
    public List<Categorie> addCategories(List<Categorie> categories) {
        try {
            return Lists.newArrayList(categoriesRepository.save(categories));
        } catch (Exception e) {
            throw new DaoException(104, getMessagesForException(e));
        }
    }

    @Override
    public void deleteAllCategories() {
        try {
            categoriesRepository.deleteAll();
        } catch (Exception e) {
            throw new DaoException(105, getMessagesForException(e));
        }
    }

    @Override
    public List<Categorie> updateCategories(List<Categorie> categories) {
        try {
            return Lists.newArrayList(categoriesRepository.save(categories));
        } catch (Exception e) {
            throw new DaoException(106, getMessagesForException(e));
        }
    }

    @Override
    public List<Categorie> getAllCategories() {
        try {
            return Lists.newArrayList(categoriesRepository.findAll());
        } catch (Exception e) {
            throw new DaoException(107, getMessagesForException(e));
        }
    }

    @Override
    public List<Produit> getAllProduits() {
        try {
            return Lists.newArrayList(produitsRepository.findAll());
        } catch (Exception e) {
            throw new DaoException(108, getMessagesForException(e));
        }
    }

    @Override
    public Produit getProduitByIdWithCategorie(Long idProduit) {
        try {
            return produitsRepository.getProduitByIdWithCategorie(idProduit);
        } catch (Exception e) {
            throw new DaoException(109, getMessagesForException(e));
        }
    }

    @Override
    public Categorie getCategorieByIdWithProduits(Long idCategorie) {
        try {
            return categoriesRepository.getCategorieByIdWithProduits(idCategorie);
        } catch (Exception e) {
            throw new DaoException(110, getMessagesForException(e));
        }
    }

    @Override
    public Categorie getCategorieByNameWithProduits(String nom) {
        try {
            return categoriesRepository.getCategorieByNameWithProduits(nom);
        } catch (Exception e) {
            throw new DaoException(111, getMessagesForException(e));
        }
    }

    @Override
    public Produit getProduitByNameWithCategorie(String nom) {
        try {
            return produitsRepository.getProduitByNameWithCategorie(nom);
        } catch (Exception e) {
            throw new DaoException(112, getMessagesForException(e));
        }
    }

    @Override
    public Produit getProduitByIdWithoutCategorie(Long idProduit) {
        try {
            return produitsRepository.findOne(idProduit);
        } catch (Exception e) {
            throw new DaoException(113, getMessagesForException(e));
        }
    }

    @Override
    public Categorie getCategorieByIdWithoutProduits(Long idCategorie) {
        try {
            return categoriesRepository.findOne(idCategorie);
        } catch (Exception e) {
            throw new DaoException(114, getMessagesForException(e));
        }
    }

    @Override
    public Produit getProduitByNameWithoutCategorie(String nom) {
        try {
            return produitsRepository.findByNom(nom);
        } catch (Exception e) {
            throw new DaoException(115, getMessagesForException(e));
        }
    }

    @Override
    public Categorie getCategorieByNameWithoutProduits(String nom) {
        try {
            return categoriesRepository.findByNom(nom);
        } catch (Exception e) {
            throw new DaoException(116, getMessagesForException(e));
        }
    }

}
  • 16. satır: [@Component] anotasyonu, [Dao] sınıfını bir Spring bileşeni haline getirir;
  • 19-23. satırlar: [Spring Data] arayüzünün iki [CrudRepository] arayüzüne referansların enjeksiyonu. Bu enjeksiyon, Spring nesnelerinin oluşturulması sırasında, genellikle Spring projesinin yürütülmesinin başlangıcında gerçekleşecektir;
  • 28. ve 46. satırlarda, [produitsRepository] arayüzünün [save] yönteminin hem ürün ekleme hem de güncelleme işlemleri için kullanıldığı görülmektedir. [Spring Data], ürünün birilik anahtarını kullanarak ekleme mi yoksa güncelleme mi yapması gerektiğini belirler. Birilik anahtarı [null] ise ekleme yapılır, aksi takdirde güncelleme yapılır;
  • 82. satır: Ürün listesini almak için Guava kütüphanesindeki [Lists.newArrayList] yöntemi kullanılır. [produitsRepository.findAll()] yöntemi, [Iterable<Produit>] türünde bir değer döndürür;
  • 28. satır: [produitsRepository.save(produits)] yöntemi, [Iterable<Produit>] türünde bir değer döndürür. Sınıfın diğer [save] işlemleri için de durum aynıdır;

Yukarıdaki [Dao] sınıfında, meydana gelebilecek istisnalar aşağıdaki [DaoException] türünde kapsüllenmiştir:


package spring.data.dao;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

// "Seçimler" uygulaması için istisna sınıfı
// istisna denetlenmez

public class DaoException extends RuntimeException implements Serializable {

    // seri numarası ID
    private static final long serialVersionUID = 1L;

    // yerel alanlar
    private int code;
    private List<String> erreurs;

    // oluşturucular
    public DaoException() {
        super();
    }

    public DaoException(int code, Throwable e) {
        // üst
        super(e);
        // yerel
        this.code = code;
        this.erreurs = getErreursForException(e);
    }

    public DaoException(int code, String message, Throwable e) {
        // üst öğe
        super(message, e);
        // yerel
        this.code = code;
        this.erreurs = getErreursForException(e);
    }

    public DaoException(int code, String message) {
        // üst
        super(message);
        // yerel
        this.code = code;
        List<String> erreurs = new ArrayList<>();
        erreurs.add(message);
        this.erreurs = erreurs;
    }

    public DaoException(int code, List<String> erreurs) {
        // üst
        super();
        // yerel
        this.code = code;
        this.erreurs = erreurs;
    }

    // bir istisnaya ait hata mesajları listesi
    private List<String> getErreursForException(Throwable th) {
        // istisnanın hata mesajları listesi alınır
        Throwable cause = th;
        List<String> erreurs = new ArrayList<>();
        while (cause != null) {
            // mesaj, yalnızca !=null ve boş değilse alınır
            String message = cause.getMessage();
            if (message != null) {
                message = message.trim();
                if (message.length() != 0) {
                    erreurs.add(message);
                }
            }
            // sonraki neden
            cause = cause.getCause();
        }
        return erreurs;
    }

    // getter ve setter'lar
...
}
  • 10. satır: Sınıf, [RuntimeException] sınıfından türetilmiştir ve bu nedenle kontrol edilemeyen bir istisnadır;
  • 16. satır: bir hata kodu;
  • satır 17: [DaoException]'i tetikleyen istisna yığınıyla ilişkili hata mesajlarının listesi;
  • satır 59-76: özel [getMessagesForException] yöntemi, istisna yığınına ait istisnalarla ilişkili hata mesajlarının listesini almayı sağlar. Aslında, Exception sınıfının aşağıdaki oluşturucuları kullanılarak istisnalar yığınlanabilir:
    • Exception(String message, Throwable cause): bir mesaj ve kapsanmak istenen istisna ile bir istisna oluşturur;
    • Exception(Throwable cause): kapsanmasını istediğimiz istisna ile bir istisna oluşturur;

[Throwable] türü, [Exception] sınıfının üst sınıfıdır. Yukarıdaki yapıcılar tekrar tekrar çalıştırılırsa, son istisna birden fazla istisna içerir. Buna istisna yığını denir.

  • e1 istisnasının son nedeni, [e1.getCause()] ifadesiyle elde edilir;
  • e1 istisnasının sondan bir önceki nedeni, [e1.getCause().getCause()] ifadesiyle elde edilir;
  • bu şekilde [getCause()==null] elde edilene kadar devam edilir;

11.3.8. Spring projesinin yapılandırılması

  

[DaoConfig] sınıfı, [DAO] katmanını yapılandırır:


package spring.data.config;

import javax.persistence.EntityManagerFactory;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
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;

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

    // sabitler
    final static String URL = "jdbc:mysql://localhost:3306/dbIntroSpringData";
    final static String USER = "root";
    final static String PASSWD = "";
    final static String DRIVER_CLASSNAME = "com.mysql.jdbc.Driver";
    final static String[] ENTITIES_PACKAGES = { "spring.data.entities" };

    // veri kaynağı [tomcat-jdbc]
    @Bean
    public DataSource dataSource() {
        // veri kaynağı TomcatJdbc
        DataSource dataSource = new DataSource();
        // erişim yapılandırması JDBC
        dataSource.setDriverClassName(DRIVER_CLASSNAME);
        dataSource.setUsername(USER);
        dataSource.setPassword(PASSWD);
        dataSource.setUrl(URL);
        // başlangıçta açık bir bağlantı
        dataSource.setInitialSize(1);
        // sonuç
        return dataSource;
    }

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

    // EntityManagerFactory
    @Bean
    public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(jpaVendorAdapter);
        factory.setPackagesToScan(packagesToScan());
        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;
    }

    @Bean
    public String[] packagesToScan() {
        return ENTITIES_PACKAGES;
    }

}

Benzer bir yapılandırma 11.2.5. paragrafında ele alınmış ve açıklanmıştır. Buraya aşağıdaki Spring anotasyonlarını ekledik:

  • 17. satır: [@EnableJpaRepositories] anotasyonu, [CrudRepository] ve [Spring Data] arayüzlerinin bulunduğu paketleri belirtmek için kullanılır;
  • 18. satır: Sınıf, bir Spring yapılandırma sınıfıdır. Bu bilgi önemlidir. Bu not kaldırılsa bile proje çalışır. Ancak belgenin ilerleyen kısımlarında, bu projeyi temel alan projeler oluşturulduğunda, 18. satırdaki not kaldırılırsa bunlardan bazıları çalışmaz hale gelir;
  • 19. satır: [@ComponentScan] anotasyonu, Spring nesnelerinin bulunduğu paketleri belirtir. Bunlar, [@Component, @Service, @Controller, ...] ile anotlanmış sınıflardır. Burada [Dao] adlı Spring bileşeni bulunacak ve örneklenecektir;
  • 73-76. satırlar: JPA varlıklarını bulmak için taranacak paketler dizisini temsil eden bir bean tanımladık. Bu, [DaoConfig] sınıfını içe aktaran bir projenin bu bean'i yeniden tanımlamasına ve böylece 59. satırda taranacak paketleri değiştirmesine olanak sağlayacaktır. Belgenin ilerleyen kısımlarında bu sorunla karşılaşacağız;

[AppConfig] sınıfı, projenin tamamını yapılandırır:


package spring.data.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;

@Configuration
@Import({DaoConfig.class})
public class AppConfig {
    // filtreler jSON
    @Bean(name = "jsonMapper")
    public ObjectMapper jsonMapper() {
        return new ObjectMapper();
    }

    @Bean(name = "jsonMapperCategorieWithProduits")
    public ObjectMapper jsonMapperCategorieWithProduits() {
        // eşleştirici jSON
        ObjectMapper mapper = new ObjectMapper();
        // filtreler
        mapper.setFilters(
                new SimpleFilterProvider().addFilter("jsonFilterCategorie", SimpleBeanPropertyFilter.serializeAllExcept())
                        .addFilter("jsonFilterProduit", SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
        // sonuç
        return mapper;
    }

    @Bean(name = "jsonMapperProduitWithCategorie")
    public ObjectMapper jsonMapperProduitWithCategorie() {
        // eşleştirici jSON
        ObjectMapper mapper = new ObjectMapper();
        // filtreler
        mapper.setFilters(
                new SimpleFilterProvider().addFilter("jsonFilterProduit", SimpleBeanPropertyFilter.serializeAllExcept())
                        .addFilter("jsonFilterCategorie", SimpleBeanPropertyFilter.serializeAllExcept("produits")));
        // sonuç
        return mapper;
    }

    @Bean(name = "jsonMapperCategorieWithoutProduits")
    public ObjectMapper jsonMapperCategorieWithoutProduits() {
        // eşleştirici jSON
        ObjectMapper mapper = new ObjectMapper();
        // filtreler
        mapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept("produits")));
        // sonuç
        return mapper;
    }

    @Bean(name = "jsonMapperProduitWithoutCategorie")
    public ObjectMapper jsonMapperProduitWithoutCategorie() {
        // eşleştirici jSON
        ObjectMapper mapper = new ObjectMapper();
        // filtreler
        mapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
                SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
        // sonuç
        return mapper;
    }
}
  • 11. satır: Bu sınıf, bir Spring yapılandırma sınıfıdır;
  • 12. satır: az önce incelediğimiz [DaoConfig] sınıfı tarafından tanımlanan bean'leri içe aktarır;
  • [console] katmanı, burada tanımlanan jSON eşleştiricilerini kullanır;
  • satır 14-64: beş adet jSON eşleştiricisini tanımlar;
  • satır 15-18: jSON [jsonMapper] filtresinde filtre bulunmamaktadır;
  • satır 20-30: jSON ve [jsonMapperCategorieWithProduits] filtreleri, bir [Categorie] nesnesini ve ürünlerini serileştirmeye/serileştirmeyi kaldırmaya olanak tanır;
  • satır 32-42: jSON [jsonMapperProduitWithCategorie] filtresi, bir [Produit] nesnesini kategorisi ile birlikte serileştirmeye/deserileştirmeye olanak tanır;
  • satır 43-53: jSON ve [jsonMapperCategorieWithoutProduits] filtreleri, [Categorie] nesnesini ürünleri olmadan serileştirmeye/deserileştirmeye olanak tanır;
  • satır 55-64: jSON [jsonMapperProduitWithoutCategorie] filtresi, bir [Produit] nesnesini kategorisi olmadan serileştirmeye/deserileştirmeye olanak tanır;

Bir T varlığı için jSON filtresi oluşturulurken, sadece T varlığının filtresini değil, aynı zamanda T varlığının içerebileceği Ti varlıklarının filtrelerini de yapılandırmak gerektiği unutulmamalıdır.

11.3.9. [console] katmanı

Katman

[DAO]

Katman

[console]

Katman

[JPA]

Sürücü

[JDBC]

Katman

[Spring Data]

Spring 4

SGBD

  

[Main] sınıfı şöyledir:


package spring.data.console;

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

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;

import spring.data.config.AppConfig;
import spring.data.dao.DaoException;
import spring.data.dao.IDao;
import spring.data.entities.Categorie;
import spring.data.entities.Produit;

public class Main {

    public static void main(String[] args) throws JsonProcessingException {
        AnnotationConfigApplicationContext context = null;
        try {
            // Spring bağlamı örneklendirme
            context = new AnnotationConfigApplicationContext(AppConfig.class);
            ObjectMapper jsonMapperCategorieWithProduits = context.getBean("jsonMapperCategorieWithProduits",
                    ObjectMapper.class);
            ObjectMapper jsonMapperProduitWithCategorie = context.getBean("jsonMapperProduitWithCategorie",
                    ObjectMapper.class);
            ObjectMapper jsonMapperCategorieWithoutProduits = context.getBean("jsonMapperCategorieWithoutProduits",
                    ObjectMapper.class);
            ObjectMapper jsonMapperProduitWithoutCategorie = context.getBean("jsonMapperProduitWithoutCategorie",
                    ObjectMapper.class);
            IDao dao = context.getBean(IDao.class);
            // --------------------------------------------------------------------------------------
            // veritabanı boşaltılıyor
            log("Vidage de la base de données", 1);
            // [CATEGORIES] tablosu boşaltılıyor - zincirleme olarak [PRODUITS] tablosu da boşaltılacak
            dao.deleteAllCategories();
            // --------------------------------------------------------------------------------------
            log("Remplissage de la base", 1);
            // tablolar dolduruluyor
            List<Categorie> categories = new ArrayList<Categorie>();
            for (int i = 0; i < 2; i++) {
                Categorie categorie = new Categorie(String.format("categorie%d", i));
                for (int j = 0; j < 5; j++) {
                    categorie.addProduit(new Produit(String.format("produit%d%d", i, j), 100 * (1 + (double) (i * 10 + j) / 100),
                            String.format("desc%d%d", i, j)));
                }
                categories.add(categorie);
            }
            // kategori ekleniyor - zincirleme olarak ürünler de eklenecek
            dao.addCategories(categories);
            // --------------------------------------------------------------------------------------
            log("Affichage de la base", 1);
            // kategori listesi
            log("Liste des catégories", 2);
            affiche(dao.getAllCategories(), jsonMapperCategorieWithoutProduits);
            // ürün listesi
            log("Liste des produits", 2);
            affiche(dao.getAllProduits(), jsonMapperProduitWithoutCategorie);
            // 1. kategori ve ürünleri
            Categorie categorie = dao.getCategorieByNameWithProduits("categorie1");
            log("Catégorie 1 avec ses produits", 2);
            affiche(categorie, jsonMapperCategorieWithProduits);
            // [produit14] ürünü ve kategorisi
            Produit p = dao.getProduitByNameWithCategorie("produit14");
            log("Produit [produit14] avec sa catégorie", 2);
            affiche(p, jsonMapperProduitWithCategorie);
            // --------------------------------------------------------------------------------------
            log("Mise à jour du prix des produits de [categorie1]", 1);
            log("Produits de la catégorie [categorie1] avant la mise à jour", 2);
            Categorie categorie1 = dao.getCategorieByNameWithProduits("categorie1");
            Set<Produit> produits = categorie1.getProduits();
            affiche(categorie1, jsonMapperCategorieWithProduits);
            for (Produit produit : produits) {
                produit.setPrix(1.1 * produit.getPrix());
            }
            dao.updateProduits(Lists.newArrayList(produits));
            log("Produits de la catégorie [categorie1] après la mise à jour", 2);
            affiche(dao.getCategorieByNameWithProduits("categorie1"), jsonMapperCategorieWithProduits);
            // --------------------------------------------------------------------------------------
            log("Vidage de la base de données", 1);
            // [CATEGORIES] tablosu boşaltılıyor - zincirleme olarak [PRODUITS] tablosu da boşaltılacak
            dao.deleteAllCategories();
            // veritabanının görüntülenmesi
            log("Liste des categories avant l'ajout", 2);
            affiche(dao.getAllCategories(), jsonMapperCategorieWithoutProduits);
            log("Liste des produits avant l'ajout", 2);
            affiche(dao.getAllProduits(), jsonMapperProduitWithoutCategorie);
            log("Ajout d'une catégorie [cat1] avec deux produits de même nom", 1);
            // ekleme işlemi gerçekleştiriliyor
            categorie = new Categorie("cat1");
            categorie.addProduit(new Produit("x", 1.0, ""));
            categorie.addProduit(new Produit("x", 1.0, ""));
            // kategori ekleniyor - zincirleme olarak ürünler de eklenecek
            try {
                dao.addCategories(Lists.newArrayList(categorie));
            } catch (DaoException e) {
                System.out.println(e);
            }
            // doğrulama
            log("Liste des categories après l'ajout", 2);
            affiche(dao.getAllCategories(), jsonMapperCategorieWithoutProduits);
            log("Liste des produits après l'ajout", 2);
            affiche(dao.getAllProduits(), jsonMapperProduitWithoutCategorie);
        } catch (DaoException e) {
            System.out.println(e);
        } finally {
            if (context != null) {
                // tamam
                context.close();
            }
        }
        System.out.println("Travail terminé");
    }

    // T türünde bir öğenin görüntülenmesi
    static private <T> void affiche(T element, ObjectMapper jsonMapper) throws JsonProcessingException {
        System.out.println(jsonMapper.writeValueAsString(element));
    }

    // T türündeki öğelerin listesinin görüntülenmesi
    static private <T> void affiche(List<T> elements, ObjectMapper jsonMapper) throws JsonProcessingException {
        for (T element : elements) {
            affiche(element, jsonMapper);
        }
    }

    private static 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);
    }
}
  • 25. satır: [AppConfig] yapılandırma sınıfından Spring bean'lerinin oluşturulması;
  • 26-33. satırlar: jSON eşleştiricilerinden referansların alınması. [ApplicationContext].getBean yönteminin aşağıdaki imzası kullanılır:
    • [ApplicationContext].getBean(String id, Class sınıf) : [classe] türünde birden fazla bean olduğunda kullanılır. Bu durumda, istenen bean'in tanımlayıcısı belirtilir. Eğer bu bean, [@Bean] anotasyonu ile tanımlanmışsa, kimliği anotasyon uygulanan yöntemin adıdır. [@Bean(« identifiant »] anotasyonu ile tanımlanmışsa, kimliği anotasyonda belirtilen değerdir;
  • 34. satır: [DAO] katmanından bir referans alınması;
  • satır 37-39: veritabanının boşaltılması. Kategori tablosu boşaltılır (satır 39). Çünkü şunu yazdık:

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "categorie", cascade = { CascadeType.ALL })
    public Set<Produit> produits = new HashSet<Produit>();

bir kategori silindiğinde, o kategoriye bağlı tüm ürünler de silinir;

  • satır 43-53: tabloya her biri 5 ürün içeren 2 kategori ekleniyor. Satır 50'de, iki kategorinin eklenmesi aynı anda ürünlerinin de eklenmesine neden olacak, çünkü yine [cascade = { CascadeType.ALL }] yazdık;
  • 58. satır: Kategoriler görüntülenir. Kategorileri ürünleri olmadan görüntülemek için jSON [jsonMapperCategorieWithoutProduits] eşleştiricisi kullanılır. Nitekim [dao.getAllCategories()] yöntemi, kategorileri ürünleri olmadan görüntüler (gecikmeli yükleme);
  • 61. satır: Ürünler, kategorileri olmadan görüntülenir. [dao.getAllProduits()] yöntemi, ürünleri kategorileri olmadan görüntüler (gecikmeli yükleme);
  • 63-65. satırlar: [categorie1] adlı kategoriyi ürünleriyle birlikte görüntüler (eager loading);
  • 67-69. satırlar: Bir ürünü, kategorisi ile birlikte görüntüler;
  • 71-81. satırlar: [categorie1] kategorisindeki tüm ürünlerin fiyatlarını %10 artırır;
  • 91-101. satırlar: aynı ada sahip iki ürün içeren bir kategori eklenir. Ancak [PRODUITS] tablosunda [NOM] sütununda tekillik kısıtlaması vardır. Dolayısıyla ikinci ürünün eklenmesi reddedilecek ve bir istisna atılacaktır. Ancak [dao.addProduits] yöntemi bir işlem içinde yürütülür. İkinci eklemenin başarısız olması, dolayısıyla ilk ürünün ve bunların kategorisi olan [cat1]'in eklenmesini de iptal etmelidir. Doğrulamak istediğimiz şey budur;
  • satır 119-121: T türündeki herhangi bir öğenin jSON dizesini görüntüleyebilen genel bir yöntem. jSON serileştirmesi, parametre olarak geçirilen eşleştirici tarafından kontrol edilir;
  • satır 124-128: benzer bir yöntem, bu sefer T türündeki öğelerden oluşan bir liste için;

[Main] sınıfının çalıştırılması şu sonuçları verir (Spring günlükleri hariç):


Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
Affichage de la base --------------------------------
-- Liste des catégories
{"id":4,"version":0,"nom":"categorie0"}
{"id":5,"version":0,"nom":"categorie1"}
-- Liste des produits
{"id":13,"version":0,"nom":"produit00","idCategorie":4,"prix":100.0,"description":"desc00"}
{"id":14,"version":0,"nom":"produit01","idCategorie":4,"prix":101.0,"description":"desc01"}
{"id":15,"version":0,"nom":"produit02","idCategorie":4,"prix":102.0,"description":"desc02"}
{"id":16,"version":0,"nom":"produit03","idCategorie":4,"prix":103.0,"description":"desc03"}
{"id":17,"version":0,"nom":"produit04","idCategorie":4,"prix":104.0,"description":"desc04"}
{"id":18,"version":0,"nom":"produit10","idCategorie":5,"prix":110.0,"description":"desc10"}
{"id":19,"version":0,"nom":"produit11","idCategorie":5,"prix":111.0,"description":"desc11"}
{"id":20,"version":0,"nom":"produit12","idCategorie":5,"prix":112.0,"description":"desc12"}
{"id":21,"version":0,"nom":"produit13","idCategorie":5,"prix":113.0,"description":"desc13"}
{"id":22,"version":0,"nom":"produit14","idCategorie":5,"prix":114.0,"description":"desc14"}
-- Catégorie 1 avec ses produits
{"id":5,"version":0,"nom":"categorie1","produits":[{"id":18,"version":0,"nom":"produit10","idCategorie":5,"prix":110.0,"description":"desc10"},{"id":19,"version":0,"nom":"produit11","idCategorie":5,"prix":111.0,"description":"desc11"},{"id":20,"version":0,"nom":"produit12","idCategorie":5,"prix":112.0,"description":"desc12"},{"id":21,"version":0,"nom":"produit13","idCategorie":5,"prix":113.0,"description":"desc13"},{"id":22,"version":0,"nom":"produit14","idCategorie":5,"prix":114.0,"description":"desc14"}]}
-- Produit [produit14] avec sa catégorie
{"id":22,"version":0,"nom":"produit14","idCategorie":5,"prix":114.0,"description":"desc14","categorie":{"id":5,"version":0,"nom":"categorie1"}}
Mise à jour du prix des produits de [categorie1] --------------------------------
-- Produits de la catégorie [categorie1] avant la mise à jour
{"id":5,"version":0,"nom":"categorie1","produits":[{"id":18,"version":0,"nom":"produit10","idCategorie":5,"prix":110.0,"description":"desc10"},{"id":19,"version":0,"nom":"produit11","idCategorie":5,"prix":111.0,"description":"desc11"},{"id":20,"version":0,"nom":"produit12","idCategorie":5,"prix":112.0,"description":"desc12"},{"id":21,"version":0,"nom":"produit13","idCategorie":5,"prix":113.0,"description":"desc13"},{"id":22,"version":0,"nom":"produit14","idCategorie":5,"prix":114.0,"description":"desc14"}]}
-- Produits de la catégorie [categorie1] après la mise à jour
{"id":5,"version":0,"nom":"categorie1","produits":[{"id":18,"version":1,"nom":"produit10","idCategorie":5,"prix":121.0,"description":"desc10"},{"id":19,"version":1,"nom":"produit11","idCategorie":5,"prix":122.1,"description":"desc11"},{"id":20,"version":1,"nom":"produit12","idCategorie":5,"prix":123.2,"description":"desc12"},{"id":21,"version":1,"nom":"produit13","idCategorie":5,"prix":124.3,"description":"desc13"},{"id":22,"version":1,"nom":"produit14","idCategorie":5,"prix":125.4,"description":"desc14"}]}
Vidage de la base de données --------------------------------
-- Liste des categories avant l'ajout
-- Liste des produits avant l'ajout
Ajout d'une catégorie [cat1] avec deux produits de même nom --------------------------------
Les erreurs suivantes se sont produites : 
- org.hibernate.exception.ConstraintViolationException: could not execute statement
- could not execute statement
- Duplicate entry 'x' for key 'NOM'
-- Liste des categories après l'ajout
-- Liste des produits après l'ajout
Travail terminé
  • satır 4-17: tabloya eklenen kategoriler ve ürünler;
  • 18-19. satırlar: ürünleri ile birlikte bir kategori;
  • 20-21. satırlar: bir ürün ve kategorisi;
  • 22-26. satırlar: Bazı ürünlerin fiyatlarının güncellenmesi. 24. satırda, fiyatların gerçekten %10 arttığı görülüyor;
  • satır 27-36: aynı isimde iki ürün içeren [cat1] kategorisinin eklenmesi. Tablonun ekleme öncesi (satır 28-29) ve ekleme sonrası (satır 35-36) aynı olduğu görülüyor; bu da işlemin tüm eklemelerinin gerçekten iptal edildiğini gösteriyor;
  • satır 31-34: İkinci ürünün eklenmesi sırasında meydana gelen ve tüm işlemin başarısız olmasına neden olan istisna;

11.3.10. JUnit birim testi

  

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


package spring.data.tests;

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

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

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;

import spring.data.config.AppConfig;
import spring.data.dao.DaoException;
import spring.data.dao.IDao;
import spring.data.entities.Categorie;
import spring.data.entities.Produit;

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

    // katman [DAO]
    @Autowired
    private IDao dao;

    // jSON filtreleri
    @Autowired
    @Qualifier("jsonMapper")
    private ObjectMapper jsonMapper;
    @Autowired
    @Qualifier("jsonMapperCategorieWithProduits")
    private ObjectMapper jsonMapperCategorieWithProduits;
    @Autowired
    @Qualifier("jsonMapperProduitWithCategorie")
    private ObjectMapper jsonMapperProduitWithCategorie;
    @Autowired
    @Qualifier("jsonMapperCategorieWithoutProduits")
    private ObjectMapper jsonMapperCategorieWithoutProduits;
    @Autowired
    @Qualifier("jsonMapperProduitWithoutCategorie")
    private ObjectMapper jsonMapperProduitWithoutCategorie;

    @Before
    public void cleanAndFill() {
        // her testten önce veritabanı temizlenir
        log("Vidage de la base de données", 1);
        // [CATEGORIES] tablosu boşaltılır - buna bağlı olarak [PRODUITS] tablosu da boşaltılacaktır
        dao.deleteAllCategories();
        // --------------------------------------------------------------------------------------
        log("Remplissage de la base", 1);
        // tablolar doldurulur
        List<Categorie> categories = new ArrayList<Categorie>();
        for (int i = 0; i < 2; i++) {
            Categorie categorie = new Categorie(String.format("categorie%d", i));
            for (int j = 0; j < 5; j++) {
                categorie.addProduit(new Produit(String.format("produit%d%d", i, j), 100 * (1 + (double) (i * 10 + j) / 100),
                        String.format("desc%d%d", i, j)));
            }
            categories.add(categorie);
        }
        // kategori ekleniyor - zincirleme olarak ürünler de eklenecek
        categories = dao.addCategories(categories);
    }

    @Test
    public void showDataBase() throws BeansException, JsonProcessingException {
        // kategori listesi
        log("Liste des catégories", 2);
        List<Categorie> categories = dao.getAllCategories();
        affiche(categories, jsonMapperCategorieWithoutProduits);
        // ürün listesi
        log("Liste des produits", 2);
        List<Produit> produits = dao.getAllProduits();
        affiche(produits, jsonMapperProduitWithoutCategorie);
        // bazı kontroller
        Assert.assertEquals(2, categories.size());
        Assert.assertEquals(10, produits.size());
        Categorie categorie = findCategorieByName("categorie0", categories);
        Assert.assertNotNull(categorie);
        Produit produit = findProduitByName("produit03", produits);
        Assert.assertNotNull(produit);
        Long idCategorie = produit.getIdCategorie();
        Assert.assertEquals(categorie.getId(), idCategorie);
    }

    @Test
    public void getCategorieByNameWithProduits() {
        log("getCategorieByNameWithProduits", 1);
        Categorie categorie1 = dao.getCategorieByNameWithProduits("categorie1");
        Assert.assertNotNull(categorie1);
        Assert.assertEquals(5, categorie1.getProduits().size());
    }

    @Test
    public void getCategorieByNameWithoutProduits() {
        log("getCategorieByNameWithoutProduits", 1);
        Categorie categorie1 = dao.getCategorieByNameWithoutProduits("categorie1");
        Assert.assertNotNull(categorie1);
        Assert.assertEquals("categorie1", categorie1.getNom());
    }

    @Test
    public void getProduitByIdWithCategorie() {
        log("getProduitByNameWithCategorie", 1);
        Produit produit = dao.getProduitByNameWithCategorie("produit03");
        Produit produit2 = dao.getProduitByIdWithCategorie(produit.getId());
        Assert.assertNotNull(produit2);
        Assert.assertEquals(produit2.getNom(), produit.getNom());
        Assert.assertEquals(produit2.getId(), produit.getId());
        Assert.assertEquals(produit.getCategorie().getId(), produit2.getCategorie().getId());
    }

    @Test
    public void getProduitByIdWithoutCategorie() {
        log("getProduitByIdWithoutCategorie", 1);
        Produit produit = dao.getProduitByNameWithCategorie("produit03");
        Produit produit2 = dao.getProduitByIdWithoutCategorie(produit.getId());
        Assert.assertNotNull(produit2);
        Assert.assertEquals(produit2.getNom(), produit.getNom());
        Assert.assertEquals(produit2.getId(), produit.getId());
    }
...
    // -------------- özel yöntemler
    private Produit findProduitByName(String nom, List<Produit> produits) {
        for (Produit produit : produits) {
            if (produit.getNom().equals(nom)) {
                return produit;
            }
        }
        return null;
    }

    private Categorie findCategorieByName(String nom, List<Categorie> categories) {
        for (Categorie categorie : categories) {
            if (categorie.getNom().equals(nom)) {
                return categorie;
            }
        }
        return null;
    }

    // T türünde bir öğenin görüntülenmesi
    static private <T> void affiche(T element, ObjectMapper jsonMapper) throws JsonProcessingException {
        System.out.println(jsonMapper.writeValueAsString(element));
    }

    // T türündeki öğelerin listesinin görüntülenmesi
    static private <T> void affiche(List<T> elements, ObjectMapper jsonMapper) throws JsonProcessingException {
        for (T element : elements) {
            affiche(element, jsonMapper);
        }
    }

    private static 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);
    }

    private static void show(String title, List<String> messages) {
        // başlık
        System.out.println(String.format("%s : ", title));
        // mesajlar
        for (String message : messages) {
            System.out.println(String.format("- %s", message));
        }
    }

}
  • 27. satır: birim testi, 11.3.8. paragrafında daha önce tanıtılan [AppConfig] sınıfı tarafından yapılandırılmıştır;
  • 32-33. satırlar: [DAO] katmanına bir referans eklenmesi;
  • satır 36-50: beş jSON eşleştiricisinin eklenmesi;
  • satır 60-71: veritabanını boşalttıktan sonra (satır 57), veritabanı her biri 5 ürün içeren 2 kategori ile doldurulur. Bu yöntem, satır 52'deki [@Before] açıklaması nedeniyle her testten önce yürütülür;
  • satır 75-93: veritabanının içeriğini görüntüler;
  • 95-101. satırlar: adıyla tanımlanan bir kategoriyi, içindeki ürünlerle birlikte ister;
  • satır 103-109: ürünleri olmadan bir kategori sorgulanır; kategori, adıyla tanımlanır;
  • satır 111-120: kategorisi ile birlikte bir ürün talep eder; ürün, numarasıyla tanımlanır;
  • satır 122-130: kategorisi olmadan bir ürün ister; ürün numarasıyla tanımlanır;
  • satır 133-184: farklı testler tarafından paylaşılan özel yöntemler;

Yapılması gereken: Testi çalıştırın. Testin başarılı olması gerekir.


11.3.11. Günlük yönetimi

Konsol uygulamasının veya JUnit testinin günlükleri, aşağıdaki [logback.xml] dosyası ile yapılandırılır:

  

Dosyanın adı [logback.xml] olmalı ve projenin Classpath'inde bulunmalıdır. Bunun için, Classpath'in bir parçası olan [src/main/resources] klasörüne yerleştirilmiştir. İçeriği şöyledir:


<configuration> 

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- kodlayıcılara varsayılan olarak tür atanır
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- günlük düzeyini kontrol eder -->
  <root level="info"> <!-- bilgi, hata ayıklama, uyarı -->
    <appender-ref ref="STDOUT" />
  </root>
</configuration>
  • 12. satır: [<root level="info">] etiketi, [info] seviyesindeki günlükleri görüntüler. [info] yerine şunlar yazılabilir:
    • [debug]: Bu, en ayrıntılı günlük seviyesidir. Projenin hata ayıklama aşamasında kullanılması tavsiye edilir, çünkü istemci/sunucu arasındaki iletişimle ilgili çok ilginç günlükler içerir. Bu, "perde arkasında" neler olup bittiğini anlamanın bir yoludur;
    • [off]: Hiçbir günlük kaydı yoktur;
    • [warn]: Spring'in hata sayılmayan ancak olağandışı durumları görüntülediği orta düzey bir günlük seviyesi. Beklenen sonucu alamazsanız bu günlükleri incelemeniz gerekir;

Yapılması gereken: 12. satırdaki seviyeyi [debug] olarak değiştirin ve ardından birim testini çalıştırın. Günlüklerdeki farkı inceleyin.


11.3.12. Projenin Maven arşivinin oluşturulması

Proje arşivini yerel Maven deposuna yüklemek için aşağıdaki adımları izleyin [1-3]:

Arşiv, [pom.xml] dosyasında bulunan kimlik bilgileriyle oluşturulacaktır:


    <groupId>istia.st.springdata</groupId>
    <artifactId>intro-spring-data-01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

Yerel Maven deposunun konumu Eclipse yapılandırmasında bulunabilir:

 

Böylece, Maven artefaktının doğru şekilde kurulup kurulmadığını kontrol etmek mümkündür:

 

Artık başka bir yerel Maven projesi bu arşivi kullanabilir.