3. Örnek uygulama – 01: rdvmedecins-jsf2-ejb
Aşağıdaki metin, adresindeki aşağıdaki belgelere atıfta bulunmaktadır:
- [ref7]: Java'ya Giriş EE 5 (Haziran 2010) [http://tahe.developpez.com/java/javaee]. Bu belge, JSF 1 ve EJB3'i keşfetmenize olanak tanır.
- [ref8]: Uygulamalı Java Veri Kalıcılığı (Haziran 2007) [http://tahe.developpez.com/java/jpa]. Bu belge, JPA (Java Persistence API) ile veri kalıcılığını keşfetmenizi sağlar.
- [ref9]: NetBeans ve GlassFish sunucusu ile bir Java web hizmeti oluşturma (Ocak 2009) EE. Bu belge, bir web hizmetinin oluşturulmasını incelemektedir.
İncelenecek örnek uygulama [ref9]'ten alınmıştır.
3.1. L'application
Bir bilişim hizmetleri şirketi olan [ISTIA-AGI], randevu alma hizmeti sunmak istemektedir. Hedeflenen ilk pazar, tek başına çalışan doktorlardır. Bu doktorların genellikle sekreterleri yoktur. Randevu almak isteyen müşteriler, doğrudan doktoru ararlar. Bu durum, doktorun gün içinde sık sık rahatsız edilmesine neden olur ve hastalarına ayırabileceği zamanı azaltır. [ISTIA-AGI] şirketi, onlara şu prensiple çalışan bir randevu alma hizmeti sunmak istemektedir:
- Bir sekreterlik ofisi, çok sayıda doktorun randevu alımını üstlenir. Bu sekreterlik ofisi tek bir kişiden oluşabilir. Bu kişinin maaşı, randevu hizmetini kullanan tüm doktorlar arasında paylaştırılır.
- Sekreterlik ve tüm doktorlar İnternet’e bağlıdır
- Randevular, sekreterlik ve doktorlar tarafından internet üzerinden erişilebilen merkezi bir veritabanına kaydedilir
- RV kaydı normalde sekreterlik tarafından yapılır. Ancak doktorlar tarafından da yapılabilir. Bu durum özellikle muayene sonunda doktorun hastasına yeni bir RV numarası vermesi halinde geçerlidir.
RV alma hizmetinin yapısı şu şekildedir:
![]() |
Doktorlar, RV'leri yönetmek zorunda kalmadıkları takdirde verimliliklerini artırırlar. Yeterli sayıda doktor katılırsa, sekreterliğin işletme masraflarına katkıları düşük olacaktır.
[ISTIA-AGI] şirketi, uygulamayı iki sürümde geliştirmeye karar verir:
- bir sürüm JSF / EJB3 / JPA EclipseLink / Glassfish sunucusu:
![]() |
- ve JSF / Spring / JPA Hibernate / Tomcat sunucusu sürümü:
![]() |
3.2. Uygulamanın Çalışması
Uygulamaya [RdvMedecins] adını vereceğiz. Aşağıda uygulamanın işleyişine ait ekran görüntülerini sunuyoruz.
Uygulamanın ana sayfası şöyledir:
![]() |
Kullanıcı (Sekreter, Doktor), bu ilk sayfadan başlayarak bir dizi işlem gerçekleştirecektir. Bunları aşağıda sunuyoruz. Soldaki ekran görüntüsü, kullanıcının talepte bulunduğu ekranı; sağdaki ekran görüntüsü ise sunucu tarafından gönderilen yanıtı göstermektedir.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Son olarak, bir hata sayfası da görüntülenebilir:
![]() |
3.3. Veritabanı
Oluşturulacak uygulamanın mimarisine geri dönelim:
![]() |
[dbrdvmedecins2] olarak adlandıracağımız veritabanı, dört tablo içeren bir MySQL5 veritabanıdır:
![]() |
3.3.1. [MEDECINS] tablosu
Bu, [RdvMedecins] uygulaması tarafından yönetilen doktorlar hakkında bilgiler içerir.
![]() | ![]() |
- ID: doktoru tanımlayan numara - tablonun birincil anahtarı
- VERSION: Tablodaki satırın sürümünü tanımlayan numara. Bu sayı, satırda her değişiklik yapıldığında 1 artırılır.
- NOM: doktorun soyadı
- PRENOM: doktorun adı
- TITRE: unvanı (Bayan, Bayan, Bay)
3.3.2. [CLIENTS] tablosu
Farklı doktorların hastaları [CLIENTS] tablosunda kaydedilir:
![]() | ![]() |
- ID: müşteriyi tanımlayan numara - tablonun birincil anahtarı
- VERSION: Tablodaki satırın sürümünü tanımlayan numara. Bu sayı, satırda her değişiklik yapıldığında 1 artırılır.
- NOM: müşterinin adı
- PRENOM: müşterinin adı
- TITRE: unvanı (Bayan, Bayan, Bay)
3.3.3. [CRENEAUX] tablosu
Bu tablo, RV'lerin mümkün olduğu zaman aralıklarını listeler:
![]() |
![]() |
- ID: zaman aralığını tanımlayan numara - tablonun birincil anahtarı (satır 8)
- VERSION: Tablodaki satırın sürümünü tanımlayan numara. Bu sayı, satırda her değişiklik yapıldığında 1 artırılır.
- ID_MEDECIN: Bu zaman diliminin ait olduğu doktoru tanımlayan numara – MEDECINS (ID) sütunundaki yabancı anahtar.
- HDEBUT: zaman aralığının başlangıç saati
- MDEBUT: zaman aralığının başlangıç dakikası
- HFIN: zaman aralığının bitiş saati
- MFIN: zaman aralığının bitiş dakikası
[CRENEAUX] tablosunun ikinci satırı (yukarıdaki [1]'e bakınız), örneğin, 2 numaralı zaman diliminin saat 08:20'de başlayıp 08:40'ta bittiğini ve 1 numaralı doktora ait olduğunu gösterir (Bayan Marie PELISSIER) ait olduğunu gösterir.
3.3.4. [RV] tablosu
Bu tablo, her doktor için ayrılan RV'leri listeler:
![]() |
- ID: RV'i benzersiz şekilde tanımlayan numara – birincil anahtar
- JOUR: RV'in günü
- ID_CRENEAU: RV'in zaman aralığı – [ID] alanındaki yabancı anahtar ([CRENEAUX] tablosunda) – hem zaman aralığını hem de ilgili doktoru belirler.
- ID_CLIENT: Rezervasyonun yapıldığı müşterinin numarası – [CLIENTS] tablosundaki [ID] alanında yabancı anahtar
Bu tablo, birleştirilen sütunların değerleri (JOUR, ID_CRENEAU) üzerinde tekillik kısıtlaması içeren bir 'e sahiptir:
[RV] tablosundaki bir satırın değeri (JOUR1, ID_CRENEAU1) değerine sahipse, bu değer başka hiçbir yerde bulunamaz. Aksi takdirde, bu durum aynı hekim için aynı anda iki adet RV'in alınmış olduğu anlamına gelir. Java programlama açısından bakıldığında, veritabanındaki JDBC sürücüsü bu durum meydana geldiğinde bir SQLException başlatır.
id satırındaki 3 değeri (yukarıdaki [1]'e bakınız), 23/08/2006 tarihinde 20 numaralı zaman dilimi ve 4 numaralı müşteri için bir RV'in rezerve edildiği anlamına gelir. [CRENEAUX] tablosundan, 20 numaralı randevu aralığının 16:20 - 16:40 saat aralığına karşılık geldiği ve 1 numaralı hekime (Bayan Marie PELISSIER) ait olduğu anlaşılmaktadır. [CLIENTS] tablosundan, 4 numaralı müşterinin Bayan Brigitte BISTROU olduğu anlaşılmaktadır.
3.3.5. Veritabanının Oluşturulması
Tabloları oluşturmak ve doldurmak için örnekler sayfasında bulunan [dbrdvmedecins2.sql] komut dosyasını kullanabilirsiniz. [WampServer] (bkz. paragraf 1.3.3) ile şu şekilde ilerleyebilirsiniz:
![]() |
- [1]'te, [WampServer] simgesine tıklayıp [PhpMyAdmin] [2] seçeneğini seçin,
- [3]'e, açılan pencerede [Bases de données] bağlantısını seçin,
![]() |
- [2]'e, [4] adını ve [5] kodlamasını verdiğimiz bir veritabanı oluştururuz,
- [7]'te veritabanı oluşturulmuştur. Bağlantısına tıklanır,
![]() |
- [8]'te, SQL adlı bir dosyayı içe aktarıyoruz,
- dosya sisteminde [9] düğmesiyle seçilir,
![]() |
- [11]'te, SQL komut dosyasını seçip [12]'te çalıştırdığımızda,
- [13]'te, veritabanındaki dört tablo oluşturulmuştur. Bağlantılardan birini takip ediyoruz,
![]() |
- 'de [14]'te tablonun içeriği görüntülenir.
Bundan sonra bu veritabanına bir daha dönmeyeceğiz. Ancak okuyucunun, özellikle de programlar çalışmadığında, veritabanının gelişimini takip etmesi önerilir.
3.4. [DAO] ve [JPA] katmanları
Şimdi inşa etmemiz gereken mimariye geri dönelim:
![]() |
Dört adet Maven projesi oluşturacağız:
- [DAO] ve [JPA] katmanları için bir proje,
- [métier] katmanı için bir proje,
- [web] katmanı için bir proje,
- önceki üç projeyi bir araya getirecek bir kurumsal proje.
Şimdi [DAO] ve [JPA] katmanları için Maven projesini oluşturuyoruz.
Not: [métier], [DAO] ve [JPA] katmanlarını anlamak için Java EE bilgisi gereklidir. Bunun için [ref7] bölümüne bakabilirsiniz (bkz. 3. paragraf).
3.4.1. NetBeans projesi
Şu şekildedir:
![]() |
- [1]'te, [EJB Module] [2] türünde bir Maven projesi oluşturulur,
- [3]'te projeye bir isim verilir,
![]() |
- [4] adımında, sunucu olarak Glassfish'i seçiyoruz,
- [5]'te, proje oluşturulur.
3.4.2. [JPA] katmanının oluşturulması
Şimdi inşa etmemiz gereken mimariye geri dönelim:
![]() |
NetBeans ile, [JPA] katmanını ve oluşturulan JPA varlıklarına erişimi kontrol eden [EJB] katmanını otomatik olarak oluşturmak mümkündür. Bu otomatik oluşturma yöntemlerini bilmek önemlidir, çünkü oluşturulan kod, JPA varlıklarının veya bunları kullanan EJB kodunun nasıl yazılacağına dair değerli ipuçları verir.
Şimdi bu otomatik oluşturma araçlarından bazılarını açıklayacağız. Oluşturulan kodu anlamak için, JPA, [ref8] ve EJB, [ref7] varlıklarına ilişkin iyi bir bilgiye sahip olmak gerekir (bkz. 3. paragraf).
3.4.2.1. NetBeans'te veritabanına bağlantı oluşturma
- SGBD ve MySQL 5'i çalıştırarak BD'in kullanılabilir hale gelmesini sağlayın,
- [dbrdvmedecins2] veritabanına bir NetBeans bağlantısı oluşturun,
![]() |
- sekmesinde [Services] [1], [Databases] [2] dalında, JDBC MySQL [3] sürücüsünü seçin,
- ardından [4] "Connect Using" seçeneğini seçin; bu, MySQL veritabanıyla bağlantı kurmanızı sağlar,
- [5]'te istenen bilgileri girin. [6]'te veritabanının adını, [7]'te veritabanı kullanıcısını ve şifresini,
- [8]'te girdiğiniz bilgileri test edebilirsiniz,
- [9]'te, bilgiler doğruysa beklenen mesaj,
![]() |
- [10]'te bağlantı kurulur. Burada, bağlanılan veritabanındaki dört tablo görülür.
3.4.2.2. Kalıcılık biriminin oluşturulması
Şu anda oluşturulmakta olan mimariye geri dönelim:
![]() |
Şu anda [JPA] katmanını oluşturuyoruz. Bu katmanın yapılandırması, kalıcılık birimlerinin tanımlandığı [persistence.xml] dosyasında yapılır. Her bir kalıcılık birimi için aşağıdaki bilgiler gereklidir:
- veritabanına erişim özellikleri (JDBC; URL, kullanıcı adı, şifre),
- veritabanı tablolarının görüntülerini oluşturacak sınıflar,
- kullanılan JPA uygulaması. Aslında, JPA, çeşitli ürünler tarafından uygulanan bir spesifikasyondur. Burada, Glassfish sunucusu tarafından varsayılan olarak kullanılan EclipseLink uygulamasını kullanacağız. Bu sayede, Glassfish'e başka bir uygulamanın kütüphanelerini eklememiz gerekmez.
NetBeans, bir sihirbaz kullanarak bu kalıcılık dosyasını oluşturabilir.
![]() |
- projeye sağ tıklayın ve bir [1] kalıcılık birimi oluşturmayı seçin,
- [2]'te, oluşturulan kalıcılık birimine bir ad verin,
- [3]'te, JPA EclipseLink (JPA 2.0) uygulamasını seçin,
- [4]'te, veritabanı işlemlerinin Glassfish sunucusundaki EJB konteyneri tarafından yönetileceğini belirtin,
- [5]'te, BD'teki tabloların zaten oluşturulmuş olduğunu ve bu nedenle bunların yeniden oluşturulmayacağını belirtin,
![]() |
- [6]'te, Glassfish sunucusu için yeni bir veri kaynağı oluşturun,
- [7]'te, JNDI adını verin (Java Naming Directory Interface),
- [8], bu adı bir önceki adımda oluşturulan MySQL bağlantısına bağlayın,
![]() |
- [9] olarak değiştirin, sihirbazı tamamlayın,
- [10] olarak, yeni projeyi,
- [11]'e, [persistence.xml] dosyası [META-INF] klasöründe oluşturuldu,
- [12]'te, [setup] klasörü oluşturuldu,
- [13]'te, Maven projesine yeni bağımlılıklar eklendi.
Oluşturulan [META-INF/persistence.xml] dosyası şöyledir:
<?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="dbrdvmedecins2-PU" transaction-type="JTA">
<jta-data-source>jdbc/dbrdvmedecins2</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties/>
</persistence-unit>
</persistence>
Bu dosya, sihirbazda girilen bilgileri içerir:
- 3. satır: kalıcılık biriminin adı,
- 3. satır: veritabanı işlemlerinin türü; burada, Glassfish sunucusunun EJB3 konteyneri tarafından yönetilen JTA (Java Transaction API) işlemleri,
- 4. satır: veri kaynağının adı JNDI.
Normalde bu dosyada, kullanılan JPA uygulama türü bulunur. Sihirbazda EclipseLink değerini belirttik. Bu, Glassfish sunucusu tarafından varsayılan olarak kullanılan JPA uygulaması olduğundan, [persistence.xml] dosyasında yer almamaktadır.
[Design] sekmesinde, [persistence.xml] dosyasının genel bir görünümünü elde edebiliriz:
![]() |
EclipseLink dosyasının günlüklerini elde etmek için aşağıdaki [persistence.xml] dosyasını kullanacağız:
<?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="dbrdvmedecins2-PU" transaction-type="JTA">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>jdbc/dbrdvmedecins2</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="eclipselink.logging.level" value="FINE"/>
</properties>
</persistence-unit>
</persistence>
- 4. satır: EclipseLink'in JPA uygulamasının kullanıldığını belirtir,
- 7-9. satırlar: JPA sağlayıcısının yapılandırma özelliklerini içerir; burada EclipseLink,
- 8. satır: Bu özellik, EclipseLink tarafından gönderilecek SQL komutlarının günlüğe kaydedilmesini sağlar.
Oluşturulan [glassfish-resources.xml] dosyası şöyledir:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
<jdbc-connection-pool allow-non-component-callers="false" ... steady-pool-size="8" validate-atmost-once-period-in-seconds="0" wrap-jdbc-objects="false">
<property name="serverName" value="localhost"/>
<property name="portNumber" value="3306"/>
<property name="databaseName" value="dbrdvmedecins2"/>
<property name="User" value="root"/>
<property name="Password" value=""/>
<property name="URL" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/>
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
</jdbc-connection-pool>
<jdbc-resource enabled="true" jndi-name="jdbc/dbrdvmedecins2" object-type="user" pool-name="mysql_dbrdvmedecins2_rootPool"/>
</resources>
Bu dosya, daha önce kullandığımız iki sihirbazda girdiğimiz bilgileri içerir:
- 5-11. satırlar: MySQL5 ve [dbrdvmedecins2] veritabanlarının JDBC özellikleri,
- 13. satır: JNDI veri kaynağının adı.
Bu dosya, Glassfish sunucusundaki JNDI ve [jdbc/dbrdvmedecins2] veri kaynaklarını oluşturmak için kullanılacak. Bu işlem tamamen söz konusu sunucuya özeldir. Başka bir sunucu için farklı bir yöntem izlenmesi gerekir; genellikle bir yönetim aracı kullanılır. Bu araç Glassfish için de mevcuttur.
Son olarak, projeye bağımlılıklar eklendi. [pom.xml] dosyası şu şekildedir:
<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-rdvmedecins-ejb-dao-jpa</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>ejb</packaging>
<name>mv-rdvmedecins-ejb-dao-jpa</name>
...
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>2.0.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa.modelgen.processor</artifactId>
<version>2.3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
...
<repositories>
<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>
</project>
- 32-37. satırlarda, bir [JPA] katmanı, [javaee-api] artefaktını gerektirir;
- 16., 22. ve 28. satırlarda, burada kullanılan JPA / EclipseLink uygulaması için gerekli olan artefaktlar belirtilmiştir.
- satır 18, 24, 30, 36: tüm artefaktlar provided özniteliğine sahiptir. Bunun, derleme için gerekli oldukları ancak yürütme için gerekli olmadıkları anlamına geldiğini hatırlatırız. Nitekim yürütme sırasında bunlar (provided) Glassfish sunucusu tarafından sağlanır,
- 41-48. satırlar: EclipseLink artefaktlarının bulunabileceği yeni bir Maven artefakt deposu tanımlar.
3.4.2.3. JPA varlıklarının oluşturulması
JPA varlıkları, NetBeans'teki bir sihirbaz aracılığıyla oluşturulabilir:
![]() |
- [1]'te, bir veritabanından JPA varlıklar oluşturulur,
- [2]'te, daha önce oluşturulan [jdbc / dbrdvmedecins2] veri kaynağı seçilir,
- [3]'te, bu veri kaynağının tablo listesi,
- [4] adımında hepsini seçin,
![]() |
- [5]'te seçilen tablolar,
- [6] dosyasında, dört tabloya ilişkin Java sınıflarına bir ad verilir,
- ayrıca bir paket adı verilir: [7],
- [8]'te, JPA, BD'teki tablo satırlarını koleksiyonlarda birleştirir. Koleksiyon olarak listeyi seçiyoruz,
![]() |
- [9]'te, sihirbaz tarafından oluşturulan Java sınıfları.
3.4.2.4. Oluşturulan JPA varlıklar
[Medecin] varlığı, [medecins] tablosunun yansımasıdır. Java sınıfı, ilk bakışta kodu okunması zor hale getiren çok sayıda açıklama ile doludur. Varlığın rolünü anlamak için gerekli olan unsurları elimizde tutarsak, şu kodu elde ederiz:
package rdvmedecins.jpa;
...
@Entity
@Table(name = "medecins")
public class Medecin implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "TITRE")
private String titre;
@Column(name = "NOM")
private String nom;
@Column(name = "VERSION")
private int version;
@Column(name = "PRENOM")
private String prenom;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
private List<Creneau> creneauList;
// yapıcılar
....
// getter ve setter'lar
....
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
- 4. satırda, @Entity anotasyonu, [Medecin] sınıfını bir varlık haline getirir: JPA, c.a.d. BD tablosuna, API ve JPA aracılığıyla bağlı bir sınıf,
- 5. satırda, BD tablosunun adı, JPA varlığıyla ilişkilidir. Tablodaki her alan, Java sınıfındaki bir alana karşılık gelir,
- 6. satırda, sınıf Serializable arayüzünü uygular. Bu, varlıkların istemci ile sunucu arasında serileştirildiği istemci/sunucu uygulamalarında gereklidir.
- 10-11. satırlar: [Medecin] sınıfındaki id alanı, [medecins] tablosundaki [ID] alanına (10. satır) karşılık gelir,
- 13-14. satırlar: [Medecin] sınıfındaki titre alanı, [medecins] tablosundaki [TITRE] alanına (13. satır) karşılık gelir,
- satır 16-17: [Medecin] sınıfındaki “ad” alanı, [medecins] tablosundaki [NOM] alanına (satır 16) karşılık gelir,
- 19-20. satırlar: [Medecin] sınıfındaki "version" alanı, [medecins] tablosundaki [VERSION] alanına (19. satır) karşılık gelir. Burada sihirbaz, sütunun aslında ait olduğu satır her değiştirildiğinde artırılması gereken bir sürüm sütunu olduğunu tanımıyor. Ona bu rolü vermek için @Version ek açıklaması eklenmelidir. Bunu bir sonraki adımda yapacağız,
- 22-23. satırlar: [Medecin] sınıfındaki prenom alanı, [medecins] tablosundaki [PRENOM] alanına karşılık gelir,
- 10-11. satırlar: id alanı, tablonun [ID] birincil anahtarına karşılık gelir. 8-9. satırlardaki anotasyonlar bu noktayı netleştirir,
- 8. satır: @Id açıklaması, açıklanan alanın tablonun birincil anahtarıyla ilişkili olduğunu belirtir,
- 9. satır: [JPA] katmanı, [Medecins] tablosuna ekleyeceği satırların birincil anahtarını oluşturacaktır. Birkaç farklı strateji mümkündür. Burada GenerationType.IDENTITY stratejisi, JPA katmanının MySQL tablosunun auto_increment modunu kullanacağını belirtir,
- 25-26. satırlar: [creneaux] tablosu, [medecins] tablosuna bir yabancı anahtara sahiptir. Bir randevu aralığı bir doktora aittir. Tersine, bir doktorun kendisine atanan birden fazla randevu aralığı vardır. Dolayısıyla, bir (doktor) ile birden fazla (randevu aralığı) arasındaki bir ilişki söz konusudur; bu ilişki, @OneToMany tarafından JPA'e yapılan notla tanımlanmıştır (25. satır). 26. satırdaki alan, doktorun tüm randevu zaman aralıklarını içerecektir. Bu, herhangi bir programlama gerektirmez. 25. satırı tam olarak anlamak için [Creneau] sınıfını tanıtmamız gerekir.
Bu sınıf şu şekildedir:
package rdvmedecins.jpa;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "MDEBUT")
private int mdebut;
@Column(name = "HFIN")
private int hfin;
@Column(name = "HDEBUT")
private int hdebut;
@Column(name = "MFIN")
private int mfin;
@Column(name = "VERSION")
private int version;
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin idMedecin;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idCreneau")
private List<Rv> rvList;
// yapıcılar
...
// getter ve setter'lar
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
Sadece yeni açıklamaları yorumlayacağız:
- [creneaux] tablosunun, [medecins] tablosuna bir yabancı anahtara sahip olduğunu belirtmiştik: bir randevu aralığı bir doktora atanır. Aynı doktora birden fazla randevu aralığı atanabilir. [creneaux] tablosundan [medecins] tablosuna giden bir ilişki vardır ve bu ilişki "birden çok (zaman aralığı)" - "bir (doktor)" şeklinde tanımlanmıştır. Yabancı anahtarı tanımlamak için 32. satırdaki @ManyToOne açıklaması kullanılır;
- 31. satırdaki @JoinColumn notu ise yabancı anahtar ilişkisini şu şekilde belirtir: [ID_MEDECIN] tablosunun [creneaux] sütunu, [medecins] tablosunun [ID] sütununa yabancı anahtardır,
- 33. satır: zaman aralığının sahibi olan doktora bir referans. Bu da yine programlama gerektirmeden elde edilir.
Dolayısıyla, [Creneau] varlığı ile [Medecin] varlığı arasındaki yabancı anahtar ilişkisi iki notla belirtilmiştir:
- [Creneau] varlığında:
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin idMedecin;
- [Medecin] varlığında:
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
private List<Creneau> creneauList;
Bu iki anotasyon aynı ilişkiyi yansıtmaktadır: [creneaux] tablosundan [medecins] tablosuna giden yabancı anahtar ilişkisi. Bunların birbirinin tersi olduğu söylenir. Yalnızca @ManyToOne ilişkisi zorunludur. Bu ilişki, yabancı anahtar ilişkisini kesin bir şekilde tanımlar. @OneToMany ilişkisi ise isteğe bağlıdır. Varsa, yalnızca ilişkili olduğu @ManyToOne ilişkisine referans verir. Bu, [Medecin] varlığının 1. satırındaki mappedBy özniteliğinin anlamıdır. Bu özniteliğin değeri, yabancı anahtarı belirten @ManyToOne açıklaması içeren [Creneau] varlığının alan adıdır. Yine [Medecin] varlığının aynı 1. satırında, cascade=CascadeType.ALL özniteliği, [Medecin] varlığının [Creneau] varlığına karşı davranışını belirler:
- eğer veritabanına yeni bir [Medecin] varlığı eklenirse, o zaman 2. satırdaki alandaki [Creneau] varlıkları da eklenmelidir,
- veritabanındaki bir [Medecin] nesnesi değiştirilirse, 2. satırdaki alandaki [Creneau] nesneleri de değiştirilmelidir,
- veritabanındaki bir [Medecin] varlığı silinirse, 2. satırdaki alandaki [Creneau] varlıkları da silinmelidir.
Diğer iki varlığın kodlarını, yeni bir notasyon getirmedikleri için özel bir açıklama yapmadan veriyoruz.
[Client] varlığı
package rdvmedecins.jpa;
...
@Entity
@Table(name = "clients")
public class Client implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "TITRE")
private String titre;
@Column(name = "NOM")
private String nom;
@Column(name = "VERSION")
private int version;
@Column(name = "PRENOM")
private String prenom;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idClient")
private List<Rv> rvList;
// yapıcılar
...
// getter ve setter'lar
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
- 24-25. satırlar, [rv] tablosu ile [clients] tablosu arasındaki yabancı anahtar ilişkisini yansıtmaktadır.
[Rv] varlığı:
package rdvmedecins.jpa;
...
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "JOUR")
@Temporal(TemporalType.DATE)
private Date jour;
@JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Creneau idCreneau;
@JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Client idClient;
// yapıcılar
...
// getter ve setter'lar
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
- 13. satır, Date tablosundaki "gün" alanının Java türü olduğunu belirtir. Burada, [rv] tablosunda [JOUR] sütununun (12. satır) tarih (saatsiz) türü olduğu belirtilir,
- 16-18. satırlar: [rv] tablosunun [creneaux] tablosuna olan yabancı anahtar ilişkisini tanımlar,
- satır 20-22: [rv] tablosunun [clients] tablosuna olan yabancı anahtar ilişkisini tanımlar.
JPA varlıklarının otomatik olarak oluşturulması, bize bir çalışma temeli sağlar. Bazen bu yeterlidir, bazen değildir. Burada durum böyledir:
- varlıkların çeşitli sürüm alanlarına @Version ek açıklaması eklenmesi gerekir,
- oluşturulan yöntemlerden daha açıklayıcı toString yöntemleri yazmak gerekiyor,
- [Medecin] ve [Client] varlıklar birbirine benziyor. Bunları [Personne] sınıfından türeteceğiz,
- @ManyToOne ilişkilerinin tersi olan @OneToMany ilişkilerini kaldıracağız. Bunlar zorunlu değildir ve programlamayı karmaşıklaştırır,
- birincil anahtarlar üzerindeki @NotNull doğrulamasını kaldırıyoruz. Bir JPA varlığını MySQL ile kalıcı hale getirdiğimizde, başlangıçtaki varlığın birincil anahtarı null'tir. Veritabanına kaydedildikten sonra, kaydedilen öğenin birincil anahtarı bir değere sahip olur.
Bu özelliklere göre, sınıflar şu şekilde şekillenir:
Personne sınıfı, doktorları ve müşterileri temsil etmek için kullanılır:
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@MappedSuperclass
public class Personne implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Size(min = 1, max = 5)
@Column(name = "TITRE")
private String titre;
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 30)
@Column(name = "NOM")
private String nom;
@Basic(optional = false)
@NotNull
@Column(name = "VERSION")
@Version
private int version;
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 30)
@Column(name = "PRENOM")
private String prenom;
// yapıcılar
...
// getter ve setter'lar
...
@Override
public String toString() {
return String.format("[%s,%s,%s,%s,%s]", id, version, titre, prenom, nom);
}
}
- 8. satır: [Personne] sınıfının kendisinin bir varlık (@Entity) olmadığına dikkat edilmelidir. Bu sınıf, varlıkların üst sınıfı olacaktır. @MappedSuperClass anotasyonu bu durumu belirtir.
[Client] varlığı, [clients] tablosundaki satırları kapsüller. Bu varlık, önceki [Personne] sınıfından türetilmiştir:
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
@Entity
@Table(name = "clients")
public class Client extends Personne implements Serializable {
private static final long serialVersionUID = 1L;
// yapıcılar
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return String.format("Client[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(), getNom());
}
}
- 6. satır: [Client] sınıfı bir JPA varlığıdır,
- 7. satır: [clients] tablosuyla ilişkilidir,
- 8. satır: [Personne] sınıfından türetilmiştir.
[medecins] tablosunun satırlarını kapsayan [Medecin] varlığı da aynı modeli izler:
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
@Entity
@Table(name = "medecins")
public class Medecin extends Personne implements Serializable {
private static final long serialVersionUID = 1L;
// yapıcılar
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return String.format("Médecin[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(), getNom());
}
}
[Creneau] varlığı, [creneaux] tablosundaki satırları kapsar:
package rdvmedecins.jpa;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@NotNull
@Column(name = "MDEBUT")
private int mdebut;
@Basic(optional = false)
@NotNull
@Column(name = "HFIN")
private int hfin;
@Basic(optional = false)
@NotNull
@Column(name = "HDEBUT")
private int hdebut;
@Basic(optional = false)
@NotNull
@Column(name = "MFIN")
private int mfin;
@Basic(optional = false)
@NotNull
@Column(name = "VERSION")
@Version
private int version;
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin medecin;
// yapıcılar
...
// getter ve setter'lar
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
// TODO: Uyarı - id alanları ayarlanmamışsa bu yöntem çalışmayacaktır
...
}
@Override
public String toString() {
return String.format("Creneau [%s, %s, %s:%s, %s:%s,%s]", id, version, hdebut, mdebut, hfin, mfin, medecin);
}
}
- 45-47. satırlar, veritabanındaki [creneaux] tablosu ile [medecins] tablosu arasında bulunan "birden çok-bir" ilişkisini modellemektedir: bir doktorun birden çok randevu aralığı vardır, bir randevu aralığı ise yalnızca bir doktora aittir.
[Rv] varlığı, [rv] tablosundaki satırları kapsar:
package rdvmedecins.jpa;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@NotNull
@Column(name = "JOUR")
@Temporal(TemporalType.DATE)
private Date jour;
@JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Creneau creneau;
@JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Client client;
// yapıcılar
...
// getter ve setter'lar
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return String.format("Rv[%s, %s, %s]", id, creneau, client);
}
}
- 29-31. satırlar, veritabanındaki [rv] tablosu ile [clients] tablosu arasında bulunan "birden çok-bir" ilişkisini modellemektedir (bir müşteri birden fazla Rv'de yer alabilir) ve 25-27. satırlar, [rv] tablosu ile [creneaux] tablosu arasında var olan "birden çok-bir" ilişkisini (bir zaman aralığı birden fazla Rv'de yer alabilir) modellemektedir.
3.4.3. İstisna sınıfı
![]() |
Uygulamanın [RdvMedecinsException] istisna sınıfı şu şekildedir:
package rdvmedecins.exceptions;
import java.io.Serializable;
import javax.ejb.ApplicationException;
@ApplicationException(rollback=true)
public class RdvMedecinsException extends RuntimeException implements Serializable{
// özel alanlar
private int code = 0;
// yapıcılar
public RdvMedecinsException() {
super();
}
public RdvMedecinsException(String message) {
super(message);
}
public RdvMedecinsException(String message, Throwable cause) {
super(message, cause);
}
public RdvMedecinsException(Throwable cause) {
super(cause);
}
public RdvMedecinsException(String message, int code) {
super(message);
setCode(code);
}
public RdvMedecinsException(Throwable cause, int code) {
super(cause);
setCode(code);
}
public RdvMedecinsException(String message, Throwable cause, int code) {
super(message, cause);
setCode(code);
}
// alıcı ve ayarlayıcılar
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
- 7. satır: Sınıf, [RuntimeException] sınıfından türetilmiştir. Dolayısıyla derleyici, bu istisnayı try / catch ile yönetmeyi zorunlu kılmaz.
- 6. satır: @ApplicationException anotasyonu, istisnanın [EjbException] türündeki bir istisna tarafından "yutulmamasını" sağlar.
@ApplicationException anotasyonunu anlamak için sunucu tarafında kullanılan mimariye geri dönelim:
![]() |
[RdvMedecinsException] türündeki istisna, EJB3 kapsayıcısının içindeki [DAO] katmanına ait EJB yöntemleri tarafından tetiklenecek ve bu kapsayıcı tarafından yakalanacaktır. @ApplicationException ek açıklaması olmadan, EJB3 kapsayıcısı meydana gelen istisnayı [EjbException] türünde bir istisna içinde kapsüller ve bunu yeniden atar. Bu kapsüllemeyi istemediğinizde, EJB3 kapsayıcısından [RdvMedecinsException] türünde bir istisnanın dışarı çıkmasına izin verebilirsiniz. @ApplicationException anotasyonu bunu sağlar. Ayrıca, bu anotasyonun (rollback=true) özniteliği, EJB3 konteynerine, SGBD ile bir işlem kapsamında yürütülen bir yöntem içinde [RdvMedecinsException] türünde bir istisna meydana gelirse, işlemin geri alınması gerektiğini belirtir. Teknik terimlerle ifade edersek, buna işlemin rollback'i yapılması denir.
3.4.4. [DAO] katmanındaki EJB
![]() |
![]() |
[DAO] katmanının java arayüzü [IDao] şu şekildedir:
package rdvmedecins.dao;
import java.util.Date;
import java.util.List;
import rdvmedecins.jpa.Client;
import rdvmedecins.jpa.Creneau;
import rdvmedecins.jpa.Medecin;
import rdvmedecins.jpa.Rv;
public interface IDao {
// müşteri listesi
public List<Client> getAllClients();
// doktor listesi
public List<Medecin> getAllMedecins();
// bir doktorun randevu saatleri listesi
public List<Creneau> getAllCreneaux(Medecin medecin);
// belirli bir günde bir doktorun randevu listesi
public List<Rv> getRvMedecinJour(Medecin medecin, Date jour);
// ID'siyle bir müşteriyi bul
public Client getClientById(Long id);
// ID'siyle bir müşteriyi bulma
public Medecin getMedecinById(Long id);
// ID'siyle tanımlanan bir randevuyu bulma
public Rv getRvById(Long id);
// ID'siyle tanımlanan bir zaman aralığını bulma
public Creneau getCreneauById(Long id);
// bir RV eklemek
public Rv ajouterRv(Date jour, Creneau creneau, Client client);
// bir RV'i sil
public void supprimerRv(Rv rv);
}
Bu arayüz, [web] katmanının ihtiyaçları belirlendikten sonra oluşturulmuştur:
- 14. satır: müşteri listesi. Buna, müşterilerin açılır listesini doldurmak için ihtiyacımız olacak,
- 16. satır: doktor listesi. Bunu, doktorların açılır menüsünü doldurmak için kullanacağız,
- 18. satır: Bir doktorun randevu zaman aralıkları listesi. Belirli bir gün için doktorun randevu takvimini görüntülemek için buna ihtiyacımız olacak,
- 20. satır: Belirli bir gün için bir doktorun randevu listesi. Bir önceki yöntemle birleştirildiğinde, bu liste sayesinde belirli bir gün için doktorun takvimini, önceden rezerve edilmiş randevu aralıklarıyla birlikte görüntüleyebileceğiz,
- 22. satır: Bir müşteriyi numarasına göre bulmamızı sağlar. Bu yöntem, müşteri açılır listesinden yapılan bir seçimle bir müşteriyi bulmamızı sağlayacaktır,
- satır 24: doktorlar için de aynı şekilde,
- satır 26: bir randevuyu numarasına göre bulur. Bir randevuyu silmeden önce, o randevunun gerçekten var olup olmadığını kontrol etmek için kullanılabilir,
- satır 28: bir zaman aralığını numarasına göre bulur. Bir kullanıcının eklemek veya silmek istediği zaman aralığını belirlemeye olanak tanır,
- satır 30: randevu eklemek için,
- satır 32: randevuyu silmek için.
[IDaoLocal] yerel arayüzü, EJB'in önceki [IDao] arayüzünden türetilmiştir:
package rdvmedecins.dao;
import javax.ejb.Local;
@Local
public interface IDaoLocal extends IDao{
}
Aynı durum, uzaktaki [IDaoRemote] arayüzü için de geçerlidir:
package rdvmedecins.dao;
import javax.ejb.Remote;
@Remote
public interface IDaoRemote extends IDao{
}
EJB ve [DaoJpa], hem yerel hem de uzak arayüzleri uygular:
package rdvmedecins.dao;
...
@Singleton (mappedName="rdvmedecins.dao")
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class DaoJpa implements IDaoLocal, IDaoRemote, Serializable {
- 5. satır, uzak EJB'in "rdvmedecins.dao" adını taşıdığını gösterir. Ayrıca, @Singleton anotasyonu (Java EE6), EJB'in yalnızca bir örneğinin oluşturulmasını sağlar. @Stateless anotasyonu (Java EE5), bir EJB'in birden fazla örneği oluşturulabileceğini ve bu örneklerin bir EJB havuzunu besleyebileceğini tanımlar,
- 6. satır, EJB sınıfındaki tüm yöntemlerin, EJB3 konteyneri tarafından yönetilen bir işlem içinde gerçekleştirildiğini gösterir;
- 7. satır, EJB'in yerel ve uzak arayüzleri uyguladığını ve aynı zamanda serileştirilebilir olduğunu göstermektedir.
EJB'in tam kodu şöyledir:
package rdvmedecins.dao;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import javax.ejb.Singleton;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import rdvmedecins.exceptions.RdvMedecinsException;
import rdvmedecins.jpa.Client;
import rdvmedecins.jpa.Creneau;
import rdvmedecins.jpa.Medecin;
import rdvmedecins.jpa.Rv;
@Singleton (mappedName="rdvmedecins.dao")
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class DaoJpa implements IDaoLocal, IDaoRemote, Serializable {
@PersistenceContext
private EntityManager em;
// müşteri listesi
public List<Client> getAllClients() {
try {
return em.createQuery("select rc from Client rc").getResultList();
} catch (Throwable th) {
throw new RdvMedecinsException(th, 1);
}
}
// doktor listesi
public List<Medecin> getAllMedecins() {
try {
return em.createQuery("select rm from Medecin rm").getResultList();
} catch (Throwable th) {
throw new RdvMedecinsException(th, 2);
}
}
// belirli bir doktorun randevu saatleri listesi
// doktor: doktor
public List<Creneau> getAllCreneaux(Medecin medecin) {
try {
return em.createQuery("select rc from Creneau rc join rc.medecin m where m.id=:idMedecin").setParameter("idMedecin", medecin.getId()).getResultList();
} catch (Throwable th) {
throw new RdvMedecinsException(th, 3);
}
}
// belirli bir doktorun belirli bir günkü randevu listesi
// doktor: doktor
// gün: gün
public List<Rv> getRvMedecinJour(Medecin medecin, Date jour) {
try {
return em.createQuery("select rv from Rv rv join rv.creneau c join c.idMedecin m where m.id=:idMedecin and rv.jour=:jour").setParameter("idMedecin", medecin.getId()).setParameter("jour", jour).getResultList();
} catch (Throwable th) {
throw new RdvMedecinsException(th, 3);
}
}
// randevu eklenmesi
// gün: randevunun yapıldığı gün
// zaman aralığı: randevunun zaman aralığı
// müşteri: randevu alınan müşteri
public Rv ajouterRv(Date jour, Creneau creneau, Client client) {
try {
Rv rv = new Rv(null, jour);
rv.setClient(client);
rv.setCreneau(creneau);
em.persist(rv);
return rv;
} catch (Throwable th) {
throw new RdvMedecinsException(th, 4);
}
}
// Randevu silme
// randevu: silinen randevu
public void supprimerRv(Rv rv) {
try {
em.remove(em.merge(rv));
} catch (Throwable th) {
throw new RdvMedecinsException(th, 5);
}
}
// belirli bir müşteriyi bulma
public Client getClientById(Long id) {
try {
return (Client) em.find(Client.class, id);
} catch (Throwable th) {
throw new RdvMedecinsException(th, 6);
}
}
// belirli bir doktoru bulma
public Medecin getMedecinById(Long id) {
try {
return (Medecin) em.find(Medecin.class, id);
} catch (Throwable th) {
throw new RdvMedecinsException(th, 6);
}
}
// belirli bir randevuyu bulma
public Rv getRvById(Long id) {
try {
return (Rv) em.find(Rv.class, id);
} catch (Throwable th) {
throw new RdvMedecinsException(th, 6);
}
}
// belirli bir randevu aralığını almak
public Creneau getCreneauById(Long id) {
try {
return (Creneau) em.find(Creneau.class, id);
} catch (Throwable th) {
throw new RdvMedecinsException(th, 6);
}
}
}
- 22. satır: Kalıcılık bağlamına erişimi yöneten EntityManager nesnesi. Sınıfın örneklendirilmesi sırasında, bu alan 21. satırdaki @PersistenceContext anotasyonu sayesinde EJB konteyneri tarafından başlatılacaktır,
- 27. satır: JPQL sorgusu (Java Persistence Query Language), [clients] tablosundaki tüm satırları bir [Client] nesneleri listesi biçiminde döndürür,
- satır 36: doktorlar için benzer bir sorgu,
- satır 46: JPQL sorgusu, [creneaux] ve [medecins] tabloları arasında bir birleştirme gerçekleştirir. Bu sorgu, doktorun kimliği ile parametreleştirilir,
- satır 57: [rv], [creneaux] ve [medecins] tabloları arasında birleştirme gerçekleştiren ve iki parametreye sahip bir JPQL sorgusu: doktor kimliği ve randevu günü,
- 69-73. satırlar: Bir randevunun oluşturulması ve ardından veritabanına kaydedilmesi,
- 83. satır: Veritabanından bir randevunun silinmesi,
- 92. satır: Belirli bir müşteriyi bulmak için veritabanında select sorgusu gerçekleştirilir,
- satır 101: bir doktor için de aynısı,
- satır 110: randevu için de aynısı,
- satır 119: bir zaman aralığı için de aynısı,
- 22. satırdaki em kalıcılık bağlamıyla gerçekleştirilen tüm işlemlerde veritabanıyla ilgili bir sorun yaşanabilir. Bu nedenle hepsi bir try / catch bloğu içine alınmıştır. Olası istisna, "özel" RdvMedecinsException istisnası içinde kapsüllenmiştir.
3.4.5. MySQL'ten JDBC sürücüsünün kurulumu
Aşağıdaki mimaride:
![]() |
EclipseLink, MySQL'in JDBC sürücüsüne ihtiyaç duyar. Bu sürücüyü, <glassfish> /domains /domain1 /lib /ext klasöründeki Glassfish sunucusunun kütüphanelerine yüklemek gerekir; burada <glassfish>, Glassfish sunucusunun kurulum klasörüdür. Bu sürücüyü şu şekilde edinebilirsiniz:
![]() |
MySQL sürücüsünün JDBC sürücüsünün yerleştirileceği klasör <Domains folder>[1]/domain1/lib/ext [2]'tir. Bu sürücü, URL ve [http://www.mysql.fr/downloads/connector/j/] adreslerinde mevcuttur. Sürücü yüklendikten sonra, Glassfish sunucusunun bu yeni kütüphaneyi algılaması için sunucuyu yeniden başlatmanız gerekir.
3.4.6. EJB katmanının [DAO]'e dağıtımı
Şu ana kadar oluşturulan mimariye geri dönelim:
![]() |
[web, métier, DAO, JPA] kümesi, Glassfish sunucusuna dağıtılmalıdır. Bunu şu şekilde yapıyoruz:
![]() |
- [1]'te Maven projesini derliyoruz,
- [2]'te projeyi çalıştırıyoruz,
- [3]'te, Glassfish sunucusuna dağıtılmıştır ([Services] sekmesi)
Glassfish günlüklerini incelemek isteyebilirsiniz:
![]() |
[1]'te, Glassfish günlükleri [Output / Glassfish Server 3+] sekmesinde mevcuttur. Bunlar şunlardır:
[Config] ve [Précis] ile tanımlanan satırlar EclipseLink'in günlükleridir; [Infos] ile tanımlananlar ise Glassfish'ten gelmektedir.
- 1-12. satırlar: EclipseLink, tespit ettiği JPA varlıklarını işliyor,
- 13-17. satırlar: JPA varlıklarının işlenmesinin normal şekilde gerçekleştiğini gösteren bilgiler,
- 18. satır: EclipseLink kendini bildirir,
- 19. satır: EclipseLink, SGBD MySQL ile karşı karşıya olduğunu fark eder,
- 20-24. satırlar: EclipseLink, BD'e bağlanmaya çalışıyor,
- 25-28. satırlar: Bağlantı kuruldu,
- 29-33. satırlar: bu sefer özellikle MySQL platformunu kullanarak yeniden bağlanmaya çalışıyor (30. satır),
- satır 34-37: burada da başarılı,
- satır 38: [dbrdvmedecins-PU] kalıcılık biriminin oluşturulabildiğinin onayı,
- satır 39: EJB ve [DaoJpa]'in uzak ve yerel arayüzlerinin taşınabilir adları; burada "taşınabilir", tüm Java uygulama sunucuları tarafından tanınan anlamına gelir,
- 40. satır: EJB ve [DaoJpa]'in uzak ve yerel arayüz adları; bunlar Glassfish'e özeldir. Önümüzdeki testte "rdvmedecins.dao" adını kullanacağız.
- ve 40. satırlar önemlidir. Glassfish üzerinde bir EJB istemcisi yazarken, bu satırları bilmek gerekir.
3.4.7. [DAO] katmanının EJB'i için testler
Uygulamamızın [DAO] katmanına ait EJB artık devreye alındığına göre, bunu test edebiliriz. Bunu bir istemci/sunucu uygulaması kapsamında yapacağız:
![]() |
İstemci, Glassfish sunucusunda dağıtılan EJB [DAO]'in uzak arayüzünü test edecek.
İlk olarak, Maven'da yeni bir projesi oluşturacağız:
![]() |
- [1] içinde yeni bir proje oluşturuyoruz,
- [2,3] dizininde, [Java Application] türünde bir Maven projesi oluşturuyoruz,
- [4]'te, ona bir isim verip EJB ve [DAO] ile aynı klasöre yerleştiriyoruz,
![]() |
- [5] dosyasına, oluşturulan proje,
- [6]'e, bir [App.java] sınıfı oluşturuldu. Bunu sileceğiz,
- [7]'te, [Source Packages] adlı bir dal oluşturuldu. Bununla henüz karşılaşmamıştık. Bu dala JUnit testleri ekleyebiliriz. Bunu yapacağız. Oluşturulan [AppTest] test sınıfını tutmayacağız,
- [8]'te, Maven projesinin bağımlılıkları yer alıyor. [Dependencies] dalı boş. Buraya yeni bağımlılıklar eklememiz gerekecek. [Test Dependencies] dalı, testler için gerekli bağımlılıkları bir araya getirir. Burada kullanılan kütüphane, JUnit 3.8 çerçeve kütüphanesidir. Bunu değiştirmemiz gerekecek.
Proje şu şekilde gelişiyor:
![]() |
- [1]'e, burada oluşturulan iki sınıf ve JUnit bağımlılığı kaldırılmıştır.
Test için kullanılacak istemci/sunucu mimarisine geri dönelim:
![]() |
İstemci, EJB ve [DAO] tarafından sunulan uzak arayüzü bilmek zorundadır. Ayrıca, EJB ile JPA varlıklarını alışverişinde bulunacaktır. Bu nedenle, bu varlıkların tanımlarına ihtiyacı vardır. EJB test projesinin bu bilgilere erişebilmesi için, EJB ve [DAO] projelerini projeye bağımlılık olarak ekleyeceğiz:
![]() |
- [1]'te, [Test Dependencies] dalına bir bağımlılık ekleyeceğiz,
- [2]'te, [Open Projects] sekmesini seçin,
- [3]'te, EJB [DAO]'in Maven projesini seçin,
![]() |
- [4]'te, bağımlılık eklenir.
Testin istemci/sunucu mimarisine geri dönelim:
![]() |
Çalıştırma sırasında, istemci ve sunucu TCP-IP ağı üzerinden iletişim kurar. Bu veri alışverişlerini programlamayacağız. Her uygulama sunucusu için, istemcinin bağımlılıklarına eklenmesi gereken bir kütüphane vardır. Glassfish için olanın adı [gf-client]'tir. Bunu ekliyoruz:
![]() |
- [1] dosyasına bir bağımlılık ekliyoruz,
- [2]'e istenen artefaktın özelliklerini giriyoruz,
- [3]'te çok sayıda bağımlılık eklenir. Maven bunları indirecektir. Bu işlem birkaç dakika sürebilir. Ardından bunlar Maven'ın yerel deposunda saklanır.
Artık JUnit testini oluşturabiliriz:
![]() |
- [2]'te, [Test Packages]'e sağ tıklayarak yeni bir test olan JUnit'i oluşturabiliriz,
![]() |
- [3]'e dönüştürün, test sınıfına bir ad ve [4] için bir paket adı verin,
- [5]'e dönüştürürken, JUnit 4.x çerçeve seçilir,
- [6]'te, oluşturulan test sınıfı,
- [7] dosyasında ise projenin yeni Maven bağımlılıkları.
[pom.xml] dosyası şu şekildedir:
<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-client-rdvmedecins-ejb-dao</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-client-rdvmedecins-ejb-dao</name>
<url>http://maven.apache.org</url>
<repositories>
<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>
<repository>
<url>http://repo1.maven.org/maven2/</url>
<id>junit_4</id>
<layout>default</layout>
<name>Repository for library Library[junit_4]</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>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mv-rdvmedecins-ejb-dao-jpa</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.appclient</groupId>
<artifactId>gf-client</artifactId>
<version>3.1.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Dikkat edilmesi gereken noktalar:
- 32-51. satırlar: projenin bağımlılıkları,
- 13-26. satırlar: iki Maven deposu tanımlanmıştır; biri EclipseLink için (14-19. satırlar), diğeri JUnit4 için (20-25. satırlar).
Test sınıfı şu şekilde olacaktır:
package rdvmedecins.tests.dao;
import java.util.Date;
import java.util.List;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import junit.framework.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import rdvmedecins.dao.IDaoRemote;
import rdvmedecins.jpa.Client;
import rdvmedecins.jpa.Creneau;
import rdvmedecins.jpa.Medecin;
import rdvmedecins.jpa.Rv;
public class JUnitTestDao {
// [dao] katmanı test edildi
private static IDaoRemote dao;
// bugünün tarihi
Date jour = new Date();
@BeforeClass
public static void init() throws NamingException {
// JNDI ortamının başlatılması
InitialContext initialContext = new InitialContext();
// DAO katmanının örneklenmesi
dao = (IDaoRemote) initialContext.lookup("rdvmedecins.dao");
}
@Test
public void test1() {
// müşterilerin görüntülenmesi
List<Client> clients =dao.getAllClients();
display("Liste des clients :", clients);
// doktorların görüntülenmesi
List<Medecin> medecins =dao.getAllMedecins();
display("Liste des médecins :", medecins);
// bir doktorun randevu aralıklarının görüntülenmesi
Medecin medecin = medecins.get(0);
List<Creneau> creneaux = dao.getAllCreneaux(medecin);
display(String.format("Liste des créneaux du médecin %s", medecin), creneaux);
// belirli bir günde bir doktorun randevu listesi
display(String.format("Liste des créneaux du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, jour));
// bir RV ekle
Rv rv = null;
Creneau creneau = creneaux.get(2);
Client client = clients.get(0);
System.out.println(String.format("Ajout d'un Rv le [%s] dans le créneau %s pour le client %s", jour, creneau, client));
rv = dao.ajouterRv(jour, creneau, client);
System.out.println("Rv ajouté");
display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, jour));
// aynı günün aynı randevu aralığına bir RV ekle
// bir istisna oluşturmalıdır
System.out.println(String.format("Ajout d'un Rv le [%s] dans le créneau %s pour le client %s", jour, creneau, client));
Boolean erreur = false;
try {
rv = dao.ajouterRv(jour, creneau, client);
System.out.println("Rv ajouté");
} catch (Exception ex) {
Throwable th = ex;
while (th != null) {
System.out.println(ex.getMessage());
th = th.getCause();
}
// hata kaydedilir
erreur=true;
}
// bir hata olup olmadığı kontrol ediliyor
Assert.assertTrue(erreur);
// RV'lerin listesi
display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, jour));
// bir RV'i sil
System.out.println("Suppression du Rv ajouté");
dao.supprimerRv(rv);
System.out.println("Rv supprimé");
display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, jour));
}
// yardımcı yöntem - bir koleksiyondaki öğeleri görüntüler
private static void display(String message, List elements) {
System.out.println(message);
for (Object element : elements) {
System.out.println(element);
}
}
}
- 23-29. satırlar: @BeforeClass etiketli yöntem, diğer tüm yöntemlerden önce yürütülür. Burada, EJB ve [DaoJpa]'in uzak arayüzüne bir referans oluşturulur. Hatırlayacağınız üzere, ona "JNDI" adını vermiştik,
- 34-35. satırlar: müşteri listesini görüntüler,
- 37-38. satırlar: doktor listesini görüntüler,
- 40-42. satırlar: ilk doktorun randevu saatlerini gösterir,
- 44. satır: 21. satırdaki gün için ilk doktorun randevularını gösterir,
- satır 46-51: ilk doktorun 2 numaralı randevu aralığı ve satır 21'deki gün için bir randevu ekler,
- satır 52: doğrulama amacıyla satır 21'deki gün için ilk doktorun randevularını görüntüler. En az bir tane olmalı; bu, az önce eklenen randevudur,
- satır 55-70: Aynı randevu eklenir. [RV] tablosunda teklik kısıtlaması olduğundan, bu ekleme bir istisna oluşturmalıdır. Bunu satır 70'te doğrularız,
- 72. satır: Doğrulama amacıyla 21. satırdaki gün için ilk doktorun randevularını görüntüleyin. Eklemek istediğimiz randevu burada olmamalıdır,
- 74-76. satırlar: Eklenen tek randevuyu siliyoruz,
- 77. satır: Doğrulama amacıyla 21. satırdaki gün için ilk doktorun randevuları görüntülenir. Az önce silinen randevu burada olmamalıdır.
Bu test, JUnit adlı sahte bir testtir. İçinde yalnızca bir doğrulama ifadesi (70. satır) bulunur. Bu, ilgili hataları içeren görsel bir testtir.
Her şey yolunda giderse, testler başarılı olmalıdır:
![]() |
- [1]'te, test projesini oluştururuz,
- [2]'te testi çalıştırıyoruz,
- [3]'te test başarılı oldu.
Test çıktılarına daha yakından bakalım:
Okuyucunun bu günlükleri, bunları oluşturan kodla birlikte okumasını öneririz. Zaten mevcut olan bir randevunun eklenmesi sırasında ortaya çıkan istisnaya, 41-49. satırlara odaklanalım. İstisna yığını 42-48. satırlarda gösterilmiştir. Bu beklenmedik bir durumdur. Randevu ekleme yönteminin koduna geri dönelim:
// Rv ekleme
// tarih: Rv'nin tarihi
// zaman aralığı: Randevunun zaman aralığı
// müşteri: randevu alınan müşteri
public Rv ajouterRv(Date jour, Creneau creneau, Client client) {
try {
Rv rv = new Rv(null, jour);
rv.setClient(client);
rv.setCreneau(creneau);
System.out.println(String.format("avant persist : %s",rv));
em.persist(rv);
System.out.println(String.format("après persist : %s",rv));
return rv;
} catch (Throwable th) {
throw new RdvMedecinsException(th, 4);
}
}
İki randevu eklenirken Glassfish günlüklerine bir göz atalım:
- 2. satır: ilk persist'ten önce,
- 3. satır: İlk persist'ten sonra,
- 4. satır: Yürütülecek olan INSERT komutu. Bunun, persist işlemiyle aynı anda gerçekleşmediğine dikkat edilmelidir. Öyle olsaydı, bu günlük kaydı 2. satırdan önce görünürdü. INSERT işlemi normalde, yöntemin yürütüldüğü işlemin sonunda gerçekleşir,
- 6. satır: EclipseLink, MySQL’e en son kullanılan birincil anahtarın ne olduğunu sorar. Eklenen randevunun birincil anahtarını alır. Bu değer, kalıcı hale getirilen [Rv] varlığının id alanına yazılır,
- 7-8. satırlar: Doktorun randevularını görüntüleyecek olan SELECT sorgusu,
- 9-10. satırlar: İkinci persist'in ekran görüntüleri,
- satır 11-12: Yürütülecek olan INSERT komutu. Bu komut bir istisna oluşturmalıdır. Bu istisna, satır 15-16'da görülür ve açıktır. Bu istisna, randevuların teklik kısıtlamasının ihlali nedeniyle başlangıçta MySQL'in JDBC sürücüsü tarafından tetiklenir. Bundan, bu istisnaların JUnit test günlüklerinde görülmesi gerektiği sonucuna varılır. Ancak durum böyle değildir:
Testin istemci/sunucu mimarisini hatırlayalım:
![]() |
EJB [DAO] bir istisna oluşturduğunda, bu istisnanın istemciye ulaşabilmesi için serileştirilmesi gerekir. Muhtemelen bu işlem, benim anlayamadığım bir nedenden dolayı başarısız olmuştur. Tam uygulamamız istemci/sunucu mimarisinde çalışmayacağından, bu sorunu göz ardı edebiliriz.
Artık [DAO] katmanındaki EJB çalışır durumda olduğuna göre, [métier] katmanındaki EJB'e geçebiliriz.
3.5. [métier] katmanı
Şu anda geliştirilmekte olan uygulamanın mimarisine geri dönelim:
![]() |
EJB ve [métier] katmanları için yeni bir Maven projesi oluşturacağız. Yukarıda görüldüğü gibi, bu proje [DAO] ve [JPA] katmanları için oluşturulan Maven projesine bağımlı olacaktır.
3.5.1. NetBeans Projesi
EJB türünde yeni bir Maven projesi oluşturuyoruz. Bunun için, daha önce kullanılmış ve sayfa 174'te açıklanan prosedürü takip etmek yeterlidir.
![]() |
- [1]'te, [métier] katmanının Maven projesinde,
- [2]'e bir bağımlılık eklenir,
- [3]'te, [DAO] ve [JPA] katmanlarının Maven projesini seçin,
- [4]'te, [provided] kapsamı seçilir. Bunun, projenin derlenmesi için gerekli olduğu ancak çalıştırılması için gerekli olmadığı anlamına geldiğini hatırlatırız. Nitekim,[métier] katmanına ait EJB, [DAO] ve [JPA] katmanlarına ait EJB ile birlikte Glassfish sunucusuna dağıtılacaktır. Dolayısıyla, bu katman çalıştırıldığında, [DAO] ve [JPA] katmanlarına ait EJB katmanı zaten mevcut olacaktır,
![]() |
- [6]'te, bağımlılığıyla birlikte yeni proje.
Şimdi [métier] katmanının kaynak kodlarını inceleyelim:
![]() |
EJB [Metier], aşağıdaki [IMetier] arayüzüne sahip olacaktır:
package rdvmedecins.metier.service;
import java.util.Date;
import java.util.List;
import rdvmedecins.jpa.Client;
import rdvmedecins.jpa.Creneau;
import rdvmedecins.jpa.Medecin;
import rdvmedecins.jpa.Rv;
import rdvmedecins.metier.entites.AgendaMedecinJour;
public interface IMetier {
// DAO katmanı
// müşteri listesi
public List<Client> getAllClients();
// doktor listesi
public List<Medecin> getAllMedecins();
// bir doktorun randevu zaman aralıkları listesi
public List<Creneau> getAllCreneaux(Medecin medecin);
// belirli bir günde bir doktorun randevu listesi
public List<Rv> getRvMedecinJour(Medecin medecin, Date jour);
// kimlik numarasıyla bir müşteriyi bul
public Client getClientById(Long id);
// kimlik numarasıyla bir müşteriyi bulma
public Medecin getMedecinById(Long id);
// ID'siyle tanımlanan bir randevuyu bulma
public Rv getRvById(Long id);
// ID'siyle tanımlanan bir zaman aralığını bulma
public Creneau getCreneauById(Long id);
// bir RV ekle
public Rv ajouterRv(Date jour, Creneau creneau, Client client);
// RV'i sil
public void supprimerRv(Rv rv);
// meslek
public AgendaMedecinJour getAgendaMedecinJour(Medecin medecin, Date jour);
}
Bu arayüzü anlamak için projenin mimarisini hatırlamak gerekir:
![]() |
[DAO] katmanının arayüzünü tanımladık (3.4.4. paragraf) ve bunun [web] katmanının ihtiyaçlarını, yani kullanıcı ihtiyaçlarını karşıladığını belirttik. [web] katmanı, [DAO] katmanıyla yalnızca [métier] katmanı aracılığıyla iletişim kurar. Bu durum, [métier] katmanında [DAO] katmanının tüm yöntemlerinin bulunmasını açıklar. Bu yöntemler, [web] katmanından gelen talebi yalnızca [DAO] katmanına devredecektir. Hepsi bu kadar.
Uygulamanın incelenmesi sırasında bir ihtiyaç ortaya çıkmıştır: O günkü dolu ve boş randevu aralıklarını öğrenmek amacıyla, bir web sayfasında belirli bir gün için bir doktorun randevu takvimini görüntüleyebilmek. Bu durum, tipik olarak sekreterin telefonda bir talebe yanıt verdiği zaman ortaya çıkar. Sekretere, belirli bir gün için belirli bir doktorla randevu alınması istenir. Bu ihtiyacı karşılamak için, [métier] katmanı 46. satırdaki yöntemi sunar.
// meslek
public AgendaMedecinJour getAgendaMedecinJour(Medecin medecin, Date jour);
Bu yöntemin nereye yerleştirileceği sorusu akla gelebilir:
- [DAO] katmanına yerleştirilebilir. Ancak bu yöntem, aslında veri erişim ihtiyacını değil, daha çok bir iş ihtiyacını karşılar;
- [web] katmanına yerleştirilebilir. Ancak bu kötü bir fikir olur. Çünkü [web] katmanını [Swing] katmanına değiştirirsek, ihtiyaç hala mevcut olmasına rağmen yöntemi kaybederiz.
Yöntem, parametre olarak doktoru ve randevu takvimini görmek istediğimiz günü alır. Doktorun o günkü randevu takvimini temsil eden bir [AgendaMedecinJour] nesnesi döndürür:
package rdvmedecins.metier.entites;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import rdvmedecins.jpa.Medecin;
public class AgendaMedecinJour implements Serializable {
private static final long serialVersionUID = 1L;
// alanlar
private Medecin medecin;
private Date jour;
private CreneauMedecinJour[] creneauxMedecinJour;
// yapıcılar
public AgendaMedecinJour() {
}
public AgendaMedecinJour(Medecin medecin, Date jour, CreneauMedecinJour[] creneauxMedecinJour) {
this.medecin = medecin;
this.jour = jour;
this.creneauxMedecinJour = creneauxMedecinJour;
}
public String toString() {
StringBuffer str = new StringBuffer("");
for (CreneauMedecinJour cr : creneauxMedecinJour) {
str.append(" ");
str.append(cr.toString());
}
return String.format("Agenda[%s,%s,%s]", medecin, new SimpleDateFormat("dd/MM/yyyy").format(jour), str.toString());
}
// getter ve setter'lar
...
}
- 12. satır: takvimin ait olduğu doktor,
- 13. satır: takvimin ait olduğu gün,
- 14. satır: o gün için doktorun randevu zaman aralıkları.
- Sınıf, oluşturucular (satır 17, 21) ve uyarlanmış bir toString yöntemi (satır 27) içerir.
[CreneauMedecinJour] sınıfı (satır 14) şu şekildedir:
package rdvmedecins.metier.entites;
import java.io.Serializable;
import rdvmedecins.jpa.Creneau;
import rdvmedecins.jpa.Rv;
public class CreneauMedecinJour implements Serializable {
private static final long serialVersionUID = 1L;
// alanlar
private Creneau creneau;
private Rv rv;
// yapıcılar
public CreneauMedecinJour() {
}
public CreneauMedecinJour(Creneau creneau, Rv rv) {
this.creneau=creneau;
this.rv=rv;
}
// toString
@Override
public String toString() {
return String.format("[%s %s]", creneau,rv);
}
// getter ve setter'lar
...
}
- 12. satır: doktorun bir randevu saati,
- satır 13: ilgili randevu; zaman aralığı boşsa null.
Böylece, [AgendaMedecinJour] sınıfının 14. satırındaki creneauxMedecinJour alanı, doktorun tüm zaman dilimlerini her biri için "meşgul" veya "boş" bilgisiyle birlikte almamızı sağlar. [IMetier] arayüzünün yeni [getAgendaMedecinJour] yönteminin amacı da buydu.
[Metier] EJB'miz, ana arayüz olan [IMetier]'ten türetilen bir yerel arayüz ve bir uzak arayüze sahip olacaktır:
package rdvmedecins.metier.service;
import javax.ejb.Local;
@Local
public interface IMetierLocal extends IMetier{
}
package rdvmedecins.metier.service;
import javax.ejb.Remote;
@Remote
public interface IMetierRemote extends IMetier{
}
EJB ve [Metier], bu arayüzleri aşağıdaki şekilde uygular:
package rdvmedecins.metier.service;
import java.io.Serializable;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import javax.ejb.EJB;
import javax.ejb.Singleton;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import rdvmedecins.dao.IDaoLocal;
import rdvmedecins.jpa.Client;
import rdvmedecins.jpa.Creneau;
import rdvmedecins.jpa.Medecin;
import rdvmedecins.jpa.Rv;
import rdvmedecins.metier.entites.AgendaMedecinJour;
import rdvmedecins.metier.entites.CreneauMedecinJour;
@Singleton
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote, Serializable {
// DAO katmanı
@EJB
private IDaoLocal dao;
public Metier() {
}
@Override
public List<Client> getAllClients() {
return dao.getAllClients();
}
@Override
public List<Medecin> getAllMedecins() {
return dao.getAllMedecins();
}
@Override
public List<Creneau> getAllCreneaux(Medecin medecin) {
return dao.getAllCreneaux(medecin);
}
@Override
public List<Rv> getRvMedecinJour(Medecin medecin, Date jour) {
return dao.getRvMedecinJour(medecin, jour);
}
@Override
public Client getClientById(Long id) {
return dao.getClientById(id);
}
@Override
public Medecin getMedecinById(Long id) {
return dao.getMedecinById(id);
}
@Override
public Rv getRvById(Long id) {
return dao.getRvById(id);
}
@Override
public Creneau getCreneauById(Long id) {
return dao.getCreneauById(id);
}
@Override
public Rv ajouterRv(Date jour, Creneau creneau, Client client) {
return dao.ajouterRv(jour, creneau, client);
}
@Override
public void supprimerRv(Rv rv) {
dao.supprimerRv(rv);
}
@Override
public AgendaMedecinJour getAgendaMedecinJour(Medecin medecin, Date jour) {
// doktorun randevu saatleri listesi
List<Creneau> creneauxHoraires = dao.getAllCreneaux(medecin);
// aynı doktorun aynı gün için randevu listesi
List<Rv> reservations = dao.getRvMedecinJour(medecin, jour);
// Alınan randevulardan bir sözlük oluşturulur
Map<Long, Rv> hReservations = new Hashtable<Long, Rv>();
for (Rv resa : reservations) {
hReservations.put(resa.getCreneau().getId(), resa);
}
// istenen gün için takvim oluşturulur
AgendaMedecinJour agenda = new AgendaMedecinJour();
// doktor
agenda.setMedecin(medecin);
// gün
agenda.setJour(jour);
// randevu zaman aralıkları
CreneauMedecinJour[] creneauxMedecinJour = new CreneauMedecinJour[creneauxHoraires.size()];
agenda.setCreneauxMedecinJour(creneauxMedecinJour);
// rezervasyon aralıklarının doldurulması
for (int i = 0; i < creneauxHoraires.size(); i++) {
// takvim satırı
creneauxMedecinJour[i] = new CreneauMedecinJour();
// zaman aralığı kimliği
creneauxMedecinJour[i].setCreneau(creneauxHoraires.get(i));
// zaman aralığı boş mu yoksa rezerve mi?
if (hReservations.containsKey(creneauxHoraires.get(i).getId())) {
// zaman aralığı dolu - rezervasyon kaydediliyor
Rv resa = hReservations.get(creneauxHoraires.get(i).getId());
creneauxMedecinJour[i].setRv(resa);
}
}
// sonuç gönderiliyor
return agenda;
}
}
- 22. satırda, [Metier] sınıfı, EJB'in tek örneğidir;
- 23. satırda, EJB sınıfındaki her yöntem bir işlem içinde yürütülür. Bu, işlemin yöntemin başında, [métier] katmanında başladığı anlamına gelir. Bu katman, [DAO] katmanındaki yöntemleri çağıracaktır. Bunlar da aynı işlem kapsamında gerçekleştirilecektir,
- 24. satırda, EJB, yerel ve uzak arayüzlerini uygular ve ayrıca serileştirilebilir,
- 27. satır: [DAO] katmanından EJB'e bir referans,
- 29. satır: Bu, @EJB anotasyonu sayesinde Glassfish sunucusundaki EJB konteyneri tarafından enjekte edilecektir. Dolayısıyla, [Metier] sınıfının yöntemleri çalıştırıldığında, [DAO] katmanındaki EJB'e yapılan referans başlatılmış olur;
- 33-81. satırlar: Bu referans, [métier] katmanına yapılan çağrıyı [DAO] katmanına devretmek için kullanılır,
- 84. satır: Belirli bir gün için bir doktorun randevu takvimini almayı sağlayan getAgendaMedecinJour yöntemi. Yorumları okuyucunun kendisinin takip etmesini bırakıyoruz.
3.5.2. [métier] katmanının dağıtımı
[métier] katmanı, [DAO] katmanına bağımlıdır. Her katman, bir EJB ile uygulanmıştır. EJB ve [métier]'i test etmek için her iki EJB'i de dağıtmamız gerekiyor. Bunun için bir kurumsal projeye ihtiyacımız var.
![]() |
- [1] için yeni bir proje oluşturuyoruz;
- Maven türü [2] ve Kurumsal Uygulama türü [3],
- ve ona [4] adını veriyoruz. ear son eki otomatik olarak eklenecektir,
![]() |
- [5] olarak, Glassfish sunucusu ve Java EE 6 seçilir,
- [6]'e dönüştürülür; bir kurumsal uygulama modüller içerir; genellikle EJB modülleri ve web modülleri. Burada, kurumsal uygulama, oluşturduğumuz iki EJB modülünü içerecektir. Bu modüller mevcut olduğundan, kutuları işaretlemiyoruz,
- [7,8]'te iki proje oluşturulmuştur. [8], kullanacağımız kurumsal projedir. [7] ise işlevini bilmediğim bir projedir. Bu projeyi kullanmak zorunda kalmadım ve Maven'ı derinlemesine incelemediğim için ne işe yarayabileceğini bilmiyorum. Dolayısıyla bunu göz ardı edeceğiz.
Artık kurumsal proje oluşturulduğuna göre, bu projeye modüller tanımlayabiliriz.
![]() |
- [1]'te yeni bir bağımlılık oluşturuyoruz,
- [2]'te, EJB [DAO] projesini seçiyoruz,
- [3]'te bunun bir EJB olduğunu belirtilir. Türü boş bırakmayın, çünkü bu durumda jar türü kullanılacaktır ve burada bu tür uygun değildir,
- [4]'te, [compile] kapsamı kullanılır,
- [5]'te ise yeni bağımlılığıyla birlikte proje,
![]() |
- [6, 7, 8]'te, [métier] katmanından EJB'i eklemek için işlemi tekrarlıyoruz,
- [9]'e dönüştürüyoruz; her iki bağımlılığı da
- [10]'e, projeyi derliyoruz,
![]() |
- [11]'te proje çalıştırılır,
- [12]'te, [Services] sekmesinde, projenin Glassfish sunucusuna dağıtıldığını görüyoruz. Bu, her iki EJB'nin artık sunucuda mevcut olduğu anlamına gelir.
Glassfish sunucusunun günlüklerinde, her iki EJB'in dağıtımıyla ilgili bilgiler bulunur:
![]() |
- ve [1], Glassfish günlükleri sekmesinde.
Burada şu günlük kayıtları bulunur:
- 1-5. satırlar: JPA varlıkları tanındı,
- 7. satır: [dbrdvmedecins2-PU] kalıcılık biriminin oluşturulmasının başarılı olduğunu ve ilgili veritabanına bağlantının kurulduğunu gösterir,
- satır 8: EJB'in uzak ve yerel arayüzlerinin taşınabilir adları [DaoJpa] ve portable'tir; bu, tüm uygulama sunucuları tarafından tanındığı anlamına gelir,
- 9. satır: Aynı durum, ancak GlassFish'e özgü adlarla,
- satır 10-11: EJB ve [Metier] için de aynı durum geçerlidir.
EJB ve [Metier] uzaktan arayüzünün taşınabilir adını not edeceğiz:
java:global/istia.st_mv-rdvmedecins-metier-dao-ear_ear_1.0-SNAPSHOT/mv-rdvmedecins-ejb-metier-1.0-SNAPSHOT/Metier!rdvmedecins.metier.service.IMetierRemote
Buna, [métier] katmanını test ederken ihtiyacımız olacak.
3.5.3. [métier] katmanının testi
[DAO] katmanında yaptığımız gibi, [métier] katmanını da bir istemci/sunucu uygulaması kapsamında test edeceğiz:
![]() |
İstemci, Glassfish sunucusunda dağıtılmış olan EJB [Metier] katmanının uzak arayüzünü test edecektir.
İlk olarak yeni bir Maven projesi oluşturacağız. Bunun için, [dao] katmanının test projesini oluştururken izlediğimiz adımları takip edeceğiz (bkz. paragraf 3.4.7); ancak JUnit testinin oluşturulması hariç tutulacaktır. Bu şekilde oluşturulan proje şöyledir
![]() |
- [1]'te, bağımlılıklarıyla birlikte oluşturulan proje: [dao] katmanındaki EJB'e, [métier] katmanındaki EJB'e, [gf-client] kütüphanesinden.
Bu noktada, projenin [pom.xml] dosyası şu şekildedir:
<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-client-rdvmedecins-ejb-metier</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-client-rdvmedecins-ejb-metier</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.glassfish.appclient</groupId>
<artifactId>gf-client</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mv-rdvmedecins-ejb-dao-jpa</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mv-rdvmedecins-ejb-metier</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
17-33. satırlarda açıklanan bağımlılıkların mevcut olduğundan emin olun. Test, basit bir konsol sınıfı olacaktır:
![]() |
[ClientRdvMedecinsMetier] sınıfının kodu şu şekildedir:
package istia.st.client;
import java.util.Date;
import java.util.List;
import javax.naming.InitialContext;
import rdvmedecins.jpa.Client;
import rdvmedecins.jpa.Creneau;
import rdvmedecins.jpa.Medecin;
import rdvmedecins.jpa.Rv;
import rdvmedecins.metier.entites.AgendaMedecinJour;
import rdvmedecins.metier.service.IMetierRemote;
public class ClientRdvMedecinsMetier {
// EJB [Metier] uzaktaki arayüzün adı
private static String IDaoRemoteName = "java:global/istia.st_mv-rdvmedecins-metier-dao-ear_ear_1.0-SNAPSHOT/mv-rdvmedecins-ejb-metier-1.0-SNAPSHOT/Metier!rdvmedecins.metier.service.IMetierRemote";
// bugünün tarihi
private static Date jour = new Date();
public static void main(String[] args) {
try {
// Glassfish sunucusunun JNDI bağlamı
InitialContext initialContext = new InitialContext();
// uzak katman referansı [metier]
IMetierRemote metier = (IMetierRemote) initialContext.lookup(IDaoRemoteName);
// müşteri ekranı
List<Client> clients = metier.getAllClients();
display("Liste des clients :", clients);
// doktorlar ekranı
List<Medecin> medecins = metier.getAllMedecins();
display("Liste des médecins :", medecins);
// bir doktorun randevu aralıklarının görüntülenmesi
Medecin medecin = medecins.get(0);
List<Creneau> creneaux = metier.getAllCreneaux(medecin);
display(String.format("Liste des créneaux du médecin %s", medecin), creneaux);
// Belirli bir günde bir doktorun randevu listesi
display(String.format("Liste des rendez-vous du médecin %s, le [%s]", medecin, jour), metier.getRvMedecinJour(medecin, jour));
// takvim görüntüleme
AgendaMedecinJour agenda = metier.getAgendaMedecinJour(medecin, jour);
System.out.println(agenda);
// RV ekle
Rv rv = null;
Creneau creneau = creneaux.get(2);
Client client = clients.get(0);
System.out.println(String.format("Ajout d'un Rv le [%s] dans le créneau %s pour le client %s", jour, creneau, client));
rv = metier.ajouterRv(jour, creneau, client);
System.out.println("Rv ajouté");
display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), metier.getRvMedecinJour(medecin, jour));
// takvimi görüntüleme
agenda = metier.getAgendaMedecinJour(medecin, jour);
System.out.println(agenda);
// bir RV'i sil
System.out.println("Suppression du Rv ajouté");
metier.supprimerRv(rv);
System.out.println("Rv supprimé");
display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), metier.getRvMedecinJour(medecin, jour));
// takvim görüntüleme
agenda = metier.getAgendaMedecinJour(medecin, jour);
System.out.println(agenda);
} catch (Throwable ex) {
System.out.println("Erreur...");
while (ex != null) {
System.out.println(String.format("%s : %s", ex.getClass().getName(), ex.getMessage()));
ex = ex.getCause();
}
}
}
// yardımcı yöntem - bir koleksiyondaki öğeleri görüntüler
private static void display(String message, List elements) {
System.out.println(message);
for (Object element : elements) {
System.out.println(element);
}
}
}
- 18. satır: [Metier] EJB'sinin uzak arayüzünün taşınabilir adı GlassFish günlüklerinden alınmıştır,
- 24-27. satırlar: [Metier] EJB'sinin uzak arayüzüne bir referans alınmıştır,
- 29-30. satırlar: müşterileri gösterir,
- 32-33. satırlar: doktorları gösterir,
- 35-37. satırlar: bir doktorun randevu aralıklarını gösterir,
- 39. satır: belirli bir günde bir doktorun randevularını gösterir,
- 41-42. satırlar: aynı doktorun aynı gün için ajandasını gösterir,
- 44-49. satırlar: randevu eklenir,
- satır 50: doktorun randevuları görüntülenir. Bir tane daha olması gerekir,
- satır 52-53: doktorun ajandası görüntülenir. Eklenen randevu görünmelidir,
- 55-57. satırlar: az önce eklenen randevuyu siliyoruz,
- 58. satır: Bu değişiklik, doktorun randevu listesinde görünmelidir,
- 60-61. satırlar: ve takviminde de.
Testi çalıştırıyoruz:
![]() | ![]() |
Elde edilen ekran görüntüleri ( ) şunlardır:
Liste des clients :
Client[1,Mr,Jules,MARTIN]
Client[2,Mme,Christine,GERMAN]
Client[3,Mr,Jules,JACQUARD]
Client[4,Melle,Brigitte,BISTROU]
Liste des médecins :
Médecin[1,Mme,Marie,PELISSIER]
Médecin[2,Mr,Jacques,BROMARD]
Médecin[3,Mr,Philippe,JANDOT]
Médecin[4,Melle,Justine,JACQUEMOT]
Liste des créneaux du médecin Médecin[1,Mme,Marie,PELISSIER]
Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]]
Liste des créneaux du médecin Médecin[1,Mme,Marie,PELISSIER], le [Wed May 23 16:25:26 CEST 2012]
Agenda[Médecin[1,Mme,Marie,PELISSIER],23/05/2012, [Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]] null]]
Ajout d'un Rv le [Wed May 23 16:25:26 CEST 2012] dans le créneau Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] pour le client Client[1,Mr,Jules,MARTIN]
Rv ajouté
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Wed May 23 16:25:26 CEST 2012]
Rv[252, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Agenda[Médecin[1,Mme,Marie,PELISSIER],23/05/2012, [Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] Rv[252, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]] [Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]] null]]
Suppression du Rv ajouté
Rv supprimé
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Wed May 23 16:25:26 CEST 2012]
Agenda[Médecin[1,Mme,Marie,PELISSIER],23/05/2012, [Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]] null]]
- 37. satır: Bayan PELISSIER'in 23 Mayıs 2012 tarihli ajandası. Hiçbir zaman aralığı rezerve edilmemiştir,
- 39. satır: bir randevu eklenmiştir,
- 42. satır: Bayan PELISSIER'in yeni ajandası. Artık Bay MARTIN için bir zaman aralığı rezerve edilmiştir,
- 44. satır: randevu silindi,
- satır 46: Bayan PELISSIER'in takvimi, rezerve edilmiş zaman aralığı olmadığını gösteriyor.
Artık [DAO] ve [métier] katmanlarının çalışır durumda olduğunu varsayıyoruz. Geriye, [web] katmanını JSF çerçeve yapısı ile yazmak kalıyor. Bunun için, bu belgenin başında edindiğimiz bilgileri kullanacağız.
3.6. [web] katmanı
Şu anda oluşturulmakta olan mimariye geri dönelim:
![]() |
Son katmanı, yani [web] katmanını oluşturacağız.
3.6.1. NetBeans projesi
Bir Maven projesi oluşturuyoruz:
![]() |
- [1]'te yeni bir proje oluşturuyoruz,
- [2, 3]'te, [Web Application] türünde bir Maven projesi oluşturuyoruz,
- [4]'te ona bir isim veriyoruz,
![]() |
- [5]'te Glassfish sunucusunu ve Java EE 6 Web'i seçin,
- [6]'e, böylece oluşturulan proje,
- [7]'te, [index.jsp] sayfası ve [Source Packages]'te bulunan paket kaldırıldıktan sonra proje,
![]() |
- [8, 9]'te, proje özelliklerinde bir çerçeve eklenir,
- [10]'te Java Server Faces seçilir,
![]() |
- [11]'te, Java Server Faces yapılandırması yapılır. Varsayılan değerler korunur. Burada JSF 2'nin kullanıldığına dikkat edilir,
- [12]'te proje iki noktada değiştirilir: bir [web.xml] dosyası ve bir [index.html] sayfası oluşturulur.
[web.xml] dosyası şu şekildedir:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/index.xhtml</welcome-file>
</welcome-file-list>
</web-app>
Bu dosyayla daha önce karşılaşmıştık.
- 7-11. satırlar: uygulamaya yapılan tüm istekleri işleyecek servleti tanımlar. Bu, JSF servletidir,
- 12-15. satırlar: Bu servlet tarafından işlenecek URL'leri tanımlar. Bunlar /faces/* biçimindeki URL'lerdir,
- 21-23. satırlar: [index.xhtml] sayfasını ana sayfa olarak tanımlar.
Bu sayfa şöyledir:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
Hello from Facelets
</h:body>
</html>
Bunu daha önce görmüştük. Bu projeyi çalıştırabiliriz:
![]() |
- [1]'te, projeyi çalıştırıyoruz ve tarayıcıda [2] sonucunu alıyoruz.
Şimdi projenin tamamını sunacağız ve ardından farklı unsurlarını ayrıntılı olarak ele alacağız.
![]() |
- [1], projenin XHTML sayfaları,
- [2]'te Java kodları,
- [3]'te, uygulama uluslararasılaştırılmış olduğu için mesaj dosyaları,
![]() |
- [4]'e, projenin bağımlılıkları.
3.6.2. Projenin bağımlılıkları
Proje mimarisine geri dönelim:
![]() |
JSF katmanı, [métier], [DAO] ve [JPA] katmanlarına dayanmaktadır. Bu üç katman, oluşturduğumuz iki Maven projesinde kapsüllenmiştir; bu da [4] projesinin bağımlılıklarını açıklamaktadır. Basitçe, bu bağımlılıkların nasıl eklendiğini gösterelim:
![]() |
- [1] projesinde, bağımlılığın EJB projesine ait olduğunu belirtmek için ejb yazacağız;
- [2]'te ise [provided] yazacağız. Zira web projesi, EJB adlı iki projeyle aynı anda dağıtılacaktır. Bu nedenle, EJB'e ait JAR dosyalarını dahil etmesine gerek yoktur.
3.6.3. Projenin yapılandırması
Proje yapılandırması, bu belgenin başında incelediğimiz JSF projelerinkiyle aynıdır. Yapılandırma dosyalarını tekrar açıklamadan listeliyoruz.
![]() | ![]() |
[web.xml]: web uygulamasını yapılandırır.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Production</param-value>
</context-param>
<context-param>
<param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
<param-value>true</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/index.xhtml</welcome-file>
</welcome-file-list>
<error-page>
<error-code>500</error-code>
<location>/faces/exception.xhtml</location>
</error-page>
<error-page>
<exception-type>Exception</exception-type>
<location>/faces/exception.xhtml</location>
</error-page>
</web-app>
- satırda, [index.xhtml] sayfasının uygulamanın ana sayfası olduğu dikkat çekmektedir.
[faces-config.xml]: uygulamayı yapılandırır JSF
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
<message-bundle>messages</message-bundle>
</application>
</faces-config>
[beans.xml]: Boş olmakla birlikte @Named açıklaması için gereklidir
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>
[styles.css]: uygulamanın stil sayfası
.reservationsHeaders {
text-align: center;
font-style: italic;
color: Snow;
background: Teal;
}
.creneau {
height: 25px;
text-align: center;
background: MediumTurquoise;
}
.client {
text-align: left;
background: PowderBlue;
}
.action {
width: 6em;
text-align: left;
color: Black;
background: MediumTurquoise;
}
.erreursHeaders {
background: Teal;
background-color: #ff6633;
color: Snow;
font-style: italic;
text-align: center
}
.erreurClasse {
background: MediumTurquoise;
background-color: #ffcc66;
height: 25px;
text-align: center
}
.erreurMessage {
background: PowderBlue;
background-color: #ffcc99;
text-align: left
}
[messages_fr.properties]: Fransızca mesaj dosyası
# düzen
layout.entete=Les M\u00e9decins Associ\u00e9s
layout.basdepage=ISTIA, universit\u00e9 d'Angers
layout.entete.langue1=Fran\u00e7ais
layout.entete.langue2=Anglais
# istisna
exception.header=L'exception suivante s'est produite
exception.httpCode=Code HTTP de l'erreur
exception.message=Message de l'exception
exception.requestUri=Url demand\u00e9e lors de l'erreur
exception.servletName=Nom de la servlet demand\u00e9e lorsque l'erreur s'est produite
# form 1
form1.titre=R\u00e9servations
form1.medecin=M\u00e9decin
form1.jour=Jour (jj/mm/aaaa)
form1.button.agenda=Agenda
form1.jour.required=date requise
form1.jour.erreur=date erron\u00e9e
# form 2
form2.titre=Agenda de {0} {1} {2} le {3}
form2.titre_detail=Agenda de {0} {1} {2} le {3}
form2.creneauHoraire=Cr\u00e9neau horaire
form2.client=Client
form2.accueil=Accueil
form2.supprimer=Supprimer
form2.reserver=R\u00e9server
# form 3
form3.titre=Prise de rendez-vous de {0} {1} {2}, le {3} dans le cr\u00e9neau {4,number,#00}:{5,sayı,#00} - {6,sayı,#00}:{7,sayı,#00}
form3.titre_detail=Prise de rendez-vous de {0} {1} {2}, le {3} dans le cr\u00e9neau {4,number,#00}:{5,sayı,#00} - {6,sayı,#00}:{7,sayı,#00}
form3.client=Client
form3.valider=Valider
form3.annuler=Annuler
# hata
erreur.titre=Une erreur s'est produite.
erreur.message=Message d'erreur
erreur.accueil=Page d'accueil
erreur.classe=Cause
[messages_en.properties]: İngilizce mesaj dosyası
# düzen
layout.entete=Associated Doctors
layout.basdepage=ISTIA, Angers university
layout.entete.langue1=French
layout.entete.langue2=English
# istisna
exception.header=The following exceptions occurred
exception.httpCode=Error HTTP code
exception.message=Exception message
exception.requestUri=Url targeted when error occurred
exception.servletName=Servlet targeted's name when error occurred
# form 1
form1.titre=Reservations
form1.medecin=Doctor
form1.jour=Date (dd/mm/yyyy)
form1.button.agenda=Diary
form1.jour.required=The date is required
form1.jour.erreur=The date is invalid
# form 2
form2.titre={0} {1} {2}'' diary on {3}
form2.titre_detail={0} {1} {2}'' diary on {3}
form2.creneauHoraire=Time Period
form2.client=Client
form2.accueil=Welcome Page
form2.supprimer=Delete
form2.reserver=Reserve
# form 3
form3.titre=Reservation for {0} {1} {2}, on {3} in the time period {4,number,#00}:{5,sayı,#00} - {6,sayı,#00}:{7,sayı,#00}
form3.titre_detail=Reservation for {0} {1} {2}, on {3} in the time period {4,number,#00}:{5,sayı,#00} - {6,sayı,#00}:{7,sayı,#00}
form3.client=Client
form3.valider=Submit
form3.annuler=Cancel
# hata
erreur.titre=An error occurred
erreur.message=Error message
erreur.accueil=Welcome Page
erreur.classe=Cause
3.6.4. Projenin görünümleri
Uygulamanın işleyişini hatırlayalım. Ana sayfa şu şekildedir:
![]() |
Bu ilk sayfadan yola çıkarak kullanıcı (Sekreter, Doktor) bir dizi işlem gerçekleştirecektir. Bunları aşağıda sunuyoruz. Soldaki görünüm, kullanıcının talepte bulunduğu ekranı; sağdaki görünüm ise sunucu tarafından gönderilen yanıtı göstermektedir.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Son olarak, bir hata sayfası da görüntülenebilir:
![]() |
Bu farklı görünümler, web projesinin aşağıdaki sayfalarından elde edilir:
![]() |
- [1]'te, [basdepage, entete, layout] sayfaları tüm görünümlerin biçimlendirilmesini sağlar;
- [2] sayfasında ise [layout.xhtml] tarafından üretilen görünüm yer almaktadır.
Burada facelets teknolojisi kullanılmıştır. Bu teknoloji 2.11. paragrafında açıklanmıştır. Biz sadece sayfa düzeni için kullanılan XHTML sayfalarının kodunu veriyoruz:
[entete.xhtml]
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<body>
<h2><h:outputText value="#{msg['layout.entete']}"/></h2>
<div align="left">
<h:commandLink value="#{msg['layout.entete.langue1']}" actionListener="#{changeLocale.setFrenchLocale}"/>
<h:outputText value=" "/>
<h:commandLink value="#{msg['layout.entete.langue2']}" actionListener="#{changeLocale.setEnglishLocale}"/>
</div>
</body>
</html>
10-12. satırlarda, uygulamanın dilini değiştirmek için kullanılan iki bağlantıya dikkat edilmelidir.
[basdepage.xhtml]
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<body>
<h:outputText value="#{msg['layout.basdepage']}"/>
</body>
</html>
[layout.xhtml]
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<f:view locale="#{changeLocale.locale}">
<h:head>
<title>RdvMedecins</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<table style="width: 1200px">
<tr>
<td colspan="2" bgcolor="#ccccff">
<ui:include src="entete.xhtml"/>
</td>
</tr>
<tr>
<td style="width: 100px; height: 200px" bgcolor="#ffcccc">
</td>
<td>
<ui:insert name="contenu" >
<h2>Contenu</h2>
</ui:insert>
</td>
</tr>
<tr bgcolor="#ffcc66">
<td colspan="2">
<ui:include src="basdepage.xhtml"/>
</td>
</tr>
</table>
</h:form>
</h:body>
</f:view>
</html>
Bu sayfa, [index.xhtml] sayfasının şablonudur:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<ui:composition template="layout.xhtml">
<ui:define name="contenu">
<h:panelGroup rendered="#{form.form1Rendered}">
<ui:include src="form1.xhtml"/>
</h:panelGroup>
<h:panelGroup rendered="#{form.form2Rendered}">
<ui:include src="form2.xhtml"/>
</h:panelGroup>
<h:panelGroup rendered="#{form.form3Rendered}">
<ui:include src="form3.xhtml"/>
</h:panelGroup>
<h:panelGroup rendered="#{form.erreurRendered}">
<ui:include src="erreur.xhtml"/>
</h:panelGroup>
</ui:define>
</ui:composition>
</html>
8-21. satırlar, [layout.xhtml] (7. satır) içinde "içerik" (8. satır) olarak adlandırılan alanı tanımlar. Bu, görünümlerin orta kısmıdır:
![]() |
[index.xhtml] sayfası, uygulamanın tek sayfasıdır. Dolayısıyla sayfalar arasında herhangi bir gezinme olmayacaktır. Bu sayfa, dört [form1.xhtml, form2.xhtml, form3.xhtml, erreur.xhtml] sayfasından birini görüntüler. Bu görüntüleme, birazdan açıklayacağımız form bean'indeki dört boole değeri ([form1Rendered, form2Rendered, form3Rendered, erreurRendered]) tarafından kontrol edilir.
3.6.5. Projenin bean'leri
![]() |
[utils] paketindeki sınıflar daha önce tanıtılmıştır:
- [ChangeLocale] sınıfı, dil değiştirme işlevini sağlayan sınıftır. Bu sınıf daha önce incelenmiştir (2.4.4. paragraf).
- [Messages] sınıfı, bir uygulamanın mesajlarının uluslararasılaştırılmasını kolaylaştıran bir sınıftır. Bu sınıf, 2.8.5.7. paragrafında incelenmiştir.
3.6.5.1. Application bean'i
[Application] bean'i şöyledir:
package beans;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
import rdvmedecins.jpa.Client;
import rdvmedecins.jpa.Medecin;
import rdvmedecins.metier.service.IMetierLocal;
@Named(value = "application")
@ApplicationScoped
public class Application implements Serializable{
// iş katmanı
@EJB
private IMetierLocal metier;
// önbellek
private List<Medecin> medecins;
private List<Client> clients;
private Map<Long, Medecin> hMedecins = new HashMap<Long, Medecin>();
private Map<Long, Client> hClients = new HashMap<Long, Client>();
// hatalar
private List<Erreur> erreurs = new ArrayList<Erreur>();
private Boolean erreur = false;
public Application() {
}
@PostConstruct
public void init() {
// doktorlar ve müşteriler önbelleğe alınır
try {
medecins = metier.getAllMedecins();
clients = metier.getAllClients();
} catch (Throwable th) {
// hata kaydediliyor
erreur = true;
erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
while (th.getCause() != null) {
th = th.getCause();
erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
}
return;
}
// listelerin doğrulanması
if (medecins.size() == 0) {
// hata kaydediliyor
erreur = true;
erreurs.add(new Erreur("", "La liste des médecins est vide"));
}
if (clients.size() == 0) {
// hatayı not ediyoruz
erreur = true;
erreurs.add(new Erreur("", "La liste des clients est vide"));
}
// hata mı?
if (erreur) {
return;
}
// sözlükler
for (Medecin m : medecins) {
hMedecins.put(m.getId(), m);
}
for (Client c : clients) {
hClients.put(c.getId(), c);
}
}
// getter ve setter'lar
...
}
- 15-16. satırlar: [Application] sınıfı, Uygulama kapsamındaki bir bean'dir. Bu bean, JSF uygulamasının yaşam döngüsünün başında bir kez oluşturulur ve tüm kullanıcıların tüm istekleri tarafından erişilebilir durumdadır. Genellikle bu bean'e salt okunur veriler yerleştirilir. Burada, doktorların ve müşterilerin listelerini yerleştireceğiz. Dolayısıyla, bu listelerin sık sık değişmeyeceğini varsayıyoruz. XHTML sayfaları, application adı aracılığıyla bu listeye erişir,
- 20-21. satırlar: EJB ve [Metier]'in yerel arayüzüne bir referans, Glassfish'in EJB konteyneri tarafından eklenecektir. Uygulamanın mimarisini hatırlayalım:
![]() |
JSF uygulaması ile EJB ve [Metier] uygulamaları aynı JVM (Java Sanal Makinesi) içinde çalışacaktır. Dolayısıyla [JSF] katmanı, EJB'in yerel arayüzünü kullanacaktır. Burada uygulama bean'i, EJB ve [Metier]'i kullanmaktadır. Öyle olmasa bile, [métier] katmanında buna bir referans bulunması normal olurdu. Bu, aslında tüm kullanıcıların tüm istekleri tarafından paylaşılabilecek bir bilgidir; dolayısıyla Application kapsamındaki bir veridir.
- 34-35. satırlar: init yöntemi, [Application] sınıfının örneklendirilmesinden hemen sonra yürütülür (@PostConstruct anotasyonunun varlığı),
- 36-73. satırlarda, yöntem şu öğeleri oluşturur: 23. satırdaki doktorlar listesi, 24. satırdaki müşteriler listesi, 25. satırda kimlik numaralarına göre indekslenmiş doktorlar sözlüğü ve 26. satırda müşteriler için aynısı. Hatalar meydana gelebilir. Bu hatalar 28. satırdaki listeye kaydedilir.
[Erreur] sınıfı şu şekildedir:
package beans;
public class Erreur {
public Erreur() {
}
// alan
private String classe;
private String message;
// yapıcı
public Erreur(String classe, String message){
this.setClasse(classe);
this.message=message;
}
// getter ve setter'lar
...
}
- 9. satır, bir istisna atılmışsa istisna sınıfının adı,
- 10. satır: bir hata mesajı.
3.6.5.2. [Form] bean'i
Kodu şu şekildedir:
package beans;
...
@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
public Form() {
}
// Uygulama bean'i
@Inject
private Application application;
// model
private Long idMedecin;
private Date jour = new Date();
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean form3Rendered = false;
private Boolean erreurRendered = false;
private String form2Titre;
private String form3Titre;
private AgendaMedecinJour agendaMedecinJour;
private Long idCreneau;
private Medecin medecin;
private Client client;
private Long idClient;
private CreneauMedecinJour creneauChoisi;
private List<Erreur> erreurs;
@PostConstruct
private void init() {
// başlatma işlemi başarılı oldu mu?
if (application.getErreur()) {
// hata listesi alınır
erreurs = application.getErreurs();
// hata görünümü görüntüleniyor
setForms(false, false, false, true);
}
}
// görünümün görüntülenmesi
private void setForms(Boolean form1Rendered, Boolean form2Rendered, Boolean form3Rendered, Boolean erreurRendered) {
this.form1Rendered = form1Rendered;
this.form2Rendered = form2Rendered;
this.form3Rendered = form3Rendered;
this.erreurRendered = erreurRendered;
}
.................................................
}
- 5-7. satırlar: [Form] sınıfı, adı "form" olan ve kapsamı oturum olan bir bean'dir. Bu durumda sınıfın serileştirilebilir olması gerektiğini hatırlatırız.
- 13-14. satırlar: form bean'i, uygulama bean'ine bir referansa sahiptir. Bu referans, uygulamanın çalıştığı servlet konteyneri tarafından enjekte edilecektir (@Inject anotasyonunun varlığı).
- 17-31. satırlar: [form1.xhtml, form2.xhtml, form3.xhtml, erreur.xhtml] sayfalarının şablonu. Bu sayfaların görüntülenmesi, 19-22. satırlardaki boole değerleri tarafından kontrol edilir. Varsayılan olarak [form1.xhtml] sayfasının görüntülendiğine dikkat edilmelidir,
- 33-34. satırlar: init yöntemi, sınıfın örneklendirilmesinden hemen sonra yürütülür (@PostConstruct anotasyonunun varlığı),
- 35-41. satırlar: init yöntemi, hangi sayfanın ilk olarak görüntülenmesi gerektiğini belirlemek için kullanılır: normalde [form1.xhtml] sayfası (satır 19), ancak uygulamanın başlatılması başarısız olursa (satır 36), bu durumda [erreur.xhtml] sayfası görüntülenir (satır 40).
[erreur.xhtml] sayfası şöyledir:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<body>
<h2><h:outputText value="#{msg['erreur.titre']}"/></h2>
<p>
<h:commandButton value="#{msg['erreur.accueil']}" actionListener="#{form.accueil()}"/>
</p>
<hr/>
<h:dataTable value="#{form.erreurs}" var="erreur" headerClass="erreursHeaders" columnClasses="erreurClasse,erreurMessage">
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['erreur.classe']}"/>
</f:facet>
<h:outputText value="#{erreur.classe}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['erreur.message']}"/>
</f:facet>
<h:outputText value="#{erreur.message}"/>
</h:column>
</h:dataTable>
</body>
</html>
Hata listesini görüntülemek için <h:dataTable> etiketini (satır 14-27) kullanır. Bu, aşağıdakine benzer bir sayfa oluşturur:

Şimdi uygulamanın yaşam döngüsünün farklı aşamalarını tanımlayacağız.
3.6.6. Sayfalar ve model arasındaki etkileşimler
3.6.6.1. Ana sayfanın görüntülenmesi
Her şey yolunda giderse, görüntülenen ilk sayfa [form1.xhtml] olur. Bu, aşağıdaki görünümü verir:
![]() |
[form1.xhtml] sayfası şöyledir:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<body>
<h2><h:outputText value="#{msg['form1.titre']}"/></h2>
<h:panelGrid columns="3">
<h:panelGroup>
<div align="center"><h3><h:outputText value="#{msg['form1.medecin']}"/></h3></div>
</h:panelGroup>
<h:panelGroup>
<div align="center"><h3><h:outputText value="#{msg['form1.jour']}"/></h3></div>
</h:panelGroup>
<h:panelGroup/>
<h:selectOneMenu value="#{form.idMedecin}">
<f:selectItems value="#{form.medecins}" var="medecin" itemLabel="#{medecin.titre} #{medecin.prenom} #{medecin.nom}" itemValue="#{medecin.id}"/>
</h:selectOneMenu>
<h:inputText id="jour" value="#{form.jour}" required="true" requiredMessage="#{msg['form1.jour.required']}" converterMessage="#{msg['form1.jour.erreur']}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
</h:inputText>
<h:message for="jour" styleClass="error"/>
</h:panelGrid>
<h:commandButton value="#{msg['form1.button.agenda']}" actionListener="#{form.getAgenda}"/>
</body>
</html>
Bu sayfa aşağıdaki şablonla oluşturulmuştur:
@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
// Uygulama bean'i
@Inject
private Application application;
// şablon
private Long idMedecin;
private Date jour = new Date();
// doktor listesi
public List<Medecin> getMedecins() {
return application.getMedecins();
}
// takvim
public void getAgenda() {
...
}
- 9. satırdaki alan, sayfanın 18. satırındaki listenin değerini okuma ve yazma işlemleriyle besler. Sayfa ilk görüntülendiğinde, açılır menüde seçili değeri sabitler. İlk görüntülenişte, idMedecin, null'e eşittir; dolayısıyla ilk doktor seçilecektir,
- 13-15. satırlardaki yöntem, doktorlar açılır menüsünün öğelerini oluşturur (sayfanın 19. satırı). Oluşturulan her seçeneğin etiketi (itemLabel) doktorun unvanı, soyadı ve adı olacak ve değeri (itemValue) ise doktorun kimliği olacaktır,
- 10. satırdaki alan, sayfanın 21. satırındaki giriş alanına okuma/yazma yoluyla veri sağlar. İlk görüntülemede, bu nedenle günün tarihi görüntülenir,
- 17-19. satırlar: getAgenda yöntemi, sayfanın 26. satırındaki [Agenda] düğmesine yapılan tıklamayı yönetir. Gezinti olmadığı için (her zaman [index.html] sayfası talep edilir), genellikle action özniteliği yerine actionListener özniteliği kullanılır. Bu durumda, şablonda çağrılan yöntem hiçbir sonuç döndürmez.
[Agenda] düğmesine tıklandığında,
- bazı değerler gönderilir: doktorlar açılır menüsünden seçilen değer, şablonun idMedecin alanına kaydedilir ve gün alanından seçilen gün,
- modeldeki getAgenda yöntemi çağrılır.
getAgenda yöntemi şu şekildedir:
// Uygulama bean'i
@Inject
private Application application;
// şablon
private Long idMedecin;
private Date jour = new Date();
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean form3Rendered = false;
private Boolean erreurRendered = false;
private String form2Titre;
private AgendaMedecinJour agendaMedecinJour;
private Medecin medecin;
private List<Erreur> erreurs;
// takvim
public void getAgenda() {
try {
// doktor bilgisi alınır
medecin = application.gethMedecins().get(idMedecin);
// form başlığı 2
form2Titre = Messages.getMessage(null, "form2.titre", new Object[]{medecin.getTitre(), medecin.getPrenom(), medecin.getNom(), new SimpleDateFormat("dd MMM yyyy").format(jour)}).getSummary();
// belirli bir gün için doktorun randevu takvimi
agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
// form 2 görüntüleniyor
setForms(false, true, false, false);
} catch (Throwable th) {
// hataların görünümü
prepareVueErreur(th);
}
}
// hazırlık vueErreur
private void prepareVueErreur(Throwable th) {
// hata listesi oluşturuluyor
erreurs = new ArrayList<Erreur>();
erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
while (th.getCause() != null) {
th = th.getCause();
erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
}
// hata görünümü görüntülenir
setForms(false, false, false, true);
}
getAgenda yönteminin neyi görüntülemesi gerektiğini hatırlayalım:
![]() |
- 21. satır: application bean'inde depolanan doktor sözlüğünden seçilen doktor alınır. Bunun için idMedecin'e gönderilen kimlik numarası kullanılır,
- 23. satır: Görüntülenecek olan [form2.xhtml] sayfasının başlığı hazırlanıyor. Bu mesaj, uluslararasılaştırılabilmesi için mesaj dosyasından alınır. Bu teknik, 135. sayfadaki 2.8.5.7 numaralı paragrafta açıklanmıştır.
- 25. satır: Seçilen gün için seçilen doktorun randevu takvimini hesaplamak üzere [métier] katmanına başvurulur,
- 27. satır: [form2.xhtml] görüntülenir,
- 28. satır: Bir istisna oluşursa, bir hata listesi oluşturulur (37-42. satırlar) ve [erreur.xhtml] sayfası görüntülenir (44. satır).
3.6.6.2. Bir doktorun randevu takvimini görüntüleme
[form2.xhtml] sayfası aşağıdaki görünüme karşılık gelir:
![]() |
[form2.xhtml] sayfasının kodu şöyledir:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<body>
<h2><h:outputText value="#{form.form2Titre}"/></h2>
<h:commandButton value="#{msg['form2.accueil']}" action="#{form.accueil}" />
<h:dataTable value="#{form.agendaMedecinJour.creneauxMedecinJour}" var="creneauMedecinJour" headerClass="reservationsHeaders" columnClasses="creneau,client,action">
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['form2.creneauHoraire']}"/>
</f:facet>
<h:outputText value="#{creneauMedecinJour.creneau.hdebut}:#{creneauMedecinJour.creneau.mdebut} - #{creneauMedecinJour.creneau.hfin}:#{creneauMedecinJour.creneau.mfin}" />
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['form2.client']}"/>
</f:facet>
<c:if test="#{creneauMedecinJour.rv==null}">
<h:outputText value=""/>
<c:otherwise>
<h:outputText value="#{creneauMedecinJour.rv.client.titre} #{creneauMedecinJour.rv.client.prenom} #{creneauMedecinJour.rv.client.nom}"/>
</c:otherwise>
</c:if>
</h:column>
<h:column>
<f:facet name="header"/>
<h:commandLink action="#{form.action()}" value="#{creneauMedecinJour.rv==null ? msg['form2.reserver'] : msg['form2.supprimer']}">
<f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneau}"/>
</h:commandLink>
</h:column>
</h:dataTable>
</body>
</html>
Hatırlanacağı üzere, getAgenda yöntemi şablonda iki alanı başlatmıştır:
// şablon
private String form2Titre;
private AgendaMedecinJour agendaMedecinJour;
Bu iki alan, [form2.xhtml] sayfasına veri sağlar:
- 10. satır, sayfanın başlığı,
- 12. satır: doktorun randevu takvimi, üç sütunlu bir <h:dataTable> etiketiyle görüntülenir,
- 13-18. satırlar: ilk sütun randevu zaman aralıklarını gösterir,
- 19-30. satırlar: ikinci sütun, randevu aralığını rezerve etmiş olan müşterinin adını gösterir; aksi takdirde boş kalır. Bu seçimi yapmak için, 7. satırda referans verilen JSTL Core kütüphanesindeki etiketler kullanılır,
- satır 30-35: üçüncü sütun, zaman aralığı boşsa [Réserver] bağlantısını, zaman aralığı doluysa [Supprimer] bağlantısını gösterir.
Üçüncü sütundaki bağlantılar aşağıdaki şablona bağlıdır:
// şablon
private Long idCreneau;
// RV üzerinde işlem
public void action() {
...
}
- Kullanıcı "Rezervasyon Yap / Sil" bağlantısına tıkladığında (satır 32) action yöntemi çağrılır. Burada action özniteliğinin kullanıldığına dikkat edilmelidir. Bu özniteliğin işaret ettiği yöntemin String action() imzasına sahip olması gerekir, çünkü yöntemin bir gezinme anahtarı döndürmesi gerekir. Oysa burada yöntem void action() şeklindedir. Bu durum bir hata oluşturmamıştır ve bu durumda gezinme olmadığı varsayılabilir. İstenen de buydu. action yerine actionListener yazılması bir arızaya neden oluyordu;
- 2. satırdaki idCreneau alanı, tıklanan bağlantının zaman aralığı kimliğini alır (sayfanın 33. satırı).
3.6.6.3. Randevuyu Silme
Bir randevunun silinmesini yöneten kodu inceleyelim. Bu, aşağıdaki görünüm dizisine karşılık gelir:
![]() |
Bu işlemle ilgili kod şöyledir:
// Uygulama bean'i
@Inject
private Application application;
// şablon
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean form3Rendered = false;
private Boolean erreurRendered = false;
private AgendaMedecinJour agendaMedecinJour;
private Long idCreneau;
private CreneauMedecinJour creneauChoisi;
private List<Erreur> erreurs;
// RV üzerinde işlem
public void action() {
// takvimde uygun zaman aralığı aranıyor
int i = 0;
Boolean trouvé = false;
while (!trouvé && i < agendaMedecinJour.getCreneauxMedecinJour().length) {
if (agendaMedecinJour.getCreneauxMedecinJour()[i].getCreneau().getId() == idCreneau) {
trouvé = true;
} else {
i++;
}
}
// bulundu mu?
if (!trouvé) {
// bu garip - form2'yi yeniden görüntülüyoruz
setForms(false, true, false, false);
return;
}
// bulundu
creneauChoisi = agendaMedecinJour.getCreneauxMedecinJour()[i];
// istenen eyleme göre
if (creneauChoisi.getRv() == null) {
reserver();
} else {
supprimer();
}
}
// rezervasyon
public void reserver() {
...
}
public void supprimer() {
try {
// Randevu silme
application.getMetier().supprimerRv(creneauChoisi.getRv());
// takvim güncelleniyor
agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
// form2 görüntüleniyor
setForms(false, true, false, false);
} catch (Throwable th) {
// hatalar sayfası
prepareVueErreur(th);
}
}
- 16. satır: action yöntemi başlatıldığında, seçilen zaman aralığının kimliği idCreneau'e (11. satır) kaydedilmiştir,
- 18-26. satırlar: Zaman aralığı, id (21. satır) dosyasından alınmaya çalışılır. Bu zaman aralığı, 10. satırdaki güncel takvim dosyası agendaMedecinJour'te aranır. Normalde bulunması gerekir. Bulunmazsa, herhangi bir işlem yapılmaz (28-32. satırlar),
- satır 34: Aranan zaman aralığı bulunursa, referans numarası alınır ve satır 12'ye kaydedilir,
- 36. satır: Seçilen zaman aralığında bir randevu olup olmadığına bakılır. Varsa, silinir (39. satır), yoksa bir randevu rezerve edilir (37. satır),
- satır 51: Seçilen zaman aralığındaki randevu silinir. Bu işlemi [métier] katmanı gerçekleştirir,
- satır 53: [métier] katmanından doktorun yeni randevu takvimini isteriz. Elbette burada bir randevu eksik olduğunu göreceğiz. Ancak uygulama çok kullanıcılı olduğu için, diğer kullanıcılar tarafından yapılan değişiklikleri de görebiliriz,
- 55. satır: [form2.xhtml] sayfası yeniden görüntülenir,
- 58. satır: [métier] katmanı çağrıldığı için istisnalar ortaya çıkabilir. Bu durumda, istisna yığını 13. satırdaki hata listesine kaydedilir ve [erreur.xhtml] görünümü kullanılarak görüntülenir.
3.6.6.4. Randevu Alma
Randevu alma işlemi aşağıdaki sırayla gerçekleşir:
![]() |
Bu işlemde kullanılan şablon şöyledir:
// şablon
private Date jour = new Date();
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean form3Rendered = false;
private Boolean erreurRendered = false;
private String form3Titre;
private AgendaMedecinJour agendaMedecinJour;
private Medecin medecin;
private CreneauMedecinJour creneauChoisi;
private List<Erreur> erreurs;
// RV üzerinde işlem
public void action() {
...
// bulundu
creneauChoisi = agendaMedecinJour.getCreneauxMedecinJour()[i];
// istenen eyleme göre
if (creneauChoisi.getRv() == null) {
reserver();
} else {
supprimer();
}
}
// rezervasyon
public void reserver() {
try {
// form başlığı 3
form3Titre = Messages.getMessage(null, "form3.titre", new Object[]{medecin.getTitre(), medecin.getPrenom(), medecin.getNom(), new SimpleDateFormat("dd MMM yyyy").format(jour),
creneauChoisi.getCreneau().getHdebut(), creneauChoisi.getCreneau().getMdebut(), creneauChoisi.getCreneau().getHfin(), creneauChoisi.getCreneau().getMfin()}).getSummary();
// açılır menüden seçilen müşteri
idClient=null;
// form 3 görüntüleniyor
setForms(false, false, true, false);
} catch (Throwable th) {
// hata sayfası
prepareVueErreur(th);
}
}
- 14. satır: Seçilen zaman aralığında randevu yoksa, bu bir rezervasyondur,
- 30. satır: [form3.xhtml] sayfasının başlığı, [form2.xhtml] sayfasının başlığı için kullanılan teknikle aynı şekilde hazırlanır,
- satır 34: Bu formda, değeri idClient tarafından doldurulan bir açılır menü bulunmaktadır. Kimseyi seçmemek için bu alanın değerini null olarak ayarlıyoruz,
- 36. satır: [form3.xhtml] sayfasını görüntüleriz,
- satır 39: bir istisna oluşmuşsa hata sayfası görüntülenir.
[form3.xhtml] sayfası şöyledir:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<body>
<h2><h:outputText value="#{form.form3Titre}"/></h2>
<h:panelGrid columns="2">
<h:outputText value="#{msg['form3.client']}"/>
<h:selectOneMenu value="#{form.idClient}">
<f:selectItems value="#{form.clients}" var="client" itemLabel="#{client.titre} #{client.prenom} #{client.nom}" itemValue="#{client.id}"/>
</h:selectOneMenu>
<h:panelGroup>
<h:commandButton value="#{msg['form3.valider']}" actionListener="#{form.validerRv}" />
<h:commandButton value="#{msg['form3.annuler']}" actionListener="#{form.annulerRv}"/>
</h:panelGroup>
</h:panelGrid>
</body>
</html>
Bu sayfa aşağıdaki şablonla oluşturulur:
// Uygulama bean'i
@Inject
private Application application;
// şablon
private Long idClient;
// müşteri listesi
public List<Client> getClients() {
return application.getClients();
}
- 6. satır: Müşteri numarası, sayfanın 12. satırındaki müşteri açılır menüsünün value özniteliğine değer atar. Seçili açılır menü öğesini sabitler,
- 9-11. satırlar: getClients yöntemi, açılır menünün içeriğini doldurur (13. satır). Her seçeneğin etiketi (itemLabel), müşterinin [Titre Prénom Nom] değeridir ve ilişkili değer (itemValue) ise müşterinin kimliğidir. Dolayısıyla, gönderilecek olan değer budur.
3.6.6.5. Randevunun Onaylanması
Bir randevunun onaylanması aşağıdaki sırayla gerçekleştirilir:
![]() |
ve [Valider] düğmesine tıklanmasına karşılık gelir:
<h:commandButton value="#{msg['form3.valider']}" actionListener="#{form.validerRv}" />
Dolayısıyla, bu olayı yönetecek olan yöntem [Form].validerRv'tir. Kod şu şekildedir:
// Uygulama bean'i
@Inject
private Application application;
// şablon
private Date jour = new Date();
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean form3Rendered = false;
private Boolean erreurRendered = false;
private Long idCreneau;
private Long idClient;
private List<Erreur> erreurs;
// Rv doğrulaması
public void validerRv() {
try {
// seçilen zaman aralığının bir örneğini alıyoruz
Creneau creneau = application.getMetier().getCreneauById(idCreneau);
// Rv eklenir
application.getMetier().ajouterRv(jour, creneau, application.gethClients().get(idClient));
// takvimi güncellenir
agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
// form2 görüntüleniyor
setForms(false, true, false, false);
} catch (Throwable th) {
// hatalar ekranı
prepareVueErreur(th);
}
}
- 12. satır: validerRv yöntemi çalıştırılmadan önce, idClient alanına kullanıcı tarafından seçilen müşterinin kimliği aktarılmıştır,
- 19. satır: Bir önceki adımda saklanan zaman aralığı kimliği temel alınarak (bean oturum kapsamındadır), [métier] katmanından zaman aralığının kendisine ait bir referans istenir,
- 21. satır: [métier] katmanından, seçilen gün (gün), seçilen zaman aralığı (zaman aralığı) ve seçilen müşteri (idClient) için bir randevu eklenmesi istenir,
- 23. satır: [métier] katmanından doktorun takvimini yenilemesi istenir. Eklenen randevu ile birlikte, uygulamanın diğer kullanıcıları tarafından yapılmış olabilecek tüm değişiklikler görüntülenecektir,
- 25. satır: [form2.xhtml] takvimini yeniden görüntüleriz,
- 28. satır: Bir hata oluşursa hata sayfası görüntülenir.
3.6.6.6. Randevu İptali
Bu, aşağıdaki sıraya karşılık gelir:
![]() |
[form3.xhtml] sayfasındaki [Annuler] düğmesi şöyledir:
<h:commandButton value="#{msg['form3.annuler']}" actionListener="#{form.annulerRv}"/>
Böylece [Form].annulerRv yöntemi çağrılır:
// Randevu iptal edilir
public void annulerRv() {
// form2 görüntülenir
setForms(false, true, false, false);
}
3.6.6.7. Ana sayfaya dön
İncelenmesi gereken bir eylem daha var, o da şu dizideki eylemdir:
![]() |
[form2.xhtml] sayfasındaki [Accueil] düğmesinin kodu şöyledir:
<h:commandButton value="#{msg['form2.accueil']}" action="#{form.accueil}" />
[Form].accueil yöntemi şöyledir:
public void accueil() {
// ana sayfayı görüntülüyor
setForms(true, false, false, false);
}
3.7. Conclusion
Aşağıdaki uygulamayı oluşturduk:
![]() |
Uygulamanın kullanıcı arayüzünden çok işlevlerine odaklandık. Kullanıcı arayüzü, PrimeFaces bileşen kütüphanesi kullanılarak iyileştirilecektir. EJB bileşenlerini kullanarak, katmanlı bir Java mimarisini temsil eden basit bir uygulama geliştirdik: EE. Uygulama çeşitli şekillerde geliştirilebilir:
- kimlik doğrulama gereklidir. Herkesin randevu ekleme/silme yetkisi yoktur,
- boş randevu aralıklarının olduğu bir gün ararken takvimi ileri ve geri kaydırabilmeliyiz,
- bir doktor için boş zaman aralıklarının bulunduğu günlerin listesi istenebilmelidir. Zira, söz konusu doktor bir göz doktoruysa randevuları genellikle altı ay önceden dolmaktadır,
- ...
3.8. Eclipse ile testler
3.8.1. [DAO] katmanı
![]() |
- [1] katmanında, [DAO] katmanındaki EJB projesini ve müşterisini içe aktarırsak,
- [2]'te, [DAO] katmanından EJB projesini seçip çalıştırıyoruz; [3]'te,
- [4]'te, bunu bir sunucuda çalıştırırız,
![]() |
- [5]'te, EJB konteynerine sahip tek sunucu Glassfish olduğu için sadece bu sunucu önerilir,
- [6]'te, EJB modülü dağıtılmıştır,
![]() |
- [7]'te, günlükler görüntülenir:
Bunlar NetBeans'te de gördüğümüz günlükler.
![]() |
- [7A] ve [7B]'te, JUnit istemci testi yürütülür,
![]() |
- [8]'te test başarılı oldu,
- [9]'te konsol günlükleri.
![]() |
[10]'te, EJB uygulaması kaldırılıyor.
3.8.2. [métier] katmanı
![]() |
- [1]'te, [métier] katmanındaki dört Maven projesini içe aktarıyoruz,
- [2]'te kurumsal proje seçilir ve çalıştırılır; [3]'te, bir Glassfish sunucusunda [4] ve [5],
![]() |
- [6]'te, kurumsal proje Glassfish'e dağıtıldı,
![]() |
- ve [7]'te, Glassfish günlüklerini inceliyoruz,
- satırda, EJB [Metier]'in taşınabilir adını not ediyoruz ve bunu bu EJB'in konsol istemcisine yapıştırıyoruz:
public class ClientRdvMedecinsMetier {
// EJB [Metier] uzaktaki arayüzün adı
private static String IDaoRemoteName = "java:global/mv-rdvmedecins-metier-dao-ear/mv-rdvmedecins-ejb-metier-1.0-SNAPSHOT/Metier!rdvmedecins.metier.service.IMetierRemote";
// bugünün tarihi
private static Date jour = new Date();
![]() |
- [8]'te konsol istemcisini çalıştırın,
- [9]'te ise günlükleri inceleyelim.
![]() |
- [10]'te kurumsal uygulama kaldırılır;
3.8.3. [web] katmanı
![]() |
- [1]'te, [web] katmanındaki üç Maven projesini içe aktarıyoruz. Sonunda "ear" uzantısı bulunan proje, Glassfish üzerinde dağıtılması gereken kurumsal projedir,
- [2]'te bu proje çalıştırılır,
![]() |
- Glassfish sunucusunda [3],
- [4]'te kurumsal uygulama başarıyla dağıtılmıştır,
![]() |
- [5]'te, Eclipse'in dahili tarayıcısında uygulamanın URL'i çağrılır.


























































































































