14. 3 katmanlı mimaride MVC web uygulaması – Örnek 1
14.1. Présentation
Şimdiye kadar, sadece eğitim amaçlı örneklerle yetindik. Bu nedenle, bu örneklerin basit olması gerekiyordu. Şimdi, temel düzeyde ancak şimdiye kadar sunulan tüm örneklerden daha zengin bir uygulama sunuyoruz. Bu uygulamanın özelliği, 3 katmanlı mimarinin üç katmanını da kullanması olacaktır:

Okuyucunun, 3 katmanlı bir mimaride bir web uygulamasının MVC ilkelerini unutmuşsa, 4. paragrafta bunları tekrar gözden geçirmesi önerilir.
Yazacağımız web uygulaması, dört işlemle bir grup kişiyi yönetmeye olanak sağlayacaktır:
- grup üyelerinin listesi
- gruba kişi ekleme
- gruptaki bir kişinin bilgilerinin değiştirilmesi
- gruptan bir kişinin silinmesi
Bu dört temel işlemi, bir veritabanı tablosundaki işlemlerle özdeşleştirebiliriz. Bu uygulamanın iki sürümünü yazacağız:
- 1. sürümde, [dao] katmanı bir veritabanı kullanmayacaktır. Gruptaki kişiler, [dao] katmanı tarafından dahili olarak yönetilen basit bir [ArrayList] nesnesinde depolanacaktır. Bu, okuyucunun veritabanı kısıtlaması olmadan uygulamayı test etmesini sağlayacaktır.
- Sürüm 2'de, kişi grubunu bir veritabanı tablosuna yerleştireceğiz. Bunun, sürüm 1'in web katmanını etkilemeden gerçekleştirileceğini ve bu katmanın değişmeden kalacağını göstereceğiz.
Aşağıdaki ekran görüntüleri , uygulamanın kullanıcıyla karşılıklı olarak görüntülediği sayfaları göstermektedir.



![]() |
![]() |
14.2. Eclipse Projesi
Uygulama projesinin adı [personnes-01]'tir:

Bu proje, uygulamanın 3 katmanlı mimarisinin üç katmanını da kapsamaktadır:
![]() |
- [dao] katmanı, [istia.st.mvc.personnes.dao] paketinde yer almaktadır
- [metier] veya [service] katmanı, [istia.st.mvc.personnes.service] paketinde yer almaktadır
- [web] veya [ui] katmanı, [istia.st.mvc.personnes.web] paketinde yer almaktadır
- [istia.st.mvc.personnes.entites] paketi, farklı katmanlar arasında paylaşılan nesneleri içerir
- [istia.st.mvc.personnes.tests] paketi, [dao] ve [service] katmanlarının Junit testlerini içerir
[dao], [service] ve [web] katmanlarını sırasıyla inceleyeceğiz. Yazması çok uzun sürer ve okuması da sıkıcı olabilir, bu nedenle sunulan içerik yeni olmadıkça açıklamaları bazen biraz hızlı geçebiliriz.
14.3. Bir kişinin temsil edilmesi
Uygulama, bir grup kişiyi yönetir. 14.1 numaralı paragrafta yer alan ekran görüntüleri, bir kişinin bazı özelliklerini göstermiştir. Biçimsel olarak, bunlar [Personne] sınıfı ile temsil edilir:
![]()
[Personne] sınıfı şu şekildedir:
- Bir kişi aşağıdaki bilgilerle tanımlanır:
- id: bir kişiyi benzersiz bir şekilde tanımlayan bir numara
- soyadı: kişinin soyadı
- ad: kişinin adı
- dateNaissance: doğum tarihi
- evli: evli olup olmadığı
- nbEnfants: çocuk sayısı
- [version] özniteliği, uygulamanın ihtiyaçları doğrultusunda yapay olarak eklenmiş bir özniteliktir. Nesne yönünden bakıldığında, bu özniteliği [Personne]'ten türetilen bir sınıfa eklemek şüphesiz daha uygun olurdu. Bu özniteliğe duyulan ihtiyaç, web uygulamasının kullanım senaryoları üzerinde çalışırken ortaya çıkmaktadır. Bunlardan biri şöyledir:
T1 zamanında, bir U1 kullanıcısı, P adlı bir kişinin düzenleme ekranına girer. Bu anda çocuk sayısı 0’dır. Bu sayıyı 1’e değiştirir ancak değişikliğini onaylamadan önce, U2 adlı bir kullanıcı aynı kişi P’nin düzenleme sayfasına girer. U1 henüz değişikliğini onaylamadığından, U2 çocuk sayısını 0 olarak görür. U2, P adlı kişinin adını büyük harfe çevirir. Ardından U1 ve U2, değişikliklerini bu sırayla onaylar. U2'in yaptığı değişiklik geçerli olacaktır: isim büyük harfe dönüştürülecek ve çocuk sayısı sıfır olarak kalacaktır; oysa U1, bu sayıyı 1 olarak değiştirdiğini sanmaktadır.
Kişi sürümü kavramı bu sorunu çözmemize yardımcı olur. Aynı kullanım örneğini ele alalım:
T1 zamanında, U1 adlı bir kullanıcı, P adlı kişinin düzenleme moduna girer. Bu anda çocuk sayısı 0’dır ve sürüm V1’tir. Çocuk sayısını 1 olarak değiştirir ancak değişikliğini onaylamadan önce, U2 adlı bir kullanıcı aynı kişi P'nin düzenleme sayfasına girer. U1 henüz değişikliğini onaylamadığından, U2, çocuk sayısını 0 ve sürümü V1 olarak görür. U2, P adlı kişinin adını büyük harflerle yazar. Ardından U1 ve U2, değişikliklerini bu sırayla onaylar. Bir değişikliği onaylamadan önce, P kişisini değiştiren kişinin, halihazırda kayıtlı olan P kişisiyle aynı sürüme sahip olup olmadığı kontrol edilir. Bu durum, U1 kullanıcısı için geçerlidir. Dolayısıyla, yaptığı değişiklik kabul edilir ve değiştirilen kişinin sürümü, kişide bir değişiklik yapıldığına işaret etmek amacıyla V1'ten V2'e değiştirilir. U2'in değişikliği onaylandığında, bu kullanıcının kişi P'nin V1 sürümüne sahip olduğu, oysa kişi P'nin mevcut sürümünün V2 olduğu fark edilecektir. Böylece U2 kullanıcıya, kendisinden önce birinin bu işlemi gerçekleştirdiği ve P kişisinin yeni sürümünden başlaması gerektiği söylenebilir. Kullanıcı bunu yapacak, artık bir çocuğu olan V2 sürümündeki P kişisini alacak, ismi büyük harflerle yazacak ve onaylayacaktır. Kayıtlı P kişisinin sürümü hâlâ V2 ise, yaptığı değişiklik kabul edilecektir. Sonuç olarak, U1 ve U2 tarafından yapılan değişiklikler dikkate alınacaktır; oysa sürüm kullanılmayan senaryoda, değişikliklerden biri kaybolurdu.
- 32-40. satırlar: Bir kişinin alanlarını başlatabilen bir oluşturucu. [version] alanı atlanır.
- 43-51. satırlar: Parametre olarak verilen kişinin bir kopyasını oluşturan bir oluşturucu. Böylece, içeriği aynı olan ancak iki farklı işaretçi tarafından referans verilen iki nesne elde edilir.
- 55. satır: [toString] yöntemi, kişinin durumunu temsil eden bir karakter dizesi döndürmek üzere yeniden tanımlanmıştır
14.4. [dao] katmanı
[dao] katmanı aşağıdaki sınıf ve arayüzlerden oluşur:
![]()
- [IDao], [dao] katmanı tarafından sunulan arayüzdür
- [DaoImpl], kişi grubunun bir [ArrayList] nesnesi içinde kapsüllendiği bu arayüzün bir uygulamasıdır
- [DaoException], [dao] katmanı tarafından tetiklenen denetlenmemiş (unchecked) istisna türüdür
[IDao] arayüzü şu şekildedir:
- Arayüz, kişi grubu üzerinde gerçekleştirilmek istenen dört işlem için dört yönteme sahiptir:
- getAll: bir kişi koleksiyonunu almak için
- getOne: belirli bir id değerine sahip bir kişiyi almak için
- saveOne: bir kişi eklemek (id=-1) veya mevcut bir kişiyi düzenlemek (id <> -1)
- deleteOne: belirli bir id değerine sahip bir kişiyi silmek için
[dao] katmanı istisnalar oluşturabilir. Bu istisnalar [DaoException] türünde olacaktır :
- 3. satır: [RuntimeException]'ten türetilen [DaoException] sınıfı, kontrol edilemeyen bir istisna türüdür: derleyici,
- bu tür istisnaları, istisna atabilecek bir yöntemi çağırdığımızda try / catch ile yönetmemizi
- istisnayı tetikleyebilecek bir yöntemin imzasına "throws DaoException" işaretçisini eklemek
Bu teknik, [IDao] arayüzündeki yöntemleri belirli bir türdeki istisnalarla imzalamak zorunda kalmamızı önler. Böylece, kontrolsüz istisnalar atan tüm uygulamalar kabul edilebilir hale gelir ve bu da mimariye esneklik kazandırır.
- 6. satır: bir hata kodu. [dao] katmanı, farklı hata kodlarıyla tanımlanacak çeşitli istisnalar atacaktır. Bu, istisnayı yönetmeye karar verecek katmanın hatanın tam kaynağını bilmesini ve böylece uygun önlemleri almasını sağlayacaktır. Aynı sonuca ulaşmanın başka yolları da vardır. Bunlardan biri, olası her hata türü için bir istisna türü oluşturmaktır; örneğin NomManquantException, PrenomManquantException, AgeIncorrectException, ...
- 13-16. satırlar: Bir hata kodu ve bir hata mesajıyla tanımlanan bir istisna oluşturmaya yarayan oluşturucu.
- satır 8-10: istisna işleme kodunun hata kodunu almasını sağlayacak yöntem.
[DaoImpl] sınıfı , [IDao] arayüzünü uygular:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
Bu kodun sadece ana hatlarını vereceğiz. Ancak en hassas kısımlar üzerinde biraz duracağız.
- 13. satır: Kişi grubunu içerecek olan [ArrayList] nesnesi
- 16. satır: En son eklenen kişinin kimliği. Her yeni eklemeyle bu kimlik 1 artırılacaktır.
[DaoImpl] sınıfından tek bir örnek oluşturulacaktır. Buna singleton denir. Bir web uygulaması, kullanıcılarına eşzamanlı olarak hizmet verir. Belirli bir anda, web sunucusu tarafından yürütülen birden fazla iş parçacığı bulunur. Bu iş parçacıkları, singletonları paylaşır:
- [dao] katmanına ait olan
- [service] katmanına ait olan
- web katmanındaki çeşitli denetleyiciler, veri doğrulayıcılar vb.
Bir singletonun özel alanları varsa, hemen neden böyle alanlara sahip olduğu sorulmalıdır. Bunlar haklı mı? Zira bunlar farklı iş parçacıkları arasında paylaşılacaktır. Eğer salt okunur iseler, yalnızca bir iş parçacığının aktif olduğundan emin olduğumuz bir anda başlatılabilmeleri durumunda sorun teşkil etmez. Genellikle bu anı belirlemek mümkündür. Bu, web uygulamasının henüz müşterilere hizmet vermeye başlamadığı başlangıç anıdır. Eğer okuma/yazma erişimine açıklarsa, alanlara erişim için bir senkronizasyon mekanizması kurulmalıdır; aksi takdirde felakete davetiye çıkarılır. Bu sorunu, [dao] katmanını test ederken örnekleyeceğiz.
- [DaoImpl] sınıfının oluşturucusu yoktur. Dolayısıyla varsayılan oluşturucusu kullanılacaktır.
- 19-38. satırlar: [init] yöntemi, [dao] katmanının singleton örneğinin oluşturulduğu sırada çağrılacaktır. Bu yöntem, üç kişiden oluşan bir liste oluşturur.
- 41-43. satırlar: [IDao] arayüzünün [getAll] yöntemini uygular. Kişi listesine bir referans döndürür.
- 46-55. satırlar: [IDao] arayüzünün [getOne] yöntemini uygular. Parametresi, aranan kişinin kimliğidir.
Bu kimliği almak için 113-126. satırlardaki özel [getPosition] yöntemine başvurulur. Bu yöntem, aranan kişinin listede bulunduğu konumu döndürür; kişi bulunamazsa -1 değerini döndürür.
Kişi bulunmuşsa, [getOne] yöntemi, kişinin kendisine değil, bu kişinin bir kopyasına ilişkin bir referans (51. satır) döndürür. Nitekim, bir kullanıcı bir kişiyi düzenlemek istediğinde, bu kişiye ait bilgiler [dao] katmanından talep edilir ve bir [Personne] nesnesine yapılan referans şeklinde, düzenleme amacıyla [web] katmanına iletilir. Bu referans, değiştirme formunda giriş kutusu olarak işlev görecektir. Web katmanında kullanıcı değişikliklerini gönderdiğinde, giriş kutusunun içeriği değiştirilecektir. Eğer giriş kutusu, [dao] katmanındaki [ArrayList] kişisine ait bir referansa karşılık geliyorsa, o kişi de değiştirilmiş olur; ancak bu değişiklikler [service] ve [dao] katmanlarına yansıtılmamıştır. Kişi listesini yönetme yetkisi yalnızca sonuncu katmana aittir. Bu nedenle, web katmanının değiştirilecek kişinin bir kopyası üzerinde çalışması gerekir. Burada [dao] katmanı bu kopyayı sağlar.
Aranan kişi bulunamazsa, hata kodu 2 ile [DaoException] türünde bir istisna atılır (53. satır).
- 94-104. satırlar: [IDao] arayüzünün [deleteOne] yöntemini uygular. Bu yöntemin parametresi, silinecek kişinin kimliğidir. Silinecek kişi mevcut değilse, hata kodu 2 ile [DaoException] türünde bir istisna atılır.
- 58-91. satırlar: [IDao] arayüzünün [saveOne] yöntemini uygular. Bu yöntemin parametresi bir [Personne] nesnesidir. Bu nesnenin id değeri -1 ise, bu durum bir kişinin eklenmesi anlamına gelir. Aksi takdirde, listedeki bu id'ye sahip kişinin parametre değerleriyle güncellenmesi söz konusudur.
- 60. satır: [Personne] parametresinin geçerliliği, 129-155. satırlarda tanımlanan özel bir yöntem olan [check] ile kontrol edilir. Bu yöntem, [Personne] nesnesinin çeşitli alanlarının değerleri üzerinde temel kontroller yapar. Herhangi bir anormallik tespit edildiğinde, belirli bir hata koduna sahip bir [DaoException] istisnası tetiklenir. [saveOne] yöntemi bu istisnayı yönetmediğinden, istisna çağıran yönteme iletilir.
- 62. satır: [Personne] parametresinin kimliği -1 ise, bu bir ekleme işlemidir. [Personne] nesnesi, mevcut ilk kimlik numarasıyla (satır 64) ve 1'e eşit bir sürüm numarasıyla (satır 65) dahili kişi listesine eklenir (satır 66).
- [Personne] parametresinin [id] değeri -1'den farklıysa, bu durumda iç listede bu [id] değerine sahip kişinin bilgilerinin değiştirilmesi söz konusudur. Öncelikle, değiştirilecek kişinin var olup olmadığı kontrol edilir (satır 70-75). Eğer yoksa, hata kodu 2 ile [DaoException] türünde bir istisna atılır.
- Kişi gerçekten mevcutsa, mevcut sürümünün, orijinalde yapılacak değişiklikleri içeren [Personne] parametresindeki sürümle aynı olup olmadığı kontrol edilir. Aksi takdirde, bu, kişide değişiklik yapmak isteyen kişinin en son sürüme sahip olmadığı anlamına gelir. Bunu, hata kodu 3 olan [DaoException] türünde bir istisna oluşturarak bildiririz (satır 79-80).
- Her şey yolunda giderse, değişiklikler kişinin orijinal kaydına yansıtılır (satır 85-90).
Bu yöntemin senkronize edilmesi gerektiği açıktır. Örneğin, değiştirilecek kişinin mevcut olup olmadığının kontrol edildiği an ile değişikliğin yapılacağı an arasında, bu kişi başka biri tarafından listeden silinmiş olabilir. Bu nedenle, yöntemin aynı anda yalnızca tek bir iş parçacığı tarafından yürütülmesini sağlamak için [synchronized] olarak tanımlanması gerekir. Aynı durum, [IDao] arayüzündeki diğer yöntemler için de geçerlidir. Ancak biz bunu yapmıyoruz; bu senkronizasyonu [service] katmanına taşımayı tercih ediyoruz. Senkronizasyon sorunlarını ortaya çıkarmak için, [dao] katmanının testleri sırasında, değişikliği yapabileceğimizi bildiğimiz an ile değişikliği fiilen yaptığımız an arasında [saveOne]'in yürütülmesini 10 ms süreyle durduracağız (83. satır). Böylece, [saveOne]'i çalıştıran iş parçacığı, işlemciyi başka bir iş parçacığına devredecektir. Bu sayede, kişi listesine erişim çakışmalarının ortaya çıkma olasılığını artırmış olacağız.
14.5. [dao] katmanının testleri
[dao] katmanı için bir JUnit testi yazılmıştır:
![]() | ![]() |
[TestDao], JUnit testidir. Kişi listesine eşzamanlı erişim sorunlarını ortaya çıkarmak için [ThreadDaoMajEnfants] türünde iş parçacıkları oluşturulur. Bu iş parçacıkları, belirli bir kişinin çocuk sayısını 1 artırmakla görevlidir.
[TestDao], [test1]'ten [test5]'e kadar beş teste sahiptir. Bunlardan sadece ikisini sunuyoruz; okuyucunun diğerlerini bu makaleye ait kaynak kodda keşfetmesi önerilir.
- 9. satır: Test edilen [dao] katmanının uygulamasına yapılan referans
- 12-15. satırlar: JUnit test oluşturucusu. Test edilecek [dao] katmanından [DaoImpl] türünde bir örnek oluşturur ve bunu başlatır.
[test1] yöntemi, [IDao] arayüzünün dört yöntemini şu şekilde test eder:
- 3. satır: kişi listesi istenir
- 6. satır: Liste görüntülenir
[1,1,Joachim,Major,13/01/1984,true,2]
[2,1,Mélanie,Humbort,12/01/1985,false,1]
[3,1,Charles,Lemarchand,01/01/1986,false,0]
Ardından test, bir kişi ekler, onu değiştirir ve siler. Böylece [IDao] arayüzünün dört yöntemi de kullanılmış olur.
- 8-10. satırlar: Yeni bir kişi eklenir (id=-1).
- 11. satır: Eklenen kişinin kimliği alınır, çünkü ekleme işlemi ona bir kimlik atamıştır. Daha önce bir kimliği yoktu.
- 13-14. satırlar: [dao] katmanından, az önce eklenen kişinin bir kopyası istenir. Unutulmamalıdır ki, istenen kişi bulunamazsa, [dao] katmanı bir istisna oluşturur. Bu durumda 13. satırda bir çökme meydana gelir. Bu durumu daha düzgün bir şekilde yönetebilirdik. 14. satırda, bulunan kişinin adı kontrol edilir.
- 16-17. satırlar: Bu adı değiştiriyoruz ve [dao] katmanından değişiklikleri kaydetmesini istiyoruz.
- 19-20. satırlar: [dao] katmanından az önce eklenen kişinin bir kopyasını isteriz ve yeni adını doğrularız.
- 22. satır: Testin başında eklenen kişi silinir.
- 23-34. satırlar: [dao] katmanından az önce silinen kişinin bir kopyası istenir. Kod 2 olan bir [DaoException] elde edilmelidir.
- 36-37. satırlar: Kişi listesi yeniden istenir. Testin başlangıcındakiyle aynı liste elde edilmelidir.
[test4] yöntemi, [dao] katmanındaki yöntemlere eşzamanlı erişim sorunlarını ortaya çıkarmayı amaçlamaktadır. Bu yöntemlerin senkronize edilmediğini hatırlayalım. Test kodu şöyledir:
- 3-6. satırlar: Listeye çocuğu olmayan P adlı bir kişi eklenir. Bu kişinin [id] değeri kaydedilir (6. satır).
- 7-13. satırlar: N adet iş parçacığı başlatılır. Her biri, P kişisinin çocuk sayısını 1 birim artıracaktır. Sonuç olarak, P kişisinin N çocuğu olması gerekir.
- satır 15-17: N iş parçacığını başlatan [test4] yöntemi, bu iş parçacıklarının işlerini tamamlamasını bekledikten sonra P kişisinin yeni çocuk sayısını kontrol eder.
- 18-21. satırlar: P kişisi alınır ve çocuk sayısının N olduğu kontrol edilir.
- 22-35. satırlar: P kişisi silinir ve ardından listede artık bulunmadığı kontrol edilir.
- satırda, iş parçacıklarının [ThreadDaoMajEnfants] türünde olduğu görülüyor. Bu türün oluşturucusunun üç parametresi vardır:
- İş parçacığına verilen ad; bu, günlükler aracılığıyla iş parçacığını takip etmek içindir
- [dao] katmanında bir referans; böylece iş parçacığı bu katmana erişebilir
- İş parçacığının üzerinde çalışacağı kişinin kimliği
[ThreadDaoMajEnfants] türü şu şekildedir:
- 9. satır: [ThreadDaoMajEnfants], bir iş parçacığıdır
- 18-22. satırlar: İş parçacığını üç bilgiyle başlatan oluşturucu
- iş parçacığına verilen [name] adı
- [dao] katmanına ait [dao] referansı. Burada da yine, [DaoImpl] uygulamasının türüyle değil, [IDao] arayüzünün türüyle çalıştığımızı belirtelim.
- İş parçacığının üzerinde çalışması gereken kişinin [id] kimliği
[test4], [ThreadDaoMajEnfants] iş parçacığını başlattığında (test4'ün 12. satırı), bu iş parçacığının [run] yöntemi (25. satır) yürütülür:
- 78-81. satırlar: Özel [suivi] yöntemi, ekran günlükleri oluşturulmasına olanak tanır. [run] yöntemi, iş parçacığının yürütülmesini takip edebilmek için bunu kullanır.
- iş parçacığı, [id] kimlik numaralı P kişisinin çocuk sayısını 1 artırmaya çalışır. Bu güncelleme birkaç deneme gerektirebilir. [TH1] ve [TH2] adlı iki iş parçacığını ele alalım. [TH1], [dao] katmanından P kişisinin bir kopyasını ister. Kopyasını alır ve bunun V1 sürümüne sahip olduğunu görür. [TH1] kesintiye uğrar. Onu takip eden [TH2] de aynı işlemi gerçekleştirir ve P kişisinin aynı V1 sürümünü alır. [TH2] kesintiye uğrar. [TH2] kontrolü geri alır, P’nin çocuk sayısını artırır ve değişikliklerini kaydeder. Bu noktada, değişikliklerin kaydedildiğini ve P’nin sürümünün V2’e geçeceğini biliyoruz. [TH1] işini tamamlamıştır. [TH2] kontrolü devralır ve aynı işlemi yapar. P’ye yaptığı güncelleme reddedilecektir, çünkü elinde V1 sürümündeki P’nin bir kopyası varken, orijinal P artık V2 sürümündedir. [TH2], bu durumda [lecture -> mise à jour -> sauvegarde] döngüsünün tamamını yeniden başlatmak zorundadır. Bu nedenle, 32-72. satırlarda bir döngüyle karşılaşıyoruz. Bu döngüde, iş parçacığı:
- değiştirilecek P kişisinin bir kopyasını ister (34. satır)
- 10 ms bekler (satır 43). Bu yapay bir işlemdir ve çakışma olasılığını artırmak amacıyla, P kişisinin okunması ile kişi listesindeki fiili güncellemesi arasında iş parçacığını kesintiye uğratmayı amaçlamaktadır.
- P'nin çocuk sayısını artırır (satır 54) ve P'yi kaydeder (satır 56). İş parçacığı P'nin doğru sürümüne sahip değilse, [dao] katmanı tarafından bir istisna tetiklenir. Ardından istisna kodu alınır (satır 61) ve bunun gerçekten 3 kodu (P'nin yanlış sürümü) olup olmadığı kontrol edilir. Aksi takdirde, istisna çağıran yönteme, yani sonuçta [test4] test yöntemine yeniden atılır. Kod 3 istisnası alınırsa, [lecture -> mise à jour -> sauvegarde] döngüsüne yeniden başlanır. İstisna alınmazsa, güncelleme yapılmış demektir ve iş parçacığının işi tamamlanmış olur.
Testlerin sonuçları nedir?
Test edilen ilk yapılandırmada:
- [DaoImpl] yöntemindeki [saveOne] yöntemindeki bekleme komutu yorumlanmıştır (satır 83, paragraf 14.4).
- [test4] yöntemi 100 iş parçacığı oluşturur (satır 8, paragraf 14.5).
Aşağıdaki sonuçlar elde edilir:

Beş test de başarıyla tamamlandı.
Test edilen ikinci yapılandırmada:
- [DaoImpl] yöntemindeki [saveOne] komutundaki bekleme talimatının yorum satırı kaldırılır (satır 83, paragraf 14.4).
- [test4] yöntemi 2 iş parçacığı oluşturur (satır 8, paragraf 14.5).
Aşağıdaki sonuçlar elde edilir:
![]() | ![]() |
[test4] testi başarısız oldu. Başlangıçta 0 çocuğu olan P adlı kişinin çocuk sayısını 1 artırmakla görevli iki iş parçacığı oluşturuldu. Dolayısıyla, iki iş parçacığının çalışmasından sonra 2 çocuk olması bekleniyordu, ancak sadece bir çocuk var.
Neler olduğunu anlamak için [test4] iş parçacığının ekran günlüklerini inceleyelim:
- 1. satır: 0 numaralı iş parçacığı çalışmaya başlar
- 2. satır: P kişisinin bir kopyasını aldı ve çocuk sayısının 0 olduğunu gördü
- 3. satır: [run] yönteminden gelen [Thread.sleep(10)] ile karşılaşır ve bu nedenle [1145536368171] (ms) süresinde durur
- 4. satır: 1 numaralı iş parçacığı işlemciyi devralır ve çalışmaya başlar
- satır 5: Kişi P'nin bir kopyasını alır ve çocuk sayısının 0 olduğunu tespit eder
- 6. satır: [run] yöntemindeki [Thread.sleep(10)] zamanına ulaşır ve bu nedenle durur
- 7. satır: 0 numaralı iş parçacığı, [1145536368187] (ms) zamanında, yani c.a.d'ten 16 ms sonra işlemciyi geri alır.
- 8. satır: 1 numaralı iş parçacığı için de durum aynıdır
- 9. satır: 0 numaralı iş parçacığı güncellemesini yaptı ve alt iş parçacık sayısını 1'e çıkardı
- 10. satır: 1 numaralı iş parçacığı da aynısını yaptı
Buradaki soru, normalde 0 numaralı iş parçacığı tarafından az önce güncellenen P kişisinin doğru sürümüne artık sahip olmamasına rağmen, 1 numaralı iş parçacığının neden güncellemesini yapabildiğidir.
Öncelikle, 7. ve 8. satırlar arasında bir tutarsızlık göze çarpmaktadır: Görünüşe göre 0 numaralı iş parçacığı, bu iki satır arasında işlemci kontrolünü 1 numaralı iş parçacığına kaptırmıştır. O sırada ne yapıyordu? [saveOne] katmanındaki [saveOne] yöntemini çalıştırıyordu. Bu yöntemin iskeleti şu şekildedir (bkz. paragraf 14.4):
- 0 numaralı iş parçacığı, [saveOne]'i çalıştırdı ve 8. satıra kadar ilerledi; burada işlemciyi bırakmak zorunda kaldı. Bu sırada, P kişisinin sürümünü okudu ve bu değer 1'di, çünkü P kişisi henüz güncellenmemişti.
- İşlemci boşaldığı için, 1 numaralı iş parçacığı işlemciyi devraldı. Bu iş parçacığı da [saveOne] programını çalıştırdı ve 8. satıra kadar ilerledi; burada işlemciyi bırakmak zorunda kaldı. Bu sırada, P kişisinin sürümünü okudu ve bu değer 1'di, çünkü P kişisi henüz güncellenmemişti.
- İşlemci boşaldığı için, 0 numaralı iş parçacığı onu devraldı. 9. satırdan itibaren güncellemesini yaptı ve çocuk sayısını 1'e değiştirdi. Ardından, 0 numaralı iş parçacığının [run] yöntemi sona erdi ve iş parçacığı, çocuk sayısını 1'e değiştirdiğini belirten günlüğü görüntüledi (9. satır).
- İşlemci boşaldığı için, iş parçacığı 1 bu iş parçacığını devraldı. 9. satırdan itibaren güncellemesini yaptı ve çocuk sayısını 1'e değiştirdi. Neden 1? Çünkü çocuk sayısı 0 olan P'nin bir kopyasına sahip. Bunu log (5. satır) gösteriyor. Ardından 1 numaralı iş parçacığının [run] yöntemi sona erdi ve iş parçacığı, çocuk sayısını 1'e değiştirdiğini belirten günlüğü görüntüledi (10. satır).
Sorun nereden kaynaklanıyor? Sorun, 0 numaralı iş parçacığının, 1 numaralı iş parçacığı P'nin durumunun değişip değişmediğini öğrenmek için bu sürümü okumaya çalışmadan önce, yaptığı değişikliği onaylayıp P'nin sürümünü değiştirecek zamana sahip olmamasından kaynaklanıyor. Bu senaryo pek olası olmasa da imkansız değildir. Sadece iki iş parçacığıyla bu durumun ortaya çıkmasını sağlamak için 0 numaralı iş parçacığının işlemciyi kaybetmesini zorlamak gerekti. Bu hile olmadan, önceki yapılandırma 100 iş parçacığıyla aynı durumu ortaya çıkaramamıştı. [test4] testi başarılı olmuştu.
Çözüm nedir? Muhtemelen birkaç çözüm vardır. Bunlardan biri, uygulaması kolay olan, [saveOne] yöntemini senkronize etmektir:
public synchronized void saveOne(Personne personne)
[synchronized] anahtar sözcüğü, yöntemi aynı anda yalnızca bir iş parçacığının çalıştırabilmesini sağlar. Böylece 1 numaralı iş parçacığı, ancak 0 numaralı iş parçacığı [saveOne] yönteminden çıktığında bu yöntemi çalıştırabilecektir. Böylece, 1 numaralı iş parçacığı [saveOne]'e girdiğinde, P'nin sürümünün değiştirilmiş olacağından emin oluruz. Doğru P sürümüne sahip olmadığı için güncellemesi reddedilecektir.
Senkronize edilmesi gereken, [dao] katmanındaki bu dört yöntemdir. Ancak, bu katmanı açıklanan haliyle korumaya ve senkronizasyonu [service] katmanına ertelemeye karar veriyoruz. Bunun birkaç nedeni vardır:
- [dao] katmanına erişimin her zaman bir [service] katmanı üzerinden gerçekleştiğini varsayıyoruz. Web uygulamamızda durum böyledir.
- [dao] katmanındaki yöntemleri senkronize etmemize neden olan nedenlerin dışında, başka nedenlerle [service] katmanındaki yöntemlere erişimi de senkronize etmek gerekebilir. Bu durumda, [dao] katmanındaki yöntemleri senkronize etmeye gerek yoktur. Şu durumdan eminsek:
- [dao] katmanına yapılan tüm erişimlerin [service] katmanından geçtiği
- ve [service] katmanını aynı anda yalnızca tek bir iş parçacığı kullanıyorsa
o zaman [dao] katmanındaki yöntemlerin aynı anda iki iş parçacığı tarafından yürütülmeyeceğinden emin olabiliriz.
Şimdi [service] katmanını inceleyeceğiz.
14.6. [service] katmanı
[service] katmanı aşağıdaki sınıf ve arayüzlerden oluşur:
![]()
- [IService], [dao] katmanı tarafından sunulan arayüzdür
- [ServiceImpl], bunun bir uygulamasıdır
[IService] arayüzü şöyledir:
Bu arayüz, [IDao] arayüzüyle aynıdır.
[IService] arayüzünün [ServiceImpl] uygulaması şöyledir:
- 10-19. satırlar: [IDao dao] özniteliği, [dao] katmanına bir referanstır. Bu öznitelik, Spring tarafından IoC olarak başlatılacaktır.
- 22-24. satırlar: [IService] arayüzünün [getAll] yönteminin uygulaması. Yöntem, isteği [dao] katmanına devretmekle yetinir.
- 27-29. satırlar: [IService] arayüzünün [getOne] yönteminin uygulaması. Yöntem, isteği sadece [dao] katmanına devretmektedir.
- satır 32-34: [IService] arayüzünün [saveOne] yönteminin uygulaması. Yöntem, isteği sadece [dao] katmanına devretmektedir.
- 37-39. satırlar: [IService] arayüzünün [deleteOne] yönteminin uygulaması. Yöntem, isteği sadece [dao] katmanına devretmektedir.
- Tüm yöntemler senkronize edilmiştir (synchronized anahtar sözcüğü), bu sayede [service] katmanını ve dolayısıyla [dao] katmanını aynı anda yalnızca bir iş parçacığı kullanabilir.
14.7. [service] katmanının testleri
[service] katmanı için bir JUnit testi yazılmıştır:
![]() | ![]() |
[TestService], JUnit testidir. Yapılan testler, [dao] katmanı için yapılanlarla tamamen aynıdır. [TestService]'in iskeleti şöyledir:
- 9. satır: [service] katmanı, [ServiceImpl] türü olarak test edilmiştir.
- 11-15. satırlar: JUnit test oluşturucusu, test edilecek [service] katmanının bir örneğini oluşturur (12. satır), [dao] katmanının bir örneğini oluşturur (satır 13) ve [service] katmanına bu [dao] katmanını kullanması gerektiğini belirtir (satır 14).
[test1] yöntemi, [IService] arayüzünün dört yöntemini, aynı adlı [dao] katmanının test yöntemiyle aynı şekilde test eder. Tek fark, [dao] katmanı yerine [service] katmanına (25., 32. ve 35. satırlar) erişilmesidir.
[test4] yöntemi, [service] katmanındaki yöntemlere eşzamanlı erişim sorunlarını ortaya çıkarmayı amaçlamaktadır. Bu yöntem de yine [dao] katmanındaki [test4] test yöntemiyle aynıdır. Ancak bazı ayrıntılarda farklılıklar vardır:
- [dao] katmanı yerine [service] katmanına başvurulur (55. satır)
- iş parçacıklarına [dao] katmanı yerine [service] katmanına bir referans aktarılır (61. satır)
[ThreadServiceMajEnfants] türü de [ThreadDaoMajEnfants] türüyle neredeyse aynıdır; tek farkı, [dao] katmanı yerine [service] katmanıyla çalışmasıdır:
- 12. satır: iş parçacığı [service] katmanıyla çalışıyor
Testleri, [dao] katmanında sorun yaratan yapılandırma ile gerçekleştiriyoruz:
- [DaoImpl]'teki [saveOne] yöntemindeki bekleme komutunun yorum satırını kaldırıyoruz (satır 83, paragraf 14.4).
- [test4] yöntemi 100 iş parçacığı oluşturur (satır 65, paragraf 14.7).
Elde edilen sonuçlar şunlardır:
![]() |
[service] katmanındaki yöntemlerin senkronizasyonu, [test4] testinin başarıyla sonuçlanmasını sağlamıştır.
14.8. [web] katmanı
Uygulamamızın 3 katmanlı mimarisini hatırlayalım:
![]() |
[web] katmanı, kullanıcının kişi grubunu yönetebilmesi için ekranlar sunacaktır:
- grup üyelerinin listesi
- gruba kişi ekleme
- gruptaki bir kişiyi düzenleme
- gruptan bir kişinin silinmesi
Bunun için, [service] katmanını kullanacak ve bu katman da [dao] katmanını kullanacaktır. [web] katmanı tarafından yönetilen ekranları daha önce tanıtmıştık (14.1. paragraf). Web katmanını açıklamak için sırasıyla şunları ele alacağız:
- yapılandırmasını
- görünümleri
- denetleyicisi
- bazı testler
14.8.1. Web uygulamasının yapılandırması
Uygulamanın Eclipse projesi şu şekildedir:

- [istia.st.mvc.personnes.web] paketinde, [Application] denetleyicisi bulunur.
- JSP / JSTL sayfaları, [WEB-INF/vues] içinde yer almaktadır.
- [lib] klasörü, uygulama için gerekli olan üçüncü taraf arşivlerini içerir. Bunlar, [Web App Libraries] klasöründe görülebilir.
[web.xml]
[web.xml] dosyası, web sunucusu tarafından uygulamayı yüklemek için kullanılan dosyadır. İçeriği şöyledir:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>mvc-personnes-01</display-name>
<!-- ServletPersonne -->
<servlet>
<servlet-name>personnes</servlet-name>
<servlet-class>
istia.st.mvc.personnes.web.Application
</servlet-class>
<init-param>
<param-name>urlEdit</param-name>
<param-value>/WEB-INF/vues/edit.jsp</param-value>
</init-param>
<init-param>
<param-name>urlErreurs</param-name>
<param-value>/WEB-INF/vues/erreurs.jsp</param-value>
</init-param>
<init-param>
<param-name>urlList</param-name>
<param-value>/WEB-INF/vues/list.jsp</param-value>
</init-param>
</servlet>
<!-- Eşleme ServletPersonne-->
<servlet-mapping>
<servlet-name>personnes</servlet-name>
<url-pattern>/do/*</url-pattern>
</servlet-mapping>
<!-- ana sayfalar -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- Beklenmedik hata sayfası -->
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/WEB-INF/vues/exception.jsp</location>
</error-page>
</web-app>
- 27-30. satırlar: [/do/*] URL'leri, [personnes] servleti tarafından işlenecektir
- 9-12. satırlar: [personnes] servleti, [Application] sınıfının bir örneğidir; bu sınıfı daha sonra oluşturacağız.
- 13-24. satırlar: JSP sayfalarının URL'lerini tanımlayan üç [urlList, urlEdit, urlErreurs] parametresini tanımlar; bu sayfalar, [list, edit, erreurs] görünümlerine aittir.
- 32-34. satırlar: Uygulamanın, web uygulaması klasörünün kök dizininde bulunan varsayılan bir giriş sayfası ([index.jsp]) vardır.
- 36-39. satırlar: Uygulamanın, web sunucusu tarafından uygulama tarafından yönetilmeyen bir istisna algılandığında görüntülenen varsayılan bir hata sayfası vardır.
- 37. satır: <exception-type> etiketi, <error-page> yönergesi tarafından yönetilen istisna türünü belirtir; burada [java.lang.Exception] türü ve türevleri, yani tüm istisnalar belirtilmiştir.
- 38. satır: <location> etiketi, <exception-type> ile tanımlanan türde bir istisna oluştuğunda görüntülenecek JSP sayfasını belirtir. Sayfada şu yönerge varsa, oluşan istisna bu sayfada exception adlı bir nesne içinde bulunur:
<%@ page isErrorPage="true" %>
- (devam)
- <exception-type> etiketinde T1 türü belirtilmişse ve T1'ten türetilmemiş T2 türünde bir istisna web sunucusuna ulaşırsa, sunucu müşteriye genellikle kullanıcı dostu olmayan özel bir istisna sayfası gönderir. İşte bu nedenle [web.xml] dosyasındaki <error-page> etiketinin önemi ortaya çıkmaktadır.
[index.jsp]
Bu sayfa, bir kullanıcı URL belirtmeden doğrudan uygulama bağlamını talep ettiğinde görüntülenir, c.a.d. Burada [/personnes-01]. İçeriği şöyledir:
<%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<c:redirect url="/do/list"/>
[index.jsp], istemciyi [/do/list] URL'sine yönlendirir. Bu URL, gruptaki kişilerin listesini gösterir.
14.8.2. Uygulamadaki JSP / JSTL sayfaları
[list.jsp] görünümü
Bu sayfa, kişi listesini görüntülemek için kullanılır:

Kodu şöyledir:
<%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ taglib uri="/WEB-INF/taglibs-datetime.tld" prefix="dt" %>
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body background="<c:url value="/ressources/standard.jpg"/>">
<h2>Liste des personnes</h2>
<table border="1">
<tr>
<th>Id</th>
<th>Version</th>
<th>Prénom</th>
<th>Nom</th>
<th>Date de naissance</th>
<th>Marié</th>
<th>Nombre d'enfants</th>
<th></th>
</tr>
<c:forEach var="personne" items="${personnes}">
<tr>
<td><c:out value="${personne.id}"/></td>
<td><c:out value="${personne.version}"/></td>
<td><c:out value="${personne.prenom}"/></td>
<td><c:out value="${personne.nom}"/></td>
<td><dt:format pattern="dd/MM/yyyy">${personne.dateNaissance.time}</dt:format></td>
<td><c:out value="${personne.marie}"/></td>
<td><c:out value="${personne.nbEnfants}"/></td>
<td><a href="<c:url value="/do/edit?id=${personne.id}"/>">Modifier</a></td>
<td><a href="<c:url value="/do/delete?id=${personne.id}"/>">Supprimer</a></td>
</tr>
</c:forEach>
</table>
<br>
<a href="<c:url value="/do/edit?id=-1"/>">Ajout</a>
</body>
</html>
- Bu görünüm, modelinde bir öğe alır:
- [personnes] öğesi, [ArrayList] türündeki bir nesneye ve [Personne] türündeki nesnelere bağlıdır
- 22-34. satırlar: ${personnes} listesi taranarak, gruptaki kişileri içeren bir HTML tablosu görüntülenir.
- 31. satır: [Modifier] bağlantısının işaret ettiği URL, mevcut kişinin [id] alanı tarafından ayarlanır; böylece [/do/edit] URL'siyle ilişkili denetleyici, hangi kişinin değiştirileceğini bilir.
- 32. satır: Aynı işlem [Supprimer] bağlantısı için de yapılır.
- 28. satır: Kişinin doğum tarihini JJ/MM/AAAA biçiminde görüntülemek için, Apache [Jakarta Taglibs] projesinin [DateTime] etiket kütüphanesindeki <dt> etiketi kullanılır:

Bu etiket kütüphanesinin açıklama dosyası 3. satırda tanımlanmıştır.
- 37. satır: Yeni bir kişi ekleme bağlantısı olan [Ajout], 31. satırdaki [Modifier] bağlantısı gibi [/do/edit] URL'sini hedeflemektedir. [id] parametresinin -1 değeri, burada bir düzenleme değil, bir ekleme işlemi yapıldığını gösterir.
[edit.jsp] görünümü
Bu görünüm, yeni bir kişinin eklenmesi veya mevcut bir kişinin değiştirilmesi için kullanılan formu görüntülemek amacıyla kullanılır:
![]() |
[edit.jsp] görünümünün kodu şöyledir:
<%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ taglib uri="/WEB-INF/taglibs-datetime.tld" prefix="dt" %>
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body background="../ressources/standard.jpg">
<h2>Ajout/Modification d'une personne</h2>
<c:if test="${erreurEdit != ''}">
<h3>Echec de la mise à jour :</h3>
L'erreur suivante s'est produite : ${erreurEdit}
<hr>
</c:if>
<form method="post" action="<c:url value="/do/validate"/>">
<table border="1">
<tr>
<td>Id</td>
<td>${id}</td>
</tr>
<tr>
<td>Version</td>
<td>${version}</td>
</tr>
<tr>
<td>Prénom</td>
<td>
<input type="text" value="${prenom}" name="prenom" size="20">
</td>
<td>${erreurPrenom}</td>
</tr>
<tr>
<td>Nom</td>
<td>
<input type="text" value="${nom}" name="nom" size="20">
</td>
<td>${erreurNom}</td>
</tr>
<tr>
<td>Date de naissance (JJ/MM/AAAA)</td>
<td>
<input type="text" value="${dateNaissance}" name="dateNaissance">
</td>
<td>${erreurDateNaissance}</td>
</tr>
<tr>
<td>Marié</td>
<td>
<c:choose>
<c:when test="${marie}">
<input type="radio" name="marie" value="true" checked>Oui
<input type="radio" name="marie" value="false">Non
</c:when>
<c:otherwise>
<input type="radio" name="marie" value="true">Oui
<input type="radio" name="marie" value="false" checked>Non
</c:otherwise>
</c:choose>
</td>
</tr>
<tr>
<td>Nombre d'enfants</td>
<td>
<input type="text" value="${nbEnfants}" name="nbEnfants">
</td>
<td>${erreurNbEnfants}</td>
</tr>
</table>
<br>
<input type="hidden" value="${id}" name="id">
<input type="hidden" value="${version}" name="version">
<input type="submit" value="Valider">
<a href="<c:url value="/do/list"/>">Annuler</a>
</form>
</body>
</html>
Bu görünüm, yeni bir kişi ekleme veya mevcut bir kişiyi güncelleme formunu gösterir. Bundan sonra, yazımı basitleştirmek amacıyla tek bir terim olan [mise à jour]'i kullanacağız. [Valider] düğmesi (73. satır), formun POST görünümünü [/do/validate] URL'sinde (16. satır) tetikler. POST işlemi başarısız olursa, [edit.jsp] görünümü, meydana gelen hata veya hatalarla birlikte yeniden görüntülenir; aksi takdirde [list.jsp] görünümü görüntülenir.
- Hem GET hem de başarısız olan POST üzerinde görüntülenen [edit.jsp] görünümü, şablonunda aşağıdaki öğeleri alır:
öznitelik | GET | POST |
güncellenen kişinin kimlik numarası | aynı | |
sürümü | aynı | |
ad | girilen adı | |
soyadı | girdilen soyadı | |
doğum tarihi | girdilen doğum tarihi | |
medeni durumu | girilen medeni durumu | |
çocuk sayısı | girilen çocuk sayısı | |
boş | POST sırasında, [Envoyer] düğmesi tarafından tetiklenen ekleme veya değiştirme işleminin başarısız olduğunu belirten bir hata mesajı. Hata yoksa boş bırakılır. | |
boş | hatalı bir ad bildirir – aksi takdirde boştur | |
boş | hatalı soyad belirtir – aksi takdirde boş | |
boş | hatalı doğum tarihi bildirir – aksi takdirde boş bırakılır | |
boş | yanlış çocuk sayısı bildirildi – aksi takdirde boş bırakılır |
- 11-15. satırlar: Formdaki POST işlemi başarısız olursa, [erreurEdit!=''] değeri alınır ve bir hata mesajı görüntülenir.
- satır 16: form, [/do/validate] URL'sine gönderilecektir
- 20. satır: Şablondaki [id] öğesi görüntülenir
- 24. satır: Şablondaki [version] öğesi görüntülenir
- 26-32. satırlar: Kişinin adının girilmesi:
- formun ilk görüntülenişinde (GET), ${prenom}, güncellenen [Personne] nesnesinin [prenom] alanının geçerli değerini gösterir ve ${erreurPrenom} boştur.
- POST'ten sonra bir hata oluşması durumunda, girilen ${prenom} değeri ile olası hata mesajı ${erreurPrenom} yeniden görüntülenir
- 33-39. satırlar: kişinin soyadının girilmesi
- satır 40-46: kişinin doğum tarihinin girilmesi
- satır 47-61: Kişinin medeni durumunun bir radyo düğmesi ile girilmesi. Hangi radyo düğmesinin işaretlenmesi gerektiğini belirlemek için [Personne] nesnesinin [marie] alanındaki değer kullanılır.
- satır 62-68: kişinin çocuk sayısının girilmesi
- satır 71: HTML adlı gizli bir alan; adı [id] olup, değeri güncellenmekte olan kişinin [id] alanının değeridir; ekleme için -1, değişiklik için başka bir değer alır.
- satır 72: [version] adlı gizli bir HTML alanı; değeri, güncellenmekte olan kişinin [id] alanının değeridir.
- 73. satır: Formdaki [Submit] türündeki [Valider] düğmesi
- 74. satır: Kişi listesine geri dönmeyi sağlayan bir bağlantı. Formu onaylamadan çıkmaya olanak tanıdığı için [Annuler] olarak adlandırılmıştır.
[exception.jsp] görünümü
Bu görünüm, uygulama tarafından yönetilmeyen ve web sunucusuna iletilen bir istisna oluştuğunu bildiren bir sayfayı görüntülemek için kullanılır.
Örneğin, grupta bulunmayan bir kişiyi silelim:
![]() |
[exception.jsp] görünümünün kodu şöyledir:
<%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ page isErrorPage="true" %>
<%
response.setStatus(200);
%>
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body background="<c:url value="/ressources/standard.jpg"/>">
<h2>MVC - personnes</h2>
L'exception suivante s'est produite :
<%= exception.getMessage()%>
<br><br>
<a href="<c:url value="/do/list"/>">Retour à la liste</a>
</body>
</html>
- Bu görünüm, modelinde [exception] öğesini içeren bir anahtar alır; bu, web sunucusu tarafından yakalanan istisnadır. Bu öğenin web sunucusu tarafından JSP sayfasının modeline dahil edilebilmesi için, sayfanın 3. satırdaki etiketini tanımlamış olması gerekir.
- 6. satır: Yanıtın durum kodu HTTP, 200 olarak belirlenir. Bu, yanıtın ilk başlığı HTTP'tir. 200 kodu, istemciye isteğinin yerine getirildiğini bildirir. Genellikle, sunucunun yanıtına bir HTML belgesi eklenir. Burada da durum böyledir. Yanıtın HTTP durum kodu 200 olarak ayarlanmazsa, burada 500 değerini alır ve bu da bir hata oluştuğunu gösterir. Nitekim, yönetilmeyen bir istisna yakalayan web sunucusu bu durumu anormal olarak değerlendirir ve 500 koduyla bildirir. HTTP 500 koduna verilen tepki, tarayıcılara göre farklılık gösterir: Firefox, bu yanıtla birlikte gelebilecek HTML belgesini görüntülerken, IE bu belgeyi yok sayar ve kendi sayfasını görüntüler. Bu nedenle 500 kodunu 200 koduyla değiştirdik.
- 16. satır: istisna metni görüntülenir
- 18. satır: Kullanıcıya kişi listesine geri dönmesi için bir bağlantı sunulur
[erreurs.jsp] görünümü
Bu görünüm, uygulamanın başlatılmasında meydana gelen hataları bildiren bir sayfayı (c.a.d) ve denetleyici servletinin [init] yönteminin yürütülmesi sırasında tespit edilen hataları görüntülemek için kullanılır. Bu, örneğin aşağıdaki örnekte gösterildiği gibi [web.xml] dosyasında bir parametrenin eksik olması olabilir:

[erreurs.jsp] sayfasının kodu şöyledir:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body>
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
<c:forEach var="erreur" items="${erreurs}">
<li>${erreur}</li>
</c:forEach>
</ul>
</body>
</html>
Sayfa, şablonunda [erreurs] öğesini alır; bu öğe, [ArrayList] türünde bir nesnedir ve [String] nesnelerinden oluşur; bu nesneler ise hata mesajlarıdır. Bunlar, 13-15. satırlardaki döngü ile görüntülenir.
14.8.3. Uygulama denetleyicisi
[Application] denetleyicisi, [istia.st.mvc.personnes.web] paketinde tanımlanmıştır:
![]()
'in yapısı ve denetleyicinin başlatılması
[Application] denetleyicisinin iskeleti şu şekildedir:
- 20-36. satırlar: [web.xml] dosyasında beklenen parametreler alınır.
- 39-41. satırlar: [urlErreurs] parametresi, olası başlatma hatalarını görüntüleyebilen [erreurs] görünümünün URL'sini belirlediği için mutlaka mevcut olmalıdır. Bu parametre yoksa, [ServletException] çalıştırılarak uygulama durdurulur (satır 40). Bu istisna web sunucusuna iletilir ve [web.xml] dosyasındaki <error-page> etiketiyle yönetilir. Böylece [exception.jsp] görünümü görüntülenir:

Yukarıdaki [Retour à la liste] bağlantısı çalışmaz. Uygulama değiştirilip yeniden yüklenene kadar bu bağlantı kullanıldığında aynı yanıt verilir. Daha önce gördüğümüz gibi, bu bağlantı diğer istisna türleri için de yararlıdır.
- 43. satır: [dao] katmanını uygulayan bir [DaoImpl] örneği oluşturur
- 44. satır: Bu örneği başlatır (üç kişiden oluşan bir başlangıç listesi oluşturur)
- 46. satır: [service] katmanını uygulayan bir [ServiceImpl] örneği oluşturur
- 47. satır: [service] katmanını, [dao] katmanına bir referans vererek başlatır
Denetleyicinin başlatılmasından sonra, denetleyicinin yöntemleri, [service] katmanına ait bir [service] referansına (satır 15) sahip olur ve bu referansı, kullanıcı tarafından talep edilen eylemleri yürütmek için kullanır. Bu eylemler, [doGet] yöntemi tarafından yakalanacak ve denetleyicinin özel bir yöntemi tarafından işlenecektir:
Url | HTTP yöntemi | denetleyici yöntemi |
GET | doListPersonnes | |
GET | doEditPersonne | |
POST | doValidatePersonne | |
GET | doDeletePersonne |
[doGet] yöntemi
Bu yöntemin amacı, kullanıcı tarafından talep edilen eylemlerin işlenmesini doğru yönteme yönlendirmektir. Kod şöyledir:
- 7-13. satırlar: Başlatma hataları listesinin boş olup olmadığı kontrol edilir. Boş değilse, hatayı veya hataları bildirecek olan [erreurs(erreurs)] görünümü görüntülenir.
- 15. satır: Müşterinin isteğini yapmak için kullandığı [get] veya [post] yöntemini alırız.
- 17. satır: İsteğin [action] parametresinin değeri alınır.
- 23-27. satırlar: Kişi listesini isteyen [GET /do/list] isteğinin işlenmesi.
- 28-32. satırlar: Bir kişinin silinmesini talep eden [GET /do/delete] isteğinin işlenmesi.
- satır 33-37: Bir kişinin güncelleme formunu talep eden [GET /do/edit] isteğinin işlenmesi.
- satır 38-42: Güncellenen kişinin onaylanmasını talep eden [POST /do/validate] isteğinin işlenmesi.
- 44. satır: İstenen eylem önceki beş eylemden biri değilse, o zaman [GET /do/list] gibi işlem yapılır.
[doListPersonnes] yöntemi
Bu yöntem, kişi listesini talep eden [GET /do/list] isteğini işler:

Kodu şu şekildedir:
- 5. satır: [service] katmanından grubun kişi listesi istenir ve bu liste, şablonda "kişiler" anahtarı altında yerleştirilir.
- 7. satır: 14.8.2. paragrafında açıklanan [list.jsp] görünümünü görüntülenir.
[doDeletePersonne] yöntemi
Bu yöntem, id=XX olan kişinin silinmesini talep eden [GET /do/delete?id=XX] sorgusunu işler. [/do/delete?id=XX] URL'si, [list.jsp] görünümündeki [Supprimer] bağlantılarının URL'sidir:

ve kodu şöyledir:
- satırda, [Supprimer] bağlantısının [/do/delete?id=XX] URL'si görülmektedir. Bu URL'yi işlemekle görevli [doDeletePersonne] yöntemi, id=XX olan kişiyi silmeli ve ardından grubun yeni kişi listesini görüntülemelidir. Kod şu şekildedir:
- 5. satır: İşlenen URL, [/do/delete?id=XX] biçimindedir. [id] parametresinden [XX] değeri alınır.
- 7. satır: [service] katmanından, elde edilen kimliğe sahip kişinin silinmesi istenir. Herhangi bir doğrulama yapmıyoruz. Silinmesi istenen kişi mevcut değilse, [dao] katmanı bir istisna oluşturur ve bu istisna [service] katmanı tarafından üst katmana iletilir. Bunu denetleyicide de yönetmiyoruz. Dolayısıyla istisna, yapılandırma gereği 14.8.2 numaralı paragrafta açıklanan [exception.jsp] sayfasını görüntüleyecek olan web sunucusuna iletilir:

- 9. satır: Silme işlemi gerçekleştiyse (istisna yok), istemciden [list] göreceli URL'sine yönlendirilmesi istenir. Az önce işlenen URL [/do/delete] olduğu için, yönlendirme URL'si [/do/list] olacaktır. Böylece tarayıcı, kişi listesinin görüntülenmesini sağlayacak olan [GET /do/list] adresine yönlendirilecektir.
[doEditPersonne] yöntemi
Bu yöntem, id=XX olan kişinin güncelleme formunu talep eden [GET /do/edit?id=XX] isteğini işler. [/do/edit?id=XX] URL'si, [Modifier] bağlantılarının ve [list.jsp] görünümündeki [Ajout] bağlantısının URL'sidir:

kodu şu şekildedir:
- satırda, [Modifier] bağlantısının [/do/edit?id=XX] URL'si ve 17. satırda, [Ajout] bağlantısının [/do/edit?id=-1] URL'si görülmektedir. [doEditPersonne] yöntemi, id=XX olan kişinin düzenleme formunu görüntülemeli veya yeni bir giriş söz konusuysa boş bir form sunmalıdır.
![]() | ![]() |
[doEditPersonne] yönteminin kodu şöyledir:
- GET, [/do/edit?id=XX] türünde bir URL'yi hedefler. 5. satırda, [id]'in değerini alıyoruz. Ardından iki durum söz konusudur:
- id, -1'den farklıysa, bu bir düzenleme işlemidir ve değiştirilecek kişinin bilgileriyle önceden doldurulmuş bir form görüntülenmelidir. 10. satırda, bu kişi [service] katmanından istenir.
- id değeri -1'e eşitse, bu bir ekleme işlemidir ve boş bir form görüntülenmelidir. Bunun için 13-14. satırlarda boş bir kişi oluşturulur.
- Elde edilen [Personne] nesnesi, 14.8.2 paragrafında açıklanan [edit.jsp] sayfa şablonuna yerleştirilir. Bu şablon, [erreurEdit, id, version, prenom, erreurPrenom, nom, erreurNom, dateNaissance, erreurDateNaissance, marie, nbEnfants, erreurNbEnfants] öğelerini içerir. Bu öğeler, değeri boş dize olan [erreurPrenom, erreurNom, erreurDateNaissance, erreurNbEnfants] hariç olmak üzere 17-30. satırlarda başlatılır. Şu bilinmektedir ki, şablonda bulunmadıkları takdirde, JSTL kütüphanesi bunların değeri olarak boş bir dize görüntüleyecektir. [erreurEdit] öğesinin değeri de boş bir dize olsa da, [edit.jsp] sayfasında değeri üzerinde bir test yapıldığı için yine de başlatılır.
- Şablon hazır hale geldiğinde, kontrol [edit.jsp] sayfasının 32-33. satırlarına geçer ve bu sayfa [edit] görünümünü oluşturur.
[doValidatePersonne] yöntemi
Bu yöntem, güncelleme formunu doğrulayan [POST /do/validate] isteğini işler. Bu POST, [Valider] düğmesi tarafından tetiklenir:

Yukarıdaki görünümdeki HTML formundaki giriş alanlarını hatırlayalım:
POST isteği, [prenom, nom, dateNaissance, marie, nbEnfants, id, version] parametrelerini içerir ve [/do/validate] URL'sine gönderilir (1. satır). Bu istek, aşağıdaki [doValidatePersonne] yöntemi tarafından işlenir:
- 8-14. satırlar: POST isteğinin [prenom] parametresi alınır ve geçerliliği kontrol edilir. Yanlış olduğu tespit edilirse, [erreurPrenom] öğesi bir hata mesajıyla başlatılır ve isteğin özniteliklerine eklenir.
- 16-22. satırlar: [nom] parametresi için de benzer şekilde işlem yapılır
- 24-32. satırlar: [dateNaissance] parametresi için de benzer şekilde işlem yapılır
- 34. satır: [marie] parametresi alınır. Geçerliliği kontrol edilmez, çünkü ilk bakışta bir radyo düğmesinin değerinden geldiği varsayılır. Bununla birlikte, bir programın [POST /personnes-01/do/validate] parametresini, uydurma bir [marie] parametresiyle birlikte oluşturmasını engelleyen hiçbir şey yoktur. Bu nedenle bu parametrenin geçerliliğini test etmeliyiz. Burada, denetleyici bunları kendisi yönetemediği takdirde [exception.jsp] sayfasının görüntülenmesini sağlayan istisna yönetimine güveniyoruz. Dolayısıyla, 34. satırda [marie] parametresinin boole değerine dönüştürülmesi başarısız olursa, bir istisna ortaya çıkacak ve bu da [exception.jsp] sayfasının müşteriye gönderilmesine yol açacaktır. Bu işleyiş bizim için uygundur.
- 34-54. satırlar: [nbEnfants] parametresini alır ve değerini kontrol ederiz.
- 56. satır: [id] parametresini, değerini kontrol etmeden alıyoruz
- 58. satır: [version] parametresi için de aynı işlem yapılır
- 60-65. satırlar: Form hatalıysa, daha önce oluşturulan hata mesajlarıyla birlikte yeniden görüntülenir
- 67-69. satırlar: Form geçerliyse, form öğeleriyle yeni bir [Personne] nesnesi oluşturulur
- satır 70-78: Kişi kaydedilir. Kaydetme işlemi başarısız olabilir. Çoklu kullanıcı ortamında, değiştirilecek kişi silinmiş veya başka biri tarafından zaten değiştirilmiş olabilir. Bu durumda, [dao] katmanı bir istisna oluşturur ve bu istisna burada yönetilir.
- 80. satır: İstisna oluşmamışsa, kullanıcıyı grubun yeni durumunu göstermek üzere [/do/list] URL’sine yönlendiririz.
- 75. satır: Kaydetme sırasında bir istisna oluştuysa, istisnaya ait hata mesajını (3. parametre) ileterek ilk formun yeniden görüntülenmesini talep ederiz.
[showFormulaire] yöntemi (satır 84-101), girilen değerlerle (request.getParameter(" ... ")) [edit.jsp] sayfası için gerekli şablonu oluşturur. Hata mesajlarının daha önce [doValidatePersonne] yöntemi tarafından şablona yerleştirildiğini hatırlayalım. [edit.jsp] sayfası 99-100. satırlarda görüntülenir.
14.9. Web uygulamasının testleri
14.1. paragrafında bir dizi test sunulmuştur. Okuyucuyu bu testleri tekrar yapmaya davet ediyoruz. Burada, çoklu kullanıcı ortamında veri erişim çakışmaları durumlarını gösteren başka ekran görüntüleri sunuyoruz:
[Firefox], U1 kullanıcısının tarayıcısı olacaktır. Bu kullanıcı, [http://localhost:8080/personnes-01] URL’sini talep eder:

[IE], U2 kullanıcısının tarayıcısı olacaktır. Bu kullanıcı aynı URL'yi ister:

U1 kullanıcısı, [Lemarchand] kişisinin düzenleme sayfasına girer:

U2 kullanıcısı da aynısını yapar:

U1 kullanıcısı değişiklikleri yapar ve onaylar:
![]() |
U2 kullanıcısı da aynısını yapıyor:
![]() |
U2 kullanıcısı, formdaki [Annuler] bağlantısını kullanarak kişi listesine geri döner:

[Lemarchand] adlı kişiyi, U1 tarafından değiştirilmiş haliyle bulur. Şimdi U2, [Lemarchand]'i siler:
![]() |
U1 her zaman kendi listesine sahiptir ve [Lemarchand]'i yeniden değiştirmek ister:
![]() |
U1, ne olduğunu görmek için [Retour à la liste] bağlantısını kullanır:

Gerçekten de [Lemarchand]'in artık listede olmadığını fark eder...
14.10. Conclusion
Kişi listesi yönetimine ilişkin basit bir örnek üzerinde, 3 katmanlı bir mimari olan [web, metier, dao] içinde MVC mimarisini hayata geçirdik. Bu, önceki bölümlerde sunulan kavramları kullanmamızı sağladı. İncelenen sürümde, kişi listesi bellekte tutuluyordu. Yakında bu listenin bir veritabanı tablosunda tutulduğu sürümleri inceleyeceğiz.
Ancak öncelikle, bir ntier uygulamasının farklı katmanlarının entegrasyonunu kolaylaştıran Spring IoC adlı bir aracı tanıtacağız.

















