Skip to content

19. 3 katmanlı mimaride MVC web uygulaması – Örnek 5, MySQL

19.1. MySQL veritabanı

Bu sürümde, kişi listesini MySQL 4.x veritabanı tablosuna kaydedeceğiz. [http://www.easyphp.org] adresinde bulunan [Apache – MySQL – PHP] paketini kullandık. Aşağıdaki ekran görüntüleri, EMS, MySQL ve [http://www.sqlmanager.net/fr/products/mysql/manager] istemcilerinden ve SGBD ve MySQL'in ücretsiz yönetim istemcisidir.

Veritabanının adı [dbpersonnes]'tir. Bu veritabanında [PERSONNES] adlı bir tablo bulunmaktadır:

Image

[PERSONNES] tablosu, web uygulaması tarafından yönetilen kişilerin listesini içerecektir. Bu tablo, aşağıdaki SQL komutlarıyla oluşturulmuştur:

CREATE TABLE `personnes` (
  `ID` int(11) NOT NULL auto_increment,
  `VERSION` int(11) NOT NULL default '0',
  `NOM` varchar(30) NOT NULL default '',
  `PRENOM` varchar(30) NOT NULL default '',
  `DATENAISSANCE` date NOT NULL default '0000-00-00',
  `MARIE` tinyint(4) NOT NULL default '0',
  `NBENFANTS` int(11) NOT NULL default '0',
  PRIMARY KEY  (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

MySQL 4.x, önceki iki SGBD komutuna kıyasla daha yetersiz görünüyor. Tabloya herhangi bir kısıtlama (kontrol) ekleyemedim.

  • 10. satır: Tablo, işlemleri desteklemeyen [MyISAM] türü değil, [InnoDB] türünde olmalıdır.
  • 2. satır: Birincil anahtar, auto_increment türündedir. Tablonun ID sütununa değer girilmeden bir satır eklenirse, MySQL bu sütun için otomatik olarak bir tamsayı üretecektir. Bu sayede birincil anahtarları kendimiz oluşturmamız gerekmeyecektir.

[PERSONNES] tablosu aşağıdaki içeriğe sahip olabilir:

Image

[Personne] nesnesini [dao] katmanımız aracılığıyla eklerken, bu nesnenin [id] alanının ekleme öncesinde -1'e eşit olduğunu ve sonrasında -1'den farklı bir değere sahip olduğunu biliyoruz; bu değer, [PERSONNES] tablosuna eklenen yeni satıra atanan birincil anahtardır. Bir örnek üzerinden bu değeri nasıl öğrenebileceğimize bakalım.

SQL siparişi

SELECT LAST_INSERT_ID()

komutu, tablonun ID alanına en son eklenen değeri öğrenmemizi sağlar. Bu komut, ekleme işleminden sonra çalıştırılmalıdır. Bu, ekleme işleminden önce eklenen kişinin birincil anahtar değerini sorgulayan SGBD, [Firebird] ve [Postgres] komutlarından farklıdır. Bunu, veritabanında oluşturulan SQL komutlarını bir araya getiren [personnes-mysql.xml] dosyasında kullanacağız.

19.2. [dao] ve [service] katmanlarının Eclipse projesi

MySQL veritabanını kullanan uygulamamızın [dao] ve [service] katmanlarını geliştirmek için aşağıdaki [mvc-personnes-05] Eclipse projesini kullanacağız:

Image

Bu proje, bir Tomcat web projesi değil, basit bir Java projesidir.


[src] klasörü


Bu klasör, [dao] ve [service] katmanlarının kaynak kodlarını ve bu iki katmanın yapılandırma dosyalarını içerir:

Image

Adında [mysql] geçen tüm dosyalar, Firebird ve Postgres sürümlerine göre değiştirilmiş olabilir veya olmayabilir. Aşağıda, değiştirilen dosyaları açıklıyoruz.


[database] klasörü


Bu klasör, kişilere ait MySQL veritabanını oluşturan komut dosyasını içerir:

Image

# EMS MySQL Manager Lite 3.2.0.1
# ---------------------------------------
# Ana B    ilgisayar : localhost
# Bağla    ntı Noktası : 3306
# Veritabanı : dbpersonnes


SET FOREIGN_KEY_CHECKS=0;

CREATE DATABASE `dbpersonnes`
    CHARACTER SET 'latin1'
    COLLATE 'latin1_swedish_ci';

USE `dbpersonnes`;

#
# `personnes` tablosunun yapısı : 
#

CREATE TABLE `personnes` (
  `ID` int(11) NOT NULL auto_increment,
  `VERSION` int(11) NOT NULL default '0',
  `NOM` varchar(30) NOT NULL default '',
  `PRENOM` varchar(30) NOT NULL default '',
  `DATENAISSANCE` date NOT NULL default '0000-00-00',
  `MARIE` tinyint(4) NOT NULL default '0',
  `NBENFANTS` int(11) NOT NULL default '0',
  PRIMARY KEY  (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

#
# `personnes` tablosuna ait veril er (LIMIT 0,500)
#

INSERT INTO `personnes` (`ID`, `VERSION`, `NOM`, `PRENOM`, `DATENAISSANCE`, `MARIE`, `NBENFANTS`) VALUES 
  (1,1,'Major','Joachim','1984-01-13',1,2),
  (2,1,'Humbort','Mélanie','1985-01-12',0,1),
  (3,1,'Lemarchand','Charles','1986-01-01',0,0);

COMMIT;

[lib] klasörü


Bu klasör, uygulama için gerekli dosyaları içerir:

SGBD MySQL jdbc sürücüsünün de mevcut olduğu unutulmamalıdır. Tüm bu dosyalar, Eclipse projesinin Classpath klasörünün bir parçasıdır.

19.3. [dao] katmanı

[dao] katmanı şu şekildedir:

Image

[Firebird] sürümüne göre sadece değişenleri sunuyoruz.

[personne-mysql.xml] eşleme dosyası şu şekildedir:


<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE sqlMap
    PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
    "http://www.ibatis.com/dtd/sql-map-2.dtd">

<sqlMap>
    <!-- [Personne] sınıf takma adı -->
    <typeAlias alias="Personne.classe" 
        type="istia.st.mvc.personnes.entites.Personne"/>
    <!-- eşleme tablosu [PERSONNES] - nesne [Personne] -->
    <resultMap id="Personne.map" 
        class="istia.st.mvc.personnes.entites.Personne">
        <result property="id" column="ID" />
        <result property="version" column="VERSION" />
        <result property="nom" column="NOM"/>
        <result property="prenom" column="PRENOM"/>
        <result property="dateNaissance" column="DATENAISSANCE"/>
        <result property="marie" column="MARIE"/>
        <result property="nbEnfants" column="NBENFANTS"/>
    </resultMap>
    <!-- tüm kişilerin listesi -->
    <select id="Personne.getAll" resultMap="Personne.map" > select ID, VERSION, NOM, 
        PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES</select>
    <!-- belirli bir kişiyi bulma -->
        <select id="Personne.getOne" resultMap="Personne.map" >select ID, VERSION, NOM, 
        PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES WHERE ID=#değer#</select>
    <!-- bir kişi ekle -->
    <insert id="Personne.insertOne" parameterClass="Personne.classe">
        insert into 
        PERSONNES(VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS) 
        VALUES(#sürüm#, #soyadı#, #adı#, #dateNaissance#, #evli#, 
         #nbEnfants#) 
        <selectKey keyProperty="id">
            select LAST_INSERT_ID() as value
        </selectKey>         
    </insert>
    <!-- bir kişiyi güncelle -->
    <update id="Personne.updateOne" parameterClass="Personne.classe"> update 
        PERSONNES set VERSION=#sürüm#+1, NOM=#soyadı#, PRENOM=#adı#, DATENAISSANCE=#dateNaissance#, 
        MARIE=#eş#, NBENFANTS=#nbEnfants# WHERE ID=#id# ve 
        VERSION=#sürüm#</update>
    <!-- bir kişiyi sil -->
    <delete id="Personne.deleteOne" parameterClass="int"> delete FROM PERSONNES WHERE 
        ID=#değer# </sil>
    <!-- en son eklenen kişinin [id] birincil anahtar değerini al -->
    <select id="Personne.getNextId" resultClass="int">select 
        LAST_INSERT_ID()</select>
</sqlMap>

Bu dosya, aşağıdaki ayrıntılar dışında [personnes-firebird.xml] ile aynı içeriğe sahiptir:

  • SQL " Personne.insertOne " komutunda 29-37. satırlar değişmiştir:
  • SQL ekleme komutu, eklenen satırın birincil anahtar değerini alabilmeyi sağlayacak olan SELECT komutundan önce yürütülür
  • SQL ekleme komutunda, [PERSONNES] tablosunun ID sütunu için bir değer bulunmamaktadır

Bu durum, 19.1. paragrafında ele aldığımız ekleme örneğini yansıtmaktadır.

Burada, eşzamanlı iş parçacıkları arasında sorunlara yol açabilecek bir durumun olabileceğine dikkat edilmelidir. Aynı anda ekleme işlemi gerçekleştiren Th1 ve Th2 adlı iki iş parçacığı olduğunu varsayalım. Toplamda dört adet SQL emri gönderilmesi gerekiyor. Bunların aşağıdaki sırayla gerçekleştirildiğini varsayalım:

  1. Th1 tarafından I1 ekleme işlemi
  2. Th2 tarafından I2 ekleme işlemi
  3. Th1 tarafından S1 seçme işlemi
  4. Th2'den S2 seçme

3. adımda, Th1 son ekleme sırasında oluşturulan birincil anahtarı alır; yani kendi anahtarını değil, Th2'nin anahtarını alır. iBATIS'in [insert] yönteminin bu durum için korunup korunmadığını bilmiyorum. Bunun düzgün bir şekilde yönetildiğini varsayalım. Aksi takdirde, [dao] katmanındaki [DaoImplCommon] uygulama sınıfını, [insertPersonne] yönteminin senkronize edileceği bir [DaoImplMySQL] sınıfına türetmemiz gerekir. Bu, sorunu yalnızca uygulamamızdaki iş parçacıkları için çözer. Yukarıdaki örnekte Th1 ve Th2, iki farklı uygulamanın iş parçacıklarıysa, sorunu hem işlemler hem de işlemler arasında uygun bir yalıtım düzeyi (isolation level) ile çözmek gerekir. İşlemlerin sıralıymış gibi yürütüldüğü [serializable] seviyesi uygun olacaktır.

Firebird ve Postgres'te bu sorunun yaşanmadığını belirtmek gerekir; bu sistemler INSERT işleminden önce SELECT işlemini gerçekleştirir. Örneğin şu komut dizisi ele alınırsa:

  1. select S1 from Th1
  2. select S2 de Th2
  3. Th1'den I1 ekleme
  4. Th2'den I2 eklenmesi

1. ve 2. adımlarda, Th1 ve Th2 aynı oluşturucudan birincil anahtar değerlerini alır. Bu işlem normalde atomiktir ve Th1 ile Th2 iki farklı değer alır. Eğer işlem atomik olmasaydı ve Th1 ile Th2 aynı iki değeri alsaydı, Th2 tarafından 4. adımda yapılan ekleme, birincil anahtar çakışması nedeniyle başarısız olurdu. Bu tamamen düzeltilebilir bir hatadır ve Th2 eklemeyi yeniden deneyebilir.

"Personne.insertOne" işlemini şu anda [personnes-mysql.xml] dosyasında olduğu gibi bırakacağız, ancak okuyucunun burada potansiyel bir sorun olabileceğinin farkında olması gerekir.

[dao] katmanının [DaoImplCommon] uygulama sınıfı, önceki iki sürümünkiyle aynıdır.

[dao] katmanının yapılandırması, SGBD ve [MySQL]'e uyarlanmıştır. Dolayısıyla, [spring-config-test-dao-mysql.xml] yapılandırma dosyası şu şekildedir:


<?xml version="1.0" encoding="ISO_8859-1"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- veri kaynağı DBCP -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 
        destroy-method="close">
        <property name="driverClassName">
            <value>com.mysql.jdbc.Driver</value>
        </property>
        <property name="url">
            <value>jdbc:mysql://localhost/dbpersonnes</value>
        </property>
        <property name="username">
            <value>root</value>
        </property>
        <property name="password">
            <value></value>
        </property>
    </bean>
    <!-- SqlMapCllient -->
    <bean id="sqlMapClient" 
        class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
        <property name="dataSource">
            <ref local="dataSource"/>
        </property>
        <property name="configLocation">
            <value>classpath:sql-map-config-mysql.xml</value>
        </property>
    </bean>
    <!-- katmana erişim sınıfı [dao] -->
    <bean id="dao" class="istia.st.mvc.personnes.dao.DaoImplCommon">
        <property name="sqlMapClient">
            <ref local="sqlMapClient"/>
        </property>
    </bean>
</beans>
  • 5-19. satırlar: [dataSource] bean'i artık, yöneticisi şifresiz [root] olan [MySQL] ve [dbpersonnes] tabanlarını işaret etmektedir. Kullanıcı, bu yapılandırmayı kendi ortamına göre değiştirecektir.
  • 31. satır: [DaoImplCommon] sınıfı, [dao] katmanının uygulama sınıfıdır.

Bu değişiklikler yapıldıktan sonra testlere geçilebilir.

19.4. [dao] ve [service] katmanlarının testleri

[dao] ve [service] katmanlarının testleri, [Firebird] sürümüyle aynıdır. Elde edilen sonuçlar şunlardır:

[DaoImplCommon] uygulamasında testlerin başarıyla geçtiği görülmektedir. SGBD ve [Firebird] sürümlerinde olduğu gibi bu sınıfı türetmemize gerek kalmayacaktır.

19.5. [web] uygulamasının testleri

SGBD ve [MySQL] ile web uygulamasını test etmek için, Firebird veritabanıyla [mvc-personnes-03B] projesini oluştururken kullandığımız yönteme benzer şekilde bir Eclipse projesi ([mvc-personnes-05B]) oluşturuyoruz (bkz. paragraf 17.7). Ancak, Postgres'te olduğu gibi, hiçbir sınıfta değişiklik yapmadığımız için [personnes-dao.jar] ve [personnes-service.jar] arşivlerini yeniden oluşturmamıza gerek yoktur.

[mvc-personnes-05B] web projesini Tomcat ortamında devreye alıyoruz:

SGBD ve MySQL başlatıldı. [PERSONNES] tablosunun içeriği şu şekildedir:

Image

Tomcat da sırayla başlatılır. Bir tarayıcı kullanarak [http://localhost:8080/mvc-personnes-05B] URL'sini çağırıyoruz:

Image

Şu bağlantıyla [Ajout] bağlantısı ile yeni bir kişi ekliyoruz:

Veritabanında eklenmiş olup olmadığını kontrol ediyoruz:

Image

Kullanıcı, [modification, suppression] ile başka testler yapmaya davet ediliyor.