Skip to content

5. Sürüm 1: Spring Mimarisi / JPA

Bir belediyenin "Erken Çocukluk Merkezi"nde çalışan çocuk bakıcılarının maaş bordrolarını hazırlamaya yarayan bir konsol uygulaması ve bir grafik kullanıcı arayüzü uygulaması yazmayı planlıyoruz. Bu uygulama aşağıdaki mimariye sahip olacaktır:

5.1. BD Veritabanı

Maaş bordrosunu oluşturmak için gerekli statik veriler, bundan sonra dbpam olarak adlandıracağımız bir veritabanına yerleştirilecektir. Bu veritabanı aşağıdaki tabloları içerebilir:

EMPLOYES tablosu: farklı çocuk bakıcıları hakkındaki bilgileri toplar

Yapısı:

ID
birincil anahtar
VERSION
sürüm numarası – satır her değiştirildiğinde artar
SS
çalışanın sosyal güvenlik numarası – benzersiz
NOM
çalışanın adı
prenom
ad
ADRESSE
adresi
VILLE
şehri
CODEPOSTAL
posta kodu
INDEMNITE_ID
[ID] alanındaki yabancı anahtar, [INDEMNITES] tablosunda

İçeriği şu şekilde olabilir:

Image

COTISATIONS Tablosu: Sosyal güvenlik primlerinin hesaplanmasında gerekli olan yüzdeleri içerir

Yapı:

ID
birincil anahtar
VERSION
sürüm numarası – satır her değiştirildiğinde artar
CSGRDS
yüzde: genel sosyal katkı payı + sosyal borç geri ödemesine katkı payı
CSGD
yüzde: indirilebilir genel sosyal katkı payı
SECU
yüzde: sosyal güvenlik, dul ve yaşlılık
RETRAITE
yüzde: ek emeklilik + işsizlik sigortası

İçeriği şu şekilde olabilir:

Image

Sosyal güvenlik prim oranları çalışandan bağımsızdır. Yukarıdaki tablo sadece bir satırdan oluşur.

INDEMNITES tablosu: ödenecek maaşın hesaplanmasına olanak tanıyan unsurları bir araya getirir.
ID
birincil anahtar
 
VERSION
sürüm numarası – satır her değiştirildiğinde artar
 
INDICE
işlem numarası – benzersiz
 
BASEHEURE
bir saatlik nöbetin net fiyatı (Euro)
 
ENTRETIENJOUR
bakım günü başına euro cinsinden bakım ücreti
 
REPASJOUR
bakım günü başına euro cinsinden yemek ödeneği
 
INDEMNITESCP
Ücretli izin ödeneği. Bu, temel maaşa uygulanacak bir yüzde oranıdır.
 
  

İçeriği şu şekilde olabilir:

Image

Ödeneklerin çocuk bakıcısından bakıcıya farklılık gösterebileceği unutulmamalıdır. Bu ödenekler, çocuk bakıcısının maaş endeksi aracılığıyla o bakıcıya özel olarak belirlenir. Örneğin, maaş endeksi 2 olan Bayan Marie Jouveinal’ın (EMPLOYES tablosu) saatlik maaşı 2,1 avrodur (INDEMNITES tablosu).

5.2. Bir çocuk bakıcısının maaşının hesaplanma yöntemi

Şimdi bir çocuk bakıcısının aylık maaşının hesaplanma yöntemini sunuyoruz. Bu, gerçekte kullanılan yöntem olduğunu iddia etmemektedir. Örnek olarak, ödeme yapılacak ay boyunca 20 gün boyunca 150 saat çalışmış olan Bayan Marie Jouveinal’ın maaşını ele alıyoruz.

Aşağıdaki unsurlar dikkate alınmaktadır:

[TOTALHEURES]: total des heures
travaillées dans le mois

[TOTALJOURS]: total des jours travaillés
dans le mois
[TOTALHEURES]=150
[TOTALJOURS]= 20
Çocuk bakıcısının temel maaşı
aşağıdaki formülle hesaplanır:
[SALAIREBASE]=([TOTALHEURES]
*[BASEHEURE])*(1+
[INDEMNITESCP]/100)
[SALAIREBASE]=
(150*[2.1])*(1+0.15)= 362,25
Bir miktar sosyal güvenlik primi
bu temel maaştan
:

Contribution sociale généralisée et
contribution au remboursement de la
dette sociale :
 [SALAIREBASE]*[CSGRDS/100]

Contribution sociale généralisée déductible :
 [SALAIREBASE]*[CSGD/100]

Sécurité sociale, veuvage, vieillesse :
 [SALAIREBASE]*[SECU/100]

Retraite Complémentaire + AGPF +
Assurance Chômage :
 [SALAIREBASE]*[RETRAITE/100]
CSGRDS : 12,64
CSGD : 22,28
Sécurité sociale : 34,02
Retraite : 28,55
Toplam sosyal güvenlik primleri:
[COTISATIONSSOCIALES]=
[SALAIREBASE]*(CSGRDS+CSGD
+SECU+RETRAITE)/100
[COTISATIONSSOCIALES]=97,48
Ayrıca, çocuk bakıcısı, çalıştığı her gün için geçim ödeneği ve yemek ödeneği almaya hak kazanır. Bu kapsamda aşağıdaki ödenekleri alır:

[Indemnités]=[TOTALJOURS]
*(ENTRETIENJOUR+REPASJOUR)
[INDEMNITES]=104
Sonuç olarak, çocuk bakıcısına ödenecek net maaş şu şekildedir:
[SALAIREBASE]-[COTISATIONSSOCIALES]+[INDEMNITÉS]
[salaire NET]=368,77

5.3. Konsol uygulamasının işleyişi

İşte konsol uygulamasının bir DOS penceresinde çalıştırılmasına ilişkin bir örnek:

dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150 20

Valeurs saisies :
N° de sécurité sociale de l'employé : 254104940426058
Nombre d'heures travaillées : 150
Nombre de jours travaillés : 20

Informations Employé :
Nom : Jouveinal
Prénom : Marie
Adresse : 5 rue des Oiseaux
Ville : St Corentin
Code Postal : 49203
Indice : 2

Informations Cotisations :
CSGRDS : 3.49 %
CSGD : 6.15 %
Retraite : 7.88 %
Sécurité sociale : 9.39 %

Informations Indemnités :
Salaire horaire : 2.1 euro
Entretien/jour : 2.1 euro
Repas/jour : 3.1 euro
Congés Payés : 15.0 %

Informations Salaire :
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnités d'entretien : 42.0 euro
Indemnités de repas : 62.0 euro
Salaire net : 368.77 euro

Aşağıdaki bilgileri alacak bir program yazacağız:

  1. çocuk bakıcısının sosyal güvenlik numarası (örnekte 254104940426058 - 1. satır)
  2. toplam çalışma saati sayısı (örnekte 150 - 1. satır)
  3. toplam çalışma günü sayısı (örnekte 20 - 1. satır)

Görüldüğü gibi:

  • 9-14. satırlar: Sosyal güvenlik numarası verilen çalışana ait bilgileri gösterir
  • 17-20. satırlar: çeşitli prim oranlarını gösterir
  • 23-26. satırlar: çalışanın maaş endeksine (burada endeks 2) bağlı ödenekleri gösterir
  • 29-33. satırlar: ödenecek maaşın bileşenlerini gösterir

Uygulama, olası hataları bildirir:

Parametre olmadan çağrıldığında:


dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar
Syntaxe : pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés

Hatalı verilerle çağrı:


dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar  254104940426058 150x 20x
Le nombre d'heures travaillées [150x] est erroné
Le nombre de jours travaillés [20x] est erroné

Hatalı sosyal güvenlik numarasıyla yapılan çağrı:


dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar  xx 150 20
L'erreur suivante s'est produite : L'employé de n°[xx] est introuvable

5.4. Grafik uygulamanın işleyişi

Grafik uygulama, bir Swing formu aracılığıyla çocuk bakıcılarının maaşlarının hesaplanmasına olanak tanır:

  • Konsol programına parametre olarak aktarılan bilgiler artık [1, 2, 3] giriş alanları aracılığıyla girilmektedir.
  • [4] düğmesi maaş hesaplamasını başlatır
  • Form, ödenecek net maaşa kadar maaşın çeşitli bileşenlerini görüntüler: [5]

[1, 6] açılır listesi, çalışanların SS numaralarını değil, ad ve soyadlarını gösterir. Burada, aynı ad ve soyada sahip iki çalışan olmadığı varsayılmaktadır.

5.5. Veritabanının oluşturulması

WampServer komutunu çalıştırıyoruz ve PhpMyAdmin ile [1] araçlarını kullanıyoruz:

  • [2]'te [Bases de données] seçeneğini seçiyoruz,
  • [3]'e, bir veritabanı oluşturulur [dbpam_hibernate],
  • [4]'te, oluşturulan veritabanı. Onu seçiyoruz,
  • [5]'te, SQL komut dosyasını içe aktarmak istiyoruz,
  • [6]'e, dosyayı seçmek için [Parcourir] düğmesini kullanıyoruz,
  • [7,8]'te, SQL komut dosyasını seçiyoruz,
  • [9]'te, komut dosyası çalıştırılır,
  • [10]'e dönüştürüldüğünde, tablolar oluşturulmuştur. İçerikleri şöyledir:

EMPLOYES tablosu

Image

INDEMNITES tablosu

Image

COTISATIONS tablosu

Image

5.6. Uygulama JPA

5.6.1. JPA Katmanı / Hibernate

Aşağıdaki ortamda JPA katmanını yapılandıracağız:

Bir konsol programı veritabanıyla çalışacaktır. Bunun için şunlar gereklidir:

  • bir veritabanına sahip olmak,
  • SGBD sürücüsünün JDBC sürücüsüne sahip olmak (burada MySQL),
  • Hibernate ile JPA katmanını uygulamak,
  • konsol programını yazmak.

[mv-pam-jpa-hibernate] [1] adlı Maven projesini oluşturuyoruz:

Uygulamamızın mimarisinde şu öğelere ihtiyacımız var:

  • veritabanı,
  • JDBC sürücüsü (SGBD ve MySQL),
  • JPA / Hibernate katmanı (varlıklar ve yapılandırma),
  • test konsol programı.

5.6.1.1. Veritabanı

Öncelikle boş veritabanını oluşturalım. WampServer'i başlatıp PhpMyAdmin ve [1] araçlarını kullanıyoruz:

  • [2]'te [Bases de données] seçeneğini seçin,
  • [3]'e dönüştürürken, [dbpam_hibernate] adlı bir veritabanı oluşturulur,
  • [4]'te, veritabanı oluşturuldu.

5.6.1.2. JPA katmanının yapılandırılması

JDBC katmanı ile veritabanı arasındaki bağlantı, JPA katmanını yapılandıran [persistence.xml] dosyası aracılığıyla sağlanır. Bu dosya NetBeans ile oluşturulabilir:

  • [services] [1] sekmesinde, JDBC sürücüsü kullanılarak MySQL [2] veritabanına bağlanılır,
  • [3] sekmesinde, bağlanmak istediğiniz veritabanının adı.
  • [4]'te, veritabanının URL JDBC'i,
  • [5]'te, root olarak şifre girmeden bağlanılır,
  • [6]'te bağlantıyı test edebiliriz,
  • [7]'te bağlantı başarılı oldu.
  • bağlantı [8] ve [9] olarak görünür,
  • [10]'te projeye yeni bir öğe ekleniyor,
  • [11]'te [Persistence] kategorisi seçilir ve [12]'te [Persistence Unit] öğesi seçilir,
  • [13]'te bu kalıcılık birimine bir ad verilir,
  • [14]'te bir Hibernate uygulaması seçilir,
  • [15]'te, az önce oluşturduğumuz MySQL veritabanına bağlantıyı belirleriz,
  • [16] adımında, JPA katmanının örneklendirilmesi sırasında, bu katmanın projedeki JPA varlıklarına karşılık gelen tabloları oluşturması (create) gerektiğini belirtiriz.

Sihirbazın tamamlanmasıyla [persistence.xml] dosyası oluşturulur:

  • dosya, projenin yeni bir dalında, [META-INF] [1] klasöründe görünür;
  • bu klasör, [2,3] projesindeki [src/main/resources] klasörüne karşılık gelir.

İçeriği şu şekildedir:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
      <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
    </properties>
  </persistence-unit>
</persistence>
  • 3. satır: kalıcılık biriminin adı ve işlem türü. RESOURCE_LOCAL, projenin işlemleri kendi başına yönettiğini gösterir. Burada bunu yapması gereken konsol programıdır,
  • 4. satır: Kullanılan JPA uygulaması Hibernate'tir,
  • satır 6-9: veritabanı bağlantısının özellikleri JDBC,
  • 11. satır: JPA varlıklarına karşılık gelen tabloların oluşturulmasını ister. Aslında, NetBeans burada hatalı bir yapılandırma oluşturur. Yapılandırma şu şekilde olmalıdır:

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

"create" seçeneği ile Hibernate, JPA katmanının oluşturulması sırasında, JPA varlıklarına karşılık gelen tabloları siler ve ardından yeniden oluşturur. "create-drop" seçeneği de aynı işlevi görür, ancak JPA katmanının ömrü sona erdiğinde tüm tabloları siler. Başka bir seçenek daha vardır:


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

Bu seçenek, tablolar mevcut değilse oluşturur, ancak zaten mevcutsa silmez.

Hibernate yapılandırmasına üç özellik daha ekleyeceğiz:


      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>

Bu özellikler, Hibernate'ten veritabanına gönderdiği SQL komutlarını görüntülemesini ister. Dolayısıyla tam dosya şu şekildedir:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
      <property name="hibernate.hbm2ddl.auto" value="create"/>
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>
      <property name="use_sql_comments" value="true"/>
    </properties>
  </persistence-unit>
</persistence>

5.6.1.3. Bağımlılıklar

Proje mimarisine geri dönelim:

JPA katmanını [persistence.xml] dosyası aracılığıyla yapılandırdık. Seçilen uygulama Hibernate oldu. Bu, projeye bazı bağımlılıklar getirdi:

  

Bu bağımlılıklar, Hibernate'in projeye dahil edilmesinden kaynaklanmaktadır. Mimariye ait JDBC katmanını uygulayan MySQL'in JDBC sürücüsüne ait bir başka bağımlılığı daha eklememiz gerekiyor. [pom.xml] dosyasını şu şekilde güncelliyoruz:


<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>    
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.1.2</version>
    </dependency>
    ...
    <dependency>
      <groupId>org.hibernate.common</groupId>
      <artifactId>hibernate-commons-annotations</artifactId>
      <version>4.0.1.Final</version>
    </dependency>
  </dependencies>

8-12. satırlar, MySQL dosyasına JDBC sürücüsünün bağımlılığını ekler.

5.6.1.4. JPA


Soru: 4.4. paragrafındaki örnekteki adımları izleyerek, [Cotisation, Indemnite, Employe] varlıklarını oluşturun.


Notlar:

  • varlıklar, [jpa] adlı bir paketin parçası olacaktır,
  • her varlık bir sürüm numarasına sahip olacaktır,
  • iki varlık bir ilişkiyle birbirine bağlıysa, yalnızca ana ilişki @ManyToOne oluşturulacaktır. Ters ilişki @OneToMany oluşturulmayacaktır.

5.6.1.5. Ana sınıfın kodu

Projeye, daha önce geliştirilen JPA ve [1] varlıklarını dahil ediyoruz:

ardından [2]'i ve aşağıdaki [main.Main] sınıfını ekliyoruz:


package main;

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

public class Main {

  public static void main(String[] args) {
    // Entity Manager'ı oluşturmak, JPA katmanını oluşturmak için yeterlidir
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-pam-jpa-hibernatePU");
    EntityManager em=emf.createEntityManager();
    // kaynakların serbest bırakılması
    em.close();
    emf.close();
  }
}
  • 10. satır: [mv-pam-jpa-hibernatePU] adlı kalıcılık biriminin EntityManagerFactory'ini oluşturuyoruz. Bu ad, [persistence.xml] dosyasından gelmektedir:

  <persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    ...
  </persistence-unit>
  • 12. satır: EntityManager dosyası oluşturulur. Bu işlem, JPA katmanını oluşturur. [persistence.xml] dosyası işlenecek ve dolayısıyla veritabanı tabloları oluşturulacaktır,
  • 14-15. satırlar: Kaynaklar serbest bırakılır.

5.6.1.6. Tests

Projemizin mimarisine geri dönelim:

Tüm katmanlar uygulandı. [2] projesini çalıştırıyoruz.

Konsol çıktıları şu şekildedir:

------------------------------------------------------------------------
Building mv-pam-jpa-hibernate 1.0-SNAPSHOT
------------------------------------------------------------------------

[resources:resources]
[debug] execute contextualize
Using 'UTF-8' encoding to copy filtered resources.
Copying 1 resource

[compiler:compile]
Nothing to compile - all classes are up to date

[exec:exec]
juin 21, 2012 4:22:47 PM org.hibernate.annotations.common.Version <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.1.Final}
juin 21, 2012 4:22:47 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.1.2}
juin 21, 2012 4:22:47 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
juin 21, 2012 4:22:47 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000402: Using Hibernate built-in connection pool (not for production use!)
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000115: Hibernate connection pool size: 20
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000006: Autocommit mode: true
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000401: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/dbpam_hibernate]
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000046: Connection properties: {user=root, autocommit=true, release_mode=auto}
juin 21, 2012 4:22:48 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
juin 21, 2012 4:22:48 PM org.hibernate.engine.jdbc.internal.LobCreatorBuilder useContextualLobCreation
INFO: HHH000423: Disabling contextual LOB creation as JDBC driver reported JDBC version [3] less than 4
juin 21, 2012 4:22:48 PM org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
INFO: HHH000268: Transaction strategy: org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory
juin 21, 2012 4:22:48 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
juin 21, 2012 4:22:48 PM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: HHH000227: Running hbm2ddl schema export
Hibernate: 
    alter table EMPLOYES 
        drop 
        foreign key FK75C8D6BC73F24A67
juin 21, 2012 4:22:48 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: HHH000389: Unsuccessful: alter table EMPLOYES drop foreign key FK75C8D6BC73F24A67
juin 21, 2012 4:22:48 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: Table 'dbpam_hibernate.employes' doesn't exist
Hibernate: 
    drop table if exists COTISATIONS
Hibernate: 
    drop table if exists EMPLOYES
Hibernate: 
    drop table if exists INDEMNITES
Hibernate: 
    create table COTISATIONS (
        id bigint not null auto_increment,
        CSGD double precision not null,
        CSGRDS double precision not null,
        RETRAITE double precision not null,
        SECU double precision not null,
        VERSION integer not null,
        primary key (id)
    )
Hibernate: 
    create table EMPLOYES (
        id bigint not null auto_increment,
        SS varchar(15) not null unique,
        ADRESSE varchar(50) not null,
        CP varchar(5) not null,
        NOM varchar(30) not null,
        PRENOM varchar(20) not null,
        VERSION integer not null,
        VILLE varchar(30) not null,
        INDEMNITE_ID bigint not null,
        primary key (id)
    )
Hibernate: 
    create table INDEMNITES (
        id bigint not null auto_increment,
        BASE_HEURE double precision not null,
        ENTRETIEN_JOUR double precision not null,
        INDEMNITES_CP double precision not null,
        INDICE integer not null unique,
        REPAS_JOUR double precision not null,
        VERSION integer not null,
        primary key (id)
    )
Hibernate: 
    alter table EMPLOYES 
        add index FK75C8D6BC73F24A67 (INDEMNITE_ID), 
        add constraint FK75C8D6BC73F24A67 
        foreign key (INDEMNITE_ID) 
        references INDEMNITES (id)
juin 21, 2012 4:22:49 PM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: HHH000230: Schema export complete
juin 21, 2012 4:22:49 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH000030: Cleaning up connection pool [jdbc:mysql://localhost:3306/dbpam_hibernate]
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 2.637s
Finished at: Thu Jun 21 16:22:49 CEST 2012
Final Memory: 8M/153M

Çalıştırılan program, JPA katmanını örneklendirmek dışında hiçbir işlem yapmadığından, konsolda yalnızca Hibernate günlükleri bulunur. Aşağıdaki noktalara dikkat edilmelidir:

  • 43. satır: Hibernate, [EMPLOYES] tablosundaki yabancı anahtarı silmeye çalışıyor,
  • 51-55. satırlar: üç tablonun silinmesi,
  • 57. satır: [COTISATIONS] tablosunun oluşturulması,
  • 67. satır: [EMPLOYES] tablosunun oluşturulması,
  • satır 80: [INDEMNITES] tablosunun oluşturulması,
  • 91. satır: [EMPLOYES] tablosunun yabancı anahtarının oluşturulması.

NetBeans'te, daha önce oluşturulan bağlantıda tablolar görülebilir:

Oluşturulan tablolar, hem kullanılan JPA katmanının hem de kullanılan SGBD'in uygulamasına bağlıdır. Dolayısıyla, aynı veritabanını kullanan bir JPA / EclipseLink uygulaması, farklı tablolar oluşturabilir. Şimdi bunu inceleyeceğiz.

Aşağıdaki ortamda yeni bir Maven projesi oluşturacağız:

Bir önceki paragrafta anlatılan adımları takip edeceğiz:

  1. MySQL ve [dbpam_eclipselink] veritabanlarını oluşturacağız. Bunları oluşturmak için [dbpam_eclipselink.sql] komut dosyasını kullanacağız,
  2. projenin [persistence.xml] dosyasını oluşturacağız. JPA 2.0 EclipseLink uygulamasını alacağız,
  3. oluşturulan bağımlılıklara MySQL'in JDBC sürücüsü bağımlılığını ekleyin,
  4. JPA varlıklarını ve konsol programını ekleyin,
  5. testleri yapın.

[persistence.xml] dosyası şu şekilde olacaktır:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="pam-jpa-eclipselinkPU" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <properties>
      <property name="eclipselink.target-database" value="MySQL"/>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="eclipselink.logging.level" value="FINE"/>
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
    </properties>
  </persistence-unit>
</persistence>
  • 9-13 numaralı özellikler NetBeans sihirbazı tarafından oluşturulmuştur,
  • 14. satır: Bu özellik, EclipseLink'in günlük düzeyini ayarlamamızı sağlar. FINE seviyesi, EclipseLink'in veritabanına göndereceği SQL komutlarını bilmemizi sağlar,
  • 15. satır: JPA / EclipseLink katmanının örneklendirilmesi sırasında, JPA varlık tabloları silinecek ve ardından yeniden oluşturulacaktır.

Konsolda elde edilen sonuçlar şunlardır:

------------------------------------------------------------------------
Building mv-pam-jpa-eclipselink 1.0-SNAPSHOT
------------------------------------------------------------------------

[resources:resources]
[debug] execute contextualize
Using 'UTF-8' encoding to copy filtered resources.
Copying 1 resource

[compiler:compile]
Nothing to compile - all classes are up to date

[exec:exec]
[EL Config]: 2012-06-22 14:35:01.852--ServerSession(730572764)--İş Parçacığı(Thread[main,5,main])--Kalıcı sınıf [class jpa.Cotisation] için erişim türü [FIELD] olarak ayarlanmıştır.
[EL Config]: 2012-06-22 14:35:01.884--ServerSession(730572764)--İş parçacığı(Thread[main,5,main])--Kalıcı sınıf [class jpa.Employe] için erişim türü [FIELD] olarak ayarlanmıştır.
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--İş Parçacığı(Thread[main,5,main])--Çoktan bire eşleme öğesi [field indemnite] için hedef varlık (referans) sınıfı varsayılan olarak şuna ayarlanıyor: jpa.Indemnite sınıfı.
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--İş parçacığı(Thread[main,5,main])--Kalıcı sınıf [class jpa.Indemnite] için erişim türü [FIELD] olarak ayarlanmıştır.
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--İş Parçacığı(Thread[main,5,main])--[class jpa.Cotisation] varlık sınıfı için takma ad varsayılan olarak Cotisation olarak ayarlanıyor.
[EL Config]: 2012-06-22 14:35:01.915--ServerSession(730572764)--İş Parçacığı(Thread[main,5,main])--[id] öğesinin sütun adı varsayılan olarak şuna ayarlanıyor: ID.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--[class jpa.Employe] varlık sınıfının takma adı şu şekilde varsayılan olarak ayarlanıyor: Employe.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--İş Parçacığı(Thread[main,5,main])--[id] öğesinin sütun adı varsayılan olarak şuna ayarlanıyor: ID.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--[class jpa.Indemnite] varlık sınıfının takma adı şu şekilde varsayılan olarak ayarlanıyor: Indemnite.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--İş Parçacığı(Thread[main,5,main])--[id] öğesinin sütun adı varsayılan olarak şuna ayarlanıyor: ID.
[EL Config]: 2012-06-22 14:35:01.962--ServerSession(730572764)--Thread(Thread[main,5,main])--Eşleme öğesi [field indemnite] için birincil anahtar sütun adı varsayılan olarak şuna ayarlanıyor: ID.
[EL Info]: 2012-06-22 14:35:02.558--ServerSession(730572764)--İş Parçacığı(Thread[main,5,main])--EclipseLink, sürüm: Eclipse Persistence Services - 2.3.0.v20110604-r9504
[EL Config]: 2012-06-22 14:35:02.568--ServerSession(730572764)--Bağlantı(1543921451)--İş Parçacığı(Thread[main,5,main])--bağlanıyor(DatabaseLogin(
    platform=>MySQLPlatform
    user name=> "root"
    datasource URL=> "jdbc:mysql://localhost:3306/dbpam_eclipselink"
))
[EL Config]: 2012-06-22 14:35:02.738--ServerSession(730572764)--Bağlantı(1296716340)--İş Parçacığı(Thread[main,5,main])--Bağlandı: jdbc:mysql://localhost:3306/dbpam_eclipselink
    User: root@localhost
    Database: MySQL  Version: 5.5.20-log
    Driver: MySQL-AB JDBC Driver  Version: mysql-connector-java-5.1.6 ( Revision: ${svn.Revision} )
[EL Info]: 2012-06-22 14:35:02.798--ServerSession(730572764)--İş Parçacığı (Thread[main,5,main])--file:/D:/data/istia-1112/netbeans/glassfish/mv-pam/05/mv-pam-jpa-eclipselink/target/classes/_pam-jpa-eclipselinkPU oturum açma işlemi başarılı
[EL Fine]: 2012-06-22 14:35:02.818--ServerSession(730572764)--Bağlantı(1296716340)--İş parçacığı(Thread[main,5,main])--ALTER TABLE EMPLOYES DROP FOREIGN KEY FK_EMPLOYES_INDEMNITE_ID
[EL Fine]: 2012-06-22 14:35:03.088--ServerSession(730572764)--Bağlantı(1296716340)--İş Parçacığı(Thread[main,5,main])--DROP TABLE COTISATIONS
[EL Fine]: 2012-06-22 14:35:03.118--ServerSession(730572764)--Bağlantı(1296716340)--İş Parçacığı(Thread[main,5,main])--CREATE TABLE COTISATIONS (ID BIGINT NOT NULL, CSGD DOUBLE NOT NULL, CSGRDS DOUBLE NOT NULL, RETRAITE DOUBLE NOT NULL, SECU DOUBLE NOT NULL, VERSION INTEGER NOT NULL, PRIMARY KEY (ID))
[EL Fine]: 2012-06-22 14:35:03.198--ServerSession(730572764)--Bağlantı(1296716340)--İş Parçacığı(Thread[main,5,main])--DROP TABLE EMPLOYES
[EL Fine]: 2012-06-22 14:35:03.238--ServerSession(730572764)--Bağlantı(1296716340)--İş Parçacığı(Thread[main,5,main])--CREATE TABLE EMPLOYES (ID BIGINT NOT NULL, SS VARCHAR(15) NOT NULL UNIQUE, ADRESSE VARCHAR(50) NOT NULL, CP VARCHAR(5) NOT NULL, NOM VARCHAR(30) NOT NULL, PRENOM VARCHAR(20) NOT NULL, VERSION INTEGER NOT NULL, VILLE VARCHAR(30) NOT NULL, INDEMNITE_ID BIGINT NOT NULL, PRIMARY KEY (ID))
[EL Fine]: 2012-06-22 14:35:03.318--ServerSession(730572764)--Bağlantı(1296716340)--İş Parçacığı(Thread[main,5,main])--DROP TABLE INDEMNITES
[EL Fine]: 2012-06-22 14:35:03.338--ServerSession(730572764)--Bağlantı(1296716340)--İş Parçacığı(Thread[main,5,main])--CREATE TABLE INDEMNITES (ID BIGINT NOT NULL, BASE_HEURE DOUBLE NOT NULL, ENTRETIEN_JOUR DOUBLE NOT NULL, INDEMNITES_CP DOUBLE NOT NULL, INDICE INTEGER NOT NULL UNIQUE, REPAS_JOUR DOUBLE NOT NULL, VERSION INTEGER NOT NULL, PRIMARY KEY (ID))
[EL Fine]: 2012-06-22 14:35:03.418--ServerSession(730572764)--Bağlantı(1296716340)--İş Parçacığı(Thread[main,5,main])--ALTER TABLE EMPLOYES ADD CONSTRAINT FK_EMPLOYES_INDEMNITE_ID FOREIGN KEY (INDEMNITE_ID) REFERENCES INDEMNITES (ID)
[EL Fine]: 2012-06-22 14:35:03.568--ServerSession(730572764)--Bağlantı(1296716340)--İş Parçacığı(Thread[main,5,main])--CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
[EL Fine]: 2012-06-22 14:35:03.578--ServerSession(730572764)--Konu(Konu[main,5,main])--SELECT 1
[EL Warning]: 2012-06-22 14:35:03.578--ServerSession(730572764)--İş Parçacığı(Thread[main,5,main])--İstisna [EclipseLink-4002] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'sequence' already exists
Error Code: 1050
Call: CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
Query: DataModifyQuery(sql="CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))")
[EL Fine]: 2012-06-22 14:35:03.578--ServerSession(730572764)--Bağlantı(1296716340)--İş Parçacığı(Thread[main,5,main])--DELETE FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN
[EL Fine]: 2012-06-22 14:35:03.638--ServerSession(730572764)--Bağlantı(1296716340)--İş Parçacığı(Thread[main,5,main])--SELECT * FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN
[EL Fine]: 2012-06-22 14:35:03.638--ServerSession(730572764)--Bağlantı(1296716340)--İş Parçacığı(Thread[main,5,main])--INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) değerleri (SEQ_GEN, 0)
[EL Config]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Bağlantı(1296716340)--İş parçacığı(Thread[main,5,main])--bağlantı kesildi
[EL Info]: 2012-06-22 14:35:03.748--ServerSession(730572764)--İş Parçacığı(Thread[main,5,main])--file:/D:/data/istia-1112/netbeans/glassfish/mv-pam/05/mv-pam-jpa-eclipselink/target/classes/_pam-jpa-eclipselinkPU oturum kapatma işlemi başarıyla tamamlandı
[EL Config]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Bağlantı(1543921451)--İş Parçacığı(Thread[main,5,main])--bağlantı kesildi
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 3.503s
Finished at: Fri Jun 22 14:35:03 CEST 2012
Final Memory: 8M/153M
  • 26-30. satırlar: MySQL veritabanına bağlantı,
  • satır 31-34: bağlantının başarılı olduğu teyit edildi,
  • 36. satır: [EMPLOYES] tablosundaki yabancı anahtarın silinmesi,
  • 37. satır: [COTISATIONS] tablosunun silinmesi,
  • satır 38: [COTISATIONS] tablosunun oluşturulması. İlginç bir şekilde, ID birincil anahtarının MySQL ve auto_increment özniteliklerine sahip olmadığı görülmektedir. Bu, birincil anahtarın değerlerini oluşturanın MySQL olmadığı anlamına gelir,
  • 39. satır: [EMPLOYES] tablosunun silinmesi,
  • 40. satır: [EMPLOYES] tablosu oluşturuldu. Bu tablonun birincil anahtarı ID, MySQL ve auto_increment özniteliklerine sahip değildir,
  • 41. satır: [INDEMNITES] tablosunun silinmesi,
  • 42. satır: [INDEMNITES] tablosu oluşturuldu. Bu tablonun birincil anahtarı ID, MySQL ve auto_increment özniteliklerine sahip değildir,
  • 43. satır: [EMPLOYES] tablosundan [INDEMNITES] tablosuna yabancı anahtar oluşturuldu,
  • satır 44: [SEQUENCE] tablosu oluşturuldu. Bu tablo, önceki üç tablonun birincil anahtarlarını oluşturmak için kullanılacak,
  • satır 47: Bu tablo zaten mevcut olduğu için bir istisna oluştu,
  • 51-53. satırlar: [SEQUENCE] tablosunun başlatılması.

Oluşturulan tabloların varlığı NetBeans'te [1]'te kontrol edilebilir:

Dolayısıyla, aynı JPA varlıklarından yola çıkıldığında, JPA, Hibernate ve EclipseLink uygulamaları aynı tabloları oluşturmaz. Belgenin devamında, kullanılan JPA uygulaması:

  • Hibernate ise, [dbpam_hibernate] veritabanı kullanılacak,
  • EclipseLink ise [dbpam_eclipselink] veritabanı kullanılacaktır.

5.6.3. Yapılması gereken iş

Daha önce uygulanan adımları takip ederek,

  1. bir [mv-pam-jpa-hibernate-oracle] projesi oluşturun ve test edin; bu projede JPA Hibernate uygulaması ve SGBD Oracle uygulaması kullanılmalıdır,
  2. Hibernate JPA uygulaması ve SGBD SQL sunucusunu kullanan bir [mv-pam-jpa-hibernate-mssql] projesi oluşturun ve test edin,
  3. JPA ve EclipseLink uygulamalarını ve bir SGBD Oracle sunucusunu kullanarak bir [mv-pam-jpa-eclipselink-oracle] projesi oluşturmak ve test etmek,
  4. JPA ve EclipseLink uygulamalarını ve bir SGBD ile SQL sunucusunu kullanarak bir [mv-pam-jpa-eclipselink-mssql] projesi oluşturmak ve test etmek,

5.6.4. Tembel mi, Hevesli mi?

[Employe] varlığının olası bir tanımına geri dönelim:


package jpa;

...

@Entity
@Table(name="EMPLOYES")
public class Employe implements Serializable {
  
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  @Version
  @Column(name="VERSION",nullable=false)
  private int version;
  @Column(name="SS", nullable=false, unique=true, length=15)
  private String SS;
  @Column(name="NOM", nullable=false, length=30)
  private String nom;
  @Column(name="PRENOM", nullable=false, length=20)
  private String prenom;
  @Column(name="ADRESSE", nullable=false, length=50)
  private String adresse;
  @Column(name="VILLE", nullable=false, length=30)
  private String ville;
  @Column(name="CP", nullable=false, length=5)
  private String codePostal;
  @ManyToOne(fetch= FetchType.LAZY)
  @JoinColumn(name="INDEMNITE_ID",nullable=false)
  private Indemnite indemnite;
  ...
}

27-29. satırlar, [EMPLOYES] tablosundan [INDEMNITES] tablosuna giden yabancı anahtarı tanımlar. 27. satırdaki fetch özniteliği, 29. satırdaki indemnite alanının arama stratejisini tanımlar. İki mod vardır:

  • FetchType.LAZY: Bir çalışan arandığında, ona ait tazminat getirilir. Bu tazminat, [Employe].indemnite alanı ilk kez referans alındığında getirilir.
  • FetchType.EAGER: Bir çalışan arandığında, ona ait tazminat getirilir. Bu, herhangi bir mod belirtilmediğinde varsayılan moddur.

FetchType.LAZY seçeneğinin yararını anlamak için aşağıdaki örneği ele alabiliriz. [Details] bağlantısı içeren bir web sayfasında, tazminatları içermeyen bir çalışan listesi gösterilir. Bu bağlantıya tıklandığında, seçilen çalışanın tazminatları görüntülenir. Şu durum görülür:

  • İlk sayfayı görüntülemek için çalışanların ek ödemeleriyle birlikte verilmesine gerek yoktur. Bu durumda FetchType.LAZY modu uygundur;
  • ayrıntıları içeren ikinci sayfayı görüntülemek için, seçilen çalışanın ek ödemelerini almak üzere veritabanına ek bir sorgu gönderilmesi gerekir.

FetchType.LAZY modu, uygulamanın hemen ihtiyaç duymadığı fazla verinin getirilmesini önler. Bir örneğe bakalım.

[mv-pam-jpa-hibernate] projesi kopyalanır:

  • [1]'e kopyalanır;
  • [2]'te kopyanın bulunduğu klasör belirtilir ve [3]'te adı belirtilir,
  • [4]'te yeni proje eskisiyle aynı adı taşıyor. Bunu değiştiriyoruz:
  • [1] olarak değiştirip projeyi yeniden adlandırıyoruz,
  • [2] olarak projenin adını değiştiriyoruz ve artifactId,
  • yeni projeyi [3] olarak yeniden adlandırıyoruz.

[Main.java] programını şu şekilde değiştiriyoruz:


package main;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import jpa.Employe;

public class Main {

  // Aşağıdaki JPQL sorgusu bir çalışanı döndürüyor
  // [Employe].indemnite yabancı anahtarı FetchType.LAZY'tedir
  public static void main(String[] args) {
    // Entity Manager'ı oluşturmak, JPA katmanını oluşturmak için yeterlidir
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("pam-jpa-hibernatePU");
    // ilk deneme
    EntityManager em = emf.createEntityManager();
    Employe employe = (Employe) em.createQuery("select e from Employe e where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
    em.close();
    // çalışanı görüntülüyoruz
    try {
      System.out.println(employe);
    } catch (Exception ex) {
      System.out.println(ex);
    }
    // ikinci deneme
    em = emf.createEntityManager();
    employe = (Employe) em.createQuery("select e from Employe e left join fetch e.indemnite where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
    // kaynakları serbest bırak
    em.close();
    // çalışanı görüntüleme
    try {
      System.out.println(employe);
    } catch (Exception ex) {
      System.out.println(ex);
    }
    // kaynakların serbest bırakılması
    emf.close();
  }
}
  • 15. satır: JPA katmanından EntityManagerFactory'i oluşturuyoruz,
  • 17. satır: JPA katmanıyla iletişim kurmamızı sağlayan EntityManager'i elde ediyoruz,
  • 18. satır: Jouveinal adlı çalışanı talep ediyoruz,
  • 19. satır: EntityManager'i kapatıyoruz. Bu işlem, kalıcılık bağlamını kapatır.
  • 22. satır: Alınan çalışanı görüntülüyoruz.

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


package jpa;

...

@Entity
@Table(name="EMPLOYES")
public class Employe implements Serializable {
  
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  @Version
  @Column(name="VERSION",nullable=false)
  private int version;
  @Column(name="SS", nullable=false, unique=true, length=15)
  private String SS;
  @Column(name="NOM", nullable=false, length=30)
  private String nom;
  @Column(name="PRENOM", nullable=false, length=20)
  private String prenom;
  @Column(name="ADRESSE", nullable=false, length=50)
  private String adresse;
  @Column(name="VILLE", nullable=false, length=30)
  private String ville;
  @Column(name="CP", nullable=false, length=5)
  private String codePostal;
  @ManyToOne(fetch= FetchType.LAZY)
  @JoinColumn(name="INDEMNITE_ID",nullable=false)
  private Indemnite indemnite;
  
  
  /**
   * Returns a string representation of the object.  This implementation constructs
   * that representation based on the id fields.
   * @return a string representation of the object.
   */
  @Override
  public String toString() {
    return "jpa.Employe[id=" + getId()
    + ",version="+getVersion()
    +",SS="+getSS()
    + ",nom="+getNom()
    + ",prenom="+getPrenom()
    + ",adresse="+getAdresse()
    +",ville="+getVille()
    +",code postal="+getCodePostal()
    +",indice="+getIndemnite().getIndice()
    +"]";
  }
  ...
}
  • 27. satır: indemnite alanı, LAZY moduna geri döndürülür,
  • satır 47: indemnite alanı kullanılır. indemnite alanı henüz geri döndürülmemişken toString yöntemi çağrılırsa, o anda geri döndürülür. Örnekte olduğu gibi kalıcılık bağlamı kapatılmamışsa tabii.

[Main] koduna geri dönelim:

  • 21-25. satırlar: burada bir istisna oluşması gerekir. Nitekim, toString yöntemi çağrılacaktır. Bu yöntem, indemnite alanını kullanacaktır. Bu alan aranacaktır. Kalıcılık bağlamı kapatıldığı için, geri getirilen [Employe] varlığı artık mevcut değildir; bu da istisnaya neden olur.
  • 27. satır: yeni bir EntityManager oluşturulur,
  • 28. satır: Jouveinal çalışanını, JPQL sorgusunda ilgili tazminatı açıkça belirterek talep ediyoruz. Bu açık talep gereklidir çünkü bu tazminatın arama modu LAZY'tir,
  • 30. satır: EntityManager kapatılır,
  • 32-36. satırlar: Çalışan yeniden görüntülenir. Herhangi bir istisna olmamalıdır.

Projeyi çalıştırmak için doldurulmuş bir veritabanına ihtiyaç vardır. Bu veritabanı, 5.5. paragrafındaki adımlar izlenerek oluşturulacaktır. Ayrıca, [persistence.xml] dosyası değiştirilmelidir:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
    </properties>
  </persistence-unit>
</persistence>
  • Tabloları oluşturan seçenek kaldırılmıştır. Buradaki veritabanı zaten mevcuttur ve verilerle doldurulmuştur,
  • Hibernate'in veritabanına gönderdiği SQL komutlarını günlüğe kaydeden seçenekler kaldırılmıştır.

Projenin çalıştırılması sonucunda konsolda aşağıdaki iki çıktı görüntülenir:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session
jpa.Employe[id=31,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]
  • 1. satır: oturum kapalıyken eksik olan tazminatın aranması gerektiğinde ortaya çıkan istisna. LAZY modu nedeniyle tazminatın getirilmediğini görüyoruz,
  • 2. satır: LAZY modunu atlayan bir sorgu ile elde edilen tazminat bilgisine sahip çalışan.

5.6.5. Yapılması gereken iş

Az önce izlenen yaklaşıma benzer bir şekilde, [mv-pam-pa-eclipselink-lazy] adlı bir proje oluşturun; bu proje, EclipseLink'in LAZY moduna karşı nasıl davrandığını göstermelidir.

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

jpa.Employe[id=453,version=1,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]
jpa.Employe[id=453,version=1,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]

LAZY modunda, her iki sorgu da tazminatı çalışanla birlikte geri getirmiştir. İnternette bu tutarsızlık hakkında araştırma yapıldığında, [FetchType.LAZY] notunun (1. satır):


  @ManyToOne(fetch= FetchType.LAZY)
  @JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;

bir emir değil, bir istek olduğu ortaya çıkıyor. JPA uygulayıcısı bunu uygulamak zorunda değildir. Dolayısıyla, kodun bazen kullanılan JPA uygulamasına bağlı hale geldiği görülmektedir. EclipseLink'e, LAZY modu için beklenen davranışı yapılandırma yoluyla kazandırmak mümkündür.

5.6.6. Devamı için

Oluşturulacak uygulamanın mimarisi şu şekildedir:

Belgenin devamında, [mv-pam-jpa-hibernate] adlı Maven projesini [mv-pam-spring-hibernate] ve [1, 2, 3] projelerine kopyalayacağız:

  • ardından yeni projenin adını [4, 5, 6] olarak değiştireceğiz.

Yeni projenin bağımlılıklarını değiştireceğiz. [pom.xml] dosyası şu şekilde olur:


<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</groupId>
  <artifactId>mv-pam-spring-hibernate</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mv-pam-spring-hibernate</name>
  <url>http://maven.apache.org</url>
  <repositories>
    <repository>
      <url>http://repo1.maven.org/maven2/</url>
      <id>swing-layout</id>
      <layout>default</layout>
      <name>Repository for library Library[swing-layout]</name>
    </repository>
  </repositories>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>commons-pool</groupId>
      <artifactId>commons-pool</artifactId>
      <version>1.6</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.1.2</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
    <dependency>
      <groupId>org.swinglabs</groupId>
      <artifactId>swing-layout</artifactId>
      <version>1.0.3</version>
    </dependency>
  </dependencies>
</project>
  • 25-31. satırlar: JUnit testleri için bağımlılık,
  • 32-41. satırlar: Apache bağlantı havuzu DBCP'e ilişkin bağımlılıklar,
  • 42-65. satırlar: Spring çerçevesine ilişkin bağımlılıklar,
  • satır 67-71: JPA / Hibernate uygulaması için bağımlılıklar,
  • 72-76. satırlar: MySQL için JDBC sürücüsüne ait bağımlılık,
  • 77-81. satırlar: Swing arayüzü için bağımlılık. Bu bağımlılık, projeye bir Swing arayüzü eklendiğinde NetBeans tarafından otomatik olarak eklenir.

Ayrıca, MySQL adlı iki veritabanı oluşturulacaktır:

  • [dbpam_hibernate], [dbpam_hibernate.sql] komut dosyasından,
  • [dbpam_eclipselink], [dbpam_eclipselink.sql] komut dosyasından,

5.7. , [metier] ve [DAO] katmanlarının arayüzleridir

Uygulamanın mimarisine geri dönelim:

Yukarıdaki mimaride, [DAO] katmanı [metier] katmanına hangi arayüzü sunmalıdır ve [metier] katmanı [ui] katmanına hangi arayüzü sunmalıdır? Farklı katmanların arayüzlerini tanımlamak için ilk yaklaşım, uygulamanın farklı kullanım senaryolarını (use cases) incelemektir. Burada, seçilen kullanıcı arayüzüne göre iki kullanım senaryosu vardır: konsol veya grafik form.

Konsol uygulamasının kullanım şeklini inceleyelim:

dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150 20

Valeurs saisies :
N° de sécurité sociale de l'employé : 254104940426058
Nombre d'heures travaillées : 150
Nombre de jours travaillés : 20

Informations Employé :
Nom : Jouveinal
...

Informations Cotisations :
CSGRDS : 3.49 %
...

Informations Indemnités :
...

Informations Salaire :
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnités d'entretien : 42.0 euro
Indemnités de repas : 62.0 euro
Salaire net : 368.77 euro

Uygulama, kullanıcıdan üç bilgi alır (yukarıdaki 1. satıra bakın)

  • çocuk bakıcısının sosyal güvenlik numarası
  • ay içinde çalışılan saat sayısı
  • ay içinde çalışılan gün sayısı

Bu bilgiler ve yapılandırma dosyalarında kayıtlı diğer bilgilerden yola çıkarak, uygulama aşağıdaki bilgileri görüntüler:

  • 4-6. satırlar: girilen değerler
  • satır 8-10: sosyal güvenlik numarası girilen çalışanla ilgili bilgiler
  • 12-14. satırlar: çeşitli sosyal güvenlik prim oranları
  • 16-17. satırlar: çocuk bakıcısına ödenen çeşitli ödenekler
  • satır 19-24: çocuk bakıcısının maaş bordrosundaki kalemler

[metier] katmanı tarafından [ui] katmanına bir dizi bilgi sağlanmalıdır:

  1. Sosyal güvenlik numarasıyla tanımlanan bir çocuk bakıcısına ilişkin bilgiler. Bu bilgiler [EMPLOYES] tablosunda bulunur. Bu sayede 6-8. satırlar görüntülenebilir.
  2. brüt maaştan kesilecek çeşitli sosyal güvenlik prim oranlarının tutarları. Bu bilgiler [COTISATIONS] tablosunda bulunur. Bu sayede 10-12. satırlar görüntülenebilir.
  3. çocuk bakıcılığı göreviyle ilgili çeşitli ödeneklerin tutarları. Bu bilgiler [INDEMNITES] tablosunda bulunur. Bu sayede 14-15. satırlar görüntülenebilir.
  4. Maaşın bileşenleri, 18-22. satırlarda görüntülenir.

Buna göre, [metier] katmanı tarafından [ui] katmanına sunulan [IMetier] arayüzüne ilişkin ilk kayıt şu şekilde belirlenebilir:

1
2
3
4
5
6
package metier;

public interface IMetier {
   // maaş bordrosunu almak
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
}
  • 1. satır: [metier] katmanındaki öğeler, [metier] paketine aktarılır
  • 5. satır: [ calculerFeuilleSalaire ] yöntemi, [ui] katmanı tarafından elde edilen üç bilgiyi parametre olarak alır ve [ui] katmanının konsolda görüntüleyeceği bilgileri içeren [FeuilleSalaire] türünde bir nesne döndürür. [FeuilleSalaire] sınıfı şu şekilde olabilir:
package metier;

import jpa.Cotisation;
import jpa.Employe;
import jpa.Indemnite;

public class FeuilleSalaire {
   // özel alanlar
  private Employe employe;
  private Cotisation cotisation;
  private ElementsSalaire elementsSalaire;

  ...
}
  • 9. satır: maaş bordrosu ile ilgili çalışan - [ui] katmanı tarafından görüntülenen 1 numaralı bilgi
  • 10. satır: çeşitli katkı payı oranları - [ui] katmanı tarafından görüntülenen 2 numaralı bilgi
  • 11. satır: Çalışanın endeksine bağlı çeşitli ödenekler – [ui] katmanı tarafından görüntülenen 3 numaralı bilgi
  • satır 12: maaşını oluşturan unsurlar - [ui] katmanı tarafından görüntülenen bilgi no. 4

[métier] katmanının ikinci bir kullanım örneği, grafik arayüzde ortaya çıkar:

Yukarıda görüldüğü gibi, [1, 2] açılır listesi tüm çalışanları göstermektedir. Bu liste, [métier] katmanından talep edilmelidir. arayüzü bu durumda şu şekilde değişir:

package metier;

import java.util.List;
import jpa.Employe;

public interface IMetier {
   // maaş bordrosunu al
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // çalışan listesi
  public List<Employe> findAllEmployes();
}
  • [10] satırı: [ui] katmanının, [métier] katmanından tüm çalışanların listesini talep etmesini sağlayacak yöntem.

[metier] katmanı, yukarıdaki [FeuilleSalaire] nesnesinin [Employe, Cotisation, Indemnite] alanlarını ancak [DAO] katmanını sorgulayarak başlatabilir; çünkü bu bilgiler veritabanı tablolarında yer almaktadır. Aynı durum, tüm çalışanların listesini almak için de geçerlidir. Üç [Employe, Cotisation, Indemnite] varlığına erişimi yöneten tek bir [DAO] arayüzü oluşturulabilir. Ancak burada, her varlık için ayrı bir [DAO] arayüzü oluşturmaya karar verdik.

[COTISATIONS] tablosundaki [Cotisation] varlıklarına erişim için [DAO] arayüzü şu şekilde olacaktır:

package dao;

import java.util.List;
import jpa.Cotisation;

public interface ICotisationDao {
       // yeni katkı payı oluştur
  public Cotisation create(Cotisation cotisation);
       // mevcut bir katkı payını düzenle
  public Cotisation edit(Cotisation cotisation);
       // mevcut bir katkı payını sil
  public void destroy(Cotisation cotisation);
       // belirli bir katkı payını arama
  public Cotisation find(Long id);
       // tüm Katkı nesnelerini al
  public List<Cotisation> findAll();

}
  • 6. satırda, [ICotisationDao] arayüzü, [Cotisation] varlığına ve dolayısıyla veritabanındaki [COTISATIONS] tablosuna erişimi yönetir. Uygulamamızın ihtiyacı olan tek şey, 16. satırdaki [findAll] yöntemidir; bu yöntem, [COTISATIONS] tablosunun tüm içeriğini almayı sağlar. Burada, varlık üzerinde tüm CRUD işlemleri (Oluşturma, Okuma, Güncelleme, Silme) gerçekleştirilen daha genel bir durumu ele almak istedik.
  • 8. satır: [create] yöntemi, yeni bir [Cotisation] varlığı oluşturur
  • 10. satır: [edit] yöntemi, mevcut bir [Cotisation] varlığını değiştirir
  • 12. satır: [destroy] yöntemi, mevcut bir [Cotisation] varlığını siler
  • satır 14: [find] yöntemi, id tanımlayıcısı aracılığıyla mevcut bir [Cotisation] varlığını bulmaya olanak tanır
  • 16. satır: [findAll] yöntemi, mevcut tüm [Cotisation] varlıklarını bir liste halinde döndürür

[create] yönteminin imzasına biraz daha yakından bakalım:

       // yeni bir katkı payı oluştur
Cotisation create(Cotisation cotisation);

create yöntemi, Cotisation türünde bir cotisation parametresine sahiptir. cotisation parametresi kalıcı hale getirilmelidir; c.a.d burada [COTISATIONS] tablosuna kaydedilir. Bu kalıcı hale getirilme işleminden önce, cotisation parametresinin, değeri olmayan id kimliği vardır. Kalıcı hale getirilme işleminden sonra, id alanı, [COTISATIONS] tablosuna eklenen kaydın birincil anahtarı olan bir değere sahiptir. Dolayısıyla, cotisation parametresi, create yönteminin bir giriş/çıkış parametresidir. create yönteminin, cotisation parametresini de sonuç olarak döndürmesi gerekli görünmemektedir. Çağıran yöntem, [Cotisation cotisation] nesnesine bir referansa sahip olduğundan, bu nesne değiştirilirse, üzerinde bir referansı olduğu için değiştirilmiş nesneye erişebilir. Dolayısıyla, create yönteminin [Cotisation cotisation] nesnesinin id alanına atadığı değeri öğrenebilir. Bu nedenle yöntemin imzası daha basit bir şekilde şöyle olabilir:

       // yeni bir katkı payı oluştur
void create(Cotisation cotisation);

Bir arayüz yazarken, bunun iki farklı bağlamda kullanılabileceğini hatırlamak önemlidir: local ve distant. local bağlamında, çağıran yöntem ve çağrılan yöntem aynı JVM içinde yürütülür:

[metier] katmanı, [DAO] katmanındaki create yöntemini çağırıyorsa, yönteme aktardığı [Cotisation cotisation] parametresine bir referans içerir.

distant bağlamında, çağıran yöntem ve çağrılan yöntem farklı JVM'lerde yürütülür:

Yukarıda, [metier] katmanı JVM 1'de, [DAO] katmanı ise JVM 2'de, iki farklı makinede yürütülür. Bu iki katman birbiriyle doğrudan iletişim kurmaz. Aralarında, [1] iletişim katmanı olarak adlandıracağımız bir katman yer alır. Bu katman, bir [2] gönderim katmanı ve bir [3] alım katmanından oluşur. Geliştiricinin genellikle bu iletişim katmanlarını yazması gerekmez. Bunlar yazılım araçları tarafından otomatik olarak oluşturulur. [metier] katmanı, sanki [DAO] katmanıyla aynı JVM içinde çalışıyormuş gibi yazılır. Dolayısıyla kodda herhangi bir değişiklik yapılmaz.

[metier] katmanı ile [DAO] katmanı arasındaki iletişim mekanizması şöyledir:

  • [metier] katmanı, [DAO] katmanının create yöntemini çağırır ve ona [Cotisation cotisation1] parametresini aktarır
  • bu parametre aslında [2] gönderim katmanına aktarılır. Bu katman, referansını değil, cotisation1 parametresinin değerini ağ üzerinden iletecektir. Bu değerin tam biçimi, kullanılan iletişim protokolüne bağlıdır.
  • [3] alım katmanı bu değeri alacak ve bu değerden yola çıkarak, [metier] katmanı tarafından gönderilen başlangıç parametresinin bir kopyası olan [Cotisation cotisation2] nesnesini yeniden oluşturacaktır. Artık iki farklı JVM'te (içerik açısından) aynı iki nesneye sahibiz: cotisation1 ve cotisation2.
  • Alıcı katman, cotisation2 nesnesini, [DAO] katmanındaki create yöntemine aktaracak ve bu yöntem nesneyi veritabanında kalıcı hale getirecektir. Bu işlemden sonra, cotisation2 nesnesinin id alanı, [COTISATIONS] tablosuna eklenen kaydın birincil anahtarıyla başlatılmıştır. Ancak, [metier] katmanının bir referansını barındıran cotisation1 nesnesi için durum böyle değildir. [metier] katmanının cotisation2 nesnesine bir referans vermesini istiyorsak, bu nesneyi ona göndermemiz gerekir. Bu nedenle, [DAO] katmanındaki create yönteminin imzasını şu şekilde değiştirmemiz gerekir:
       // yeni bir katkı payı oluştur
Cotisation create(Cotisation cotisation);
  • Bu yeni imzayla, create yöntemi, kalıcı nesne cotisation2'i sonuç olarak döndürecektir. Bu sonuç, [DAO] katmanını çağıran alıcı katman [3]'e iletilir. Bu katman, cotisation2 nesnesinin değerini (referansını değil) [2] gönderim katmanına döndürecektir.
  • Gönderim katmanı [2] bu değeri alacak ve bu değerden yola çıkarak, [DAO] katmanındaki create yönteminin döndürdüğü sonucun bir kopyası olan [Cotisation cotisation3] nesnesini yeniden oluşturacaktır.
  • [Cotisation cotisation3] nesnesi, tüm bu mekanizmayı [DAO] katmanındaki create yöntemine yapılan çağrı ile başlatan [metier] katmanındaki yönteme geri verilir. Böylece [metier] katmanı, kalıcılığını talep ettiği [Cotisation cotisation1] nesnesine verilen birincil anahtar değerini öğrenebilir: bu, cotisation3 nesnesinin id alanının değeridir.

Yukarıdaki mimari en yaygın olanı değildir. Aynı JVM içinde [metier] ve [DAO] katmanlarına daha sık rastlanır:

Bu mimaride, sonuçları döndürmesi gerekenler [metier] katmanındaki yöntemlerdir, [DAO] katmanındakiler değil. Bununla birlikte, [DAO] katmanındaki create yönteminin imzası şöyledir:

       // yeni bir katkı oluştur
Cotisation create(Cotisation cotisation);

sayesinde, fiilen kurulan mimari hakkında herhangi bir varsayımda bulunmamıza gerek kalmaz. Seçilen mimari ne olursa olsun (yerel veya uzak) çalışacak imzalar kullanmak, çağrılan bir yöntemin bazı parametrelerini değiştirmesi durumunda:

  • bu parametrelerin de çağrılan yöntemin sonucunun bir parçası olması gerekir
  • çağıran yöntem, çağrılan yönteme ilettiği değiştirilmiş parametre referanslarını değil, çağrılan yöntemin sonucunu kullanmalıdır.

Böylelikle, kodda herhangi bir değişiklik yapmadan locale mimarisinden distante mimarisine geçiş yapma imkânı elde ederiz. Bu bağlamda, [ICotisationDao] arayüzünü yeniden inceleyelim:

package dao;

import java.util.List;
import jpa.Cotisation;

public interface ICotisationDao {
       // yeni bir aidat oluştur
  public Cotisation create(Cotisation cotisation);
       // mevcut bir katkı payını düzenle
  public Cotisation edit(Cotisation cotisation);
       // mevcut bir katkı payını sil
  public void destroy(Cotisation cotisation);
       // belirli bir katkı payını ara
  public Cotisation find(Long id);
       // tüm Katkı nesnelerini al
  public List<Cotisation> findAll();

}
  • 8. satır: create yönteminin durumu işlendi
  • 10. satır: edit yöntemi, [Cotisation cotisation1] parametresini kullanarak, cotisation nesnesiyle aynı birincil anahtara sahip olan [COTISATIONS] tablosundaki kaydı günceller. Sonuç olarak, değiştirilen kaydın görüntüsü olan cotisation2 nesnesini döndürür. cotisation1 parametresi ise değiştirilmez. Yöntem, ister distante ister locale mimarisi kapsamında olsun, sonuç olarak cotisation2'i döndürmelidir.
  • 12. satır: destroy yöntemi, parametre olarak geçirilen cotisation nesnesiyle aynı birincil anahtara sahip olan [COTISATIONS] tablosundaki kaydı siler. Bu kayıt değiştirilmez. Dolayısıyla, döndürülmesi gerekmez.
  • 14. satır: find yönteminin id parametresi, yöntem tarafından değiştirilmez. Sonucun bir parçası olması gerekmez.
  • 16. satır: findAll yönteminin parametresi yoktur. Dolayısıyla bu yöntem incelenmesine gerek yoktur.

Sonuç olarak, create yönteminin imzası, distante mimarisi kapsamında kullanılabilir hale getirilmek üzere uyarlanmalıdır. Yukarıdaki açıklamalar, diğer [DAO] arayüzleri için de geçerlidir. Bunları tekrar etmeyeceğiz ve hem distante hem de locale mimarisi kapsamında kullanılabilen imzaları doğrudan kullanacağız.

[INDEMNITES] tablosundaki [Indemnite] varlıklarına erişim için [DAO] arayüzü şu şekilde olacaktır:

package dao;

import java.util.List;
import jpa.Indemnite;

public interface IIndemniteDao {
     // Tazminat nesnesi oluştur
  public Indemnite create(Indemnite indemnite);
     // bir Tazminat nesnesini düzenle
  public Indemnite edit(Indemnite indemnite);
     // bir Tazminat varlığını sil
  public void destroy(Indemnite indemnite);
     // kimlik numarasıyla bir Tazminat varlığını ara
  public Indemnite find(Long id);
     // tüm Indemnite varlıklarını alma
  public List<Indemnite> findAll();

}
  • 6. satırda, [IIndemniteDao] arayüzü, [Indemnite] varlığına ve dolayısıyla veritabanındaki [INDEMNITES] tablosuna erişimi yönetir. Uygulamamızın, [INDEMNITES] tablosunun tüm içeriğini almayı sağlayan 16. satırdaki [findAll] yöntemine ihtiyacı vardır. Burada, varlık üzerinde tüm CRUD işlemleri (Oluşturma, Okuma, Güncelleme, Silme) gerçekleştirilen daha genel bir durumu ele almak istedik.
  • 8. satır: [create] yöntemi, yeni bir [Indemnite] varlığı oluşturur
  • 10. satır: [edit] yöntemi, mevcut bir [Indemnite] varlığını değiştirir
  • 12. satır: [destroy] yöntemi, mevcut bir [Indemnite] varlığını siler
  • satır 14: [find] yöntemi, id tanımlayıcısı aracılığıyla mevcut bir [Indemnite] varlığını bulmaya olanak tanır
  • 16. satır: [findAll] yöntemi, mevcut tüm [Indemnite] varlıklarını bir liste halinde döndürür

[EMPLOYES] tablosundaki [Employe] varlıklarına erişim için [DAO] arayüzü şu şekilde olacaktır:

package dao;

import java.util.List;
import jpa.Employe;

public interface IEmployeDao {
     // yeni bir Çalışan varlığı oluştur
  public Employe create(Employe employe);
     // Mevcut bir Çalışan varlığını düzenle
  public Employe edit(Employe employe);
     // bir "Employe" varlığını sil
  public void destroy(Employe employe);
     // "id" kimliği ile bir "Çalışan" varlığını ara
  public Employe find(Long id);
     // Bir "Çalışan" varlığını numarasıyla aramak SS
  public Employe find(String SS);
     // tüm "Çalışan" varlıklarını al
  public List<Employe> findAll();
}
  • 6. satırda, [IEmployeDao] arayüzü, [Employe] varlığına ve dolayısıyla veritabanındaki [EMPLOYES] tablosuna erişimi yönetir. Uygulamamızın ihtiyacı olan tek şey, 16. satırdaki [findAll] yöntemidir; bu yöntem, [EMPLOYES] tablosunun tüm içeriğini almayı sağlar. Burada, varlık üzerinde tüm CRUD işlemleri (Oluşturma, Okuma, Güncelleme, Silme) gerçekleştirilen daha genel bir durumu ele almak istedik.
  • 8. satır: [create] yöntemi, yeni bir [Employe] varlığı oluşturur
  • 10. satır: [edit] yöntemi, mevcut bir [Employe] varlığını değiştirir
  • 12. satır: [destroy] yöntemi, mevcut bir [Employe] varlığını siler
  • 14. satır: [find] yöntemi, id kimliği aracılığıyla mevcut bir [Employe] varlığını bulmaya olanak tanır
  • 16. satır: [find(String SS)] yöntemi, [Employe] numaralı mevcut bir varlığı, SS numarası aracılığıyla bulmaya olanak tanır. Bu yöntemin konsol uygulaması için gerekli olduğunu görmüştük.
  • 18. satır: [findAll] yöntemi, mevcut tüm [Employe] varlıklarını bir liste halinde döndürür. Bu yöntemin grafik uygulaması için gerekli olduğunu görmüştük.

5.8. [PamException] sınıfı

[DAO] katmanı, Java'daki API ve JDBC ile birlikte çalışacaktır. Bu API, [SQLException] türünde kontrollü istisnalar tetikler; bu istisnaların iki dezavantajı vardır:

  • bu istisnaları zorunlu olarak try/catch ile yönetmek zorunda olan kodu ağırlaştırırlar.
  • [IDao] arayüzünün yöntem imzalarında "throws SQLException" ifadesiyle bildirilmeleri gerekir. Bu durum, [SQLException] türünden farklı bir kontrollü istisna oluşturan sınıfların bu arayüzü uygulamasına engel olmaktadır.

Bu sorunu gidermek için, [DAO] katmanı yalnızca [PamException] türündeki kontrolsüz istisnaları "yukarıya iletecektir".

  • [JDBC] katmanı, [SQLException] türünde istisnalar atar
  • [JPA] katmanı, kullanılan JPA uygulamasına özgü istisnalar oluşturur
  • [DAO] katmanı, kontrol edilemeyen [PamException] türünde istisnalar oluşturur

Bunun iki sonucu vardır:

  • [metier] katmanı, [DAO] katmanındaki istisnaları try/catch ile yönetmek zorunda kalmayacaktır. Bunları basitçe [ui] katmanına kadar iletebilir.
  • [IDao] arayüzünün yöntemleri, imza alanlarına [PamException] istisnasının türünü eklemek zorunda değildir; bu da, bu arayüzü, başka bir tür kontrol edilemeyen istisna oluşturan sınıflarla uygulayabilme imkânı sağlar.

[PamException] sınıfı, NetBeans projesinin [exception] paketine yerleştirilecektir:

Kodu şu şekildedir:

package exception;

@SuppressWarnings("serial")
public class PamException extends RuntimeException {

   // hata kodu
  private int code;

  public PamException(int code) {
    super();
    this.code = code;
  }

  public PamException(String message, int code) {
    super(message);
    this.code = code;
  }

  public PamException(Throwable cause, int code) {
    super(cause);
    this.code = code;
  }

  public PamException(String message, Throwable cause, int code) {
    super(message, cause);
    this.code = code;
  }

   // getter ve setter

  public int getCode() {
    return code;
  }

  public void setCode(int code) {
    this.code = code;
  }

}
  • 4. satır: [PamException], [RuntimeException]'ten türetilmiştir. Dolayısıyla bu, derleyicinin bizden try/catch ile yönetmemizi veya yöntem imzalarına eklememizi zorunlu kılmadığı bir istisna türüdür. Bu nedenle, [PamException], [IDao] arayüzünün yöntem imzalarında yer almamaktadır. Bu, söz konusu arayüzün, başka bir istisna türü atan bir sınıf tarafından uygulanmasına olanak tanır; tabii ki bu sınıfın da [RuntimeException]'ten türetilmiş olması şartıyla.
  • Oluşabilecek hataları ayırt etmek için 7. satırdaki hata kodu kullanılır. 14., 19. ve 24. satırlardaki üç yapıcı, üst sınıf [RuntimeException]’e ait olanlardır ve bunlara bir parametre eklenmiştir: istisnaya atanacak hata kodu.

Uygulamanın istisnalar açısından işleyişi şu şekilde olacaktır:

  • [DAO] katmanı, karşılaşılan tüm istisnaları [PamException] türünde bir istisna içinde kapsülleyecek ve bunu [métier] katmanına yeniden atacaktır.
  • [métier] katmanı, [DAO] katmanı tarafından atılan istisnaların yukarıya iletilmesine izin verecektir. [métier] katmanı, [DAO] katmanında meydana gelen tüm istisnaları [PamException] türünde bir istisna olarak kapsayacak ve bunu [ui] katmanına yeniden iletecektir.
  • [ui] katmanı, [métier] ve [DAO] katmanlarından gelen tüm istisnaları yakalar. Bu katman, istisnayı konsolda veya grafik kullanıcı arayüzünde görüntülemekle yetinir.

Şimdi sırasıyla [DAO] ve [metier] katmanlarının uygulamasını inceleyelim.

5.9. [PAM] uygulamasının [DAO] katmanı

Aşağıdaki mimari yapısı çerçevesinde ele alacağız:

5.9.1. Uygulama

Önerilen okumalar: [ref1]'in 3.1.3 paragrafı


Soru: Spring / JPA entegrasyonunu kullanarak, [ICotisationDao, IIndemniteDao, IEmployeDao] arayüzlerini uygulayan [CotisationDao, IndemniteDao, EmployeDao] sınıflarını yazınız. Her sınıf yöntemi, olası bir istisnayı yakalayacak ve bunu, yakalanan istisnaya özgü bir hata kodu içeren [PamException] türünde bir istisna içine kapsülleyecektir.


Uygulama sınıfları, [dao] paketinin bir parçası olacaktır:

  

5.9.2. Yapılandırma

Önerilen okuma: [ref1]'in 3.1.5 paragrafı

DAO / JPA entegrasyonu, Spring dosyası [spring-config-dao.xml] ve JPA [persistence.xml] dosyaları ile yapılandırılır:


Soru: Bu iki dosyanın içeriğini yazınız. Kullanılan veritabanının, SQL [dbpam_hibernate.sql] komut dosyası tarafından oluşturulan MySQL5 [dbpam_hibernate] veritabanı olduğu varsayılacaktır. Spring dosyası, aşağıdaki üç bean'i tanımlayacaktır: employeDao (türü EmployeDao), indemniteDao (türü IndemniteDao), CotisationDao türündeki cotisationDao. Ayrıca, kullanılacak JPA uygulaması Hibernate olacaktır.


5.9.3. Testler

Önerilen okumalar: [ref1] belgesinin 3.1.6 ve 3.1.7 paragrafları

[DAO] katmanı yazıldı ve yapılandırıldı; artık bunu test edebiliriz. Test mimarisi şu şekilde olacaktır:

5.9.4. InitDB

[DAO] katmanı için iki test programı oluşturacağız. Bu programlar, NetBeans projesinin [Test Packages] [1] dalındaki [dao] [2] paketine yerleştirilecektir. Bu dal, [Build project] seçeneği ile oluşturulan projeye dahil edilmez; bu sayede, buraya yerleştirdiğimiz test programlarının projenin nihai .jar dosyasına dahil edilmeyeceğinden emin oluruz.

[Test Packages] dalına yerleştirilen sınıflar, [Source Packages] dalında bulunan sınıfların yanı sıra projenin sınıf kütüphanelerini de tanır. Testlerin projeye ait olanlar dışındaki kütüphanelere ihtiyacı varsa, bunlar [Test Libraries] ve [2] dallarında tanımlanmalıdır.

Test sınıfları, JUnit birim test aracını kullanır:

  • [JUnitInitDB] herhangi bir test yapmaz. Veritabanını birkaç kayıtla doldurur ve ardından bunları konsolda görüntüler.
  • [JUnitDao], bir dizi test gerçekleştirir ve sonuçlarını doğrular.

[JUnitInitDB] sınıfının iskeleti şu şekildedir:

package dao;

...

public class JUnitInitDB {

  private IEmployeDao employeDao = null;
  private ICotisationDao cotisationDao = null;
  private IIndemniteDao indemniteDao = null;

  @BeforeClass
  public void init(){
     // uygulama yapılandırması
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
     // katmanlar DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
  }

  @Test
  public void initDB(){
     // veritabanını doldurma
...
     // veritabanının içeriğini görüntüleme
...
  }

  @Before()
  public void clean(){
     // veritabanını boşaltma
...
  }
}
  • [init] yöntemi, test serisi başlamadan önce yürütülür (@BeforeClass anotasyonu). Bu yöntem, [DAO] katmanını örneklendirir.
  • [clean] yöntemi her testten önce çalıştırılır (@Before etiketi). Bu yöntem veritabanını boşaltır.
  • [initDB] yöntemi bir testtir (@Test anotasyonu). Tek test budur. Bir test, Assert.assertCondition onay komutlarını içermelidir. Burada ise hiçbiri olmayacaktır. Dolayısıyla bu yöntem sahte bir testtir. Amacı, veritabanını birkaç satırla doldurmak ve ardından veritabanının içeriğini konsolda görüntülemektir. Burada, [DAO] katmanına ait create ve findAll yöntemleri kullanılmaktadır.

Soru: [JUnitInitDB] sınıfının kodunu tamamlayınız. [ref1]'in 3.1.6 numaralı paragrafındaki örnekten yararlanınız. Kod, 5.1 numaralı paragrafta sunulan içeriği oluşturacaktır.


5.9.5. Uygulam : Testler

Artık [InitDB]'i çalıştırmaya hazırız. SGBD ve MySQL5 örneklerini kullanarak prosedürü açıklayacağız:

  • [1] sınıfları, [2] yapılandırma dosyaları ve [DAO] ile [3] katman test sınıfları kuruldu,
  • proje derlenir [4]
  • [JUnitInitDB] sınıfı çalıştırılır ([5]). SGBD ve MySQL5, mevcut bir [dbpam_hibernate] veritabanıyla başlatılır,
  • [Test Results] ve [6] pencereleri, testlerin başarılı olduğunu bildiriyor. Bu mesaj burada anlamlı değildir, çünkü [JUnitInitDB] programı, testin başarısız olmasına neden olabilecek herhangi bir Assert.assertCondition doğrulama talimatı içermez. Bununla birlikte, bu durum testin yürütülmesi sırasında herhangi bir istisna oluşmadığını gösterir.

[Output] penceresi, yürütme günlüklerini, Spring günlüklerini ve testin kendisinin günlüklerini içerir. [JUnitInitDB] sınıfı tarafından yapılan çıktılar şunlardır:

------------- Standard Output ---------------
Employés ----------------------
jpa.Employe[id=5,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]
jpa.Employe[id=6,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La brûlerie,ville=St Marcel,code postal=49014,indice=1]
Indemnités ----------------------
jpa.Indemnite[id=5,version=0,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités CP=12.0]
jpa.Indemnite[id=6,version=0,indice=2,base heure=2.1,entretien jour2.1,repas jour=3.1,indemnités CP=15.0]
Cotisations ----------------------
jpa.Cotisation[id=3,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88]
------------- ---------------- ---------------

[EMPLOYES, INDEMNITES, COTISATIONS] tabloları doldurulmuştur. Bunu, NetBeans ile [dbpam_hibernate] veritabanına bağlanarak doğrulayabilirsiniz.

  • [1]'te, [services] sekmesinde, [dbpam_hibernate] [2] bağlantısının [employes] tablosundaki veriler görüntülenir,
  • [3]'te ise sonuç görüntülenir.

5.9.6. JUnitDao

Şimdi ikinci bir test sınıfı olan [JUnitDao] ile ilgileniyoruz:

Sınıfın iskeleti şu şekilde olacaktır:

package dao;

import exception.PamException;
...

public class JUnitDao {

// katmanlar DAO
  static private IEmployeDao employeDao;
  static private IIndemniteDao indemniteDao;
  static private ICotisationDao cotisationDao;

  @BeforeClass
  public static void init() {
     // günlük
    log("init");
     // uygulama yapılandırması
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
     // katmanlar DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
  }

  @AfterClass
  public static void terminate() {
  }

  @Before()
  public void clean() {
...
  }

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

   // testler
  @Test
  public void test01() {
    log("test01");
     // katkı listesi
    List<Cotisation> cotisations = cotisationDao.findAll();
    int nbCotisations = cotisations.size();
     // katkı ekleniyor
    Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
     // talep edildiğinde
    cotisation = cotisationDao.find(cotisation.getId());
     // doğrulama
    Assert.assertNotNull(cotisation);
    Assert.assertEquals(3.49, cotisation.getCsgrds(), 1e-6);
    Assert.assertEquals(6.15, cotisation.getCsgd(), 1e-6);
    Assert.assertEquals(9.39, cotisation.getSecu(), 1e-6);
    Assert.assertEquals(7.88, cotisation.getRetraite(), 1e-6);
     // değiştiriliyor
    cotisation.setCsgrds(-1);
    cotisation.setCsgd(-1);
    cotisation.setRetraite(-1);
    cotisation.setSecu(-1);
    Cotisation cotisation2 = cotisationDao.edit(cotisation);
     // doğrulamalar
    Assert.assertEquals(cotisation.getVersion() + 1, cotisation2.getVersion());
    Assert.assertEquals(-1, cotisation2.getCsgrds(), 1e-6);
    Assert.assertEquals(-1, cotisation2.getCsgd(), 1e-6);
    Assert.assertEquals(-1, cotisation2.getRetraite(), 1e-6);
    Assert.assertEquals(-1, cotisation2.getSecu(), 1e-6);
     // değiştirilen öğe isteniyor
    Cotisation cotisation3 = cotisationDao.find(cotisation2.getId());
     // doğrulamalar
    Assert.assertEquals(cotisation3.getVersion(), cotisation2.getVersion());
    Assert.assertEquals(-1, cotisation3.getCsgrds(), 1e-6);
    Assert.assertEquals(-1, cotisation3.getCsgd(), 1e-6);
    Assert.assertEquals(-1, cotisation3.getRetraite(), 1e-6);
    Assert.assertEquals(-1, cotisation3.getSecu(), 1e-6);
     // öğeyi siliyor
    cotisationDao.destroy(cotisation3);
     // doğrulamalar
    Cotisation cotisation4 = cotisationDao.find(cotisation3.getId());
    Assert.assertNull(cotisation4);
    cotisations = cotisationDao.findAll();
    Assert.assertEquals(nbCotisations, cotisations.size());
  }


  @Test
  public void test02(){
    log("test02");
     // tazminat listesi isteniyor
...
     // bir tazminat ekleniyor
..
     // veritabanından tazminat aranıyor – tazminat1 alınır
..
     // tazminat1 = tazminat olup olmadığını kontrol ediliyor
...
     // alınan tazminatı değiştirilir ve değişiklik BD'e kaydedilir. Tazminat2 elde edilir
 ...
     // tazminat2'nin sürümünü kontrol ediyoruz
    ...
     // veritabanından tazminat2 alınır – tazminat3 elde edilir
    ...
     // indemnite3'ün indemnite2'ye eşit olup olmadığını kontrol ediyoruz
    ...
     // veritabanından indemnite3'ün görüntüsünü siliyoruz
    ...
     // veritabanından indemnite3'ü alıyoruz
    ...
     // null referansı alındığını kontrol ediyoruz
 ...
  }

  @Test
  public void test03(){
    log("test03");
     // Employe için de öncekilerle benzer bir test tekrarlanır
 ...
  }

  @Test
  public void test04(){
    log("test04");
     // [IEmployeDao].find(String SS) yöntemini test ediyoruz
     // önce mevcut bir çalışanla
     // ardından var olmayan bir çalışanla
...
  }

  @Test
  public void test05(){
    log("test05");
     // aynı endeks numarasına sahip iki tazminat kaydı oluşturuyoruz
     // endeksin teklik kısıtlamasını ihlal eder
     // PamException türünde bir istisna oluştuğunu kontrol ediyoruz
     // ve bunun beklenen hata numarasına sahip olup olmadığı kontrol edilir
...
  }

  @Test
  public void test06(){
    log("test06");
     // aynı numaraya sahip iki çalışan oluşturulur: SS
     // numarasıyla iki çalışan oluşturulur; bu, SS
     // PamException türünde bir istisna oluştuğunu kontrol ediyoruz
     // ve bunun beklenen hata numarasına sahip olduğu kontrol ediliyor
...

  }

  @Test
  public void test07(){
    log("test07");
     // aynı SS numarasına sahip iki çalışan oluşturulur; ilki create ile, ikincisi edit ile
     // SS numaralı benzersizlik kısıtlamasını ihlal eder
     // PamException türünde bir istisna oluştuğu doğrulanır
     // ve bunun beklenen hata numarasına sahip olduğu kontrol ediliyor
...
  }

  @Test
  public void test08(){
    log("test08");
     // mevcut olmayan bir çalışanı silmek istisna oluşturmaz
     // çalışan eklenip ardından siliniyor – bunu kontrol ediyoruz
...
  }

  @Test
  public void test09(){
    log("test09");
     // Doğru sürüm olmadan bir çalışanı düzenlemek istisna oluşturmalıdır
     // kontrol ediliyor
...
  }

  @Test
  public void test10(){
    log("test10");
     // Doğru sürüm olmadan bir çalışanı silmek istisna oluşturmalıdır
     // bunu kontrol ediyoruz
...

  }

   // getter ve setter'lar
  ...
}

Önceki test sınıfında, her testten önce veritabanı boşaltılıyordu.


Soru: Aşağıdaki yöntemleri yazınız:

1 - test02: test01'den esinlenilecektir

2 - test03: Bir çalışanın Indemnite türünde bir alanı vardır. Bu nedenle, bir Indemnite varlığı ve bir Employe varlığı oluşturulmalıdır

3 - test04.


[JUnitInitDB] test sınıfında olduğu gibi aynı şekilde ilerlendiğinde, aşağıdaki sonuçlar elde edilir:

  • [1]'te, test sınıfı
  • [2]'te, test sonuçları [Test Results] penceresinde görüntülenir

Bir hata oluşturarak bunun sonuç sayfasında nasıl bildirildiğini görelim:

  @Test
  public void test01() {
    log("test01");
     // katkı payları listesi
    List<Cotisation> cotisations = cotisationDao.findAll();
    int nbCotisations = cotisations.size();
     // bir katkı ekleniyor
    Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
     // talep ediliyor
    cotisation = cotisationDao.find(cotisation.getId());
     // doğrulama
    Assert.assertNotNull(cotisation);
    Assert.assertEquals(0, cotisation.getCsgrds(), 1e-6);
    Assert.assertEquals(6.15, cotisation.getCsgd(), 1e-6);
    Assert.assertEquals(9.39, cotisation.getSecu(), 1e-6);
    Assert.assertEquals(7.88, cotisation.getRetraite(), 1e-6);
     // değiştirme
....
}
  1. satırda, Csgrds değeri 3,49 olduğu için (8. satır) doğrulama işlemi bir hata verecektir. Test sınıfının çalıştırılması şu sonuçları verir:
  • [1] sonuç sayfası artık başarısız testler olduğunu gösteriyor.
  • [2]'te, testin başarısız olmasına neden olan istisnanın bir özeti yer almaktadır. Burada, istisnanın meydana geldiği Java kod satırının numarası bulunur.

5.10. [PAM] uygulamasının [metier] katmanı

[DAO] katmanı yazıldığına göre, şimdi [2] iş katmanını incelemeye geçiyoruz:

5.10.1. [IMetier] Java arayüzü

Bu arayüz 5.7. paragrafında açıklanmıştı. Aşağıda özetini veriyoruz:

package metier;

import java.util.List;
import jpa.Employe;

public interface IMetier {
   // maaş bordrosunu alma
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // çalışan listesi
  public List<Employe> findAllEmployes();
}

[metier] katmanının uygulaması, [metier] paketinde gerçekleştirilecektir:

 

[metier] paketi, [IMetier] arayüzü ve bunun [Metier] uygulaması dışında, [FeuilleSalaire] ve [ElementsSalaire] adlı iki sınıfı da içerecektir. [FeuilleSalaire] sınıfı, 5.7. paragrafta kısaca tanıtılmıştı. Şimdi bu konuya tekrar dönelim.

5.10.2. [FeuilleSalaire] sınıfı

[IMetier] arayüzündeki [calculerFeuilleSalaire] yöntemi, bir maaş bordrosundaki çeşitli öğeleri temsil eden [FeuilleSalaire] türünde bir nesne döndürür. Tanımı şöyledir:

package metier;

import jpa.Cotisation;
import jpa.Employe;
import jpa.Indemnite;

public class FeuilleSalaire implements Serializable{
   // özel alanlar
  private Employe employe;
  private Cotisation cotisation;
  private ElementsSalaire elementsSalaire;

   // oluşturucular
  public FeuilleSalaire() {

  }

  public FeuilleSalaire(Employe employe, Cotisation cotisation, ElementsSalaire elementsSalaire) {
    setEmploye(employe);
    setCotisation(cotisation);
    setElementsSalaire(elementsSalaire);
  }

   // toString
  public String toString() {
    return "[" + employe + "," + cotisation + "," + elementsSalaire + "]";
  }

   // erişimciler
...  
}
  • 7. satır: Sınıf, örnekleri ağ üzerinden aktarılabileceğinden Serializable arayüzünü uygular.
  • 9. satır: maaş bordrosunda adı geçen çalışan
  • 10. satır: çeşitli prim oranları
  • 11. satır: Çalışanın endeksine bağlı çeşitli ödenekler
  • 12. satır: maaşını oluşturan unsurlar
  • satır 14-22: sınıfın iki oluşturucusu
  • satır 25-27: belirli bir [FeuilleSalaire] nesnesini tanımlayan [toString] yöntemi
  • 29. satır ve sonrası: Sınıfın özel alanlarına erişim sağlayan kamu erişimcileri

Yukarıdaki [FeuilleSalaire] sınıfının 11. satırında referans verilen [ElementsSalaire] sınıfı, bir maaş bordrosunu oluşturan unsurları bir araya getirir. Tanımı şöyledir:

package metier;

public class ElementsSalaire implements Serializable{

   // özel alanlar
  private double salaireBase;
  private double cotisationsSociales;
  private double indemnitesEntretien;
  private double indemnitesRepas;
  private double salaireNet;

   // üreticiler
  public ElementsSalaire() {

  }

  public ElementsSalaire(double salaireBase, double cotisationsSociales,
    double indemnitesEntretien, double indemnitesRepas,
    double salaireNet) {
    setSalaireBase(salaireBase);
    setCotisationsSociales(cotisationsSociales);
    setIndemnitesEntretien(indemnitesEntretien);
    setIndemnitesRepas(indemnitesRepas);
  }

   // toString
  public String toString() {
    return "[salaire base=" + salaireBase + ",cotisations sociales=" + cotisationsSociales + ",indemnités d'entretien="
      + indemnitesEntretien + ",indemnités de repas=" + indemnitesRepas + ",salaire net="
      + salaireNet + "]";
  }

   // genel erişimciler
...  
}
  • 3. satır: Sınıf, serileştirilebilir olması gereken FeuilleSalaire sınıfının bir bileşeni olduğu için Serializable arayüzünü uygular.
  • 6. satır: temel maaş
  • 7. satır: bu temel maaş üzerinden ödenen sosyal güvenlik primleri
  • 8. satır: Çocuk bakım günlük ödenekleri
  • 9. satır: Çocuğun günlük yemek yardımı
  • 10. satır: çocuk bakıcısına ödenecek net maaş
  • satır 12-24: sınıf oluşturucular
  • satır 27-31: belirli bir [ElementsSalaire] nesnesini tanımlayan [toString] yöntemi
  • satır 34 ve sonrası: sınıfın özel alanlarına erişim sağlayan genel erişim yöntemleri

5.10.3. [metier] katmanının [Metier] uygulama sınıfı

[metier] katmanının [Metier] uygulama sınıfı şu şekilde olabilir:

package metier;

...

@Transactional
public class Metier implements IMetier {

   // katman referansı [DAO]
  private ICotisationDao cotisationDao = null;
  private IEmployeDao employeDao=null;


   // maaş bordrosunu almak
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
    double nbHeuresTravaillées, int nbJoursTravaillés) {
...
  }

   // çalışan listesi
   public List<Employe> findAllEmployes() {
     ...
  }

   // alıcı ve ayarlayıcılar
...
 }
  • 5. satır: Spring @Transactional anotasyonu, sınıfın her yönteminin bir işlem içinde gerçekleştirilmesini sağlar.
  • 9-10. satırlar: [Cotisation, Employe, Indemnite] varlıklarının [DAO] katmanlarına yapılan referanslar
  • 14-17. satırlar: [calculerFeuilleSalaire] yöntemi
  • 20-22. satırlar: [findAllEmployes] yöntemi
  • 24. satır ve sonrası: Sınıfın özel alanlarına ait genel erişim yöntemleri

Soru: [findAllEmployes] yönteminin kodunu yazınız.



Soru: [calculerFeuilleSalaire] yönteminin kodunu yazınız.


Aşağıdaki noktalara dikkat edilmelidir:

  • Maaş hesaplama yöntemi 5.2. paragrafında açıklanmıştır.
  • [SS] parametresi hiçbir çalışana karşılık gelmiyorsa ([DAO] katmanı bir null işaretçisi döndürdüyse), yöntem, uygun bir hata koduyla birlikte [PamException] türünde bir istisna oluşturacaktır.

5.10.4. [metier] katmanının testleri

İki test programı oluşturuyoruz:

[3] test sınıfları, projenin [Test Packages] [1] dalındaki [metier] [2] paketinde oluşturulur.

[JUnitMetier_1] sınıfı şu şekilde olabilir:

package metier;

...

public class JUnitMetier_1 {

// iş mantığı katmanı
  private IMetier metier;

  @BeforeClass
  public void init(){
     // günlük
    log("init");
     // uygulama yapılandırması
     // katman örneklendirme [metier]
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
    metier = (IMetier) ctx.getBean("metier");
     // katmanlar DAO
    IEmployeDao employeDao=(IEmployeDao) ctx.getBean("employeDao");
    ICotisationDao cotisationDao=(ICotisationDao) ctx.getBean("cotisationDao");
    IIndemniteDao indemniteDao=(IIndemniteDao) ctx.getBean("indemniteDao");
     // veritabanını boşaltma
    for(Employe employe:employeDao.findAll()){
      employeDao.destroy(employe);
    }
    for(Cotisation cotisation:cotisationDao.findAll()){
      cotisationDao.destroy(cotisation);
    }
    for(Indemnite indemnite : indemniteDao.findAll()){
      indemniteDao.destroy(indemnite);
    }
     // veritabanını doldurma
    Indemnite indemnite1=indemniteDao.create(new Indemnite(1,1.93,2,3,12));
    Indemnite indemnite2=indemniteDao.create(new Indemnite(2,2.1,2.1,3.1,15));
    Employe employe2=employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",indemnite2));
    Employe employe1=employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",indemnite1));
    Cotisation cotisation1=cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
  }

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

   // test
  @Test
  public void test01(){
     // maaş bordrolarının hesaplanması
    System.out.println(metier.calculerFeuilleSalaire("260124402111742",30, 5));
    System.out.println(metier.calculerFeuilleSalaire("254104940426058",150, 20));
    try {
      System.out.println(metier.calculerFeuilleSalaire("xx", 150, 20));
    } catch (PamException ex) {
      System.err.println(String.format("PamException[Code=%d, message=%s]",ex.getCode(), ex.getMessage()));
    }
  }
}

Sınıfta Assert.assertCondition adlı bir onaylama yoktur. Amacımız, maaşları hesaplayıp daha sonra manuel olarak doğrulamaktır. Önceki sınıfın çalıştırılmasıyla elde edilen ekran görüntüsü şöyledir:

1
2
3
4
5
6
7
Testsuite: metier.JUnitMetier_1
----------- init
....
[jpa.Employe[id=22,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La brûlerie,ville=St Marcel,code postal=49014,indice=1],jpa.Cotisation[id=6,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88],jpa.Indemnite[id=29,version=0,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités CP=12.0],[salaire base=64.85,cotisations sociales=17.45,indemnités d'entretien=10.0,indemnités de repas=15.0,salaire net=72.4]]
[jpa.Employe[id=21,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2],jpa.Cotisation[id=6,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88],jpa.Indemnite[id=30,version=0,indice=2,base heure=2.1,entretien jour2.1,repas jour=3.1,indemnités CP=15.0],[salaire base=362.25,cotisations sociales=97.48,indemnités d'entretien=42.0,indemnités de repas=62.0,salaire net=368.77]]
PamException[Code=101, message=L'employé de n°[xx] est introuvable]
Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 3,234 sec
  • 4. satır: Justine Laverti'nin maaş bordrosu
  • 5. satır: Marie Jouveinal'ın maaş bordrosu
  • 6. satır: SS numaralı 'xx' çalışanı bulunmadığı için oluşan istisna.

Soru: [JUnitMetier_1]'in 17. satırı, metier adlı Spring bean'ini kullanmaktadır. Bu bean'in [spring-config-metier-dao.xml] dosyasındaki tanımını veriniz.


[JUnitMetier_2] sınıfı şu şekilde olabilir:

package metier;

...
public class JUnitMetier_2 {

// iş mantığı katmanı
  private IMetier metier;

  @BeforeClass
  public void init(){
...
  }

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

   // test
  @Test
  public void test01(){
...
  }
}

[JUnitMetier_2] sınıfı, [JUnitMetier_1] sınıfının bir kopyasıdır; ancak bu sefer test01 yöntemine doğrulama ifadeleri eklenmiştir.


Soru: test01 yöntemini yazınız.


[JUnitMetier_2] sınıfı çalıştırıldığında, her şey yolunda giderse aşağıdaki sonuçlar elde edilir:

Image

5.11. [PAM] uygulamasının [ui] katmanı – sürüm konsol

[metier] katmanı yazıldığına göre, geriye [ui] ve [1] katmanlarını yazmak kalıyor:

[ui] katmanının iki farklı uygulamasını oluşturacağız: bir console sürümü ve bir grafik sürümü olan swing:

5.11.1. [ui.console.Main] sınıfı

Öncelikle, yukarıdaki [ui.console.Main] sınıfı tarafından uygulanan konsol uygulamasına odaklanacağız. Bu uygulamanın işleyişi 5.3. paragrafında açıklanmıştır. [Main] sınıfının iskeleti şu şekilde olabilir:

package ui.console;

import exception.PamException;
import metier.FeuilleSalaire;
import metier.IMetier;

import java.util.ArrayList;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

  /**
   * @param args
   */
  public static void main(String[] args) {
     // yerel veriler
    final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
     // parametre sayısını kontrol ediyoruz
...
     // hata listesi
    ArrayList erreurs = new ArrayList();
     // ikinci parametre >0 olan bir gerçek sayı olmalıdır
...
     // hata mı?
    if (...) {
      erreurs.add("Le nombre d'heures travaillées [" + args[1]
        + "] est erroné");
    }
     // üçüncü parametre >0 olan bir tamsayı olmalıdır
...
     // hata mı?
    if (...) {
      erreurs.add("Le nombre de jours travaillés [" + args[2]
        + "] est erroné");
    }
     // hatalar mı var?
    if (erreurs.size() != 0) {
      for (int i = 0; i < erreurs.size(); i++) {
        System.err.println(erreurs.get(i));
      }
      return;
    }
     // sorun yok - maaş bordrosu talep edilebilir
    FeuilleSalaire feuilleSalaire = null;
    try {
       // [metier] katmanının örneklenmesi
      ...
       // maaş bordrosu hesaplaması
      ...
    } catch (PamException ex) {
      System.err.println("L'erreur suivante s'est produite : "+ ex.getMessage());
      return;
    } catch (Exception ex) {
      System.err.println("L'erreur suivante s'est produite : "+ ex.toString());
      return;
    }

     // ayrıntılı görüntüleme
    String output = "Valeurs saisies :\n";
    output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
    output += ajouteInfo("Nombre d'heures travaillées", args[1]);
    output += ajouteInfo("Nombre de jours travaillés", args[2]);
    output += ajouteInfo("\nInformations Employé", "");
    output += ajouteInfo("Nom", feuilleSalaire.getEmploye().getNom());
    output += ajouteInfo("Prénom", feuilleSalaire.getEmploye().getPrenom());
    output += ajouteInfo("Adresse", feuilleSalaire.getEmploye().getAdresse());
    output += ajouteInfo("Ville", feuilleSalaire.getEmploye().getVille());
    output += ajouteInfo("Code Postal", feuilleSalaire.getEmploye().getCodePostal());
    output += ajouteInfo("Indice", ""+ feuilleSalaire.getEmploye().getIndemnite().getIndice());
    output += ajouteInfo("\nInformations Cotisations", "");
    output += ajouteInfo("CSGRDS", ""+ feuilleSalaire.getCotisation().getCsgrds() + " %");
    output += ajouteInfo("CSGD", ""+ feuilleSalaire.getCotisation().getCsgd() + " %");
    output += ajouteInfo("Retraite", ""+ feuilleSalaire.getCotisation().getRetraite() + " %");
    output += ajouteInfo("Sécurité sociale", ""+ feuilleSalaire.getCotisation().getSecu() + " %");
    output += ajouteInfo("\nInformations Indemnités", "");
    output += ajouteInfo("Salaire horaire", ""+ feuilleSalaire.getEmploye().getIndemnite().getBaseHeure() + " euro");
    output += ajouteInfo("Entretien/jour", ""+ feuilleSalaire.getEmploye().getIndemnite().getEntretienJour() + " euro");
    output += ajouteInfo("Repas/jour", ""+ feuilleSalaire.getEmploye().getIndemnite().getRepasJour() + " euro");
    output += ajouteInfo("Congés Payés", ""+ feuilleSalaire.getEmploye().getIndemnite().getIndemnitesCP()+ " %");
    output += ajouteInfo("\nInformations Salaire", "");
    output += ajouteInfo("Salaire de base", ""+ feuilleSalaire.getElementsSalaire().getSalaireBase()+ " euro");
    output += ajouteInfo("Cotisations sociales", ""+ feuilleSalaire.getElementsSalaire().getCotisationsSociales()+ " euro");
    output += ajouteInfo("Indemnités d'entretien", ""+ feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+ " euro");
    output += ajouteInfo("Indemnités de repas", ""+ feuilleSalaire.getElementsSalaire().getIndemnitesRepas()+ " euro");
    output += ajouteInfo("Salaire net", ""+ feuilleSalaire.getElementsSalaire().getSalaireNet() + " euro");

    System.out.println(output);
  }

  static String ajouteInfo(String message, String valeur) {
    return message + " : " + valeur + "\n";
  }
}

Soru: Yukarıdaki kodu tamamlayınız.


5.11.2. Çalıştırma

[ui.console.Main] sınıfını çalıştırmak için aşağıdaki adımlar izlenmelidir:

  • [1]'te proje özelliklerini seçin,
  • [2]'te, projenin [Run] özelliğini seçin,
  • [3] düğmesini kullanarak çalıştırılacak sınıfı (ana sınıf olarak adlandırılır) belirleyin,
  • [4] sınıfını seçin,
  • sınıf [5]'te görünür. Bu sınıfın çalışması için üç argümana ihtiyaç vardır (SS numarası, çalışılan saat sayısı, çalışılan gün sayısı). Bu argümanlar [6]'e yerleştirilir,
  • bu işlem tamamlandıktan sonra [7] projesini çalıştırabiliriz. Önceki yapılandırma nedeniyle [ui.console.Main] sınıfı çalıştırılacaktır.

Çalıştırma sonuçları [output] penceresinde görüntülenir:

5.12. [PAM] uygulamasının [ui] katmanı – grafik sürümü

Şimdi [ui] katmanını bir grafik arayüzle uyguluyoruz:

  • [1]'te, grafik arayüzün [PamJFrame] sınıfı
  • [2]: grafik kullanıcı arayüzü

5.12.1. Hızlı bir eğitim

Grafik kullanıcı arayüzünü oluşturmak için şu adımları izleyebilirsiniz:

  • [1]: [1] düğmesiyle yeni bir dosya oluşturulur [New File...]
  • [2]: [Swing GUI Forms], c.a.d dosya kategorisini seçin. Grafik formlar
  • [3]: Tür seçilir [JFrame Form], boş bir form türü
  • [5]: Forma, aynı zamanda bir sınıf olacak şekilde bir ad verilir
  • [6]: Form bir pakete yerleştirilir
  • [8]: Form, proje ağacına eklenir
  • [9]: Forma iki farklı bakış açısı üzerinden erişilebilir: [Design] ve [9] (formun çeşitli bileşenlerini tasarlamaya olanak sağlar), [Source] ve [10 ci-dessous] (formun Java koduna erişim sağlar). Sonuç olarak, bir form da diğerleri gibi bir Java sınıfıdır. [Design] görünümü, formu tasarlamayı kolaylaştırır. [Design] modunda her bileşen eklendiğinde, bunu hesaba katmak için [Source] görünümüne Java kodu eklenir.
  • [11]: Bir form için kullanılabilir Swing bileşenlerinin listesi, [Palette] penceresinde bulunur.
  • [12]: [Inspector] penceresi, form bileşenlerinin ağaç yapısını gösterir. Görsel bir temsili olan bileşenler [JFrame] dalında, diğerleri ise [Other Components] dalında bulunur.
  • [13]'te, tek bir tıklamayla [JLabel] bileşenini seçiyoruz
  • [14]'te, onu [Design] modunda forma bırakıyoruz
  • [15]'te, JLabel'in özelliklerini (metin, yazı tipi) tanımlıyoruz.
  • [16]'te elde edilen sonuç.
  • [17]'te, formun önizlemesi isteniyor
  • [18]'te, sonuç
  • [19]'te, [JLabel1] etiketi, [Inspector] penceresindeki bileşen ağacına eklendi
  • [20] ve [21]: Formun [Source] görünümüne, eklenen JLabel'i yönetmek için Java kodu eklendi.

NetBeans ile form oluşturmaya ilişkin bir eğitim kılavuzu [http://www.netbeans.org/kb/trails/matisse.html] adresinde mevcuttur.

5.12.2. [PamJFrame] grafik kullanıcı arayüzü

Aşağıdaki grafik kullanıcı arayüzünü oluşturacağız:

  • [1]'te, grafik arayüz
  • [2]'te, bileşenlerinin ağaç yapısı: bir JLabel ve altı JPanel konteyneri

JLabel1

JPanel1

JPanel2

JPanel3

JPanel4

JPanel5


Uygulama ödevi: [http://www.netbeans.org/kb/trails/matisse.html] öğreticisini kullanarak önceki grafik kullanıcı arayüzünü oluşturun.


5.12.3. Grafik kullanıcı arayüzündeki olaylar

Önerilen okumalar: [ref2] kitabının [Interfaces graphiques] bölümü.

[jButtonSalaire] düğmesine yapılan tıklamayı işleyeceğiz. Bu olayı işleyen yöntemi oluşturmak için şu adımları izleyebiliriz:

[JButtonSalaire] düğmesine tıklama işleyicisi oluşturulur:

1
2
3
    private void jButtonSalaireActionPerformed(java.awt.event.ActionEvent evt) {
       // TODO işleme kodunuzu buraya ekleyin:
}

Önceki yöntemi [JButtonSalaire] düğmesine tıklamaya bağlayan Java kodu da oluşturulur:

1
2
3
4
5
6
    jButtonSalaire.setText("Salaire");
    jButtonSalaire.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        jButtonSalaireActionPerformed(evt);
      }
});

2-5. satırlar, [jButtonSalaire] düğmesine (2. satır) yapılan tıklamanın (muhtemelen ActionPerformed türünde) [jButtonSalaireActionPerformed] yöntemi (4. satır) tarafından işlenmesi gerektiğini belirtir.

Ayrıca, [jTextFieldHT] giriş alanında gerçekleşen [caretUpdate] olayını (giriş imlecinin yer değiştirmesi) da işleyeceğiz. Bu olayın işleyicisini oluşturmak için, daha önce yaptığımız gibi ilerleyeceğiz:

[jTextFieldHT] giriş alanında [caretUpdate] olayının işleyicisi oluşturulur:

  private void jTextFieldHTCaretUpdate(javax.swing.event.CaretEvent evt) {                                         
 ...
  }

[jTextFieldHT] giriş alanındaki [caretUpdate] olayını önceki yönteme bağlayan Java kodu da oluşturulmuştur:

1
2
3
4
5
    jTextFieldHT.addCaretListener(new javax.swing.event.CaretListener() {
      public void caretUpdate(javax.swing.event.CaretEvent evt) {
        jTextFieldHTCaretUpdate(evt);
      }
});

1-4. satırlar, [jTextFieldHT] düğmesi (1. satır) üzerindeki [caretUpdate] olayı (2. satır) [ jTextFieldHTCaretUpdate] yöntemi (3. satır) tarafından işlenmelidir.

5.12.4. Grafik kullanıcı arayüzünün başlatılması

Uygulamamızın mimarisine geri dönelim:

[ui] katmanı, [metier] katmanına bir referansa ihtiyaç duyar. Bu referansın console uygulamasında nasıl elde edildiğini hatırlayalım:

1
2
3
    // [metier] katmanının örneklenmesi
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
IMetier metier = (IMetier) ctx.getBean("metier");

Grafik uygulamasında da yöntem aynıdır. Uygulama başlatıldığında, yukarıdaki 3. satırdaki [IMetier metier] referansının da başlatılması gerekir. Grafik arayüz için şu anda oluşturulan kod şöyledir:

package ui.swing;

...
public class PamJFrame extends javax.swing.JFrame {

   /** Yeni form oluşturur PamJFrame */
  public PamJFrame() {
    initComponents();
  }

   /** Bu yöntem, oluşturucu içinden çağrılır ve
   * initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is
   * always regenerated by the Form Editor.
   */
   // <editor-fold defaultstate="collapsed" desc=" Oluşturulan Kod ">
  private void initComponents() {
...
  }// </editor-fold>

  private void jTextFieldHTCaretUpdate(javax.swing.event.CaretEvent evt) {                                         
 ...
  }                                        

  private void jButtonSalaireActionPerformed(java.awt.event.ActionEvent evt) {                                               
...
  }                                              

  public static void main(String args[]) {
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        new PamJFrame().setVisible(true);
      }
    });
  }

   // Değişken bildirimleri - değiştirmeyin
  private javax.swing.JButton jButtonSalaire;
...
   // Değişken bildiriminin sonu

}
  • 29-35. satırlar: Uygulamayı başlatan statik [main] yöntemi
  • 32. satır: [PamJFrame] grafik arayüzünün bir örneği oluşturulur ve görünür hale getirilir.
  • 7-9. satırlar: Grafik kullanıcı arayüzünün oluşturucusu.
  • 8. satır: 17. satırda tanımlanan [initComponents] yöntemine çağrı yapılır. Bu yöntem, [Design] modunda yapılan çalışmadan otomatik olarak oluşturulmuştur. Bu yönteme dokunulmamalıdır.
  • 21. satır: [jTextFieldHT] alanında giriş imlecinin hareketini yönetecek yöntem
  • 25. satır: [jButtonSalaire] düğmesine tıklamayı yönetecek yöntem

Önceki koda kendi başlatma kodlarımızı eklemek için şu şekilde ilerleyebiliriz:

  /** Yeni form oluşturur PamJFrame */
  public PamJFrame() {
    initComponents();
    doMyInit();
  }

...

   // örnek değişkenleri
  private IMetier metier=null;
  private List<Employe> employes=null;
  private String[] employesCombo=null;
  private double heuresTravaillées=0;

   // Özel başlatmalar
  public void doMyInit(){
     // bağlam başlatma
    try{
       // [metier] katmanının örneklenmesi
...
     // çalışan listesi
...
    }catch (PamException ex){
     // istisna mesajı [jTextAreaStatus]'e yerleştirilir
...
     // geri dönüş
      return;
    }
     // maaş düğmesi devre dışı
...
     // jScrollPane1 gizlendi
...
     // çalışılan günler döner menüsü
    jSpinnerJT.setModel(new SpinnerNumberModel(0,0,31,1));
     // çalışanlar açılır menüsü
    employesCombo=new String[employes.size()];
    int i=0;
    for(Employe employe : employes){
      employesCombo[i++]=employe.getPrenom()+" "+employe.getNom();
    }
    jComboBoxEmployes.setModel(new DefaultComboBoxModel(employesCombo));
}
  • 4. satır: Kendi başlangıç ayarlamalarımızı yapmak için özel bir yöntem çağrılır. Bu ayarlamalar 10-42. satırlardaki kodda tanımlanmıştır

Soru: Yorumlardan yararlanarak, [doMyInit] prosedürünün kodunu tamamlayınız.


5.12.5. Olay Yöneticileri


Soru: [jTextFieldHTCaretUpdate] yöntemini yazınız. Bu yöntem, [jTextFieldHT] alanında bulunan verinin >=0 olan bir gerçek sayı olmaması durumunda, [jButtonSalaire] düğmesinin devre dışı kalmasını sağlamalıdır.



Soru: [jComboBoxEmployes]'te seçilen çalışanın maaş bordrosunu görüntüleyen [jButtonSalaireActionPerformed] yöntemini yazınız.


5.12.6. Grafik kullanıcı arayüzünün çalıştırılması

Grafik kullanıcı arayüzünü çalıştırmak için, projenin [Run] yapılandırmasını değiştireceğiz:

  • [1] olarak değiştirin ve grafik arayüz sınıfını ekleyin

Proje, yapılandırma dosyaları (persistence.xml, spring-config-metier-dao.xml) ve grafik kullanıcı arayüzünün sınıfı ile birlikte eksiksiz olmalıdır. Projeyi çalıştırmadan önce SGBD hedefini başlatın.

JPA katmanının artık EclipseLink tarafından uygulandığı aşağıdaki mimariyle ilgileniyoruz:

5.13.1. NetBeans projesi

Yeni NetBeans projesi, önceki projenin kopyalanmasıyla elde edilir:

  • [1]'e: Hibernate projesine sağ tıklayın ve Copy
  • [2] düğmesini kullanarak yeni projenin üst klasörünü seçin. Klasörün adı [3] olarak görünür.
  • [4]'te, yeni projeye bir ad verin
  • [5]'te, proje klasörünün adını
  • [1]'te yeni proje oluşturulmuştur. Proje, orijinal projeyle aynı adı taşımaktadır,
  • [2] ve [3]'te, projenin adı [mv-pam-spring-eclipselink] olarak değiştirilir.

Proje, yeni katman JPA / EclipseLink'e uyarlanabilmesi için iki noktada değiştirilmelidir:

  1. [4]'te, Spring yapılandırma dosyaları değiştirilmelidir. Bu dosyalarda aslında JPA katmanının yapılandırması bulunmaktadır.
  2. [5]'te, projenin kütüphaneleri değiştirilmelidir: Hibernate kütüphaneleri, EclipseLink'teki kütüphanelerle değiştirilmelidir.

Sonuncusuyla başlayalım. Yeni proje için [pom.xml] dosyası şu şekilde olacaktır:


<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</groupId>
  <artifactId>mv-pam-spring-eclipselink</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mv-pam-spring-eclipselink</name>
  <url>http://maven.apache.org</url>
  <repositories>
    <repository>
      <url>http://repo1.maven.org/maven2/</url>
      <id>swing-layout</id>
      <layout>default</layout>
      <name>Repository for library Library[swing-layout]</name>
    </repository>
    <repository>
      <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
      <id>eclipselink</id>
      <layout>default</layout>
      <name>Repository for library Library[eclipselink]</name>
    </repository>    
  </repositories>
  
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>commons-pool</groupId>
      <artifactId>commons-pool</artifactId>
      <version>1.6</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>eclipselink</artifactId>
      <version>2.3.0</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>javax.persistence</artifactId>
      <version>2.0.3</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
    <dependency>
      <groupId>org.swinglabs</groupId>
      <artifactId>swing-layout</artifactId>
      <version>1.0.3</version>
    </dependency>
  </dependencies>
</project>
  • 73-82. satırlar: JPA ve EclipseLink uygulamaları için bağımlılıklar,
  • 19-24. satırlar: EclipseLink için Maven deposu.

Spring yapılandırma dosyaları, JPA uygulamasının değiştiğini belirtmek üzere güncellenmelidir. Her iki dosyada da yalnızca JPA katmanını yapılandıran bölüm değişir. Örneğin, [spring-config-metier-dao.xml] dosyasında şunlar yer alır:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

   <!-- uygulama katmanları -->
   <!- - DAO -->
  <bean id="employeDao" class="dao.EmployeDao" />
  <bean id="indemniteDao" class="dao.IndemniteDao" />
  <bean id="cotisationDao" class="dao.CotisationDao" />
   <!-- iş alanı -->
  <bean id="metier" class="metier.Metier">
    <property name="employeDao" ref="employeDao"/>
    <property name="indemniteDao" ref="indemniteDao"/>
    <property name="cotisationDao" ref="cotisationDao"/>  
  </bean>

   <!-- yapılandırma JPA -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <!--
          <property name="showSql" value="true" />
    -->
        <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
        <property name="generateDdl" value="true" />
   <!--
        <property name="generateDdl" value="true" />
        -->
      </bean>
    </property>
    <property name="loadTimeWeaver">
      <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
  </bean>

   <!-- veri kaynağı DBCP -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/dbpam_hibernate" />
    <property name="username" value="root" />
<!--
    <property name="password" value="" />
-->
  </bean>
....  
</beans>

19-36. satırlar, JPA katmanını yapılandırmaktadır. Kullanılan JPA uygulaması Hibernate'dir (22. satır). Ayrıca, hedef veritabanı [dbpam_hibernate]'tir (41. satır).

JPA / EclipseLink uygulamasına geçmek için, yukarıdaki 19-35. satırlar aşağıdaki satırlarla değiştirilir:

   <!-- yapılandırma JPA -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
        <!--
          <property name="showSql" value="true" />
  -->
        <property name="databasePlatform" value="org.eclipse.persistence.platform.database.MySQLPlatform" />
        <!--
        <property name="generateDdl" value="true" />
        -->
      </bean>
    </property>
    <property name="loadTimeWeaver">
      <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
</bean>
  • 5. satır: Kullanılan JPA uygulaması, EclipseLink'tir
  • 9. satır: databasePlatform özelliği, hedef SGBD'i belirler; burada MySQL
  • 11. satır: JPA katmanı örneklendiğinde veritabanı tablolarını oluşturmak için. Burada özellik yorum satırında yer almaktadır.
  • 7. satır: JPA katmanı tarafından gönderilen SQL komutlarını konsolda görüntülemek için. Burada, özellik yorum olarak işaretlenmiştir.

Ayrıca, hedef veritabanı [dbpam_eclipselink] olur (aşağıdaki 4. satır):

1
2
3
4
5
6
7
8
9
<!-- veri kaynağı DBCP -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink" />
    <property name="username" value="root" />
<!--
    <property name="password" value="" />
-->
  </bean>

5.13.2. Testlerin Uygulanması

Uygulamanın tamamını test etmeden önce, JUnit testlerinin yeni JPA uygulamasıyla başarılı olup olmadığını kontrol etmek iyi bir fikirdir. Testlere başlamadan önce, veritabanındaki tabloları sileceğiz. Bunun için NetBeans'in [Runtime] sekmesinde, gerekirse dbpam_eclipselink / MySQL5 veritabanına bir bağlantı oluşturacağız. dbpam_eclipselink / MySQL5 veritabanına bağlandıktan sonra, aşağıda gösterildiği gibi tabloları silebiliriz:

  • [1]: silme işleminden önce
  • [2]: silme işleminden sonra

Bu işlem tamamlandıktan sonra, veritabanını dolduran [DAO]: InitDB katmanında ilk testi gerçekleştirebiliriz. Daha önce silinen tabloların uygulama tarafından yeniden oluşturulabilmesi için, Spring’in JPA / EclipseLink yapılandırmasında şu satırın bulunduğundan emin olunmalıdır:

        <property name="generateDdl" value="true" />

satırının mevcut olduğundan ve yorum satırına alınmadığından emin olunmalıdır.

Projeyi derliyoruz (Build) ve ardından [JUnitInitDB] testini çalıştırıyoruz:

  • [1]'te, InitDB testi çalıştırılır.
  • [2]'te ise test başarısız olur. İstisna, başarısız olan bir test tarafından değil, Spring tarafından tetiklenir.

Nedeni: org.springframework.beans.factory.BeanCreationException: Sınıf yolu kaynağında tanımlanan 'entityManagerFactory' adlı bean oluşturulurken hata oluştu [spring-config-DAO.xml]: init yönteminin çağrılması başarısız oldu; iç içe geçmiş istisna java.lang.IllegalStateException: InstrumentationLoadTimeWeaver'i kullanmak için Java ajanı ile başlatılmalıdır. Spring belgelerine bakınız.

Spring, bir yapılandırma sorunu olduğunu belirtiyor. Mesaj net değil. İstisnanın nedeni, [ref1]'in 3.1.9 numaralı paragrafında açıklanmıştır. Spring / EclipseLink yapılandırmasının çalışması için, uygulamayı çalıştıran JVM'in belirli bir parametre, yani bir Java ajanı ile başlatılması gerekir. Bu parametrenin biçimi şöyledir:

-javaagent:C:\...\spring-agent.jar

[spring-agent.jar], JVM'in Spring / EclipseLink yapılandırmasını yönetmek için ihtiyaç duyduğu Java ajanıdır.

Bir proje çalıştırıldığında, JVM'e argümanlar aktarılabilir:

  • [1]'te, projenin özelliklerine erişilir
  • [2] ile Run'in özelliklerine erişilir
  • [3]'te, -javaagent parametresini JVM'e aktarıyoruz

5.13.3. InitDB

Şimdi, [InitDB]'i yeniden test etmeye hazırız. Bu sefer elde edilen sonuçlar şunlardır:

  • [1]'te test başarılı oldu
  • [2]'te, [Services] sekmesinde, NetBeans'in veritabanıyla olan bağlantısı yenileniyor [dbpam_eclipselink]
  • [3]'te dört tablo oluşturuldu
  • [5]'te, [employes] tablosunun içeriği görüntülenir
  • [6]'te sonuç görüntülenir.

5.13.4. JUnitDao

[JUnitDao] test sınıfının çalıştırılması, JPA / Hibernate uygulamasıyla başarılı olmuş olsa bile başarısız olabilir. Nedenini anlamak için bir örneği inceleyelim.

Test edilen yöntem, aşağıdaki IndemniteDao.create yöntemidir:

package dao;

...
@Transactional(propagation=Propagation.REQUIRED)
public class IndemniteDao implements IIndemniteDao{

  @PersistenceContext
  private EntityManager em;

   // üretici
  public IndemniteDao() {
  }

   // tazminat oluşturma
  public Indemnite create(Indemnite indemnite) {
    try{
      em.persist(indemnite);
    }catch(Throwable th){
      throw new PamException(th,31);
    }
    return indemnite;
  }

...
}
  • 15-22. satırlar: test edilen yöntem

Test yöntemi şöyledir:


package dao;

...

public class JUnitDao {

// katmanlar DAO
  static private IEmployeDao employeDao;
  static private IIndemniteDao indemniteDao;
  static private ICotisationDao cotisationDao;

  @BeforeClass
  public static void init() {
    // günlük
    log("init");
    // uygulama yapılandırması
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-DAO.xml");
    // katmanlar DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
  }

  @Before()
  public void clean() {
    // veritabanını boşaltma
    for (Employe employe : employeDao.findAll()) {
      employeDao.destroy(employe);
    }
    for (Cotisation cotisation : cotisationDao.findAll()) {
      cotisationDao.destroy(cotisation);
    }
    for (Indemnite indemnite : indemniteDao.findAll()) {
      indemniteDao.destroy(indemnite);
    }
  }

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

  // testler
….
  @Test
  public void test05() {
    log("test05");
    // aynı endeksle iki tazminat oluşturuluyor
    // endeksin teklik kuralını ihlal ediyor
    boolean erreur = true;
    Indemnite indemnite1 = null;
    Indemnite indemnite2 = null;
    Throwable th = null;
    try {
      indemnite1 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
      indemnite2 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
      erreur = false;
    } catch (PamException ex) {
      th = ex;
      // denetimler
      Assert.assertEquals(31, ex.getCode());
    } catch (Throwable th1) {
      th = th1;
    }
    // kontroller
    Assert.assertTrue(erreur);
    // istisna dizisi
    System.out.println("Chaîne des exceptions --------------------------------------");
    System.out.println(th.getClass().getName());
    while (th.getCause() != null) {
      th = th.getCause();
      System.out.println(th.getClass().getName());
    }
    // 1. tazminatın kalıcı hale getirilmesi gerekiyordu
    Indemnite indemnite = indemniteDao.find(indemnite1.getId());
    // doğrulama
    Assert.assertNotNull(indemnite);
    Assert.assertEquals(1, indemnite.getIndice());
    Assert.assertEquals(1.93, indemnite.getBaseHeure(), 1e-6);
    Assert.assertEquals(2, indemnite.getEntretienJour(), 1e-6);
    Assert.assertEquals(3, indemnite.getRepasJour(), 1e-6);
    Assert.assertEquals(12, indemnite.getIndemnitesCP(), 1e-6);
    // ikinci tazminat kalıcı hale getirilmemeliydi
    List<Indemnite> indemnites = indemniteDao.findAll();
    int nbIndemnites = indemnites.size();
    Assert.assertEquals(nbIndemnites, 1);
  }

...
}

Soru: test05 testinin ne yaptığını açıklayın ve beklenen sonuçları belirtin.


JPA / Hibernate katmanıyla elde edilen sonuçlar şunlardır:

----------- test05
4 juin 2010 16:45:43 org.hibernate.util.JDBCExceptionReporter logExceptions
ATTENTION: SQL Error: 1062, SQLState: 23000
4 juin 2010 16:45:43 org.hibernate.util.JDBCExceptionReporter logExceptions
GRAVE: Duplicate entry '1' for key 2
Chaîne des exceptions --------------------------------------
exception.PamException
javax.persistence.EntityExistsException
org.hibernate.exception.ConstraintViolationException
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException

Test başarılı oldu, c.a.d. Doğrulamalar kontrol edildi ve test yönteminden herhangi bir istisna çıkmadı.


Soru: Neler olduğunu açıklayın.


JPA / EclipseLink katmanıyla elde edilen sonuçlar şunlardır:

----------- test05
[EL Warning]: 2010-06-04 16:48:26.421--UnitOfWork(749304)--[EclipseLink-4002] istisnası (Eclipse Persistence Services - 2.0.0.v20091127-r5931): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 2
Error Code: 1062
Call: INSERT INTO INDEMNITES (ID, ENTRETIEN_JOUR, REPAS_JOUR, INDICE, INDEMNITES_CP, BASE_HEURE, VERSION) VALUES (?, ?, ?, ?, ?, ?, ?)
        bind => [108, 2.0, 3.0, 1, 12.0, 1.93, 1]
Query: InsertObjectQuery(jpa.Indemnite[id=108,version=1,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités CP=12.0])
Chaîne des exceptions --------------------------------------
org.springframework.transaction.TransactionSystemException
javax.persistence.RollbackException
org.eclipse.persistence.exceptions.DatabaseException
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException

Daha önce Hibernate ile olduğu gibi, test başarılıdır (c.a.d). Doğrulamalar gerçekleştirilmiştir ve test yönteminden herhangi bir istisna çıkmamıştır.


Soru: Neler olduğunu açıklayın.



Soru: Bu iki örnekten, JPA uygulamalarının birbirinin yerine kullanılabilirliği konusunda ne gibi bir sonuç çıkarılabilir? Burada tam bir değiştirilebilirlik söz konusu mu?


5.13.5. Diğer testler

[DAO] katmanı test edilip doğru kabul edildikten sonra, [metier] katmanının testlerine ve projenin konsol veya grafik sürümündeki testlere geçilebilir. JPA uygulamasındaki değişiklik, [metier] ve [ui] katmanlarını hiçbir şekilde etkilemez; dolayısıyla bu katmanlar Hibernate ile çalışıyorsa, EclipseLink ile de çalışacaktır; ancak birkaç istisna dışında: önceki örnek, [DAO] katmanları tarafından atılan istisnaların farklı olabileceğini göstermektedir. Dolayısıyla, testin kullanıldığı durumda, Spring / JPA / Hibernate, [PamException] türünde bir istisna atar; bu, [pam] uygulamasına özgü bir istisnadır; oysa Spring / JPA / EclipseLink ise Spring çerçevesine ait bir istisna olan [TransactionSystemException] türünde bir istisna tetikler. Test kullanım senaryosunda, [ui] katmanı, Hibernate ile oluşturulduğu için [PamException] türünde bir istisna bekliyorsa, EclipseLink'e geçildiğinde artık çalışmayacaktır.

5.13.6. Yapılması gerekenler


Uygulamalı çalışma: console ve swing uygulamalarının testlerini farklı SGBD değerleriyle yeniden yapın: MySQL5, Oracle XE, SQL Sunucusu.