19. تطبيق الويب MVC في بنية ثلاثية الطبقات – المثال 5، MySQL
19.1. قاعدة البيانات MySQL
في هذا الإصدار، سنقوم بتثبيت قائمة الأشخاص في جدول قاعدة بيانات MySQL 4.x. استخدمنا الحزمة [Apache – MySQL – PHP] المتوفرة على الرابط [http://www.easyphp.org]. فيما يلي، تأتي لقطات الشاشة من عميل EMS MySQL Manager Lite [http://www.sqlmanager.net/fr/products/mysql/manager]، وهو عميل إدارة مجاني لـ SGBD MySQL.
تسمى قاعدة البيانات [dbpersonnes]. وهي تحتوي على جدول [PERSONNES]:

ستحتوي الجدولة [PERSONNES] على قائمة بالأشخاص الذين تديرهم تطبيق الويب. وقد تم إنشاؤها باستخدام الأوامر التالية SQL:
MySQL يبدو 4.x أقل شمولاً من الأمرين السابقين SGBD. لم أتمكن من وضع قيود (فحوصات) على الجدول.
- السطر 10: يجب أن يكون نوع الجدول [InnoDB] وليس النوع [MyISAM] الذي لا يدعم المعاملات.
- السطر 2: المفتاح الأساسي من النوع auto_increment. إذا أدخلنا سطراً بدون قيمة للعمود ID في الجدول، فسيقوم MySQL تلقائياً بإنشاء رقم صحيح لهذا العمود. وهذا سيوفر علينا عناء إنشاء المفاتيح الأساسية بأنفسنا.
قد تحتوي الجدولة [PERSONNES] على المحتوى التالي:

نحن نعلم أنه عند إدراج كائن [Personne] بواسطة طبقتنا [dao]، فإن الحقل [id] لهذا الكائن يساوي -1 قبل الإدراج ويكون له قيمة مختلفة عن -1 بعد ذلك، وهذه القيمة هي المفتاح الأساسي المخصص للسطر الجديد الذي تم إدراجه في الجدول [PERSONNES]. لنرى من خلال مثال كيف يمكننا معرفة هذه القيمة.
![]() |
![]() |
الطلب SQL
معرفة آخر قيمة تم إدخالها في الحقل ID في الجدول. ويجب إصدارها بعد الإدخال. وهذا يختلف عن أوامر SGBD و [Firebird] و [Postgres] حيث كنا نطلب قيمة المفتاح الأساسي للشخص المضاف قبل الإدراج. سنستخدمها في الملف [personnes-mysql.xml] الذي يجمع الأوامر SQL الصادرة على قاعدة البيانات.
19.2. مشروع Eclipse للطبقات [dao] و [service]
لتطوير الطبقات [dao] و [service] لتطبيقنا باستخدام قاعدة البيانات MySQL، سنستخدم مشروع Eclipse [mvc-personnes-05] التالي:

المشروع هو مشروع Java بسيط، وليس مشروع ويب Tomcat.
مجلد [src]
يحتوي هذا المجلد على أكواد المصدر للطبقات [dao] و [service] بالإضافة إلى ملفات التكوين لهاتين الطبقتين:

قد تكون جميع الملفات التي تحتوي على [mysql] في اسمها قد خضعت أو لم تخضع لتعديل فيما يتعلق بإصدارات Firebird و Postgres. وفيما يلي، نصف تلك التي تم تعديلها.
المجلد [database]
يحتوي هذا المجلد على البرنامج النصي لإنشاء قاعدة بيانات MySQL للأشخاص:
![]()
المجلد [lib]
يحتوي هذا الملف على الأرشيفات اللازمة للتطبيق:
![]() |
تجدر الإشارة إلى وجود برنامج التشغيل jdbc الخاص بـ SGBD و MySQL. جميع هذه الملفات المضغوطة هي جزء من Classpath التابع لمشروع Eclipse.
19.3. الطبقة [dao]
الطبقة [dao] هي التالية:

نقدم فقط التغييرات التي طرأت مقارنة بالإصدار [Firebird].
ملف التعيين [personne-mysql.xml] هو التالي:
<?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] -->
<typeAlias alias="Personne.classe"
type="istia.st.mvc.personnes.entites.Personne"/>
<!-- جدول التعيين [PERSONNES] - الكائن [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>
<!-- قائمة بجميع الأشخاص -->
<select id="Personne.getAll" resultMap="Personne.map" > select ID, VERSION, NOM,
PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES</select>
<!-- الحصول على شخص معين -->
<select id="Personne.getOne" resultMap="Personne.map" >select ID, VERSION, NOM,
PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES WHERE ID=#القيمة#</select>
<!-- إضافة شخص -->
<insert id="Personne.insertOne" parameterClass="Personne.classe">
insert into
PERSONNES(VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS)
VALUES(#الإصدار#، #اللقب#، #الاسم الأول#، #dateNaissance#، #ماري#،
#nbEnfants#)
<selectKey keyProperty="id">
select LAST_INSERT_ID() as value
</selectKey>
</insert>
<!-- تحديث شخص -->
<update id="Personne.updateOne" parameterClass="Personne.classe"> update
PERSONNES set VERSION=#الإصدار#+1، NOM=#اللقب#، PRENOM=#الاسم الأول#، DATENAISSANCE=#dateNaissance#,
MARIE=#marie#، NBENFANTS=#nbEnfants# WHERE ID=#id# و
VERSION=#version#</update>
<!-- حذف شخص -->
<delete id="Personne.deleteOne" parameterClass="int"> delete FROM PERSONNES WHERE
ID=#value# </delete>
<!-- الحصول على قيمة المفتاح الأساسي [id] لآخر شخص تم إدراجه -->
<select id="Personne.getNextId" resultClass="int">select
LAST_INSERT_ID()</select>
</sqlMap>
وهو نفس محتوى [personnes-firebird.xml] باستثناء التفاصيل التالية:
- تغير ترتيب SQL " Personne.insertOne " في الأسطر 29-37:
- يتم تنفيذ أمر الإدراج SQL قبل الأمر SELECT الذي سيسمح باسترداد قيمة المفتاح الأساسي للسطر الذي تم إدراجه
- أمر الإدراج SQL ليس له قيمة في العمود ID من الجدول [PERSONNES]
وهذا يعكس مثال الإدراج الذي ناقشناه في الفقرة 19.1.
تجدر الإشارة إلى أنه قد يكون هناك مصدر محتمل للمشاكل بين الخيوط المتنافسة. لنفترض وجود خيطين Th1 و Th2 يقومان بإدراج في نفس الوقت. هناك ما مجموعه أربعة أوامر SQL يجب إصدارها. لنفترض أنها تتم بالترتيب التالي:
- إدراج I1 من Th1
- إدراج I2 من Th2
- اختيار S1 من Th1
- select S2 من Th2
في الخطوة 3، يسترد Th1 المفتاح الأساسي الذي تم إنشاؤه عند آخر عملية إدراج، أي مفتاح Th2 وليس مفتاحه الخاص. لا أعرف ما إذا كانت الطريقة [insert] الخاصة بـ iBATIS محمية في هذه الحالة. سنفترض أنها تتعامل معها بشكل صحيح. إذا لم يكن الأمر كذلك، فسيتعين علينا اشتقاق فئة التنفيذ [DaoImplCommon] من الطبقة [dao] إلى فئة [DaoImplMySQL] حيث يتم مزامنة الطريقة [insertPersonne]. وهذا لن يحل المشكلة إلا بالنسبة لخيوط تطبيقنا. إذا كان Th1 و Th2 أعلاه خيوطين لتطبيقين مختلفين، فسيكون من الضروري حل المشكلة باستخدام المعاملات ومستوى عزل مناسب بين المعاملات. سيكون المستوى [serializable]، الذي يتم فيه تنفيذ المعاملات كما لو كانت تُنفذ بالتسلسل، مناسبًا.
تجدر الإشارة إلى أن هذه المشكلة لا توجد مع Firebird و Postgres اللذين يقومان بتنفيذ SELECT قبل INSERT. على سبيل المثال، إذا كان لدينا التسلسل التالي:
- select S1 من Th1
- select S2 من Th2
- إدراج I1 من Th1
- إدراج I2 من Th2
في الخطوتين 1 و 2، يسترد Th1 و Th2 قيم المفتاح الأساسي من نفس المولد. هذه العملية عادةً ما تكون ذرية، وسيسترد Th1 و Th2 قيمتين مختلفتين. إذا لم تكن العملية ذرية، واسترد Th1 و Th2 قيمتين متطابقتين، فإن الإدراج الذي يتم في الخطوة 4 بواسطة Th2 سيفشل بسبب تكرار المفتاح الأساسي. هذا خطأ يمكن استرداده تمامًا ويمكن لـ Th2 إعادة محاولة الإدراج.
سنترك العملية "Personne.insertOne" كما هي حاليًا في الملف [personnes-mysql.xml]، ولكن يجب أن يدرك القارئ أن هناك مشكلة محتملة هنا.
فئة التنفيذ [DaoImplCommon] للطبقة [dao] هي نفسها الموجودة في الإصدارين السابقين.
تم تكييف تكوين الطبقة [dao] مع SGBD [MySQL]. وبالتالي، فإن ملف التكوين [spring-config-test-dao-mysql.xml] هو التالي:
<?xml version="1.0" encoding="ISO_8859-1"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- مصدر البيانات 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>
<!-- فئة الوصول إلى الطبقة [dao] -->
<bean id="dao" class="istia.st.mvc.personnes.dao.DaoImplCommon">
<property name="sqlMapClient">
<ref local="sqlMapClient"/>
</property>
</bean>
</beans>
- الأسطر 5-19: يشير bean [dataSource] الآن إلى قاعدة [MySQL] [dbpersonnes] التي يكون مسؤولها هو [root] بدون كلمة مرور. سيقوم القارئ بتعديل هذا التكوين وفقًا لبيئته الخاصة.
- السطر 31: الفئة [DaoImplCommon] هي فئة تنفيذ الطبقة [dao]
بعد إجراء هذه التعديلات، يمكننا الانتقال إلى الاختبارات.
19.4. اختبارات الطبقات [dao] و [service]
اختبارات الطبقات [dao] و [service] هي نفسها التي أجريت على الإصدار [Firebird]. النتائج التي تم الحصول عليها هي كما يلي:
![]() |
يلاحظ أن الاختبارات قد نجحت مع التنفيذ [DaoImplCommon]. لن نضطر إلى اشتقاق هذه الفئة كما كان ضروريًا مع SGBD [Firebird].
19.5. اختبارات تطبيق [web]
لاختبار تطبيق الويب باستخدام SGBD و [MySQL]، نقوم بإنشاء مشروع Eclipse [mvc-personnes-05B] بطريقة مشابهة لتلك المستخدمة لإنشاء مشروع [mvc-personnes-03B] باستخدام قاعدة بيانات Firebird (انظر الفقرة 17.7). ومع ذلك، كما هو الحال مع Postgres، لا يتعين علينا إعادة إنشاء الأرشيفات [personnes-dao.jar] و [personnes-service.jar] لأننا لم نقم بتعديل أي فئة.
نقوم بنشر مشروع الويب [mvc-personnes-05B] داخل Tomcat:
![]() | ![]() |
يتم تشغيل SGBD MySQL. ويكون محتوى الجدول [PERSONNES] كما يلي:

يتم تشغيل Tomcat بدوره. باستخدام متصفح، نطلب عنوان URL [http://localhost:8080/mvc-personnes-05B]:

نضيف شخصًا جديدًا عبر الرابط [Ajout]:
![]() | ![]() |
نتحقق من الإضافة في قاعدة البيانات:

يُطلب من القارئ إجراء اختبارات أخرى [modification, suppression].







