3. JPA çok katmanlı bir mimaride
API JPA'i incelemek için aşağıdaki test mimarisini kullandık:
![]() |
Test programlarımız, JPA katmanını doğrudan sorgulayan konsol uygulamalarıydı. Bu süreçte, JPA katmanının temel yöntemlerini keşfettik. "Java SE" (Standard Edition) olarak adlandırılan bir ortamda çalışıyorduk. JPA, hem Java SE hem de Java EE5 (Enterprise Edition) ortamlarında çalışır.
Artık hem ilişkisel/nesne köprüsünün yapılandırmasını hem de JPA katmanındaki yöntemlerin kullanımını bir ölçüde kavradığımıza göre, daha klasik bir çok katmanlı mimariye geri dönüyoruz:
![]() |
[JPA] katmanına, [metier] ve [dao]'ten oluşan 2 katmanlı bir mimari aracılığıyla erişilecektir. Bu katmanları birbirine bağlamak için Spring [7] çerçeve yapısı ve ardından JBoss ile [8]'in EJB3 konteyneri kullanılacaktır.
Yukarıda, JPA'in SE ve EE5 ortamlarında kullanılabilir olduğunu belirtmiştik. EE5 Java ortamı, kalıcı verilere erişim alanında bağlantı havuzları, işlem yöneticileri gibi birçok hizmet sunar. Bir geliştirici için bu hizmetlerden yararlanmak faydalı olabilir. EE5 Java ortamı henüz yaygın değildir (Mayıs 2007). Şu anda Sun Application Server 9.x (Glassfish) uygulama sunucusunda bulunur. Bir uygulama sunucusu, esasen bir web uygulaması sunucusudur. Swing türü bağımsız bir grafik uygulama geliştiriliyorsa, EE ortamından ve bu ortamın sunduğu hizmetlerden yararlanmak mümkün değildir. Bu bir sorundur. Artık "bağımsız" EE ortamları ve c.a.d ortamları görülmeye başlanmıştır. Uygulama sunucusu dışında kullanılabilen ortamlar. Bu belgede kullanacağımız JBoss ve EJB3 de bu türden ortamlardır.
Bir EE5 ortamında, katmanlar EJB (Enterprise Java Bean) adı verilen nesnelerle uygulanır. EE'in önceki sürümlerinde, EJB'lerin (EJB, 2.x) uygulanması ve test edilmesi zor olduğu ve bazen düşük performans gösterdiği bilinmektedir. EJB2.x "entity" ve EJB2.x "session" arasında bir ayrım yapılır. Kısaca, bir EJB2.x "entity", bir veritabanı tablosundaki satırın görüntüsüdür; EJB2.x "session" ise çok katmanlı bir mimaride [metier] katmanlarını uygulamak için kullanılan bir nesnedir, [dao] katmanlarını uygulamak için kullanılan bir nesnedir. EJB ile uygulanan katmanlara yöneltilen başlıca eleştirilerden biri, bunların yalnızca EJB kapsayıcıları içinde kullanılabilmesidir; bu kapsayıcılar, EE ortamı tarafından sağlanan bir hizmettir. Bu durum, birim testlerini zorlaştırmaktadır. Dolayısıyla yukarıdaki şemada, EJB ile oluşturulan [metier] ve [dao] katmanlarının birim testleri, bir uygulama sunucusunun kurulmasını gerektirecektir; bu oldukça zahmetli bir işlemdir ve geliştiricinin sık sık test yapmasını pek teşvik etmez.
Spring çerçevesi, EJB2'lerin karmaşıklığına bir tepki olarak ortaya çıkmıştır. Spring, bir SE ortamında, genellikle EE ortamları tarafından sağlanan hizmetlerin önemli bir kısmını sunar. Dolayısıyla, burada ilgilendiğimiz "Veri Kalıcılığı" bölümünde Spring, uygulamaların ihtiyaç duyduğu bağlantı havuzlarını ve işlem yöneticilerini sağlar. Spring'in ortaya çıkışı, bir anda uygulanması çok daha kolay hale gelen birim test kültürünü teşvik etmiştir. Spring, bir uygulamanın katmanlarının klasik Java nesneleri (POJO, Plain Old/Ordinary Java Object) ile uygulanmasına olanak tanıyarak, bunların başka bir bağlamda yeniden kullanılmasını sağlar. Son olarak, Hibernate, Ibatis gibi kalıcılık araçları başta olmak üzere birçok üçüncü taraf aracını oldukça şeffaf bir şekilde entegre eder.
Java EE5, önceki EE spesifikasyonundaki eksiklikleri gidermek üzere tasarlanmıştır. EJB ve 2.x, EJB3 haline gelmiştir. Bunlar, bir EJB3 konteyneri içindeyken onları özel nesneler haline getiren açıklamalarla etiketlenmiş POJOs'lerdir. Bu kapsayıcı içinde, EJB3, kapsayıcının hizmetlerinden (bağlantı havuzu, işlem yöneticisi vb.) yararlanabilecektir. EJB3 konteynerinin dışında, EJB3 normal bir Java nesnesi haline gelir. EJB anotasyonları göz ardı edilir.
Yukarıda, Spring ve JBoss ile EJB3'i, çok katmanlı mimarimizin olası bir altyapısı (çerçeve) olarak gösterdik. İhtiyacımız olan hizmetleri (bağlantı havuzu ve işlem yöneticisi) sağlayacak olan bu altyapıdır.
- Spring ile katmanlar, POJOs'ler kullanılarak uygulanacaktır. Bu POJOs'ler, Spring hizmetlerine (bağlantı havuzu, işlem yöneticisi) bağımlılık enjeksiyonu yoluyla erişecektir: bu POJOs'ler oluşturulurken, Spring onlara ihtiyaç duyacakları hizmetlere ilişkin referansları enjekte eder.
- JBoss EJB3, bir uygulama sunucusunun dışında çalışabilen bir EJB konteyneridir. Çalışma prensibi (geliştirici açısından) Spring için açıklananla benzerdir. Aralarında çok az fark vardır.
Bu belgeyi, basit ancak yine de temsil edici nitelikteki üç katmanlı bir web uygulaması örneğiyle sonlandıracağız:
![]() |
3.1. Örnek 1: Spring / JPA ile Kişi varlığı
2.1. paragrafta incelenen Personne varlığını alıyoruz ve katmanların entegrasyonunun Spring ile yapıldığı, kalıcılık katmanının ise Hibernate tarafından uygulandığı çok katmanlı bir mimariye entegre ediyoruz.
![]() |
Burada okuyucunun Spring hakkında temel bilgilere sahip olduğu varsayılmaktadır. Aksi takdirde, Spring’in temelini oluşturan bağımlılık enjeksiyonu kavramını açıklayan aşağıdaki belgeyi okuyabilirsiniz:
[ref3]: Spring IOC (Kontrolün Tersine Çevrilmesi) [http://tahe.developpez.com/java/springioc].
3.1.1. Eclipse / Spring / Hibernate projesi
Eclipse projesi şöyledir:
![]() |
![]() |
- [1]'te: Eclipse projesi. Bu proje, [6]'te, [5] numaralı öğreticinin örnekleri arasında bulunabilir. Bu projeyi içe aktaracağız.
- [2] dosyasında: paketler halinde sunulan katmanların Java kodları:
- [entites]: JPA varlık paketini
- [dao]: Veri erişim katmanı - JPA katmanına dayanır
- [service]: iş mantığından çok hizmet odaklı bir katman. Burada konteynerlerin işlem hizmeti kullanılacaktır.
- [tests]: Test programlarını bir araya getirir.
- [3]: [jpa-spring] kütüphanesi, Spring için gerekli jar dosyalarını bir araya getirir (ayrıca bkz. [7] ve [8]).
- [4]: [conf] klasörü, bu öğreticide kullanılan her bir SGBD için Spring yapılandırma dosyalarını bir araya getirir.
3.1.2. t JPA
![]() |
Burada yönetilen tek bir varlık vardır: 2.1. paragrafta incelenen Personne varlığı. Bu varlığın yapılandırmasını aşağıda tekrar hatırlatıyoruz:
package entites;
...
@Entity
@Table(name="jpa01_hb_personne")
public class Personne {
@Id
@Column(name = "ID", nullable = false)
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column(name = "VERSION", nullable = false)
@Version
private int version;
@Column(name = "NOM", length = 30, nullable = false, unique = true)
private String nom;
@Column(name = "PRENOM", length = 30, nullable = false)
private String prenom;
@Column(name = "DATENAISSANCE", nullable = false)
@Temporal(TemporalType.DATE)
private Date datenaissance;
@Column(name = "MARIE", nullable = false)
private boolean marie;
@Column(name = "NBENFANTS", nullable = false)
private int nbenfants;
// oluşturucular
public Personne() {
}
public Personne(String nom, String prenom, Date datenaissance, boolean marie,
int nbenfants) {
...
}
// toString
public String toString() {
return String.format("[%d,%d,%s,%s,%s,%s,%d]", getId(), getVersion(),
getNom(), getPrenom(), new SimpleDateFormat("dd/MM/yyyy")
.format(getDatenaissance()), isMarie(), getNbenfants());
}
// alıcı ve ayarlayıcılar
...
}
3.1.3. [dao] katmanı
![]() | ![]() |
[dao] katmanı, aşağıdaki IDao arayüzünü sunar:
package dao;
import java.util.List;
import entites.Personne;
public interface IDao {
// kimlik numarasıyla bir kişiyi bulma
public Personne getOne(Integer id);
// tüm kişileri alma
public List<Personne> getAll();
// bir kişiyi kaydetme
public Personne saveOne(Personne personne);
// bir kişiyi güncellemek
public Personne updateOne(Personne personne);
// kimlik numarasıyla bir kişiyi silme
public void deleteOne(Integer id);
// adı belirli bir şablona uyan kişileri al
public List<Personne> getAllLike(String modele);
}
Bu arayüzün [Dao] uygulaması şöyledir:
package dao;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import entites.Personne;
public class Dao implements IDao {
@PersistenceContext
private EntityManager em;
// kimlik numarasıyla bir kişiyi sil
public void deleteOne(Integer id) {
Personne personne = em.find(Personne.class, id);
if (personne == null) {
throw new DaoException(2);
}
em.remove(personne);
}
@SuppressWarnings("unchecked")
// tüm kişileri almak
public List<Personne> getAll() {
return em.createQuery("select p from Personne p").getResultList();
}
@SuppressWarnings("unchecked")
// adları belirli bir şablona uyan kişileri almak
public List<Personne> getAllLike(String modele) {
return em.createQuery("select p from Personne p where p.nom like :modele")
.setParameter("modele", modele).getResultList();
}
// kimlik numarasıyla bir kişiyi bul
public Personne getOne(Integer id) {
return em.find(Personne.class, id);
}
// bir kişiyi kaydetmek
public Personne saveOne(Personne personne) {
em.persist(personne);
return personne;
}
// bir kişiyi güncelle
public Personne updateOne(Personne personne) {
return em.merge(personne);
}
}
- Öncelikle, [Dao] uygulamasının basitliğine dikkat çekmek gerekir. Bu basitlik, veri erişim işinin büyük kısmını üstlenen JPA katmanının kullanılmasına bağlıdır.
- 10. satır: [Dao] sınıfı, [IDao] arayüzünü uygular
- 13. satır: JPA kalıcılık bağlamını işlemek için kullanılacak [EntityManager] türündeki nesne. Dil kullanımındaki bir yanlışlık nedeniyle, bunu bazen kalıcılık bağlamının kendisiyle karıştıracağız. Kalıcılık bağlamı, Personne varlıklarını içerecektir.
- 12. satır: Kodun hiçbir yerinde [EntityManager em] alanı başlatılmamıştır. Bu alan, uygulamanın başlatılması sırasında Spring tarafından başlatılacaktır. 12. satırdaki JPA @PersistenceContext anotasyonu, Spring'den em'e bir kalıcılık bağlam yöneticisi enjekte etmesini ister.
- 26-28. satırlar: Tüm kişilerin listesi, JPQL sorgusu ile alınır.
- 32-35. satırlar: Adı belirli bir modele uyan tüm kişilerin listesi, JPQL sorgusu ile alınır.
- satır 38-40: Belirli bir kimliğe sahip kişi, API JPA'in find yöntemi ile alınır. Kişi mevcut değilse, bir null işaretçisi döndürür.
- satır 43-46: Bir kişi, API ve JPA'in persist yöntemiyle kalıcı hale getirilir. Yöntem, kişiyi kalıcı hale getirir.
- satır 49-51: Bir kişinin güncellenmesi, API JPA'in merge yöntemi ile gerçekleştirilir. Bu yöntem, güncellenen kişinin daha önce ayrılmış olması durumunda anlamlıdır. Yöntem, bu şekilde oluşturulan kişiyi kalıcı hale getirir.
- 16-22. satırlar: Parametre olarak verilen kimlik numarasına sahip kişinin silinmesi iki aşamada gerçekleşir:
- 17. satır: Kişi, kalıcı veritabanı bağlamında aranır
- 18-20. satırlar: Kişi bulunamazsa, hata kodu 2 ile bir istisna atılır
- 21. satır: Kişi bulunursa, API JPA'in remove yöntemi kullanılarak kalıcılık bağlamından kaldırılır.
- Şu anda görünmeyen şey, her yöntemin [service] katmanı tarafından başlatılan bir işlem içinde yürütüleceğidir.
Uygulamanın [DaoException] adlı kendine özgü bir istisna türü vardır:
package dao;
@SuppressWarnings("serial")
public class DaoException extends RuntimeException {
// hata kodu
private int code;
public DaoException(int code) {
super();
this.code = code;
}
public DaoException(String message, int code) {
super(message);
this.code = code;
}
public DaoException(Throwable cause, int code) {
super(cause);
this.code = code;
}
public DaoException(String message, Throwable cause, int code) {
super(message, cause);
this.code = code;
}
// getter ve setter
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
- 4. satır: [DaoException], [RuntimeException]'ten türetilmiştir. Dolayısıyla bu, derleyicinin bizden try/catch ile yönetmemizi veya yöntem imzalarına eklememizi zorunlu kılmadığı bir istisna türüdür. Bu nedenle, [DaoException], [IDao] arayüzünün [deleteOne] yönteminin imzasında yer almamaktadır. Bu, söz konusu arayüzün, aynı zamanda [RuntimeException] sınıfından türetilmiş olması koşuluyla, başka türde istisnalar atan bir sınıf tarafından uygulanmasına olanak tanır.
- Oluşabilecek hataları ayırt etmek için 7. satırdaki hata kodu kullanılır. 14., 19. ve 24. satırlardaki üç yapıcı, üst sınıf [RuntimeException]’e ait olanlardır ve bunlara bir parametre eklenmiştir: istisnaya atanacak hata kodu.
3.1.4. [metier / service] katmanı
![]() |
[service] katmanı, aşağıdaki [IService] arayüzünü sunar:
package service;
import java.util.List;
import entites.Personne;
public interface IService {
// kimlik numarasıyla bir kişiyi almak
public Personne getOne(Integer id);
// tüm kişileri almak
public List<Personne> getAll();
// bir kişiyi kaydetmek
public Personne saveOne(Personne personne);
// bir kişiyi güncellemek
public Personne updateOne(Personne personne);
// bir kişiyi kimlik numarasıyla silmek
public void deleteOne(Integer id);
// adı belirli bir şablona uyan kişileri almak
public List<Personne> getAllLike(String modele);
// birden fazla kişiyi aynı anda sil
public void deleteArray(Personne[] personnes);
// birden fazla kişiyi aynı anda kaydetmek
public Personne[] saveArray(Personne[] personnes);
// birden fazla kişiyi aynı anda güncelleme
public Personne[] updateArray(Personne[] personnes);
}
- 8-24. satırlar: [IService] arayüzü, [IDao] arayüzünün yöntemlerini içerir
- 27. satır: [deleteArray] yöntemi, bir işlem içindeki bir grup kişiyi silmeye olanak tanır: ya tüm kişiler silinir ya da hiçbiri silinmez.
- 30. ve 33. satırlar: Bir işlem içindeki bir grup kişiyi kaydetmek (30. satır) veya güncellemek (33. satır) için [deleteArray]'e benzer yöntemler.
[IService] arayüzünün [Service] uygulaması şu şekildedir:
package service;
...
// Sınıfın tüm yöntemleri bir işlem içinde gerçekleştirilir
@Transactional
public class Service implements IService {
// [dao] katmanı
private IDao dao;
public IDao getDao() {
return dao;
}
public void setDao(IDao dao) {
this.dao = dao;
}
// birden fazla kişiyi aynı anda silme
public void deleteArray(Personne[] personnes) {
for (Personne p : personnes) {
dao.deleteOne(p.getId());
}
}
// bir kişiyi kimlik numarasıyla silme
public void deleteOne(Integer id) {
dao.deleteOne(id);
}
// tüm kişileri almak
public List<Personne> getAll() {
return dao.getAll();
}
// adı belirli bir şablona uyan kişileri almak
public List<Personne> getAllLike(String modele) {
return dao.getAllLike(modele);
}
// kimlik numarasıyla bir kişiyi almak
public Personne getOne(Integer id) {
return dao.getOne(id);
}
// birden fazla kişiyi aynı anda kaydetmek
public Personne[] saveArray(Personne[] personnes) {
Personne[] personnes2 = new Personne[personnes.length];
for (int i = 0; i < personnes.length; i++) {
personnes2[i] = dao.saveOne(personnes[i]);
}
return personnes2;
}
// bir kişiyi kaydet
public Personne saveOne(Personne personne) {
return dao.saveOne(personne);
}
// birden fazla kişiyi aynı anda güncelle
public Personne[] updateArray(Personne[] personnes) {
Personne[] personnes2 = new Personne[personnes.length];
for (int i = 0; i < personnes.length; i++) {
personnes2[i] = dao.updateOne(personnes[i]);
}
return personnes2;
}
// bir kişiyi güncelle
public Personne updateOne(Personne personne) {
return dao.updateOne(personne);
}
}
- 6. satır: Spring @Transactional anotasyonu, sınıfın tüm yöntemlerinin bir işlem kapsamında yürütülmesi gerektiğini belirtir. Yöntemin yürütülmesine başlamadan önce bir işlem başlatılır ve yürütme tamamlandıktan sonra kapatılır. Yöntemin yürütülmesi sırasında [RuntimeException] türünde veya bunun türevlerinden bir istisna meydana gelirse, otomatik bir geri alma işlemi tüm işlemi iptal eder; aksi takdirde otomatik bir onaylama işlemi işlemi geçerli kılar. Java kodunun işlemlerle ilgilenmesine gerek olmadığı unutulmamalıdır. İşlemler Spring tarafından yönetilir.
- 10. satır: [dao] katmanına bir referans. Daha sonra göreceğimiz üzere, bu referans uygulamanın başlatılması sırasında Spring tarafından başlatılır.
- [Service] yöntemleri, yalnızca 10. satırdaki [IDao dao] arayüzünün yöntemlerini çağırır. Kodu incelemeyi okuyucuya bırakıyoruz. Özel bir zorluk yoktur.
- Daha önce, [Service]'in her yönteminin bir işlem içinde yürütüldüğünü belirtmiştik. Bu işlem, yöntemin yürütme iş parçacığına bağlıdır. Bu iş parçacığında, [dao] katmanındaki yöntemler yürütülür. Bu yöntemler, yürütme iş parçacığının işlemine otomatik olarak eklenecektir. Örneğin, [deleteArray] yöntemi (21. satır), [dao] katmanındaki [deleteOne] yöntemini N kez çalıştırır. Bu N yürütme, [deleteArray] yönteminin yürütme iş parçacığı içinde, yani aynı işlem içinde gerçekleşecektir. Dolayısıyla, her şey yolunda giderse tümü onaylanacak (commit), ya da [dao] katmanındaki [deleteOne] yönteminin N yürütmesinden birinde bir istisna meydana gelirse tümü geri alınacaktır (rollback).
3.1.5. Katmanların yapılandırılması
![]() | ![]() |
[service], [dao] ve [JPA] katmanlarının yapılandırması, yukarıdaki iki dosya tarafından sağlanır: [META-INF/persistence.xml] ve [spring-config.xml]. Bu iki dosya, uygulamanın classpath klasöründe bulunmalıdır; bu nedenle Eclipse projesinin [src] klasöründedirler. [spring-config.xml] dosyasının adı serbesttir.
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL" />
</persistence>
- 4. satır: Dosya, jpa adlı bir kalıcılık birimini tanımlamaktadır; bu birim, EJB3 konteyneri tarafından sağlanmayan “yerel” işlemler olan c.a.d’i kullanır. Bu işlemler Spring tarafından oluşturulur ve yönetilir; yapılandırmaları ise [spring-config.xml] dosyasında bulunur.
spring-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<!-- uygulama katmanları -->
<bean id="dao" class="dao.Dao" />
<bean id="service" class="service.Service">
<property name="dao" ref="dao" />
</bean>
<!-- kalıcılık katmanı JPA -->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--
<property name="showSql" value="true" />
-->
<property name="databasePlatform"
value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<property name="generateDdl" value="true" />
</bean>
</property>
<property name="loadTimeWeaver">
<bean
class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
<!-- veri kaynağı DBCP -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/jpa" />
<property name="username" value="jpa" />
<property name="password" value="jpa" />
</bean>
<!-- işlem yöneticisi -->
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory"
ref="entityManagerFactory" />
</bean>
<!-- istisna çevirisi -->
<bean
class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<!-- kalıcılık açıklamaları -->
<bean
class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
</beans>
- 2-5. satırlar: yapılandırma dosyasının kök <beans> etiketidir. Bu etiketin çeşitli öznitelikleri hakkında açıklama yapmayacağız. Bu özniteliklerden birinde hata yapılması, bazen anlaşılması zor hatalara yol açabileceğinden, kopyala/yapıştır işlemini dikkatli bir şekilde gerçekleştirin.
- 8. satır: "dao" bean'i, [dao.Dao] sınıfının bir örneğine yapılan bir referanstır. Tek bir örnek oluşturulacak (singleton) ve uygulamanın [dao] katmanını uygulayacaktır.
- 9-11. satırlar: [service] katmanının örneklenmesi. "service" bean'i, [service.Service] sınıfının bir örneğine bir referanstır. Tek bir örnek oluşturulacak (singleton) ve uygulamanın [service] katmanını uygulayacaktır. [service.Service] sınıfının [IDao dao] adlı özel bir alanı olduğunu görmüştük. Bu alan, 8. satırda tanımlanan "dao" bean'i tarafından 10. satırda başlatılır.
- Sonuç olarak, 8-11. satırlar [dao] ve [service] katmanlarını yapılandırmıştır. Bunların ne zaman ve nasıl örnekleneceğini daha sonra göreceğiz.
- 35-42. satırlar: Bir veri kaynağı tanımlanmıştır. Hibernate ile JPA varlıklarını incelerken veri kaynağı kavramıyla daha önce karşılaşmıştık:
![]() |
Yukarıda, "bağlantı havuzu" olarak adlandırılan [c3p0], "veri kaynağı" olarak da adlandırılabilirdi. Bir veri kaynağı, "bağlantı havuzu" hizmetini sağlar. Spring ile [c3p0] dışında bir veri kaynağı kullanacağız. Bu, Apache Commons projesine ait DBCP ve [http://jakarta.apache.org/commons/dbcp/]'ten gelen [DBCP]'tir. [DBCP] arşivleri, [jpa-spring] kullanıcı kütüphanesine yerleştirilmiştir:
![]() |
- 38-41. satırlar: Hedef veritabanıyla bağlantı kurmak için veri kaynağının kullanılan JDBC sürücüsünü (38. satır), veritabanının URL'sini (39. satır), bağlantı kullanıcısını ve şifresini (40-41. satırlar) bilmesi gerekir.
- 14-32. satırlar: JPA katmanını yapılandırır
- 14-15. satırlar: Kalıcılık bağlamlarını yönetmek üzere [EntityManager] türünde nesneler oluşturabilen [EntityManagerFactory] türünde bir bean tanımlar. Oluşturulan [LocalContainerEntityManagerFactoryBean] sınıfı Spring tarafından sağlanır. Bu sınıfın oluşturulabilmesi için 16-31. satırlarda tanımlanan bir dizi parametreye ihtiyaç duyar.
- 16. satır: SGBD'e bağlantı kurmak için kullanılacak veri kaynağı. Bu, 35-42. satırlarda tanımlanan [DBCP] kaynağıdır.
- 17-27. satırlar: Kullanılacak JPA uygulaması
- 18-26. satırlar: Hibernate'i (19. satır) kullanılacak JPA uygulaması olarak tanımlar
- 23-24. satırlar: Hibernate'in hedef SGBD ile birlikte kullanması gereken SQL lehçesi; burada hedef MySQL5'tir.
- 25. satır: Uygulamanın başlatılması sırasında veritabanının oluşturulmasını (drop ve create) talep eder.
- 28-31. satırlar: Bir "sınıf yükleyici" tanımlar. JPA katmanındaki EntityManagerFactory tarafından kullanılan bu bean'in rolünü net bir şekilde açıklayamam. Her neyse, bu, uygulamayı çalıştıran JVM'e, içeriği uygulamanın başlatılması sırasında sınıfların yüklenmesini yönetecek bir arşivin adının aktarılmasını gerektirir. Burada bu arşiv, [jpa-spring] kullanıcı kütüphanesinde bulunan [spring-agent.jar]'tir (yukarıya bakınız). Hibernate'in bu ajana ihtiyacı olmadığını, ancak Toplink'in buna ihtiyaç duyduğunu göreceğiz.
- 45-50. satırlar: kullanılacak işlem yöneticisini tanımlar
- 45. satır: İşlemlerin Java anotasyonlarıyla yönetildiğini belirtir (bunlar spring-config.xml içinde de tanımlanabilirdi). Bu, özellikle [Service] sınıfında (6. satır) görülen @Transactional anotasyonudur.
- 46-50. satırlar: işlem yöneticisi
- 47. satır: İşlem yöneticisi, Spring tarafından sağlanan bir sınıftır
- 48-49. satırlar: Spring işlem yöneticisi, JPA katmanını yöneten EntityManagerFactory sınıfını bilmelidir. Bu sınıf, 14-32. satırlarda tanımlanmıştır.
- satır 57-58: Java kodunda bulunan Spring kalıcılık anotasyonlarını yöneten sınıfı tanımlar; örneğin, [dao.Dao] sınıfındaki @PersistenceContext anotasyonu (satır 12).
- 53-54. satırlar: Özellikle @Repository anotasyonunu yöneten Spring sınıfını tanımlar; bu anotasyon, anotasyonu yapılmış bir sınıfın, SGBD Jdbc sürücüsünün yerel istisnalarının [DataAccessException] türündeki genel Spring istisnalarına dönüştürülmesine uygun hale getirir. Bu dönüştürme, yerel Jdbc istisnasını çeşitli alt sınıflara sahip [DataAccessException] türünde kapsüller:

Bu çeviri, istemci programının hedef SGBD ne olursa olsun istisnaları genel bir şekilde yönetmesine olanak tanır. Java kodumuzda @Repository anotasyonunu kullanmadık. Dolayısıyla 53-54. satırlar gereksizdir. Bunları sadece bilgilendirme amacıyla bıraktık.
Spring yapılandırma dosyası ile işimiz bitti. Bu dosya karmaşıktır ve pek çok husus belirsizliğini korumaktadır. Dosya, Spring belgelerinden alınmıştır. Neyse ki, çeşitli durumlara uyarlanması genellikle iki değişiklikle sınırlıdır:
- hedef veritabanı değişikliği: 38-41. satırlar. Bir Oracle örneği vereceğiz.
- JPA uygulamasının ayarlanması: 14-32. satırlar. Toplink örneği vereceğiz.
3.1.6. [InitDB] istemci programı
Daha önce açıklanan mimarinin ilk istemcisinin yazımına geçiyoruz:
![]() |
[InitDB] kodunun içeriği şöyledir:
package tests;
...
public class InitDB {
// hizmet katmanı
private static IService service;
// oluşturucu
public static void main(String[] args) throws ParseException {
// uygulama yapılandırması
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
// hizmet katmanı
service = (IService) ctx.getBean("service");
// veritabanını boşaltma
clean();
// veritabanını doldurma
fill();
// gözle kontrol ediliyor
dumpPersonnes();
}
// tablo içeriğinin görüntülenmesi
private static void dumpPersonnes() {
System.out.format("[personnes]%n");
for (Personne p : service.getAll()) {
System.out.println(p);
}
}
// tablo doldurma
public static void fill() throws ParseException {
// kullanıcılar oluşturuluyor
Personne p1 = new Personne("p1", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
Personne p2 = new Personne("p2", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
// kaydedilir
service.saveArray(new Personne[] { p1, p2 });
}
// tablodaki öğeleri silme
public static void clean() {
for (Personne p : service.getAll()) {
service.deleteOne(p.getId());
}
}
}
- 12. satır: [spring-config.xml] dosyası, dosyanın bellek görüntüsü olan bir [ApplicationContext ctx] nesnesi oluşturmak için kullanılır. Bu işlem sırasında [spring-config.xml] içinde tanımlanan bean'ler örneklenir.
- 14. satır: ctx uygulama bağlamından [service] katmanına ilişkin bir referans istenir. Bu katmanın "service" adlı bir bean ile temsil edildiği bilinmektedir.
- 16. satır: Veritabanı, clean yöntemi kullanılarak boşaltılır. 41-45. satırlar:
- 42-44. satırlar: Kalıcılık bağlamından tüm kişilerin listesi istenir ve bunlar tek tek silinmek üzere bir döngü içinde işlenir. Hatırlanacağı üzere, [spring-config.xml] yöntemi, veritabanının uygulamanın başlatılması sırasında oluşturulmasını gerektirir. Dolayısıyla bizim durumumuzda, boş bir veritabanından başladığımız için clean yönteminin çağrılmasına gerek yoktur.
- 18. satır: fill yöntemi veritabanını doldurur. Veritabanı 32-38. satırlarda tanımlanmıştır:
- 34-35. satırlar: iki kişi oluşturulur
- 37. satır: [service] katmanından bunların kalıcı hale getirilmesi istenir.
- 20. satır: dumpPersonnes yöntemi, kalıcı kişileri görüntüler. Bu yöntem 24-29. satırlarda tanımlanmıştır
- 26-28. satırlar: [service] katmanından tüm kalıcı kişilerin listesi istenir ve bunlar konsolda görüntülenir.
[InitDB]'in çalıştırılması şu sonucu verir:
3.1.7. [TestNG] birim testleri
[TestNG] eklentisinin kurulumu 5.2.4. paragrafında açıklanmaktadır. [TestNG] programının kodu şöyledir:
package tests;
....
public class TestNG {
// hizmet katmanı
private IService service;
@BeforeClass
public void init() {
// günlük
log("init");
// uygulama yapılandırması
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
// hizmet katmanı
service = (IService) ctx.getBean("service");
}
@BeforeMethod
public void setUp() throws ParseException {
// veritabanını boşaltma
clean();
// veritabanını doldurma
fill();
}
// günlükler
private void log(String message) {
System.out.println("----------- " + message);
}
// tablo içeriğinin görüntülenmesi
private void dump() {
log("dump");
System.out.format("[personnes]%n");
for (Personne p : service.getAll()) {
System.out.println(p);
}
}
// tablo doldurma
public void fill() throws ParseException {
log("fill");
// kullanıcı oluşturma
Personne p1 = new Personne("p1", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
Personne p2 = new Personne("p2", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
// kaydediliyor
service.saveArray(new Personne[] { p1, p2 });
}
// tablodaki öğelerin silinmesi
public void clean() {
log("clean");
for (Personne p : service.getAll()) {
service.deleteOne(p.getId());
}
}
@Test()
public void test01() {
...
}
...
}
- 9. satır: @BeforeClass yorumu, testler için gerekli yapılandırmayı başlatmak üzere çalıştırılacak yöntemi belirtir. Bu yöntem, ilk test çalıştırılmadan önce yürütülür. Burada kullanılmayan @AfterClass anotasyonu, tüm testler tamamlandıktan sonra çalıştırılacak yöntemi belirtir.
- 10-17. satırlar: @BeforeClass anotasyonuyla işaretlenen init yöntemi, Spring yapılandırma dosyasını kullanarak uygulamanın farklı katmanlarını örneklendirir ve [service] katmanına bir referans oluşturur. Daha sonra tüm testler bu referansı kullanır.
- 19. satır: @BeforeMethod anotasyonu, her testten önce çalıştırılacak yöntemi belirtir. Burada kullanılmayan @AfterMethod anotasyonu ise her testten sonra çalıştırılacak yöntemi belirtir.
- 20-25. satırlar: @BeforeMethod ile işaretlenmiş setUp yöntemi, veritabanını boşaltır (clean satır 52-56) ve ardından iki kişi ile doldurur (fill satır 42-49).
- 59. satır: @Test açıklaması, çalıştırılacak bir test yöntemini belirtir. Şimdi bu testleri açıklayacağız.
@Test()
public void test01() {
log("test1");
dump();
// kişi listesi
List<Personne> personnes = service.getAll();
assert 2 == personnes.size();
}
@Test()
public void test02() {
log("test2");
// kişileri adlarına göre arama
List<Personne> personnes = service.getAllLike("p1%");
assert 1 == personnes.size();
Personne p1 = personnes.get(0);
assert "Paul".equals(p1.getPrenom());
}
@Test()
public void test03() throws ParseException {
log("test3");
// yeni bir kişinin eklenmesi
Personne p3 = new Personne("p3", "x", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
// kayıtın kalıcı hale getirilmesi
service.saveOne(p3);
// kişiyi yeniden sorgulama
Personne loadedp3 = service.getOne(p3.getId());
// görüntüleme
System.out.println(loadedp3);
// doğrulama
assert "p3".equals(loadedp3.getNom());
}
- satır 2-8: 01 numaralı test. Her testin başlangıcında, veritabanında sırasıyla p1 ve p2 adlı iki kişi bulunduğunu unutmamak gerekir.
- 6. satır: Kişi listesi isteniyor
- 7. satır: Elde edilen listede kişi sayısının 2 olup olmadığı kontrol edilir
- 14. satır: adı p1 ile başlayan kişilerin listesi istenir
- Elde edilen listenin tek bir öğe içerdiği (satır 15) ve elde edilen tek kişinin adının "Paul" olduğu (satır 17) kontrol edilir
- 24. satır: p3 adlı bir kişi oluşturulur
- 25. satır: bu kişi kalıcı hale getirilir
- 28. satır: Doğrulama amacıyla bu kişiyi kalıcılık bağlamından yeniden istenir
- satır 32: Alınan kişinin adının gerçekten p3 olup olmadığını kontrol ediyoruz.
@Test()
public void test04() throws ParseException {
log("test4");
// p1 kişisini yükleme
List<Personne> personnes = service.getAllLike("p1%");
Personne p1 = personnes.get(0);
// görüntüleniyor
System.out.println(p1);
// doğrulama
assert "p1".equals(p1.getNom());
int version1 = p1.getVersion();
// adını değiştiriliyor
p1.setPrenom("x");
// kaydediliyor
service.updateOne(p1);
// yeniden yükleniyor
p1 = service.getOne(p1.getId());
// görüntüleniyor
System.out.println(p1);
// sürüm numarasının artırıldığını kontrol ediliyor
assert (version1 + 1) == p1.getVersion();
}
- 5. satır: p1 adlı kişiyi talep ediyoruz
- satır 10: adını kontrol ediyoruz
- 11. satır: Sürüm numarasını not ediyoruz
- 13. satır: Adı değiştirilir
- 15. satır: Değişiklik kaydedilir
- 17. satır: p1 kişisini tekrar çağırıyoruz
- 21. satır: sürüm numarasının 1 arttığını kontrol edin
@Test()
public void test05() {
log("test5");
// p2 kişisini yüklenir
List<Personne> personnes = service.getAllLike("p2%");
Personne p2 = personnes.get(0);
// görüntüleniyor
System.out.println(p2);
// kontrol ediliyor
assert "p2".equals(p2.getNom());
// p2 kişisini siliyoruz
service.deleteOne(p2.getId());
// yeniden yükleniyor
p2 = service.getOne(p2.getId());
// null işaretçisi elde edildiğini kontrol ediyoruz
assert null == p2;
// tablo görüntülenir
dump();
}
- 5. satır: p2 kişisini çağırıyoruz
- 10. satır: Adını kontrol ediyoruz
- 12. satır: Siliniyor
- 14. satır: Kişi yeniden aranır
- 16. satır: Kişinin bulunamadığı kontrol ediliyor
@Test()
public void test06() throws ParseException {
log("test6");
// aynı ada sahip 2 kişiden oluşan bir dizi oluşturulur (adın benzersizlik kuralı ihlal edilir)
Personne[] personnes = { new Personne("p3", "x", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2),
new Personne("p4", "x", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2),
new Personne("p4", "x", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2)};
// bu tablo kaydediliyor - bir istisna ve geri alma işlemi gerçekleşmelidir
boolean erreur = false;
try {
service.saveArray(personnes);
} catch (RuntimeException e) {
erreur = true;
}
// döküm
dump();
// kontroller
assert erreur;
// p3 adlı kişiyi arama
List<Personne> personnesp3 = service.getAllLike("p3%");
assert 0 == personnesp3.size();
// döküm
dump();
}
- 5. satır: İki kişinin adı "p4" olan üç kişilik bir tablo oluşturulur. Bu, @Entity Personne'in adının benzersiz olması kuralını ihlal eder:
@Column(name = "NOM", length = 30, nullable = false, unique = true)
private String nom;
- 11. satır: Üç kişiden oluşan dizi, kalıcılık bağlamına eklenir. İkinci kişi olan p4'ün eklenmesi başarısız olmalıdır. [saveArray] yöntemi bir işlem içinde gerçekleştirildiğinden, daha önce yapılmış olabilecek tüm eklemeler geri alınacaktır. Sonuç olarak, hiçbir ekleme yapılmayacaktır.
- 18. satır: [saveArray] yönteminin bir istisna oluşturduğu doğrulanır
- 20-21. satırlar: Eklenmiş olabilecek p3 kişisinin eklenmediğini kontrol ediyoruz.
@Test()
public void test07() {
log("test7");
// iyimser kilitleme testi
// p1 adlı kişiyi yüklüyor
List<Personne> personnes = service.getAllLike("p1%");
Personne p1 = personnes.get(0);
// görüntüleniyor
System.out.println(p1);
// çocuk sayısını artırıyoruz
int nbEnfants1 = p1.getNbenfants();
p1.setNbenfants(nbEnfants1 + 1);
// p1'i kaydediyoruz
Personne newp1 = service.updateOne(p1);
assert (nbEnfants1 + 1) == newp1.getNbenfants();
System.out.println(newp1);
// ikinci kez kaydediliyor - p1 artık doğru sürüme sahip olmadığı için bir istisna oluşması gerekir
// bunu newp1 aldı
boolean erreur = false;
try {
service.updateOne(p1);
} catch (RuntimeException e) {
erreur = true;
}
// doğrulama
assert erreur;
// newp1'in çocuk sayısını artırıyoruz
int nbEnfants2 = newp1.getNbenfants();
newp1.setNbenfants(nbEnfants2 + 1);
// newp1'e kaydediliyor
service.updateOne(newp1);
// yeniden yükleniyor
p1 = service.getOne(p1.getId());
// doğrulama
assert (nbEnfants1 + 2) == p1.getNbenfants();
System.out.println(p1);
}
- 6. satır: p1 kişisi istenir
- 12. satır: Çocuk sayısını 1 artırıyoruz
- 14. satır: p1 kişisi kalıcılık bağlamında güncellenir. [updateOne] yöntemi, p1’in yeni sürümü olan newp1’i kalıcı hale getirir. Bu sürüm, artırılmış olması gereken sürüm numarasıyla p1’den farklıdır.
- 15. satır: newp1'in çocuk sayısını kontrol ediyoruz.
- 21. satır: Eski sürüm p1'den yola çıkarak p1 kişisinin güncellenmesini yeniden talep ediyoruz. p1, p1 kişisinin en son sürümü olmadığı için bir istisna oluşması gerekir. En son sürüm newp1'dir.
- 23. satır: Hatanın gerçekten meydana geldiği kontrol edilir.
- 27-35. satırlar: En son sürüm olan newp1'den bir güncelleme yapıldığında her şeyin yolunda gittiği kontrol edilir.
@Test()
public void test08() {
log("test8");
// updateArray üzerinde geri alma testi
// p1 kişisi yükleniyor
List<Personne> personnes = service.getAllLike("p1%");
Personne p1 = personnes.get(0);
// görüntüleniyor
System.out.println(p1);
// çocuk sayısını artırılıyor
int nbEnfants1 = p1.getNbenfants();
p1.setNbenfants(nbEnfants1 + 1);
// 2 değişiklik kaydediliyor; bunlardan ikincisi başarısız olmalı (kişi yanlış başlatılmış)
// işlem nedeniyle her ikisi de iptal edilmelidir
boolean erreur = false;
try {
service.updateArray(new Personne[] { p1, new Personne() });
} catch (RuntimeException e) {
erreur = true;
}
// kontroller
assert erreur;
// p1 kişisi yeniden yükleniyor
personnes = service.getAllLike("p1%");
p1 = personnes.get(0);
// çocuk sayısı değişmemiş olmalı
assert nbEnfants1 == p1.getNbenfants();
}
- Test 8, test 6'ya benzerdir: İkinci kişinin doğru şekilde başlatılmadığı iki kişilik bir dizide çalışan updateArray üzerinde rollback'i kontrol eder. JPA açısından bakıldığında, henüz mevcut olmayan ikinci kişi üzerindeki birleştirme işlemiSQL insert emrini oluşturacak ve bu emir, nullable=false varlığının bazı alanlarında bulunan kısıtlamalar nedeniyle başarısız olacaktır.
@Test()
public void test09() {
log("test9");
// deleteArray üzerinde geri alma testi
// döküm
dump();
// p1 kişisi yükleniyor
List<Personne> personnes = service.getAllLike("p1%");
Personne p1 = personnes.get(0);
// görüntüleniyor
System.out.println(p1);
// 2 silme işlemi yapılıyor; bunlardan ikincisi başarısız olmalı (bilinmeyen kişi)
// işlem nedeniyle her ikisi de iptal edilmelidir
boolean erreur = false;
try {
service.deleteArray(new Personne[] { p1, new Personne() });
} catch (RuntimeException e) {
erreur = true;
}
// kontroller
assert erreur;
// p1 kişisi yeniden yükleniyor
personnes = service.getAllLike("p1%");
// doğrulama
assert 1 == personnes.size();
// döküm
dump();
}
- 9 numaralı test bir öncekine benzer: rollback'i, iki kişiden oluşan bir tabloda çalışan deleteArray üzerinde doğrular; bu tabloda ikinci kişi mevcut değildir. Ancak bu durumda, [dao] katmanındaki [deleteOne] yöntemi bir istisna oluşturur.
// iyimser kilitleme - çoklu iş parçacığı erişimi
@Test()
public void test10() throws Exception {
// bir kişinin eklenmesi
Personne p3 = new Personne("X", "X", new SimpleDateFormat("dd/MM/yyyy").parse("01/02/2006"), true, 0);
service.saveOne(p3);
int id3 = p3.getId();
// çocuk sayısını güncellemek için N iş parçacığı oluşturma
final int N = 20;
Thread[] taches = new Thread[N];
for (int i = 0; i < taches.length; i++) {
taches[i] = new ThreadMajEnfants("thread n° " + i, service, id3);
taches[i].start();
}
// iş parçacıklarının bitmesini bekliyoruz
for (int i = 0; i < taches.length; i++) {
taches[i].join();
}
// kişiyi alıyoruz
p3 = service.getOne(id3);
// bu kişinin N çocuğu olması gerekir
assert N == p3.getNbenfants();
// p3 kişisinin silinmesi
service.deleteOne(p3.getId());
// doğrulama
p3 = service.getOne(p3.getId());
// null işaretçisi olmalı
assert p3 == null;
}
- Test 10'un amacı, bir kişinin çocuk sayısını paralel olarak artırmak için N sayıda iş parçacığı (9. satır) başlatmaktır. Sürüm numarası sisteminin bu senaryoya karşı dayanıklı olduğunu doğrulamak istiyoruz. Sistem bu amaçla oluşturulmuştur.
- 5-6. satırlar: p3 adlı bir kişi oluşturulur ve kalıcı hale getirilir. Başlangıçta 0 çocuğu vardır.
- 7. satır: Kimliği kaydedilir.
- 9-14. satırlar: p3'ün çocuk sayısını 1 artırmakla görevli N adet iş parçacığı paralel olarak başlatılır.
- 16-18. satırlar: Tüm iş parçacıklarının tamamlanmasını bekliyoruz
- 20. satır: p3 adlı kişiyi görüntülemek istenir
- 22. satır: Artık N çocuğu olup olmadığı kontrol edilir.
- 24. satır: p3 silinir.
[ThreadMajEnfants] iş parçacığı şöyledir:
package tests;
...
public class ThreadMajEnfants extends Thread {
// iş parçacığının adı
private String name;
// katman referansı [service]
private IService service;
// üzerinde çalışılacak kişinin kimliği
private int idPersonne;
// oluşturucu
public ThreadMajEnfants(String name, IService service, int idPersonne) {
this.name = name;
this.service = service;
this.idPersonne = idPersonne;
}
// konu özü
public void run() {
// izleme
suivi("lancé");
// 1 artırmayı başaramayana kadar döngü devam eder
// idPersonne kişisinin çocuk sayısı
boolean fini = false;
int nbEnfants = 0;
while (!fini) {
// idPersonne'ten kişinin bir kopyası alınır
Personne personne = service.getOne(idPersonne);
nbEnfants = personne.getNbenfants();
// takip
suivi("" + nbEnfants + " -> " + (nbEnfants + 1) + " pour la version " + personne.getVersion());
// kişinin çocuk sayısını 1 artırır
personne.setNbenfants(nbEnfants + 1);
// işlemciyi serbest bırakmak için 10 ms bekler
try {
// devam
suivi("début attente");
// işlemciyi serbest bırakmak için kesintiye gidilir
Thread.sleep(10);
// takip
suivi("fin attente");
} catch (Exception ex) {
throw new RuntimeException(ex.toString());
}
// bekleme sona erdi - kopyayı onaylamaya çalışılıyor
// bu arada diğer iş parçacıkları orijinali değiştirmiş olabilir
try {
// orijinali değiştirmeye çalışılıyor
service.updateOne(personne);
// işlem tamamlandı - orijinal değiştirildi
fini = true;
} catch (javax.persistence.OptimisticLockException e) {
// nesnenin sürümü yanlış: yeniden başlamak için istisna göz ardı ediliyor
} catch (org.springframework.transaction.UnexpectedRollbackException e2) {
// zaman zaman ortaya çıkan Spring istisnası
} catch (RuntimeException e3) {
// başka bir istisna türü - istisna üst katmana iletiliyor
throw e3;
}
}
// izleme
suivi("a terminé et passé le nombre d'enfants à " + (nbEnfants + 1));
}
// takip
private void suivi(String message) {
System.out.println(name + " [" + new Date().getTime() + "] : " + message);
}
}
- satır 15-19: Oluşturucu, çalışmak için ihtiyaç duyduğu bilgileri hafızaya alır: kendi adı (satır 16), kullanması gereken [service] katmanındaki referans (satır 17) ve çocuk sayısını artırması gereken p kişisinin kimliği (satır 18).
- satır 22-66: Tüm iş parçacıkları tarafından paralel olarak yürütülen [run] yöntemi.
- 29. satır: İş parçacığı, p kişisinin çocuk sayısını artırmayı tekrar tekrar dener. Ancak başarılı olduğunda durur.
- satır 31: p kişisi sorgulanır
- satır 36: bellekte çocuk sayısı artırılır
- satır 38-47: 10 ms'lik bir duraklama yapılır. Bu, diğer iş parçacıklarının p kişisinin aynı sürümüne erişmesini sağlayacaktır. Böylece aynı anda, p kişisinin aynı sürümüne sahip olan ve onu değiştirmek isteyen birden fazla iş parçacığı olacaktır. İstenen de budur.
- 52. satır: Bekleme süresi bittiğinde, iş parçacığı [service] katmanından değişikliği kalıcı hale getirmesini ister. Zaman zaman istisnalar olacağı bilindiğinden, işlem bir try / catch bloğu içine alınmıştır.
- 55. satır: Testler, [javax.persistence.OptimisticLockException] türünde istisnalar oluştuğunu göstermektedir. Bu normaldir: Bir iş parçacığı, p kişisinin en son sürümüne sahip olmadan onu değiştirmeye çalıştığında JPA katmanı tarafından atılan istisnadır. Bu istisna, iş parçacığının işlemi başardığı ana kadar yeniden denemesine izin vermek için göz ardı edilir.
- 57. satır: Testler, [org.springframework.transaction.UnexpectedRollbackException] türünde istisnalar da oluştuğunu gösteriyor. Bu can sıkıcı ve beklenmedik bir durum. Bunun için bir açıklamam yok. Böylece Spring'e bağımlı hale geldik, oysa bunu önlemek istemiştik. Bu, örneğin uygulamamızı JBoss Ejb3'te çalıştırırsak, iş parçacığının kodunun değiştirilmesi gerektiği anlamına gelir. Burada da Spring istisnası, iş parçacığının artırım işlemini yeniden denemesine olanak sağlamak için göz ardı edilir.
- 59. satır: Diğer istisna türleri uygulamaya iletilir.
[TestNG] çalıştırıldığında şu sonuçlar elde edilir:

10 testin tümü başarıyla geçilmiştir.
10 numaralı test, başarılı olması biraz sihirli bir yönü olduğu için ek açıklamayı hak ediyor. Öncelikle [dao] katmanının yapılandırmasına geri dönelim:
public class Dao implements IDao {
@PersistenceContext
private EntityManager em;
- 4. satır: [EntityManager] nesnesi, JPA @PersistenceContext anotasyonu aracılığıyla em alanına eklenir. [dao] katmanı tek bir kez örneklenir. Bu, JPA katmanını kullanan tüm iş parçacıkları tarafından kullanılan bir singleton'dur. Dolayısıyla, EntityManager em değeri tüm iş parçacıkları tarafından paylaşılır. Bunu, [ThreadMajEnfants] iş parçacıkları tarafından kullanılan [updateOne] yönteminde em değerini görüntüleyerek doğrulayabiliriz: tüm iş parçacıkları için aynı değer elde edilir.
Bu durumda, tüm iş parçacıkları için aynı olan EntityManager em tarafından yönetilen farklı iş parçacıklarının kalıcı nesnelerinin birbirine karışıp aralarında çakışmalara yol açıp açmayacağı sorusu akla gelebilir. Bunun ne anlama gelebileceğine dair bir örnek [ThreadMajEnfants]'te bulunabilir:
while (!fini) {
// idPersonne'ten kişinin bir kopyası alınır
Personne personne = service.getOne(idPersonne);
nbEnfants = personne.getNbenfants();
// takip
suivi("" + nbEnfants + " -> " + (nbEnfants + 1) + " pour la version " + personne.getVersion());
// kişinin çocuk sayısını 1 artırır
personne.setNbenfants(nbEnfants + 1);
// işlemciyi serbest bırakmak için 10 ms bekler
try {
// takip
suivi("début attente");
// işlemciyi serbest bırakmak için kesintiye gidilir
Thread.sleep(10);
// takip
suivi("fin attente");
} catch (Exception ex) {
throw new RuntimeException(ex.toString());
}
- 3. satır: Bir T1 iş parçacığı p kişisini alır
- 8. satır: p'nin çocuk sayısını artırır
- 14. satır: T1 iş parçacığı bir ara verir
T2 iş parçacığı devreye girer ve o da 3. satırı yürütür: T1 ile aynı p kişisini ister. Eğer iş parçacıklarının kalıcılık bağlamı aynı olsaydı, T1 sayesinde bağlamda zaten bulunan p kişisi, T2'e geri verilmeliydi. Nitekim, [getOne] yöntemi, [EntityManager] yöntemini kullanır.API, JPA'i bulur ve bu yöntem, istenen nesne kalıcılık bağlamının bir parçası değilse veritabanına erişir; aksi takdirde kalıcılık bağlamındaki nesneyi döndürür. Böyle bir durumda, T1 ve T2 aynı p nesnesine sahip olurdu. T2 ise p'nin çocuk sayısını tekrar 1 artırırdı (8. satır). Duraklamadan sonra iş parçacıklarından biri güncellemeyi başarıyla gerçekleştirirse, p'nin çocuk sayısı beklendiği gibi 1 değil, 2 artmış olur. Bu durumda N iş parçacığının çocuk sayısını N'ye değil, daha fazla bir sayıya yükselteceği beklenebilir. Ancak durum böyle değildir. Bundan, T1 ve T2'in aynı p referansına sahip olmadığı sonucuna varabiliriz. Bunu, iş parçacıklarının p'nin adresini görüntülemesini sağlayarak doğrulayabiliriz: her biri için adres farklıdır.
Dolayısıyla, iş parçacıkları:
- aynı kalıcılık bağlam yöneticisini (EntityManager) paylaşıyor
- ancak her birinin kendine özgü bir kalıcılık bağlamı var.
Bunlar sadece varsayımlardır ve bu konuda bir uzmanın görüşü faydalı olabilir.
3.1.8. SGBD'e geçmek
![]() |
SGBD'i değiştirmek için, [src/spring-config.xml] [2] dosyasını, ilgili SGBD dosyasının [spring-config.xml] dosyasını [conf] [1] klasöründeki ilgili SGBD dosyasını [spring-config.xml] dosyasıyla değiştirmek yeterlidir.
Örneğin, Oracle'ın [spring-config.xml] dosyası şöyledir:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
...
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--
<property name="showSql" value="true" />
-->
<property name="databasePlatform" value="org.hibernate.dialect.OracleDialect" />
<property name="generateDdl" value="true" />
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
<!-- veri kaynağı DBCP -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
<property name="username" value="jpa" />
<property name="password" value="jpa" />
</bean>
...
</beans>
Daha önce MySQL5 için kullanılan aynı dosyaya kıyasla yalnızca bazı satırlar değişmiştir:
- 14. satır: Hibernate'in kullanması gereken SQL lehçesi
- 25-28. satırlar: SGBD ile JDBC bağlantısının özellikleri
Okuyucunun, MySQL5 için açıklanan testleri diğer SGBD dosyalarıyla da tekrarlaması önerilir.
3.1.9. JPA uygulamasını değiştirme
Önceki testlerin mimarisine geri dönelim:
![]() |
JPA / Hibernate uygulamasını, JPA / Toplink uygulamasıyla değiştiriyoruz. Toplink, Hibernate ile aynı kütüphaneleri kullanmadığından, yeni bir Eclipse projesi kullanıyoruz:
![]() |
- [1]: Eclipse projesi. Bu proje, öncekiyle aynıdır. Değişen tek şey, [spring-config.xml] ve [2] yapılandırma dosyaları ile [jpa-hibernate] kütüphanesinin yerine geçen [jpa-toplink] kütüphanesidir.
- [3]: Bu öğreticinin örnek dosyaları. [4]: İçe aktarılacak Eclipse projesi.
Toplink için [spring-config.xml] yapılandırma dosyası şu şekilde olur:
<?xml version="1.0" encoding="UTF-8"?>
<!-- JVM, -javaagent:C:\data\2006-2007\eclipse\dvp-jpa\lib\spring\spring-agent.jar
(à remplacer par le chemin exact de spring-agent.jar)-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<!-- uygulama katmanları -->
<bean id="dao" class="dao.Dao" />
<bean id="service" class="service.Service">
<property name="dao" ref="dao" />
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
<!--
<property name="showSql" value="true" />
-->
<property name="databasePlatform" value="oracle.toplink.essentials.platform.database.MySQL4Platform" />
<property name="generateDdl" value="true" />
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
<!-- veri kaynağı DBCP -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/jpa" />
<property name="username" value="jpa" />
<property name="password" value="jpa" />
</bean>
<!-- işlem yöneticisi -->
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<!-- istisna çevirisi -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<!-- kalıcılık -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
</beans>
Hibernate'ten Toplink'e geçmek için birkaç satırın değiştirilmesi gerekir:
- 19. satır: JPA uygulaması artık Toplink tarafından gerçekleştiriliyor
- 23. satır: [databasePlatform] özelliğinin değeri, Hibernate'de olduğundan farklıdır: Toplink'e özgü bir sınıf adıdır. Bu adın nerede bulunacağı 2.1.15.2. paragrafında açıklanmıştır.
Hepsi bu kadar. Spring ile SGBD veya JPA uygulamasını ne kadar kolay değiştirebileceğimize dikkat edin.
Yine de henüz tam olarak bitmiş sayılmayız. Örneğin, [InitDB]'i çalıştırdığımızda, anlaşılması kolay olmayan bir istisna ile karşılaşırız:
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [spring-config.xml]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation.
Caused by: java.lang.IllegalStateException: Must start with Java agent to use
- satırdaki hata mesajı, Spring belgelerini okumaya yönlendiriyor. Burada, [spring-config.xml] dosyasındaki belirsiz bir bildirimin oynadığı rol hakkında biraz daha bilgi ediniyoruz:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--
<property name="showSql" value="true" />
-->
<property name="databasePlatform" value="org.hibernate.dialect.OracleDialect" />
<property name="generateDdl" value="true" />
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
İstisnanın 1. satırı, [InstrumentationLoadTimeWeaver] adlı bir sınıfa atıfta bulunuyor; bu sınıf, Spring yapılandırma dosyasının 13. satırında yer alıyor. Spring belgelerinde, bu sınıfın bazı durumlarda uygulama sınıflarını yüklemek için gerekli olduğu ve bu sınıfın çalışabilmesi için JVM'in bir aracı ile başlatılması gerektiği açıklanmaktadır. Bu aracı Spring tarafından sağlanır ve adı [spring-agent]'tir:
![]() |
- [spring-agent.jar] dosyası, <exemples>/lib [1] klasöründe bulunur. Bu dosya, Spring dağıtımıyla birlikte sağlanır (bkz. paragraf 5.11).
- [3] dosyasında, [Run/Run...] adlı bir yürütme yapılandırması oluşturulur
- [4]'te bir Java yürütme yapılandırması oluşturulur (çeşitli türde yürütme yapılandırmaları vardır)
![]() |
- [5]'te, [Main] sekmesi seçilir
- [6]'te yapılandırmaya bir ad verin
- [7]'te, bu yapılandırmayla ilgili Eclipse projesine bir ad verin (Browse düğmesini kullanın)
- [8]'te, [main] yöntemini içeren Java sınıfına bir ad verin (Browse düğmesini kullanın)
- [9]'te, [Arguments] sekmesine geçin. Bu sekmede iki tür argüman belirtebilirsiniz:
- [9]'te, [main] yöntemine geçirilenler
- [10]'te, kodu çalıştıracak olan JVM'e geçirilenler. Spring ajanı, JVM'in -javaagent:değer parametresi kullanılarak tanımlanır. Değer, [spring-agent.jar] dosyasının yoludur.
- [11]'te: yapılandırma onaylanır
- [12]'te: yapılandırma oluşturulur
- [13]'te: yapılandırma çalıştırılır
Bu işlem tamamlandıktan sonra, [InitDB] çalıştırılır ve Hibernate ile aynı sonuçları verir. [TestNG] için de aynı şekilde ilerlenmelidir:
![]() |
- [1]'te, [Run/Run...] adlı bir yürütme yapılandırması oluşturulur
- [2]'te, TestNG adlı bir yürütme yapılandırması oluşturulur
- [3]'te, [Test] sekmesi seçilir
- [4]'te yapılandırmaya bir ad verilir
- [5]'te, bu yapılandırmayla ilgili Eclipse projesinin adını girin (Browse düğmesini kullanın)
- [6]'te, test sınıfına bir ad verin (Browse düğmesini kullanın)
![]() |
- [7]'te, [Arguments] sekmesine geçin.
- [8]'te: JVM'in -javaagent argümanını ayarlayın.
- [9]'te: yapılandırmayı onaylayın
- [10]'te: yapılandırma oluşturulur
- [11]'te: çalıştırılır
Bu işlem tamamlandıktan sonra, [TestNG] çalıştırılır ve Hibernate ile aynı sonuçları verir.
3.2. Örnek 2: JBoss EJB3 / JPA, Personne varlığı ile
Önceki örnekle aynı örneği kullanıyoruz, ancak bunu EJB3 konteynerinde, yani JBoss'in konteynerinde çalıştırıyoruz:
![]() |
Bir Ejb3 konteyneri normalde bir uygulama sunucusuna entegre edilir. JBoss, uygulama sunucusu dışında kullanılabilen "bağımsız" bir Ejb3 konteyneri sunar. Bu konteynerin, Spring tarafından sunulanlara benzer hizmetler sağladığını göreceğiz. Bu konteynerlerden hangisinin daha kullanışlı olduğunu incelemeye çalışacağız.
JBoss Ejb3 konteynerinin kurulumu 5.12. paragrafta açıklanmaktadır.
3.2.1. Eclipse / JBoss EJB3 / Hibernate Projesi
Eclipse projesi şöyledir:
![]() |
![]() |
- [1]: Eclipse projesi. Bu proje, [6] dosyasında, [5] numaralı öğreticinin örnekleri arasında bulunabilir. Bu projeyi içe aktaracağız.
- [2] dosyasında: paketler halinde sunulan katmanların Java kodları:
- [entites]: JPA varlık paketi
- [dao]: Veri erişim katmanı - JPA katmanına dayanır
- [service]: iş mantığından çok hizmet odaklı bir katman. Burada Ejb3 konteynerinin işlem hizmeti kullanılacaktır.
- [tests]: Test programlarını bir araya getirir.
- [3]: [jpa-jbossejb3] kütüphanesi, JBoss EJB3 için gerekli jar dosyalarını bir araya getirir (ayrıca bkz. [7] ve [8]).
- [4]: [conf] klasörü, bu öğreticide kullanılan her bir SGBD için yapılandırma dosyalarını bir araya getirir. Her seferinde iki dosya bulunur: [persistence.xml], JPA katmanını yapılandırır; [jboss-config.xml] ise Ejb3 konteynerini yapılandırır.
3.2.2. JPA varlıkları
![]() |
Burada yönetilen tek bir varlık vardır: daha önce 3.1.2. paragrafında incelenen Personne varlığı.
3.2.3. [dao] katmanı
![]() |
[dao] katmanı, daha önce 3.1.3. paragrafında açıklanan [IDao] arayüzünü sunar.
Bu arayüzün [Dao] uygulaması şu şekildedir:
package dao;
...
@Stateless
public class Dao implements IDao {
@PersistenceContext
private EntityManager em;
// kimlik numarasıyla bir kişiyi silme
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deleteOne(Integer id) {
Personne personne = em.find(Personne.class, id);
if (personne == null) {
throw new DaoException(2);
}
em.remove(personne);
}
// tüm kişileri alma
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public List<Personne> getAll() {
return em.createQuery("select p from Personne p").getResultList();
}
// adı belirli bir şablona uyan kişileri alma
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public List<Personne> getAllLike(String modele) {
return em.createQuery("select p from Personne p where p.nom like :modele")
.setParameter("modele", modele).getResultList();
}
// kimlik numarasıyla bir kişiyi alma
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Personne getOne(Integer id) {
return em.find(Personne.class, id);
}
// bir kişiyi kaydetmek
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Personne saveOne(Personne personne) {
em.persist(personne);
return personne;
}
// bir kişiyi güncelle
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Personne updateOne(Personne personne) {
return em.merge(personne);
}
}
- Bu kod, Spring ile kullandığımız kodla her açıdan aynıdır. Yalnızca Java anotasyonları değişmektedir ve biz de bu konuyu ele alıyoruz.
- 4. satır: @Stateless anotasyonu, [Dao] sınıfını durumuz bir EJB yapar. @Stateful anotasyonu ise bir sınıfı durumlu bir EJB yapar. Durumlu bir EJB, zaman içinde değerlerinin korunması gereken özel alanlara sahiptir. Klasik bir örnek, bir uygulamanın web kullanıcısıyla ilgili bilgileri içeren bir sınıftır. Bu sınıfın bir örneği belirli bir kullanıcıyla ilişkilidir ve bu kullanıcının bir isteğinin yürütme iş parçacığı sona erdiğinde, aynı müşterinin bir sonraki isteğinde kullanılabilir olması için bu örnek korunmalıdır. Bir @Stateless EJB'nin durumu yoktur. Aynı örneği ele alırsak, bir kullanıcının isteğinin yürütme iş parçacığı sona erdiğinde, @Stateless EJB bir @Stateless EJB havuzuna eklenir ve başka bir kullanıcının isteğinin yürütme iş parçacığı için kullanılabilir hale gelir.
- Geliştirici açısından, EJB3 @Stateless kavramı Spring’deki singleton kavramına yakındır. Geliştirici, bunu aynı durumlarda kullanacaktır.
- 7. satır: @PersistenceContext anotasyonu, [dao] katmanının Spring sürümünde karşılaşılan anotasyonla aynıdır. Bu, EntityManager'i alacak olan alanı belirtir; bu da [dao] katmanının kalıcılık bağlamını yönetmesini sağlar.
- 11. satır: Bir yönteme uygulanan @TransactionAttribute anotasyonu, yöntemin çalışacağı işlemi yapılandırmaya yarar. Bu anotasyonun olası değerlerinden bazıları şunlardır:
- TransactionAttributeType.REQUIRED: Yöntem bir işlem içinde yürütülmelidir. Zaten başlatılmış bir işlem varsa, yöntemin kalıcılık işlemleri bu işlem içinde gerçekleştirilir. Aksi takdirde, bir işlem oluşturulur ve başlatılır.
- TransactionAttributeType.REQUIRES_NEW: Yöntem, yeni bir işlem içinde yürütülmelidir. Bu işlem oluşturulur ve başlatılır.
- TransactionAttributeType.MANDATORY: Yöntem, mevcut bir işlem içinde çalıştırılmalıdır. Eğer böyle bir işlem yoksa, bir istisna atılır.
- TransactionAttributeType.NEVER: Yöntem hiçbir zaman bir işlem içinde çalıştırılmaz.
- ...
Bu açıklama sınıfın kendisine de eklenebilirdi:
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Dao implements IDao {
Bu durumda öznitelik, sınıfın tüm yöntemlerine uygulanır.
3.2.4. [metier / service] katmanı
![]() |
[service] katmanı, daha önce 3.1.4 paragrafında incelenen [IService] arayüzünü sunar. [IService] arayüzünün [Service] uygulaması, 3.1.4. paragrafında daha önce incelenen uygulamayla üç ayrıntı dışında aynıdır:
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Service implements IService {
// katman [dao]
@EJB
private IDao dao;
public IDao getDao() {
return dao;
}
public void setDao(IDao dao) {
this.dao = dao;
}
- 2. satır: [Service] sınıfı durum bilgisi içermeyen bir EJB'dir
- 3. satır: [Service] sınıfındaki tüm yöntemler bir işlem içinde gerçekleştirilmelidir
- 7-8. satırlar: [dao] katmanındaki EJB'ye ait bir referans, EJB konteyneri tarafından 8. satırdaki [IDao dao] alanına enjekte edilecektir. Bu enjeksiyonu talep eden, 7. satırdaki @EJB anotasyonudur. Enjekte edilen nesne bir EJB olmalıdır. Bu, herhangi bir nesne türünün başka bir nesneye enjekte edilebildiği Spring ile önemli bir fark oluşturur.
3.2.5. Katmanların yapılandırılması
![]() |
[service], [dao] ve [JPA] katmanlarının yapılandırması aşağıdaki dosyalarla sağlanır:
- [META-INF/persistence.xml] dosyası, JPA katmanını yapılandırır
- [jboss-config.xml], Ejb3 konteynerini yapılandırır. Bu dosya, [default.persistence.properties, ejb3-interceptors-aop.xml, embedded-jboss-beans.xml, jndi.properties] dosyalarını kullanır. Bu dosyalar Jboss Ejb3 ile birlikte gelir ve normalde değiştirilmeyen bir varsayılan yapılandırma sağlar. Geliştirici yalnızca [jboss-config.xml] dosyasıyla ilgilenir
İki yapılandırma dosyasını inceleyelim:
persistence.xml
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="jpa">
<!-- JPA sağlayıcısı Hibernate'dir -->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- DataSource JTA, Java ortamı tarafından yönetilir EE5 -->
<jta-data-source>java:/datasource</jta-data-source>
<properties>
<!-- JBA katmanındaki varlıkların aranması -->
<property name="hibernate.archive.autodetection" value="class, hbm" />
<!-- SQL Hibernate günlükleri
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
-->
<!-- SGBD türü yönetiliyor -->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect" />
<!-- kalıcılık biriminin dağıtımı sırasında tüm tabloların yeniden oluşturulması (drop+create) -->
<property name="hibernate.hbm2ddl.auto" value="create" />
</properties>
</persistence-unit>
</persistence>
Bu dosya, JPA varlıklarını incelerken daha önce karşılaştıklarımıza benziyor. Bir JPA Hibernate katmanını yapılandırıyor. Yeni özellikler şunlardır:
- 5. satır: jpa kalıcılık birimi, şimdiye kadar her zaman mevcut olan transaction-type özniteliğine sahip değildir:
<persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL" />
Değer belirtilmediğinde, transaction-type özniteliğinin varsayılan değeri "JTA" (Java Transaction API için) olur; bu, işlem yöneticisinin bir EJB3 konteyneri tarafından sağlandığını gösterir. "JTA" işlem yöneticisi, "RESOURCE_LOCAL" işlem yöneticisinden daha fazla işlev görebilir: birden fazla bağlantıyı kapsayan işlemleri yönetebilir. JTA ile, SGBD 1 üzerindeki c1 bağlantısı üzerinden t1 işlem açılabilir, SGBD 1 üzerinde bir c1 bağlantısı üzerinden bir t2 işlemi açabilir ve (t1,t2) 'yi, ya tüm işlemlerin başarılı olduğu (commit) ya da hiçbirinin başarılı olmadığı (rollback) tek bir işlem olarak değerlendirebiliriz.
Burada, Jboss Ejb3 konteynerinin JTA yöneticisiyle çalışıyoruz.
- 11. satır: JTA yöneticisinin kullanması gereken veri kaynağını bildirir. Bu, JNDI (Java Naming and Directory Interface) adı biçiminde verilir. Bu veri kaynağı, [jboss-config.xml] içinde tanımlanmıştır.
jboss-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:bean-deployer bean-deployer_1_0.xsd"
xmlns="urn:jboss:bean-deployer:2.0">
<!-- DataSource fabrikası -->
<bean name="datasourceFactory" class="org.jboss.resource.adapter.jdbc.local.LocalTxDataSource">
<!-- DataSource'in adı JNDI -->
<property name="jndiName">java:/datasource</property>
<!-- yönetilen veritabanı -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="connectionURL">jdbc:mysql://localhost:3306/jpa</property>
<property name="userName">jpa</property>
<property name="password">jpa</property>
<!-- bağlantı havuzu özellikleri -->
<property name="minSize">0</property>
<property name="maxSize">10</property>
<property name="blockingTimeout">1000</property>
<property name="idleTimeout">100000</property>
<!-- işlem yöneticisi, burada JTA -->
<property name="transactionManager">
<inject bean="TransactionManager" />
</property>
<!-- Hibernate önbellek yöneticisi -->
<property name="cachedConnectionManager">
<inject bean="CachedConnectionManager" />
</property>
<!-- JNDI örneklendirme özellikleri? -->
<property name="initialContextProperties">
<inject bean="InitialContextProperties" />
</property>
</bean>
<!-- DataSource bir fabrikadan talep ediliyor -->
<bean name="datasource" class="java.lang.Object">
<constructor factoryMethod="getDatasource">
<factory bean="datasourceFactory" />
</constructor>
</bean>
</deployment>
- 3. satır: Dosyanın kök etiketi <deployment>'dir. Bu dağıtım dosyası, esas olarak persistence.xml dosyasında tanımlanan java:/datasource veri kaynağını yapılandırmayı amaçlamaktadır.
- Veri kaynağı, 38. satırdaki "datasource" bean'i tarafından tanımlanmaktadır. Veri kaynağının (40. satır), 7. satırdaki "datasourceFactory" bean'i tarafından tanımlanan bir "factory"den alındığı görülmektedir. Uygulamanın veri kaynağını elde etmek için istemci, factory'in [getDatasource] yöntemini çağırmalıdır (39. satır).
- 7. satır: Veri kaynağını sağlayan factory, bir Jboss sınıfıdır.
- 9. satır: Veri kaynağının adı JNDI'tir. Bu ad, persistence.xml dosyasındaki <jta-data-source> etiketinde bildirilen adla aynı olmalıdır. Nitekim, JPA katmanı, veri kaynağını talep etmek için bu JNDI adını kullanacaktır.
- 12-15. satırlar: Daha klasik bir örnek: SGBD bağlantısının JDBC özellikleri
- 18-21. satırlar: JBoss EJB3 konteynerinin dahili bağlantı havuzunun yapılandırması.
- 24-26. satırlar: JTA yöneticisi. 25. satırda enjekte edilen [TransactionManager] sınıfı, [embedded-jboss-beans.xml] dosyasında tanımlanmıştır.
- 28-30. satırlar: Hibernate önbelleği; bu kavramı daha önce ele almamıştık. 29. satırda enjekte edilen [CachedConnectionManager] sınıfı, [embedded-jboss-beans.xml] dosyasında tanımlanmıştır. Yapılandırmanın artık Hibernate’e bağlı olduğu unutulmamalıdır; bu durum, Toplink’e geçiş yapmak istediğimizde sorun yaratacaktır.
- 32-34. satırlar: JNDI hizmetinin yapılandırması.
Jboss Ejb3 yapılandırma dosyası ile işimiz bitti. Dosya karmaşıktır ve pek çok husus belirsizliğini korumaktadır. Bu dosya, [ref1] dosyasından alınmıştır. Ancak bunu başka bir SGBD'e uyarlayabileceğiz (jboss-config.xml'in 12-15. satırları, persistence.xml'in 24. satırı). Örneklerin eksikliği nedeniyle Toplink'e geçiş mümkün olmadı.
3.2.6. [InitDB] istemci programı
Daha önce açıklanan mimariye ait ilk istemciyi yazmaya başlıyoruz:
![]() |
[InitDB] kodunun içeriği şöyledir:
package tests;
...
public class InitDB {
// hizmet katmanı
private static IService service;
// oluşturucu
public static void main(String[] args) throws ParseException, NamingException {
// EJB3 JBoss konteyneri başlatılıyor
// ejb3-interceptors-aop.xml ve embedded-jboss-beans.xml yapılandırma dosyaları kullanılıyor
EJB3StandaloneBootstrap.boot(null);
// Uygulamaya özgü bean'lerin oluşturulması
EJB3StandaloneBootstrap.deployXmlResource("META-INF/jboss-config.xml");
// Sınıf yolunda bulunan tüm EJBs'leri dağıt (yavaş, tümünü tarar)
// EJB3StandaloneBootstrap.scanClasspath();
// Uygulamanın sınıf yolunda bulunan tüm EJB dosyaları dağıtılıyor
EJB3StandaloneBootstrap.scanClasspath("bin".replace("/", File.separator));
// JNDI bağlamı başlatılıyor. jndi.properties dosyası kullanılıyor
InitialContext initialContext = new InitialContext();
// hizmet katmanının örneği oluşturuluyor
service = (IService) initialContext.lookup("Service/local");
// veritabanı boşaltılıyor
clean();
// veritabanı dolduruluyor
fill();
// görsel olarak kontrol ediliyor
dumpPersonnes();
// EJB konteyneri durduruluyor
EJB3StandaloneBootstrap.shutdown();
}
// tablo içeriğinin görüntülenmesi
private static void dumpPersonnes() {
System.out.format("[personnes]-------------------------------------------------------------------%n");
for (Personne p : service.getAll()) {
System.out.println(p);
}
}
// tablo dolduruluyor
public static void fill() throws ParseException {
// kullanıcılar oluşturulur
Personne p1 = new Personne("p1", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
Personne p2 = new Personne("p2", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
// kaydediliyor
service.saveArray(new Personne[] { p1, p2 });
}
// tablodaki öğeleri silme
public static void clean() {
for (Personne p : service.getAll()) {
service.deleteOne(p.getId());
}
}
}
- Jboss Ejb3 konteynerinin nasıl başlatılacağı, [ref1]'te bulunmuştur.
- 13. satır: konteyner başlatılır. [EJB3StandaloneBootstrap], konteynerin bir sınıfıdır.
- 16. satır: [jboss-config.xml] tarafından yapılandırılan dağıtım birimi konteynere dağıtılır: JTA yöneticisi, veri kaynağı, bağlantı havuzu, Hibernate önbelleği ve JNDI hizmeti kurulur.
- 22. satır: Konteynerden, Eclipse projesinin bin klasörünü tarayarak EJB'leri bulması istenir. [service] ve [dao] katmanlarına ait EJB'ler bulunacak ve konteyner tarafından yönetilecektir.
- 25. satır: Bir JNDI bağlamı başlatılır. Bu bağlam, EJB'leri bulmak için kullanılacaktır.
- 28. satır: [service] katmanındaki [Service] sınıfına karşılık gelen EJB, JNDI hizmetinden istenir. Bir EJB'ye yerel olarak (local) veya ağ üzerinden (remote) erişilebilir. Burada, aranan EJB'nin "Service/local" adı, yerel erişim için [service] katmanındaki [Service] sınıfını belirtir.
- Şimdi uygulama dağıtıldı ve [service] katmanına bir referansımız var. Spring sürümündeki [InitDB] kodunun aşağıdaki 11. satırından sonraki durumla aynı durumdayız. Dolayısıyla her iki sürümde de aynı kodla karşılaşıyoruz.
public class InitDB {
// hizmet katmanı
private static IService service;
// oluşturucu
public static void main(String[] args) throws ParseException {
// uygulama yapılandırması
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
// hizmet katmanı
service = (IService) ctx.getBean("service");
// veritabanını boşaltma
clean();
// veritabanını doldurma
fill();
// gözle kontrol ediliyor
dumpPersonnes();
}
...
- 36. satır (Jboss Ejb3): Ejb3 konteynerini durduruyoruz.
[InitDB] kodunun çalıştırılması şu sonuçları verir:
Okuyucunun bu günlükleri incelemesi önerilir. Burada, Ejb3 konteynerinin ne yaptığına dair ilginç bilgiler bulunur.
3.2.7. [TestNG] birim testleri
[TestNG] programının kodu şöyledir:
package tests;
...
public class TestNG {
// hizmet katmanı
private IService service = null;
@BeforeClass
public void init() throws NamingException, ParseException {
// günlük
log("init");
// konteyneri başlatıyoruz EJB3 JBoss
// ejb3-interceptors-aop.xml ve embedded-jboss-beans.xml yapılandırma dosyaları kullanılıyor
EJB3StandaloneBootstrap.boot(null);
// Uygulamaya özgü bean'lerin oluşturulması
EJB3StandaloneBootstrap.deployXmlResource("META-INF/jboss-config.xml");
// Sınıf yolunda bulunan tüm EJBs'leri dağıt (yavaş, tümünü tarar)
// EJB3StandaloneBootstrap.scanClasspath();
// Uygulamanın sınıf yolunda bulunan tüm EJB dosyaları dağıtılıyor
EJB3StandaloneBootstrap.scanClasspath("bin".replace("/", File.separator));
// JNDI bağlamı başlatılır. jndi.properties dosyası kullanılır
InitialContext initialContext = new InitialContext();
// hizmet katmanının örneği oluşturuluyor
service = (IService) initialContext.lookup("Service/local");
// veritabanı boşaltılıyor
clean();
// veritabanı dolduruluyor
fill();
// gözle kontrol ediliyor
dumpPersonnes();
}
@AfterClass
public void terminate() {
// günlük
log("terminate");
// EJB konteynerini kapatma
EJB3StandaloneBootstrap.shutdown();
}
@BeforeMethod
public void setUp() throws ParseException {
...
}
...
}
- Testler için gerekli ortamı hazırlamak amacıyla kullanılan init yöntemi (satır 10-37), daha önce [InitDB]'te açıklanan kodu kullanır.
- Testlerin sonunda yürütülen ( @AfterClass anotasyonunun varlığıyla belirlenen) terminate yöntemi (satır 40-45), Ejb3 konteynerini durdurur (satır 44).
- Geri kalan her şey Spring sürümündekiyle aynıdır.
Testler başarılı:

3.2.8. SGBD'e değiştir
![]() |
SGBD'i değiştirmek için, [META-INF] [2] klasörünün içeriğini, [conf] [1] klasöründeki SGBD klasörünün içeriğiyle değiştirmeniz yeterlidir. SQL Sunucusu örneğini ele alalım:
[persistence.xml] dosyası şu şekildedir:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="jpa">
<!-- JPA sağlayıcısı Hibernate'dir -->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- DataSource JTA, Java ortamı tarafından yönetilir EE5 -->
<jta-data-source>java:/datasource</jta-data-source>
<properties>
<!-- JBA katmanındaki varlıkların aranması -->
<property name="hibernate.archive.autodetection" value="class, hbm" />
<!-- SQL Hibernate günlükleri
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
-->
<!-- yönetilen SGBD türü -->
<property name="hibernate.dialect" value="org.hibernate.dialect.SQLServerDialect" />
<!-- kalıcılık biriminin dağıtımı sırasında tüm tabloların yeniden oluşturulması (drop+create) -->
<property name="hibernate.hbm2ddl.auto" value="create" />
</properties>
</persistence-unit>
</persistence>
Sadece bir satır değişmiştir:
- 24. satır: Hibernate'in kullanması gereken SQL lehçesi
SQL Sunucusundaki [jboss-config.xml] dosyası ise şu şekildedir:
<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:bean-deployer bean-deployer_1_0.xsd"
xmlns="urn:jboss:bean-deployer:2.0">
<!-- DataSource fabrikası -->
<bean name="datasourceFactory" class="org.jboss.resource.adapter.jdbc.local.LocalTxDataSource">
<!-- DataSource'in adı JNDI -->
<property name="jndiName">java:/datasource</property>
<!-- yönetilen veritabanı -->
<property name="driverClass">com.microsoft.sqlserver.jdbc.SQLServerDriver</property>
<property name="connectionURL">jdbc:sqlserver://localhost\\SQLEXPRESS:1246;databaseName=jpa</property>
<property name="userName">jpa</property>
<property name="password">jpa</property>
<!-- bağlantı havuzu özellikleri -->
...
</bean>
</deployment>
Sadece 12-15. satırlar değiştirilmiştir: bunlar yeni Jdbc bağlantısının özelliklerini belirtir.
Okuyucunun, MySQL5 için açıklanan testleri diğer SGBD dosyalarıyla da tekrarlaması önerilir.
3.2.9. JPA Uygulamasını Değiştirme
Yukarıda belirtildiği gibi, Jboss Ejb3 konteynerinin Toplink ile birlikte kullanıldığına dair bir örnek bulamadık. Bugüne kadar (Haziran 2007), bu yapılandırmanın mümkün olup olmadığını hâlâ bilmiyorum.
3.3. Diğer örnekler
Personne varlığıyla yapılan çalışmaları özetleyelim. Aynı testleri gerçekleştirmek için üç farklı mimari oluşturduk:
1 - Spring / Hibernate uygulaması
![]() |
2 - Spring / Toplink uygulaması
![]() |
3 - bir Jboss Ejb3 / Hibernate uygulaması
![]() |
Öğreticideki örnekler, bu üç mimariyi, öğreticinin ilk bölümünde incelenen diğer varlıklarla birlikte ele almaktadır:
Kategori - Makale
![]() |
- [1]'te: Spring / Hibernate sürümü
- [2]: Spring / Toplink sürümü
- [3]: Jboss Ejb3 / Hibernate sürümü
Kişi - Adres - Faaliyet
![]() |
- [1]: Spring / Hibernate sürümü
- [2]: Spring / Toplink sürümü
- [3]: JBoss EJB3 / Hibernate sürümü
Bu örnekler mimari açısından yeni bir şey getirmiyor. Bunlar, aralarında bir-çok veya çok-çok ilişkileri olan yönetilmesi gereken birden fazla varlık bulunan bir bağlamda yer alıyor; Personne varlığı içeren örneklerde bu durum söz konusu değildi.
3.4. Örnek 3: Bir web uygulamasında Spring / JPA
3.4.1. Giriş
Burada, aşağıdaki belgede sunulan bir uygulamayı ele alıyoruz:
[ref4]: Java'da MVC Web Geliştirmenin Temelleri [http://tahe.developpez.com/java/baseswebmvc/].
Bu belge, Java'da MVC web geliştirmenin temellerini ele almaktadır. Aşağıdaki örneği anlayabilmek için okuyucunun bu temel bilgilere sahip olması gerekmektedir. Web uygulaması Tomcat sunucusunu kullanacaktır. Tomcat'in kurulumu ve Eclipse ortamında kullanımı 5.3. paragrafında anlatılmaktadır.
Uygulama, ilişkisel-nesne köprüsünü sağlayan Ibatis / SqlMap [http://ibatis.apache.org/] aracına dayanan bir [dao] katmanı ile geliştirilmişti. Biz sadece Ibatis'i JPA ile değiştiriyoruz. Uygulamanın mimarisi şu şekilde olacaktır:
![]() |
Yazacağımız web uygulaması, dört işlemle bir grup kişiyi yönetmeye olanak sağlayacak:
- grup üyelerinin listelenmesi
- gruba kişi ekleme
- gruptaki bir kişinin bilgilerinin değiştirilmesi
- gruptan bir kişinin silinmesi
Bu dört temel işlem, bir veritabanı tablosundaki işlemlerle aynıdır. Aşağıdaki ekran görüntüler , uygulamanın kullanıcıyla karşılıklı olarak görüntülediği sayfaları göstermektedir.
![]() |
![]() |
![]() |
![]() |
![]() |
3.4.2. Eclipse Projesi
Uygulamanın Eclipse projesi şu şekildedir:
![]() |
- [1]: web projesi. Bu, [Dynamic Web Project] [2] türünde bir Eclipse projesidir. Bu proje, öğreticinin örnekleri klasöründeki [3] klasöründe [4] adıyla bulunur. Bu projeyi içe aktaracağız.
![]() |
- [5] dosyasında: [service, dao, jpa] katmanlarının kaynakları ve yapılandırması. 3.1.1. paragrafında incelenen Eclipse projesi [hibernate-spring-personnes-metier-dao]'ten elde edilen [dao, entites, service] bilgilerini koruyoruz. Yalnızca burada [web] paketi ile temsil edilen [web] katmanını geliştiriyoruz. Ayrıca, bu projenin [persistence.xml, spring-config.xml] yapılandırma dosyalarını, SGBD Postgres'i kullanacağımız ayrıntısı dışında aynen koruyoruz; bu da [spring-config.xml] dosyasında aşağıdaki değişiklikleri gerektiriyor:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
...
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
...
<property name="databasePlatform" value="org.hibernate.dialect.PostgreSQLDialect" />
...
</property>
...
</bean>
<!-- veri kaynağı DBCP -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.postgresql.Driver" />
<property name="url" value="jdbc:postgresql:jpa" />
<property name="username" value="jpa" />
<property name="password" value="jpa" />
</bean>
....
</beans>
- satır ile 16-19. satırlar Postgres'e uyarlanmıştır.
- [6]'te: [WebContent] klasörü, projenin JSP sayfalarını ve gerekli kütüphaneleri içerir. Bu kütüphaneler, [8]'te gösterilmiştir
- Uygulama, çeşitli SGBD sürümleriyle kullanılabilir. Bunun için [spring-config.xml] dosyasını değiştirmeniz yeterlidir. [conf] [7] klasörü, çeşitli SGBD'lere uyarlanmış [spring-config.xml] dosyasını içerir.
3.4.3. [web] katmanı
Uygulamamız aşağıdaki çok katmanlı mimariye sahiptir:
![]() |
[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, kendisi de [dao] katmanını kullanacak olan [service] katmanından yararlanacaktır. [web] katmanı tarafından yönetilen ekranları daha önce tanıtmıştık (3.4.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
3.4.3.1. Web uygulamasının yapılandırması
Eclipse projesinin mimarisine tekrar bir göz atalım:
![]() | ![]() |
- [web] paketinde, web uygulamasının denetleyicisi olan [Application] sınıfı bulunur.
- Uygulamanın JSP / JSTL sayfaları, [WEB-INF/vues] içinde yer almaktadır.
- [WEB-INF/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>spring-jpa-hibernate-personnes-crud</display-name>
<!-- ServletPersonne -->
<servlet>
<servlet-name>personnes</servlet-name>
<servlet-class>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>
- 23-26. satırlar: [/do/*] URL'leri, [personnes] servleti tarafından işlenecektir
- 7-8. satırlar: [personnes] servleti, [Application] sınıfının bir örneğidir; bu sınıfı daha sonra oluşturacağız.
- 9-20. 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.
- 28-30. satırlar: Uygulamanın, web uygulaması klasörünün kök dizininde bulunan [index.jsp] adlı bir varsayılan giriş sayfası vardır.
- 32-35. 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.
- 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, meydana gelen 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 [/spring-jpa-hibernate-personnes-crud]. İç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 (4. satır) [/do/list] URL'sine yönlendirir. Bu URL, gruptaki kişilerin listesini gösterir.
3.4.3.2. Uygulamanın JSP / JSTL sayfaları
[list.jsp] görünümü
Bu görünüm, 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"/>">
<c:if test="${erreurs!=null}">
<h3>Les erreurs suivantes se sont produites :</h3>
<ul>
<c:forEach items="${erreurs}" var="erreur">
<li><c:out value="${erreur}"/></li>
</c:forEach>
</ul>
<hr>
</c:if>
<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 iki öğe alır:
- [personnes] öğesi, [List] türündeki bir nesneye ve bu nesne de [Personne] türündeki nesnelere bağlıdır: bir kişi listesi.
- isteğe bağlı [erreurs] öğesi, [List] türündeki bir nesneye ve bu nesne de [String] türündeki nesnelere bağlıdır: bir hata mesajları listesi.
- 31-43. satırlar: ${personnes} listesi taranarak, gruptaki kişileri içeren bir HTML tablosu görüntülenir.
- 40. 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.
- 41. satır: Aynı işlem [Supprimer] bağlantısı için de yapılır.
- 37. 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.
- 46. satır: Yeni bir kişi ekleme bağlantısı olan [Ajout], 40. 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.
- 10-18. satırlar: ${hatalar} öğesi şablonda varsa, içindeki hata mesajları görüntülenir.
[edit.jsp] görünümü
Bu görünüm, yeni bir kişi ekleme veya mevcut bir kişiyi düzenleme formunu görüntülemek için 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 işlemini [/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 güncellenen | 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ş | ekleme veya değiştirme işleminin başarısız olduğunu belirten bir hata mesajı boş. Hata yoksa boş. | |
boş | yanlış bir isim olduğunu 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ş | hatalı ç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 ve varsa 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; bu alanın adı [id]'tir ve 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: HTML adlı gizli bir alan; adı [version] olup, 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ığı olan 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.
3.4.3.3. Uygulama denetleyicisi
[Application] denetleyicisi, [web] paketinde tanımlanmıştır:

yapısı ve denetleyicinin başlatılması
[Application] denetleyicisinin iskeleti şu şekildedir:
package web;
...
@SuppressWarnings("serial")
public class Application extends HttpServlet {
// örnek ayarları
private String urlErreurs = null;
private ArrayList erreursInitialisation = new ArrayList<String>();
private String[] paramètres = { "urlList", "urlEdit", "urlErreurs" };
private Map params = new HashMap<String, String>();
// hizmet
private IService service = null;
// başlatma
@SuppressWarnings("unchecked")
public void init() throws ServletException {
// servletin başlatma parametreleri alınır
ServletConfig config = getServletConfig();
// diğer başlatma parametreleri işleniyor
String valeur = null;
for (int i = 0; i < paramètres.length; i++) {
// parametre değeri
valeur = config.getInitParameter(paramètres[i]);
// parametre mevcut mu?
if (valeur == null) {
// hata kaydedilir
erreursInitialisation.add("Le paramètre [" + paramètres[i] + "] n'a pas été initialisé");
} else {
// parametrenin değeri kaydedilir
params.put(paramètres[i], valeur);
}
}
// [erreurs] görünümünün URL'si özel bir işleme tabi tutulur
urlErreurs = config.getInitParameter("urlErreurs");
if (urlErreurs == null)
throw new ServletException("Le paramètre [urlErreurs] n'a pas été initialisé");
// uygulama yapılandırması
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
// hizmet katmanı
service = (IService) ctx.getBean("service");
// veritabanı boşaltılır
clean();
// veritabanını dolduruyoruz
try {
fill();
} catch (ParseException e) {
throw new ServletException(e);
}
}
// tablo doldurma
public void fill() throws ParseException {
// kullanıcı oluşturma
Personne p1 = new Personne("p1", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
Personne p2 = new Personne("p2", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
// kaydediliyor
service.saveArray(new Personne[] { p1, p2 });
}
// tablodaki öğeleri silme
public void clean() {
for (Personne p : service.getAll()) {
service.deleteOne(p.getId());
}
}
// GET
@SuppressWarnings("unchecked")
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
...
}
// kişi listesini görüntüleme
private void doListPersonnes(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
}
// kişi düzenleme / ekleme
private void doEditPersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
}
// bir kişinin silinmesi
private void doDeletePersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
}
// kişi düzenleme / ekleme işleminin onaylanması
public void doValidatePersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
}
// önceden doldurulmuş formun görüntülenmesi
private void showFormulaire(HttpServletRequest request, HttpServletResponse response, String erreurEdit) throws ServletException, IOException {
...
}
// gönderme
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// işlem GET'e devrediliyor
doGet(request, response);
}
}
- 21-34. satırlar: [web.xml] dosyasında beklenen parametreler alınır.
- 37-39. satırlar: [urlErreurs] parametresi mutlaka bulunmalıdır, çünkü bu parametre, olası başlatma hatalarını görüntüleyebilen [erreurs] görünümünün URL’sini belirtir. Bu parametre mevcut değilse, [ServletException] komutu çalıştırılarak uygulama durdurulur (satır 39). 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.
- 40-43. satırlar: Spring yapılandırma dosyasını kullanarak [service] katmanına ilişkin bir referans alır. Denetleyicinin başlatılmasından sonra, denetleyicinin yöntemleri, [service] katmanında (satır 15) bir [service] referansına sahip olur ve bu referansı, kullanıcı tarafından talep edilen eylemleri yürütmek için kullanırlar. 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 işlemlerin doğru yönteme yönlendirilmesini sağlamaktır. Kod şöyledir:
// GET
@SuppressWarnings("unchecked")
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// servletin başlatılmasının nasıl gerçekleştiği kontrol ediliyor
if (erreursInitialisation.size() != 0) {
// hata sayfasına yönlendiriliyor
request.setAttribute("erreurs", erreursInitialisation);
getServletContext().getRequestDispatcher(urlErreurs).forward(request, response);
// son
return;
}
// isteği gönderen yöntemi alınır
String méthode = request.getMethod().toLowerCase();
// yürütülecek eylemi alıyoruz
String action = request.getPathInfo();
// eylem?
if (action == null) {
action = "/list";
}
// eylemin yürütülmesi
if (méthode.equals("get") && action.equals("/list")) {
// kişi listesi
doListPersonnes(request, response);
return;
}
if (méthode.equals("get") && action.equals("/delete")) {
// bir kişinin silinmesi
doDeletePersonne(request, response);
return;
}
if (méthode.equals("get") && action.equals("/edit")) {
// kişi ekleme/değiştirme formunun görüntülenmesi
doEditPersonne(request, response);
return;
}
if (méthode.equals("post") && action.equals("/validate")) {
// kişi ekleme/değiştirme formunun onaylanması
doValidatePersonne(request, response);
return;
}
// diğer durumlar
doListPersonnes(request, response);
}
- 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:
// kişi listesinin görüntülenmesi
private void doListPersonnes(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// [list] görünüm şablonu
request.setAttribute("personnes", service.getAll());
// [list] görünümünün görüntülenmesi
getServletContext().getRequestDispatcher((String) params.get("urlList")).forward(request, response);
}
- 4. satır: [service] katmanından gruptaki kişilerin listesi istenir ve bu liste, şablonda "kişiler" anahtarı altında yerleştirilir.
- 6. satır: 3.4.3.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ına aittir:

ve kodu şöyledir:
...
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body background="<c:url value="/ressources/standard.jpg"/>">
...
<c:forEach var="personne" items="${personnes}">
<tr>
...
<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>
- 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:
// bir kişinin silinmesi
private void doDeletePersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// kişinin kimliğini alıyoruz
int id = Integer.parseInt(request.getParameter("id"));
// kişiyi siliyoruz
service.deleteOne(id);
// kişi listesine yönlendirme
response.sendRedirect("list");
}
- 4. satır: İşlenen URL, [/do/delete?id=XX] biçimindedir. [id] parametresinden [XX] değeri alınır.
- 6. 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, web sunucusuna kadar iletilir ve yapılandırma gereği, 3.4.3.2 numaralı paragrafta açıklanan [exception.jsp] sayfası görüntülenir:

- 9. satır: Silme işlemi gerçekleştiyse (istisna yoksa), istemciden [list] göreceli URL'sine yönlendirilmesi istenir. Az önce işlenen sayfa [/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] sayfasına 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:

ve kodu şöyledir:
...
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body background="<c:url value="/ressources/standard.jpg"/>">
...
<c:forEach var="personne" items="${personnes}">
<tr>
...
<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>
- 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.
![]() |
- Yukarıdaki [1], ekleme formunu; [2] ise düzenleme formunu gösterir.
[doEditPersonne] yönteminin kodu şöyledir:
// bir kişinin bilgilerinin değiştirilmesi / eklenmesi
private void doEditPersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// kişinin kimliğini alır
int id = Integer.parseInt(request.getParameter("id"));
// Ekleme mi, düzenleme mi?
Personne personne = null;
if (id != -1) {
// değişiklik - değiştirilecek kişi alınır
personne = service.getOne(id);
request.setAttribute("id", personne.getId());
request.setAttribute("version", personne.getVersion());
} else {
// ekleme - boş bir kişi oluşturulur
personne = new Personne();
request.setAttribute("id", -1);
request.setAttribute("version", -1);
}
// [Personne] nesnesini kullanıcının oturumuna eklenir
request.getSession().setAttribute("personne", personne);
// ve [edit] görünüm şablonuna
request.setAttribute("erreurEdit", "");
request.setAttribute("prenom", personne.getPrenom());
request.setAttribute("nom", personne.getNom());
Date dateNaissance = personne.getDatenaissance();
if (dateNaissance != null) {
request.setAttribute("datenaissance", new SimpleDateFormat("dd/MM/yyyy").format(dateNaissance));
} else {
request.setAttribute("datenaissance", "");
}
request.setAttribute("marie", personne.isMarie());
request.setAttribute("nbenfants", personne.getNbenfants());
// [edit] görünümünün görüntülenmesi
getServletContext().getRequestDispatcher((String) params.get("urlEdit")).forward(request, response);
}
- GET, [/do/edit?id=XX] türünde bir URL’yi hedefler. 4. satırda, [id] değerini alıyoruz. Ardından iki durum söz konusudur:
- id, -1'den farklıysa, bu bir düzenleme işlemidir ve düzenlenecek kişinin bilgileriyle önceden doldurulmuş bir form görüntülenmelidir. 9. satırda, bu kişi [service] katmanından istenir.
- id değeri -1'e eşittir. Bu durumda bir ekleme söz konusudur ve boş bir form görüntülenmelidir. Bunun için 14. satırda boş bir kişi oluşturulur.
- Her iki durumda da, 3.4.3.2 numaralı paragrafta açıklanan [edit.jsp] sayfa şablonundaki [id, version] öğeleri başlatılır.
- Elde edilen [Personne] nesnesi, [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 19-31. satırlarda başlatılır. Şablonda bulunmadıkları takdirde, JSTL kütüphanesinin bunların değeri olarak boş bir dize göstereceği bilinmektedir. [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 33. satırına geçer ve bu satır [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:
<form method="post" action="<c:url value="/do/validate"/>">
...
<input type="text" value="${nom}" name="nom" size="20">
...
<input type="text" value="${datenaissance}" name="datenaissance">
...
<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>
...
<input type="text" value="${nbenfants}" name="nbenfants">
...
<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>
POST isteği, [prenom, nom, datenaissance, marie, nbenfants, id] 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:
// kişi düzenleme / ekleme onayı
public void doValidatePersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// gönderilen öğeleri alıyoruz
boolean formulaireErroné = false;
boolean erreur;
// ad
String prenom = request.getParameter("prenom").trim();
// ad geçerli mi?
if (prenom.length() == 0) {
// hatayı not ediyoruz
request.setAttribute("erreurPrenom", "Le prénom est obligatoire");
formulaireErroné = true;
}
// soyadı
String nom = request.getParameter("nom").trim();
// İsim geçerli mi?
if (nom.length() == 0) {
// hata kaydediliyor
request.setAttribute("erreurNom", "Le nom est obligatoire");
formulaireErroné = true;
}
// doğum tarihi
Date datenaissance = null;
try {
datenaissance = new SimpleDateFormat("dd/MM/yyyy").parse(request.getParameter("datenaissance").trim());
} catch (ParseException e) {
// hata kaydedildi
request.setAttribute("erreurDateNaissance", "Date incorrecte");
formulaireErroné = true;
}
// medeni durum
boolean marie = Boolean.parseBoolean(request.getParameter("marie").trim());
// çocuk sayısı
int nbenfants = 0;
erreur = false;
try {
nbenfants = Integer.parseInt(request.getParameter("nbenfants").trim());
if (nbenfants < 0) {
erreur = true;
}
} catch (NumberFormatException ex) {
// hatayı not edin
erreur = true;
}
// çocuk sayısı yanlış mı?
if (erreur) {
// hata bildiriliyor
request.setAttribute("erreurNbEnfants", "Nombre d'enfants incorrect");
formulaireErroné = true;
}
// kişinin kimlik numarası
int id = Integer.parseInt(request.getParameter("id"));
// Formda hata mı var?
if (formulaireErroné) {
// Form, hata mesajlarıyla birlikte yeniden görüntüleniyor
showFormulaire(request, response, "");
// işlem tamamlandı
return;
}
// form doğru - oturumda bulunan kişinin bilgileri güncelleniyor
// müşteri tarafından gönderilen bilgilerle
Personne personne = (Personne)request.getSession().getAttribute("personne");
personne.setDatenaissance(datenaissance);
personne.setMarie(marie);
personne.setNbenfants(nbenfants);
personne.setNom(nom);
personne.setPrenom(prenom);
// kalıcılık
try {
if (id == -1) {
// oluşturma
service.saveOne(personne);
} else {
// güncelleme
service.updateOne(personne);
}
} catch (DaoException ex) {
// meydana gelen hata mesajıyla birlikte form yeniden görüntülenir
showFormulaire(request, response, ex.getMessage());
// bitti
return;
}
// kişi listesine yönlendirme
response.sendRedirect("list");
}
// önceden doldurulmuş form görüntülenir
private void showFormulaire(HttpServletRequest request, HttpServletResponse response, String erreurEdit) throws ServletException, IOException {
// [edit] görünüm şablonu hazırlanır
request.setAttribute("erreurEdit", erreurEdit);
request.setAttribute("id", request.getParameter("id"));
request.setAttribute("version", request.getParameter("version"));
request.setAttribute("prenom", request.getParameter("prenom").trim());
request.setAttribute("nom", request.getParameter("nom").trim());
request.setAttribute("datenaissance", request.getParameter("datenaissance").trim());
request.setAttribute("marie", request.getParameter("marie"));
request.setAttribute("nbenfants", request.getParameter("nbenfants").trim());
// [edit] görünümünün görüntülenmesi
getServletContext().getRequestDispatcher((String) params.get("urlEdit")).forward(request, response);
}
- 7-13. 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.
- 15-21. satırlar: [nom] parametresi için de benzer şekilde işlem yapılır
- 23-30. satırlar: [datenaissance] parametresi için de benzer şekilde işlem yapılır
- 32. 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 /.../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, 32. 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-50. satırlar: [nbenfants] parametresini alır ve değerini kontrol ederiz.
- 52. satır: [id] parametresini, değerini kontrol etmeden alınır
- 54-59. satırlar: Form hatalıysa, daha önce oluşturulan hata mesajlarıyla birlikte yeniden görüntülenir
- 62-67. satırlar: Form geçerliyse, form öğeleriyle yeni bir [Personne] nesnesi oluşturulur
- 69-82. satırlar: 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.
- 84. satır: İstisna oluşmamışsa, müşteriyi grubun yeni durumunu göstermek üzere [/do/list] URL’sine yönlendiririz.
- 79. 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 88-97), girilen değerlerle [edit.jsp] sayfası için gerekli şablonu oluşturur (request.getParameter(" ... ")). Hata mesajlarının daha önce [doValidatePersonne] yöntemi tarafından şablona yerleştirildiğini hatırlayalım. [edit.jsp] sayfası 99. satırda görüntülenir.
3.4.4. Web uygulamasının testleri
3.4.1 numaralı paragrafta 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/spring-jpa-hibernate-personnes-crud/do/list] URL’sini talep eder:

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

U1 kullanıcısı, [p2] 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 [Retour à la liste] bağlantısını kullanarak kişi listesine geri döner:

U1 tarafından değiştirilen haliyle (evli, 2 çocuklu) [Lemarchand] adlı kişiyi bulur. p2'nin sürüm numarası değişti. Artık U2, [p2]'i siliyor:
![]() |
U1 hâlâ kendi listesine sahiptir ve [p2]'i yeniden değiştirmek istemektedir:
![]() |
U1, ne olduğunu görmek için [Retour à la liste] bağlantısını kullanır:

Gerçekten de [p2]'in artık listede olmadığını fark eder...
3.4.5. Sürüm 2
Önceki sürümü, [service, dao, jpa] katmanlarının kaynak kodlarını değil, arşivlerini kullanmak üzere biraz değiştiriyoruz:
![]() |
- [1]: yeni Eclipse projesi. [service, dao, entites] paketlerinin ortadan kalktığına dikkat edilmelidir. Bunlar, [service-dao-jpa-personne.jar] ve [2] arşivlerine kapsüllenmiş ve [WEB-INF/lib] içine yerleştirilmiştir.
- Proje dosyası [4]'tedir. Bunu içe aktaracağız.
Yapılacak başka bir şey yok. Yeni web uygulaması başlatıldığında ve kişi listesi istendiğinde, şu yanıt alınır:
![]() |
Hibernate, [Personne] varlığını bulamıyor. Bu sorunu çözmek için, [persistence.xml] içinde yönetilen varlıkları açıkça tanımlamak gerekiyor:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
<class>entites.Personne</class>
</persistence-unit>
</persistence>
- 7. satır: Personne varlığı tanımlanmıştır.
Bu yapıldıktan sonra istisna ortadan kalkar:
![]() |
3.4.6. JPA uygulamasını değiştirin
![]() |
- [1] olarak değiştirin: yeni Eclipse projesi
- [2]: Toplink kütüphaneleri, Hibernate kütüphanelerinin yerini aldı
- proje klasörü [4]'tedir. Bunu içe aktaracağız.
JPA uygulamasını değiştirmek, yalnızca [spring-config.xml] dosyasında birkaç değişiklik gerektirir. Başka hiçbir şey değişmez. [spring-config.xml] dosyasında yapılan değişiklikler 3.1.9 numaralı paragrafta açıklanmıştır:
<?xml version="1.0" encoding="UTF-8"?>
<!-- JVM, -javaagent:C:\data\2006-2007\eclipse\dvp-jpa\lib\spring\spring-agent.jar
(à remplacer par le chemin exact de spring-agent.jar)-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
...
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
...
<property name="databasePlatform" value="oracle.toplink.essentials.platform.database.MySQL4Platform" />
...
</bean>
...
</beans>
Hibernate'ten Toplink'e geçmek için birkaç satırın değiştirilmesi gerekiyor:
- 11. satır: JPA uygulaması artık Toplink tarafından gerçekleştirilmektedir
- 13. satır: [databasePlatform] özelliğinin değeri, Hibernate'tekinden farklıdır: Toplink'e özgü bir sınıf adıdır. Bu adın nerede bulunacağı, 2.1.15.2. paragrafında açıklanmıştır.
Hepsi bu kadar. Spring ile SGBD veya JPA uygulamasını ne kadar kolay değiştirebileceğimize dikkat edin. Yine de henüz tam olarak bitmiş sayılmayız. Uygulamayı çalıştırdığımızda bir istisna ile karşılaşıyoruz:
![]() |
Bu, 3.1.9. paragrafında anlatılan ve daha önce karşılaştığımız bir sorundur. Bu sorun, JVM'i bir Spring ajanı ile çalıştırarak çözülür. Bunun için Tomcat'in başlatma yapılandırmasını şu şekilde değiştiririz:
![]() |
- [1] olarak değiştirilir: Tomcat yapılandırmasını değiştirmek için [Run / Run...] seçeneği seçilmiştir
- [2] olarak: [Arguments] sekmesini seçtik
- [3]'te: 3.1.9. paragrafında açıklandığı şekilde -javaagent parametresini ekledik.
Bu işlem tamamlandıktan sonra, kişi listesini isteyebiliriz:

3.5. Diğer örnekler
Paragraf 3.2'de incelenen Jboss Ejb3 konteynerinin Spring konteynerinin yerine kullanıldığı bir web örneği göstermek isterdik:
![]() |
- [1]: Eclipse projesi
- [3]: örnekler klasöründeki konumu. Bunu içe aktaracağız.
3.2. paragrafta açıklanan [jboss-config.xml, persistence.xml] yapılandırmasını kullandık, ardından [Application.java] denetleyicisinin [init] yöntemini şu şekilde değiştirdik:
// init
@SuppressWarnings("unchecked")
public void init() throws ServletException {
try {
// servletin başlatma parametreleri alınır
ServletConfig config = getServletConfig();
// diğer başlatma parametreleri işlenir
String valeur = null;
for (int i = 0; i < paramètres.length; i++) {
// parametre değeri
valeur = config.getInitParameter(paramètres[i]);
// parametre mevcut mu?
if (valeur == null) {
// hata kaydedilir
erreursInitialisation.add("Le paramètre [" + paramètres[i] + "] n'a pas été initialisé");
} else {
// parametrenin değeri kaydedilir
params.put(paramètres[i], valeur);
}
}
// [erreurs] görünümünün URL'si özel bir işleme tabi tutulur
urlErreurs = config.getInitParameter("urlErreurs");
if (urlErreurs == null)
throw new ServletException("Le paramètre [urlErreurs] n'a pas été initialisé");
// uygulama yapılandırması
// EJB3 JBoss konteyneri başlatılır
// ejb3-interceptors-aop.xml ve embedded-jboss-beans.xml yapılandırma dosyaları kullanılıyor
EJB3StandaloneBootstrap.boot(null);
// Uygulamaya özgü bean'lerin oluşturulması
EJB3StandaloneBootstrap.deployXmlResource("META-INF/jboss-config.xml");
// Uygulamanın sınıf yolunda bulunan tüm EJB dosyaları dağıtılıyor
//EJB3StandaloneBootstrap.scanClasspath("WEB-INF/classes".replace("/", File.separator));
EJB3StandaloneBootstrap.scanClasspath();
// JNDI bağlamı başlatılıyor. jndi.properties dosyası kullanılıyor
InitialContext initialContext = new InitialContext();
// hizmet katmanının örneği oluşturuluyor
service = (IService) initialContext.lookup("Service/local");
// veritabanı boşaltılıyor
clean();
// veritabanı dolduruluyor
fill();
} catch (Exception e) {
throw new ServletException(e);
}
}
- 28-38. satırlar: Ejb3 konteyneri başlatılır. Bu konteyner, Spring konteynerinin yerini alır.
- 41. satır: Uygulamanın [service] katmanından bir referans istenir.
İlk bakışta yapılması gereken değişiklikler bunlarla sınırlıdır. Çalıştırma sırasında şu hata ile karşılaşılır:
![]() |
Sorunun tam olarak nerede olduğunu anlayamadım. Tomcat tarafından bildirilen istisna, "TransactionManager" adlı nesnenin JNDI hizmetinden talep edildiğini ve bu hizmetin söz konusu nesneyi tanımadığını gösteriyor gibi görünüyor. Bu soruna bir çözüm bulmayı okuyuculara bırakıyorum. Bir çözüm bulunursa, bu belgeye eklenecektir.





























































